import { Fragment, useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "../auth/AuthContext"; import { useI18n } from "../i18n"; import { apiGet } from "../services/api"; import { formatSystemTime } from "../utils/time"; type AuditItem = { id: string; ts: string | null; actor_username: string; action: string; method: string; path: string; status_code: number; client_ip: string; detail: Record; }; /** Quick filters map to action substring and/or exclude_noise flag. */ type QuickFilter = "business" | "webcrt." | "ne.exec" | "auth." | "http." | "all"; function statusClass(code: number): string { if (code >= 500) return "pt-list-status--failed"; if (code >= 400) return "pt-list-status--warning"; if (code >= 200 && code < 300) return "pt-list-status--ok"; return "pt-list-status--other"; } function detailStr(detail: Record, key: string): string { const v = detail?.[key]; if (v == null) return ""; return String(v).trim(); } function deviceLabel(detail: Record): string { const name = detailStr(detail, "ne_name"); const ip = detailStr(detail, "ne_ip"); if (name && ip) return `${name} (${ip})`; return name || ip || detailStr(detail, "ne_id") || ""; } export function auditSummary( action: string, detail: Record, t: (key: string, vars?: Record) => string, row?: Pick, ): string { const d = detail || {}; const device = deviceLabel(d); if (action === "webcrt.session_connecting") { return t("audit.summary.connecting", { device: device || t("common.empty") }); } if (action === "webcrt.session_created") { return t("audit.summary.loginOk", { device: device || t("common.empty") }); } if (action === "webcrt.session_open_failed") { const err = detailStr(d, "error") || t("common.empty"); return t("audit.summary.loginFail", { device: device || t("common.empty"), error: err.slice(0, 80) }); } if (action === "webcrt.session_closed") { const reason = detailStr(d, "reason"); return reason ? t("audit.summary.logoutReason", { device: device || t("common.empty"), reason }) : t("audit.summary.logout", { device: device || t("common.empty") }); } if (action === "webcrt.command") { const cmd = detailStr(d, "command") || t("common.empty"); const redacted = Boolean(d.redacted); return t("audit.summary.command", { device: device || t("common.empty"), command: redacted ? "***" : cmd, }); } if (action === "ne.exec") { const cmds = Array.isArray(d.commands) ? d.commands.map(String).filter(Boolean) : []; return t("audit.summary.neExec", { device: device || t("common.empty"), commands: cmds.slice(0, 3).join("; ") || t("common.empty"), }); } if (action === "ne.exec_batch") { return t("audit.summary.neExecBatch", { n: String(d.target_count ?? ((d.ne_ids as unknown[]) || []).length || 0), ok: String(d.ok_count ?? 0), fail: String(d.fail_count ?? 0), }); } if (action.startsWith("auth.")) { if (action === "auth.unauthorized") { const method = row?.method || "GET"; const path = row?.path || ""; return path ? `${method} ${path}` : "unauthorized"; } return action.replace(/^auth\./, ""); } if (action.startsWith("http.") || action.startsWith("ume.")) { const method = row?.method || action.replace(/^http\./, "").toUpperCase(); const path = row?.path || ""; return path ? `${method} ${path}` : method; } return ""; } function quickToQuery(quick: QuickFilter): { action: string; excludeNoise: boolean } { if (quick === "business") return { action: "", excludeNoise: true }; if (quick === "all") return { action: "", excludeNoise: false }; if (quick === "http.") return { action: "http.", excludeNoise: false }; return { action: quick, excludeNoise: true }; } export function AuditPage() { const { t } = useI18n(); const { ready, isAdmin } = useAuth(); const [page, setPage] = useState(1); const [username, setUsername] = useState(""); const [action, setAction] = useState(""); const [quick, setQuick] = useState("business"); const [expanded, setExpanded] = useState(null); const derived = quickToQuery(quick); const effectiveAction = action.trim() || derived.action; const excludeNoise = action.trim() ? false : derived.excludeNoise; const query = useQuery({ queryKey: ["auditLogs", page, username, effectiveAction, excludeNoise], queryFn: () => { const p = new URLSearchParams(); p.set("page", String(page)); p.set("page_size", "50"); p.set("exclude_noise", excludeNoise ? "true" : "false"); if (username.trim()) p.set("username", username.trim()); if (effectiveAction) p.set("action", effectiveAction); return apiGet<{ total: number; page: number; page_size: number; items: AuditItem[] }>( `/v1/audit-logs?${p.toString()}`, ); }, enabled: ready, }); const items = useMemo(() => query.data?.items || [], [query.data]); const total = query.data?.total || 0; const pages = Math.max(1, Math.ceil(total / 50)); const hasFilters = Boolean(username.trim() || action.trim() || quick !== "business"); const setQuickFilter = (next: QuickFilter) => { setPage(1); setQuick(next); if (next !== "all" && next !== "business") setAction(""); else setAction(""); }; return (

{t("audit.logsTitle")}

{( [ ["business", "audit.filterBusiness"], ["webcrt.", "audit.filterWebcrt"], ["ne.exec", "audit.filterNeExec"], ["auth.", "audit.filterAuth"], ["http.", "audit.filterHttp"], ["all", "audit.filterAll"], ] as const ).map(([value, labelKey]) => ( ))}
{isAdmin ? ( { setPage(1); setUsername(e.target.value); }} /> ) : null} { setPage(1); setAction(e.target.value); if (e.target.value.trim()) setQuick("all"); }} />
{query.isLoading ?

{t("common.refreshing")}

: null} {!items.length && !query.isLoading ? (

{t("common.empty")}

) : (
{items.map((row) => { const open = expanded === row.id; const summary = auditSummary(row.action, row.detail || {}, t, row); return ( {open ? ( ) : null} ); })}
{t("auth.colTime")} {t("auth.colUser")} {t("auth.colAction")} {t("audit.colSummary")} {t("auth.colStatus")} {t("auth.colIp")} {t("audit.colDetail")}
{row.ts ? formatSystemTime(row.ts) : t("common.empty")} {row.actor_username || t("common.empty")} {row.action} {summary || row.path || t("common.empty")} {row.status_code ? ( {row.status_code} ) : ( — )} {row.client_ip || t("common.empty")}
{JSON.stringify(row.detail || {}, null, 2)}
{row.method || row.path ? (

{row.method} {row.path}

) : null}
)}
{t("common.pagerMeta", { total: String(total), page: String(page), pages: String(pages), })}
); }