日志管理支持删除日志文件和目录

This commit is contained in:
whyour 2022-09-23 20:09:10 +08:00
parent 25b03d4345
commit dfc706e16d
6 changed files with 136 additions and 27 deletions

View File

@ -3,8 +3,9 @@ import { Container } from 'typedi';
import { Logger } from 'winston'; import { Logger } from 'winston';
import * as fs from 'fs'; import * as fs from 'fs';
import config from '../config'; import config from '../config';
import { getFileContentByName, readDirs } from '../config/util'; import { emptyDir, getFileContentByName, readDirs } from '../config/util';
import { join } from 'path'; import { join } from 'path';
import { celebrate, Joi } from 'celebrate';
const route = Router(); const route = Router();
const blacklist = ['.tmp']; const blacklist = ['.tmp'];
@ -45,4 +46,34 @@ export default (app: Router) => {
} }
}, },
); );
route.delete(
'/',
celebrate({
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().allow(''),
type: Joi.string().optional()
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
try {
let { filename, path, type } = req.body as {
filename: string;
path: string;
type: string;
};
const filePath = join(config.logPath, path, filename);
if (type === 'directory') {
emptyDir(filePath);
} else {
fs.unlinkSync(filePath);
}
res.send({ code: 200 });
} catch (e) {
return next(e);
}
},
);
}; };

View File

@ -68,18 +68,18 @@ export default {
icon: <IconFont type="ql-icon-dependence" />, icon: <IconFont type="ql-icon-dependence" />,
component: '@/pages/dependence/index', component: '@/pages/dependence/index',
}, },
{
path: '/log',
name: '日志管理',
icon: <IconFont type="ql-icon-log" />,
component: '@/pages/log/index',
},
{ {
path: '/diff', path: '/diff',
name: '对比工具', name: '对比工具',
icon: <IconFont type="ql-icon-diff" />, icon: <IconFont type="ql-icon-diff" />,
component: '@/pages/diff/index', component: '@/pages/diff/index',
}, },
{
path: '/log',
name: '任务日志',
icon: <IconFont type="ql-icon-log" />,
component: '@/pages/log/index',
},
{ {
path: '/setting', path: '/setting',
name: '系统设置', name: '系统设置',

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, Key, useRef } from 'react'; import { useState, useEffect, useCallback, Key, useRef } from 'react';
import { TreeSelect, Tree, Input, Empty } from 'antd'; import { TreeSelect, Tree, Input, Empty, Button, message, Modal, Tooltip, Typography } from 'antd';
import config from '@/utils/config'; import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout'; import { PageContainer } from '@ant-design/pro-layout';
import Editor from '@monaco-editor/react'; import Editor from '@monaco-editor/react';
@ -9,6 +9,11 @@ import { Controlled as CodeMirror } from 'react-codemirror2';
import SplitPane from 'react-split-pane'; import SplitPane from 'react-split-pane';
import { useOutletContext } from '@umijs/max'; import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts'; import { SharedContext } from '@/layouts';
import { DeleteOutlined } from '@ant-design/icons';
import { depthFirstSearch } from '@/utils';
import { debounce } from 'lodash';
const { Text } = Typography;
function getFilterData(keyword: string, data: any) { function getFilterData(keyword: string, data: any) {
const expandedKeys: string[] = []; const expandedKeys: string[] = [];
@ -49,6 +54,8 @@ const Log = () => {
const [height, setHeight] = useState<number>(); const [height, setHeight] = useState<number>();
const treeDom = useRef<any>(); const treeDom = useRef<any>();
const [expandedKeys, setExpandedKeys] = useState<string[]>([]); const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const [currentNode, setCurrentNode] = useState<any>();
const [searchValue, setSearchValue] = useState('');
const getLogs = () => { const getLogs = () => {
setLoading(true); setLoading(true);
@ -77,9 +84,16 @@ const Log = () => {
if (node.key === select || !value) { if (node.key === select || !value) {
return; return;
} }
setValue('加载中...'); setCurrentNode(node);
setSelect(value); setSelect(value);
setTitle(node.key); setTitle(node.key);
if (node.type === 'directory') {
setValue('请选择日志文件');
return;
}
setValue('加载中...');
getLog(node); getLog(node);
}; };
@ -90,16 +104,75 @@ const Log = () => {
const onSearch = useCallback( const onSearch = useCallback(
(e) => { (e) => {
const keyword = e.target.value; const keyword = e.target.value;
debounceSearch(keyword);
},
[data, setFilterData],
);
const debounceSearch = useCallback(
debounce((keyword) => {
setSearchValue(keyword);
const { tree, expandedKeys } = getFilterData( const { tree, expandedKeys } = getFilterData(
keyword.toLocaleLowerCase(), keyword.toLocaleLowerCase(),
data, data,
); );
setFilterData(tree); setFilterData(tree);
setExpandedKeys(expandedKeys); setExpandedKeys(expandedKeys);
}, }, 300),
[data, setFilterData], [data, setFilterData],
); );
const deleteFile = () => {
Modal.confirm({
title: `确认删除`,
content: (
<>
<Text style={{ wordBreak: 'break-all' }} type="warning">
{select}
</Text>
{currentNode.type === 'directory' ? '夹下所以日志':''}
</>
),
onOk() {
request
.delete(`${config.apiPrefix}logs`, {
data: {
filename: currentNode.title,
path: currentNode.parent || '',
type: currentNode.type
},
})
.then(({ code }) => {
if (code === 200) {
message.success(`删除成功`);
let newData = [...data];
if (currentNode.parent) {
newData = depthFirstSearch(newData, (c) => c.key === currentNode.key);
} else {
const index = newData.findIndex(
(x) => x.key === currentNode.key,
);
if (index !== -1) {
newData.splice(index, 1);
}
}
setData(newData);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
useEffect(() => {
const word = searchValue || '';
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
setFilterData(tree);
}, [data]);
useEffect(() => { useEffect(() => {
getLogs(); getLogs();
if (treeDom && treeDom.current) { if (treeDom && treeDom.current) {
@ -113,7 +186,7 @@ const Log = () => {
title={title} title={title}
loading={loading} loading={loading}
extra={ extra={
isPhone && [ isPhone ? [
<TreeSelect <TreeSelect
className="log-select" className="log-select"
value={select} value={select}
@ -124,6 +197,15 @@ const Log = () => {
showSearch showSearch
onSelect={onSelect} onSelect={onSelect}
/>, />,
] : [
<Tooltip title="删除">
<Button
type="primary"
disabled={!select}
onClick={deleteFile}
icon={<DeleteOutlined />}
/>
</Tooltip>,
] ]
} }
header={{ header={{
@ -144,6 +226,7 @@ const Log = () => {
></Input.Search> ></Input.Search>
<div className={styles['left-tree-scroller']} ref={treeDom}> <div className={styles['left-tree-scroller']} ref={treeDom}>
<Tree <Tree
expandAction="click"
className={styles['left-tree']} className={styles['left-tree']}
treeData={filterData} treeData={filterData}
showIcon={true} showIcon={true}
@ -199,7 +282,7 @@ const Log = () => {
onBeforeChange={(editor, data, value) => { onBeforeChange={(editor, data, value) => {
setValue(value); setValue(value);
}} }}
onChange={(editor, data, value) => {}} onChange={(editor, data, value) => { }}
/> />
)} )}
</div> </div>

View File

@ -283,17 +283,7 @@ const Script = () => {
message.success(`删除成功`); message.success(`删除成功`);
let newData = [...data]; let newData = [...data];
if (currentNode.parent) { if (currentNode.parent) {
const parentNodeIndex = newData.findIndex( newData = depthFirstSearch(newData, (c) => c.key === currentNode.key);
(x) => x.key === currentNode.parent,
);
const parentNode = newData[parentNodeIndex];
const index = parentNode.children.findIndex(
(y) => y.key === currentNode.key,
);
if (index !== -1 && parentNodeIndex !== -1) {
parentNode.children.splice(index, 1);
newData.splice(parentNodeIndex, 1, { ...parentNode });
}
} else { } else {
const index = newData.findIndex( const index = newData.findIndex(
(x) => x.key === currentNode.key, (x) => x.key === currentNode.key,

View File

@ -56,7 +56,7 @@ export default {
value: 'scripts', value: 'scripts',
}, },
{ {
name: '任务日志', name: '日志管理',
value: 'logs', value: 'logs',
}, },
{ {
@ -74,7 +74,7 @@ export default {
subscriptions: '订阅管理', subscriptions: '订阅管理',
configs: '配置文件', configs: '配置文件',
scripts: '脚本管理', scripts: '脚本管理',
logs: '任务日志', logs: '日志管理',
dependencies: '依赖管理', dependencies: '依赖管理',
system: '系统信息', system: '系统信息',
}, },
@ -263,7 +263,7 @@ export default {
'/config': '配置文件', '/config': '配置文件',
'/script': '脚本管理', '/script': '脚本管理',
'/diff': '对比工具', '/diff': '对比工具',
'/log': '任务日志', '/log': '日志管理',
'/setting': '系统设置', '/setting': '系统设置',
'/error': '错误日志', '/error': '错误日志',
}, },

View File

@ -244,7 +244,7 @@ export function exportJson(name: string, data: string) {
export function depthFirstSearch< export function depthFirstSearch<
T extends Record<string, any> & { children?: T[] }, T extends Record<string, any> & { children?: T[] },
>(children: T[], condition: (column: T) => boolean, item: T) { >(children: T[], condition: (column: T) => boolean, item?: T) {
const c = [...children]; const c = [...children];
const keys = []; const keys = [];
@ -252,6 +252,11 @@ export function depthFirstSearch<
if (!cls) return; if (!cls) return;
for (let i = 0; i < cls?.length; i++) { for (let i = 0; i < cls?.length; i++) {
if (condition(cls[i])) { if (condition(cls[i])) {
if (!item) {
cls.splice(i, 1);
return;
}
if (cls[i].children) { if (cls[i].children) {
cls[i].children!.unshift(item); cls[i].children!.unshift(item);
} else { } else {