diff --git a/src/global.less b/src/global.less
index e8bf87f6..90de4304 100644
--- a/src/global.less
+++ b/src/global.less
@@ -235,6 +235,7 @@ body {
.ant-pro-sider-logo-collapsed {
padding-left: 12px;
+ cursor: e-resize;
.collapse-wrap {
display: none;
diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx
index e57e1eb6..da931c6e 100644
--- a/src/layouts/index.tsx
+++ b/src/layouts/index.tsx
@@ -285,22 +285,21 @@ export default (props: any) => {
{logo}
>
diff --git a/src/locales/en-US/resources.ts b/src/locales/en-US/resources.ts
index 0e3359a5..71a7907e 100644
--- a/src/locales/en-US/resources.ts
+++ b/src/locales/en-US/resources.ts
@@ -96,5 +96,13 @@ export default {
'resources.model.instance': 'Model Instance',
'resources.worker.download.privatekey': 'Download Private Key',
'resources.modelfiles.form.exsting': 'Downloaded',
- 'resources.modelfiles.form.added': 'Added'
+ 'resources.modelfiles.form.added': 'Added',
+ 'resources.worker.maintenance.title': 'System Maintenance',
+ 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
+ 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+ 'resources.worker.maintenance.remark': 'Remark',
+ 'resources.worker.maintenance.remark.rules':
+ 'Please enter maintenance remarks',
+ 'resources.worker.maintenance.tips':
+ 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.'
};
diff --git a/src/locales/ja-JP/resources.ts b/src/locales/ja-JP/resources.ts
index 263f7b92..861c08c0 100644
--- a/src/locales/ja-JP/resources.ts
+++ b/src/locales/ja-JP/resources.ts
@@ -97,7 +97,15 @@ export default {
'resources.worker.download.privatekey': 'Download Private Key',
'resources.worker': 'Worker',
'resources.modelfiles.form.exsting': 'Downloaded',
- 'resources.modelfiles.form.added': 'Added'
+ 'resources.modelfiles.form.added': 'Added',
+ 'resources.worker.maintenance.title': 'System Maintenance',
+ 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
+ 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+ 'resources.worker.maintenance.remark': 'Remark',
+ 'resources.worker.maintenance.remark.rules':
+ 'Please enter maintenance remarks',
+ 'resources.worker.maintenance.tips':
+ 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -117,5 +125,11 @@ export default {
// 14. 'resources.worker.download.privatekey': 'Download Private Key',
// 15. 'resources.worker': 'Worker',
// 16. 'resources.modelfiles.form.exsting': 'Downloaded',
-// 17. 'resources.modelfiles.form.added': 'Added'
+// 17. 'resources.modelfiles.form.added': 'Added',
+// 18. 'resources.worker.maintenance.title': 'System Maintenance',
+// 19. 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
+// 20. 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+// 21. 'resources.worker.maintenance.remark': 'Remark',
+// 22. 'resources.worker.maintenance.remark.rules': 'Please enter maintenance remarks',
+// 23. 'resources.worker.maintenance.tips': 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.'
// ========== End of To-Do List ==========
diff --git a/src/locales/ru-RU/resources.ts b/src/locales/ru-RU/resources.ts
index edd332e0..acfa5ada 100644
--- a/src/locales/ru-RU/resources.ts
+++ b/src/locales/ru-RU/resources.ts
@@ -95,9 +95,22 @@ export default {
'resources.worker.download.privatekey': 'Скачать приватный ключ',
'resources.worker': 'Рабочий узел',
'resources.modelfiles.form.exsting': 'Загружено',
- 'resources.modelfiles.form.added': 'Добавлено'
+ 'resources.modelfiles.form.added': 'Добавлено',
+ 'resources.worker.maintenance.title': 'System Maintenance',
+ 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
+ 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+ 'resources.worker.maintenance.remark': 'Remark',
+ 'resources.worker.maintenance.remark.rules':
+ 'Please enter maintenance remarks',
+ 'resources.worker.maintenance.tips':
+ 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
-
+// 2. 'resources.worker.maintenance.title': 'System Maintenance',
+// 3. 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
+// 4. 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+// 5. 'resources.worker.maintenance.remark': 'Remark',
+// 6. 'resources.worker.maintenance.remark.rules': 'Please enter maintenance remarks',
+// 7. 'resources.worker.maintenance.tips': 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.'
// ========== End of To-Do List ==========
diff --git a/src/locales/zh-CN/resources.ts b/src/locales/zh-CN/resources.ts
index 3bdda76b..515f3be9 100644
--- a/src/locales/zh-CN/resources.ts
+++ b/src/locales/zh-CN/resources.ts
@@ -94,5 +94,12 @@ export default {
'resources.model.instance': '模型实例',
'resources.worker.download.privatekey': '下载私钥',
'resources.modelfiles.form.exsting': '已下载',
- 'resources.modelfiles.form.added': '已添加'
+ 'resources.modelfiles.form.added': '已添加',
+ 'resources.worker.maintenance.title': '系统维护',
+ 'resources.worker.maintenance.enable': '开启维护',
+ 'resources.worker.maintenance.disable': '停止维护',
+ 'resources.worker.maintenance.remark': '备注',
+ 'resources.worker.maintenance.remark.rules': '请输入维护备注',
+ 'resources.worker.maintenance.tips':
+ '启用维护后,节点将停止调度新的模型实例部署任务,正在运行的实例不会受到影响。'
};
diff --git a/src/pages/cluster-management/components/add-worker-command.tsx b/src/pages/cluster-management/components/add-worker-command.tsx
index cbac73cf..78594d62 100644
--- a/src/pages/cluster-management/components/add-worker-command.tsx
+++ b/src/pages/cluster-management/components/add-worker-command.tsx
@@ -4,6 +4,7 @@ import React from 'react';
type ViewModalProps = {
currentGPU?: string;
+ workerIP?: string;
registrationInfo: {
token: string;
image: string;
@@ -13,6 +14,7 @@ type ViewModalProps = {
const AddWorkerCommand: React.FC = ({
registrationInfo,
+ workerIP,
currentGPU
}) => {
const code = React.useMemo(() => {
@@ -21,10 +23,11 @@ const AddWorkerCommand: React.FC = ({
gpu: currentGPU || '',
server: registrationInfo.server_url || origin,
tag: '',
+ workerIP: '${WORKER_IP}',
image: registrationInfo.image,
token: registrationInfo.token || '${token}'
});
- }, [registrationInfo, currentGPU]);
+ }, [registrationInfo, currentGPU, workerIP]);
return (
= ({
const [currentGPU, setCurrentGPU] = React.useState(
GPUDriverMap.NVIDIA
);
- React.useState(GPUDriverMap.NVIDIA);
const [workerCommand, setWorkerCommand] = React.useState>(
{
label: 'NVIDIA',
diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx
index 9376c748..20b21c11 100644
--- a/src/pages/resources/components/workers.tsx
+++ b/src/pages/resources/components/workers.tsx
@@ -20,6 +20,7 @@ import {
} from '../apis';
import { ListItem } from '../config/types';
import useWorkerColumns from '../hooks/use-worker-columns';
+import useWorkerMaintenance from '../hooks/use-worker-maintenance';
import UpdateLabels from './update-labels';
import WorkerDetailModal from './worker-detail-modal';
@@ -46,6 +47,8 @@ const Workers: React.FC = () => {
API: WORKERS_API
});
const [, setClusterSession] = useAtom(clusterSessionAtom);
+ const { MaintenanceModal, handleStopMaintenance, setOpenStatus } =
+ useWorkerMaintenance({ fetchData });
const navigate = useNavigate();
const intl = useIntl();
@@ -154,6 +157,17 @@ const Workers: React.FC = () => {
name: record.name
});
}
+
+ if (val === 'star_maintenance') {
+ setOpenStatus({
+ open: true,
+ currentData: record
+ });
+ }
+
+ if (val === 'stop_maintenance') {
+ handleStopMaintenance(record);
+ }
});
const handleAddWorker = () => {
@@ -253,6 +267,7 @@ const Workers: React.FC = () => {
setWorkerDetailStatus({ currentData: null, open: false })
}
/>
+ {MaintenanceModal}
>
);
diff --git a/src/pages/resources/config/gpu-driver.ts b/src/pages/resources/config/gpu-driver.ts
index c07d6b16..65197f08 100644
--- a/src/pages/resources/config/gpu-driver.ts
+++ b/src/pages/resources/config/gpu-driver.ts
@@ -134,25 +134,19 @@ const registerWorker = (params: {
token: string;
image: string;
gpu: string;
+ workerIP?: string;
}) => {
const config = GPUsConfigs[params.gpu];
- return `CONTAINER_NAME="gpustack-worker" \\
-WORKER_PORT="80" \\
-WORKER_IP="" \\
-sudo docker run -d --name \${CONTAINER_NAME} \\
+ return `sudo docker run -d --name gpustack-worker \\
--restart=unless-stopped \\
--privileged \\
- --publish \${WORKER_PORT}:\${WORKER_PORT} \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT=true" \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME=\${CONTAINER_NAME}" \\
--volume /var/run/docker.sock:/var/run/docker.sock \\
--volume gpustack-data:/var/lib/gpustack \\
--runtime ${config.runtime} \\
${params.image} \\
--server-url ${params.server} \\
--token ${params.token} \\
- --worker-port \${WORKER_PORT} \\
- --advertise-address \${WORKER_IP}`;
+ ${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
};
// avaliable for Ascend
@@ -162,17 +156,13 @@ const registerAscendWorker = (params: {
token: string;
image: string;
gpu: string;
+ workerIP?: string;
}) => {
const config = GPUsConfigs[params.gpu];
- return `CONTAINER_NAME="gpustack-worker" \\
-WORKER_PORT="80" \\
-WORKER_IP="" \\
-sudo docker run -d --name \${CONTAINER_NAME} \\
+ return `sudo docker run -d --name gpustack-worker \\
--restart=unless-stopped \\
--privileged \\
- --publish \${WORKER_PORT}:\${WORKER_PORT} \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT=true" \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME=\${CONTAINER_NAME}" \\
+ --network=host \\
--env "ASCEND_VISIBLE_DEVICES=$(npu-smi info -m | tail -n 1 | awk '{print $1}') \\
--volume /var/run/docker.sock:/var/run/docker.sock \\
--volume gpustack-data:/var/lib/gpustack \\
@@ -180,8 +170,7 @@ sudo docker run -d --name \${CONTAINER_NAME} \\
${params.image} \\
--server-url ${params.server} \\
--token ${params.token} \\
- --worker-port \${WORKER_PORT} \\
- --advertise-address \${WORKER_IP}`;
+ ${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
};
const registerHygonWorker = (params: {
@@ -190,17 +179,13 @@ const registerHygonWorker = (params: {
token: string;
image: string;
gpu: string;
+ workerIP?: string;
}) => {
const config = GPUsConfigs[params.gpu];
- return `CONTAINER_NAME="gpustack-worker" \\
-WORKER_PORT="80" \\
-WORKER_IP="" \\
-sudo docker run -d --name \${CONTAINER_NAME} \\
+ return `sudo docker run -d --name gpustack-worker \\
--restart=unless-stopped \\
--privileged \\
- --publish \${WORKER_PORT}:\${WORKER_PORT} \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT=true" \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME=\${CONTAINER_NAME}" \\
+ --network=host \\
--env ROCM_PATH=/opt/dtk \\
--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib \\
--volume /var/run/docker.sock:/var/run/docker.sock \\
@@ -210,8 +195,7 @@ sudo docker run -d --name \${CONTAINER_NAME} \\
${params.image} \\
--server-url ${params.server} \\
--token ${params.token} \\
- --worker-port \${WORKER_PORT} \\
- --advertise-address \${WORKER_IP}`;
+ ${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
};
const registerIluvatarWorker = (params: {
@@ -220,17 +204,13 @@ const registerIluvatarWorker = (params: {
token: string;
image: string;
gpu: string;
+ workerIP?: string;
}) => {
const config = GPUsConfigs[params.gpu];
- return `CONTAINER_NAME="gpustack-worker" \\
-WORKER_PORT="80" \\
-WORKER_IP="" \\
-sudo docker run -d --name \${CONTAINER_NAME} \\
+ return `sudo docker run -d --name gpustack-worker \\
--restart=unless-stopped \\
--privileged \\
- --publish \${WORKER_PORT}:\${WORKER_PORT} \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT=true" \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME=\${CONTAINER_NAME}" \\
+ --network=host \\
--volume /var/run/docker.sock:/var/run/docker.sock \\
--volume gpustack-data:/var/lib/gpustack \\
--volume /lib/modules:/lib/modules:ro \\
@@ -240,8 +220,7 @@ sudo docker run -d --name \${CONTAINER_NAME} \\
${params.image} \\
--server-url ${params.server} \\
--token ${params.token} \\
- --worker-port \${WORKER_PORT} \\
- --advertise-address \${WORKER_IP}`;
+ ${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
};
const registerMetaXWorker = (params: {
@@ -250,17 +229,13 @@ const registerMetaXWorker = (params: {
token: string;
image: string;
gpu: string;
+ workerIP?: string;
}) => {
const config = GPUsConfigs[params.gpu];
- return `CONTAINER_NAME="gpustack-worker" \\
-WORKER_PORT="80" \\
-WORKER_IP="" \\
-sudo docker run -d --name \${CONTAINER_NAME} \\
+ return `sudo docker run -d --name gpustack-worker \\
--restart=unless-stopped \\
--privileged \\
- --publish \${WORKER_PORT}:\${WORKER_PORT} \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT=true" \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME=\${CONTAINER_NAME}" \\
+ --network=host \\
--volume /var/run/docker.sock:/var/run/docker.sock \\
--volume gpustack-data:/var/lib/gpustack \\
--volume /opt/mxdriver:/opt/mxdriver:ro \\
@@ -268,8 +243,7 @@ sudo docker run -d --name \${CONTAINER_NAME} \\
${params.image} \\
--server-url ${params.server} \\
--token ${params.token} \\
- --worker-port \${WORKER_PORT} \\
- --advertise-address \${WORKER_IP}`;
+ ${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
};
const registerCambriconWorker = (params: {
@@ -278,17 +252,13 @@ const registerCambriconWorker = (params: {
token: string;
image: string;
gpu: string;
+ workerIP?: string;
}) => {
const config = GPUsConfigs[params.gpu];
- return `CONTAINER_NAME="gpustack-worker" \\
-WORKER_PORT="80" \\
-WORKER_IP="" \\
-sudo docker run -d --name \${CONTAINER_NAME} \\
+ return `sudo docker run -d --name gpustack-worker \\
--restart=unless-stopped \\
--privileged \\
- --publish \${WORKER_PORT}:\${WORKER_PORT} \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT=true" \\
- --env "GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME=\${CONTAINER_NAME}" \\
+ --network=host \\
--volume /var/run/docker.sock:/var/run/docker.sock \\
--volume gpustack-data:/var/lib/gpustack \\
--volume /usr/local/neuware:/usr/local/neuware:ro \\
@@ -296,8 +266,7 @@ sudo docker run -d --name \${CONTAINER_NAME} \\
${params.image} \\
--server-url ${params.server} \\
--token ${params.token} \\
- --worker-port \${WORKER_PORT} \\
- --advertise-address \${WORKER_IP}`;
+ ${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
};
export const registerAddWokerCommandMap = {
diff --git a/src/pages/resources/config/index.ts b/src/pages/resources/config/index.ts
index e635047a..0fce6d88 100644
--- a/src/pages/resources/config/index.ts
+++ b/src/pages/resources/config/index.ts
@@ -14,7 +14,8 @@ export const WorkerStatusMap = {
deleting: 'deleting',
error: 'error',
pending: 'pending',
- initializing: 'initializing'
+ initializing: 'initializing',
+ maintenance: 'maintenance'
};
export const WorkerStatusMapValue = {
@@ -25,7 +26,8 @@ export const WorkerStatusMapValue = {
[WorkerStatusMap.deleting]: 'Deleting',
[WorkerStatusMap.error]: 'Error',
[WorkerStatusMap.initializing]: 'Initializing',
- [WorkerStatusMap.pending]: 'Pending'
+ [WorkerStatusMap.pending]: 'Pending',
+ [WorkerStatusMap.maintenance]: 'Maintenance'
};
export const status: any = {
@@ -36,7 +38,8 @@ export const status: any = {
[WorkerStatusMap.deleting]: StatusMaps.transitioning,
[WorkerStatusMap.error]: StatusMaps.error,
[WorkerStatusMap.pending]: StatusMaps.transitioning,
- [WorkerStatusMap.initializing]: StatusMaps.transitioning
+ [WorkerStatusMap.initializing]: StatusMaps.transitioning,
+ [WorkerStatusMap.maintenance]: StatusMaps.warning
};
export const checkEnvCommand = (gpu: string) => {
return {
diff --git a/src/pages/resources/config/types.ts b/src/pages/resources/config/types.ts
index 10f46340..793dacdb 100644
--- a/src/pages/resources/config/types.ts
+++ b/src/pages/resources/config/types.ts
@@ -68,6 +68,10 @@ export interface ListItem {
state_message: string;
ssh_key_id: string;
provision_progress: string;
+ maintenance: {
+ enabled: boolean;
+ message: string;
+ };
status: {
cpu: {
total: number;
diff --git a/src/pages/resources/hooks/use-worker-columns.tsx b/src/pages/resources/hooks/use-worker-columns.tsx
index 531b4072..672a7f3d 100644
--- a/src/pages/resources/hooks/use-worker-columns.tsx
+++ b/src/pages/resources/hooks/use-worker-columns.tsx
@@ -9,7 +9,9 @@ import {
DeleteOutlined,
DownloadOutlined,
EditOutlined,
- InfoCircleOutlined
+ InfoCircleOutlined,
+ SafetyOutlined,
+ ToolOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Tooltip } from 'antd';
@@ -31,6 +33,16 @@ const ActionList = [
key: 'download_ssh_key',
icon:
},
+ {
+ label: 'resources.worker.maintenance.enable',
+ key: 'star_maintenance',
+ icon:
+ },
+ {
+ label: 'resources.worker.maintenance.disable',
+ key: 'stop_maintenance',
+ icon:
+ },
// {
// label: 'common.button.logs',
// locale: false,
@@ -51,6 +63,12 @@ const setActions = (row: ListItem) => {
if (action.key === 'download_ssh_key') {
return !!row.ssh_key_id;
}
+ if (action.key === 'star_maintenance') {
+ return !row.maintenance?.enabled;
+ }
+ if (action.key === 'stop_maintenance') {
+ return row.maintenance?.enabled;
+ }
return true;
});
};
diff --git a/src/pages/resources/hooks/use-worker-maintenance.tsx b/src/pages/resources/hooks/use-worker-maintenance.tsx
new file mode 100644
index 00000000..ffbea2b5
--- /dev/null
+++ b/src/pages/resources/hooks/use-worker-maintenance.tsx
@@ -0,0 +1,132 @@
+import ModalFooter from '@/components/modal-footer';
+import ScrollerModal from '@/components/scroller-modal';
+import SealInput from '@/components/seal-form/seal-input';
+import SealTextArea from '@/components/seal-form/seal-textarea';
+import { useIntl } from '@umijs/max';
+import { Form, message } from 'antd';
+import React from 'react';
+import { updateWorker } from '../apis';
+import { ListItem } from '../config/types';
+
+const useWorkerMaintenance = ({ fetchData }: { fetchData: () => void }) => {
+ const intl = useIntl();
+ const [form] = Form.useForm();
+ const [openStatus, setOpenStatus] = React.useState<{
+ open: boolean;
+ currentData: ListItem;
+ }>({
+ open: false,
+ currentData: {} as ListItem
+ });
+
+ const handleSumit = () => {
+ form.submit();
+ };
+
+ const onCancel = () => {
+ setOpenStatus({
+ open: false,
+ currentData: {} as ListItem
+ });
+ };
+
+ const onOk = async (values: any) => {
+ try {
+ await updateWorker(openStatus.currentData.id, {
+ name: openStatus.currentData.name,
+ labels: openStatus.currentData.labels,
+ maintenance: {
+ enabled: true,
+ message: values.remark
+ }
+ });
+ message.success(intl.formatMessage({ id: 'common.message.success' }));
+ fetchData();
+ onCancel();
+ } catch (error) {}
+ };
+
+ const handleStopMaintenance = async (row: ListItem) => {
+ try {
+ await updateWorker(row.id, {
+ name: row.name,
+ labels: row.labels,
+ maintenance: {
+ enabled: false,
+ message: ''
+ }
+ });
+ message.success(intl.formatMessage({ id: 'common.message.success' }));
+ fetchData();
+ onCancel();
+ } catch (error) {}
+ };
+
+ const MaintenanceModal = (
+
+ }
+ >
+
+
+
+
+
+
+
+
+ );
+
+ return {
+ MaintenanceModal,
+ handleStopMaintenance,
+ setOpenStatus
+ };
+};
+
+export default useWorkerMaintenance;