diff --git a/back/api/system.ts b/back/api/system.ts index efc51545..d291c712 100644 --- a/back/api/system.ts +++ b/back/api/system.ts @@ -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); + } + }, + ); }; diff --git a/back/services/system.ts b/back/services/system.ts index bd7c9a0a..5deb64cc 100644 --- a/back/services/system.ts +++ b/back/services/system.ts @@ -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 }; + } + } } diff --git a/shell/update.sh b/shell/update.sh index 0f7e834b..439df451 100755 --- a/shell/update.sh +++ b/shell/update.sh @@ -230,15 +230,23 @@ usage() { } reload_qinglong() { + local reload_target="${1}" local primary_branch="master" if [[ "${QL_BRANCH}" == "develop" ]]; then primary_branch="develop" fi - 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 + 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 diff --git a/src/pages/setting/checkUpdate.tsx b/src/pages/setting/checkUpdate.tsx index c05bc81a..3830dac2 100644 --- a/src/pages/setting/checkUpdate.tsx +++ b/src/pages/setting/checkUpdate.tsx @@ -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: ( 系统将在 @@ -147,7 +147,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => { }); setTimeout(() => { window.location.reload(); - }, 14); + }, 14000); }) .catch((error: any) => { console.log(error); diff --git a/src/pages/setting/other.tsx b/src/pages/setting/other.tsx index 9fda1235..5cedb4e7 100644 --- a/src/pages/setting/other.tsx +++ b/src/pages/setting/other.tsx @@ -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(); + 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,7 +100,73 @@ const Other = ({ }) .catch((error: any) => { console.log(error); + }) + .finally(() => setExportLoading(false)); + }; + + const showUploadModal = (progress: number) => { + if (modalRef.current) { + modalRef.current.update({ + content: ( + + ), }); + } else { + modalRef.current = Modal.info({ + width: 600, + maskClosable: false, + title: '上传中...', + centered: true, + content: ( + + ), + }); + } + }; + + 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: ( + + 系统将在 + + 秒后自动刷新 + + ), + duration: 15, + }); + setTimeout(() => { + window.location.reload(); + }, 14000); + }) + .catch((error: any) => { + console.log(error); + }); + }, + }); }; useEffect(() => { @@ -140,9 +221,32 @@ const Other = ({ - + { + 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)}`, + }} + > + +