95 lines
3.7 KiB
TypeScript
95 lines
3.7 KiB
TypeScript
import { History, ShieldCheck } from 'lucide-react';
|
|
import type { GatewayAuditLog } from '@easyai-ai-gateway/contracts';
|
|
import { Badge, Card, CardContent, CardHeader, CardTitle, Table, TableCell, TableHead, TableRow } from '../../components/ui';
|
|
|
|
export function AuditLogsPanel(props: { auditLogs: GatewayAuditLog[]; message?: string }) {
|
|
return (
|
|
<div className="pageStack">
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="identityHeaderTitle">
|
|
<div className="iconBox"><ShieldCheck size={17} /></div>
|
|
<div>
|
|
<CardTitle>审计日志</CardTitle>
|
|
<p className="mutedText">敏感管理动作独立记录,便于追踪操作者、对象和前后状态。</p>
|
|
</div>
|
|
<Badge variant="secondary">{props.auditLogs.length}</Badge>
|
|
</div>
|
|
</CardHeader>
|
|
{props.message && <CardContent><p className="formMessage">{props.message}</p></CardContent>}
|
|
</Card>
|
|
{props.auditLogs.length ? (
|
|
<Table className="identityDataTable auditLogTable">
|
|
<TableRow>
|
|
<TableHead>动作</TableHead>
|
|
<TableHead>操作者</TableHead>
|
|
<TableHead>目标</TableHead>
|
|
<TableHead>摘要</TableHead>
|
|
<TableHead>时间</TableHead>
|
|
</TableRow>
|
|
{props.auditLogs.map((item) => (
|
|
<TableRow key={item.id}>
|
|
<TableCell>
|
|
<span className="identityTableName">
|
|
<strong>{actionLabel(item.action)}</strong>
|
|
<small>{item.category}</small>
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>{item.actorUsername || item.actorUserId || '系统'}</TableCell>
|
|
<TableCell>
|
|
<span className="identityTableName">
|
|
<strong>{targetLabel(item.targetType)}</strong>
|
|
<small>{shortId(item.targetId)}</small>
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>{auditSummary(item)}</TableCell>
|
|
<TableCell>{formatDateTime(item.createdAt)}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</Table>
|
|
) : (
|
|
<Card>
|
|
<CardContent className="emptyState">
|
|
<History size={18} />
|
|
<strong>暂无审计日志</strong>
|
|
<span>后台余额调整后会在这里留下独立记录。</span>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function actionLabel(action: string) {
|
|
if (action === 'wallet.balance.set') return '余额调整';
|
|
if (action === 'wallet.balance.recharge') return '余额充值';
|
|
return action;
|
|
}
|
|
|
|
function targetLabel(targetType: string) {
|
|
if (targetType === 'gateway_user') return '用户';
|
|
return targetType;
|
|
}
|
|
|
|
function auditSummary(item: GatewayAuditLog) {
|
|
const metadata = item.metadata ?? {};
|
|
const direction = typeof metadata.direction === 'string' ? metadata.direction : '';
|
|
const amount = typeof metadata.amount === 'number' ? metadata.amount : undefined;
|
|
const currency = typeof metadata.currency === 'string' ? metadata.currency : 'resource';
|
|
const reason = typeof metadata.reason === 'string' ? metadata.reason : '';
|
|
const prefix = amount === undefined ? '' : `${direction === 'debit' ? '-' : '+'}${formatBalance(amount)} ${currency}`;
|
|
return [prefix, reason].filter(Boolean).join(' · ') || '已记录';
|
|
}
|
|
|
|
function formatDateTime(value: string) {
|
|
return value ? new Date(value).toLocaleString() : '-';
|
|
}
|
|
|
|
function formatBalance(value: number) {
|
|
return new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 6 }).format(value);
|
|
}
|
|
|
|
function shortId(value: string) {
|
|
return value.length > 12 ? `${value.slice(0, 8)}...${value.slice(-4)}` : value;
|
|
}
|