mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 23:29:05 +00:00
Finish preliminary v2 audit logs
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// TODO: deprecate in favor of new audit logs
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Image from "next/image";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: deprecate in favor of new audit logs
|
||||
|
||||
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { EventType, UserAgentType } from "./enums";
|
||||
|
||||
export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.GET_SECRETS]: "Get Secrets",
|
||||
[EventType.GET_SECRET]: "Get Secret",
|
||||
[EventType.CREATE_SECRET]: "Create Secret",
|
||||
[EventType.UPDATE_SECRET]: "Update Secret",
|
||||
[EventType.DELETE_SECRET]: "Delete Secret",
|
||||
};
|
||||
|
||||
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
|
||||
[UserAgentType.WEB]: "Web",
|
||||
[UserAgentType.CLI]: "CLI",
|
||||
[UserAgentType.K8_OPERATOR]: "K8s operator",
|
||||
[UserAgentType.OTHER]: "Other",
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
export enum ActorType {
|
||||
USER = "user",
|
||||
SERVICE = "service"
|
||||
}
|
||||
|
||||
export enum UserAgentType {
|
||||
WEB = "web",
|
||||
CLI = "cli",
|
||||
K8_OPERATOR = "k8-operator",
|
||||
OTHER = "other"
|
||||
}
|
||||
|
||||
export enum EventType {
|
||||
GET_SECRETS = "get-secrets",
|
||||
GET_SECRET = "get-secret",
|
||||
CREATE_SECRET = "create-secret",
|
||||
UPDATE_SECRET = "update-secret",
|
||||
DELETE_SECRET = "delete-secret"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./queries";
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import {
|
||||
AuditLog,
|
||||
Actor
|
||||
} from "./types";
|
||||
import { EventType, UserAgentType } from "./enums";
|
||||
|
||||
export const workspaceKeys = {
|
||||
getAuditLogs: (workspaceId: string, filters: {
|
||||
eventType?: EventType;
|
||||
userAgentType?: UserAgentType;
|
||||
actor?: string;
|
||||
}) => [{ workspaceId, filters }, "audit-logs"] as const,
|
||||
getAuditLogActorFilterOpts: (workspaceId: string) => [{ workspaceId }, "audit-log-actor-filters"] as const
|
||||
}
|
||||
|
||||
export const useGetAuditLogs = (workspaceId: string, filters: {
|
||||
eventType?: EventType;
|
||||
userAgentType?: UserAgentType;
|
||||
actor?: string;
|
||||
}) => {
|
||||
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);
|
||||
}
|
||||
|
||||
const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params });
|
||||
return data.auditLogs;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
ActorType,
|
||||
EventType,
|
||||
UserAgentType
|
||||
} from "./enums";
|
||||
|
||||
interface UserActorMetadata {
|
||||
userId: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface ServiceActorMetadata {
|
||||
serviceId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
|
||||
interface UserActor {
|
||||
type: ActorType.USER;
|
||||
metadata: UserActorMetadata;
|
||||
}
|
||||
|
||||
export interface ServiceActor {
|
||||
type: ActorType.SERVICE;
|
||||
metadata: ServiceActorMetadata;
|
||||
}
|
||||
|
||||
export type Actor =
|
||||
| UserActor
|
||||
| ServiceActor;
|
||||
|
||||
interface GetSecretsEvent {
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateSecretEvent {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteSecretEvent {
|
||||
type: EventType.DELETE_SECRET;
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
secretVersion: number;
|
||||
}
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| GetSecretsEvent
|
||||
| GetSecretEvent
|
||||
| CreateSecretEvent
|
||||
| UpdateSecretEvent
|
||||
| DeleteSecretEvent;
|
||||
|
||||
export type AuditLog = {
|
||||
_id: string;
|
||||
actor: Actor;
|
||||
organization: string;
|
||||
workspace: string;
|
||||
ipAddress: string;
|
||||
event: Event;
|
||||
userAgent: string;
|
||||
userAgentType: UserAgentType;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export * from "./ssoConfig";
|
||||
export * from "./subscriptions";
|
||||
export * from "./tags";
|
||||
export * from "./trustedIps";
|
||||
export * from "./auditLogs";
|
||||
export * from "./users";
|
||||
export * from "./webhooks";
|
||||
export * from "./workspace";
|
||||
|
||||
@@ -3,6 +3,7 @@ export type SubscriptionPlan = {
|
||||
membersUsed: number;
|
||||
memberLimit: number;
|
||||
auditLogs: boolean;
|
||||
auditLogsRetentionDays: number;
|
||||
customAlerts: boolean;
|
||||
customRateLimits: boolean;
|
||||
pitRecovery: boolean;
|
||||
|
||||
@@ -28,7 +28,8 @@ export const workspaceKeys = {
|
||||
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
|
||||
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
||||
getAllUserWorkspace: ["workspaces"] as const,
|
||||
getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const
|
||||
getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const,
|
||||
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const
|
||||
};
|
||||
|
||||
const fetchWorkspaceById = async (workspaceId: string) => {
|
||||
@@ -259,3 +260,4 @@ export const useDeleteWsEnvironment = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -53,4 +53,4 @@ export type UpdateEnvironmentDTO = {
|
||||
environmentName: string;
|
||||
};
|
||||
|
||||
export type DeleteEnvironmentDTO = { workspaceID: string; environmentSlug: string };
|
||||
export type DeleteEnvironmentDTO = { workspaceID: string; environmentSlug: string };
|
||||
@@ -483,6 +483,18 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?._id}/logs`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?._id}/logs`
|
||||
}
|
||||
icon="system-outline-168-view-headline"
|
||||
>
|
||||
Audit Logs V2
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "../../../components/v2";
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { LogsPage } from "@app/views/Project/LogsPage";
|
||||
|
||||
const Logs = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="h-full bg-bunker-800">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("billing.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Head>
|
||||
<LogsPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Logs;
|
||||
|
||||
Logs.requireAuth = true;
|
||||
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
LogsSection
|
||||
} from "./components";
|
||||
|
||||
export const LogsPage = () => {
|
||||
return (
|
||||
<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="my-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
|
||||
<div />
|
||||
</div>
|
||||
<LogsSection />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Control, Controller, UseFormReset } from "react-hook-form";
|
||||
import {
|
||||
FormControl,
|
||||
Select,
|
||||
SelectItem,
|
||||
Button
|
||||
} from "@app/components/v2";
|
||||
import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useGetAuditLogActorFilterOpts } from "@app/hooks/api";
|
||||
import { Actor } from "~/hooks/api/auditLogs/types";
|
||||
import { ActorType } from "~/hooks/api/auditLogs/enums";
|
||||
import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { AuditLogFilterFormData } from "./LogsSection";
|
||||
|
||||
const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value }));
|
||||
const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({ label, value }));
|
||||
|
||||
type Props = {
|
||||
control: Control<AuditLogFilterFormData>;
|
||||
reset: UseFormReset<AuditLogFilterFormData>;
|
||||
}
|
||||
|
||||
export const LogsFilter = ({
|
||||
control,
|
||||
reset
|
||||
}: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<div className="w-40 mr-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="eventType"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Event"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{eventTypes.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={label}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{!isLoading && data && data.length > 0 && (
|
||||
<div className="w-40 mr-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="actor"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Actor"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{data.map((actor) => renderActorSelectItem(actor))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-40">
|
||||
<Controller
|
||||
control={control}
|
||||
name="userAgentType"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Source"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{userAgentTypes.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={label}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
isLoading={false}
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
|
||||
onClick={() => reset({
|
||||
eventType: "",
|
||||
actor: "",
|
||||
userAgentType: ""
|
||||
})}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { LogsFilter } from "./LogsFilter";
|
||||
import { LogsTable } from "./LogsTable";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums";
|
||||
|
||||
const schema = yup.object({
|
||||
eventType: yup.string()
|
||||
.oneOf(Object.values(EventType), 'Invalid event type'),
|
||||
actor: yup.string(),
|
||||
userAgentType: yup.string()
|
||||
.oneOf(Object.values(UserAgentType), 'Invalid user agent type'),
|
||||
}).required();
|
||||
|
||||
export type AuditLogFilterFormData = yup.InferType<typeof schema>;
|
||||
|
||||
export const LogsSection = () => {
|
||||
const {
|
||||
control,
|
||||
reset,
|
||||
watch,
|
||||
} = useForm<AuditLogFilterFormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const eventType = watch("eventType") as EventType | undefined;
|
||||
const userAgentType = watch("userAgentType") as UserAgentType | undefined;
|
||||
const actor = watch("actor") as string | undefined;
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||
<div className="flex items-center mb-8">
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { faFile } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
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 { LogsTableRow } from "./LogsTableRow";
|
||||
import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums";
|
||||
|
||||
type Props = {
|
||||
eventType: EventType | undefined;
|
||||
userAgentType: UserAgentType | undefined;
|
||||
actor: string | undefined;
|
||||
}
|
||||
|
||||
export const LogsTable = ({
|
||||
eventType,
|
||||
userAgentType,
|
||||
actor
|
||||
}: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data, isLoading } = useGetAuditLogs(currentWorkspace?._id ?? "", {
|
||||
eventType,
|
||||
userAgentType,
|
||||
actor
|
||||
});
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Timestamp</Th>
|
||||
<Th>Event</Th>
|
||||
<Th>Actor</Th>
|
||||
<Th>Source</Th>
|
||||
<Th>Metadata</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{!isLoading && data && data.map((auditLog) => (
|
||||
<LogsTableRow
|
||||
auditLog={auditLog}
|
||||
key={`audit-log-${auditLog._id}`}
|
||||
/>
|
||||
))}
|
||||
{isLoading && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
|
||||
{!isLoading && data && data.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<EmptyState
|
||||
title="No audit logs on file"
|
||||
icon={faFile}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { AuditLog, Actor, Event } from "~/hooks/api/auditLogs/types";
|
||||
import { ActorType, EventType } from "~/hooks/api/auditLogs/enums";
|
||||
import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants";
|
||||
import {
|
||||
Td,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
|
||||
type Props = {
|
||||
auditLog: AuditLog
|
||||
}
|
||||
|
||||
export const LogsTableRow = ({
|
||||
auditLog
|
||||
}: Props) => {
|
||||
const renderActor = (actor: Actor) => {
|
||||
switch (actor.type) {
|
||||
case ActorType.USER:
|
||||
return (
|
||||
<Td>
|
||||
<p>{actor.metadata.email}</p>
|
||||
<p>User</p>
|
||||
</Td>
|
||||
);
|
||||
case ActorType.SERVICE:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`${actor.metadata.name}`}</p>
|
||||
<p>Service token</p>
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const renderMetadata = (event: Event) => {
|
||||
switch (event.type) {
|
||||
case EventType.GET_SECRETS:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`Environment: ${event.metadata.environment}`}</p>
|
||||
<p>{`Path: ${event.metadata.secretPath}`}</p>
|
||||
<p>{`# Secrets: ${event.metadata.numberOfSecrets}`}</p>
|
||||
</Td>
|
||||
);
|
||||
case EventType.GET_SECRET:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`Environment: ${event.metadata.environment}`}</p>
|
||||
<p>{`Path: ${event.metadata.secretPath}`}</p>
|
||||
<p>{`Secret: ${event.metadata.secretKey}`}</p>
|
||||
</Td>
|
||||
);
|
||||
case EventType.CREATE_SECRET:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`Environment: ${event.metadata.environment}`}</p>
|
||||
<p>{`Path: ${event.metadata.secretPath}`}</p>
|
||||
<p>{`Secret: ${event.metadata.secretKey}`}</p>
|
||||
</Td>
|
||||
);
|
||||
case EventType.UPDATE_SECRET:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`Environment: ${event.metadata.environment}`}</p>
|
||||
<p>{`Path: ${event.metadata.secretPath}`}</p>
|
||||
<p>{`Secret: ${event.metadata.secretKey}`}</p>
|
||||
</Td>
|
||||
);
|
||||
case EventType.DELETE_SECRET:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`Environment: ${event.metadata.environment}`}</p>
|
||||
<p>{`Path: ${event.metadata.secretPath}`}</p>
|
||||
<p>{`Secret: ${event.metadata.secretKey}`}</p>
|
||||
</Td>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Td>Test</Td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateToFormat: string) => {
|
||||
const date = new Date(dateToFormat);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
|
||||
let hours = date.getHours();
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
|
||||
// convert from 24h to 12h format
|
||||
const period = hours >= 12 ? 'PM' : 'AM';
|
||||
hours = hours % 12;
|
||||
hours = hours ? hours : 12; // the hour '0' should be '12'
|
||||
|
||||
const formattedDate = `${day}-${month}-${year} at ${hours}:${minutes} ${period}`;
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr className={`log-${auditLog._id} h-10`}>
|
||||
<Td>{formatDate(auditLog.createdAt)}</Td>
|
||||
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
||||
{renderActor(auditLog.actor)}
|
||||
<Td>
|
||||
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
||||
<p>{auditLog.ipAddress}</p>
|
||||
</Td>
|
||||
{renderMetadata(auditLog.event)}
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { LogsSection } from "./LogsSection";
|
||||
@@ -0,0 +1 @@
|
||||
export { LogsPage } from "./LogsPage";
|
||||
Reference in New Issue
Block a user