mirror of
https://github.com/whyour/qinglong.git
synced 2025-07-07 03:46:07 +08:00
修复脚本和日志搜索逻辑
This commit is contained in:
parent
6428ac3624
commit
654b51e476
|
@ -312,6 +312,7 @@ export function readDirs(
|
||||||
return {
|
return {
|
||||||
title: file,
|
title: file,
|
||||||
type: 'file',
|
type: 'file',
|
||||||
|
isLeaf: true,
|
||||||
key,
|
key,
|
||||||
parent: relativePath,
|
parent: relativePath,
|
||||||
};
|
};
|
||||||
|
@ -346,7 +347,7 @@ export function readDir(
|
||||||
|
|
||||||
export function emptyDir(path: string) {
|
export function emptyDir(path: string) {
|
||||||
const files = fs.readdirSync(path);
|
const files = fs.readdirSync(path);
|
||||||
files.forEach(file => {
|
files.forEach((file) => {
|
||||||
const filePath = `${path}/${file}`;
|
const filePath = `${path}/${file}`;
|
||||||
const stats = fs.statSync(filePath);
|
const stats = fs.statSync(filePath);
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory()) {
|
||||||
|
@ -358,7 +359,6 @@ export function emptyDir(path: string) {
|
||||||
fs.rmdirSync(path);
|
fs.rmdirSync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function promiseExec(command: string): Promise<string> {
|
export function promiseExec(command: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(
|
exec(
|
||||||
|
@ -379,22 +379,29 @@ export function parseHeaders(headers: string) {
|
||||||
let val;
|
let val;
|
||||||
let i;
|
let i;
|
||||||
|
|
||||||
headers && headers.split('\n').forEach(function parser(line) {
|
headers &&
|
||||||
i = line.indexOf(':');
|
headers.split('\n').forEach(function parser(line) {
|
||||||
key = line.substring(0, i).trim().toLowerCase();
|
i = line.indexOf(':');
|
||||||
val = line.substring(i + 1).trim();
|
key = line.substring(0, i).trim().toLowerCase();
|
||||||
|
val = line.substring(i + 1).trim();
|
||||||
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
|
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
|
||||||
});
|
});
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
};
|
}
|
||||||
|
|
||||||
export function parseBody(body: string, contentType: 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded') {
|
export function parseBody(
|
||||||
|
body: string,
|
||||||
|
contentType:
|
||||||
|
| 'application/json'
|
||||||
|
| 'multipart/form-data'
|
||||||
|
| 'application/x-www-form-urlencoded',
|
||||||
|
) {
|
||||||
if (!body) return '';
|
if (!body) return '';
|
||||||
|
|
||||||
const parsed: any = {};
|
const parsed: any = {};
|
||||||
|
@ -402,22 +409,23 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
|
||||||
let val;
|
let val;
|
||||||
let i;
|
let i;
|
||||||
|
|
||||||
body && body.split('\n').forEach(function parser(line) {
|
body &&
|
||||||
i = line.indexOf(':');
|
body.split('\n').forEach(function parser(line) {
|
||||||
key = line.substring(0, i).trim().toLowerCase();
|
i = line.indexOf(':');
|
||||||
val = line.substring(i + 1).trim();
|
key = line.substring(0, i).trim().toLowerCase();
|
||||||
|
val = line.substring(i + 1).trim();
|
||||||
|
|
||||||
if (!key || parsed[key]) {
|
if (!key || parsed[key]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed[key] = val;
|
parsed[key] = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case 'multipart/form-data':
|
case 'multipart/form-data':
|
||||||
return Object.keys(parsed).reduce((p, c) => {
|
return Object.keys(parsed).reduce((p, c) => {
|
||||||
p.append(c, parsed[c])
|
p.append(c, parsed[c]);
|
||||||
return p;
|
return p;
|
||||||
}, new FormData());
|
}, new FormData());
|
||||||
case 'application/x-www-form-urlencoded':
|
case 'application/x-www-form-urlencoded':
|
||||||
|
@ -427,4 +435,4 @@ export function parseBody(body: string, contentType: 'application/json' | 'multi
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
};
|
}
|
||||||
|
|
|
@ -125,12 +125,12 @@
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"rc-tween-one": "^3.0.6",
|
"rc-tween-one": "^3.0.6",
|
||||||
"react": "18.x",
|
"react": "18.2.0",
|
||||||
"react-codemirror2": "^7.2.1",
|
"react-codemirror2": "^7.2.1",
|
||||||
"react-diff-viewer": "^3.1.1",
|
"react-diff-viewer": "^3.1.1",
|
||||||
"react-dnd": "^14.0.2",
|
"react-dnd": "^14.0.2",
|
||||||
"react-dnd-html5-backend": "^14.0.0",
|
"react-dnd-html5-backend": "^14.0.0",
|
||||||
"react-dom": "18.x",
|
"react-dom": "18.2.0",
|
||||||
"react-split-pane": "^0.1.92",
|
"react-split-pane": "^0.1.92",
|
||||||
"sockjs-client": "^1.6.0",
|
"sockjs-client": "^1.6.0",
|
||||||
"ts-node": "^10.6.0",
|
"ts-node": "^10.6.0",
|
||||||
|
|
48
src/hooks/useFilterTreeData.ts
Normal file
48
src/hooks/useFilterTreeData.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export default (
|
||||||
|
treeData: any[],
|
||||||
|
searchValue: string,
|
||||||
|
{
|
||||||
|
treeNodeFilterProp,
|
||||||
|
}: {
|
||||||
|
treeNodeFilterProp: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
return useMemo(() => {
|
||||||
|
const keys: string[] = [];
|
||||||
|
|
||||||
|
if (!searchValue) {
|
||||||
|
return { treeData, keys };
|
||||||
|
}
|
||||||
|
|
||||||
|
const upperStr = searchValue.toUpperCase();
|
||||||
|
function filterOptionFunc(_: string, dataNode: any[]) {
|
||||||
|
const value = dataNode[treeNodeFilterProp as any];
|
||||||
|
|
||||||
|
return String(value).toUpperCase().includes(upperStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dig(list: any[], keepAll: boolean = false): any[] {
|
||||||
|
return list
|
||||||
|
.map((dataNode) => {
|
||||||
|
const children = dataNode.children;
|
||||||
|
|
||||||
|
const match = keepAll || filterOptionFunc!(searchValue, dataNode);
|
||||||
|
const childList = dig(children || [], match);
|
||||||
|
|
||||||
|
if (match || childList.length) {
|
||||||
|
childList.length && keys.push(dataNode.key);
|
||||||
|
return {
|
||||||
|
...dataNode,
|
||||||
|
children: childList,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((node) => node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { treeData: dig(treeData), keys };
|
||||||
|
}, [treeData, searchValue, treeNodeFilterProp]);
|
||||||
|
};
|
|
@ -21,44 +21,16 @@ import { useOutletContext } from '@umijs/max';
|
||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import { depthFirstSearch } from '@/utils';
|
import { depthFirstSearch } from '@/utils';
|
||||||
import { debounce } from 'lodash';
|
import { debounce, uniq } from 'lodash';
|
||||||
|
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||||
|
|
||||||
const { Text } = Typography;
|
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 Log = () => {
|
||||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const [value, setValue] = useState('请选择日志文件');
|
const [value, setValue] = useState('请选择日志文件');
|
||||||
const [select, setSelect] = useState<string>('');
|
const [select, setSelect] = useState<string>('');
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [filterData, setFilterData] = useState<any[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [height, setHeight] = useState<number>();
|
const [height, setHeight] = useState<number>();
|
||||||
const treeDom = useRef<any>();
|
const treeDom = useRef<any>();
|
||||||
|
@ -73,7 +45,6 @@ const Log = () => {
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setData(data);
|
setData(data);
|
||||||
setFilterData(data);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
|
@ -115,22 +86,26 @@ const Log = () => {
|
||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
debounceSearch(keyword);
|
debounceSearch(keyword);
|
||||||
},
|
},
|
||||||
[data, setFilterData],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debounceSearch = useCallback(
|
const debounceSearch = useCallback(
|
||||||
debounce((keyword) => {
|
debounce((keyword) => {
|
||||||
setSearchValue(keyword);
|
setSearchValue(keyword);
|
||||||
const { tree, expandedKeys } = getFilterData(
|
|
||||||
keyword.toLocaleLowerCase(),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
setFilterData(tree);
|
|
||||||
setExpandedKeys(expandedKeys);
|
|
||||||
}, 300),
|
}, 300),
|
||||||
[data, setFilterData],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
|
||||||
|
data,
|
||||||
|
searchValue,
|
||||||
|
{ treeNodeFilterProp: 'title' },
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
|
||||||
|
}, [searchExpandedKeys]);
|
||||||
|
|
||||||
const deleteFile = () => {
|
const deleteFile = () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: `确认删除`,
|
title: `确认删除`,
|
||||||
|
@ -187,11 +162,9 @@ const Log = () => {
|
||||||
setValue('请选择脚本文件');
|
setValue('请选择脚本文件');
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const onExpand = (expKeys: any) => {
|
||||||
const word = searchValue || '';
|
setExpandedKeys(expKeys);
|
||||||
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
};
|
||||||
setFilterData(tree);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLogs();
|
getLogs();
|
||||||
|
@ -217,8 +190,10 @@ const Log = () => {
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
placeholder="请选择日志"
|
placeholder="请选择日志"
|
||||||
fieldNames={{ value: 'key', label: 'title' }}
|
fieldNames={{ value: 'key' }}
|
||||||
|
treeNodeFilterProp="title"
|
||||||
showSearch
|
showSearch
|
||||||
|
allowClear
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
|
@ -260,6 +235,8 @@ const Log = () => {
|
||||||
selectedKeys={[select]}
|
selectedKeys={[select]}
|
||||||
showLine={{ showLeafIcon: true }}
|
showLine={{ showLeafIcon: true }}
|
||||||
onSelect={onTreeSelect}
|
onSelect={onTreeSelect}
|
||||||
|
expandedKeys={expandedKeys}
|
||||||
|
onExpand={onExpand}
|
||||||
></Tree>
|
></Tree>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -37,37 +37,11 @@ import { history, useOutletContext, useLocation } from '@umijs/max';
|
||||||
import { parse } from 'query-string';
|
import { parse } from 'query-string';
|
||||||
import { depthFirstSearch } from '@/utils';
|
import { depthFirstSearch } from '@/utils';
|
||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
|
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||||
|
import { uniq } from 'lodash';
|
||||||
|
|
||||||
const { Text } = Typography;
|
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 LangMap: any = {
|
const LangMap: any = {
|
||||||
'.py': 'python',
|
'.py': 'python',
|
||||||
'.js': 'javascript',
|
'.js': 'javascript',
|
||||||
|
@ -81,7 +55,6 @@ const Script = () => {
|
||||||
const [value, setValue] = useState('请选择脚本文件');
|
const [value, setValue] = useState('请选择脚本文件');
|
||||||
const [select, setSelect] = useState<string>('');
|
const [select, setSelect] = useState<string>('');
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [filterData, setFilterData] = useState<any[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [mode, setMode] = useState('');
|
const [mode, setMode] = useState('');
|
||||||
const [height, setHeight] = useState<number>();
|
const [height, setHeight] = useState<number>();
|
||||||
|
@ -101,7 +74,6 @@ const Script = () => {
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setData(data);
|
setData(data);
|
||||||
setFilterData(data);
|
|
||||||
initGetScript();
|
initGetScript();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -153,10 +125,6 @@ const Script = () => {
|
||||||
getDetail(node);
|
getDetail(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onExpand = (expKeys: any) => {
|
|
||||||
setExpandedKeys(expKeys);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTreeSelect = useCallback(
|
const onTreeSelect = useCallback(
|
||||||
(keys: Key[], e: any) => {
|
(keys: Key[], e: any) => {
|
||||||
const content = editorRef.current
|
const content = editorRef.current
|
||||||
|
@ -187,22 +155,30 @@ const Script = () => {
|
||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
debounceSearch(keyword);
|
debounceSearch(keyword);
|
||||||
},
|
},
|
||||||
[data, setFilterData],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debounceSearch = useCallback(
|
const debounceSearch = useCallback(
|
||||||
debounce((keyword) => {
|
debounce((keyword) => {
|
||||||
setSearchValue(keyword);
|
setSearchValue(keyword);
|
||||||
const { tree, expandedKeys } = getFilterData(
|
|
||||||
keyword.toLocaleLowerCase(),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
setExpandedKeys(expandedKeys);
|
|
||||||
setFilterData(tree);
|
|
||||||
}, 300),
|
}, 300),
|
||||||
[data, setFilterData],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
|
||||||
|
data,
|
||||||
|
searchValue,
|
||||||
|
{ treeNodeFilterProp: 'title' },
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
|
||||||
|
}, [searchExpandedKeys]);
|
||||||
|
|
||||||
|
const onExpand = (expKeys: any) => {
|
||||||
|
setExpandedKeys(expKeys);
|
||||||
|
};
|
||||||
|
|
||||||
const editFile = () => {
|
const editFile = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
|
@ -367,12 +343,6 @@ const Script = () => {
|
||||||
setValue('请选择脚本文件');
|
setValue('请选择脚本文件');
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const word = searchValue || '';
|
|
||||||
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
|
||||||
setFilterData(tree);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getScripts();
|
getScripts();
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -462,8 +432,10 @@ const Script = () => {
|
||||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
treeData={data}
|
treeData={data}
|
||||||
placeholder="请选择脚本"
|
placeholder="请选择脚本"
|
||||||
fieldNames={{ value: 'key', label: 'title' }}
|
fieldNames={{ value: 'key' }}
|
||||||
|
treeNodeFilterProp="title"
|
||||||
showSearch
|
showSearch
|
||||||
|
allowClear
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user