mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-23 14:56:07 +08:00
系统设置增加数据恢复功能
This commit is contained in:
parent
88b87de391
commit
93e94ea94c
|
@ -14,8 +14,18 @@ import {
|
||||||
promiseExec,
|
promiseExec,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import multer from 'multer';
|
||||||
|
|
||||||
const route = Router();
|
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) => {
|
export default (app: Router) => {
|
||||||
app.use('/system', route);
|
app.use('/system', route);
|
||||||
|
@ -118,11 +128,16 @@ export default (app: Router) => {
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/reload',
|
'/reload',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
type: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const result = await systemService.reloadSystem();
|
const result = await systemService.reloadSystem(req.body.type);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,11 +20,13 @@ import {
|
||||||
killTask,
|
killTask,
|
||||||
parseContentVersion,
|
parseContentVersion,
|
||||||
parseVersion,
|
parseVersion,
|
||||||
|
promiseExec,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import { TASK_COMMAND } from '../config/const';
|
import { TASK_COMMAND } from '../config/const';
|
||||||
import taskLimit from '../shared/pLimit';
|
import taskLimit from '../shared/pLimit';
|
||||||
import tar from 'tar';
|
import tar from 'tar';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SystemService {
|
export default class SystemService {
|
||||||
|
@ -182,8 +184,8 @@ export default class SystemService {
|
||||||
return { code: 200 };
|
return { code: 200 };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reloadSystem() {
|
public async reloadSystem(target: 'system' | 'data') {
|
||||||
const cp = spawn('ql -l reload', { shell: '/bin/bash' });
|
const cp = spawn(`ql -l reload ${target || ''}`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
cp.stdout.on('data', (data) => {
|
cp.stdout.on('data', (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
|
@ -266,4 +268,14 @@ export default class SystemService {
|
||||||
return res.send({ code: 400, message: error.message });
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,15 +230,23 @@ usage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
reload_qinglong() {
|
reload_qinglong() {
|
||||||
|
local reload_target="${1}"
|
||||||
local primary_branch="master"
|
local primary_branch="master"
|
||||||
if [[ "${QL_BRANCH}" == "develop" ]]; then
|
if [[ "${QL_BRANCH}" == "develop" ]]; then
|
||||||
primary_branch="develop"
|
primary_branch="develop"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp -rf ${dir_tmp}/qinglong-${primary_branch}/* ${dir_root}/
|
if [[ "$reload_target" == 'system' ]]; then
|
||||||
rm -rf $dir_static/*
|
cp -rf ${dir_tmp}/qinglong-${primary_branch}/* ${dir_root}/
|
||||||
cp -rf ${dir_tmp}/qinglong-static-${primary_branch}/* ${dir_static}/
|
rm -rf $dir_static/*
|
||||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
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
|
reload_pm2
|
||||||
}
|
}
|
||||||
|
@ -246,7 +254,6 @@ reload_qinglong() {
|
||||||
## 更新qinglong
|
## 更新qinglong
|
||||||
update_qinglong() {
|
update_qinglong() {
|
||||||
rm -rf ${dir_tmp}/*
|
rm -rf ${dir_tmp}/*
|
||||||
local needRestart=${1:-"true"}
|
|
||||||
local mirror="gitee"
|
local mirror="gitee"
|
||||||
local downloadQLUrl="https://gitee.com/whyour/qinglong/repository/archive"
|
local downloadQLUrl="https://gitee.com/whyour/qinglong/repository/archive"
|
||||||
local downloadStaticUrl="https://gitee.com/whyour/qinglong-static/repository/archive"
|
local downloadStaticUrl="https://gitee.com/whyour/qinglong-static/repository/archive"
|
||||||
|
@ -490,10 +497,11 @@ main() {
|
||||||
case $p1 in
|
case $p1 in
|
||||||
update)
|
update)
|
||||||
fix_config
|
fix_config
|
||||||
eval update_qinglong "$2" $cmd
|
local needRestart=${p2:-"true"}
|
||||||
|
eval update_qinglong $cmd
|
||||||
;;
|
;;
|
||||||
reload)
|
reload)
|
||||||
eval reload_qinglong $cmd
|
eval reload_qinglong "$p2" $cmd
|
||||||
;;
|
;;
|
||||||
extra)
|
extra)
|
||||||
eval run_extra_shell $cmd
|
eval run_extra_shell $cmd
|
||||||
|
|
|
@ -129,9 +129,9 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
|
||||||
okText: '重启',
|
okText: '重启',
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}system/reload`)
|
.put(`${config.apiPrefix}system/reload`, { data: { type: 'system' } })
|
||||||
.then((_data: any) => {
|
.then((_data: any) => {
|
||||||
message.warning({
|
message.success({
|
||||||
content: (
|
content: (
|
||||||
<span>
|
<span>
|
||||||
系统将在
|
系统将在
|
||||||
|
@ -147,7 +147,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 14);
|
}, 14000);
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Button, InputNumber, Form, Radio, message, Input } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
InputNumber,
|
||||||
|
Form,
|
||||||
|
Radio,
|
||||||
|
message,
|
||||||
|
Input,
|
||||||
|
Upload,
|
||||||
|
Modal,
|
||||||
|
Progress,
|
||||||
|
} from 'antd';
|
||||||
import * as DarkReader from '@umijs/ssr-darkreader';
|
import * as DarkReader from '@umijs/ssr-darkreader';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
|
@ -7,6 +17,8 @@ import CheckUpdate from './checkUpdate';
|
||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
import Countdown from 'antd/lib/statistic/Countdown';
|
||||||
|
|
||||||
const optionsWithDisabled = [
|
const optionsWithDisabled = [
|
||||||
{ label: '亮色', value: 'light' },
|
{ label: '亮色', value: 'light' },
|
||||||
|
@ -25,6 +37,8 @@ const Other = ({
|
||||||
cronConcurrency?: number | null;
|
cronConcurrency?: number | null;
|
||||||
}>();
|
}>();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
const modalRef = useRef<any>();
|
||||||
|
const [exportLoading, setExportLoading] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
enable: enableDarkMode,
|
enable: enableDarkMode,
|
||||||
|
@ -78,6 +92,7 @@ const Other = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportData = () => {
|
const exportData = () => {
|
||||||
|
setExportLoading(true);
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}system/data/export`, { responseType: 'blob' })
|
.put(`${config.apiPrefix}system/data/export`, { responseType: 'blob' })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
@ -85,7 +100,73 @@ const Other = ({
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -140,9 +221,32 @@ const Other = ({
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="数据备份还原" name="frequency">
|
<Form.Item label="数据备份还原" name="frequency">
|
||||||
<Button type="primary" onClick={exportData}>
|
<Button type="primary" onClick={exportData} loading={exportLoading}>
|
||||||
备份
|
备份
|
||||||
</Button>
|
</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>
|
||||||
<Form.Item label="检查更新" name="update">
|
<Form.Item label="检查更新" name="update">
|
||||||
<CheckUpdate systemInfo={systemInfo} socketMessage={socketMessage} />
|
<CheckUpdate systemInfo={systemInfo} socketMessage={socketMessage} />
|
||||||
|
|
Loading…
Reference in New Issue
Block a user