mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #2802 from Infisical/audit-logs-project-select-filter
Improvement: Filterable Project Select for Audit Logs
This commit is contained in:
@@ -14,6 +14,7 @@ export type DatePickerProps = Omit<DayPickerProps, "selected"> & {
|
|||||||
onChange: (date?: Date) => void;
|
onChange: (date?: Date) => void;
|
||||||
popUpProps: PopoverProps;
|
popUpProps: PopoverProps;
|
||||||
popUpContentProps: PopoverContentProps;
|
popUpContentProps: PopoverContentProps;
|
||||||
|
dateFormat?: "PPP" | "PP" | "P"; // extend as needed
|
||||||
};
|
};
|
||||||
|
|
||||||
// Doc: https://react-day-picker.js.org/
|
// Doc: https://react-day-picker.js.org/
|
||||||
@@ -22,6 +23,7 @@ export const DatePicker = ({
|
|||||||
onChange,
|
onChange,
|
||||||
popUpProps,
|
popUpProps,
|
||||||
popUpContentProps,
|
popUpContentProps,
|
||||||
|
dateFormat = "PPP",
|
||||||
...props
|
...props
|
||||||
}: DatePickerProps) => {
|
}: DatePickerProps) => {
|
||||||
const [timeValue, setTimeValue] = useState<string>(value ? format(value, "HH:mm") : "00:00");
|
const [timeValue, setTimeValue] = useState<string>(value ? format(value, "HH:mm") : "00:00");
|
||||||
@@ -53,7 +55,7 @@ export const DatePicker = ({
|
|||||||
<Popover {...popUpProps}>
|
<Popover {...popUpProps}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="outline_bg" leftIcon={<FontAwesomeIcon icon={faCalendar} />}>
|
<Button variant="outline_bg" leftIcon={<FontAwesomeIcon icon={faCalendar} />}>
|
||||||
{value ? format(value, "PPP") : "Pick a date and time"}
|
{value ? format(value, dateFormat) : "Pick a date and time"}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-fit p-2" {...popUpContentProps}>
|
<PopoverContent className="w-fit p-2" {...popUpContentProps}>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const FilterableSelect = <T,>({
|
|||||||
indicatorSeparator: () => "bg-bunker-400",
|
indicatorSeparator: () => "bg-bunker-400",
|
||||||
dropdownIndicator: () => "text-bunker-200 p-1",
|
dropdownIndicator: () => "text-bunker-200 p-1",
|
||||||
menu: () =>
|
menu: () =>
|
||||||
"mt-2 border text-sm text-mineshaft-200 bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
"mt-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
||||||
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
||||||
option: ({ isFocused, isSelected }) =>
|
option: ({ isFocused, isSelected }) =>
|
||||||
twMerge(
|
twMerge(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
FilterableSelect,
|
||||||
FormControl,
|
FormControl,
|
||||||
Select,
|
Select,
|
||||||
SelectItem
|
SelectItem
|
||||||
@@ -64,7 +65,7 @@ export const LogsFilter = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspacesInOrg.length) {
|
if (workspacesInOrg.length) {
|
||||||
setValue("projectId", workspacesInOrg[0].id);
|
setValue("project", workspacesInOrg[0]);
|
||||||
}
|
}
|
||||||
}, [workspaces]);
|
}, [workspaces]);
|
||||||
|
|
||||||
@@ -111,11 +112,34 @@ export const LogsFilter = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"sticky top-20 z-10 flex items-center justify-between bg-bunker-800",
|
"sticky top-20 z-10 flex flex-wrap items-center justify-between bg-bunker-800",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
{isOrgAuditLogs && workspacesInOrg.length > 0 && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="project"
|
||||||
|
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Project"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="mr-12 w-64"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder="Select a project..."
|
||||||
|
options={workspacesInOrg.map(({ name, id }) => ({ name, id }))}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="mt-1 flex items-center space-x-2">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="eventType"
|
name="eventType"
|
||||||
@@ -123,7 +147,7 @@ export const LogsFilter = ({
|
|||||||
<FormControl label="Events">
|
<FormControl label="Events">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<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">
|
<div className="inline-flex w-full cursor-pointer items-center justify-between whitespace-nowrap 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">
|
||||||
{selectedEventTypes?.length === 1
|
{selectedEventTypes?.length === 1
|
||||||
? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0])
|
? eventTypes.find((eventType) => eventType.value === selectedEventTypes[0])
|
||||||
?.label
|
?.label
|
||||||
@@ -235,37 +259,6 @@ export const LogsFilter = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isOrgAuditLogs && workspacesInOrg.length > 0 && (
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="projectId"
|
|
||||||
render={({ field: { onChange, value, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Project"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
className="w-40"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
value={value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className={twMerge(
|
|
||||||
"w-full border border-mineshaft-500 bg-mineshaft-700 ",
|
|
||||||
value === undefined && "text-mineshaft-400"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{workspacesInOrg.map((project) => (
|
|
||||||
<SelectItem value={String(project.id || "")} key={project.id}>
|
|
||||||
{project.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Controller
|
<Controller
|
||||||
name="startDate"
|
name="startDate"
|
||||||
control={control}
|
control={control}
|
||||||
@@ -275,6 +268,7 @@ export const LogsFilter = ({
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
value={field.value || undefined}
|
value={field.value || undefined}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
dateFormat="P"
|
||||||
popUpProps={{
|
popUpProps={{
|
||||||
open: isStartDatePickerOpen,
|
open: isStartDatePickerOpen,
|
||||||
onOpenChange: setIsStartDatePickerOpen
|
onOpenChange: setIsStartDatePickerOpen
|
||||||
@@ -294,6 +288,7 @@ export const LogsFilter = ({
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
value={field.value || undefined}
|
value={field.value || undefined}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
dateFormat="P"
|
||||||
popUpProps={{
|
popUpProps={{
|
||||||
open: isEndDatePickerOpen,
|
open: isEndDatePickerOpen,
|
||||||
onOpenChange: setIsEndDatePickerOpen
|
onOpenChange: setIsEndDatePickerOpen
|
||||||
@@ -304,27 +299,27 @@ export const LogsFilter = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
isLoading={false}
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
className="mt-[0.45rem]"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} />}
|
||||||
|
onClick={() =>
|
||||||
|
reset({
|
||||||
|
eventType: presets?.eventType || [],
|
||||||
|
actor: presets?.actorId,
|
||||||
|
userAgentType: undefined,
|
||||||
|
startDate: undefined,
|
||||||
|
endDate: undefined,
|
||||||
|
project: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Clear filters
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
isLoading={false}
|
|
||||||
colorSchema="primary"
|
|
||||||
variant="outline_bg"
|
|
||||||
className="mt-1.5"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} />}
|
|
||||||
onClick={() =>
|
|
||||||
reset({
|
|
||||||
eventType: presets?.eventType || [],
|
|
||||||
actor: presets?.actorId,
|
|
||||||
userAgentType: undefined,
|
|
||||||
startDate: undefined,
|
|
||||||
endDate: undefined,
|
|
||||||
projectId: undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Clear filters
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const LogsSection = ({
|
|||||||
const { control, reset, watch, setValue } = useForm<AuditLogFilterFormData>({
|
const { control, reset, watch, setValue } = useForm<AuditLogFilterFormData>({
|
||||||
resolver: yupResolver(auditLogFilterFormSchema),
|
resolver: yupResolver(auditLogFilterFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
projectId: undefined,
|
project: null,
|
||||||
actor: presets?.actorId,
|
actor: presets?.actorId,
|
||||||
eventType: presets?.eventType || [],
|
eventType: presets?.eventType || [],
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -66,7 +66,7 @@ export const LogsSection = ({
|
|||||||
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 projectId = watch("projectId");
|
const projectId = watch("project")?.id;
|
||||||
|
|
||||||
const startDate = watch("startDate");
|
const startDate = watch("startDate");
|
||||||
const endDate = watch("endDate");
|
const endDate = watch("endDate");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
|||||||
export const auditLogFilterFormSchema = yup
|
export const auditLogFilterFormSchema = yup
|
||||||
.object({
|
.object({
|
||||||
eventMetadata: yup.object({}).optional(),
|
eventMetadata: yup.object({}).optional(),
|
||||||
projectId: yup.string().optional(),
|
project: yup.object({ id: yup.string().required(), name: yup.string().required() }).nullable(),
|
||||||
eventType: yup.array(yup.string().oneOf(Object.values(EventType), "Invalid event type")),
|
eventType: yup.array(yup.string().oneOf(Object.values(EventType), "Invalid event type")),
|
||||||
actor: yup.string(),
|
actor: yup.string(),
|
||||||
userAgentType: yup.string().oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
userAgentType: yup.string().oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
||||||
|
|||||||
Reference in New Issue
Block a user