mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-23 06:46:09 +08:00
脚本管理支持编辑删除文件
This commit is contained in:
parent
ff481ef7f3
commit
5e13264393
|
@ -70,7 +70,10 @@ export default (app: Router) => {
|
||||||
path += '/';
|
path += '/';
|
||||||
}
|
}
|
||||||
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
||||||
return res.send({ code: 400, data: '文件路径错误,可保存目录/ql/scripts、/ql/config、/ql/jbot、/ql/bak' });
|
return res.send({
|
||||||
|
code: 400,
|
||||||
|
data: '文件路径错误,可保存目录/ql/scripts、/ql/config、/ql/jbot、/ql/bak',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const filePath = `${path}${filename.replace(/\//g, '')}`;
|
const filePath = `${path}${filename.replace(/\//g, '')}`;
|
||||||
const bakPath = '/ql/bak';
|
const bakPath = '/ql/bak';
|
||||||
|
@ -88,4 +91,52 @@ export default (app: Router) => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/scripts',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
filename: Joi.string().required(),
|
||||||
|
content: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
let { filename, content } = req.body as {
|
||||||
|
filename: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
const filePath = `${config.scriptPath}${filename}`;
|
||||||
|
fs.writeFileSync(filePath, content);
|
||||||
|
return res.send({ code: 200 });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
'/scripts',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
filename: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
let { filename } = req.body as {
|
||||||
|
filename: string;
|
||||||
|
};
|
||||||
|
const filePath = `${config.scriptPath}${filename}`;
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
res.send({ code: 200 });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
||||||
import { TreeSelect, Tree, Input, Button } from 'antd';
|
import {
|
||||||
|
TreeSelect,
|
||||||
|
Tree,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
message,
|
||||||
|
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';
|
||||||
|
@ -10,6 +18,8 @@ import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||||
import { useCtx, useTheme } from '@/utils/hooks';
|
import { useCtx, useTheme } from '@/utils/hooks';
|
||||||
import SplitPane from 'react-split-pane';
|
import SplitPane from 'react-split-pane';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
function getFilterData(keyword: string, data: any) {
|
function getFilterData(keyword: string, data: any) {
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
const tree: any = [];
|
const tree: any = [];
|
||||||
|
@ -33,7 +43,7 @@ const LangMap: any = {
|
||||||
const Script = () => {
|
const Script = () => {
|
||||||
const [title, setTitle] = useState('请选择脚本文件');
|
const [title, setTitle] = useState('请选择脚本文件');
|
||||||
const [value, setValue] = useState('请选择脚本文件');
|
const [value, setValue] = useState('请选择脚本文件');
|
||||||
const [select, setSelect] = useState();
|
const [select, setSelect] = useState<string>();
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [filterData, setFilterData] = useState<any[]>([]);
|
const [filterData, setFilterData] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
@ -43,6 +53,9 @@ const Script = () => {
|
||||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||||
const { headerStyle, isPhone } = useCtx();
|
const { headerStyle, isPhone } = useCtx();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const editorRef = useRef<any>(null);
|
||||||
|
|
||||||
const getScripts = () => {
|
const getScripts = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -76,12 +89,102 @@ const Script = () => {
|
||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
const keyword = e.target.value;
|
const keyword = e.target.value;
|
||||||
|
setSearchValue(keyword);
|
||||||
const { tree } = getFilterData(keyword.toLocaleLowerCase(), data);
|
const { tree } = getFilterData(keyword.toLocaleLowerCase(), data);
|
||||||
setFilterData(tree);
|
setFilterData(tree);
|
||||||
},
|
},
|
||||||
[data, setFilterData],
|
[data, setFilterData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const editFile = () => {
|
||||||
|
setIsEditing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveFile = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: `确认保存`,
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认保存文件
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{select}
|
||||||
|
</Text>{' '}
|
||||||
|
,保存后不可恢复
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
const content = editorRef.current
|
||||||
|
? editorRef.current.getValue().replace(/\r\n/g, '\n')
|
||||||
|
: value;
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}scripts`, {
|
||||||
|
data: {
|
||||||
|
filename: select,
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((_data: any) => {
|
||||||
|
if (_data.code === 200) {
|
||||||
|
message.success(`保存成功`);
|
||||||
|
} else {
|
||||||
|
message.error(_data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFile = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: `确认删除`,
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认删除文件
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{select}
|
||||||
|
</Text>{' '}
|
||||||
|
,删除后不可恢复
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.delete(`${config.apiPrefix}scripts`, {
|
||||||
|
data: {
|
||||||
|
filename: select,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((_data: any) => {
|
||||||
|
if (_data.code === 200) {
|
||||||
|
message.success(`删除成功`);
|
||||||
|
let newData = [...data];
|
||||||
|
const index = newData.findIndex((x) => x.value === select);
|
||||||
|
newData.splice(index, 1);
|
||||||
|
setData(newData);
|
||||||
|
} else {
|
||||||
|
message.error(_data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const word = searchValue || '';
|
||||||
|
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
||||||
|
console.log(word);
|
||||||
|
console.log(tree);
|
||||||
|
setFilterData(tree);
|
||||||
|
setSelect('');
|
||||||
|
setTitle('请选择脚本文件');
|
||||||
|
setValue('请选择脚本文件');
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getScripts();
|
getScripts();
|
||||||
setHeight(treeDom.current.clientHeight);
|
setHeight(treeDom.current.clientHeight);
|
||||||
|
@ -105,8 +208,23 @@ const Script = () => {
|
||||||
key="value"
|
key="value"
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>,
|
/>,
|
||||||
|
<Button type="primary" onClick={deleteFile}>
|
||||||
|
删除
|
||||||
|
</Button>,
|
||||||
|
]
|
||||||
|
: isEditing
|
||||||
|
? [
|
||||||
|
<Button type="primary" onClick={saveFile}>
|
||||||
|
保存
|
||||||
|
</Button>,
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
|
<Button type="primary" onClick={editFile}>
|
||||||
|
编辑
|
||||||
|
</Button>,
|
||||||
|
<Button type="primary" onClick={deleteFile}>
|
||||||
|
删除
|
||||||
|
</Button>,
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -145,12 +263,15 @@ const Script = () => {
|
||||||
value={value}
|
value={value}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
options={{
|
options={{
|
||||||
readOnly: true,
|
readOnly: !isEditing,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
folding: false,
|
folding: false,
|
||||||
glyphMargin: false,
|
glyphMargin: false,
|
||||||
}}
|
}}
|
||||||
|
onMount={(editor) => {
|
||||||
|
editorRef.current = editor;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user