diff --git a/server/routers/auditLogs/queryRequestAuditLog.ts b/server/routers/auditLogs/queryRequestAuditLog.ts
index 24116aa31..aa7769bdf 100644
--- a/server/routers/auditLogs/queryRequestAuditLog.ts
+++ b/server/routers/auditLogs/queryRequestAuditLog.ts
@@ -82,7 +82,7 @@ export const queryAccessAuditLogsQuery = z.strictObject({
.default("0")
.transform(Number)
.pipe(z.int().nonnegative()),
- ips: z
+ ip: z
.preprocess((val) => {
if (val === undefined || val === null || val === "") {
return undefined;
@@ -146,7 +146,8 @@ function getWhere(data: Q) {
data.path ? eq(requestAuditLog.path, data.path) : undefined,
data.action !== undefined
? eq(requestAuditLog.action, data.action)
- : undefined
+ : undefined,
+ data.ip ? inArray(requestAuditLog.ip, data.ip) : undefined
);
}
diff --git a/src/app/[orgId]/settings/logs/request/page.tsx b/src/app/[orgId]/settings/logs/request/page.tsx
index d1300f73f..5918060fe 100644
--- a/src/app/[orgId]/settings/logs/request/page.tsx
+++ b/src/app/[orgId]/settings/logs/request/page.tsx
@@ -22,6 +22,7 @@ import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import type { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types";
import { ColumnFilterButton } from "@app/components/ColumnFilterButton";
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
+import { ColumnMultiFilterButton } from "@app/components/ColumnMultiFilterButton";
export default function GeneralPage() {
const router = useRouter();
@@ -44,6 +45,7 @@ export default function GeneralPage() {
method?: string;
reason?: string;
path?: string;
+ ip?: string[];
}>({
action: searchParams.get("action") || undefined,
host: searchParams.get("host") || undefined,
@@ -52,7 +54,8 @@ export default function GeneralPage() {
actor: searchParams.get("actor") || undefined,
method: searchParams.get("method") || undefined,
reason: searchParams.get("reason") || undefined,
- path: searchParams.get("path") || undefined
+ path: searchParams.get("path") || undefined,
+ ip: searchParams.getAll("ip") || undefined
});
const getDefaultDateRange = () => {
@@ -159,7 +162,7 @@ export default function GeneralPage() {
const handleFilterChange = (
filterType: keyof typeof filters,
- value: string | undefined
+ value: string | string[] | undefined
) => {
const newFilters = { ...filters, [filterType]: value };
setFilters(newFilters);
@@ -177,10 +180,13 @@ export default function GeneralPage() {
) => {
const params = new URLSearchParams(searchParams);
Object.entries(newFilters).forEach(([key, value]) => {
- if (value) {
+ params.delete(key);
+ if (typeof value === "string") {
params.set(key, value);
- } else {
- params.delete(key);
+ } else if (typeof value !== "undefined" && "length" in value) {
+ for (const element of value) {
+ params.append(key, element);
+ }
}
});
router.replace(`?${params.toString()}`, { scroll: false });
@@ -329,7 +335,22 @@ export default function GeneralPage() {
},
{
accessorKey: "ip",
- header: ({ column }) => {t("ip")}
+ header: ({ column }) => (
+
+ ({
+ label: ip,
+ value: ip
+ }))}
+ label={t("ip")}
+ allowArbitraryValues
+ selectedValues={filters.ip ?? []}
+ onSelectedValuesChange={(value) =>
+ handleFilterChange("ip", value)
+ }
+ />
+
+ )
},
{
accessorKey: "location",
diff --git a/src/components/ColumnMultiFilterButton.tsx b/src/components/ColumnMultiFilterButton.tsx
index d6ebaa482..ba1c4b06a 100644
--- a/src/components/ColumnMultiFilterButton.tsx
+++ b/src/components/ColumnMultiFilterButton.tsx
@@ -35,6 +35,7 @@ type ColumnMultiFilterButtonProps = {
emptyMessage?: string;
className?: string;
label: string;
+ allowArbitraryValues?: boolean;
};
export function ColumnMultiFilterButton({
@@ -44,11 +45,26 @@ export function ColumnMultiFilterButton({
searchPlaceholder = "Search...",
emptyMessage = "No options found",
className,
- label
+ label,
+ allowArbitraryValues
}: ColumnMultiFilterButtonProps) {
const [open, setOpen] = useState(false);
+ const [searchQuery, setSearchQuery] = useState("");
const t = useTranslations();
+ const visibleOptions = useMemo(() => {
+ const newOptions = [...options];
+
+ if (allowArbitraryValues && searchQuery.trim().length > 0) {
+ newOptions.push({
+ label: searchQuery,
+ value: searchQuery
+ });
+ }
+
+ return newOptions;
+ }, [options, allowArbitraryValues, searchQuery]);
+
const selectedSet = useMemo(
() => new Set(selectedValues),
[selectedValues]
@@ -108,7 +124,11 @@ export function ColumnMultiFilterButton({
align="start"
>
-
+
{emptyMessage}
@@ -123,7 +143,7 @@ export function ColumnMultiFilterButton({
{t("accessFilterClear")}
)}
- {options.map((option) => (
+ {visibleOptions.map((option) => (
;