diff --git a/backend/spec.json b/backend/spec.json index ecb6977b5..1f9c0a498 100644 --- a/backend/spec.json +++ b/backend/spec.json @@ -306,12 +306,77 @@ }, "/api/v1/workspace/{workspaceId}/audit-logs": { "get": { - "description": "", + "summary": "Return audit logs", + "description": "Return audit logs", "parameters": [ { "name": "workspaceId", "in": "path", "required": true, + "schema": { + "type": "string" + }, + "description": "ID of the workspace where to get folders from" + }, + { + "name": "offset", + "description": "Number of logs to skip before starting to return logs for pagination", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "description": "Maximum number of logs to return for pagination", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "startDate", + "description": "Filter logs from this date in ISO-8601 format", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "endDate", + "description": "Filter logs till this date in ISO-8601 format", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "eventType", + "description": "Filter by type of event such as get-secrets, get-secret, create-secret, update-secret, delete-secret, etc.", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "userAgentType", + "description": "Filter by type of user agent such as web, cli, k8-operator, or other", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "actor", + "description": "Filter by actor such as user or service", + "required": false, + "in": "query", "schema": { "type": "string" } @@ -319,9 +384,30 @@ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "auditLogs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLog" + }, + "description": "List of audit log" + } + } + } + } + } } - } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] } }, "/api/v1/workspace/{workspaceId}/audit-logs/filters/actors": { @@ -1132,6 +1218,43 @@ } } }, + "/api/v1/admin/config": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + }, + "patch": { + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/admin/signup": { + "post": { + "description": "", + "parameters": [ + { + "name": "user-agent", + "in": "header", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/api/v1/bot/{workspaceId}": { "get": { "description": "", @@ -4560,65 +4683,6 @@ } } } - }, - "get": { - "summary": "Get all accessible environments of a workspace", - "description": "Fetch all environments that the user has access to in a specified workspace", - "parameters": [ - { - "name": "workspaceId", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "ID of the workspace" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "accessibleEnvironments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "Development" - }, - "slug": { - "type": "string", - "example": "development" - }, - "isWriteDenied": { - "type": "boolean", - "example": false - }, - "isReadDenied": { - "type": "boolean", - "example": false - } - } - } - } - }, - "description": "List of environments the user has access to in the specified workspace" - } - } - } - } - }, - "security": [ - { - "apiKeyAuth": [] - } - ] } }, "/api/v2/workspace/{workspaceId}/tags": { @@ -6864,6 +6928,61 @@ "example": "2023-01-13T14:16:12.210Z" } } + }, + "AuditLog": { + "type": "object", + "properties": { + "actor": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "" + }, + "metadata": { + "type": "object", + "properties": {} + } + } + }, + "organization": { + "type": "string", + "example": "" + }, + "workspace": { + "type": "string", + "example": "" + }, + "ipAddress": { + "type": "string", + "example": "" + }, + "event": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "" + }, + "metadata": { + "type": "object", + "properties": {} + } + } + }, + "userAgent": { + "type": "string", + "example": "" + }, + "userAgentType": { + "type": "string", + "example": "" + }, + "expiresAt": { + "type": "string", + "example": "" + } + } } }, "securitySchemes": { diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 7d5caae9b..fd068373b 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -578,6 +578,82 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons * @param res */ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Return audit logs' + #swagger.description = 'Return audit logs' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of the workspace where to get folders from", + "required": true, + "type": "string", + "in": "path" + } + + #swagger.parameters['offset'] = { + "description": "Number of logs to skip before starting to return logs for pagination", + "required": false, + "type": "string" + } + + #swagger.parameters['limit'] = { + "description": "Maximum number of logs to return for pagination", + "required": false, + "type": "string" + } + + #swagger.parameters['startDate'] = { + "description": "Filter logs from this date in ISO-8601 format", + "required": false, + "type": "string" + } + + #swagger.parameters['endDate'] = { + "description": "Filter logs till this date in ISO-8601 format", + "required": false, + "type": "string" + } + + #swagger.parameters['eventType'] = { + "description": "Filter by type of event such as get-secrets, get-secret, create-secret, update-secret, delete-secret, etc.", + "required": false, + "type": "string", + } + + #swagger.parameters['userAgentType'] = { + "description": "Filter by type of user agent such as web, cli, k8-operator, or other", + "required": false, + "type": "string", + } + + #swagger.parameters['actor'] = { + "description": "Filter by actor such as user or service", + "required": false, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "object", + "properties": { + "auditLogs": { + "type": "array", + "items": { + $ref: "#/components/schemas/AuditLog", + }, + "description": "List of audit log" + }, + } + } + } + } + } + */ const { query: { limit, offset, endDate, eventType, startDate, userAgentType, actor }, params: { workspaceId } @@ -592,7 +668,7 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs ); - + const query = { workspace: new Types.ObjectId(workspaceId), ...(eventType @@ -626,14 +702,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { } : {}) }; - const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit); - - const totalCount = await AuditLog.countDocuments(query); - return res.status(200).send({ - auditLogs, - totalCount + auditLogs }); }; @@ -685,7 +756,7 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res name: serviceTokenData.name } })); - + const serviceV3Actors: ServiceActorV3[] = ( await ServiceTokenDataV3.find({ workspace: new Types.ObjectId(workspaceId) @@ -697,12 +768,8 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res name: serviceTokenData.name } })); - - const actors = [ - ...userActors, - ...serviceActors, - ...serviceV3Actors - ]; + + const actors = [...userActors, ...serviceActors, ...serviceV3Actors]; return res.status(200).send({ actors diff --git a/backend/src/ee/models/auditLog/auditLog.ts b/backend/src/ee/models/auditLog/auditLog.ts index a38527e85..c2c5aba68 100644 --- a/backend/src/ee/models/auditLog/auditLog.ts +++ b/backend/src/ee/models/auditLog/auditLog.ts @@ -1,76 +1,70 @@ import { Schema, Types, model } from "mongoose"; -import { - ActorType, - EventType, - UserAgentType -} from "./enums"; -import { - Actor, - Event -} from "./types"; +import { ActorType, EventType, UserAgentType } from "./enums"; +import { Actor, Event } from "./types"; export interface IAuditLog { - actor: Actor; - organization: Types.ObjectId; - workspace: Types.ObjectId; - ipAddress: string; - event: Event; - userAgent: string; - userAgentType: UserAgentType; - expiresAt: Date; + actor: Actor; + organization: Types.ObjectId; + workspace: Types.ObjectId; + ipAddress: string; + event: Event; + userAgent: string; + userAgentType: UserAgentType; + expiresAt: Date; } const auditLogSchema = new Schema( - { - actor: { - type: { - type: String, - enum: ActorType, - required: true - }, - metadata: { - type: Schema.Types.Mixed - } - }, - organization: { - type: Schema.Types.ObjectId, - required: false - }, - workspace: { - type: Schema.Types.ObjectId, - required: false - }, - ipAddress: { - type: String, - required: true - }, - event: { - type: { - type: String, - enum: EventType, - required: true - }, - metadata: { - type: Schema.Types.Mixed - } - }, - userAgent: { - type: String, - required: true - }, - userAgentType: { - type: String, - enum: UserAgentType, - required: true - }, - expiresAt: { - type: Date, - expires: 0 - } + { + actor: { + type: { + type: String, + enum: ActorType, + required: true + }, + metadata: { + type: Schema.Types.Mixed + } }, - { - timestamps: true + organization: { + type: Schema.Types.ObjectId, + required: false + }, + workspace: { + type: Schema.Types.ObjectId, + required: false, + index: true + }, + ipAddress: { + type: String, + required: true + }, + event: { + type: { + type: String, + enum: EventType, + required: true + }, + metadata: { + type: Schema.Types.Mixed + } + }, + userAgent: { + type: String, + required: true + }, + userAgentType: { + type: String, + enum: UserAgentType, + required: true + }, + expiresAt: { + type: Date, + expires: 0 } + }, + { + timestamps: true + } ); -export const AuditLog = model("AuditLog", auditLogSchema); +export const AuditLog = model("AuditLog", auditLogSchema); diff --git a/backend/src/utils/logging/logger.ts b/backend/src/utils/logging/logger.ts index 7562a8b5b..70ba77570 100644 --- a/backend/src/utils/logging/logger.ts +++ b/backend/src/utils/logging/logger.ts @@ -5,12 +5,12 @@ export let logger: Logger; // https://github.com/pinojs/pino/blob/master/lib/levels.js#L13-L20 const logLevelToSeverityLookup: Record = { - '10': 'TRACE', - '20': 'DEBUG', - '30': 'INFO', - '40': 'WARNING', - '50': 'ERROR', - '60': 'CRITICAL' + "10": "TRACE", + "20": "DEBUG", + "30": "INFO", + "40": "WARNING", + "50": "ERROR", + "60": "CRITICAL" } export const initLogger = async () => { @@ -51,7 +51,7 @@ export const initLogger = async () => { logger = pino( { mixin(_context, level) { - return { 'severity': logLevelToSeverityLookup[level] || logLevelToSeverityLookup['30'] } + return { "severity": logLevelToSeverityLookup[level] || logLevelToSeverityLookup["30"] } }, level: process.env.PINO_LOG_LEVEL || "info", formatters: { diff --git a/backend/src/validation/workspace.ts b/backend/src/validation/workspace.ts index 5c4ae25c7..fe13ea4db 100644 --- a/backend/src/validation/workspace.ts +++ b/backend/src/validation/workspace.ts @@ -58,7 +58,7 @@ export const validateClientForWorkspace = async ({ environment, requiredPermissions }); - return { membership, workspace}; + return { membership, workspace }; case ActorType.SERVICE_V3: throw UnauthorizedRequestError({ message: "Failed service token authorization for organization" @@ -121,8 +121,8 @@ export const GetWorkspaceAuditLogsV1 = z.object({ userAgentType: z.nativeEnum(UserAgentType).nullable().optional(), startDate: z.string().datetime().nullable().optional(), endDate: z.string().datetime().nullable().optional(), - offset: z.coerce.number(), - limit: z.coerce.number(), + offset: z.coerce.number().default(0), + limit: z.coerce.number().default(20), actor: z.string().nullish().optional() }) }); @@ -309,4 +309,4 @@ export const GetWorkspaceServiceTokenDataV3 = z.object({ params: z.object({ workspaceId: z.string().trim() }) -}); \ No newline at end of file +}); diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts index 7258c727a..3c7a503d6 100644 --- a/backend/swagger/index.ts +++ b/backend/swagger/index.ts @@ -12,32 +12,32 @@ const generateOpenAPISpec = async () => { const doc = { info: { title: "Infisical API", - description: "List of all available APIs that can be consumed", + description: "List of all available APIs that can be consumed" }, host: ["https://infisical.com"], servers: [ { url: "https://app.infisical.com", - description: "Production server", + description: "Production server" }, { url: "http://localhost:8080", - description: "Local server", - }, + description: "Local server" + } ], securityDefinitions: { bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "JWT", - description: "A service token in Infisical", + description: "A service token in Infisical" }, apiKeyAuth: { type: "apiKey", in: "header", name: "X-API-Key", - description: "An API Key in Infisical", - }, + description: "An API Key in Infisical" + } }, definitions: { CurrentUser: { @@ -50,7 +50,7 @@ const generateOpenAPISpec = async () => { iv: "iv_of_enc_nacl_private_key", tag: "tag_of_enc_nacl_private_key", updatedAt: "2023-01-13T14:16:12.210Z", - createdAt: "2023-01-13T14:16:12.210Z", + createdAt: "2023-01-13T14:16:12.210Z" }, Membership: { user: { @@ -60,10 +60,10 @@ const generateOpenAPISpec = async () => { lastName: "Doe", publicKey: "johns_nacl_public_key", updatedAt: "2023-01-13T14:16:12.210Z", - createdAt: "2023-01-13T14:16:12.210Z", + createdAt: "2023-01-13T14:16:12.210Z" }, workspace: "", - role: "admin", + role: "admin" }, MembershipOrg: { user: { @@ -73,33 +73,35 @@ const generateOpenAPISpec = async () => { lastName: "Doe", publicKey: "johns_nacl_public_key", updatedAt: "2023-01-13T14:16:12.210Z", - createdAt: "2023-01-13T14:16:12.210Z", + createdAt: "2023-01-13T14:16:12.210Z" }, organization: "", role: "owner", - status: "accepted", + status: "accepted" }, Organization: { _id: "", name: "Acme Corp.", - customerId: "", + customerId: "" }, Project: { name: "My Project", organization: "", - environments: [{ - name: "development", - slug: "dev", - }], + environments: [ + { + name: "development", + slug: "dev" + } + ] }, ProjectKey: { encryptedkey: "", nonce: "", sender: { - publicKey: "senders_nacl_public_key", + publicKey: "senders_nacl_public_key" }, receiver: "", - workspace: "", + workspace: "" }, CreateSecret: { type: "shared", @@ -111,7 +113,7 @@ const generateOpenAPISpec = async () => { secretValueTag: "", secretCommentCiphertext: "", secretCommentIV: "", - secretCommentTag: "", + secretCommentTag: "" }, UpdateSecret: { id: "", @@ -123,12 +125,12 @@ const generateOpenAPISpec = async () => { secretValueTag: "", secretCommentCiphertext: "", secretCommentIV: "", - secretCommentTag: "", + secretCommentTag: "" }, Secret: { _id: "", version: 1, - workspace : "", + workspace: "", type: "shared", user: null, secretKeyCiphertext: "", @@ -141,7 +143,7 @@ const generateOpenAPISpec = async () => { secretCommentIV: "", secretCommentTag: "", updatedAt: "2023-01-13T14:16:12.210Z", - createdAt: "2023-01-13T14:16:12.210Z", + createdAt: "2023-01-13T14:16:12.210Z" }, RawSecret: { _id: "abc123", @@ -167,12 +169,10 @@ const generateOpenAPISpec = async () => { _id: "", email: "johndoe@gmail.com", firstName: "John", - lastName: "Doe", + lastName: "Doe" }, workspace: "", - actionNames: [ - "addSecrets", - ], + actionNames: ["addSecrets"], actions: [ { name: "addSecrets", @@ -181,24 +181,24 @@ const generateOpenAPISpec = async () => { payload: [ { oldSecretVersion: "", - newSecretVersion: "", - }, - ], - }, + newSecretVersion: "" + } + ] + } ], channel: "cli", ipAddress: "192.168.0.1", updatedAt: "2023-01-13T14:16:12.210Z", - createdAt: "2023-01-13T14:16:12.210Z", + createdAt: "2023-01-13T14:16:12.210Z" }, SecretSnapshot: { workspace: "", version: 1, secretVersions: [ { - _id: "", - }, - ], + _id: "" + } + ] }, SecretVersion: { _id: "", @@ -214,7 +214,7 @@ const generateOpenAPISpec = async () => { secretKeyTag: "", secretValueCiphertext: "", secretValueIV: "", - secretValueTag: "", + secretValueTag: "" }, ServiceTokenData: { _id: "", @@ -224,16 +224,32 @@ const generateOpenAPISpec = async () => { user: { _id: "", firstName: "", - lastName: "", + lastName: "" }, expiresAt: "2023-01-13T14:16:12.210Z", encryptedKey: "", iv: "", tag: "", updatedAt: "2023-01-13T14:16:12.210Z", - createdAt: "2023-01-13T14:16:12.210Z", + createdAt: "2023-01-13T14:16:12.210Z" }, - }, + AuditLog: { + actor: { + type: "", + metadata: {} + }, + organization: "", + workspace: "", + ipAddress: "", + event: { + type: "", + metadata: {} + }, + userAgent: "", + userAgentType: "", + expiresAt: "" + } + } }; const outputJSONFile = "../spec.json"; @@ -243,6 +259,6 @@ const generateOpenAPISpec = async () => { const spec = await swaggerAutogen(outputJSONFile, endpointsFiles, doc); await fs.writeFile(outputYAMLFile, yaml.dump(spec.data)); -} +}; -generateOpenAPISpec(); \ No newline at end of file +generateOpenAPISpec(); diff --git a/docs/api-reference/endpoints/audit-logs/export-audit-log.mdx b/docs/api-reference/endpoints/audit-logs/export-audit-log.mdx new file mode 100644 index 000000000..aa5adb004 --- /dev/null +++ b/docs/api-reference/endpoints/audit-logs/export-audit-log.mdx @@ -0,0 +1,4 @@ +--- +title: "Export" +openapi: "GET /api/v1/workspace/{workspaceId}/audit-logs" +--- diff --git a/docs/api-reference/endpoints/environments/create.mdx b/docs/api-reference/endpoints/environments/create.mdx index 2bb14167c..826dcce3d 100644 --- a/docs/api-reference/endpoints/environments/create.mdx +++ b/docs/api-reference/endpoints/environments/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v2/workspace/{workspaceId}/environments" ---- \ No newline at end of file +openapi: "POST /api/v1/workspace/{workspaceId}/environments" +--- diff --git a/docs/mint.json b/docs/mint.json index e46b6fa25..3b0b2460d 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -380,6 +380,10 @@ { "group": "Service Tokens", "pages": ["api-reference/endpoints/service-tokens/get"] + }, + { + "group": "Audit Logs", + "pages": ["api-reference/endpoints/audit-logs/export-audit-log"] } ] }, diff --git a/docs/spec.yaml b/docs/spec.yaml index 6f12cbdf3..366c05438 100644 --- a/docs/spec.yaml +++ b/docs/spec.yaml @@ -192,16 +192,74 @@ paths: description: Version of secret snapshot to roll back to /api/v1/workspace/{workspaceId}/audit-logs: get: - description: '' + summary: Return audit logs + description: Return audit logs parameters: - name: workspaceId in: path required: true schema: type: string + description: ID of the workspace where to get folders from + - name: offset + description: Number of logs to skip before starting to return logs for pagination + required: false + in: query + schema: + type: string + - name: limit + description: Maximum number of logs to return for pagination + required: false + in: query + schema: + type: string + - name: startDate + description: Filter logs from this date in ISO-8601 format + required: false + in: query + schema: + type: string + - name: endDate + description: Filter logs till this date in ISO-8601 format + required: false + in: query + schema: + type: string + - name: eventType + description: >- + Filter by type of event such as get-secrets, get-secret, + create-secret, update-secret, delete-secret, etc. + required: false + in: query + schema: + type: string + - name: userAgentType + description: Filter by type of user agent such as web, cli, k8-operator, or other + required: false + in: query + schema: + type: string + - name: actor + description: Filter by actor such as user or service + required: false + in: query + schema: + type: string responses: '200': description: OK + content: + application/json: + schema: + type: object + properties: + auditLogs: + type: array + items: + $ref: '#/components/schemas/AuditLog' + description: List of audit log + security: + - apiKeyAuth: [] /api/v1/workspace/{workspaceId}/audit-logs/filters/actors: get: description: '' @@ -691,6 +749,28 @@ paths: responses: '200': description: OK + /api/v1/admin/config: + get: + description: '' + responses: + '200': + description: OK + patch: + description: '' + responses: + '200': + description: OK + /api/v1/admin/signup: + post: + description: '' + parameters: + - name: user-agent + in: header + schema: + type: string + responses: + '200': + description: OK /api/v1/bot/{workspaceId}: get: description: '' @@ -2834,48 +2914,6 @@ paths: example: dev required: - environmentSlug - get: - summary: Get all accessible environments of a workspace - description: >- - Fetch all environments that the user has access to in a specified - workspace - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of the workspace - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - accessibleEnvironments: - type: array - items: - type: object - properties: - name: - type: string - example: Development - slug: - type: string - example: development - isWriteDenied: - type: boolean - example: false - isReadDenied: - type: boolean - example: false - description: >- - List of environments the user has access to in the specified - workspace - security: - - apiKeyAuth: [] /api/v2/workspace/{workspaceId}/tags: get: description: '' @@ -4342,6 +4380,45 @@ components: createdAt: type: string example: '2023-01-13T14:16:12.210Z' + AuditLog: + type: object + properties: + actor: + type: object + properties: + type: + type: string + example: '' + metadata: + type: object + properties: {} + organization: + type: string + example: '' + workspace: + type: string + example: '' + ipAddress: + type: string + example: '' + event: + type: object + properties: + type: + type: string + example: '' + metadata: + type: object + properties: {} + userAgent: + type: string + example: '' + userAgentType: + type: string + example: '' + expiresAt: + type: string + example: '' securitySchemes: bearerAuth: type: http diff --git a/frontend/src/hooks/api/auditLogs/queries.tsx b/frontend/src/hooks/api/auditLogs/queries.tsx index 94df8bf8b..3517d4435 100644 --- a/frontend/src/hooks/api/auditLogs/queries.tsx +++ b/frontend/src/hooks/api/auditLogs/queries.tsx @@ -1,59 +1,46 @@ -import { useQuery } from "@tanstack/react-query"; +import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { - Actor, - AuditLog, - AuditLogFilters -} from "./types"; +import { Actor, AuditLog, AuditLogFilters } from "./types"; export const workspaceKeys = { - getAuditLogs: (workspaceId: string, filters: AuditLogFilters) => [{ workspaceId, filters }, "audit-logs"] as const, - getAuditLogActorFilterOpts: (workspaceId: string) => [{ workspaceId }, "audit-log-actor-filters"] as const -} + getAuditLogs: (workspaceId: string, filters: AuditLogFilters) => + [{ workspaceId, filters }, "audit-logs"] as const, + getAuditLogActorFilterOpts: (workspaceId: string) => + [{ workspaceId }, "audit-log-actor-filters"] as const +}; export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) => { - return useQuery({ - queryKey: workspaceKeys.getAuditLogs(workspaceId, filters), - queryFn: async () => { - - const params = new URLSearchParams(); - if (filters.eventType) { - params.append("eventType", filters.eventType); - } - - if (filters.userAgentType) { - params.append("userAgentType", filters.userAgentType); - } - - if (filters.actor) { - params.append("actor", filters.actor); - } - - if (filters.startDate) { - params.append("startDate", filters.startDate.toISOString()); - } - - if (filters.endDate) { - params.append("endDate", filters.endDate.toISOString()); - } - - params.append("offset", String(filters.offset)); - params.append("limit", String(filters.limit)); - - const { data } = await apiRequest.get<{ auditLogs: AuditLog[], totalCount: number }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params }); - return data; + return useInfiniteQuery({ + queryKey: workspaceKeys.getAuditLogs(workspaceId, filters), + queryFn: async ({ pageParam }) => { + const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>( + `/api/v1/workspace/${workspaceId}/audit-logs`, + { + params: { + ...filters, + offset: pageParam, + startDate: filters?.startDate?.toISOString(), + endDate: filters?.endDate?.toISOString() + } } - }); -} + ); + return data.auditLogs; + }, + getNextPageParam: (lastPage, pages) => + lastPage.length !== 0 ? pages.length * filters.limit : undefined + }); +}; export const useGetAuditLogActorFilterOpts = (workspaceId: string) => { - return useQuery({ - queryKey: workspaceKeys.getAuditLogActorFilterOpts(workspaceId), - queryFn: async () => { - const { data } = await apiRequest.get<{ actors: Actor[] }>(`/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`); - return data.actors; - } - }); -} \ No newline at end of file + return useQuery({ + queryKey: workspaceKeys.getAuditLogActorFilterOpts(workspaceId), + queryFn: async () => { + const { data } = await apiRequest.get<{ actors: Actor[] }>( + `/api/v1/workspace/${workspaceId}/audit-logs/filters/actors` + ); + return data.actors; + } + }); +}; diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index 8680c9bed..579dd8fc5 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -1,8 +1,4 @@ -import { - ActorType, - EventType, - UserAgentType -} from "./enums"; +import { ActorType, EventType, UserAgentType } from "./enums"; interface UserActorMetadata { userId: string; @@ -25,174 +21,171 @@ export interface ServiceActor { } export interface ServiceActorV3 { - type: ActorType.SERVICE_V3; - metadata: ServiceActorMetadata; + type: ActorType.SERVICE_V3; + metadata: ServiceActorMetadata; } -export type Actor = - | UserActor - | ServiceActor - | ServiceActorV3; +export type Actor = UserActor | ServiceActor | ServiceActorV3; interface GetSecretsEvent { - type: EventType.GET_SECRETS; - metadata: { - environment: string; - secretPath: string; - numberOfSecrets: number; - }; + type: EventType.GET_SECRETS; + metadata: { + environment: string; + secretPath: string; + numberOfSecrets: number; + }; } interface GetSecretEvent { - type: EventType.GET_SECRET; - metadata: { - environment: string; - secretPath: string; - secretId: string; - secretKey: string; - secretVersion: number; - }; + type: EventType.GET_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + }; } interface CreateSecretEvent { - type: EventType.CREATE_SECRET; - metadata: { - environment: string; - secretPath: string; - secretId: string; - secretKey: string; - secretVersion: number; - } + type: EventType.CREATE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + }; } interface UpdateSecretEvent { - type: EventType.UPDATE_SECRET; - metadata: { - environment: string; - secretPath: string; - secretId: string; - secretKey: string; - secretVersion: number; - } + type: EventType.UPDATE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + }; } interface DeleteSecretEvent { - type: EventType.DELETE_SECRET; - metadata: { - environment: string; - secretPath: string; - secretId: string; - secretKey: string; - secretVersion: number; - } + type: EventType.DELETE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + }; } interface GetWorkspaceKeyEvent { - type: EventType.GET_WORKSPACE_KEY, - metadata: { - keyId: string; - } + type: EventType.GET_WORKSPACE_KEY; + metadata: { + keyId: string; + }; } interface AuthorizeIntegrationEvent { - type: EventType.AUTHORIZE_INTEGRATION; - metadata: { - integration: string; - } + type: EventType.AUTHORIZE_INTEGRATION; + metadata: { + integration: string; + }; } interface UnauthorizeIntegrationEvent { - type: EventType.UNAUTHORIZE_INTEGRATION; - metadata: { - integration: string; - } + type: EventType.UNAUTHORIZE_INTEGRATION; + metadata: { + integration: string; + }; } interface CreateIntegrationEvent { - type: EventType.CREATE_INTEGRATION; - metadata: { - integrationId: string; - integration: string; - environment: string; - secretPath: string; - url?: string; - app?: string; - appId?: string; - targetEnvironment?: string; - targetEnvironmentId?: string; - targetService?: string; - targetServiceId?: string; - path?: string; - region?: string; - } + type: EventType.CREATE_INTEGRATION; + metadata: { + integrationId: string; + integration: string; + environment: string; + secretPath: string; + url?: string; + app?: string; + appId?: string; + targetEnvironment?: string; + targetEnvironmentId?: string; + targetService?: string; + targetServiceId?: string; + path?: string; + region?: string; + }; } interface DeleteIntegrationEvent { - type: EventType.DELETE_INTEGRATION; - metadata: { - integrationId: string; - integration: string; - environment: string; - secretPath: string; - url?: string; - app?: string; - appId?: string; - targetEnvironment?: string; - targetEnvironmentId?: string; - targetService?: string; - targetServiceId?: string; - path?: string; - region?: string; - } + type: EventType.DELETE_INTEGRATION; + metadata: { + integrationId: string; + integration: string; + environment: string; + secretPath: string; + url?: string; + app?: string; + appId?: string; + targetEnvironment?: string; + targetEnvironmentId?: string; + targetService?: string; + targetServiceId?: string; + path?: string; + region?: string; + }; } interface AddTrustedIPEvent { - type: EventType.ADD_TRUSTED_IP; - metadata: { - trustedIpId: string; - ipAddress: string; - prefix?: number; - } + type: EventType.ADD_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + }; } interface UpdateTrustedIPEvent { - type: EventType.UPDATE_TRUSTED_IP; - metadata: { - trustedIpId: string; - ipAddress: string; - prefix?: number; - } + type: EventType.UPDATE_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + }; } interface DeleteTrustedIPEvent { - type: EventType.DELETE_TRUSTED_IP; - metadata: { - trustedIpId: string; - ipAddress: string; - prefix?: number; - } + type: EventType.DELETE_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + }; } interface CreateServiceTokenEvent { - type: EventType.CREATE_SERVICE_TOKEN; - metadata: { - name: string; - scopes: Array<{ - environment: string; - secretPath: string; - }>; - } + type: EventType.CREATE_SERVICE_TOKEN; + metadata: { + name: string; + scopes: Array<{ + environment: string; + secretPath: string; + }>; + }; } interface DeleteServiceTokenEvent { - type: EventType.DELETE_SERVICE_TOKEN; - metadata: { - name: string; - scopes: Array<{ - environment: string; - secretPath: string; - }>; - } + type: EventType.DELETE_SERVICE_TOKEN; + metadata: { + name: string; + scopes: Array<{ + environment: string; + secretPath: string; + }>; + }; } interface CreateServiceTokenV3Event { @@ -226,227 +219,226 @@ interface DeleteServiceTokenV3Event { } interface CreateEnvironmentEvent { - type: EventType.CREATE_ENVIRONMENT; - metadata: { - name: string; - slug: string; - } + type: EventType.CREATE_ENVIRONMENT; + metadata: { + name: string; + slug: string; + }; } interface UpdateEnvironmentEvent { - type: EventType.UPDATE_ENVIRONMENT; - metadata: { - oldName: string; - newName: string; - oldSlug: string; - newSlug: string; - } + type: EventType.UPDATE_ENVIRONMENT; + metadata: { + oldName: string; + newName: string; + oldSlug: string; + newSlug: string; + }; } interface DeleteEnvironmentEvent { - type: EventType.DELETE_ENVIRONMENT; - metadata: { - name: string; - slug: string; - } + type: EventType.DELETE_ENVIRONMENT; + metadata: { + name: string; + slug: string; + }; } interface AddWorkspaceMemberEvent { - type: EventType.ADD_WORKSPACE_MEMBER; - metadata: { - userId: string; - email: string; - } + type: EventType.ADD_WORKSPACE_MEMBER; + metadata: { + userId: string; + email: string; + }; } interface RemoveWorkspaceMemberEvent { - type: EventType.REMOVE_WORKSPACE_MEMBER; - metadata: { - userId: string; - email: string; - } + type: EventType.REMOVE_WORKSPACE_MEMBER; + metadata: { + userId: string; + email: string; + }; } interface CreateFolderEvent { - type: EventType.CREATE_FOLDER; - metadata: { - environment: string; - folderId: string; - folderName: string; - folderPath: string; - } + type: EventType.CREATE_FOLDER; + metadata: { + environment: string; + folderId: string; + folderName: string; + folderPath: string; + }; } interface UpdateFolderEvent { - type: EventType.UPDATE_FOLDER; - metadata: { - environment: string; - folderId: string; - oldFolderName: string; - newFolderName: string; - folderPath: string; - } + type: EventType.UPDATE_FOLDER; + metadata: { + environment: string; + folderId: string; + oldFolderName: string; + newFolderName: string; + folderPath: string; + }; } interface DeleteFolderEvent { - type: EventType.DELETE_FOLDER; - metadata: { - environment: string; - folderId: string; - folderName: string; - folderPath: string; - } + type: EventType.DELETE_FOLDER; + metadata: { + environment: string; + folderId: string; + folderName: string; + folderPath: string; + }; } interface CreateWebhookEvent { - type: EventType.CREATE_WEBHOOK, - metadata: { - webhookId: string; - environment: string; - secretPath: string; - webhookUrl: string; - isDisabled: boolean; - } + type: EventType.CREATE_WEBHOOK; + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + }; } interface UpdateWebhookStatusEvent { - type: EventType.UPDATE_WEBHOOK_STATUS, - metadata: { - webhookId: string; - environment: string; - secretPath: string; - webhookUrl: string; - isDisabled: boolean; - } + type: EventType.UPDATE_WEBHOOK_STATUS; + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + }; } interface DeleteWebhookEvent { - type: EventType.DELETE_WEBHOOK, - metadata: { - webhookId: string; - environment: string; - secretPath: string; - webhookUrl: string; - isDisabled: boolean; - } + type: EventType.DELETE_WEBHOOK; + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + }; } interface GetSecretImportsEvent { - type: EventType.GET_SECRET_IMPORTS, - metadata: { - environment: string; - secretImportId: string; - folderId: string; - numberOfImports: number; - } + type: EventType.GET_SECRET_IMPORTS; + metadata: { + environment: string; + secretImportId: string; + folderId: string; + numberOfImports: number; + }; } interface CreateSecretImportEvent { - type: EventType.CREATE_SECRET_IMPORT, - metadata: { - secretImportId: string; - folderId: string; - importFromEnvironment: string; - importFromSecretPath: string; - importToEnvironment: string; - importToSecretPath: string; - } + type: EventType.CREATE_SECRET_IMPORT; + metadata: { + secretImportId: string; + folderId: string; + importFromEnvironment: string; + importFromSecretPath: string; + importToEnvironment: string; + importToSecretPath: string; + }; } interface UpdateSecretImportEvent { - type: EventType.UPDATE_SECRET_IMPORT, - metadata: { - secretImportId: string; - folderId: string; - importToEnvironment: string; - importToSecretPath: string; - orderBefore: { - environment: string; - secretPath: string; - }[], - orderAfter: { - environment: string; - secretPath: string; - }[] - } + type: EventType.UPDATE_SECRET_IMPORT; + metadata: { + secretImportId: string; + folderId: string; + importToEnvironment: string; + importToSecretPath: string; + orderBefore: { + environment: string; + secretPath: string; + }[]; + orderAfter: { + environment: string; + secretPath: string; + }[]; + }; } interface DeleteSecretImportEvent { - type: EventType.DELETE_SECRET_IMPORT, - metadata: { - secretImportId: string; - folderId: string; - importFromEnvironment: string; - importFromSecretPath: string; - importToEnvironment: string; - importToSecretPath: string; - } + type: EventType.DELETE_SECRET_IMPORT; + metadata: { + secretImportId: string; + folderId: string; + importFromEnvironment: string; + importFromSecretPath: string; + importToEnvironment: string; + importToSecretPath: string; + }; } interface UpdateUserRole { - type: EventType.UPDATE_USER_WORKSPACE_ROLE, - metadata: { - userId: string; - email: string; - oldRole: string; - newRole: string; - } + type: EventType.UPDATE_USER_WORKSPACE_ROLE; + metadata: { + userId: string; + email: string; + oldRole: string; + newRole: string; + }; } interface UpdateUserDeniedPermissions { - type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS, - metadata: { - userId: string; - email: string; - deniedPermissions: { - environmentSlug: string; - ability: string; - }[] - } + type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS; + metadata: { + userId: string; + email: string; + deniedPermissions: { + environmentSlug: string; + ability: string; + }[]; + }; } - -export type Event = - | GetSecretsEvent - | GetSecretEvent - | CreateSecretEvent - | UpdateSecretEvent - | DeleteSecretEvent - | GetWorkspaceKeyEvent - | AuthorizeIntegrationEvent - | UnauthorizeIntegrationEvent - | CreateIntegrationEvent - | DeleteIntegrationEvent - | AddTrustedIPEvent - | UpdateTrustedIPEvent - | DeleteTrustedIPEvent - | CreateServiceTokenEvent - | DeleteServiceTokenEvent - | CreateServiceTokenV3Event - | UpdateServiceTokenV3Event - | DeleteServiceTokenV3Event - | CreateEnvironmentEvent - | UpdateEnvironmentEvent - | DeleteEnvironmentEvent - | AddWorkspaceMemberEvent - | RemoveWorkspaceMemberEvent - | CreateFolderEvent - | UpdateFolderEvent - | DeleteFolderEvent - | CreateWebhookEvent - | UpdateWebhookStatusEvent - | DeleteWebhookEvent - | GetSecretImportsEvent - | CreateSecretImportEvent - | UpdateSecretImportEvent - | DeleteSecretImportEvent - | UpdateUserRole - | UpdateUserDeniedPermissions; +export type Event = + | GetSecretsEvent + | GetSecretEvent + | CreateSecretEvent + | UpdateSecretEvent + | DeleteSecretEvent + | GetWorkspaceKeyEvent + | AuthorizeIntegrationEvent + | UnauthorizeIntegrationEvent + | CreateIntegrationEvent + | DeleteIntegrationEvent + | AddTrustedIPEvent + | UpdateTrustedIPEvent + | DeleteTrustedIPEvent + | CreateServiceTokenEvent + | DeleteServiceTokenEvent + | CreateServiceTokenV3Event + | UpdateServiceTokenV3Event + | DeleteServiceTokenV3Event + | CreateEnvironmentEvent + | UpdateEnvironmentEvent + | DeleteEnvironmentEvent + | AddWorkspaceMemberEvent + | RemoveWorkspaceMemberEvent + | CreateFolderEvent + | UpdateFolderEvent + | DeleteFolderEvent + | CreateWebhookEvent + | UpdateWebhookStatusEvent + | DeleteWebhookEvent + | GetSecretImportsEvent + | CreateSecretImportEvent + | UpdateSecretImportEvent + | DeleteSecretImportEvent + | UpdateUserRole + | UpdateUserDeniedPermissions; export type AuditLog = { _id: string; actor: Actor; - organization: string; + organization: string; workspace: string; ipAddress: string; event: Event; @@ -454,14 +446,13 @@ export type AuditLog = { userAgentType: UserAgentType; createdAt: string; updatedAt: string; -} +}; export type AuditLogFilters = { - eventType?: EventType; - userAgentType?: UserAgentType; - actor?: string; - offset: number; - limit: number; - startDate?: Date; - endDate?: Date; -} \ No newline at end of file + eventType?: EventType; + userAgentType?: UserAgentType; + actor?: string; + limit: number; + startDate?: Date; + endDate?: Date; +}; diff --git a/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx index ce3ce4ece..d0a09688d 100644 --- a/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx +++ b/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx @@ -8,7 +8,7 @@ export const AuditLogsPage = withProjectPermission( return (
-
+

Audit Logs

diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx index ddc271ef0..b48cce4e3 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx @@ -3,13 +3,7 @@ import { Control, Controller, UseFormReset } from "react-hook-form"; import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - Button, - DatePicker, - FormControl, - Select, - SelectItem -} from "@app/components/v2"; +import { Button, DatePicker, FormControl, Select, SelectItem } from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { useGetAuditLogActorFilterOpts } from "@app/hooks/api"; import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants"; @@ -19,201 +13,207 @@ import { Actor } from "@app/hooks/api/auditLogs/types"; import { AuditLogFilterFormData } from "./types"; const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value })); -const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({ label, value })); +const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({ + label, + value +})); type Props = { - control: Control; - reset: UseFormReset; -} + control: Control; + reset: UseFormReset; +}; -export const LogsFilter = ({ - control, - reset -}: Props) => { - const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false); - const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false); +export const LogsFilter = ({ control, reset }: Props) => { + const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false); + const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false); - const { currentWorkspace } = useWorkspace(); - const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? ""); - - const renderActorSelectItem = (actor: Actor) => { - switch (actor.type) { - case ActorType.USER: - return ( - - {actor.metadata.email} - - ); - case ActorType.SERVICE: - return ( - - {actor.metadata.name} - - ); - case ActorType.SERVICE_V3: - return ( - - {actor.metadata.name} - - ); - default: - return ( - - N/A - - ); - } + const { currentWorkspace } = useWorkspace(); + const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? ""); + const renderActorSelectItem = (actor: Actor) => { + switch (actor.type) { + case ActorType.USER: + return ( + + {actor.metadata.email} + + ); + case ActorType.SERVICE: + return ( + + {actor.metadata.name} + + ); + case ActorType.SERVICE_V3: + return ( + + {actor.metadata.name} + + ); + default: + return ( + + N/A + + ); } + }; - return ( -
-
- ( - - - - )} - /> - {!isLoading && data && data.length > 0 && ( - ( - - - - )} - /> - )} - ( - - - - )} - /> - { - return ( - - { - onChange(date); - setIsStartDatePickerOpen(false); - }} - popUpProps={{ - open: isStartDatePickerOpen, - onOpenChange: setIsStartDatePickerOpen - }} - popUpContentProps={{}} - /> - - ); - }} - /> - { - return ( - - { - onChange(date); - setIsEndDatePickerOpen(false); - }} - popUpProps={{ - open: isEndDatePickerOpen, - onOpenChange: setIsEndDatePickerOpen - }} - popUpContentProps={{}} - /> - - ); - }} - /> -
-
- +
+
+ ); +}; diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx index 898943f87..f21de95bb 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsSection.tsx @@ -13,78 +13,54 @@ import { LogsTable } from "./LogsTable"; import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types"; export const LogsSection = () => { - const { subscription } = useSubscription(); - const router = useRouter(); - - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ - "upgradePlan" - ] as const); - - const { - control, - reset, - watch, - setValue, - } = useForm({ - resolver: yupResolver(auditLogFilterFormSchema), - defaultValues: { - page: 1, - perPage: 10 - } - }); - - useEffect(() => { - if (subscription && !subscription.auditLogs) { - handlePopUpOpen("upgradePlan"); - } - }, [subscription]); + const { subscription } = useSubscription(); + const router = useRouter(); - const eventType = watch("eventType") as EventType | undefined; - const userAgentType = watch("userAgentType") as UserAgentType | undefined; - const actor = watch("actor"); + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); - const startDate = watch("startDate"); - const endDate = watch("endDate"); + const { control, reset, watch } = useForm({ + resolver: yupResolver(auditLogFilterFormSchema), + defaultValues: { + page: 1, + perPage: 10 + } + }); - const page = watch("page") as number; - const perPage = watch("perPage") as number; - - return ( -
- {/*
-

- Audit Logs -

-
*/} - - - { - - if (!isOpen) { - router.back(); - return; - } + useEffect(() => { + if (subscription && !subscription.auditLogs) { + handlePopUpOpen("upgradePlan"); + } + }, [subscription]); - handlePopUpToggle("upgradePlan", isOpen) - }} - text="You can use audit logs if you switch to a paid Infisical plan." - /> -
- ); - } \ No newline at end of file + const eventType = watch("eventType") as EventType | undefined; + const userAgentType = watch("userAgentType") as UserAgentType | undefined; + const actor = watch("actor"); + + const startDate = watch("startDate"); + const endDate = watch("endDate"); + + return ( +
+ + + { + if (!isOpen) { + router.back(); + return; + } + + handlePopUpToggle("upgradePlan", isOpen); + }} + text="You can use audit logs if you switch to a paid Infisical plan." + /> +
+ ); +}; diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsTable.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsTable.tsx index f6d38bf7c..273347ae2 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsTable.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsTable.tsx @@ -1,96 +1,95 @@ +import { Fragment } from "react"; import { faFile } from "@fortawesome/free-solid-svg-icons"; import { - EmptyState, - Pagination, - Table, - TableContainer, - TableSkeleton, - TBody, - Td, - Th, - THead, - Tr} from "@app/components/v2"; + Button, + EmptyState, + Table, + TableContainer, + TableSkeleton, + TBody, + Td, + Th, + THead, + Tr +} from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { useGetAuditLogs } from "@app/hooks/api"; import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; import { LogsTableRow } from "./LogsTableRow"; -import { SetValueType } from "./types"; type Props = { - eventType?: EventType; - userAgentType?: UserAgentType; - actor?: string; - startDate?: Date; - endDate?: Date; - page: number; - perPage: number; - setValue: SetValueType; -} + eventType?: EventType; + userAgentType?: UserAgentType; + actor?: string; + startDate?: Date; + endDate?: Date; +}; -export const LogsTable = ({ - eventType, - userAgentType, - actor, - startDate, - endDate, - page, - perPage, - setValue -}: Props) => { - const { currentWorkspace } = useWorkspace(); - const { data, isLoading } = useGetAuditLogs(currentWorkspace?._id ?? "", { - eventType, - userAgentType, - actor, - startDate, - endDate, - offset: (page - 1) * perPage, - limit: perPage - }); - - return ( - - - - - - - - - - - - - {!isLoading && data?.auditLogs && data?.auditLogs.map((auditLog) => ( - - ))} - {isLoading && } - {!isLoading && data?.auditLogs && data?.auditLogs.length === 0 && ( - - - - )} - -
TimestampEventActorSourceMetadata
- -
- {!isLoading && data?.totalCount !== undefined && ( - setValue("page", newPage)} - onChangePerPage={(newPerPage) => setValue("perPage", newPerPage)} - /> +const AUDIT_LOG_LIMIT = 15; + +export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate }: Props) => { + const { currentWorkspace } = useWorkspace(); + const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs( + currentWorkspace?._id ?? "", + { + eventType, + userAgentType, + actor, + startDate, + endDate, + limit: AUDIT_LOG_LIMIT + } + ); + + const isEmpty = !isLoading && !data?.pages?.[0].length; + + return ( +
+ + + + + + + + + + + + + {!isLoading && + data?.pages?.map((group, i) => ( + + {group.map((auditLog) => ( + + ))} + + ))} + {isLoading && } + {isEmpty && ( + + + )} - - ); -} \ No newline at end of file + +
TimestampEventActorSourceMetadata
+ +
+
+ {!isEmpty && ( + + )} +
+ ); +};