diff --git a/back/api/dependence.ts b/back/api/dependence.ts index c8cf6a01..9d4d5bc1 100644 --- a/back/api/dependence.ts +++ b/back/api/dependence.ts @@ -87,6 +87,24 @@ export default (app: Router) => { }, ); + route.delete( + '/dependencies/force', + celebrate({ + body: Joi.array().items(Joi.string().required()), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const dependenceService = Container.get(DependenceService); + const data = await dependenceService.removeDb(req.body); + return res.send({ code: 200, data }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + route.get( '/dependencies/:id', celebrate({ diff --git a/back/data/dependence.ts b/back/data/dependence.ts index 21e7f0ad..fbb60265 100644 --- a/back/data/dependence.ts +++ b/back/data/dependence.ts @@ -24,6 +24,9 @@ export enum DependenceStatus { 'installing', 'installed', 'installFailed', + 'removing', + 'removed', + 'removeFailed', } export enum DependenceTypes { @@ -33,13 +36,13 @@ export enum DependenceTypes { } export enum InstallDependenceCommandTypes { - 'pnpm install -g', + 'npm i -g', 'pip3 install', 'apk add --no-cache', } export enum unInstallDependenceCommandTypes { - 'pnpm uninstall -g', + 'npm uninstall -g', 'pip3 uninstall', 'apk del', } diff --git a/back/loaders/initData.ts b/back/loaders/initData.ts index cdd07d17..eea2059d 100644 --- a/back/loaders/initData.ts +++ b/back/loaders/initData.ts @@ -26,8 +26,8 @@ export default async () => { { multi: true }, ); - // 初始化时安装所有依赖 - dependenceDb.find({}).exec((err, docs) => { + // 初始化时安装所有处于安装中,安装成功,安装失败的依赖 + dependenceDb.find({ status: { $in: [0, 1, 2] } }).exec((err, docs) => { const groups = _.groupBy(docs, 'type'); for (const key in groups) { if (Object.prototype.hasOwnProperty.call(groups, key)) { diff --git a/back/loaders/sock.ts b/back/loaders/sock.ts index 11bb1c83..66c51e4c 100644 --- a/back/loaders/sock.ts +++ b/back/loaders/sock.ts @@ -30,6 +30,8 @@ export default async ({ server }: { server: Server }) => { }); return; + } else { + conn.write(JSON.stringify({ type: 'ping', message: '404' })); } } diff --git a/back/services/dependence.ts b/back/services/dependence.ts index e005c41c..83edf0cc 100644 --- a/back/services/dependence.ts +++ b/back/services/dependence.ts @@ -85,11 +85,15 @@ export default class DependenceService { public async remove(ids: string[]) { return new Promise((resolve: any) => { - this.dependenceDb.find({ _id: { $in: ids } }).exec((err, docs) => { - this.installOrUninstallDependencies(docs, false); - this.removeDb(ids); - resolve(); - }); + this.dependenceDb.update( + { _id: { $in: ids } }, + { $set: { status: DependenceStatus.removing, log: [] } }, + { multi: true, returnUpdatedDocs: true }, + async (err, num, docs: Dependence[]) => { + this.installOrUninstallDependencies(docs, false); + resolve(docs); + }, + ); }); } @@ -189,19 +193,20 @@ export default class DependenceService { ? InstallDependenceCommandTypes : unInstallDependenceCommandTypes )[dependencies[0].type as any]; + const actionText = isInstall ? '安装' : '删除'; const depIds = dependencies.map((x) => x._id) as string[]; const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' }); const startTime = Date.now(); this.sockService.sendMessage({ type: 'installDependence', - message: `开始安装依赖 ${depNames},开始时间 ${new Date( + message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date( startTime, ).toLocaleString()}`, references: depIds, }); this.updateLog( depIds, - `开始安装依赖 ${depNames},开始时间 ${new Date( + `开始${actionText}依赖 ${depNames},开始时间 ${new Date( startTime, ).toLocaleString()}\n`, ); @@ -211,7 +216,7 @@ export default class DependenceService { message: data.toString(), references: depIds, }); - isInstall && this.updateLog(depIds, data.toString()); + this.updateLog(depIds, data.toString()); }); cp.stderr.on('data', (data) => { @@ -220,7 +225,7 @@ export default class DependenceService { message: data.toString(), references: depIds, }); - isInstall && this.updateLog(depIds, data.toString()); + this.updateLog(depIds, data.toString()); }); cp.on('error', (err) => { @@ -229,34 +234,53 @@ export default class DependenceService { message: JSON.stringify(err), references: depIds, }); - isInstall && this.updateLog(depIds, JSON.stringify(err)); + this.updateLog(depIds, JSON.stringify(err)); }); cp.on('close', (code) => { const endTime = Date.now(); + const isSucceed = code === 0; + const resultText = isSucceed ? '成功' : '失败'; + this.sockService.sendMessage({ type: 'installDependence', - message: `依赖安装结束,结束时间 ${new Date( + message: `依赖${actionText}${resultText},结束时间 ${new Date( endTime, ).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`, references: depIds, }); - isInstall && - this.updateLog( - depIds, - `依赖安装结束,结束时间 ${new Date(endTime).toLocaleString()},耗时 ${ - (endTime - startTime) / 1000 - } 秒`, - ); - isInstall && - this.dependenceDb.update( - { _id: { $in: depIds } }, - { - $set: { status: DependenceStatus.installed }, - $unset: { pid: true }, - }, - { multi: true }, - ); + this.updateLog( + depIds, + `依赖${actionText}${resultText},结束时间 ${new Date( + endTime, + ).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`, + ); + + let status = null; + if (isSucceed) { + status = isInstall + ? DependenceStatus.installed + : DependenceStatus.removed; + } else { + status = isInstall + ? DependenceStatus.installFailed + : DependenceStatus.removeFailed; + } + this.dependenceDb.update( + { _id: { $in: depIds } }, + { + $set: { status }, + $unset: { pid: true }, + }, + { multi: true }, + ); + + // 如果删除依赖成功,3秒后删除数据库记录 + if (isSucceed && !isInstall) { + setTimeout(() => { + this.removeDb(depIds); + }, 5000); + } }); } } diff --git a/src/layouts/defaultProps.tsx b/src/layouts/defaultProps.tsx index 579e738a..f9160a45 100644 --- a/src/layouts/defaultProps.tsx +++ b/src/layouts/defaultProps.tsx @@ -7,6 +7,7 @@ import { FolderOutlined, RadiusSettingOutlined, ControlOutlined, + ContainerOutlined, } from '@ant-design/icons'; export default { @@ -51,7 +52,7 @@ export default { { path: '/dependence', name: '依赖管理', - icon: , + icon: , component: '@/pages/dependence/index', }, { diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 0bbb7233..596d13b1 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -26,6 +26,7 @@ export default function (props: any) { const [loading, setLoading] = useState(true); const [systemInfo, setSystemInfo] = useState<{ isInitialized: boolean }>(); const ws = useRef(null); + const [socketMessage, setSocketMessage] = useState(); const logout = () => { request.post(`${config.apiPrefix}logout`).then(() => { @@ -124,6 +125,7 @@ export default function (props: any) { }, []); useEffect(() => { + if (!user) return; ws.current = new SockJS( `${location.origin}/api/ws?token=${localStorage.getItem(config.authKey)}`, ); @@ -131,11 +133,14 @@ export default function (props: any) { ws.current.onmessage = (e: any) => { try { const data = JSON.parse(e.data); - if (data && data.message === 'hanhh') { - console.log('websocket连接成功', e); - } else { - console.log('websocket连接失败', e); + if (data.type === 'ping') { + if (data && data.message === 'hanhh') { + console.log('websocket连接成功', e); + } else { + console.log('websocket连接失败', e); + } } + setSocketMessage(data); } catch (error) { console.log('websocket连接失败', e); } @@ -146,7 +151,7 @@ export default function (props: any) { return () => { wsCurrent.close(); }; - }, []); + }, [user]); if (['/login', '/initialization'].includes(props.location.pathname)) { document.title = `${ @@ -246,7 +251,7 @@ export default function (props: any) { user, reloadUser, reloadTheme: setTheme, - ws: ws.current, + socketMessage, }); })} diff --git a/src/pages/dependence/index.tsx b/src/pages/dependence/index.tsx index 13f025bd..e622cee8 100644 --- a/src/pages/dependence/index.tsx +++ b/src/pages/dependence/index.tsx @@ -37,6 +37,9 @@ enum Status { '安装中', '已安装', '安装失败', + '删除中', + '已删除', + '删除失败', } enum StatusColor { @@ -45,7 +48,7 @@ enum StatusColor { 'error', } -const Dependence = ({ headerStyle, isPhone, ws }: any) => { +const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => { const columns: any = [ { title: '序号', @@ -69,7 +72,10 @@ const Dependence = ({ headerStyle, isPhone, ws }: any) => { render: (text: string, record: any, index: number) => { return ( - + {Status[record.status]} @@ -93,20 +99,21 @@ const Dependence = ({ headerStyle, isPhone, ws }: any) => { const isPc = !isPhone; return ( - {record.status !== Status.安装中 && ( - <> - - reInstallDependence(record, index)}> - - - - - deleteDependence(record, index)}> - - - - - )} + {record.status !== Status.安装中 && + record.status !== Status.删除中 && ( + <> + + reInstallDependence(record, index)}> + + + + + deleteDependence(record, index)}> + + + + + )} { @@ -171,10 +178,7 @@ const Dependence = ({ headerStyle, isPhone, ws }: any) => { .delete(`${config.apiPrefix}dependencies`, { data: [record._id] }) .then((data: any) => { if (data.code === 200) { - message.success('删除成功'); - const result = [...value]; - result.splice(index, 1); - setValue(result); + handleDependence(data.data[0]); } else { message.error(data); } @@ -254,13 +258,12 @@ const Dependence = ({ headerStyle, isPhone, ws }: any) => { const delDependencies = () => { Modal.confirm({ title: '确认删除', - content: <>确认删除选中的变量吗, + content: <>确认删除选中的依赖吗, onOk() { request .delete(`${config.apiPrefix}dependencies`, { data: selectedRowIds }) .then((data: any) => { if (data.code === 200) { - message.success('批量删除成功'); setSelectedRowIds([]); getDependencies(); } else { @@ -312,25 +315,41 @@ const Dependence = ({ headerStyle, isPhone, ws }: any) => { }, [logDependence]); useEffect(() => { - ws.onmessage = (e: any) => { - const { type, message, references } = JSON.parse(e.data); - if ( - type === 'installDependence' && - message === '依赖安装结束' && - references.length > 0 - ) { - const result = [...value]; - for (let i = 0; i < references.length; i++) { - const index = value.findIndex((x) => x._id === references[i]); - result.splice(index, 1, { - ...result[index], - status: Status.已安装, - }); - } - setValue(result); + if (!socketMessage) return; + const { type, message, references } = socketMessage; + if ( + type === 'installDependence' && + message.includes('结束时间') && + references.length > 0 + ) { + let status; + if (message.includes('安装')) { + status = message.includes('成功') ? Status.已安装 : Status.安装失败; + } else { + status = message.includes('成功') ? Status.已删除 : Status.删除失败; } - }; - }, [value]); + const result = [...value]; + for (let i = 0; i < references.length; i++) { + const index = value.findIndex((x) => x._id === references[i]); + result.splice(index, 1, { + ...result[index], + status, + }); + } + setValue(result); + + if (status === Status.已删除) { + setTimeout(() => { + const _result = [...value]; + for (let i = 0; i < references.length; i++) { + const index = value.findIndex((x) => x._id === references[i]); + _result.splice(index, 1); + } + setValue(_result); + }, 5000); + } + } + }, [socketMessage]); const panelContent = () => ( <> @@ -394,13 +413,13 @@ const Dependence = ({ headerStyle, isPhone, ws }: any) => { tabPosition="top" onChange={onTabChange} > - + {panelContent()} - + {panelContent()} - + {panelContent()} @@ -412,11 +431,18 @@ const Dependence = ({ headerStyle, isPhone, ws }: any) => { /> { + handleCancel={(needRemove?: boolean) => { setIsLogModalVisible(false); - getDependenceDetail(logDependence); + if (needRemove) { + const index = value.findIndex((x) => x._id === logDependence._id); + const result = [...value]; + result.splice(index, 1); + setValue(result); + } else if ([...value].map((x) => x._id).includes(logDependence._id)) { + getDependenceDetail(logDependence); + } }} - ws={ws} + socketMessage={socketMessage} dependence={logDependence} /> diff --git a/src/pages/dependence/logModal.tsx b/src/pages/dependence/logModal.tsx index 4366fa97..0b131394 100644 --- a/src/pages/dependence/logModal.tsx +++ b/src/pages/dependence/logModal.tsx @@ -12,21 +12,23 @@ const DependenceLogModal = ({ dependence, handleCancel, visible, - ws, + socketMessage, }: { dependence?: any; visible: boolean; - handleCancel: () => void; - ws: any; + handleCancel: (needRemove?: boolean) => void; + socketMessage: any; }) => { const [value, setValue] = useState(''); const [executing, setExecuting] = useState(true); const [isPhone, setIsPhone] = useState(false); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); + const [isRemoveFailed, setIsRemoveFailed] = useState(false); + const [removeLoading, setRemoveLoading] = useState(false); - const cancel = () => { + const cancel = (needRemove: boolean = false) => { localStorage.removeItem('logDependence'); - handleCancel(); + handleCancel(needRemove); }; const titleElement = () => { @@ -49,7 +51,8 @@ const DependenceLogModal = ({ if (localStorage.getItem('logDependence') === dependence._id) { const log = (data.data.log || []).join('\n') as string; setValue(log); - setExecuting(!log.includes('依赖安装结束')); + setExecuting(!log.includes('结束时间')); + setIsRemoveFailed(log.includes('删除失败')); } }) .finally(() => { @@ -57,23 +60,48 @@ const DependenceLogModal = ({ }); }; + const forceRemoveDependence = () => { + setRemoveLoading(true); + request + .delete(`${config.apiPrefix}dependencies/force`, { + data: [dependence._id], + }) + .then((data: any) => { + cancel(true); + }) + .finally(() => { + setRemoveLoading(false); + }); + }; + + const footerClick = () => { + if (isRemoveFailed) { + forceRemoveDependence(); + } else { + cancel(); + } + }; + useEffect(() => { if (dependence) { getDependenceLog(); - ws.onmessage = (e: any) => { - const { type, message, references } = JSON.parse(e.data); - if ( - type === 'installDependence' && - message === '依赖安装结束' && - references.length > 0 - ) { - setExecuting(false); - } - setValue(`${value} \n ${message}`); - }; } }, [dependence]); + useEffect(() => { + if (!socketMessage) return; + const { type, message, references } = socketMessage; + if ( + type === 'installDependence' && + message.includes('结束时间') && + references.length > 0 + ) { + setExecuting(false); + setIsRemoveFailed(message.includes('删除失败')); + } + setValue(`${value} \n ${message}`); + }, [socketMessage]); + useEffect(() => { setIsPhone(document.body.clientWidth < 768); }, []); @@ -93,8 +121,8 @@ const DependenceLogModal = ({ onOk={() => cancel()} onCancel={() => cancel()} footer={[ - , ]} > diff --git a/src/pages/dependence/modal.tsx b/src/pages/dependence/modal.tsx index c49a2999..dd7bc484 100644 --- a/src/pages/dependence/modal.tsx +++ b/src/pages/dependence/modal.tsx @@ -51,9 +51,7 @@ const DependenceModal = ({ data: payload, }, ); - if (code === 200) { - message.success(dependence ? '更新依赖成功' : '添加依赖成功'); - } else { + if (code !== 200) { message.error(data); } setLoading(false); diff --git a/src/pages/setting/checkUpdate.tsx b/src/pages/setting/checkUpdate.tsx index d16571ae..1f54023e 100644 --- a/src/pages/setting/checkUpdate.tsx +++ b/src/pages/setting/checkUpdate.tsx @@ -6,8 +6,9 @@ import { version } from '../../version'; const { Countdown } = Statistic; -const CheckUpdate = ({ ws }: any) => { +const CheckUpdate = ({ socketMessage }: any) => { const [updateLoading, setUpdateLoading] = useState(false); + const [value, setValue] = useState(''); const modalRef = useRef(); const checkUpgrade = () => { @@ -95,7 +96,7 @@ const CheckUpdate = ({ ws }: any) => { fontWeight: 400, }} > - 更新中... + {value} ), @@ -103,55 +104,60 @@ const CheckUpdate = ({ ws }: any) => { }; useEffect(() => { - let _message = ''; - ws.onmessage = (e: any) => { - if (!modalRef.current) { - return; - } - _message = `${_message}\n${e.data}`; - modalRef.current.update({ - content: ( -
-
-              {_message}
-            
-
-
- ), - }); - document.getElementById('log-identifier') && - document - .getElementById('log-identifier')! - .scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + if (!modalRef.current || !socketMessage) { + return; + } + const { type, message, references } = socketMessage; - if (e.data.includes('重启面板')) { - message.warning({ - content: ( - - 系统将在 - - 秒后自动刷新 - - ), - duration: 10, - }); - setTimeout(() => { - window.location.reload(); - }, 10000); - } - }; - }, []); + if (type !== 'updateSystemVersion') { + return; + } + + const newMessage = `${value} \n ${message}`; + modalRef.current.update({ + content: ( +
+
+            {newMessage}
+          
+
+
+ ), + }); + setValue(newMessage); + + document.getElementById('log-identifier') && + document + .getElementById('log-identifier')! + .scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + + if (newMessage.includes('重启面板')) { + message.warning({ + content: ( + + 系统将在 + + 秒后自动刷新 + + ), + duration: 10, + }); + setTimeout(() => { + window.location.reload(); + }, 10000); + } + }, [socketMessage]); return ( <> diff --git a/src/pages/setting/index.tsx b/src/pages/setting/index.tsx index 35b3e43b..efacb1ae 100644 --- a/src/pages/setting/index.tsx +++ b/src/pages/setting/index.tsx @@ -47,7 +47,7 @@ const Setting = ({ user, reloadUser, reloadTheme, - ws, + socketMessage, }: any) => { const columns = [ { @@ -377,7 +377,7 @@ const Setting = ({ /> - +