mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: audit logs on organization-level
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState } from "react";
|
||||
import { Control, Controller, UseFormReset } from "react-hook-form";
|
||||
import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Button, DatePicker, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
@@ -19,11 +20,13 @@ const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
presetActor?: string;
|
||||
className?: string;
|
||||
control: Control<AuditLogFilterFormData>;
|
||||
reset: UseFormReset<AuditLogFilterFormData>;
|
||||
};
|
||||
|
||||
export const LogsFilter = ({ control, reset }: Props) => {
|
||||
export const LogsFilter = ({ presetActor, className, control, reset }: Props) => {
|
||||
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
||||
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
||||
|
||||
@@ -69,8 +72,13 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sticky top-20 z-10 flex items-center justify-between bg-bunker-800">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={twMerge(
|
||||
"sticky top-20 z-10 flex items-center justify-between bg-bunker-800",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="eventType"
|
||||
@@ -79,7 +87,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
||||
label="Event"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="mr-4 w-40"
|
||||
className="w-40"
|
||||
>
|
||||
<Select
|
||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||
@@ -96,7 +104,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{!isLoading && data && data.length > 0 && (
|
||||
{!isLoading && data && data.length > 0 && !presetActor && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="actor"
|
||||
@@ -105,7 +113,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
||||
label="Actor"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="mr-4 w-40"
|
||||
className="w-40"
|
||||
>
|
||||
<Select
|
||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||
@@ -127,7 +135,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
||||
label="Source"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="mr-4 w-40"
|
||||
className="w-40"
|
||||
>
|
||||
<Select
|
||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||
@@ -149,12 +157,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
||||
control={control}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
label="Start date"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="mr-4"
|
||||
>
|
||||
<FormControl label="Start date" errorText={error?.message} isError={Boolean(error)}>
|
||||
<DatePicker
|
||||
value={field.value || undefined}
|
||||
onChange={(date) => {
|
||||
@@ -195,26 +198,25 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
isLoading={false}
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
|
||||
onClick={() =>
|
||||
reset({
|
||||
eventType: undefined,
|
||||
actor: undefined,
|
||||
userAgentType: undefined,
|
||||
startDate: undefined,
|
||||
endDate: undefined
|
||||
})
|
||||
}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
isLoading={false}
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mt-1.5"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} />}
|
||||
onClick={() =>
|
||||
reset({
|
||||
eventType: undefined,
|
||||
actor: presetActor,
|
||||
userAgentType: undefined,
|
||||
startDate: undefined,
|
||||
endDate: undefined
|
||||
})
|
||||
}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,19 @@ import { LogsFilter } from "./LogsFilter";
|
||||
import { LogsTable } from "./LogsTable";
|
||||
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
||||
|
||||
export const LogsSection = () => {
|
||||
type Props = {
|
||||
presetActor?: string;
|
||||
showFilters?: boolean;
|
||||
filterClassName?: string;
|
||||
isOrgAuditLogs?: boolean;
|
||||
};
|
||||
|
||||
export const LogsSection = ({
|
||||
presetActor,
|
||||
filterClassName,
|
||||
isOrgAuditLogs,
|
||||
showFilters
|
||||
}: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -21,6 +33,7 @@ export const LogsSection = () => {
|
||||
const { control, reset, watch } = useForm<AuditLogFilterFormData>({
|
||||
resolver: yupResolver(auditLogFilterFormSchema),
|
||||
defaultValues: {
|
||||
actor: presetActor,
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
startDate: new Date(new Date().setDate(new Date().getDate() - 1)), // day before today
|
||||
@@ -43,8 +56,16 @@ export const LogsSection = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LogsFilter control={control} reset={reset} />
|
||||
{showFilters && (
|
||||
<LogsFilter
|
||||
className={filterClassName}
|
||||
presetActor={presetActor}
|
||||
control={control}
|
||||
reset={reset}
|
||||
/>
|
||||
)}
|
||||
<LogsTable
|
||||
isOrgAuditLogs={isOrgAuditLogs}
|
||||
eventType={eventType}
|
||||
userAgentType={userAgentType}
|
||||
actor={actor}
|
||||
|
||||
@@ -25,15 +25,22 @@ type Props = {
|
||||
actor?: string;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
isOrgAuditLogs?: boolean;
|
||||
};
|
||||
|
||||
const AUDIT_LOG_LIMIT = 15;
|
||||
|
||||
export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate }: Props) => {
|
||||
export const LogsTable = ({
|
||||
eventType,
|
||||
userAgentType,
|
||||
actor,
|
||||
startDate,
|
||||
endDate,
|
||||
isOrgAuditLogs
|
||||
}: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
|
||||
currentWorkspace?.id ?? "",
|
||||
{
|
||||
eventType,
|
||||
userAgentType,
|
||||
@@ -41,7 +48,8 @@ export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate
|
||||
startDate,
|
||||
endDate,
|
||||
limit: AUDIT_LOG_LIMIT
|
||||
}
|
||||
},
|
||||
!isOrgAuditLogs ? currentWorkspace?.id ?? "" : null
|
||||
);
|
||||
|
||||
const isEmpty = !isLoading && !data?.pages?.[0].length;
|
||||
@@ -54,6 +62,7 @@ export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate
|
||||
<Tr>
|
||||
<Th>Timestamp</Th>
|
||||
<Th>Event</Th>
|
||||
{isOrgAuditLogs && <Th>Project</Th>}
|
||||
<Th>Actor</Th>
|
||||
<Th>Source</Th>
|
||||
<Th>Metadata</Th>
|
||||
@@ -64,7 +73,11 @@ export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate
|
||||
data?.pages?.map((group, i) => (
|
||||
<Fragment key={`auditlog-item-${i + 1}`}>
|
||||
{group.map((auditLog) => (
|
||||
<LogsTableRow auditLog={auditLog} key={`audit-log-${auditLog.id}`} />
|
||||
<LogsTableRow
|
||||
isOrgAuditLogs={isOrgAuditLogs}
|
||||
auditLog={auditLog}
|
||||
key={`audit-log-${auditLog.id}`}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
@@ -5,9 +5,10 @@ import { Actor, AuditLog, Event } from "@app/hooks/api/auditLogs/types";
|
||||
|
||||
type Props = {
|
||||
auditLog: AuditLog;
|
||||
isOrgAuditLogs?: boolean;
|
||||
};
|
||||
|
||||
export const LogsTableRow = ({ auditLog }: Props) => {
|
||||
export const LogsTableRow = ({ auditLog, isOrgAuditLogs }: Props) => {
|
||||
const renderActor = (actor: Actor) => {
|
||||
switch (actor.type) {
|
||||
case ActorType.USER:
|
||||
@@ -469,6 +470,7 @@ export const LogsTableRow = ({ auditLog }: Props) => {
|
||||
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
||||
<Td>{formatDate(auditLog.createdAt)}</Td>
|
||||
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
||||
{isOrgAuditLogs && <Td>{auditLog.project.name}</Td>}
|
||||
{renderActor(auditLog.actor)}
|
||||
<Td>
|
||||
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
||||
|
||||
Reference in New Issue
Block a user