diff --git a/backend/src/ee/routes/v1/project-router.ts b/backend/src/ee/routes/v1/project-router.ts index 51a04b783..fccfbd158 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -87,6 +87,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); + /* + * Daniel: This endpoint is no longer is use. + * We are keeping it for now because it has been exposed in our public api docs for a while, so by removing it we are likely to break users workflows. + * + * Please refer to the new endpoint, GET /api/v1/organization/audit-logs, for the same (and more) functionality. + */ server.route({ method: "GET", url: "/:workspaceId/audit-logs", @@ -101,7 +107,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId) + workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.projectId) }), querystring: z.object({ eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType), @@ -122,10 +128,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }) .merge( z.object({ - project: z.object({ - name: z.string(), - slug: z.string() - }), + project: z + .object({ + name: z.string(), + slug: z.string() + }) + .optional(), event: z.object({ type: z.string(), metadata: z.any() 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 ce345766b..5e5e6872b 100644 --- a/backend/src/ee/services/audit-log/audit-log-dal.ts +++ b/backend/src/ee/services/audit-log/audit-log-dal.ts @@ -3,7 +3,7 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; import { AuditLogsSchema, TableName } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; -import { ormify, selectAllTableCols, stripUndefinedInWhere } from "@app/lib/knex"; +import { ormify, selectAllTableCols } from "@app/lib/knex"; import { logger } from "@app/lib/logger"; import { QueueName } from "@app/queue"; import { ActorType } from "@app/services/auth/auth-type"; @@ -48,47 +48,61 @@ export const auditLogDALFactory = (db: TDbClient) => { }, tx?: Knex ) => { + if (!orgId && !projectId) { + throw new Error("Either orgId or projectId must be provided"); + } + try { + // Find statements const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog) - .where( - stripUndefinedInWhere({ - projectId, - [`${TableName.AuditLog}.orgId`]: orgId, - userAgentType - }) - ) - .leftJoin(TableName.Project, `${TableName.AuditLog}.projectId`, `${TableName.Project}.id`) + // eslint-disable-next-line func-names + .where(function () { + if (orgId) { + void this.where(`${TableName.Project}.orgId`, orgId).orWhere(`${TableName.AuditLog}.orgId`, orgId); + } else if (projectId) { + void this.where(`${TableName.AuditLog}.projectId`, projectId); + } + }); + if (userAgentType) { + void sqlQuery.where("userAgentType", userAgentType); + } + + // Select statements + void sqlQuery .select(selectAllTableCols(TableName.AuditLog)) - .select( db.ref("name").withSchema(TableName.Project).as("projectName"), db.ref("slug").withSchema(TableName.Project).as("projectSlug") ) - .limit(limit) .offset(offset) .orderBy(`${TableName.AuditLog}.createdAt`, "desc"); + // Special case: Filter by actor ID if (actorId) { void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actorId]); } + // Special case: Filter by key/value pairs in eventMetadata field if (eventMetadata && Object.keys(eventMetadata).length) { Object.entries(eventMetadata).forEach(([key, value]) => { void sqlQuery.whereRaw(`"eventMetadata"->>'${key}' = ?`, [value]); }); } + // Filter by actor type if (actorType) { void sqlQuery.where("actor", actorType); } + // Filter by event types if (eventType?.length) { void sqlQuery.whereIn("eventType", eventType); } + // Filter by date range if (startDate) { void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate); } @@ -97,13 +111,21 @@ export const auditLogDALFactory = (db: TDbClient) => { } const docs = await sqlQuery; - return docs.map((doc) => ({ - ...AuditLogsSchema.parse(doc), - project: { - name: doc.projectName, - slug: doc.projectSlug - } - })); + return docs.map((doc) => { + // Our type system refuses to acknowledge that the project name and slug are present in the doc, due to the disjointed query structure above. + // This is a quick and dirty way to get around the types. + const projectDoc = doc as unknown as { projectName: string; projectSlug: string }; + + return { + ...AuditLogsSchema.parse(doc), + ...(projectDoc?.projectSlug && { + project: { + name: projectDoc.projectName, + slug: projectDoc.projectSlug + } + }) + }; + }); } catch (error) { throw new DatabaseError({ error }); } 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 a93b2a6e1..747c53c1a 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -24,6 +24,7 @@ export const auditLogServiceFactory = ({ permissionService }: TAuditLogServiceFactoryDep) => { const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => { + // Filter logs for specific project if (filter.projectId) { const { permission } = await permissionService.getProjectPermission( actor, @@ -34,6 +35,7 @@ export const auditLogServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); } else { + // Organization-wide logs const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -44,13 +46,12 @@ export const auditLogServiceFactory = ({ /** * NOTE (dangtony98): Update this to organization-level audit log permission check once audit logs are moved - * to the organization level + * to the organization level ✅ */ - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs); } // If project ID is not provided, then we need to return all the audit logs for the organization itself. - const auditLogs = await auditLogDAL.find({ startDate: filter.startDate, endDate: filter.endDate, diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index 64ca8ad41..b9a4980be 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -25,7 +25,8 @@ export enum OrgPermissionSubjects { SecretScanning = "secret-scanning", Identity = "identity", Kms = "kms", - AdminConsole = "organization-admin-console" + AdminConsole = "organization-admin-console", + AuditLogs = "audit-logs" } export type OrgPermissionSet = @@ -43,6 +44,7 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Billing] | [OrgPermissionActions, OrgPermissionSubjects.Identity] | [OrgPermissionActions, OrgPermissionSubjects.Kms] + | [OrgPermissionActions, OrgPermissionSubjects.AuditLogs] | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]; const buildAdminPermission = () => { @@ -111,6 +113,11 @@ const buildAdminPermission = () => { can(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms); can(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms); + can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs); + can(OrgPermissionActions.Create, OrgPermissionSubjects.AuditLogs); + can(OrgPermissionActions.Edit, OrgPermissionSubjects.AuditLogs); + can(OrgPermissionActions.Delete, OrgPermissionSubjects.AuditLogs); + can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole); return rules; @@ -140,6 +147,8 @@ const buildMemberPermission = () => { can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); + can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs); + return rules; }; diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 2cafc8b64..60daa14c4 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -145,6 +145,8 @@ export const fullProjectPermissionSet: [ProjectPermissionActions, ProjectPermiss [ProjectPermissionActions.Edit, ProjectPermissionSub.Tags], [ProjectPermissionActions.Delete, ProjectPermissionSub.Tags], + // TODO(Daniel): Remove the audit logs permissions from project-level permissions. + // TODO: We haven't done this yet because it might break existing roles, since those roles will become "invalid" since the audit log permission defined on those roles, no longer exist in the project-level defined permissions. [ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs], [ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs], [ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs], diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 3bfe66718..bf1f7fd81 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -731,9 +731,12 @@ export const DASHBOARD = { export const AUDIT_LOGS = { EXPORT: { - workspaceId: "The ID of the project to export audit logs from.", + projectId: + "Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.", eventType: "The type of the event to export.", 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.", startDate: "The date to start the export from.", endDate: "The date to end the export at.", offset: "The offset to start from. If you enter 10, it will start from the 10th audit log.", diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index bb12a5151..8170fe04a 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -74,7 +74,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { schema: { description: "Get all audit logs for an organization", querystring: z.object({ - projectId: z.string().optional(), + projectId: z.string().optional().describe(AUDIT_LOGS.EXPORT.projectId), actorType: z.nativeEnum(ActorType).optional(), // eventType is split with , for multiple values, we need to transform it to array eventType: z @@ -102,7 +102,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, {} as Record ); - }), + }) + .describe(AUDIT_LOGS.EXPORT.eventMetadata), startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate), endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate), offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset), @@ -120,10 +121,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }) .merge( z.object({ - project: z.object({ - name: z.string(), - slug: z.string() - }), + project: z + .object({ + name: z.string(), + slug: z.string() + }) + .optional(), event: z.object({ type: z.string(), metadata: z.any() diff --git a/docs/api-reference/endpoints/audit-logs/export-audit-log.mdx b/docs/api-reference/endpoints/audit-logs/export-audit-log.mdx index aa5adb004..d39cbe3d1 100644 --- a/docs/api-reference/endpoints/audit-logs/export-audit-log.mdx +++ b/docs/api-reference/endpoints/audit-logs/export-audit-log.mdx @@ -1,4 +1,4 @@ --- title: "Export" -openapi: "GET /api/v1/workspace/{workspaceId}/audit-logs" +openapi: "GET /api/v1/organization/audit-logs" --- diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 5b7ef0174..c950ec179 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -21,7 +21,8 @@ export enum OrgPermissionSubjects { SecretScanning = "secret-scanning", Identity = "identity", Kms = "kms", - AdminConsole = "organization-admin-console" + AdminConsole = "organization-admin-console", + AuditLogs = "audit-logs" } export enum OrgPermissionAdminConsoleAction { @@ -43,6 +44,7 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Billing] | [OrgPermissionActions, OrgPermissionSubjects.Identity] | [OrgPermissionActions, OrgPermissionSubjects.Kms] - | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]; + | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] + | [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]; export type TOrgPermission = MongoAbility; diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index fc616321a..764d0b2a5 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -8,6 +8,7 @@ export type TGetAuditLogsFilter = { userAgentType?: UserAgentType; eventMetadata?: Record; actorType?: ActorType; + projectId?: string; actorId?: string; // user ID format startDate?: Date; endDate?: Date; @@ -885,7 +886,7 @@ export type AuditLog = { userAgentType: UserAgentType; createdAt: string; updatedAt: string; - project: { + project?: { name: string; slug: string; }; diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 4e3602686..9f5e9897f 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -675,18 +675,6 @@ export const AppLayout = ({ children }: LayoutProps) => { - - - - Audit Logs - - - { )} + + + + Audit Logs + + + { - const { t } = useTranslation(); - return (
- {t("common.head-title", { title: t("settings.project.title") })} + Infisical | Audit Logs diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationAuditLogsSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationAuditLogsSection.tsx index c90f5fef7..e7aa5b2f5 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationAuditLogsSection.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationAuditLogsSection.tsx @@ -4,7 +4,7 @@ import { EmptyState } from "@app/components/v2"; import { useSubscription } from "@app/context"; import { EventType } from "@app/hooks/api/auditLogs/enums"; import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types"; -import { LogsSection } from "@app/views/Project/AuditLogsPage/components"; +import { LogsSection } from "@app/views/Org/AuditLogsPage/components"; // Add more events if needed const INTEGRATION_EVENTS = [EventType.INTEGRATION_SYNCED]; diff --git a/frontend/src/views/Org/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/views/Org/AuditLogsPage/AuditLogsPage.tsx new file mode 100644 index 000000000..2b6ec6744 --- /dev/null +++ b/frontend/src/views/Org/AuditLogsPage/AuditLogsPage.tsx @@ -0,0 +1,21 @@ +import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { withPermission } from "@app/hoc"; + +import { LogsSection } from "./components"; + +export const AuditLogsPage = withPermission( + () => { + return ( +
+
+
+

Audit Logs

+
+
+ +
+
+ ); + }, + { action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.AuditLogs } +); diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/views/Org/AuditLogsPage/components/LogsFilter.tsx similarity index 80% rename from frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx rename to frontend/src/views/Org/AuditLogsPage/components/LogsFilter.tsx index 619b0503a..9ea92f9a9 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Org/AuditLogsPage/components/LogsFilter.tsx @@ -40,16 +40,24 @@ type Props = { eventType?: EventType[]; }; className?: string; + isOrgAuditLogs?: boolean; control: Control; reset: UseFormReset; watch: UseFormWatch; }; -export const LogsFilter = ({ presets, className, control, reset, watch }: Props) => { +export const LogsFilter = ({ + presets, + isOrgAuditLogs, + className, + control, + reset, + watch +}: Props) => { const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false); const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false); - const { currentWorkspace } = useWorkspace(); + const { currentWorkspace, workspaces } = useWorkspace(); const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?.id ?? ""); const renderActorSelectItem = (actor: Actor) => { @@ -112,7 +120,7 @@ export const LogsFilter = ({ presets, className, control, reset, watch }: Props) ? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0]) ?.label : selectedEventTypes?.length === 0 - ? "Select event types" + ? "All events" : `${selectedEventTypes?.length} events selected`}
@@ -191,7 +199,7 @@ export const LogsFilter = ({ presets, className, control, reset, watch }: Props) ( + render={({ field: { onChange, value, ...field }, fieldState: { error } }) => ( { + if (e === "all") onChange(undefined); + else onChange(e); + }} + className={twMerge( + "w-full border border-mineshaft-500 bg-mineshaft-700 text-mineshaft-100", + value === undefined && "text-mineshaft-400" + )} + > + + All projects + + {workspaces.map((project) => ( + + {project.name} + + ))} + + + )} + /> + )} diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx b/frontend/src/views/Org/AuditLogsPage/components/LogsSection.tsx similarity index 96% rename from frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx rename to frontend/src/views/Org/AuditLogsPage/components/LogsSection.tsx index 68fd354e3..a184b0f5c 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx +++ b/frontend/src/views/Org/AuditLogsPage/components/LogsSection.tsx @@ -47,6 +47,7 @@ export const LogsSection = ({ const { control, reset, watch } = useForm({ resolver: yupResolver(auditLogFilterFormSchema), defaultValues: { + projectId: undefined, actor: presets?.actorId, eventType: presets?.eventType || [], page: 1, @@ -65,6 +66,7 @@ export const LogsSection = ({ const eventType = watch("eventType") as EventType[] | undefined; const userAgentType = watch("userAgentType") as UserAgentType | undefined; const actor = watch("actor"); + const projectId = watch("projectId"); const startDate = watch("startDate"); const endDate = watch("endDate"); @@ -73,6 +75,7 @@ export const LogsSection = ({
{showFilters && ( { const { currentWorkspace } = useWorkspace(); + // Determine the project ID for filtering + const filterProjectId = + // Use the projectId from the filter if it exists + filter?.projectId ?? + // Otherwise, if we're not looking at org-wide audit logs + (!isOrgAuditLogs + ? // Use the current workspace ID (or an empty string if that's null) + currentWorkspace?.id ?? "" + : // For org-wide audit logs, use null (no specific project filter) + null); + const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs( { ...filter, limit: AUDIT_LOG_LIMIT }, - !isOrgAuditLogs ? currentWorkspace?.id ?? "" : null, + filterProjectId, { refetchInterval } diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx b/frontend/src/views/Org/AuditLogsPage/components/LogsTableRow.tsx similarity index 92% rename from frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx rename to frontend/src/views/Org/AuditLogsPage/components/LogsTableRow.tsx index 7872f9f7d..014301c56 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx +++ b/frontend/src/views/Org/AuditLogsPage/components/LogsTableRow.tsx @@ -39,6 +39,8 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop }; const renderMetadata = (event: Event) => { + const metadataKeys = Object.keys(event.metadata); + switch (event.type) { case EventType.GET_SECRETS: return ( @@ -476,7 +478,47 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop ); + + case EventType.GET_WORKSPACE_KEY: + return ( + +

{`Key ID: ${event.metadata.keyId}`}

+ + ); + + case EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH: + case EventType.ADD_IDENTITY_UNIVERSAL_AUTH: + case EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH: + case EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS: + return ( + +

{`Identity ID: ${event.metadata.identityId}`}

+ + ); + + case EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET: + case EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET: + return ( + +

{`Identity ID: ${event.metadata.identityId}`}

+

{`Client Secret ID: ${event.metadata.clientSecretId}`}

+ + ); + + // ? If for some reason non the above events are matched, we will display the first 3 metadata items in the metadata object. default: + if (metadataKeys.length) { + const maxMetadataLength = metadataKeys.length > 3 ? 3 : metadataKeys.length; + return ( + + {Object.entries(event.metadata) + .slice(0, maxMetadataLength) + .map(([key, value]) => { + return

{`${key}: ${value}`}

; + })} + + ); + } return ; } }; @@ -531,7 +573,7 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop {formatDate(auditLog.createdAt)} {`${eventToNameMap[auditLog.event.type]}`} - {isOrgAuditLogs && {auditLog.project.name}} + {isOrgAuditLogs && {auditLog?.project?.name ?? "N/A"}} {showActorColumn && renderActor(auditLog.actor)} {renderSource()} {renderMetadata(auditLog.event)} diff --git a/frontend/src/views/Project/AuditLogsPage/components/index.tsx b/frontend/src/views/Org/AuditLogsPage/components/index.tsx similarity index 100% rename from frontend/src/views/Project/AuditLogsPage/components/index.tsx rename to frontend/src/views/Org/AuditLogsPage/components/index.tsx diff --git a/frontend/src/views/Project/AuditLogsPage/components/types.tsx b/frontend/src/views/Org/AuditLogsPage/components/types.tsx similarity index 95% rename from frontend/src/views/Project/AuditLogsPage/components/types.tsx rename to frontend/src/views/Org/AuditLogsPage/components/types.tsx index f1165c80f..12afd0779 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/types.tsx +++ b/frontend/src/views/Org/AuditLogsPage/components/types.tsx @@ -5,6 +5,7 @@ import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; export const auditLogFilterFormSchema = yup .object({ eventMetadata: yup.object({}).optional(), + projectId: yup.string().optional(), eventType: yup.array(yup.string().oneOf(Object.values(EventType), "Invalid event type")), actor: yup.string(), userAgentType: yup.string().oneOf(Object.values(UserAgentType), "Invalid user agent type"), diff --git a/frontend/src/views/Project/AuditLogsPage/index.tsx b/frontend/src/views/Org/AuditLogsPage/index.tsx similarity index 100% rename from frontend/src/views/Project/AuditLogsPage/index.tsx rename to frontend/src/views/Org/AuditLogsPage/index.tsx diff --git a/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts b/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts index 13cf2316b..027ac1bfe 100644 --- a/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts +++ b/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts @@ -32,6 +32,8 @@ export const formSchema = z.object({ create: z.boolean().optional() }) .optional(), + + "audit-logs": generalPermissionSchema, member: generalPermissionSchema, groups: generalPermissionSchema, role: generalPermissionSchema, diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx index f4b237cfe..54bb903c6 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -41,6 +41,10 @@ const SIMPLE_PERMISSION_OPTIONS = [ title: "Incident Contacts", formName: "incident-contact" }, + { + title: "Audit Logs", + formName: "audit-logs" + }, { title: "Organization Profile", formName: "settings" diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserAuditLogsSection.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserAuditLogsSection.tsx index 9ed698bb1..a77e5eb4f 100644 --- a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserAuditLogsSection.tsx +++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserAuditLogsSection.tsx @@ -7,7 +7,7 @@ import { EmptyState, 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"; -import { LogsSection } from "@app/views/Project/AuditLogsPage/components"; +import { LogsSection } from "@app/views/Org/AuditLogsPage/components"; type Props = { orgMembership: OrgUser; diff --git a/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx deleted file mode 100644 index e9b8504a2..000000000 --- a/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; -import { withProjectPermission } from "@app/hoc"; - -import { LogsSection } from "./components"; - -export const AuditLogsPage = withProjectPermission( - () => { - return ( -
-
-
-

Audit Logs

-
-
- -
-
- ); - }, - { action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.AuditLogs } -); diff --git a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts index e3b05a41f..8b4958ee0 100644 --- a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts +++ b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts @@ -47,7 +47,6 @@ export const formSchema = z.object({ settings: generalPermissionSchema, environments: generalPermissionSchema, tags: generalPermissionSchema, - "audit-logs": generalPermissionSchema, "ip-allowlist": generalPermissionSchema, "certificate-authorities": generalPermissionSchema, certificates: generalPermissionSchema, diff --git a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx index 1de24ae71..814994f90 100644 --- a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -65,10 +65,6 @@ const SINGLE_PERMISSION_LIST = [ title: "Tags", formName: "tags" }, - { - title: "Audit Logs", - formName: "audit-logs" - }, { title: "IP Allowlist", formName: "ip-allowlist" diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx index 91f71f5eb..6b1da60b3 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx @@ -5,39 +5,40 @@ import { useBackfillSecretReference } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; export const BackfillSecretReferenceSecretion = () => { - const { currentWorkspace } = useWorkspace(); - const { membership } = useProjectPermission(); - const backfillSecretReferences = useBackfillSecretReference(); + const { currentWorkspace } = useWorkspace(); + const { membership } = useProjectPermission(); + const backfillSecretReferences = useBackfillSecretReference(); - if (!currentWorkspace) return null; + if (!currentWorkspace) return null; - const handleBackfill = async () => { - if (backfillSecretReferences.isLoading) return; - try { - await backfillSecretReferences.mutateAsync({ projectId: currentWorkspace.id || "" }); - createNotification({ text: "Successfully re-indexed secret references", type: "success" }); - } catch { - createNotification({ text: "Failed to re-index secret references", type: "error" }); - } - }; + const handleBackfill = async () => { + if (backfillSecretReferences.isLoading) return; + try { + await backfillSecretReferences.mutateAsync({ projectId: currentWorkspace.id || "" }); + createNotification({ text: "Successfully re-indexed secret references", type: "success" }); + } catch { + createNotification({ text: "Failed to re-index secret references", type: "error" }); + } + }; - const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); - return ( -
-
-

Index Secret References

-
-

- This will index all secret references, enabling integrations to be triggered when their values change going forward. -

- -
- ); + const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); + return ( +
+
+

Index Secret References

+
+

+ This will index all secret references, enabling integrations to be triggered when their + values change going forward. This happens automatically when secrets are created or updated. +

+ +
+ ); };