mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 12:26:05 +00:00
feat(audit-log): added index over workspace key and changed pagination to load more type
This commit is contained in:
@@ -611,14 +611,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
};
|
};
|
||||||
|
|
||||||
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
|
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
|
||||||
|
|
||||||
const totalCount = await AuditLog.countDocuments(query);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
auditLogs,
|
auditLogs
|
||||||
totalCount
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -679,11 +674,7 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const actors = [
|
const actors = [...userActors, ...serviceActors, ...serviceV3Actors];
|
||||||
...userActors,
|
|
||||||
...serviceActors,
|
|
||||||
...serviceV3Actors
|
|
||||||
];
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
actors
|
actors
|
||||||
|
|||||||
@@ -1,76 +1,70 @@
|
|||||||
import { Schema, Types, model } from "mongoose";
|
import { Schema, Types, model } from "mongoose";
|
||||||
import {
|
import { ActorType, EventType, UserAgentType } from "./enums";
|
||||||
ActorType,
|
import { Actor, Event } from "./types";
|
||||||
EventType,
|
|
||||||
UserAgentType
|
|
||||||
} from "./enums";
|
|
||||||
import {
|
|
||||||
Actor,
|
|
||||||
Event
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
export interface IAuditLog {
|
export interface IAuditLog {
|
||||||
actor: Actor;
|
actor: Actor;
|
||||||
organization: Types.ObjectId;
|
organization: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
event: Event;
|
event: Event;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
userAgentType: UserAgentType;
|
userAgentType: UserAgentType;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auditLogSchema = new Schema<IAuditLog>(
|
const auditLogSchema = new Schema<IAuditLog>(
|
||||||
{
|
{
|
||||||
actor: {
|
actor: {
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ActorType,
|
enum: ActorType,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
type: Schema.Types.Mixed
|
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
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
organization: {
|
||||||
timestamps: true
|
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<IAuditLog>("AuditLog", auditLogSchema);
|
export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const validateClientForWorkspace = async ({
|
|||||||
environment,
|
environment,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
return { membership, workspace};
|
return { membership, workspace };
|
||||||
case ActorType.SERVICE_V3:
|
case ActorType.SERVICE_V3:
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for organization"
|
message: "Failed service token authorization for organization"
|
||||||
@@ -121,8 +121,8 @@ export const GetWorkspaceAuditLogsV1 = z.object({
|
|||||||
userAgentType: z.nativeEnum(UserAgentType).nullable().optional(),
|
userAgentType: z.nativeEnum(UserAgentType).nullable().optional(),
|
||||||
startDate: z.string().datetime().nullable().optional(),
|
startDate: z.string().datetime().nullable().optional(),
|
||||||
endDate: z.string().datetime().nullable().optional(),
|
endDate: z.string().datetime().nullable().optional(),
|
||||||
offset: z.coerce.number(),
|
offset: z.coerce.number().default(0),
|
||||||
limit: z.coerce.number(),
|
limit: z.coerce.number().default(20),
|
||||||
actor: z.string().nullish().optional()
|
actor: z.string().nullish().optional()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,59 +1,46 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import {
|
import { Actor, AuditLog, AuditLogFilters } from "./types";
|
||||||
Actor,
|
|
||||||
AuditLog,
|
|
||||||
AuditLogFilters
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
export const workspaceKeys = {
|
export const workspaceKeys = {
|
||||||
getAuditLogs: (workspaceId: string, filters: AuditLogFilters) => [{ workspaceId, filters }, "audit-logs"] as const,
|
getAuditLogs: (workspaceId: string, filters: AuditLogFilters) =>
|
||||||
getAuditLogActorFilterOpts: (workspaceId: string) => [{ workspaceId }, "audit-log-actor-filters"] as const
|
[{ workspaceId, filters }, "audit-logs"] as const,
|
||||||
}
|
getAuditLogActorFilterOpts: (workspaceId: string) =>
|
||||||
|
[{ workspaceId }, "audit-log-actor-filters"] as const
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) => {
|
export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) => {
|
||||||
return useQuery({
|
return useInfiniteQuery({
|
||||||
queryKey: workspaceKeys.getAuditLogs(workspaceId, filters),
|
queryKey: workspaceKeys.getAuditLogs(workspaceId, filters),
|
||||||
queryFn: async () => {
|
queryFn: async ({ pageParam }) => {
|
||||||
|
const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(
|
||||||
const params = new URLSearchParams();
|
`/api/v1/workspace/${workspaceId}/audit-logs`,
|
||||||
if (filters.eventType) {
|
{
|
||||||
params.append("eventType", filters.eventType);
|
params: {
|
||||||
}
|
...filters,
|
||||||
|
offset: pageParam,
|
||||||
if (filters.userAgentType) {
|
startDate: filters?.startDate?.toISOString(),
|
||||||
params.append("userAgentType", filters.userAgentType);
|
endDate: filters?.endDate?.toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
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 data.auditLogs;
|
||||||
|
},
|
||||||
|
getNextPageParam: (lastPage, pages) =>
|
||||||
|
lastPage.length !== 0 ? pages.length * filters.limit : undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetAuditLogActorFilterOpts = (workspaceId: string) => {
|
export const useGetAuditLogActorFilterOpts = (workspaceId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: workspaceKeys.getAuditLogActorFilterOpts(workspaceId),
|
queryKey: workspaceKeys.getAuditLogActorFilterOpts(workspaceId),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.get<{ actors: Actor[] }>(`/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`);
|
const { data } = await apiRequest.get<{ actors: Actor[] }>(
|
||||||
return data.actors;
|
`/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`
|
||||||
}
|
);
|
||||||
});
|
return data.actors;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import {
|
import { ActorType, EventType, UserAgentType } from "./enums";
|
||||||
ActorType,
|
|
||||||
EventType,
|
|
||||||
UserAgentType
|
|
||||||
} from "./enums";
|
|
||||||
|
|
||||||
enum Permission {
|
enum Permission {
|
||||||
READ = "read",
|
READ = "read",
|
||||||
READ_WRITE = "readWrite"
|
READ_WRITE = "readWrite"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Scope {
|
interface Scope {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
permission: Permission;
|
permission: Permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -36,423 +32,419 @@ export interface ServiceActor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceActorV3 {
|
export interface ServiceActorV3 {
|
||||||
type: ActorType.SERVICE_V3;
|
type: ActorType.SERVICE_V3;
|
||||||
metadata: ServiceActorMetadata;
|
metadata: ServiceActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Actor =
|
export type Actor = UserActor | ServiceActor | ServiceActorV3;
|
||||||
| UserActor
|
|
||||||
| ServiceActor
|
|
||||||
| ServiceActorV3;
|
|
||||||
|
|
||||||
interface GetSecretsEvent {
|
interface GetSecretsEvent {
|
||||||
type: EventType.GET_SECRETS;
|
type: EventType.GET_SECRETS;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
numberOfSecrets: number;
|
numberOfSecrets: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetSecretEvent {
|
interface GetSecretEvent {
|
||||||
type: EventType.GET_SECRET;
|
type: EventType.GET_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateSecretEvent {
|
interface CreateSecretEvent {
|
||||||
type: EventType.CREATE_SECRET;
|
type: EventType.CREATE_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSecretEvent {
|
interface UpdateSecretEvent {
|
||||||
type: EventType.UPDATE_SECRET;
|
type: EventType.UPDATE_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteSecretEvent {
|
interface DeleteSecretEvent {
|
||||||
type: EventType.DELETE_SECRET;
|
type: EventType.DELETE_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetWorkspaceKeyEvent {
|
interface GetWorkspaceKeyEvent {
|
||||||
type: EventType.GET_WORKSPACE_KEY,
|
type: EventType.GET_WORKSPACE_KEY;
|
||||||
metadata: {
|
metadata: {
|
||||||
keyId: string;
|
keyId: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthorizeIntegrationEvent {
|
interface AuthorizeIntegrationEvent {
|
||||||
type: EventType.AUTHORIZE_INTEGRATION;
|
type: EventType.AUTHORIZE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integration: string;
|
integration: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UnauthorizeIntegrationEvent {
|
interface UnauthorizeIntegrationEvent {
|
||||||
type: EventType.UNAUTHORIZE_INTEGRATION;
|
type: EventType.UNAUTHORIZE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integration: string;
|
integration: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateIntegrationEvent {
|
interface CreateIntegrationEvent {
|
||||||
type: EventType.CREATE_INTEGRATION;
|
type: EventType.CREATE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integrationId: string;
|
integrationId: string;
|
||||||
integration: string;
|
integration: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
app?: string;
|
app?: string;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
targetEnvironmentId?: string;
|
targetEnvironmentId?: string;
|
||||||
targetService?: string;
|
targetService?: string;
|
||||||
targetServiceId?: string;
|
targetServiceId?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteIntegrationEvent {
|
interface DeleteIntegrationEvent {
|
||||||
type: EventType.DELETE_INTEGRATION;
|
type: EventType.DELETE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integrationId: string;
|
integrationId: string;
|
||||||
integration: string;
|
integration: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
app?: string;
|
app?: string;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
targetEnvironmentId?: string;
|
targetEnvironmentId?: string;
|
||||||
targetService?: string;
|
targetService?: string;
|
||||||
targetServiceId?: string;
|
targetServiceId?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddTrustedIPEvent {
|
interface AddTrustedIPEvent {
|
||||||
type: EventType.ADD_TRUSTED_IP;
|
type: EventType.ADD_TRUSTED_IP;
|
||||||
metadata: {
|
metadata: {
|
||||||
trustedIpId: string;
|
trustedIpId: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
prefix?: number;
|
prefix?: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateTrustedIPEvent {
|
interface UpdateTrustedIPEvent {
|
||||||
type: EventType.UPDATE_TRUSTED_IP;
|
type: EventType.UPDATE_TRUSTED_IP;
|
||||||
metadata: {
|
metadata: {
|
||||||
trustedIpId: string;
|
trustedIpId: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
prefix?: number;
|
prefix?: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteTrustedIPEvent {
|
interface DeleteTrustedIPEvent {
|
||||||
type: EventType.DELETE_TRUSTED_IP;
|
type: EventType.DELETE_TRUSTED_IP;
|
||||||
metadata: {
|
metadata: {
|
||||||
trustedIpId: string;
|
trustedIpId: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
prefix?: number;
|
prefix?: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateServiceTokenEvent {
|
interface CreateServiceTokenEvent {
|
||||||
type: EventType.CREATE_SERVICE_TOKEN;
|
type: EventType.CREATE_SERVICE_TOKEN;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
scopes: Array<{
|
scopes: Array<{
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteServiceTokenEvent {
|
interface DeleteServiceTokenEvent {
|
||||||
type: EventType.DELETE_SERVICE_TOKEN;
|
type: EventType.DELETE_SERVICE_TOKEN;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
scopes: Array<{
|
scopes: Array<{
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateServiceTokenV3Event {
|
interface CreateServiceTokenV3Event {
|
||||||
type: EventType.CREATE_SERVICE_TOKEN_V3;
|
type: EventType.CREATE_SERVICE_TOKEN_V3;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
scopes: Array<Scope>;
|
scopes: Array<Scope>;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateServiceTokenV3Event {
|
interface UpdateServiceTokenV3Event {
|
||||||
type: EventType.UPDATE_SERVICE_TOKEN_V3;
|
type: EventType.UPDATE_SERVICE_TOKEN_V3;
|
||||||
metadata: {
|
metadata: {
|
||||||
name?: string;
|
name?: string;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
scopes?: Array<Scope>;
|
scopes?: Array<Scope>;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteServiceTokenV3Event {
|
interface DeleteServiceTokenV3Event {
|
||||||
type: EventType.DELETE_SERVICE_TOKEN_V3;
|
type: EventType.DELETE_SERVICE_TOKEN_V3;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
scopes: Array<Scope>;
|
scopes: Array<Scope>;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateEnvironmentEvent {
|
interface CreateEnvironmentEvent {
|
||||||
type: EventType.CREATE_ENVIRONMENT;
|
type: EventType.CREATE_ENVIRONMENT;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateEnvironmentEvent {
|
interface UpdateEnvironmentEvent {
|
||||||
type: EventType.UPDATE_ENVIRONMENT;
|
type: EventType.UPDATE_ENVIRONMENT;
|
||||||
metadata: {
|
metadata: {
|
||||||
oldName: string;
|
oldName: string;
|
||||||
newName: string;
|
newName: string;
|
||||||
oldSlug: string;
|
oldSlug: string;
|
||||||
newSlug: string;
|
newSlug: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteEnvironmentEvent {
|
interface DeleteEnvironmentEvent {
|
||||||
type: EventType.DELETE_ENVIRONMENT;
|
type: EventType.DELETE_ENVIRONMENT;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddWorkspaceMemberEvent {
|
interface AddWorkspaceMemberEvent {
|
||||||
type: EventType.ADD_WORKSPACE_MEMBER;
|
type: EventType.ADD_WORKSPACE_MEMBER;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoveWorkspaceMemberEvent {
|
interface RemoveWorkspaceMemberEvent {
|
||||||
type: EventType.REMOVE_WORKSPACE_MEMBER;
|
type: EventType.REMOVE_WORKSPACE_MEMBER;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateFolderEvent {
|
interface CreateFolderEvent {
|
||||||
type: EventType.CREATE_FOLDER;
|
type: EventType.CREATE_FOLDER;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
folderName: string;
|
folderName: string;
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateFolderEvent {
|
interface UpdateFolderEvent {
|
||||||
type: EventType.UPDATE_FOLDER;
|
type: EventType.UPDATE_FOLDER;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
oldFolderName: string;
|
oldFolderName: string;
|
||||||
newFolderName: string;
|
newFolderName: string;
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteFolderEvent {
|
interface DeleteFolderEvent {
|
||||||
type: EventType.DELETE_FOLDER;
|
type: EventType.DELETE_FOLDER;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
folderName: string;
|
folderName: string;
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateWebhookEvent {
|
interface CreateWebhookEvent {
|
||||||
type: EventType.CREATE_WEBHOOK,
|
type: EventType.CREATE_WEBHOOK;
|
||||||
metadata: {
|
metadata: {
|
||||||
webhookId: string;
|
webhookId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateWebhookStatusEvent {
|
interface UpdateWebhookStatusEvent {
|
||||||
type: EventType.UPDATE_WEBHOOK_STATUS,
|
type: EventType.UPDATE_WEBHOOK_STATUS;
|
||||||
metadata: {
|
metadata: {
|
||||||
webhookId: string;
|
webhookId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteWebhookEvent {
|
interface DeleteWebhookEvent {
|
||||||
type: EventType.DELETE_WEBHOOK,
|
type: EventType.DELETE_WEBHOOK;
|
||||||
metadata: {
|
metadata: {
|
||||||
webhookId: string;
|
webhookId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetSecretImportsEvent {
|
interface GetSecretImportsEvent {
|
||||||
type: EventType.GET_SECRET_IMPORTS,
|
type: EventType.GET_SECRET_IMPORTS;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
numberOfImports: number;
|
numberOfImports: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateSecretImportEvent {
|
interface CreateSecretImportEvent {
|
||||||
type: EventType.CREATE_SECRET_IMPORT,
|
type: EventType.CREATE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
importFromEnvironment: string;
|
importFromEnvironment: string;
|
||||||
importFromSecretPath: string;
|
importFromSecretPath: string;
|
||||||
importToEnvironment: string;
|
importToEnvironment: string;
|
||||||
importToSecretPath: string;
|
importToSecretPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSecretImportEvent {
|
interface UpdateSecretImportEvent {
|
||||||
type: EventType.UPDATE_SECRET_IMPORT,
|
type: EventType.UPDATE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
importToEnvironment: string;
|
importToEnvironment: string;
|
||||||
importToSecretPath: string;
|
importToSecretPath: string;
|
||||||
orderBefore: {
|
orderBefore: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}[],
|
}[];
|
||||||
orderAfter: {
|
orderAfter: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}[]
|
}[];
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteSecretImportEvent {
|
interface DeleteSecretImportEvent {
|
||||||
type: EventType.DELETE_SECRET_IMPORT,
|
type: EventType.DELETE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
importFromEnvironment: string;
|
importFromEnvironment: string;
|
||||||
importFromSecretPath: string;
|
importFromSecretPath: string;
|
||||||
importToEnvironment: string;
|
importToEnvironment: string;
|
||||||
importToSecretPath: string;
|
importToSecretPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateUserRole {
|
interface UpdateUserRole {
|
||||||
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
|
type: EventType.UPDATE_USER_WORKSPACE_ROLE;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
oldRole: string;
|
oldRole: string;
|
||||||
newRole: string;
|
newRole: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateUserDeniedPermissions {
|
interface UpdateUserDeniedPermissions {
|
||||||
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS,
|
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
deniedPermissions: {
|
deniedPermissions: {
|
||||||
environmentSlug: string;
|
environmentSlug: string;
|
||||||
ability: string;
|
ability: string;
|
||||||
}[]
|
}[];
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
| CreateSecretEvent
|
| CreateSecretEvent
|
||||||
| UpdateSecretEvent
|
| UpdateSecretEvent
|
||||||
| DeleteSecretEvent
|
| DeleteSecretEvent
|
||||||
| GetWorkspaceKeyEvent
|
| GetWorkspaceKeyEvent
|
||||||
| AuthorizeIntegrationEvent
|
| AuthorizeIntegrationEvent
|
||||||
| UnauthorizeIntegrationEvent
|
| UnauthorizeIntegrationEvent
|
||||||
| CreateIntegrationEvent
|
| CreateIntegrationEvent
|
||||||
| DeleteIntegrationEvent
|
| DeleteIntegrationEvent
|
||||||
| AddTrustedIPEvent
|
| AddTrustedIPEvent
|
||||||
| UpdateTrustedIPEvent
|
| UpdateTrustedIPEvent
|
||||||
| DeleteTrustedIPEvent
|
| DeleteTrustedIPEvent
|
||||||
| CreateServiceTokenEvent
|
| CreateServiceTokenEvent
|
||||||
| DeleteServiceTokenEvent
|
| DeleteServiceTokenEvent
|
||||||
| CreateServiceTokenV3Event
|
| CreateServiceTokenV3Event
|
||||||
| UpdateServiceTokenV3Event
|
| UpdateServiceTokenV3Event
|
||||||
| DeleteServiceTokenV3Event
|
| DeleteServiceTokenV3Event
|
||||||
| CreateEnvironmentEvent
|
| CreateEnvironmentEvent
|
||||||
| UpdateEnvironmentEvent
|
| UpdateEnvironmentEvent
|
||||||
| DeleteEnvironmentEvent
|
| DeleteEnvironmentEvent
|
||||||
| AddWorkspaceMemberEvent
|
| AddWorkspaceMemberEvent
|
||||||
| RemoveWorkspaceMemberEvent
|
| RemoveWorkspaceMemberEvent
|
||||||
| CreateFolderEvent
|
| CreateFolderEvent
|
||||||
| UpdateFolderEvent
|
| UpdateFolderEvent
|
||||||
| DeleteFolderEvent
|
| DeleteFolderEvent
|
||||||
| CreateWebhookEvent
|
| CreateWebhookEvent
|
||||||
| UpdateWebhookStatusEvent
|
| UpdateWebhookStatusEvent
|
||||||
| DeleteWebhookEvent
|
| DeleteWebhookEvent
|
||||||
| GetSecretImportsEvent
|
| GetSecretImportsEvent
|
||||||
| CreateSecretImportEvent
|
| CreateSecretImportEvent
|
||||||
| UpdateSecretImportEvent
|
| UpdateSecretImportEvent
|
||||||
| DeleteSecretImportEvent
|
| DeleteSecretImportEvent
|
||||||
| UpdateUserRole
|
| UpdateUserRole
|
||||||
| UpdateUserDeniedPermissions;
|
| UpdateUserDeniedPermissions;
|
||||||
|
|
||||||
export type AuditLog = {
|
export type AuditLog = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -465,14 +457,13 @@ export type AuditLog = {
|
|||||||
userAgentType: UserAgentType;
|
userAgentType: UserAgentType;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type AuditLogFilters = {
|
export type AuditLogFilters = {
|
||||||
eventType?: EventType;
|
eventType?: EventType;
|
||||||
userAgentType?: UserAgentType;
|
userAgentType?: UserAgentType;
|
||||||
actor?: string;
|
actor?: string;
|
||||||
offset: number;
|
limit: number;
|
||||||
limit: number;
|
startDate?: Date;
|
||||||
startDate?: Date;
|
endDate?: Date;
|
||||||
endDate?: Date;
|
};
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const AuditLogsPage = withProjectPermission(
|
|||||||
return (
|
return (
|
||||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||||
<div className="max-w-7xl px-6 w-full">
|
<div className="max-w-7xl px-6 w-full">
|
||||||
<div className="my-6">
|
<div className="py-6 sticky top-0 z-10 bg-bunker-800">
|
||||||
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
|
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,13 +3,7 @@ import { Control, Controller, UseFormReset } from "react-hook-form";
|
|||||||
import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import { Button, DatePicker, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||||
Button,
|
|
||||||
DatePicker,
|
|
||||||
FormControl,
|
|
||||||
Select,
|
|
||||||
SelectItem
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useGetAuditLogActorFilterOpts } from "@app/hooks/api";
|
import { useGetAuditLogActorFilterOpts } from "@app/hooks/api";
|
||||||
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
|
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";
|
import { AuditLogFilterFormData } from "./types";
|
||||||
|
|
||||||
const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value }));
|
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 = {
|
type Props = {
|
||||||
control: Control<AuditLogFilterFormData>;
|
control: Control<AuditLogFilterFormData>;
|
||||||
reset: UseFormReset<AuditLogFilterFormData>;
|
reset: UseFormReset<AuditLogFilterFormData>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const LogsFilter = ({
|
export const LogsFilter = ({ control, reset }: Props) => {
|
||||||
control,
|
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
||||||
reset
|
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
||||||
}: Props) => {
|
|
||||||
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
|
||||||
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? "");
|
const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? "");
|
||||||
|
|
||||||
const renderActorSelectItem = (actor: Actor) => {
|
|
||||||
switch (actor.type) {
|
|
||||||
case ActorType.USER:
|
|
||||||
return (
|
|
||||||
<SelectItem value={`${actor.type}-${actor.metadata.userId}`} key={`user-actor-filter-${actor.metadata.userId}`}>
|
|
||||||
{actor.metadata.email}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
case ActorType.SERVICE:
|
|
||||||
return (
|
|
||||||
<SelectItem value={`${actor.type}-${actor.metadata.serviceId}`} key={`service-actor-filter-${actor.metadata.serviceId}`}>
|
|
||||||
{actor.metadata.name}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
case ActorType.SERVICE_V3:
|
|
||||||
return (
|
|
||||||
<SelectItem value={`${actor.type}-${actor.metadata.serviceId}`} key={`service-actor-v3-filter-${actor.metadata.serviceId}`}>
|
|
||||||
{actor.metadata.name}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<SelectItem value="actor-none" key="actor-none">
|
|
||||||
N/A
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const renderActorSelectItem = (actor: Actor) => {
|
||||||
|
switch (actor.type) {
|
||||||
|
case ActorType.USER:
|
||||||
|
return (
|
||||||
|
<SelectItem
|
||||||
|
value={`${actor.type}-${actor.metadata.userId}`}
|
||||||
|
key={`user-actor-filter-${actor.metadata.userId}`}
|
||||||
|
>
|
||||||
|
{actor.metadata.email}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
case ActorType.SERVICE:
|
||||||
|
return (
|
||||||
|
<SelectItem
|
||||||
|
value={`${actor.type}-${actor.metadata.serviceId}`}
|
||||||
|
key={`service-actor-filter-${actor.metadata.serviceId}`}
|
||||||
|
>
|
||||||
|
{actor.metadata.name}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
case ActorType.SERVICE_V3:
|
||||||
|
return (
|
||||||
|
<SelectItem
|
||||||
|
value={`${actor.type}-${actor.metadata.serviceId}`}
|
||||||
|
key={`service-actor-v3-filter-${actor.metadata.serviceId}`}
|
||||||
|
>
|
||||||
|
{actor.metadata.name}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<SelectItem value="actor-none" key="actor-none">
|
||||||
|
N/A
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center sticky top-20 z-10 bg-bunker-800">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="eventType"
|
name="eventType"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Event"
|
label="Event"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
className="w-40 mr-4"
|
className="w-40 mr-4"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => onChange(e)}
|
||||||
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
|
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
|
||||||
>
|
>
|
||||||
{eventTypes.map(({ label, value }) => (
|
{eventTypes.map(({ label, value }) => (
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
{label}
|
{label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{!isLoading && data && data.length > 0 && (
|
{!isLoading && data && data.length > 0 && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="actor"
|
name="actor"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Actor"
|
label="Actor"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
className="w-40 mr-4"
|
className="w-40 mr-4"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => onChange(e)}
|
||||||
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
|
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
|
||||||
>
|
|
||||||
{data.map((actor) => renderActorSelectItem(actor))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="userAgentType"
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Source"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
className="w-40 mr-4"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
|
|
||||||
>
|
|
||||||
{userAgentTypes.map(({ label, value }) => (
|
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
|
||||||
{label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
name="startDate"
|
|
||||||
control={control}
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
|
||||||
return (
|
|
||||||
<FormControl
|
|
||||||
label="Start date"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
className="mr-4"
|
|
||||||
>
|
|
||||||
<DatePicker
|
|
||||||
value={field.value || undefined}
|
|
||||||
onChange={date => {
|
|
||||||
onChange(date);
|
|
||||||
setIsStartDatePickerOpen(false);
|
|
||||||
}}
|
|
||||||
popUpProps={{
|
|
||||||
open: isStartDatePickerOpen,
|
|
||||||
onOpenChange: setIsStartDatePickerOpen
|
|
||||||
}}
|
|
||||||
popUpContentProps={{}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
name="endDate"
|
|
||||||
control={control}
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
|
||||||
return (
|
|
||||||
<FormControl
|
|
||||||
label="End date"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<DatePicker
|
|
||||||
value={field.value || undefined}
|
|
||||||
onChange={date => {
|
|
||||||
onChange(date);
|
|
||||||
setIsEndDatePickerOpen(false);
|
|
||||||
}}
|
|
||||||
popUpProps={{
|
|
||||||
open: isEndDatePickerOpen,
|
|
||||||
onOpenChange: setIsEndDatePickerOpen
|
|
||||||
}}
|
|
||||||
popUpContentProps={{}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
isLoading={false}
|
|
||||||
colorSchema="primary"
|
|
||||||
variant="outline_bg"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
|
|
||||||
onClick={() => reset({
|
|
||||||
eventType: undefined,
|
|
||||||
actor: undefined,
|
|
||||||
userAgentType: undefined,
|
|
||||||
startDate: undefined,
|
|
||||||
endDate: undefined
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
Clear filters
|
{data.map((actor) => renderActorSelectItem(actor))}
|
||||||
</Button>
|
</Select>
|
||||||
</div>
|
</FormControl>
|
||||||
</div>
|
)}
|
||||||
);
|
/>
|
||||||
}
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="userAgentType"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Source"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="w-40 mr-4"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
|
||||||
|
>
|
||||||
|
{userAgentTypes.map(({ label, value }) => (
|
||||||
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
label="Start date"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="mr-4"
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
value={field.value || undefined}
|
||||||
|
onChange={(date) => {
|
||||||
|
onChange(date);
|
||||||
|
setIsStartDatePickerOpen(false);
|
||||||
|
}}
|
||||||
|
popUpProps={{
|
||||||
|
open: isStartDatePickerOpen,
|
||||||
|
onOpenChange: setIsStartDatePickerOpen
|
||||||
|
}}
|
||||||
|
popUpContentProps={{}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="endDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl label="End date" errorText={error?.message} isError={Boolean(error)}>
|
||||||
|
<DatePicker
|
||||||
|
value={field.value || undefined}
|
||||||
|
onChange={(date) => {
|
||||||
|
onChange(date);
|
||||||
|
setIsEndDatePickerOpen(false);
|
||||||
|
}}
|
||||||
|
popUpProps={{
|
||||||
|
open: isEndDatePickerOpen,
|
||||||
|
onOpenChange: setIsEndDatePickerOpen
|
||||||
|
}}
|
||||||
|
popUpContentProps={{}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
isLoading={false}
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
|
||||||
|
onClick={() =>
|
||||||
|
reset({
|
||||||
|
eventType: undefined,
|
||||||
|
actor: undefined,
|
||||||
|
userAgentType: undefined,
|
||||||
|
startDate: undefined,
|
||||||
|
endDate: undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Clear filters
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,78 +13,54 @@ import { LogsTable } from "./LogsTable";
|
|||||||
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
||||||
|
|
||||||
export const LogsSection = () => {
|
export const LogsSection = () => {
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||||
"upgradePlan"
|
|
||||||
] as const);
|
|
||||||
|
|
||||||
const {
|
const { control, reset, watch } = useForm<AuditLogFilterFormData>({
|
||||||
control,
|
resolver: yupResolver(auditLogFilterFormSchema),
|
||||||
reset,
|
defaultValues: {
|
||||||
watch,
|
page: 1,
|
||||||
setValue,
|
perPage: 10
|
||||||
} = useForm<AuditLogFilterFormData>({
|
}
|
||||||
resolver: yupResolver(auditLogFilterFormSchema),
|
});
|
||||||
defaultValues: {
|
|
||||||
page: 1,
|
|
||||||
perPage: 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (subscription && !subscription.auditLogs) {
|
if (subscription && !subscription.auditLogs) {
|
||||||
handlePopUpOpen("upgradePlan");
|
handlePopUpOpen("upgradePlan");
|
||||||
}
|
}
|
||||||
}, [subscription]);
|
}, [subscription]);
|
||||||
|
|
||||||
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 startDate = watch("startDate");
|
const startDate = watch("startDate");
|
||||||
const endDate = watch("endDate");
|
const endDate = watch("endDate");
|
||||||
|
|
||||||
const page = watch("page") as number;
|
return (
|
||||||
const perPage = watch("perPage") as number;
|
<div>
|
||||||
|
<LogsFilter control={control} reset={reset} />
|
||||||
|
<LogsTable
|
||||||
|
eventType={eventType}
|
||||||
|
userAgentType={userAgentType}
|
||||||
|
actor={actor}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
/>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
router.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
handlePopUpToggle("upgradePlan", isOpen);
|
||||||
<div
|
}}
|
||||||
// className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
text="You can use audit logs if you switch to a paid Infisical plan."
|
||||||
>
|
/>
|
||||||
{/* <div className="flex items-center mb-8">
|
</div>
|
||||||
<h2 className="text-xl font-semibold flex-1 text-white">
|
);
|
||||||
Audit Logs
|
};
|
||||||
</h2>
|
|
||||||
</div> */}
|
|
||||||
<LogsFilter
|
|
||||||
control={control}
|
|
||||||
reset={reset}
|
|
||||||
/>
|
|
||||||
<LogsTable
|
|
||||||
eventType={eventType}
|
|
||||||
userAgentType={userAgentType}
|
|
||||||
actor={actor}
|
|
||||||
startDate={startDate}
|
|
||||||
endDate={endDate}
|
|
||||||
page={page}
|
|
||||||
perPage={perPage}
|
|
||||||
setValue={setValue}
|
|
||||||
/>
|
|
||||||
<UpgradePlanModal
|
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
|
||||||
onOpenChange={(isOpen) => {
|
|
||||||
|
|
||||||
if (!isOpen) {
|
|
||||||
router.back();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpToggle("upgradePlan", isOpen)
|
|
||||||
}}
|
|
||||||
text="You can use audit logs if you switch to a paid Infisical plan."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,96 +1,95 @@
|
|||||||
|
import { Fragment } from "react";
|
||||||
import { faFile } from "@fortawesome/free-solid-svg-icons";
|
import { faFile } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EmptyState,
|
Button,
|
||||||
Pagination,
|
EmptyState,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableSkeleton,
|
TableSkeleton,
|
||||||
TBody,
|
TBody,
|
||||||
Td,
|
Td,
|
||||||
Th,
|
Th,
|
||||||
THead,
|
THead,
|
||||||
Tr} from "@app/components/v2";
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useGetAuditLogs } from "@app/hooks/api";
|
import { useGetAuditLogs } from "@app/hooks/api";
|
||||||
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
||||||
|
|
||||||
import { LogsTableRow } from "./LogsTableRow";
|
import { LogsTableRow } from "./LogsTableRow";
|
||||||
import { SetValueType } from "./types";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
eventType?: EventType;
|
eventType?: EventType;
|
||||||
userAgentType?: UserAgentType;
|
userAgentType?: UserAgentType;
|
||||||
actor?: string;
|
actor?: string;
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
page: number;
|
};
|
||||||
perPage: number;
|
|
||||||
setValue: SetValueType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LogsTable = ({
|
const AUDIT_LOG_LIMIT = 15;
|
||||||
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 (
|
export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate }: Props) => {
|
||||||
<TableContainer>
|
const { currentWorkspace } = useWorkspace();
|
||||||
<Table>
|
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
|
||||||
<THead>
|
currentWorkspace?._id ?? "",
|
||||||
<Tr>
|
{
|
||||||
<Th>Timestamp</Th>
|
eventType,
|
||||||
<Th>Event</Th>
|
userAgentType,
|
||||||
<Th>Actor</Th>
|
actor,
|
||||||
<Th>Source</Th>
|
startDate,
|
||||||
<Th>Metadata</Th>
|
endDate,
|
||||||
</Tr>
|
limit: AUDIT_LOG_LIMIT
|
||||||
</THead>
|
}
|
||||||
<TBody>
|
);
|
||||||
{!isLoading && data?.auditLogs && data?.auditLogs.map((auditLog) => (
|
|
||||||
<LogsTableRow
|
const isEmpty = !isLoading && !data?.pages?.[0].length;
|
||||||
auditLog={auditLog}
|
|
||||||
key={`audit-log-${auditLog._id}`}
|
return (
|
||||||
/>
|
<div>
|
||||||
))}
|
<TableContainer>
|
||||||
{isLoading && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
|
<Table>
|
||||||
{!isLoading && data?.auditLogs && data?.auditLogs.length === 0 && (
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={5}>
|
<Th>Timestamp</Th>
|
||||||
<EmptyState
|
<Th>Event</Th>
|
||||||
title="No audit logs on file"
|
<Th>Actor</Th>
|
||||||
icon={faFile}
|
<Th>Source</Th>
|
||||||
/>
|
<Th>Metadata</Th>
|
||||||
</Td>
|
</Tr>
|
||||||
</Tr>
|
</THead>
|
||||||
)}
|
<TBody>
|
||||||
</TBody>
|
{!isLoading &&
|
||||||
</Table>
|
data?.pages?.map((group, i) => (
|
||||||
{!isLoading && data?.totalCount !== undefined && (
|
<Fragment key={`auditlog-item-${i + 1}`}>
|
||||||
<Pagination
|
{group.map((auditLog) => (
|
||||||
count={data?.totalCount}
|
<LogsTableRow auditLog={auditLog} key={`audit-log-${auditLog._id}`} />
|
||||||
page={page}
|
))}
|
||||||
perPage={perPage}
|
</Fragment>
|
||||||
onChangePage={(newPage) => setValue("page", newPage)}
|
))}
|
||||||
onChangePerPage={(newPerPage) => setValue("perPage", newPerPage)}
|
{isLoading && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
|
||||||
/>
|
{isEmpty && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={5}>
|
||||||
|
<EmptyState title="No audit logs on file" icon={faFile} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
)}
|
)}
|
||||||
</TableContainer>
|
</TBody>
|
||||||
);
|
</Table>
|
||||||
}
|
</TableContainer>
|
||||||
|
{!isEmpty && (
|
||||||
|
<Button
|
||||||
|
className="mt-4 mb-20 py-3 px-4 text-sm"
|
||||||
|
isFullWidth
|
||||||
|
variant="star"
|
||||||
|
isLoading={isFetchingNextPage}
|
||||||
|
isDisabled={isFetchingNextPage || !hasNextPage}
|
||||||
|
onClick={() => fetchNextPage()}
|
||||||
|
>
|
||||||
|
{hasNextPage ? "Load More" : "End of logs"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user