import { useEffect, useState } from 'react' import { Link, useSearchParams } from 'react-router' import { useList } from '@refinedev/core' import { Alert, Button, PageHeader, PageState, Table } from './lib/ui.jsx' import { useTitle } from './lib/hooks.js' const startOfDay = value => (value ? new Date(`${value}T00:00:00`).toISOString() : '') const endOfDay = value => (value ? new Date(`${value}T23:59:59.999`).toISOString() : '') const csvColumns = ['id', 'event_type', 'account_id', 'task_id', 'attempt_id', 'browser_env_alias', 'network_exit_id', 'binding_version', 'reason_code', 'outcome', 'created_at'] const csvCell = value => `"${String(value ?? '').replaceAll('"', '""')}"` // 导出仅含关联字段的脱敏白名单,不带 details。 export function auditCSV(events) { return [csvColumns, ...events.map(event => csvColumns.map(column => event[column]))] .map(row => row.map(csvCell).join(',')) .join('\n') } function downloadCSV(events) { const url = URL.createObjectURL(new Blob([auditCSV(events)], { type: 'text/csv;charset=utf-8' })) const anchor = document.createElement('a') anchor.href = url anchor.download = 'creatorhub-audit.csv' anchor.click() URL.revokeObjectURL(url) } const filterInput = 'h-9 w-full rounded-md border border-[#c4ccd9] bg-white px-3 text-sm focus:border-primary focus:outline-none' function FilterField({ id, label, ...props }) { return (
) } export function AuditPage() { const [searchParams] = useSearchParams() const [account, setAccount] = useState(searchParams.get('account_id') || '') const [task, setTask] = useState(searchParams.get('task_id') || '') const [attempt, setAttempt] = useState(searchParams.get('attempt_id') || '') const [env, setEnv] = useState(searchParams.get('browser_env_alias') || '') const [exit, setExit] = useState(searchParams.get('network_exit_id') || '') const [eventType, setEventType] = useState('') const [from, setFrom] = useState('') const [to, setTo] = useState('') const [page, setPage] = useState(1) const filters = [ { field: 'account_id', value: account }, { field: 'task_id', value: task }, { field: 'attempt_id', value: attempt }, { field: 'browser_env_alias', value: env }, { field: 'network_exit_id', value: exit }, { field: 'event_type', value: eventType }, { field: 'from', value: startOfDay(from) }, { field: 'to', value: endOfDay(to) }, ] const { result, query } = useList({ resource: 'audit', pagination: { currentPage: page, pageSize: 25 }, filters, queryOptions: { retry: false } }) const error = query.error const isPending = query.isPending const events = result.data ?? [] const total = result.total ?? 0 useTitle('CreatorHub · 审计记录') useEffect(() => { setPage(1) }, [account, task, attempt, env, exit, eventType, from, to]) const hasFilters = !!(account || task || attempt || env || exit || eventType || from || to) return (
setAccount(event.target.value)} /> setTask(event.target.value)} /> setAttempt(event.target.value)} /> setEnv(event.target.value)} /> setExit(event.target.value)} /> setEventType(event.target.value)} /> setFrom(event.target.value)} /> setTo(event.target.value)} />
{hasFilters ? (
) : null} {error ? {error.message} : null} (

{event.event_type}

#{event.id}

) }, { header: '关联', width: '27%', render: event => (
{event.account_id ?

账号:{event.account_id}

: null} {event.task_id ?

任务:{event.task_id}

: null} {event.attempt_id ?

Attempt:{event.attempt_id}

: null}
) }, { header: '环境 / 出口', width: '24%', render: event => (

{event.browser_env_alias ? {event.browser_env_alias} : '—'} {' / '} {event.network_exit_id ? {event.network_exit_id} : '—'}

绑定版本 {event.binding_version || '—'}

) }, { header: '原因 / 时间', width: '29%', render: event => (

{event.reason_code || event.outcome || '—'}

{new Date(event.created_at).toLocaleString('zh-CN')}

) }, ]} rows={events} rowKey={event => event.id} minWidth="1000px" /> {total > 25 ? (

第 {page} / {Math.ceil(total / 25)} 页

) : null} ) }