Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- 删除 webhook 死代码模块 (4 文件 + worker,未注册未挂载) - 删除孤立组件 StatusTag.tsx (从未被导入) - authStore 权限模型补全 (scheduler/knowledge/billing 6+ permission key) - authStore 硬编码 logout URL 改为 env 变量 - 清理未使用 service 方法 (agent-templates/billing/roles) - Logs.tsx 代码重复消除 (本地常量 → @/constants/status) - TRUTH.md 数字校准 (Tauri 177→183, SaaS API 131→130)
92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
// ============================================================
|
|
// 操作日志
|
|
// ============================================================
|
|
|
|
import { useState } from 'react'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { Tag, Select, Typography } from 'antd'
|
|
import type { ProColumns } from '@ant-design/pro-components'
|
|
import { ProTable } from '@ant-design/pro-components'
|
|
import { logService } from '@/services/logs'
|
|
import { actionLabels, actionColors } from '@/constants/status'
|
|
import type { OperationLog } from '@/types'
|
|
|
|
const { Title } = Typography
|
|
|
|
const actionOptions = Object.entries(actionLabels).map(([value, label]) => ({ value, label }))
|
|
|
|
export default function Logs() {
|
|
const [page, setPage] = useState(1)
|
|
const [actionFilter, setActionFilter] = useState<string | undefined>(undefined)
|
|
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['logs', page, actionFilter],
|
|
queryFn: ({ signal }) => logService.list({ page, page_size: 20, action: actionFilter }, signal),
|
|
})
|
|
|
|
const columns: ProColumns<OperationLog>[] = [
|
|
{
|
|
title: '操作类型',
|
|
dataIndex: 'action',
|
|
width: 140,
|
|
render: (_, r) => (
|
|
<Tag color={actionColors[r.action] || 'default'}>
|
|
{actionLabels[r.action] || r.action}
|
|
</Tag>
|
|
),
|
|
},
|
|
{ title: '目标类型', dataIndex: 'target_type', width: 100, render: (_, r) => r.target_type || '-' },
|
|
{ title: '目标 ID', dataIndex: 'target_id', width: 120, render: (_, r) => r.target_id ? <code>{r.target_id.substring(0, 8)}...</code> : '-' },
|
|
{
|
|
title: '详情',
|
|
dataIndex: 'details',
|
|
width: 250,
|
|
ellipsis: true,
|
|
render: (_, r) => {
|
|
if (!r.details) return '-'
|
|
if (typeof r.details === 'string') return r.details
|
|
return JSON.stringify(r.details)
|
|
},
|
|
},
|
|
{ title: 'IP 地址', dataIndex: 'ip_address', width: 130, render: (_, r) => <code>{r.ip_address || '-'}</code> },
|
|
{
|
|
title: '时间',
|
|
dataIndex: 'created_at',
|
|
width: 180,
|
|
render: (_, r) => new Date(r.created_at).toLocaleString('zh-CN'),
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
|
<Title level={4} style={{ margin: 0 }}>操作日志</Title>
|
|
<Select
|
|
value={actionFilter}
|
|
onChange={(v) => { setActionFilter(v === 'all' ? undefined : v); setPage(1) }}
|
|
placeholder="操作类型筛选"
|
|
style={{ width: 160 }}
|
|
allowClear
|
|
options={[{ value: 'all', label: '全部操作' }, ...actionOptions]}
|
|
/>
|
|
</div>
|
|
|
|
<ProTable<OperationLog>
|
|
columns={columns}
|
|
dataSource={data?.items ?? []}
|
|
loading={isLoading}
|
|
rowKey="id"
|
|
search={false}
|
|
toolBarRender={false}
|
|
pagination={{
|
|
total: data?.total ?? 0,
|
|
pageSize: 20,
|
|
current: page,
|
|
onChange: setPage,
|
|
showSizeChanger: false,
|
|
}}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|