qinglong/src/pages/log/index.tsx
2022-09-25 13:38:10 +08:00

319 lines
8.6 KiB
TypeScript
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 { useState, useEffect, useCallback, Key, useRef } from 'react';
import {
TreeSelect,
Tree,
Input,
Empty,
Button,
message,
Modal,
Tooltip,
Typography,
} from 'antd';
import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
import Editor from '@monaco-editor/react';
import { request } from '@/utils/http';
import styles from './index.module.less';
import { Controlled as CodeMirror } from 'react-codemirror2';
import SplitPane from 'react-split-pane';
import { useOutletContext } from '@umijs/max';
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) {
const expandedKeys: string[] = [];
if (keyword) {
const tree: any = [];
data.forEach((item: any) => {
if (item.title.toLocaleLowerCase().includes(keyword)) {
tree.push(item);
} else {
const children: any[] = [];
(item.children || []).forEach((subItem: any) => {
if (subItem.title.toLocaleLowerCase().includes(keyword)) {
children.push(subItem);
}
});
if (children.length > 0) {
tree.push({
...item,
children,
});
expandedKeys.push(item.key);
}
}
});
return { tree, expandedKeys };
}
return { tree: data, expandedKeys };
}
const Log = () => {
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
const [value, setValue] = useState('请选择日志文件');
const [select, setSelect] = useState<string>('');
const [data, setData] = useState<any[]>([]);
const [filterData, setFilterData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [height, setHeight] = useState<number>();
const treeDom = useRef<any>();
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const [currentNode, setCurrentNode] = useState<any>();
const [searchValue, setSearchValue] = useState('');
const getLogs = () => {
setLoading(true);
request
.get(`${config.apiPrefix}logs`)
.then(({ code, data }) => {
if (code === 200) {
setData(data);
setFilterData(data);
}
})
.finally(() => setLoading(false));
};
const getLog = (node: any) => {
request
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
.then(({ code, data }) => {
if (code === 200) {
setValue(data);
}
});
};
const onSelect = (value: any, node: any) => {
setCurrentNode(node);
setSelect(value);
if (node.key === select || !value) {
return;
}
if (node.type === 'directory') {
setValue('请选择日志文件');
return;
}
setValue('加载中...');
getLog(node);
};
const onTreeSelect = useCallback((keys: Key[], e: any) => {
onSelect(keys[0], e.node);
}, []);
const onSearch = useCallback(
(e) => {
const keyword = e.target.value;
debounceSearch(keyword);
},
[data, setFilterData],
);
const debounceSearch = useCallback(
debounce((keyword) => {
setSearchValue(keyword);
const { tree, expandedKeys } = getFilterData(
keyword.toLocaleLowerCase(),
data,
);
setFilterData(tree);
setExpandedKeys(expandedKeys);
}, 300),
[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);
initState();
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const initState = () => {
setSelect('');
setCurrentNode(null);
setValue('请选择脚本文件');
};
useEffect(() => {
const word = searchValue || '';
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
setFilterData(tree);
}, [data]);
useEffect(() => {
getLogs();
}, []);
useEffect(() => {
if (treeDom.current) {
setHeight(treeDom.current.clientHeight);
}
}, [treeDom.current, data]);
return (
<PageContainer
className="ql-container-wrapper log-wrapper"
title={select}
loading={loading}
extra={
isPhone
? [
<TreeSelect
className="log-select"
value={select}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={data}
placeholder="请选择日志"
fieldNames={{ value: 'key', label: 'title' }}
showSearch
onSelect={onSelect}
/>,
]
: [
<Tooltip title="删除">
<Button
type="primary"
disabled={!select}
onClick={deleteFile}
icon={<DeleteOutlined />}
/>
</Tooltip>,
]
}
header={{
style: headerStyle,
}}
>
<div className={`${styles['log-container']} log-container`}>
{!isPhone && (
<SplitPane split="vertical" size={200} maxSize={-100}>
<div className={styles['left-tree-container']}>
{data.length > 0 ? (
<>
<Input.Search
className={styles['left-tree-search']}
onChange={onSearch}
placeholder="请输入日志名"
allowClear
></Input.Search>
<div className={styles['left-tree-scroller']} ref={treeDom}>
<Tree
expandAction="click"
className={styles['left-tree']}
treeData={filterData}
showIcon={true}
height={height}
selectedKeys={[select]}
showLine={{ showLeafIcon: true }}
onSelect={onTreeSelect}
></Tree>
</div>
</>
) : (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
}}
>
<Empty
description="暂无日志"
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
</div>
)}
</div>
<Editor
language="shell"
theme={theme}
value={value}
options={{
readOnly: true,
fontSize: 12,
lineNumbersMinChars: 3,
fontFamily: 'Source Code Pro',
folding: false,
glyphMargin: false,
wordWrap: 'on',
}}
/>
</SplitPane>
)}
{isPhone && (
<CodeMirror
value={value}
options={{
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: true,
}}
onBeforeChange={(editor, data, value) => {
setValue(value);
}}
onChange={(editor, data, value) => {}}
/>
)}
</div>
</PageContainer>
);
};
export default Log;