forked from xiangwang25/whale-town-end-v2
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
}
|