mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-21 11:42:37 +02:00
🚧 wip: add ip is column filter
This commit is contained in:
@@ -82,7 +82,7 @@ export const queryAccessAuditLogsQuery = z.strictObject({
|
|||||||
.default("0")
|
.default("0")
|
||||||
.transform(Number)
|
.transform(Number)
|
||||||
.pipe(z.int().nonnegative()),
|
.pipe(z.int().nonnegative()),
|
||||||
ips: z
|
ip: z
|
||||||
.preprocess((val) => {
|
.preprocess((val) => {
|
||||||
if (val === undefined || val === null || val === "") {
|
if (val === undefined || val === null || val === "") {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -146,7 +146,8 @@ function getWhere(data: Q) {
|
|||||||
data.path ? eq(requestAuditLog.path, data.path) : undefined,
|
data.path ? eq(requestAuditLog.path, data.path) : undefined,
|
||||||
data.action !== undefined
|
data.action !== undefined
|
||||||
? eq(requestAuditLog.action, data.action)
|
? eq(requestAuditLog.action, data.action)
|
||||||
: undefined
|
: undefined,
|
||||||
|
data.ip ? inArray(requestAuditLog.ip, data.ip) : undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
|
|||||||
import type { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types";
|
import type { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types";
|
||||||
import { ColumnFilterButton } from "@app/components/ColumnFilterButton";
|
import { ColumnFilterButton } from "@app/components/ColumnFilterButton";
|
||||||
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
||||||
|
import { ColumnMultiFilterButton } from "@app/components/ColumnMultiFilterButton";
|
||||||
|
|
||||||
export default function GeneralPage() {
|
export default function GeneralPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -44,6 +45,7 @@ export default function GeneralPage() {
|
|||||||
method?: string;
|
method?: string;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
ip?: string[];
|
||||||
}>({
|
}>({
|
||||||
action: searchParams.get("action") || undefined,
|
action: searchParams.get("action") || undefined,
|
||||||
host: searchParams.get("host") || undefined,
|
host: searchParams.get("host") || undefined,
|
||||||
@@ -52,7 +54,8 @@ export default function GeneralPage() {
|
|||||||
actor: searchParams.get("actor") || undefined,
|
actor: searchParams.get("actor") || undefined,
|
||||||
method: searchParams.get("method") || undefined,
|
method: searchParams.get("method") || undefined,
|
||||||
reason: searchParams.get("reason") || undefined,
|
reason: searchParams.get("reason") || undefined,
|
||||||
path: searchParams.get("path") || undefined
|
path: searchParams.get("path") || undefined,
|
||||||
|
ip: searchParams.getAll("ip") || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const getDefaultDateRange = () => {
|
const getDefaultDateRange = () => {
|
||||||
@@ -159,7 +162,7 @@ export default function GeneralPage() {
|
|||||||
|
|
||||||
const handleFilterChange = (
|
const handleFilterChange = (
|
||||||
filterType: keyof typeof filters,
|
filterType: keyof typeof filters,
|
||||||
value: string | undefined
|
value: string | string[] | undefined
|
||||||
) => {
|
) => {
|
||||||
const newFilters = { ...filters, [filterType]: value };
|
const newFilters = { ...filters, [filterType]: value };
|
||||||
setFilters(newFilters);
|
setFilters(newFilters);
|
||||||
@@ -177,10 +180,13 @@ export default function GeneralPage() {
|
|||||||
) => {
|
) => {
|
||||||
const params = new URLSearchParams(searchParams);
|
const params = new URLSearchParams(searchParams);
|
||||||
Object.entries(newFilters).forEach(([key, value]) => {
|
Object.entries(newFilters).forEach(([key, value]) => {
|
||||||
if (value) {
|
|
||||||
params.set(key, value);
|
|
||||||
} else {
|
|
||||||
params.delete(key);
|
params.delete(key);
|
||||||
|
if (typeof value === "string") {
|
||||||
|
params.set(key, value);
|
||||||
|
} else if (typeof value !== "undefined" && "length" in value) {
|
||||||
|
for (const element of value) {
|
||||||
|
params.append(key, element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
router.replace(`?${params.toString()}`, { scroll: false });
|
router.replace(`?${params.toString()}`, { scroll: false });
|
||||||
@@ -329,7 +335,22 @@ export default function GeneralPage() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "ip",
|
accessorKey: "ip",
|
||||||
header: ({ column }) => <span className="px-2">{t("ip")}</span>
|
header: ({ column }) => (
|
||||||
|
<span className="px-2">
|
||||||
|
<ColumnMultiFilterButton
|
||||||
|
options={(filters.ip ?? []).map((ip) => ({
|
||||||
|
label: ip,
|
||||||
|
value: ip
|
||||||
|
}))}
|
||||||
|
label={t("ip")}
|
||||||
|
allowArbitraryValues
|
||||||
|
selectedValues={filters.ip ?? []}
|
||||||
|
onSelectedValuesChange={(value) =>
|
||||||
|
handleFilterChange("ip", value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "location",
|
accessorKey: "location",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type ColumnMultiFilterButtonProps = {
|
|||||||
emptyMessage?: string;
|
emptyMessage?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
allowArbitraryValues?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ColumnMultiFilterButton({
|
export function ColumnMultiFilterButton({
|
||||||
@@ -44,11 +45,26 @@ export function ColumnMultiFilterButton({
|
|||||||
searchPlaceholder = "Search...",
|
searchPlaceholder = "Search...",
|
||||||
emptyMessage = "No options found",
|
emptyMessage = "No options found",
|
||||||
className,
|
className,
|
||||||
label
|
label,
|
||||||
|
allowArbitraryValues
|
||||||
}: ColumnMultiFilterButtonProps) {
|
}: ColumnMultiFilterButtonProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
|
const visibleOptions = useMemo<FilterOption[]>(() => {
|
||||||
|
const newOptions = [...options];
|
||||||
|
|
||||||
|
if (allowArbitraryValues && searchQuery.trim().length > 0) {
|
||||||
|
newOptions.push({
|
||||||
|
label: searchQuery,
|
||||||
|
value: searchQuery
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOptions;
|
||||||
|
}, [options, allowArbitraryValues, searchQuery]);
|
||||||
|
|
||||||
const selectedSet = useMemo(
|
const selectedSet = useMemo(
|
||||||
() => new Set(selectedValues),
|
() => new Set(selectedValues),
|
||||||
[selectedValues]
|
[selectedValues]
|
||||||
@@ -108,7 +124,11 @@ export function ColumnMultiFilterButton({
|
|||||||
align="start"
|
align="start"
|
||||||
>
|
>
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder={searchPlaceholder} />
|
<CommandInput
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
value={searchQuery}
|
||||||
|
onValueChange={setSearchQuery}
|
||||||
|
/>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>{emptyMessage}</CommandEmpty>
|
<CommandEmpty>{emptyMessage}</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
@@ -123,7 +143,7 @@ export function ColumnMultiFilterButton({
|
|||||||
{t("accessFilterClear")}
|
{t("accessFilterClear")}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
)}
|
)}
|
||||||
{options.map((option) => (
|
{visibleOptions.map((option) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.label}
|
value={option.label}
|
||||||
|
|||||||
+3
-2
@@ -42,7 +42,7 @@ import {
|
|||||||
queryOptions
|
queryOptions
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import z, { meta } from "zod";
|
import z from "zod";
|
||||||
import { remote } from "./api";
|
import { remote } from "./api";
|
||||||
import { durationToMs } from "./durationToMs";
|
import { durationToMs } from "./durationToMs";
|
||||||
import type { ListOrgLabelsResponse } from "@server/routers/labels/types";
|
import type { ListOrgLabelsResponse } from "@server/routers/labels/types";
|
||||||
@@ -782,7 +782,8 @@ export const httpLogsFiltersSchema = z.object({
|
|||||||
actor: z.string().optional().catch(undefined),
|
actor: z.string().optional().catch(undefined),
|
||||||
method: z.string().optional().catch(undefined),
|
method: z.string().optional().catch(undefined),
|
||||||
reason: z.string().optional().catch(undefined),
|
reason: z.string().optional().catch(undefined),
|
||||||
path: z.string().optional().catch(undefined)
|
path: z.string().optional().catch(undefined),
|
||||||
|
ips: z.array(z.string()).optional().catch(undefined)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type HttpLogFilters = z.output<typeof httpLogsFiltersSchema>;
|
export type HttpLogFilters = z.output<typeof httpLogsFiltersSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user