Files
lisglosips/apps/web/src/pages/OperationLogsPage.jsx
T

90 lines
5.5 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useMemo, useState } from 'react';
import { Badge, Button, Field, Input, Select } from '../components/ui.jsx';
import { Icon, PageTitle, Toolbar, Panel, ApiNotice, PageLoadingDialog, Drawer, SimpleTable, KeyValue, DateRangeFields } from '../components/layout.jsx';
import { operationLogRows } from '../fixtures/devFixtures.js';
import { dateRangeParams, lastSevenDays } from '../utils/dateRange.js';
const defaultRange = lastSevenDays();
function toneForStatus(status) {
return status === '成功' || status === 'SUCCESS' ? 'success' : status === '失败' || status === 'FAILURE' ? 'danger' : 'neutral';
}
export function OperationLogsPage({ logRows = operationLogRows, apiLoading, apiError, refreshApi }) {
const [keyword, setKeyword] = useState('');
const [moduleFilter, setModuleFilter] = useState('all');
const [resultFilter, setResultFilter] = useState('all');
const [startDate, setStartDate] = useState(defaultRange.startDate);
const [endDate, setEndDate] = useState(defaultRange.endDate);
const [detailLog, setDetailLog] = useState(null);
const modules = useMemo(() => Array.from(new Set(logRows.map((log) => log.module))), [logRows]);
const visibleLogs = useMemo(() => {
const normalized = keyword.trim().toLowerCase();
return logRows.filter((log) => {
const matchesKeyword = !normalized || [log.user, log.username, log.action, log.object, log.ip].some((value) => value.toLowerCase().includes(normalized));
const logDate = log.time.slice(0, 10);
return matchesKeyword && (moduleFilter === 'all' || log.module === moduleFilter) && (resultFilter === 'all' || log.result === resultFilter) && (!startDate || logDate >= startDate) && (!endDate || logDate <= endDate);
});
}, [endDate, keyword, logRows, moduleFilter, resultFilter, startDate]);
const queryLogs = (overrides = {}) => {
const next = { moduleFilter, resultFilter, startDate, endDate, ...overrides };
return refreshApi({
module: next.moduleFilter,
result: next.resultFilter === '成功' ? 'SUCCESS' : next.resultFilter === '失败' ? 'FAILURE' : 'all',
...dateRangeParams(next.startDate, next.endDate, 'createdFrom', 'createdTo'),
});
};
const resetFilters = () => {
setKeyword('');
setModuleFilter('all');
setResultFilter('all');
setStartDate(defaultRange.startDate);
setEndDate(defaultRange.endDate);
void queryLogs({ moduleFilter: 'all', resultFilter: 'all', ...defaultRange });
};
return (
<>
<PageTitle title="操作日志" desc="审计登录、配置变更、敏感操作及其执行结果。" actions={<Button variant="secondary" icon={<Icon type="export" />}>导出日志</Button>} />
<ApiNotice loading={apiLoading} error={apiError} onRetry={() => void queryLogs()} />
<PageLoadingDialog loading={apiLoading} />
<Toolbar>
<Field label="关键词"><Input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="用户、操作对象或 IP" /></Field>
<Field label="功能模块"><Select value={moduleFilter} onChange={(event) => setModuleFilter(event.target.value)}><option value="all">全部模块</option>{modules.map((module) => <option key={module} value={module}>{module}</option>)}</Select></Field>
<Field label="执行结果"><Select value={resultFilter} onChange={(event) => setResultFilter(event.target.value)}><option value="all">全部结果</option><option value="成功">成功</option><option value="失败">失败</option></Select></Field>
<DateRangeFields startDate={startDate} endDate={endDate} onStartChange={setStartDate} onEndChange={setEndDate} />
<Button icon={<Icon type="search" />} disabled={apiLoading} onClick={() => void queryLogs()}>查询</Button>
<Button variant="outline" onClick={resetFilters}>重置</Button>
</Toolbar>
<Panel title="日志列表" aside={<Badge tone="neutral"> {visibleLogs.length} </Badge>} className="wide-panel">
<SimpleTable loading={apiLoading} rows={visibleLogs} columns={[
{ key: 'time', label: '操作时间' },
{ key: 'user', label: '操作用户' },
{ key: 'module', label: '功能模块' },
{ key: 'action', label: '操作类型' },
{ key: 'object', label: '操作对象' },
{ key: 'result', label: '结果', status: true },
{ key: 'ip', label: '操作 IP' },
{ key: 'actions', label: '操作', render: (row) => <Button size="sm" variant="outline" onClick={() => setDetailLog(row)}>查看详情</Button> },
]} />
</Panel>
{detailLog ? (
<Drawer title="操作日志详情" aside={<Badge tone={toneForStatus(detailLog.result)}>{detailLog.result}</Badge>} onClose={() => setDetailLog(null)}>
<div className="detail-stack">
<KeyValue label="日志 ID" value={detailLog.id} />
<KeyValue label="操作时间" value={detailLog.time} />
<KeyValue label="操作用户" value={`${detailLog.user}${detailLog.username}`} />
<KeyValue label="功能模块" value={detailLog.module} />
<KeyValue label="操作类型" value={detailLog.action} />
<KeyValue label="操作对象" value={detailLog.object} />
<KeyValue label="执行结果" value={detailLog.result} />
<KeyValue label="操作 IP" value={detailLog.ip} />
<KeyValue label="客户端" value={detailLog.userAgent} />
</div>
<section className="log-summary"><strong>操作摘要</strong><p>{detailLog.summary}</p></section>
</Drawer>
) : null}
</>
);
}