From 4654a17e5f6815bc8eb1fad3781f1057bc2fa723 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 26 Mar 2025 05:54:33 +0400 Subject: [PATCH 1/7] feat(audit-logs): filter audit logs by secret key --- .../ee/services/audit-log/audit-log-dal.ts | 22 ++++++++++++++++-- .../services/audit-log/audit-log-service.ts | 1 + .../ee/services/audit-log/audit-log-types.ts | 1 + backend/src/lib/api-docs/constants.ts | 2 ++ .../server/routes/v1/organization-router.ts | 1 + frontend/src/hooks/api/auditLogs/types.tsx | 1 + .../AuditLogsPage/components/LogsFilter.tsx | 23 ++++++++++++++++--- .../AuditLogsPage/components/LogsSection.tsx | 3 +++ .../AuditLogsPage/components/types.tsx | 1 + 9 files changed, 50 insertions(+), 5 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-dal.ts b/backend/src/ee/services/audit-log/audit-log-dal.ts index 21f785835..31916ca1d 100644 --- a/backend/src/ee/services/audit-log/audit-log-dal.ts +++ b/backend/src/ee/services/audit-log/audit-log-dal.ts @@ -40,12 +40,14 @@ export const auditLogDALFactory = (db: TDbClient) => { actorId, actorType, secretPath, + secretKey, eventType, eventMetadata }: Omit & { actorId?: string; actorType?: ActorType; secretPath?: string; + secretKey?: string; eventType?: EventType[]; eventMetadata?: Record; }, @@ -90,8 +92,24 @@ export const auditLogDALFactory = (db: TDbClient) => { }); } - if (projectId && secretPath) { - void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object('secretPath', ?::text)`, [secretPath]); + if (projectId) { + if (secretPath) { + void sqlQuery.whereRaw(`"eventMetadata"->>'secretPath' = ?`, [secretPath]); + } + if (secretKey) { + void sqlQuery.whereRaw( + `( + "eventMetadata"->>'secretKey' = ? + OR + EXISTS ( + SELECT 1 + FROM jsonb_array_elements("eventMetadata"->'secrets') AS element + WHERE element->>'secretKey' = ? + ) + )`, + [secretKey, secretKey] + ); + } } // Filter by actor type diff --git a/backend/src/ee/services/audit-log/audit-log-service.ts b/backend/src/ee/services/audit-log/audit-log-service.ts index 295923ef5..0802d27e6 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -63,6 +63,7 @@ export const auditLogServiceFactory = ({ actorType: filter.actorType, eventMetadata: filter.eventMetadata, secretPath: filter.secretPath, + secretKey: filter.secretKey, ...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId }) }); diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index eef11b8a6..2d6337cc7 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -36,6 +36,7 @@ export type TListProjectAuditLogDTO = { auditLogActorId?: string; actorType?: ActorType; secretPath?: string; + secretKey?: string; eventMetadata?: Record; }; } & Omit; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index ec855168e..34fb41e00 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -843,6 +843,8 @@ export const AUDIT_LOGS = { eventType: "The type of the event to export.", secretPath: "The path of the secret to query audit logs for. Note that the projectId parameter must also be provided.", + secretKey: + "The key of the secret to query audit logs for. Note that the projectId parameter must also be provided.", userAgentType: "Choose which consuming application to export audit logs for.", eventMetadata: "Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.", diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 21723ef58..ab628972f 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -118,6 +118,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { .optional() .transform((val) => (!val ? val : removeTrailingSlash(val))) .describe(AUDIT_LOGS.EXPORT.secretPath), + secretKey: z.string().optional().describe(AUDIT_LOGS.EXPORT.secretKey), // eventType is split with , for multiple values, we need to transform it to array eventType: z diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index 8acaa215a..e69e8a45a 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -11,6 +11,7 @@ export type TGetAuditLogsFilter = { projectId?: string; actor?: string; // user ID format secretPath?: string; + secretKey?: string; startDate?: Date; endDate?: Date; limit: number; diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx index c32819cde..31db8f916 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx @@ -157,7 +157,24 @@ export const LogsFilter = ({ control={control} name="secretPath" render={({ field: { onChange, value, ...field } }) => ( - + + onChange(e.target.value)} + /> + + )} + /> + )} + + {selectedProject?.type === ProjectType.SecretManager && ( + ( + onChange(e.target.value)} /> )} @@ -289,7 +306,7 @@ export const LogsFilter = ({ control={control} render={({ field: { onChange, ...field }, fieldState: { error } }) => { return ( - + { return ( - + (secretPath!, 500); + const [debouncedSecretKey] = useDebounce(secretKey!, 500); return (
@@ -81,6 +83,7 @@ export const LogsSection = withPermission( refetchInterval={refetchInterval} filter={{ secretPath: debouncedSecretPath || undefined, + secretKey: debouncedSecretKey || undefined, eventMetadata: presets?.eventMetadata, projectId, actorType: presets?.actorType, diff --git a/frontend/src/pages/organization/AuditLogsPage/components/types.tsx b/frontend/src/pages/organization/AuditLogsPage/components/types.tsx index 17dcb80fd..ee6909b13 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/types.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/types.tsx @@ -14,6 +14,7 @@ export const auditLogFilterFormSchema = z actor: z.string().optional(), userAgentType: z.nativeEnum(UserAgentType), secretPath: z.string().optional(), + secretKey: z.string().optional(), startDate: z.date().optional(), endDate: z.date().optional(), page: z.coerce.number().optional(), From fb0b6b00ddd9be054274f07f71d450461a855d3b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 27 Mar 2025 03:51:16 +0400 Subject: [PATCH 2/7] fix: added suggested changes --- .../AuditLogsPage/components/LogsFilter.tsx | 363 +++++++++--------- 1 file changed, 187 insertions(+), 176 deletions(-) diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx index 31db8f916..af813ec23 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-nested-ternary */ import { useState } from "react"; import { Control, Controller, UseFormReset, UseFormSetValue, UseFormWatch } from "react-hook-form"; import { faCaretDown, faCheckCircle, faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; @@ -116,12 +115,7 @@ export const LogsFilter = ({ const selectedProject = watch("project"); return ( -
+
{isOrgAuditLogs && workspacesInOrg.length > 0 && ( ( - + ( - - onChange(e.target.value)} /> + + onChange(e.target.value)} + /> )} /> )}
-
- ( - - - -
- {selectedEventTypes?.length === 1 - ? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0]) - ?.label - : selectedEventTypes?.length === 0 - ? "All events" - : `${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} - - ); - }) - ) : ( -
- )} -
- - - - )} - /> - - {!isPending && data && data.length > 0 && !presets?.actorId && ( +
+
( + name="eventType" + render={({ field }) => ( + + + +
+ {selectedEventTypes?.length === 1 + ? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0]) + ?.label + : selectedEventTypes?.length === 0 + ? "All events" + : `${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} + + ); + }) + ) : ( +
+ )} +
+ + + + )} + /> + + {!isPending && data && data.length > 0 && !presets?.actorId && ( + ( + + + + )} + /> + )} + ( )} /> - )} - ( - - - - )} - /> - { - return ( - - - - ); - }} - /> - { - return ( - - - - ); - }} - /> - + { + return ( + + + + ); + }} + /> + { + return ( + + + + ); + }} + /> + +
); From 53c015988df98b8784aab7cf3073d8fb6ed410d2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 27 Mar 2025 09:41:46 +0400 Subject: [PATCH 3/7] feat(audit-logs): filtering revamp --- .../ee/services/audit-log/audit-log-dal.ts | 12 +- .../services/audit-log/audit-log-service.ts | 1 + .../ee/services/audit-log/audit-log-types.ts | 10 + backend/src/lib/api-docs/constants.ts | 2 + .../server/routes/v1/organization-router.ts | 1 + .../src/hooks/api/auditLogs/constants.tsx | 11 + frontend/src/hooks/api/auditLogs/enums.tsx | 4 +- frontend/src/hooks/api/auditLogs/types.tsx | 1 + .../AuditLogsPage/AuditLogsPage.tsx | 2 +- .../components/LogFilterItem.tsx | 32 + .../AuditLogsPage/components/LogsFilter.tsx | 812 +++++++++++------- .../AuditLogsPage/components/LogsSection.tsx | 75 +- .../AuditLogsPage/components/LogsTable.tsx | 6 +- .../AuditLogsPage/components/types.tsx | 12 +- .../UserAuditLogsSection.tsx | 23 - .../IntegrationAuditLogsSection.tsx | 1 - .../components/SecretSyncAuditLogsSection.tsx | 1 - 17 files changed, 615 insertions(+), 391 deletions(-) create mode 100644 frontend/src/pages/organization/AuditLogsPage/components/LogFilterItem.tsx diff --git a/backend/src/ee/services/audit-log/audit-log-dal.ts b/backend/src/ee/services/audit-log/audit-log-dal.ts index 31916ca1d..053ff2d46 100644 --- a/backend/src/ee/services/audit-log/audit-log-dal.ts +++ b/backend/src/ee/services/audit-log/audit-log-dal.ts @@ -9,13 +9,14 @@ import { logger } from "@app/lib/logger"; import { QueueName } from "@app/queue"; import { ActorType } from "@app/services/auth/auth-type"; -import { EventType } from "./audit-log-types"; +import { EventType, filterableSecretEvents } from "./audit-log-types"; export type TAuditLogDALFactory = ReturnType; type TFindQuery = { actor?: string; projectId?: string; + environment?: string; orgId?: string; eventType?: string; startDate?: string; @@ -32,6 +33,7 @@ export const auditLogDALFactory = (db: TDbClient) => { { orgId, projectId, + environment, userAgentType, startDate, endDate, @@ -92,7 +94,13 @@ export const auditLogDALFactory = (db: TDbClient) => { }); } - if (projectId) { + const eventIsSecretType = !eventType?.length || eventType.some((event) => filterableSecretEvents.includes(event)); + // We only want to filter for environment/secretPath/secretKey if the user is either checking for all event types + if (projectId && eventIsSecretType) { + if (environment) { + void sqlQuery.whereRaw(`"eventMetadata"->>'environment' = ?`, [environment]); + } + if (secretPath) { void sqlQuery.whereRaw(`"eventMetadata"->>'secretPath' = ?`, [secretPath]); } diff --git a/backend/src/ee/services/audit-log/audit-log-service.ts b/backend/src/ee/services/audit-log/audit-log-service.ts index 0802d27e6..ce6689fe9 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -64,6 +64,7 @@ export const auditLogServiceFactory = ({ eventMetadata: filter.eventMetadata, secretPath: filter.secretPath, secretKey: filter.secretKey, + environment: filter.environment, ...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId }) }); diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 2d6337cc7..b9a76dbb5 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -287,6 +287,16 @@ export enum EventType { KMIP_OPERATION_REGISTER = "kmip-operation-register" } +export const filterableSecretEvents: EventType[] = [ + EventType.GET_SECRET, + EventType.DELETE_SECRETS, + EventType.CREATE_SECRETS, + EventType.UPDATE_SECRETS, + EventType.CREATE_SECRET, + EventType.UPDATE_SECRET, + EventType.DELETE_SECRET +]; + interface UserActorMetadata { userId: string; email?: string | null; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 34fb41e00..cafbe9269 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -840,6 +840,8 @@ export const AUDIT_LOGS = { EXPORT: { projectId: "Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.", + environment: + "The environment to filter logs by. If not provided, logs from all environments will be returned. Note that the projectId parameter must also be provided.", eventType: "The type of the event to export.", secretPath: "The path of the secret to query audit logs for. Note that the projectId parameter must also be provided.", diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index ab628972f..64a5125db 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -112,6 +112,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { description: "Get all audit logs for an organization", querystring: z.object({ projectId: z.string().optional().describe(AUDIT_LOGS.EXPORT.projectId), + environment: z.string().optional().describe(AUDIT_LOGS.EXPORT.environment), actorType: z.nativeEnum(ActorType).optional(), secretPath: z .string() diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index d348c7bb1..f4366a6eb 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -1,5 +1,16 @@ import { EventType, UserAgentType } from "./enums"; +export const secretEvents: EventType[] = [ + EventType.GET_SECRETS, + EventType.GET_SECRET, + EventType.DELETE_SECRETS, + EventType.CREATE_SECRETS, + EventType.UPDATE_SECRETS, + EventType.CREATE_SECRET, + EventType.UPDATE_SECRET, + EventType.DELETE_SECRET +]; + export const eventToNameMap: { [K in EventType]: string } = { [EventType.GET_SECRETS]: "List secrets", [EventType.GET_SECRET]: "Read secret", diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index 76bb283dc..1e010b611 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -12,8 +12,8 @@ export enum UserAgentType { CLI = "cli", K8_OPERATOR = "k8-operator", TERRAFORM = "terraform", - NODE_SDK = "node-sdk", - PYTHON_SDK = "python-sdk", + NODE_SDK = "InfisicalNodeSDK", + PYTHON_SDK = "InfisicalPythonSDK", OTHER = "other" } diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index e69e8a45a..a18974f2e 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -9,6 +9,7 @@ export type TGetAuditLogsFilter = { eventMetadata?: Record; actorType?: ActorType; projectId?: string; + environment?: string; actor?: string; // user ID format secretPath?: string; secretKey?: string; diff --git a/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx index e0d2fba73..bec3bfd77 100644 --- a/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx @@ -18,7 +18,7 @@ export const AuditLogsPage = () => { title="Audit logs" description="Audit logs for security and compliance teams to monitor information access." /> - +
diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogFilterItem.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogFilterItem.tsx new file mode 100644 index 000000000..38df23aad --- /dev/null +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogFilterItem.tsx @@ -0,0 +1,32 @@ +import { twMerge } from "tailwind-merge"; + +import { Button, Tooltip } from "@app/components/v2"; + +type Props = { + hoverTooltip?: string; + className?: string; + label: string; + onClear: () => void; + children: React.ReactNode; +}; + +export const LogFilterItem = ({ label, onClear, hoverTooltip, children, className }: Props) => { + return ( + +
+
+

{label}

+ +
+ {children} +
+
+ ); +}; diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx index af813ec23..59440c814 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx @@ -1,10 +1,26 @@ -import { useState } from "react"; -import { Control, Controller, UseFormReset, UseFormSetValue, UseFormWatch } from "react-hook-form"; -import { faCaretDown, faCheckCircle, faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; +/* eslint-disable no-nested-ternary */ +import { useMemo, useState } from "react"; +import { + Control, + Controller, + UseFormGetFieldState, + UseFormReset, + UseFormResetField, + UseFormSetValue, + UseFormWatch +} from "react-hook-form"; +import { + faArrowRight, + faCaretDown, + faCheckCircle, + faFilterCircleXmark +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AnimatePresence, motion } from "framer-motion"; import { twMerge } from "tailwind-merge"; import { + Badge, Button, DatePicker, DropdownMenu, @@ -18,13 +34,17 @@ import { SelectItem } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { useGetAuditLogActorFilterOpts, useGetUserWorkspaces } from "@app/hooks/api"; -import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants"; -import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums"; -import { Actor } from "@app/hooks/api/auditLogs/types"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { useGetUserWorkspaces } from "@app/hooks/api"; +import { + eventToNameMap, + secretEvents, + userAgentTTypeoNameMap +} from "@app/hooks/api/auditLogs/constants"; +import { EventType } from "@app/hooks/api/auditLogs/enums"; +import { UserAgentType } from "@app/hooks/api/auth/types"; -import { AuditLogFilterFormData } from "./types"; +import { LogFilterItem } from "./LogFilterItem"; +import { AuditLogFilterFormData, Presets } from "./types"; const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value })); const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({ @@ -33,26 +53,70 @@ const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label })); type Props = { - presets?: { - actorId?: string; - eventType?: EventType[]; - }; - className?: string; - isOrgAuditLogs?: boolean; - setValue: UseFormSetValue; + presets?: Presets; control: Control; reset: UseFormReset; + resetField: UseFormResetField; watch: UseFormWatch; + getFieldState: UseFormGetFieldState; + setValue: UseFormSetValue; +}; + +const getActiveFilterCount = ( + getFieldState: UseFormGetFieldState, + watch: UseFormWatch +) => { + const fields = [ + "actor", + "project", + "eventType", + "startDate", + "endDate", + "environment", + "secretPath", + "userAgentType", + "secretKey" + ] as Partial[]; + + let filterCount = 0; + + // either start or end date should only be counted as one filter + let dateProcessed = false; + + fields.forEach((field) => { + const fieldState = getFieldState(field); + + if ( + field === "userAgentType" || + field === "environment" || + field === "secretKey" || + field === "secretPath" + ) { + const value = watch(field); + + if (value !== undefined && value !== "") { + filterCount += 1; + } + } else if (fieldState.isDirty && !dateProcessed) { + filterCount += 1; + + if (field === "startDate" || field === "endDate") { + dateProcessed = true; + } + } + }); + + return filterCount; }; export const LogsFilter = ({ presets, - isOrgAuditLogs, - className, control, reset, - setValue, - watch + resetField, + watch, + getFieldState, + setValue }: Props) => { const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false); const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false); @@ -62,317 +126,423 @@ export const LogsFilter = ({ const workspacesInOrg = workspaces.filter((ws) => ws.orgId === currentOrg?.id); - const { data, isPending } = useGetAuditLogActorFilterOpts(workspaces?.[0]?.id ?? ""); - - const renderActorSelectItem = (actor: Actor) => { - switch (actor.type) { - case ActorType.USER: - return ( - - {actor.metadata.email} - - ); - case ActorType.SERVICE: - return ( - - {actor.metadata.name} - - ); - case ActorType.IDENTITY: - return ( - - {actor.metadata.name} - - ); - case ActorType.KMIP_CLIENT: - return ( - - {actor.metadata.name} - - ); - default: - return ( - - N/A - - ); - } - }; - const selectedEventTypes = watch("eventType") as EventType[] | undefined; const selectedProject = watch("project"); + const showSecretsSection = + selectedEventTypes?.some( + (eventType) => secretEvents.includes(eventType) && eventType !== EventType.GET_SECRETS + ) || selectedEventTypes?.length === 0; + + const availableEnvironments = useMemo(() => { + if (!selectedProject) return []; + + return workspacesInOrg.find((ws) => ws.id === selectedProject.id)?.environments ?? []; + }, [selectedProject, workspacesInOrg]); + + const activeFilterCount = getActiveFilterCount(getFieldState, watch); + return ( -
-
- {isOrgAuditLogs && workspacesInOrg.length > 0 && ( - ( - - { - if (e === null) { - setValue("secretPath", ""); - } - onChange(e); - }} - placeholder="Select a project..." - options={workspacesInOrg.map(({ name, id, type }) => ({ name, id, type }))} - getOptionValue={(option) => option.id} - getOptionLabel={(option) => option.name} - /> - - )} - /> - )} - {selectedProject?.type === ProjectType.SecretManager && ( - ( - - onChange(e.target.value)} - /> - - )} - /> - )} - - {selectedProject?.type === ProjectType.SecretManager && ( - ( - - onChange(e.target.value)} - /> - - )} - /> - )} -
-
-
- ( - - - -
- {selectedEventTypes?.length === 1 - ? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0]) - ?.label - : selectedEventTypes?.length === 0 - ? "All events" - : `${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} - - ); - }) - ) : ( -
- )} -
- - - - )} - /> - - {!isPending && data && data.length > 0 && !presets?.actorId && ( - ( - - - - )} - /> + + + + > + ( + + onChange(e)} + placeholder="All environments" + options={availableEnvironments.map(({ name, slug }) => ({ + name, + slug + }))} + getOptionValue={(option) => option.slug} + getOptionLabel={(option) => option.name} + /> + + )} + /> + + { + setValue("secretPath", ""); + }} + > + ( + + onChange(e.target.value)} + /> + + )} + /> + + + { + setValue("secretKey", ""); + }} + > + ( + + + setValue("secretKey", e.target.value, { shouldDirty: true }) + } + /> + + )} + /> + + + )} + +
-
-
+ + ); }; diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx index 81f1ba169..1878a49e3 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx @@ -6,46 +6,40 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context"; import { withPermission } from "@app/hoc"; import { useDebounce } from "@app/hooks"; -import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; +import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; import { usePopUp } from "@app/hooks/usePopUp"; import { LogsFilter } from "./LogsFilter"; import { LogsTable } from "./LogsTable"; -import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types"; +import { AuditLogFilterFormData, auditLogFilterFormSchema, Presets } from "./types"; type Props = { - presets?: { - actorId?: string; - eventType?: EventType[]; - actorType?: ActorType; - startDate?: Date; - endDate?: Date; - eventMetadata?: Record; - }; - - showFilters?: boolean; - filterClassName?: string; + presets?: Presets; refetchInterval?: number; + showFilters?: boolean; }; export const LogsSection = withPermission( - ({ presets, filterClassName, refetchInterval, showFilters }: Props) => { + ({ presets, refetchInterval, showFilters = true }: Props) => { const { subscription } = useSubscription(); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); - const { control, reset, watch, setValue } = useForm({ - resolver: zodResolver(auditLogFilterFormSchema), - defaultValues: { - project: null, - actor: presets?.actorId, - eventType: presets?.eventType || [], - page: 1, - perPage: 10, - 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 - } - }); + const { control, reset, watch, getFieldState, resetField, setValue } = + useForm({ + resolver: zodResolver(auditLogFilterFormSchema), + defaultValues: { + project: null, + environment: undefined, + secretKey: "", + secretPath: "", + actor: presets?.actorId, + eventType: presets?.eventType || [], + userAgentType: undefined, + startDate: presets?.startDate ?? new Date(new Date().setDate(new Date().getDate() - 1)), + endDate: presets?.endDate ?? new Date(new Date(Date.now()).setHours(23, 59, 59, 999)) + } + }); useEffect(() => { if (subscription && !subscription.auditLogs) { @@ -57,6 +51,7 @@ export const LogsSection = withPermission( const userAgentType = watch("userAgentType") as UserAgentType | undefined; const actor = watch("actor"); const projectId = watch("project")?.id; + const environment = watch("environment")?.slug; const secretPath = watch("secretPath"); const secretKey = watch("secretKey"); @@ -67,18 +62,21 @@ export const LogsSection = withPermission( const [debouncedSecretKey] = useDebounce(secretKey!, 500); return ( -
- {showFilters && ( - - )} +
+
+ {showFilters && ( + + )} +
+ diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx index 69b550144..7ac42d40f 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx @@ -1,10 +1,12 @@ import { Fragment } from "react"; import { faFile, faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; import { Button, EmptyState, + Spinner, Table, TableContainer, TableSkeleton, @@ -52,7 +54,9 @@ export const LogsTable = ({ filter, refetchInterval }: Props) => { -
+ + + Timestamp void; + +export type Presets = { + actorId?: string; + eventType?: EventType[]; + actorType?: ActorType; + startDate?: Date; + endDate?: Date; + eventMetadata?: Record; +}; diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAuditLogsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAuditLogsSection.tsx index 965c0dc67..85454d48e 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAuditLogsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAuditLogsSection.tsx @@ -1,8 +1,3 @@ -import { useState } from "react"; -import { faFilter } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import { IconButton, Tooltip } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context"; import { withPermission } from "@app/hoc"; import { OrgUser } from "@app/hooks/api/types"; @@ -14,7 +9,6 @@ type Props = { export const UserAuditLogsSection = withPermission( ({ orgMembership }: Props) => { - const [showFilter, setShowFilter] = useState(false); const { subscription } = useSubscription(); // eslint-disable-next-line no-nested-ternary @@ -23,25 +17,8 @@ export const UserAuditLogsSection = withPermission(

Audit Logs

- - - setShowFilter(!showFilter)} - > -
-

Filter

- -
-
-
{ startDate: new Date(new Date().setDate(new Date().getDate() - auditLogsRetentionDays)), eventType: INTEGRATION_EVENTS }} - filterClassName="bg-mineshaft-900 static" />
) : ( diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncAuditLogsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncAuditLogsSection.tsx index cda911e1c..47b359d5e 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncAuditLogsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncAuditLogsSection.tsx @@ -41,7 +41,6 @@ export const SecretSyncAuditLogsSection = ({ secretSync }: Props) => { startDate: new Date(new Date().setDate(new Date().getDate() - auditLogsRetentionDays)), eventType: INTEGRATION_EVENTS }} - filterClassName="bg-mineshaft-900 static" /> ) : (
From 042a472f590e045362c7cb2f308479088ca0ce75 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 27 Mar 2025 10:34:52 +0400 Subject: [PATCH 4/7] fix: missing type --- backend/src/ee/services/audit-log/audit-log-types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index b9a76dbb5..d918f08e6 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -33,6 +33,7 @@ export type TListProjectAuditLogDTO = { endDate?: string; startDate?: string; projectId?: string; + environment?: string; auditLogActorId?: string; actorType?: ActorType; secretPath?: string; From 530045aaf2383881280e43469593bd7f2427b3da Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 1 Apr 2025 03:55:29 +0400 Subject: [PATCH 5/7] fix: improved query removed seq scan --- backend/src/ee/services/audit-log/audit-log-dal.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-dal.ts b/backend/src/ee/services/audit-log/audit-log-dal.ts index 053ff2d46..1b8cba462 100644 --- a/backend/src/ee/services/audit-log/audit-log-dal.ts +++ b/backend/src/ee/services/audit-log/audit-log-dal.ts @@ -106,16 +106,9 @@ export const auditLogDALFactory = (db: TDbClient) => { } if (secretKey) { void sqlQuery.whereRaw( - `( - "eventMetadata"->>'secretKey' = ? - OR - EXISTS ( - SELECT 1 - FROM jsonb_array_elements("eventMetadata"->'secrets') AS element - WHERE element->>'secretKey' = ? - ) - )`, - [secretKey, secretKey] + `("eventMetadata"->>'secretKey' = ? + OR "eventMetadata"->'secrets' @> ?::jsonb)`, + [secretKey, JSON.stringify([{ secretKey }])] ); } } From a07f168c36fb19777d3a34dd3f7d059c8d69078c Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 1 Apr 2025 03:55:49 +0400 Subject: [PATCH 6/7] fix: remove star variant and use outlined button instead --- .../pages/organization/AuditLogsPage/components/LogsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx index 7ac42d40f..1c1baccf0 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsTable.tsx @@ -98,7 +98,7 @@ export const LogsTable = ({ filter, refetchInterval }: Props) => {