forked from datawhale/whale-town-end
feat:添加日志功能
This commit is contained in:
106
client/src/pages/LogsPage.tsx
Normal file
106
client/src/pages/LogsPage.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Alert, Button, Card, InputNumber, Space, Typography } from 'antd';
|
||||
import { api, ApiError } from '../lib/api';
|
||||
|
||||
export function LogsPage() {
|
||||
const [lines, setLines] = useState<number>(200);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [downloadLoading, setDownloadLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<string>('');
|
||||
const [updatedAt, setUpdatedAt] = useState<string>('');
|
||||
const [logLines, setLogLines] = useState<string[]>([]);
|
||||
|
||||
const logText = useMemo(() => logLines.join('\n'), [logLines]);
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await api.getRuntimeLogs(lines);
|
||||
if (!res?.success) {
|
||||
setError(res?.message || '运行日志获取失败');
|
||||
return;
|
||||
}
|
||||
setFile(res?.data?.file || '');
|
||||
setUpdatedAt(res?.data?.updated_at || '');
|
||||
setLogLines(Array.isArray(res?.data?.lines) ? res.data.lines : []);
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError(e instanceof Error ? e.message : '运行日志获取失败');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const downloadArchive = async () => {
|
||||
setDownloadLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const { blob, filename } = await api.downloadLogsArchive();
|
||||
const url = URL.createObjectURL(blob);
|
||||
try {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename || 'logs.tar.gz';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} finally {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError(e instanceof Error ? e.message : '日志下载失败');
|
||||
}
|
||||
} finally {
|
||||
setDownloadLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
{error ? <Alert type="error" message={error} /> : null}
|
||||
|
||||
<Card
|
||||
title="运行日志"
|
||||
extra={
|
||||
<Space>
|
||||
<span>行数</span>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={2000}
|
||||
value={lines}
|
||||
onChange={(v) => setLines(typeof v === 'number' ? v : 200)}
|
||||
/>
|
||||
<Button onClick={() => void load()} loading={loading}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button onClick={() => void downloadArchive()} loading={downloadLoading}>
|
||||
下载日志压缩包
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
||||
<Typography.Text type="secondary">
|
||||
{file ? `文件:${file}` : '文件:-'}
|
||||
{updatedAt ? ` 更新时间:${updatedAt}` : ''}
|
||||
</Typography.Text>
|
||||
|
||||
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{logText || '暂无日志'}</pre>
|
||||
</Space>
|
||||
</Card>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user