90 lines
2.3 KiB
TypeScript
90 lines
2.3 KiB
TypeScript
/**
|
||
* 审计日志 Hooks
|
||
*
|
||
* contract §2.4:auditLogs
|
||
* 仲裁 ARB-005:经 teacher-bff GraphQL 消费(不直连 Kafka)
|
||
*/
|
||
import { useCallback, useState } from "react";
|
||
import { useGraphQuery } from "./use-graphql";
|
||
import { AUDIT_LOGS_QUERY } from "@/lib/graphql-client";
|
||
import type {
|
||
AuditLogViewModel,
|
||
PaginatedResult,
|
||
ListFilter,
|
||
} from "@/types/view-models";
|
||
|
||
interface AuditLogsResponse {
|
||
auditLogs: PaginatedResult<AuditLogViewModel>;
|
||
}
|
||
|
||
interface AuditLogFilter extends Partial<ListFilter> {
|
||
action?: string;
|
||
actorUserId?: string;
|
||
startDate?: number;
|
||
endDate?: number;
|
||
}
|
||
|
||
export function useAuditLogs(filter: AuditLogFilter) {
|
||
const result = useGraphQuery<AuditLogsResponse>(AUDIT_LOGS_QUERY, { filter });
|
||
return {
|
||
...result,
|
||
data: result.data?.auditLogs ?? null,
|
||
};
|
||
}
|
||
|
||
export function useAuditLogFilter(initial?: Partial<AuditLogFilter>) {
|
||
const [filter, setFilter] = useState<AuditLogFilter>({
|
||
page: 1,
|
||
pageSize: 20,
|
||
...initial,
|
||
});
|
||
const setPage = useCallback(
|
||
(page: number) => setFilter((f) => ({ ...f, page })),
|
||
[],
|
||
);
|
||
const setSearch = useCallback(
|
||
(search: string) => setFilter((f) => ({ ...f, search, page: 1 })),
|
||
[],
|
||
);
|
||
const setAction = useCallback(
|
||
(action: string) =>
|
||
setFilter((f) => ({ ...f, action: action || undefined, page: 1 })),
|
||
[],
|
||
);
|
||
return { filter, setFilter, setPage, setSearch, setAction };
|
||
}
|
||
|
||
/** 审计日志导出 CSV */
|
||
export function exportAuditLogsCsv(logs: AuditLogViewModel[]): void {
|
||
const headers = [
|
||
"时间",
|
||
"操作人",
|
||
"操作",
|
||
"资源类型",
|
||
"资源ID",
|
||
"IP",
|
||
"追踪ID",
|
||
];
|
||
const rows = logs.map((l) => [
|
||
new Date(l.occurredAt).toLocaleString("zh-CN"),
|
||
l.actorName,
|
||
l.action,
|
||
l.resourceType,
|
||
l.resourceId,
|
||
l.ip,
|
||
l.traceId ?? "",
|
||
]);
|
||
const csv = [headers, ...rows]
|
||
.map((row) =>
|
||
row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(","),
|
||
)
|
||
.join("\n");
|
||
const blob = new Blob([`\uFEFF${csv}`], { type: "text/csv;charset=utf-8;" });
|
||
const url = URL.createObjectURL(blob);
|
||
const link = document.createElement("a");
|
||
link.href = url;
|
||
link.download = `audit-logs-${new Date().toISOString().slice(0, 10)}.csv`;
|
||
link.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|