diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx index a9f10875c..ea4a16f5f 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx @@ -1,14 +1,29 @@ +/* eslint-disable no-nested-ternary */ import { useState } from "react"; -import { Control, Controller, UseFormReset } from "react-hook-form"; -import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; +import { Control, Controller, UseFormReset, UseFormWatch } from "react-hook-form"; +import { + faCheckCircle, + faChevronDown, + 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 { + Button, + DatePicker, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + FormControl, + Select, + SelectItem +} from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { useGetAuditLogActorFilterOpts } from "@app/hooks/api"; import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants"; -import { ActorType } from "@app/hooks/api/auditLogs/enums"; +import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums"; import { Actor } from "@app/hooks/api/auditLogs/types"; import { AuditLogFilterFormData } from "./types"; @@ -20,13 +35,17 @@ const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label })); type Props = { - presetActor?: string; + presets?: { + actorId?: string; + eventType?: EventType[]; + }; className?: string; control: Control; reset: UseFormReset; + watch: UseFormWatch; }; -export const LogsFilter = ({ presetActor, className, control, reset }: Props) => { +export const LogsFilter = ({ presets, className, control, reset, watch }: Props) => { const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false); const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false); @@ -71,6 +90,8 @@ export const LogsFilter = ({ presetActor, className, control, reset }: Props) => } }; + const selectedEventTypes = watch("eventType") as EventType[] | undefined; + return (
( - - + render={({ field }) => ( + + + +
+ {selectedEventTypes?.length === 1 + ? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0]) + ?.label + : selectedEventTypes?.length === 0 + ? "Select event types" + : `${selectedEventTypes?.length} events selected`} + +
+
+ +
+ {eventTypes && eventTypes.length > 0 ? ( + eventTypes.map((eventType) => { + const isSelected = selectedEventTypes?.includes( + eventType.value as EventType + ); + + return ( + eventTypes.length > 1 && event.preventDefault()} + onClick={() => { + if (selectedEventTypes?.includes(eventType.value as EventType)) { + field.onChange( + selectedEventTypes?.filter((e: string) => e !== eventType.value) + ); + } else { + field.onChange([...(selectedEventTypes || []), eventType.value]); + } + }} + key={`event-type-${eventType.value}`} + icon={ + isSelected ? ( + + ) : ( +
+ ) + } + iconPos="left" + className="w-[28.4rem] text-sm" + > + {eventType.label} + + ); + }) + ) : ( +
+ )} +
+ + )} /> - {!isLoading && data && data.length > 0 && !presetActor && ( + + {!isLoading && data && data.length > 0 && !presets?.actorId && ( leftIcon={} onClick={() => reset({ - eventType: undefined, - actor: presetActor, + eventType: presets?.eventType, + actor: presets?.actorId, userAgentType: undefined, startDate: undefined, endDate: undefined diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx index aeb12468d..58091b968 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx @@ -5,24 +5,38 @@ import { yupResolver } from "@hookform/resolvers/yup"; import { UpgradePlanModal } from "@app/components/v2"; import { useSubscription } from "@app/context"; -import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; +import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; import { usePopUp } from "@app/hooks/usePopUp"; import { LogsFilter } from "./LogsFilter"; -import { LogsTable } from "./LogsTable"; +import { LogsTable, TAuditLogTableHeader } from "./LogsTable"; import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types"; type Props = { - presetActor?: string; + presets?: { + actorId?: string; + eventType?: EventType[]; + actorType?: ActorType; + startDate?: Date; + endDate?: Date; + eventMetadata?: Record; + }; + showFilters?: boolean; filterClassName?: string; isOrgAuditLogs?: boolean; + showActorColumn?: boolean; + remappedHeaders?: Partial>; + refetchInterval?: number; }; export const LogsSection = ({ - presetActor, + presets, filterClassName, + remappedHeaders, isOrgAuditLogs, + showActorColumn, + refetchInterval, showFilters }: Props) => { const { subscription } = useSubscription(); @@ -33,11 +47,12 @@ export const LogsSection = ({ const { control, reset, watch } = useForm({ resolver: yupResolver(auditLogFilterFormSchema), defaultValues: { - actor: presetActor, + actor: presets?.actorId, + eventType: presets?.eventType || [], page: 1, perPage: 10, - startDate: new Date(new Date().setDate(new Date().getDate() - 1)), // day before today - endDate: new Date(new Date(Date.now()).setHours(23, 59, 59, 999)) // end of today + startDate: presets?.startDate ?? new Date(new Date().setDate(new Date().getDate() - 1)), // day before today + endDate: presets?.endDate ?? new Date(new Date(Date.now()).setHours(23, 59, 59, 999)) // end of today } }); @@ -47,31 +62,41 @@ export const LogsSection = ({ } }, [subscription]); - const eventType = watch("eventType") as EventType | undefined; + const eventType = watch("eventType") as EventType[] | undefined; const userAgentType = watch("userAgentType") as UserAgentType | undefined; const actor = watch("actor"); const startDate = watch("startDate"); const endDate = watch("endDate"); + console.log("presets", presets); + return (
{showFilters && ( )} >; + refetchInterval?: number; }; const AUDIT_LOG_LIMIT = 15; +const TABLE_HEADERS = ["Timestamp", "Event", "Project", "Actor", "Source", "Metadata"] as const; +export type TAuditLogTableHeader = (typeof TABLE_HEADERS)[number]; + export const LogsTable = ({ - eventType, - userAgentType, showActorColumn, - actor, - startDate, - endDate, - isOrgAuditLogs + isOrgAuditLogs, + filter, + remappedHeaders, + refetchInterval }: Props) => { const { currentWorkspace } = useWorkspace(); const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs( { - eventType, - userAgentType, - actor, - startDate, - endDate, + ...filter, limit: AUDIT_LOG_LIMIT }, - !isOrgAuditLogs ? currentWorkspace?.id ?? "" : null + !isOrgAuditLogs ? currentWorkspace?.id ?? "" : null, + { + refetchInterval + } ); const isEmpty = !isLoading && !data?.pages?.[0].length; @@ -62,18 +60,24 @@ export const LogsTable = ({ - - - {isOrgAuditLogs && } - {showActorColumn && } - - + {TABLE_HEADERS.map((header, idx) => { + if ( + (header === "Project" && !isOrgAuditLogs) || + (header === "Actor" && !showActorColumn) + ) { + return null; + } + + return ( + + ); + })} {!isLoading && data?.pages?.map((group, i) => ( - + {group.map((auditLog) => ( {`Secret Request Channels: ${event.metadata.secretRequestChannels}`}

); + + case EventType.INTEGRATION_SYNCED: + return ( +
+ ); default: return + ); + } + + // Platform / automatic syncs + return ( + + ); + } + + return ( + + ); + }; + return ( {isOrgAuditLogs && } {showActorColumn && renderActor(auditLog.actor)} - + {renderSource()} {renderMetadata(auditLog.event)} );
TimestampEventProjectActorSourceMetadata{remappedHeaders?.[header] || header}
+ + +

{event.metadata.isSynced ? "Synced" : "Not synced"}

+
+
+
; } @@ -484,16 +499,41 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop return formattedDate; }; + const renderSource = () => { + const { event, actor } = auditLog; + + if (event.type === EventType.INTEGRATION_SYNCED) { + if (actor.type === ActorType.USER) { + return ( + +

Manually triggered by {actor.metadata.email}

+
+

Automatically synced by Infisical

+
+

{userAgentTTypeoNameMap[auditLog.userAgentType]}

+

{auditLog.ipAddress}

+
{formatDate(auditLog.createdAt)} {`${eventToNameMap[auditLog.event.type]}`}{auditLog.project.name} -

{userAgentTTypeoNameMap[auditLog.userAgentType]}

-

{auditLog.ipAddress}

-