mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 22:28:15 +00:00
feat: more expendable audit logs
This commit is contained in:
@@ -1,14 +1,29 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Control, Controller, UseFormReset } from "react-hook-form";
|
import { Control, Controller, UseFormReset, UseFormWatch } from "react-hook-form";
|
||||||
import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faCheckCircle,
|
||||||
|
faChevronDown,
|
||||||
|
faFilterCircleXmark
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Button, DatePicker, FormControl, Select, SelectItem } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
DatePicker,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
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";
|
||||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { Actor } from "@app/hooks/api/auditLogs/types";
|
import { Actor } from "@app/hooks/api/auditLogs/types";
|
||||||
|
|
||||||
import { AuditLogFilterFormData } from "./types";
|
import { AuditLogFilterFormData } from "./types";
|
||||||
@@ -20,13 +35,17 @@ const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
presetActor?: string;
|
presets?: {
|
||||||
|
actorId?: string;
|
||||||
|
eventType?: EventType[];
|
||||||
|
};
|
||||||
className?: string;
|
className?: string;
|
||||||
control: Control<AuditLogFilterFormData>;
|
control: Control<AuditLogFilterFormData>;
|
||||||
reset: UseFormReset<AuditLogFilterFormData>;
|
reset: UseFormReset<AuditLogFilterFormData>;
|
||||||
|
watch: UseFormWatch<AuditLogFilterFormData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsFilter = ({ presetActor, className, control, reset }: Props) => {
|
export const LogsFilter = ({ presets, className, control, reset, watch }: Props) => {
|
||||||
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
||||||
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
||||||
|
|
||||||
@@ -71,6 +90,8 @@ export const LogsFilter = ({ presetActor, className, control, reset }: Props) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectedEventTypes = watch("eventType") as EventType[] | undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
@@ -82,29 +103,69 @@ export const LogsFilter = ({ presetActor, className, control, reset }: Props) =>
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="eventType"
|
name="eventType"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl label="Events">
|
||||||
label="Event"
|
<DropdownMenu>
|
||||||
errorText={error?.message}
|
<DropdownMenuTrigger asChild>
|
||||||
isError={Boolean(error)}
|
<div className="inline-flex w-full cursor-pointer items-center justify-between rounded-md border border-mineshaft-500 bg-mineshaft-700 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
||||||
className="w-40"
|
{selectedEventTypes?.length === 1
|
||||||
>
|
? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0])
|
||||||
<Select
|
?.label
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
: selectedEventTypes?.length === 0
|
||||||
{...field}
|
? "Select event types"
|
||||||
onValueChange={(e) => onChange(e)}
|
: `${selectedEventTypes?.length} events selected`}
|
||||||
className="w-full border border-mineshaft-500 bg-mineshaft-700 text-mineshaft-100"
|
<FontAwesomeIcon icon={faChevronDown} className="ml-2 text-xs" />
|
||||||
>
|
</div>
|
||||||
{eventTypes.map(({ label, value }) => (
|
</DropdownMenuTrigger>
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
<DropdownMenuContent align="start" className="z-[100] max-h-80 overflow-hidden">
|
||||||
{label}
|
<div className="max-h-80 overflow-y-auto">
|
||||||
</SelectItem>
|
{eventTypes && eventTypes.length > 0 ? (
|
||||||
))}
|
eventTypes.map((eventType) => {
|
||||||
</Select>
|
const isSelected = selectedEventTypes?.includes(
|
||||||
|
eventType.value as EventType
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={(event) => eventTypes.length > 1 && event.preventDefault()}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedEventTypes?.includes(eventType.value as EventType)) {
|
||||||
|
field.onChange(
|
||||||
|
selectedEventTypes?.filter((e: string) => e !== eventType.value)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
field.onChange([...(selectedEventTypes || []), eventType.value]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
key={`event-type-${eventType.value}`}
|
||||||
|
icon={
|
||||||
|
isSelected ? (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCheckCircle}
|
||||||
|
className="pr-0.5 text-primary"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="pl-[1.01rem]" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconPos="left"
|
||||||
|
className="w-[28.4rem] text-sm"
|
||||||
|
>
|
||||||
|
{eventType.label}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{!isLoading && data && data.length > 0 && !presetActor && (
|
|
||||||
|
{!isLoading && data && data.length > 0 && !presets?.actorId && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="actor"
|
name="actor"
|
||||||
@@ -207,8 +268,8 @@ export const LogsFilter = ({ presetActor, className, control, reset }: Props) =>
|
|||||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} />}
|
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
reset({
|
reset({
|
||||||
eventType: undefined,
|
eventType: presets?.eventType,
|
||||||
actor: presetActor,
|
actor: presets?.actorId,
|
||||||
userAgentType: undefined,
|
userAgentType: undefined,
|
||||||
startDate: undefined,
|
startDate: undefined,
|
||||||
endDate: undefined
|
endDate: undefined
|
||||||
|
|||||||
@@ -5,24 +5,38 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
|
|
||||||
import { UpgradePlanModal } from "@app/components/v2";
|
import { UpgradePlanModal } from "@app/components/v2";
|
||||||
import { useSubscription } from "@app/context";
|
import { useSubscription } from "@app/context";
|
||||||
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { LogsFilter } from "./LogsFilter";
|
import { LogsFilter } from "./LogsFilter";
|
||||||
import { LogsTable } from "./LogsTable";
|
import { LogsTable, TAuditLogTableHeader } from "./LogsTable";
|
||||||
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
presetActor?: string;
|
presets?: {
|
||||||
|
actorId?: string;
|
||||||
|
eventType?: EventType[];
|
||||||
|
actorType?: ActorType;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
eventMetadata?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
showFilters?: boolean;
|
showFilters?: boolean;
|
||||||
filterClassName?: string;
|
filterClassName?: string;
|
||||||
isOrgAuditLogs?: boolean;
|
isOrgAuditLogs?: boolean;
|
||||||
|
showActorColumn?: boolean;
|
||||||
|
remappedHeaders?: Partial<Record<TAuditLogTableHeader, string>>;
|
||||||
|
refetchInterval?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsSection = ({
|
export const LogsSection = ({
|
||||||
presetActor,
|
presets,
|
||||||
filterClassName,
|
filterClassName,
|
||||||
|
remappedHeaders,
|
||||||
isOrgAuditLogs,
|
isOrgAuditLogs,
|
||||||
|
showActorColumn,
|
||||||
|
refetchInterval,
|
||||||
showFilters
|
showFilters
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
@@ -33,11 +47,12 @@ export const LogsSection = ({
|
|||||||
const { control, reset, watch } = useForm<AuditLogFilterFormData>({
|
const { control, reset, watch } = useForm<AuditLogFilterFormData>({
|
||||||
resolver: yupResolver(auditLogFilterFormSchema),
|
resolver: yupResolver(auditLogFilterFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
actor: presetActor,
|
actor: presets?.actorId,
|
||||||
|
eventType: presets?.eventType || [],
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 10,
|
perPage: 10,
|
||||||
startDate: new Date(new Date().setDate(new Date().getDate() - 1)), // day before today
|
startDate: presets?.startDate ?? new Date(new Date().setDate(new Date().getDate() - 1)), // day before today
|
||||||
endDate: new Date(new Date(Date.now()).setHours(23, 59, 59, 999)) // end of today
|
endDate: presets?.endDate ?? new Date(new Date(Date.now()).setHours(23, 59, 59, 999)) // end of today
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,31 +62,41 @@ export const LogsSection = ({
|
|||||||
}
|
}
|
||||||
}, [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");
|
||||||
|
|
||||||
|
console.log("presets", presets);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{showFilters && (
|
{showFilters && (
|
||||||
<LogsFilter
|
<LogsFilter
|
||||||
className={filterClassName}
|
className={filterClassName}
|
||||||
presetActor={presetActor}
|
presets={presets}
|
||||||
control={control}
|
control={control}
|
||||||
|
watch={watch}
|
||||||
reset={reset}
|
reset={reset}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<LogsTable
|
<LogsTable
|
||||||
|
refetchInterval={refetchInterval}
|
||||||
|
remappedHeaders={remappedHeaders}
|
||||||
isOrgAuditLogs={isOrgAuditLogs}
|
isOrgAuditLogs={isOrgAuditLogs}
|
||||||
eventType={eventType}
|
showActorColumn={!!showActorColumn && !isOrgAuditLogs}
|
||||||
userAgentType={userAgentType}
|
filter={{
|
||||||
showActorColumn={!presetActor}
|
eventMetadata: presets?.eventMetadata,
|
||||||
actor={actor}
|
actorType: presets?.actorType,
|
||||||
startDate={startDate}
|
limit: 15,
|
||||||
endDate={endDate}
|
eventType,
|
||||||
|
userAgentType,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
actorId: actor
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
|||||||
@@ -15,43 +15,41 @@ import {
|
|||||||
} from "@app/components/v2";
|
} 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 { TGetAuditLogsFilter } from "@app/hooks/api/auditLogs/types";
|
||||||
|
|
||||||
import { LogsTableRow } from "./LogsTableRow";
|
import { LogsTableRow } from "./LogsTableRow";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
eventType?: EventType;
|
|
||||||
userAgentType?: UserAgentType;
|
|
||||||
actor?: string;
|
|
||||||
startDate?: Date;
|
|
||||||
endDate?: Date;
|
|
||||||
isOrgAuditLogs?: boolean;
|
isOrgAuditLogs?: boolean;
|
||||||
showActorColumn: boolean;
|
showActorColumn: boolean;
|
||||||
|
filter?: TGetAuditLogsFilter;
|
||||||
|
remappedHeaders?: Partial<Record<TAuditLogTableHeader, string>>;
|
||||||
|
refetchInterval?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUDIT_LOG_LIMIT = 15;
|
const AUDIT_LOG_LIMIT = 15;
|
||||||
|
|
||||||
|
const TABLE_HEADERS = ["Timestamp", "Event", "Project", "Actor", "Source", "Metadata"] as const;
|
||||||
|
export type TAuditLogTableHeader = (typeof TABLE_HEADERS)[number];
|
||||||
|
|
||||||
export const LogsTable = ({
|
export const LogsTable = ({
|
||||||
eventType,
|
|
||||||
userAgentType,
|
|
||||||
showActorColumn,
|
showActorColumn,
|
||||||
actor,
|
isOrgAuditLogs,
|
||||||
startDate,
|
filter,
|
||||||
endDate,
|
remappedHeaders,
|
||||||
isOrgAuditLogs
|
refetchInterval
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
|
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
|
||||||
{
|
{
|
||||||
eventType,
|
...filter,
|
||||||
userAgentType,
|
|
||||||
actor,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
limit: AUDIT_LOG_LIMIT
|
limit: AUDIT_LOG_LIMIT
|
||||||
},
|
},
|
||||||
!isOrgAuditLogs ? currentWorkspace?.id ?? "" : null
|
!isOrgAuditLogs ? currentWorkspace?.id ?? "" : null,
|
||||||
|
{
|
||||||
|
refetchInterval
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEmpty = !isLoading && !data?.pages?.[0].length;
|
const isEmpty = !isLoading && !data?.pages?.[0].length;
|
||||||
@@ -62,18 +60,24 @@ export const LogsTable = ({
|
|||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>Timestamp</Th>
|
{TABLE_HEADERS.map((header, idx) => {
|
||||||
<Th>Event</Th>
|
if (
|
||||||
{isOrgAuditLogs && <Th>Project</Th>}
|
(header === "Project" && !isOrgAuditLogs) ||
|
||||||
{showActorColumn && <Th>Actor</Th>}
|
(header === "Actor" && !showActorColumn)
|
||||||
<Th>Source</Th>
|
) {
|
||||||
<Th>Metadata</Th>
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Th key={`table-header-${idx + 1}`}>{remappedHeaders?.[header] || header}</Th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
data?.pages?.map((group, i) => (
|
data?.pages?.map((group, i) => (
|
||||||
<Fragment key={`auditlog-item-${i + 1}`}>
|
<Fragment key={`audit-log-fragment-${i + 1}`}>
|
||||||
{group.map((auditLog) => (
|
{group.map((auditLog) => (
|
||||||
<LogsTableRow
|
<LogsTableRow
|
||||||
showActorColumn={showActorColumn}
|
showActorColumn={showActorColumn}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Td, Tr } from "@app/components/v2";
|
import { Badge, Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
|
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
|
||||||
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { Actor, AuditLog, Event } from "@app/hooks/api/auditLogs/types";
|
import { Actor, AuditLog, Event } from "@app/hooks/api/auditLogs/types";
|
||||||
@@ -461,6 +461,21 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
|
|||||||
<p>{`Secret Request Channels: ${event.metadata.secretRequestChannels}`}</p>
|
<p>{`Secret Request Channels: ${event.metadata.secretRequestChannels}`}</p>
|
||||||
</Td>
|
</Td>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case EventType.INTEGRATION_SYNCED:
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<Tooltip
|
||||||
|
className="max-w-xs whitespace-normal break-words"
|
||||||
|
content={event.metadata.syncMessage!}
|
||||||
|
isDisabled={!event.metadata.syncMessage}
|
||||||
|
>
|
||||||
|
<Badge variant={event.metadata.isSynced ? "success" : "danger"}>
|
||||||
|
<p className="text-center">{event.metadata.isSynced ? "Synced" : "Not synced"}</p>
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <Td />;
|
return <Td />;
|
||||||
}
|
}
|
||||||
@@ -484,16 +499,41 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
|
|||||||
return formattedDate;
|
return formattedDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderSource = () => {
|
||||||
|
const { event, actor } = auditLog;
|
||||||
|
|
||||||
|
if (event.type === EventType.INTEGRATION_SYNCED) {
|
||||||
|
if (actor.type === ActorType.USER) {
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>Manually triggered by {actor.metadata.email}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform / automatic syncs
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>Automatically synced by Infisical</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
||||||
|
<p>{auditLog.ipAddress}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
||||||
<Td>{formatDate(auditLog.createdAt)}</Td>
|
<Td>{formatDate(auditLog.createdAt)}</Td>
|
||||||
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
||||||
{isOrgAuditLogs && <Td>{auditLog.project.name}</Td>}
|
{isOrgAuditLogs && <Td>{auditLog.project.name}</Td>}
|
||||||
{showActorColumn && renderActor(auditLog.actor)}
|
{showActorColumn && renderActor(auditLog.actor)}
|
||||||
<Td>
|
{renderSource()}
|
||||||
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
|
||||||
<p>{auditLog.ipAddress}</p>
|
|
||||||
</Td>
|
|
||||||
{renderMetadata(auditLog.event)}
|
{renderMetadata(auditLog.event)}
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user