mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 21:27:10 +00:00
feat: audit logs on organization-level
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState } from "react";
|
|||||||
import { Control, Controller, UseFormReset } from "react-hook-form";
|
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 { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Button, DatePicker, FormControl, Select, SelectItem } from "@app/components/v2";
|
import { Button, DatePicker, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
@@ -19,11 +20,13 @@ const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
presetActor?: string;
|
||||||
|
className?: string;
|
||||||
control: Control<AuditLogFilterFormData>;
|
control: Control<AuditLogFilterFormData>;
|
||||||
reset: UseFormReset<AuditLogFilterFormData>;
|
reset: UseFormReset<AuditLogFilterFormData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsFilter = ({ control, reset }: Props) => {
|
export const LogsFilter = ({ presetActor, className, control, reset }: Props) => {
|
||||||
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
||||||
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
||||||
|
|
||||||
@@ -69,8 +72,13 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sticky top-20 z-10 flex items-center justify-between bg-bunker-800">
|
<div
|
||||||
<div className="flex items-center">
|
className={twMerge(
|
||||||
|
"sticky top-20 z-10 flex items-center justify-between bg-bunker-800",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="eventType"
|
name="eventType"
|
||||||
@@ -79,7 +87,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
label="Event"
|
label="Event"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
className="mr-4 w-40"
|
className="w-40"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||||
@@ -96,7 +104,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{!isLoading && data && data.length > 0 && (
|
{!isLoading && data && data.length > 0 && !presetActor && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="actor"
|
name="actor"
|
||||||
@@ -105,7 +113,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
label="Actor"
|
label="Actor"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
className="mr-4 w-40"
|
className="w-40"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||||
@@ -127,7 +135,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
label="Source"
|
label="Source"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
className="mr-4 w-40"
|
className="w-40"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
|
||||||
@@ -149,12 +157,7 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl label="Start date" errorText={error?.message} isError={Boolean(error)}>
|
||||||
label="Start date"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
className="mr-4"
|
|
||||||
>
|
|
||||||
<DatePicker
|
<DatePicker
|
||||||
value={field.value || undefined}
|
value={field.value || undefined}
|
||||||
onChange={(date) => {
|
onChange={(date) => {
|
||||||
@@ -195,17 +198,17 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<Button
|
<Button
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
|
className="mt-1.5"
|
||||||
type="submit"
|
type="submit"
|
||||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
reset({
|
reset({
|
||||||
eventType: undefined,
|
eventType: undefined,
|
||||||
actor: undefined,
|
actor: presetActor,
|
||||||
userAgentType: undefined,
|
userAgentType: undefined,
|
||||||
startDate: undefined,
|
startDate: undefined,
|
||||||
endDate: undefined
|
endDate: undefined
|
||||||
@@ -215,6 +218,5 @@ export const LogsFilter = ({ control, reset }: Props) => {
|
|||||||
Clear filters
|
Clear filters
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,19 @@ import { LogsFilter } from "./LogsFilter";
|
|||||||
import { LogsTable } from "./LogsTable";
|
import { LogsTable } from "./LogsTable";
|
||||||
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
||||||
|
|
||||||
export const LogsSection = () => {
|
type Props = {
|
||||||
|
presetActor?: string;
|
||||||
|
showFilters?: boolean;
|
||||||
|
filterClassName?: string;
|
||||||
|
isOrgAuditLogs?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LogsSection = ({
|
||||||
|
presetActor,
|
||||||
|
filterClassName,
|
||||||
|
isOrgAuditLogs,
|
||||||
|
showFilters
|
||||||
|
}: Props) => {
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -21,6 +33,7 @@ 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,
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 10,
|
perPage: 10,
|
||||||
startDate: new Date(new Date().setDate(new Date().getDate() - 1)), // day before today
|
startDate: new Date(new Date().setDate(new Date().getDate() - 1)), // day before today
|
||||||
@@ -43,8 +56,16 @@ export const LogsSection = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LogsFilter control={control} reset={reset} />
|
{showFilters && (
|
||||||
|
<LogsFilter
|
||||||
|
className={filterClassName}
|
||||||
|
presetActor={presetActor}
|
||||||
|
control={control}
|
||||||
|
reset={reset}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<LogsTable
|
<LogsTable
|
||||||
|
isOrgAuditLogs={isOrgAuditLogs}
|
||||||
eventType={eventType}
|
eventType={eventType}
|
||||||
userAgentType={userAgentType}
|
userAgentType={userAgentType}
|
||||||
actor={actor}
|
actor={actor}
|
||||||
|
|||||||
@@ -25,15 +25,22 @@ type Props = {
|
|||||||
actor?: string;
|
actor?: string;
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
|
isOrgAuditLogs?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUDIT_LOG_LIMIT = 15;
|
const AUDIT_LOG_LIMIT = 15;
|
||||||
|
|
||||||
export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate }: Props) => {
|
export const LogsTable = ({
|
||||||
|
eventType,
|
||||||
|
userAgentType,
|
||||||
|
actor,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
isOrgAuditLogs
|
||||||
|
}: Props) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
|
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
|
||||||
currentWorkspace?.id ?? "",
|
|
||||||
{
|
{
|
||||||
eventType,
|
eventType,
|
||||||
userAgentType,
|
userAgentType,
|
||||||
@@ -41,7 +48,8 @@ export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate
|
|||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
limit: AUDIT_LOG_LIMIT
|
limit: AUDIT_LOG_LIMIT
|
||||||
}
|
},
|
||||||
|
!isOrgAuditLogs ? currentWorkspace?.id ?? "" : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEmpty = !isLoading && !data?.pages?.[0].length;
|
const isEmpty = !isLoading && !data?.pages?.[0].length;
|
||||||
@@ -54,6 +62,7 @@ export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate
|
|||||||
<Tr>
|
<Tr>
|
||||||
<Th>Timestamp</Th>
|
<Th>Timestamp</Th>
|
||||||
<Th>Event</Th>
|
<Th>Event</Th>
|
||||||
|
{isOrgAuditLogs && <Th>Project</Th>}
|
||||||
<Th>Actor</Th>
|
<Th>Actor</Th>
|
||||||
<Th>Source</Th>
|
<Th>Source</Th>
|
||||||
<Th>Metadata</Th>
|
<Th>Metadata</Th>
|
||||||
@@ -64,7 +73,11 @@ export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate
|
|||||||
data?.pages?.map((group, i) => (
|
data?.pages?.map((group, i) => (
|
||||||
<Fragment key={`auditlog-item-${i + 1}`}>
|
<Fragment key={`auditlog-item-${i + 1}`}>
|
||||||
{group.map((auditLog) => (
|
{group.map((auditLog) => (
|
||||||
<LogsTableRow auditLog={auditLog} key={`audit-log-${auditLog.id}`} />
|
<LogsTableRow
|
||||||
|
isOrgAuditLogs={isOrgAuditLogs}
|
||||||
|
auditLog={auditLog}
|
||||||
|
key={`audit-log-${auditLog.id}`}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import { Actor, AuditLog, Event } from "@app/hooks/api/auditLogs/types";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
auditLog: AuditLog;
|
auditLog: AuditLog;
|
||||||
|
isOrgAuditLogs?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsTableRow = ({ auditLog }: Props) => {
|
export const LogsTableRow = ({ auditLog, isOrgAuditLogs }: Props) => {
|
||||||
const renderActor = (actor: Actor) => {
|
const renderActor = (actor: Actor) => {
|
||||||
switch (actor.type) {
|
switch (actor.type) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
@@ -469,6 +470,7 @@ export const LogsTableRow = ({ auditLog }: Props) => {
|
|||||||
<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>}
|
||||||
{renderActor(auditLog.actor)}
|
{renderActor(auditLog.actor)}
|
||||||
<Td>
|
<Td>
|
||||||
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user