系统设置增加数据恢复功能

This commit is contained in:
whyour 2023-07-16 22:02:30 +08:00
parent 88b87de391
commit 93e94ea94c
5 changed files with 169 additions and 16 deletions

View File

@ -14,8 +14,18 @@ import {
promiseExec,
} from '../config/util';
import dayjs from 'dayjs';
import multer from 'multer';
const route = Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, config.tmpPath);
},
filename: function (req, file, cb) {
cb(null, 'data.tgz');
},
});
const upload = multer({ storage: storage });
export default (app: Router) => {
app.use('/system', route);
@ -118,11 +128,16 @@ export default (app: Router) => {
route.put(
'/reload',
celebrate({
body: Joi.object({
type: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const systemService = Container.get(SystemService);
const result = await systemService.reloadSystem();
const result = await systemService.reloadSystem(req.body.type);
res.send(result);
} catch (e) {
return next(e);
@ -221,4 +236,18 @@ export default (app: Router) => {
}
},
);
route.put(
'/data/import',
upload.single('data'),
async (req: Request, res: Response, next: NextFunction) => {
try {
const systemService = Container.get(SystemService);
const result = await systemService.importData();
res.send(result);
} catch (e) {
return next(e);
}
},
);
};

View File

@ -20,11 +20,13 @@ import {
killTask,
parseContentVersion,
parseVersion,
promiseExec,
} from '../config/util';
import { TASK_COMMAND } from '../config/const';
import taskLimit from '../shared/pLimit';
import tar from 'tar';
import fs from 'fs';
import path from 'path';
@Service()
export default class SystemService {
@ -182,8 +184,8 @@ export default class SystemService {
return { code: 200 };
}
public async reloadSystem() {
const cp = spawn('ql -l reload', { shell: '/bin/bash' });
public async reloadSystem(target: 'system' | 'data') {
const cp = spawn(`ql -l reload ${target || ''}`, { shell: '/bin/bash' });
cp.stdout.on('data', (data) => {
this.sockService.sendMessage({
@ -266,4 +268,14 @@ export default class SystemService {
return res.send({ code: 400, message: error.message });
}
}
public async importData() {
try {
await promiseExec(`rm -rf ${path.join(config.tmpPath, 'data')}`);
await tar.x({ file: config.dataTgzFile, cwd: config.tmpPath });
return { code: 200 };
} catch (error: any) {
return { code: 400, message: error.message };
}
}
}

View File

@ -230,15 +230,23 @@ usage() {
}
reload_qinglong() {
local reload_target="${1}"
local primary_branch="master"
if [[ "${QL_BRANCH}" == "develop" ]]; then
primary_branch="develop"
fi
if [[ "$reload_target" == 'system' ]]; then
cp -rf ${dir_tmp}/qinglong-${primary_branch}/* ${dir_root}/
rm -rf $dir_static/*
cp -rf ${dir_tmp}/qinglong-static-${primary_branch}/* ${dir_static}/
cp -f $file_config_sample $dir_config/config.sample.sh
fi
if [[ "$reload_target" == 'data' ]]; then
rm -rf ${dir_data}
cp -rf ${dir_tmp}/data ${dir_root}/
fi
reload_pm2
}
@ -246,7 +254,6 @@ reload_qinglong() {
## 更新qinglong
update_qinglong() {
rm -rf ${dir_tmp}/*
local needRestart=${1:-"true"}
local mirror="gitee"
local downloadQLUrl="https://gitee.com/whyour/qinglong/repository/archive"
local downloadStaticUrl="https://gitee.com/whyour/qinglong-static/repository/archive"
@ -490,10 +497,11 @@ main() {
case $p1 in
update)
fix_config
eval update_qinglong "$2" $cmd
local needRestart=${p2:-"true"}
eval update_qinglong $cmd
;;
reload)
eval reload_qinglong $cmd
eval reload_qinglong "$p2" $cmd
;;
extra)
eval run_extra_shell $cmd

View File

@ -129,9 +129,9 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
okText: '重启',
onOk() {
request
.put(`${config.apiPrefix}system/reload`)
.put(`${config.apiPrefix}system/reload`, { data: { type: 'system' } })
.then((_data: any) => {
message.warning({
message.success({
content: (
<span>
@ -147,7 +147,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
});
setTimeout(() => {
window.location.reload();
}, 14);
}, 14000);
})
.catch((error: any) => {
console.log(error);

View File

@ -1,5 +1,15 @@
import React, { useState, useEffect } from 'react';
import { Button, InputNumber, Form, Radio, message, Input } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
InputNumber,
Form,
Radio,
message,
Input,
Upload,
Modal,
Progress,
} from 'antd';
import * as DarkReader from '@umijs/ssr-darkreader';
import config from '@/utils/config';
import { request } from '@/utils/http';
@ -7,6 +17,8 @@ import CheckUpdate from './checkUpdate';
import { SharedContext } from '@/layouts';
import { saveAs } from 'file-saver';
import './index.less';
import { UploadOutlined } from '@ant-design/icons';
import Countdown from 'antd/lib/statistic/Countdown';
const optionsWithDisabled = [
{ label: '亮色', value: 'light' },
@ -25,6 +37,8 @@ const Other = ({
cronConcurrency?: number | null;
}>();
const [form] = Form.useForm();
const modalRef = useRef<any>();
const [exportLoading, setExportLoading] = useState(false);
const {
enable: enableDarkMode,
@ -78,6 +92,7 @@ const Other = ({
};
const exportData = () => {
setExportLoading(true);
request
.put(`${config.apiPrefix}system/data/export`, { responseType: 'blob' })
.then((res) => {
@ -85,6 +100,72 @@ const Other = ({
})
.catch((error: any) => {
console.log(error);
})
.finally(() => setExportLoading(false));
};
const showUploadModal = (progress: number) => {
if (modalRef.current) {
modalRef.current.update({
content: (
<Progress
style={{ display: 'flex', justifyContent: 'center' }}
type="circle"
percent={progress}
/>
),
});
} else {
modalRef.current = Modal.info({
width: 600,
maskClosable: false,
title: '上传中...',
centered: true,
content: (
<Progress
style={{ display: 'flex', justifyContent: 'center' }}
type="circle"
percent={progress}
/>
),
});
}
};
const showReloadModal = () => {
Modal.confirm({
width: 600,
maskClosable: false,
title: '确认重启',
centered: true,
content: '备份数据上传成功,确认覆盖数据',
okText: '重启',
onOk() {
request
.put(`${config.apiPrefix}system/reload`, { data: { type: 'data' } })
.then((_data: any) => {
message.success({
content: (
<span>
<Countdown
className="inline-countdown"
format="ss"
value={Date.now() + 1000 * 15}
/>
</span>
),
duration: 15,
});
setTimeout(() => {
window.location.reload();
}, 14000);
})
.catch((error: any) => {
console.log(error);
});
},
});
};
@ -140,9 +221,32 @@ const Other = ({
</Input.Group>
</Form.Item>
<Form.Item label="数据备份还原" name="frequency">
<Button type="primary" onClick={exportData}>
<Button type="primary" onClick={exportData} loading={exportLoading}>
</Button>
<Upload
method="put"
showUploadList={false}
maxCount={1}
action="/api/system/data/import"
onChange={(e) => {
if (e.event?.percent) {
const percent = parseFloat(e.event?.percent.toFixed(1));
showUploadModal(percent);
if (e.event?.percent === 100) {
showReloadModal();
}
}
}}
name="data"
headers={{
Authorization: `Bearer ${localStorage.getItem(config.authKey)}`,
}}
>
<Button icon={<UploadOutlined />} style={{ marginLeft: 8 }}>
</Button>
</Upload>
</Form.Item>
<Form.Item label="检查更新" name="update">
<CheckUpdate systemInfo={systemInfo} socketMessage={socketMessage} />