mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 06:26:27 +00:00
Merge pull request #2448 from Infisical/daniel/org-level-audit-logs
feat(audit-logs): moved audit logs to organization-level
This commit is contained in:
@@ -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({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/audit-logs",
|
url: "/:workspaceId/audit-logs",
|
||||||
@@ -101,7 +107,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
|
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.projectId)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||||
@@ -122,10 +128,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
project: z.object({
|
project: z
|
||||||
name: z.string(),
|
.object({
|
||||||
slug: z.string()
|
name: z.string(),
|
||||||
}),
|
slug: z.string()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
event: z.object({
|
event: z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
metadata: z.any()
|
metadata: z.any()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { AuditLogsSchema, TableName } from "@app/db/schemas";
|
import { AuditLogsSchema, TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
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 { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
@@ -48,47 +48,61 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
|
if (!orgId && !projectId) {
|
||||||
|
throw new Error("Either orgId or projectId must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Find statements
|
||||||
const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog)
|
const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog)
|
||||||
.where(
|
|
||||||
stripUndefinedInWhere({
|
|
||||||
projectId,
|
|
||||||
[`${TableName.AuditLog}.orgId`]: orgId,
|
|
||||||
userAgentType
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
.leftJoin(TableName.Project, `${TableName.AuditLog}.projectId`, `${TableName.Project}.id`)
|
.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(selectAllTableCols(TableName.AuditLog))
|
||||||
|
|
||||||
.select(
|
.select(
|
||||||
db.ref("name").withSchema(TableName.Project).as("projectName"),
|
db.ref("name").withSchema(TableName.Project).as("projectName"),
|
||||||
db.ref("slug").withSchema(TableName.Project).as("projectSlug")
|
db.ref("slug").withSchema(TableName.Project).as("projectSlug")
|
||||||
)
|
)
|
||||||
|
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
|
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
|
||||||
|
|
||||||
|
// Special case: Filter by actor ID
|
||||||
if (actorId) {
|
if (actorId) {
|
||||||
void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actorId]);
|
void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actorId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case: Filter by key/value pairs in eventMetadata field
|
||||||
if (eventMetadata && Object.keys(eventMetadata).length) {
|
if (eventMetadata && Object.keys(eventMetadata).length) {
|
||||||
Object.entries(eventMetadata).forEach(([key, value]) => {
|
Object.entries(eventMetadata).forEach(([key, value]) => {
|
||||||
void sqlQuery.whereRaw(`"eventMetadata"->>'${key}' = ?`, [value]);
|
void sqlQuery.whereRaw(`"eventMetadata"->>'${key}' = ?`, [value]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by actor type
|
||||||
if (actorType) {
|
if (actorType) {
|
||||||
void sqlQuery.where("actor", actorType);
|
void sqlQuery.where("actor", actorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by event types
|
||||||
if (eventType?.length) {
|
if (eventType?.length) {
|
||||||
void sqlQuery.whereIn("eventType", eventType);
|
void sqlQuery.whereIn("eventType", eventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by date range
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate);
|
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate);
|
||||||
}
|
}
|
||||||
@@ -97,13 +111,21 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
const docs = await sqlQuery;
|
const docs = await sqlQuery;
|
||||||
|
|
||||||
return docs.map((doc) => ({
|
return docs.map((doc) => {
|
||||||
...AuditLogsSchema.parse(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.
|
||||||
project: {
|
// This is a quick and dirty way to get around the types.
|
||||||
name: doc.projectName,
|
const projectDoc = doc as unknown as { projectName: string; projectSlug: string };
|
||||||
slug: doc.projectSlug
|
|
||||||
}
|
return {
|
||||||
}));
|
...AuditLogsSchema.parse(doc),
|
||||||
|
...(projectDoc?.projectSlug && {
|
||||||
|
project: {
|
||||||
|
name: projectDoc.projectName,
|
||||||
|
slug: projectDoc.projectSlug
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error });
|
throw new DatabaseError({ error });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const auditLogServiceFactory = ({
|
|||||||
permissionService
|
permissionService
|
||||||
}: TAuditLogServiceFactoryDep) => {
|
}: TAuditLogServiceFactoryDep) => {
|
||||||
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
||||||
|
// Filter logs for specific project
|
||||||
if (filter.projectId) {
|
if (filter.projectId) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -34,6 +35,7 @@ export const auditLogServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
} else {
|
} else {
|
||||||
|
// Organization-wide logs
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -44,13 +46,12 @@ export const auditLogServiceFactory = ({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE (dangtony98): Update this to organization-level audit log permission check once audit logs are moved
|
* 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.
|
// If project ID is not provided, then we need to return all the audit logs for the organization itself.
|
||||||
|
|
||||||
const auditLogs = await auditLogDAL.find({
|
const auditLogs = await auditLogDAL.find({
|
||||||
startDate: filter.startDate,
|
startDate: filter.startDate,
|
||||||
endDate: filter.endDate,
|
endDate: filter.endDate,
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export enum OrgPermissionSubjects {
|
|||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
AdminConsole = "organization-admin-console"
|
AdminConsole = "organization-admin-console",
|
||||||
|
AuditLogs = "audit-logs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
@@ -43,6 +44,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity]
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
@@ -111,6 +113,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms);
|
||||||
can(OrgPermissionActions.Delete, 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);
|
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
@@ -140,6 +147,8 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ export const fullProjectPermissionSet: [ProjectPermissionActions, ProjectPermiss
|
|||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Tags],
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Tags],
|
||||||
[ProjectPermissionActions.Delete, 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.Read, ProjectPermissionSub.AuditLogs],
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs],
|
[ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs],
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs],
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs],
|
||||||
|
|||||||
@@ -731,9 +731,12 @@ export const DASHBOARD = {
|
|||||||
|
|
||||||
export const AUDIT_LOGS = {
|
export const AUDIT_LOGS = {
|
||||||
EXPORT: {
|
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.",
|
eventType: "The type of the event to export.",
|
||||||
userAgentType: "Choose which consuming application to export audit logs for.",
|
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.",
|
startDate: "The date to start the export from.",
|
||||||
endDate: "The date to end the export at.",
|
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.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th audit log.",
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Get all audit logs for an organization",
|
description: "Get all audit logs for an organization",
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
projectId: z.string().optional(),
|
projectId: z.string().optional().describe(AUDIT_LOGS.EXPORT.projectId),
|
||||||
actorType: z.nativeEnum(ActorType).optional(),
|
actorType: z.nativeEnum(ActorType).optional(),
|
||||||
// eventType is split with , for multiple values, we need to transform it to array
|
// eventType is split with , for multiple values, we need to transform it to array
|
||||||
eventType: z
|
eventType: z
|
||||||
@@ -102,7 +102,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
{} as Record<string, string>
|
{} as Record<string, string>
|
||||||
);
|
);
|
||||||
}),
|
})
|
||||||
|
.describe(AUDIT_LOGS.EXPORT.eventMetadata),
|
||||||
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||||
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||||
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||||
@@ -120,10 +121,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
project: z.object({
|
project: z
|
||||||
name: z.string(),
|
.object({
|
||||||
slug: z.string()
|
name: z.string(),
|
||||||
}),
|
slug: z.string()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
event: z.object({
|
event: z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
metadata: z.any()
|
metadata: z.any()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
---
|
---
|
||||||
title: "Export"
|
title: "Export"
|
||||||
openapi: "GET /api/v1/workspace/{workspaceId}/audit-logs"
|
openapi: "GET /api/v1/organization/audit-logs"
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export enum OrgPermissionSubjects {
|
|||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
AdminConsole = "organization-admin-console"
|
AdminConsole = "organization-admin-console",
|
||||||
|
AuditLogs = "audit-logs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
@@ -43,6 +44,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity]
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs];
|
||||||
|
|
||||||
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type TGetAuditLogsFilter = {
|
|||||||
userAgentType?: UserAgentType;
|
userAgentType?: UserAgentType;
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
|
projectId?: string;
|
||||||
actorId?: string; // user ID format
|
actorId?: string; // user ID format
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
@@ -885,7 +886,7 @@ export type AuditLog = {
|
|||||||
userAgentType: UserAgentType;
|
userAgentType: UserAgentType;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
project: {
|
project?: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -675,18 +675,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`/project/${currentWorkspace?.id}/audit-logs`} passHref>
|
|
||||||
<a>
|
|
||||||
<MenuItem
|
|
||||||
isSelected={
|
|
||||||
router.asPath === `/project/${currentWorkspace?.id}/audit-logs`
|
|
||||||
}
|
|
||||||
icon="system-outline-168-view-headline"
|
|
||||||
>
|
|
||||||
Audit Logs
|
|
||||||
</MenuItem>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
<Link href={`/project/${currentWorkspace?.id}/settings`} passHref>
|
<Link href={`/project/${currentWorkspace?.id}/settings`} passHref>
|
||||||
<a>
|
<a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -755,6 +743,16 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
<Link href={`/org/${currentOrg?.id}/audit-logs`} passHref>
|
||||||
|
<a>
|
||||||
|
<MenuItem
|
||||||
|
isSelected={router.asPath === `/org/${currentOrg?.id}/audit-logs`}
|
||||||
|
icon="system-outline-168-view-headline"
|
||||||
|
>
|
||||||
|
Audit Logs
|
||||||
|
</MenuItem>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
<Link href={`/org/${currentOrg?.id}/settings`} passHref>
|
<Link href={`/org/${currentOrg?.id}/settings`} passHref>
|
||||||
<a>
|
<a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
+2
-5
@@ -1,15 +1,12 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
|
||||||
import { AuditLogsPage } from "@app/views/Project/AuditLogsPage";
|
import { AuditLogsPage } from "@app/views/Org/AuditLogsPage";
|
||||||
|
|
||||||
const Logs = () => {
|
const Logs = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-bunker-800">
|
<div className="h-full bg-bunker-800">
|
||||||
<Head>
|
<Head>
|
||||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
<title>Infisical | Audit Logs</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
<meta property="og:image" content="/images/message.png" />
|
<meta property="og:image" content="/images/message.png" />
|
||||||
</Head>
|
</Head>
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import { EmptyState } from "@app/components/v2";
|
|||||||
import { useSubscription } from "@app/context";
|
import { useSubscription } from "@app/context";
|
||||||
import { EventType } from "@app/hooks/api/auditLogs/enums";
|
import { EventType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
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
|
// Add more events if needed
|
||||||
const INTEGRATION_EVENTS = [EventType.INTEGRATION_SYNCED];
|
const INTEGRATION_EVENTS = [EventType.INTEGRATION_SYNCED];
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
|
|
||||||
|
import { LogsSection } from "./components";
|
||||||
|
|
||||||
|
export const AuditLogsPage = withPermission(
|
||||||
|
() => {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||||
|
<div className="w-full max-w-7xl px-6">
|
||||||
|
<div className="bg-bunker-800 py-6">
|
||||||
|
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<LogsSection filterClassName="static p-2" showFilters isOrgAuditLogs />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.AuditLogs }
|
||||||
|
);
|
||||||
+65
-10
@@ -40,16 +40,24 @@ type Props = {
|
|||||||
eventType?: EventType[];
|
eventType?: EventType[];
|
||||||
};
|
};
|
||||||
className?: string;
|
className?: string;
|
||||||
|
isOrgAuditLogs?: boolean;
|
||||||
control: Control<AuditLogFilterFormData>;
|
control: Control<AuditLogFilterFormData>;
|
||||||
reset: UseFormReset<AuditLogFilterFormData>;
|
reset: UseFormReset<AuditLogFilterFormData>;
|
||||||
watch: UseFormWatch<AuditLogFilterFormData>;
|
watch: UseFormWatch<AuditLogFilterFormData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
||||||
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace, workspaces } = useWorkspace();
|
||||||
const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?.id ?? "");
|
const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?.id ?? "");
|
||||||
|
|
||||||
const renderActorSelectItem = (actor: Actor) => {
|
const renderActorSelectItem = (actor: Actor) => {
|
||||||
@@ -112,7 +120,7 @@ export const LogsFilter = ({ presets, className, control, reset, watch }: Props)
|
|||||||
? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0])
|
? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0])
|
||||||
?.label
|
?.label
|
||||||
: selectedEventTypes?.length === 0
|
: selectedEventTypes?.length === 0
|
||||||
? "Select event types"
|
? "All events"
|
||||||
: `${selectedEventTypes?.length} events selected`}
|
: `${selectedEventTypes?.length} events selected`}
|
||||||
<FontAwesomeIcon icon={faChevronDown} className="ml-2 text-xs" />
|
<FontAwesomeIcon icon={faChevronDown} className="ml-2 text-xs" />
|
||||||
</div>
|
</div>
|
||||||
@@ -191,7 +199,7 @@ export const LogsFilter = ({ presets, className, control, reset, watch }: Props)
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="userAgentType"
|
name="userAgentType"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, value, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Source"
|
label="Source"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
@@ -199,13 +207,22 @@ export const LogsFilter = ({ presets, className, control, reset, watch }: Props)
|
|||||||
className="w-40"
|
className="w-40"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
value={value === undefined ? "all" : value}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => {
|
||||||
className="w-full border border-mineshaft-500 bg-mineshaft-700 text-mineshaft-100"
|
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"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{userAgentTypes.map(({ label, value }) => (
|
<SelectItem value="all" key="all">
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
All sources
|
||||||
|
</SelectItem>
|
||||||
|
{userAgentTypes.map(({ label, value: userAgent }) => (
|
||||||
|
<SelectItem value={userAgent} key={label}>
|
||||||
{label}
|
{label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -213,6 +230,43 @@ export const LogsFilter = ({ presets, className, control, reset, watch }: Props)
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{isOrgAuditLogs && workspaces.length > 0 && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="projectId"
|
||||||
|
render={({ field: { onChange, value, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Project"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="w-40"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={value === undefined ? "all" : value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => {
|
||||||
|
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"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SelectItem value="all" key="all">
|
||||||
|
All projects
|
||||||
|
</SelectItem>
|
||||||
|
{workspaces.map((project) => (
|
||||||
|
<SelectItem value={String(project.id || "")} key={project.id}>
|
||||||
|
{project.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Controller
|
<Controller
|
||||||
name="startDate"
|
name="startDate"
|
||||||
control={control}
|
control={control}
|
||||||
@@ -272,7 +326,8 @@ export const LogsFilter = ({ presets, className, control, reset, watch }: Props)
|
|||||||
actor: presets?.actorId,
|
actor: presets?.actorId,
|
||||||
userAgentType: undefined,
|
userAgentType: undefined,
|
||||||
startDate: undefined,
|
startDate: undefined,
|
||||||
endDate: undefined
|
endDate: undefined,
|
||||||
|
projectId: undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
+4
@@ -47,6 +47,7 @@ export const LogsSection = ({
|
|||||||
const { control, reset, watch } = useForm<AuditLogFilterFormData>({
|
const { control, reset, watch } = useForm<AuditLogFilterFormData>({
|
||||||
resolver: yupResolver(auditLogFilterFormSchema),
|
resolver: yupResolver(auditLogFilterFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
projectId: undefined,
|
||||||
actor: presets?.actorId,
|
actor: presets?.actorId,
|
||||||
eventType: presets?.eventType || [],
|
eventType: presets?.eventType || [],
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -65,6 +66,7 @@ export const LogsSection = ({
|
|||||||
const eventType = watch("eventType") as EventType[] | undefined;
|
const eventType = watch("eventType") as EventType[] | undefined;
|
||||||
const userAgentType = watch("userAgentType") as UserAgentType | undefined;
|
const userAgentType = watch("userAgentType") as UserAgentType | undefined;
|
||||||
const actor = watch("actor");
|
const actor = watch("actor");
|
||||||
|
const projectId = watch("projectId");
|
||||||
|
|
||||||
const startDate = watch("startDate");
|
const startDate = watch("startDate");
|
||||||
const endDate = watch("endDate");
|
const endDate = watch("endDate");
|
||||||
@@ -73,6 +75,7 @@ export const LogsSection = ({
|
|||||||
<div>
|
<div>
|
||||||
{showFilters && (
|
{showFilters && (
|
||||||
<LogsFilter
|
<LogsFilter
|
||||||
|
isOrgAuditLogs
|
||||||
className={filterClassName}
|
className={filterClassName}
|
||||||
presets={presets}
|
presets={presets}
|
||||||
control={control}
|
control={control}
|
||||||
@@ -87,6 +90,7 @@ export const LogsSection = ({
|
|||||||
showActorColumn={!!showActorColumn && !isOrgAuditLogs}
|
showActorColumn={!!showActorColumn && !isOrgAuditLogs}
|
||||||
filter={{
|
filter={{
|
||||||
eventMetadata: presets?.eventMetadata,
|
eventMetadata: presets?.eventMetadata,
|
||||||
|
projectId,
|
||||||
actorType: presets?.actorType,
|
actorType: presets?.actorType,
|
||||||
limit: 15,
|
limit: 15,
|
||||||
eventType,
|
eventType,
|
||||||
+12
-1
@@ -41,12 +41,23 @@ export const LogsTable = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
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(
|
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
|
||||||
{
|
{
|
||||||
...filter,
|
...filter,
|
||||||
limit: AUDIT_LOG_LIMIT
|
limit: AUDIT_LOG_LIMIT
|
||||||
},
|
},
|
||||||
!isOrgAuditLogs ? currentWorkspace?.id ?? "" : null,
|
filterProjectId,
|
||||||
{
|
{
|
||||||
refetchInterval
|
refetchInterval
|
||||||
}
|
}
|
||||||
+43
-1
@@ -39,6 +39,8 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderMetadata = (event: Event) => {
|
const renderMetadata = (event: Event) => {
|
||||||
|
const metadataKeys = Object.keys(event.metadata);
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case EventType.GET_SECRETS:
|
case EventType.GET_SECRETS:
|
||||||
return (
|
return (
|
||||||
@@ -476,7 +478,47 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Td>
|
</Td>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case EventType.GET_WORKSPACE_KEY:
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>{`Key ID: ${event.metadata.keyId}`}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Td>
|
||||||
|
<p>{`Identity ID: ${event.metadata.identityId}`}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
|
||||||
|
case EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET:
|
||||||
|
case EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET:
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>{`Identity ID: ${event.metadata.identityId}`}</p>
|
||||||
|
<p>{`Client Secret ID: ${event.metadata.clientSecretId}`}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
|
||||||
|
// ? If for some reason non the above events are matched, we will display the first 3 metadata items in the metadata object.
|
||||||
default:
|
default:
|
||||||
|
if (metadataKeys.length) {
|
||||||
|
const maxMetadataLength = metadataKeys.length > 3 ? 3 : metadataKeys.length;
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
{Object.entries(event.metadata)
|
||||||
|
.slice(0, maxMetadataLength)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
return <p key={`audit-log-metadata-${key}`}>{`${key}: ${value}`}</p>;
|
||||||
|
})}
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
}
|
||||||
return <Td />;
|
return <Td />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -531,7 +573,7 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
|
|||||||
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
||||||
<Td>{formatDate(auditLog.createdAt)}</Td>
|
<Td>{formatDate(auditLog.createdAt)}</Td>
|
||||||
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
||||||
{isOrgAuditLogs && <Td>{auditLog.project.name}</Td>}
|
{isOrgAuditLogs && <Td>{auditLog?.project?.name ?? "N/A"}</Td>}
|
||||||
{showActorColumn && renderActor(auditLog.actor)}
|
{showActorColumn && renderActor(auditLog.actor)}
|
||||||
{renderSource()}
|
{renderSource()}
|
||||||
{renderMetadata(auditLog.event)}
|
{renderMetadata(auditLog.event)}
|
||||||
+1
@@ -5,6 +5,7 @@ import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
|||||||
export const auditLogFilterFormSchema = yup
|
export const auditLogFilterFormSchema = yup
|
||||||
.object({
|
.object({
|
||||||
eventMetadata: yup.object({}).optional(),
|
eventMetadata: yup.object({}).optional(),
|
||||||
|
projectId: yup.string().optional(),
|
||||||
eventType: yup.array(yup.string().oneOf(Object.values(EventType), "Invalid event type")),
|
eventType: yup.array(yup.string().oneOf(Object.values(EventType), "Invalid event type")),
|
||||||
actor: yup.string(),
|
actor: yup.string(),
|
||||||
userAgentType: yup.string().oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
userAgentType: yup.string().oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
||||||
@@ -32,6 +32,8 @@ export const formSchema = z.object({
|
|||||||
create: z.boolean().optional()
|
create: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|
||||||
|
"audit-logs": generalPermissionSchema,
|
||||||
member: generalPermissionSchema,
|
member: generalPermissionSchema,
|
||||||
groups: generalPermissionSchema,
|
groups: generalPermissionSchema,
|
||||||
role: generalPermissionSchema,
|
role: generalPermissionSchema,
|
||||||
|
|||||||
+4
@@ -41,6 +41,10 @@ const SIMPLE_PERMISSION_OPTIONS = [
|
|||||||
title: "Incident Contacts",
|
title: "Incident Contacts",
|
||||||
formName: "incident-contact"
|
formName: "incident-contact"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Audit Logs",
|
||||||
|
formName: "audit-logs"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Organization Profile",
|
title: "Organization Profile",
|
||||||
formName: "settings"
|
formName: "settings"
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import { EmptyState, IconButton, Tooltip } from "@app/components/v2";
|
|||||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
import { OrgUser } from "@app/hooks/api/types";
|
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 = {
|
type Props = {
|
||||||
orgMembership: OrgUser;
|
orgMembership: OrgUser;
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
|
||||||
import { withProjectPermission } from "@app/hoc";
|
|
||||||
|
|
||||||
import { LogsSection } from "./components";
|
|
||||||
|
|
||||||
export const AuditLogsPage = withProjectPermission(
|
|
||||||
() => {
|
|
||||||
return (
|
|
||||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
|
||||||
<div className="w-full max-w-7xl px-6">
|
|
||||||
<div className="sticky top-0 z-10 bg-bunker-800 py-6">
|
|
||||||
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<LogsSection showFilters />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.AuditLogs }
|
|
||||||
);
|
|
||||||
-1
@@ -47,7 +47,6 @@ export const formSchema = z.object({
|
|||||||
settings: generalPermissionSchema,
|
settings: generalPermissionSchema,
|
||||||
environments: generalPermissionSchema,
|
environments: generalPermissionSchema,
|
||||||
tags: generalPermissionSchema,
|
tags: generalPermissionSchema,
|
||||||
"audit-logs": generalPermissionSchema,
|
|
||||||
"ip-allowlist": generalPermissionSchema,
|
"ip-allowlist": generalPermissionSchema,
|
||||||
"certificate-authorities": generalPermissionSchema,
|
"certificate-authorities": generalPermissionSchema,
|
||||||
certificates: generalPermissionSchema,
|
certificates: generalPermissionSchema,
|
||||||
|
|||||||
-4
@@ -65,10 +65,6 @@ const SINGLE_PERMISSION_LIST = [
|
|||||||
title: "Tags",
|
title: "Tags",
|
||||||
formName: "tags"
|
formName: "tags"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Audit Logs",
|
|
||||||
formName: "audit-logs"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "IP Allowlist",
|
title: "IP Allowlist",
|
||||||
formName: "ip-allowlist"
|
formName: "ip-allowlist"
|
||||||
|
|||||||
+33
-32
@@ -5,39 +5,40 @@ import { useBackfillSecretReference } from "@app/hooks/api";
|
|||||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||||
|
|
||||||
export const BackfillSecretReferenceSecretion = () => {
|
export const BackfillSecretReferenceSecretion = () => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { membership } = useProjectPermission();
|
const { membership } = useProjectPermission();
|
||||||
const backfillSecretReferences = useBackfillSecretReference();
|
const backfillSecretReferences = useBackfillSecretReference();
|
||||||
|
|
||||||
if (!currentWorkspace) return null;
|
if (!currentWorkspace) return null;
|
||||||
|
|
||||||
const handleBackfill = async () => {
|
const handleBackfill = async () => {
|
||||||
if (backfillSecretReferences.isLoading) return;
|
if (backfillSecretReferences.isLoading) return;
|
||||||
try {
|
try {
|
||||||
await backfillSecretReferences.mutateAsync({ projectId: currentWorkspace.id || "" });
|
await backfillSecretReferences.mutateAsync({ projectId: currentWorkspace.id || "" });
|
||||||
createNotification({ text: "Successfully re-indexed secret references", type: "success" });
|
createNotification({ text: "Successfully re-indexed secret references", type: "success" });
|
||||||
} catch {
|
} catch {
|
||||||
createNotification({ text: "Failed to re-index secret references", type: "error" });
|
createNotification({ text: "Failed to re-index secret references", type: "error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<p className="text-xl font-semibold">Index Secret References</p>
|
<p className="text-xl font-semibold">Index Secret References</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
||||||
This will index all secret references, enabling integrations to be triggered when their values change going forward.
|
This will index all secret references, enabling integrations to be triggered when their
|
||||||
</p>
|
values change going forward. This happens automatically when secrets are created or updated.
|
||||||
<Button
|
</p>
|
||||||
variant="outline_bg"
|
<Button
|
||||||
isLoading={backfillSecretReferences.isLoading}
|
variant="outline_bg"
|
||||||
onClick={handleBackfill}
|
isLoading={backfillSecretReferences.isLoading}
|
||||||
isDisabled={!isAdmin}
|
onClick={handleBackfill}
|
||||||
>
|
isDisabled={!isAdmin}
|
||||||
Index Secret References
|
>
|
||||||
</Button>
|
Index Secret References
|
||||||
</div>
|
</Button>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user