mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-02 17:25:44 +00:00
Add date filter and pagination component to audit log v2
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Key, Membership, MembershipOrg, User, IUser } from "../../models";
|
import { IUser, Key, Membership, MembershipOrg, User } from "../../models";
|
||||||
import { EventType } from "../../ee/models";
|
import { EventType } from "../../ee/models";
|
||||||
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
|
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
|
||||||
import { sendMail } from "../../helpers/nodemailer";
|
import { sendMail } from "../../helpers/nodemailer";
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import {
|
|||||||
} from "../../services/FolderService";
|
} from "../../services/FolderService";
|
||||||
import { ADMIN, MEMBER } from "../../variables";
|
import { ADMIN, MEMBER } from "../../variables";
|
||||||
import { validateMembership } from "../../helpers/membership";
|
import { validateMembership } from "../../helpers/membership";
|
||||||
import { FolderVersion, EventType } from "../../ee/models";
|
import { EventType, FolderVersion } from "../../ee/models";
|
||||||
import { EESecretService, EEAuditLogService } from "../../ee/services";
|
import { EEAuditLogService, EESecretService } from "../../ee/services";
|
||||||
|
|
||||||
// verify workspace id/environment
|
// verify workspace id/environment
|
||||||
export const createFolder = async (req: Request, res: Response) => {
|
export const createFolder = async (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
Workspace,
|
Workspace,
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { SecretVersion, EventType } from "../../ee/models";
|
import { EventType, SecretVersion } from "../../ee/models";
|
||||||
import { EELicenseService, EEAuditLogService } from "../../ee/services";
|
import { EEAuditLogService, EELicenseService } from "../../ee/services";
|
||||||
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
|
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Membership, Secret, ServiceTokenData, User } from "../../../models";
|
|||||||
import {
|
import {
|
||||||
ActorType,
|
ActorType,
|
||||||
AuditLog,
|
AuditLog,
|
||||||
|
EventType,
|
||||||
FolderVersion,
|
FolderVersion,
|
||||||
IPType,
|
IPType,
|
||||||
ISecretVersion,
|
ISecretVersion,
|
||||||
@@ -13,14 +14,13 @@ import {
|
|||||||
ServiceActor,
|
ServiceActor,
|
||||||
TFolderRootVersionSchema,
|
TFolderRootVersionSchema,
|
||||||
TrustedIP,
|
TrustedIP,
|
||||||
UserActor,
|
UserActor
|
||||||
EventType
|
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { EESecretService } from "../../services";
|
import { EESecretService } from "../../services";
|
||||||
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
||||||
import Folder, { TFolderSchema } from "../../../models/folder";
|
import Folder, { TFolderSchema } from "../../../models/folder";
|
||||||
import { searchByFolderId } from "../../../services/FolderService";
|
import { searchByFolderId } from "../../../services/FolderService";
|
||||||
import { EELicenseService, EEAuditLogService } from "../../services";
|
import { EEAuditLogService, EELicenseService } from "../../services";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -611,7 +611,10 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
|||||||
const offset: number = parseInt(req.query.offset as string);
|
const offset: number = parseInt(req.query.offset as string);
|
||||||
const limit: number = parseInt(req.query.limit as string);
|
const limit: number = parseInt(req.query.limit as string);
|
||||||
|
|
||||||
const auditLogs = await AuditLog.find({
|
const startDate = req.query.startDate as string;
|
||||||
|
const endDate = req.query.endDate as string;
|
||||||
|
|
||||||
|
const query = {
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
...(eventType ? {
|
...(eventType ? {
|
||||||
"event.type": eventType
|
"event.type": eventType
|
||||||
@@ -626,14 +629,25 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
|||||||
} : {
|
} : {
|
||||||
"actor.metadata.serviceId": actor.split("-", 2)[1]
|
"actor.metadata.serviceId": actor.split("-", 2)[1]
|
||||||
})
|
})
|
||||||
|
} : {}),
|
||||||
|
...(startDate || endDate ? {
|
||||||
|
createdAt: {
|
||||||
|
...(startDate && { $gte: new Date(startDate) }),
|
||||||
|
...(endDate && { $lte: new Date(endDate) })
|
||||||
|
}
|
||||||
} : {})
|
} : {})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const auditLogs = await AuditLog.find(query)
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
|
|
||||||
|
const totalCount = await AuditLog.countDocuments(query);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
auditLogs
|
auditLogs,
|
||||||
|
totalCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,9 +96,11 @@ router.get(
|
|||||||
param("workspaceId").exists().trim(),
|
param("workspaceId").exists().trim(),
|
||||||
query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }),
|
query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }),
|
||||||
query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }),
|
query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }),
|
||||||
query("actor").isString().optional({ nullable: true }),
|
query("actor").optional({ nullable: true }),
|
||||||
query("offset").default("0"),
|
query("startDate").isISO8601().withMessage("Invalid start date format").optional({ nullable: true }),
|
||||||
query("limit").default("20"),
|
query("endDate").isISO8601().withMessage("Invalid end date format").optional({ nullable: true }),
|
||||||
|
query("offset"),
|
||||||
|
query("limit"),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
workspaceController.getWorkspaceAuditLogs
|
workspaceController.getWorkspaceAuditLogs
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import Folder, { TFolderSchema, TFolderRootSchema } from "../models/folder";
|
import Folder, { TFolderRootSchema, TFolderSchema } from "../models/folder";
|
||||||
import { ResourceNotFoundError } from "../utils/errors";
|
import { ResourceNotFoundError } from "../utils/errors";
|
||||||
|
|
||||||
type TAppendFolderDTO = {
|
type TAppendFolderDTO = {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export type PaginationProps = {
|
|||||||
perPage?: number;
|
perPage?: number;
|
||||||
onChangePage: (pageNumber: number) => void;
|
onChangePage: (pageNumber: number) => void;
|
||||||
onChangePerPage: (newRows: number) => void;
|
onChangePerPage: (newRows: number) => void;
|
||||||
className: string;
|
className?: string;
|
||||||
perPageList?: number[];
|
perPageList?: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,15 +31,19 @@ export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) =
|
|||||||
params.append("actor", filters.actor);
|
params.append("actor", filters.actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.actor) {
|
if (filters.startDate) {
|
||||||
params.append("actor", filters.actor);
|
params.append("startDate", filters.startDate.toISOString());
|
||||||
}
|
}
|
||||||
|
|
||||||
params.append("offset ", String(filters.offset));
|
if (filters.endDate) {
|
||||||
params.append("limit ", String(filters.limit));
|
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[] }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params });
|
const { data } = await apiRequest.get<{ auditLogs: AuditLog[], totalCount: number }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params });
|
||||||
return data.auditLogs;
|
return data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,4 +323,6 @@ export type AuditLogFilters = {
|
|||||||
actor?: string;
|
actor?: string;
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
|
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 {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
DatePicker,
|
||||||
FormControl,
|
FormControl,
|
||||||
Select,
|
Select,
|
||||||
SelectItem} from "@app/components/v2";
|
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 "~/hooks/api/auditLogs/constants";
|
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { ActorType } from "~/hooks/api/auditLogs/enums";
|
import { Actor } from "@app/hooks/api/auditLogs/types";
|
||||||
import { Actor } from "~/hooks/api/auditLogs/types";
|
|
||||||
|
|
||||||
import { AuditLogFilterFormData } from "./types";
|
import { AuditLogFilterFormData } from "./types";
|
||||||
|
|
||||||
@@ -28,6 +29,9 @@ export const LogsFilter = ({
|
|||||||
control,
|
control,
|
||||||
reset
|
reset
|
||||||
}: Props) => {
|
}: 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 ?? "");
|
||||||
|
|
||||||
@@ -58,65 +62,41 @@ export const LogsFilter = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="w-40 mr-4">
|
<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"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Select
|
{eventTypes.map(({ label, value }) => (
|
||||||
defaultValue={field.value}
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
{...field}
|
{label}
|
||||||
onValueChange={(e) => onChange(e)}
|
</SelectItem>
|
||||||
className="w-full"
|
))}
|
||||||
>
|
</Select>
|
||||||
{eventTypes.map(({ label, value }) => (
|
</FormControl>
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
)}
|
||||||
{label}
|
/>
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{!isLoading && data && data.length > 0 && (
|
{!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
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="userAgentType"
|
name="actor"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Source"
|
label="Actor"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
|
className="w-40 mr-4"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
@@ -124,16 +104,90 @@ export const LogsFilter = ({
|
|||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => onChange(e)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{userAgentTypes.map(({ label, value }) => (
|
{data.map((actor) => renderActorSelectItem(actor))}
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
|
||||||
{label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</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
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@@ -143,9 +197,11 @@ export const LogsFilter = ({
|
|||||||
type="submit"
|
type="submit"
|
||||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
|
||||||
onClick={() => reset({
|
onClick={() => reset({
|
||||||
eventType: "",
|
eventType: undefined,
|
||||||
actor: "",
|
actor: "",
|
||||||
userAgentType: ""
|
userAgentType: "",
|
||||||
|
startDate: undefined,
|
||||||
|
endDate: undefined
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Clear filters
|
Clear filters
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
|
||||||
import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums";
|
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
||||||
|
|
||||||
import { LogsFilter } from "./LogsFilter";
|
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 = () => {
|
export const LogsSection = () => {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
watch,
|
watch,
|
||||||
|
setValue,
|
||||||
} = useForm<AuditLogFilterFormData>({
|
} = useForm<AuditLogFilterFormData>({
|
||||||
resolver: yupResolver(auditLogFilterFormSchema)
|
resolver: yupResolver(auditLogFilterFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
page: 1,
|
||||||
|
perPage: 10
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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") as string | undefined;
|
const actor = watch("actor");
|
||||||
|
|
||||||
|
const startDate = watch("startDate");
|
||||||
|
const endDate = watch("endDate");
|
||||||
|
|
||||||
|
const page = watch("page") as number;
|
||||||
|
const perPage = watch("perPage") as number;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||||
@@ -35,6 +46,11 @@ export const LogsSection = () => {
|
|||||||
eventType={eventType}
|
eventType={eventType}
|
||||||
userAgentType={userAgentType}
|
userAgentType={userAgentType}
|
||||||
actor={actor}
|
actor={actor}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
setValue={setValue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { faFile } from "@fortawesome/free-solid-svg-icons";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
EmptyState,
|
EmptyState,
|
||||||
|
Pagination,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableSkeleton,
|
TableSkeleton,
|
||||||
@@ -9,33 +10,44 @@ import {
|
|||||||
Td,
|
Td,
|
||||||
Th,
|
Th,
|
||||||
THead,
|
THead,
|
||||||
Tr
|
Tr} 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 { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums";
|
|
||||||
|
|
||||||
import { LogsTableRow } from "./LogsTableRow";
|
import { LogsTableRow } from "./LogsTableRow";
|
||||||
|
import { SetValueType } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
eventType: EventType | undefined;
|
eventType?: EventType;
|
||||||
userAgentType: UserAgentType | undefined;
|
userAgentType?: UserAgentType;
|
||||||
actor: string | undefined;
|
actor?: string;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
setValue: SetValueType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LogsTable = ({
|
export const LogsTable = ({
|
||||||
eventType,
|
eventType,
|
||||||
userAgentType,
|
userAgentType,
|
||||||
actor
|
actor,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
setValue
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data, isLoading } = useGetAuditLogs(currentWorkspace?._id ?? "", {
|
const { data, isLoading } = useGetAuditLogs(currentWorkspace?._id ?? "", {
|
||||||
eventType,
|
eventType,
|
||||||
userAgentType,
|
userAgentType,
|
||||||
actor,
|
actor,
|
||||||
offset: 0, // TODO: update with pagination
|
startDate,
|
||||||
limit: 20 // TODO: update with pagination
|
endDate,
|
||||||
|
offset: (page - 1) * perPage,
|
||||||
|
limit: perPage
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,14 +63,14 @@ export const LogsTable = ({
|
|||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{!isLoading && data && data.map((auditLog) => (
|
{!isLoading && data?.auditLogs && data?.auditLogs.map((auditLog) => (
|
||||||
<LogsTableRow
|
<LogsTableRow
|
||||||
auditLog={auditLog}
|
auditLog={auditLog}
|
||||||
key={`audit-log-${auditLog._id}`}
|
key={`audit-log-${auditLog._id}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{isLoading && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
|
{isLoading && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
|
||||||
{!isLoading && data && data.length === 0 && (
|
{!isLoading && data?.auditLogs && data?.auditLogs.length === 0 && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={5}>
|
<Td colSpan={5}>
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@@ -70,6 +82,17 @@ export const LogsTable = ({
|
|||||||
)}
|
)}
|
||||||
</TBody>
|
</TBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
{!isLoading && data?.totalCount !== undefined && (
|
||||||
|
<Pagination
|
||||||
|
count={data?.totalCount}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={(newPage) => setValue("page", newPage)}
|
||||||
|
onChangePerPage={(newPerPage) => setValue("perPage", newPerPage)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: retrieve count
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums";
|
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
||||||
|
|
||||||
export const auditLogFilterFormSchema = yup.object({
|
export const auditLogFilterFormSchema = yup.object({
|
||||||
eventType: yup.string()
|
eventType: yup.string()
|
||||||
@@ -8,6 +8,19 @@ export const auditLogFilterFormSchema = yup.object({
|
|||||||
actor: yup.string(),
|
actor: yup.string(),
|
||||||
userAgentType: yup.string()
|
userAgentType: yup.string()
|
||||||
.oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
.oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
||||||
|
startDate: yup.date(),
|
||||||
|
endDate: yup.date().min(yup.ref("startDate"), "End date cannot be before start date"),
|
||||||
|
page: yup.number(),
|
||||||
|
perPage: yup.number()
|
||||||
}).required();
|
}).required();
|
||||||
|
|
||||||
export type AuditLogFilterFormData = yup.InferType<typeof auditLogFilterFormSchema>;
|
export type AuditLogFilterFormData = yup.InferType<typeof auditLogFilterFormSchema>;
|
||||||
|
|
||||||
|
export type SetValueType = (
|
||||||
|
name: keyof AuditLogFilterFormData,
|
||||||
|
value: any,
|
||||||
|
options?: {
|
||||||
|
shouldValidate?: boolean,
|
||||||
|
shouldDirty?: boolean
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
Reference in New Issue
Block a user