mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-08 03:59:00 +08:00
Redesigned network display cards and related components for a more modern, consistent look, including improved button styles, card layouts, and responsive design. Added support for background images and dynamic theming across cards, tables, and log views. Enhanced input and select components with unified styling. Improved file table responsiveness and log display usability. Refactored OneBot API debug and navigation UI for better usability and mobile support.
125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
import { Button } from '@heroui/button';
|
|
import type { Selection } from '@react-types/shared';
|
|
import { useLocalStorage } from '@uidotdev/usehooks';
|
|
import clsx from 'clsx';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import toast from 'react-hot-toast';
|
|
import { IoDownloadOutline } from 'react-icons/io5';
|
|
|
|
import key from '@/const/key';
|
|
import { colorizeLogLevelWithTag } from '@/utils/terminal';
|
|
|
|
import WebUIManager, { Log } from '@/controllers/webui_manager';
|
|
|
|
import type { XTermRef } from '../xterm';
|
|
import XTerm from '../xterm';
|
|
import LogLevelSelect from './log_level_select';
|
|
|
|
const RealTimeLogs = () => {
|
|
const Xterm = useRef<XTermRef>(null);
|
|
const [logLevel, setLogLevel] = useState<Selection>(
|
|
new Set(['info', 'warn', 'error'])
|
|
);
|
|
const [dataArr, setDataArr] = useState<Log[]>([]);
|
|
const [backgroundImage] = useLocalStorage<string>(key.backgroundImage, '');
|
|
const hasBackground = !!backgroundImage;
|
|
|
|
const onDownloadLog = () => {
|
|
const logContent = dataArr
|
|
.filter((log) => {
|
|
if (logLevel === 'all') {
|
|
return true;
|
|
}
|
|
return logLevel.has(log.level);
|
|
})
|
|
.map((log) => colorizeLogLevelWithTag(log.message, log.level))
|
|
.join('\r\n');
|
|
const blob = new Blob([logContent], { type: 'text/plain' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'napcat.log';
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
const writeStream = () => {
|
|
try {
|
|
const _data = dataArr
|
|
.filter((log) => {
|
|
if (logLevel === 'all') {
|
|
return true;
|
|
}
|
|
return logLevel.has(log.level);
|
|
})
|
|
.map((log) => colorizeLogLevelWithTag(log.message, log.level))
|
|
.join('\r\n');
|
|
Xterm.current?.clear();
|
|
Xterm.current?.write(_data);
|
|
} catch (error) {
|
|
console.error(error);
|
|
toast.error('获取实时日志失败');
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
writeStream();
|
|
}, [logLevel, dataArr]);
|
|
|
|
useEffect(() => {
|
|
const subscribeLogs = () => {
|
|
try {
|
|
const source = WebUIManager.getRealTimeLogs((data) => {
|
|
setDataArr((prev) => {
|
|
const newData = [...prev, ...data];
|
|
if (newData.length > 1000) {
|
|
newData.splice(0, newData.length - 1000);
|
|
}
|
|
return newData;
|
|
});
|
|
});
|
|
return () => {
|
|
source.close();
|
|
};
|
|
} catch (_error) {
|
|
toast.error('获取实时日志失败');
|
|
}
|
|
};
|
|
|
|
const close = subscribeLogs();
|
|
return () => {
|
|
console.log('close');
|
|
close?.();
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<title>实时日志 - NapCat WebUI</title>
|
|
<div className={clsx(
|
|
'flex items-center gap-2 p-2 rounded-2xl border backdrop-blur-sm transition-all shadow-sm mb-4',
|
|
hasBackground ? 'bg-white/20 dark:bg-black/10 border-white/40 dark:border-white/10' : 'bg-white/60 dark:bg-black/40 border-white/40 dark:border-white/10'
|
|
)}>
|
|
<LogLevelSelect
|
|
selectedKeys={logLevel}
|
|
onSelectionChange={setLogLevel}
|
|
/>
|
|
<Button
|
|
className='flex-shrink-0'
|
|
onPress={onDownloadLog}
|
|
startContent={<IoDownloadOutline className='text-lg' />}
|
|
color='primary'
|
|
variant='flat'
|
|
>
|
|
下载日志
|
|
</Button>
|
|
</div>
|
|
<div className='flex-1 h-full overflow-hidden'>
|
|
<XTerm ref={Xterm} />
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default RealTimeLogs;
|