refactor: add worker
This commit is contained in:
@@ -77,6 +77,7 @@ export interface CollapsibleContainerProps {
|
||||
onToggle?: (open: boolean) => void;
|
||||
disabled?: boolean;
|
||||
variant?: 'outlined' | 'borderless' | undefined;
|
||||
iconPosition?: 'left' | 'right';
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
styles?: {
|
||||
@@ -99,6 +100,7 @@ export default function CollapsibleContainer({
|
||||
variant = 'borderless',
|
||||
className = '',
|
||||
collapsible,
|
||||
iconPosition = 'left',
|
||||
styles: cardStyles,
|
||||
children
|
||||
}: CollapsibleContainerProps) {
|
||||
@@ -121,6 +123,22 @@ export default function CollapsibleContainer({
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [height, setHeight] = useState(isOpen ? 'auto' : '0px');
|
||||
|
||||
const renderIcon = () => {
|
||||
if (showExpandIcon) {
|
||||
return (
|
||||
<IconFont
|
||||
rotate={isOpen ? 180 : 0}
|
||||
type="icon-down"
|
||||
style={{
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
fontSize: 12
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderTitle = () => {
|
||||
if (!collapsible) {
|
||||
return null;
|
||||
@@ -129,16 +147,7 @@ export default function CollapsibleContainer({
|
||||
<div className={styles.title} onClick={toggle}>
|
||||
<div className={styles.left}>
|
||||
<div className={styles.expandIcon}>
|
||||
{showExpandIcon && (
|
||||
<IconFont
|
||||
rotate={isOpen ? 180 : 0}
|
||||
type="icon-down"
|
||||
style={{
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
fontSize: 12
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{iconPosition === 'left' && renderIcon()}
|
||||
{title && <div>{title}</div>}
|
||||
</div>
|
||||
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
|
||||
@@ -146,6 +155,7 @@ export default function CollapsibleContainer({
|
||||
<div className={styles.right}>
|
||||
{right && <span>{right}</span>}
|
||||
{deleteBtn && <span className="del-btn">{deleteBtn}</span>}
|
||||
{iconPosition === 'right' && renderIcon()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ const SealSlider: React.FC<SealSwitchProps> = (props) => {
|
||||
defaultValue,
|
||||
checkStatus,
|
||||
labelWidth,
|
||||
size = 'small',
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -57,7 +58,7 @@ const SealSlider: React.FC<SealSwitchProps> = (props) => {
|
||||
<LabelInfo label={label} description={description}></LabelInfo>
|
||||
<Switch
|
||||
{...rest}
|
||||
size="small"
|
||||
size={size}
|
||||
defaultValue={defaultValue}
|
||||
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||
value={value}
|
||||
|
||||
@@ -45,20 +45,39 @@ export default {
|
||||
'clusters.create.register.tips':
|
||||
'On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.',
|
||||
'cluster.create.checkEnv.tips':
|
||||
'Use the following command to check if the environment is ready',
|
||||
'Use the following command to check if the environment is ready.',
|
||||
'cluster.provider.comingsoon': 'Coming soon',
|
||||
'clusters.addworker.nvidiaNotes-01':
|
||||
'If the worker has multiple outbound IP addresses, enter <span class="bold-text">WORKER_IP</span> to ensure that the node uses the desired IP. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'If multiple outbound IPs exist, specify the one you want the worker to use. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'clusters.addworker.nvidiaNotes-02':
|
||||
'If the model directory already exists on the worker, please add the <span class="bold-text">--volume</span> directive to mount it.',
|
||||
'clusters.addworker.hygonNotes':
|
||||
'If the <span class="bold-text">/opt/hyhal</span> directory does not exist, please create a symbolic link pointing to the Hygon installed path: <span class="bold-text">/opt/hyhal</span>. Same as <span class="bold-text">/opt/dtk</span> directory.',
|
||||
'clusters.addworker.corexNotes':
|
||||
'If the <span class="bold-text">/lib/modules</span> directory does not exist, please create a symbolic link pointing to the Iluvatar installed path: <span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Same as <span class="bold-text">/usr/local/corex</span> directory.',
|
||||
'clusters.addworker.metaxNotes':
|
||||
'If the <span class="bold-text">/opt/mxdriver</span> directory does not exist, please create a symbolic link pointing to the MetaX installed path: <span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Same as <span class="bold-text">/opt/maca</span> directory.',
|
||||
'clusters.addworker.cambriconNotes':
|
||||
'If the <span class="bold-text">/usr/local/neuware</span> directory does not exist, please create a symbolic link pointing to the Cambricon installed path: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.',
|
||||
'If a model directory already exists on the worker, you can specify the path to mount it.',
|
||||
'clusters.addworker.hygonNotes': `If the <span class="bold-text">/opt/hyhal</span> directory does not exist, create a symbolic link to the Hygon installation path.
|
||||
Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
||||
'clusters.addworker.corexNotes': `If the <span class="bold-text">/lib/modules</span> directory does not exist, create a symbolic link to the Iluvatar installation path:
|
||||
<span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Same applies to the <span class="bold-text">/usr/local/corex</span> directory.`,
|
||||
'clusters.addworker.metaxNotes': `If the <span class="bold-text">/opt/mxdriver</span> directory does not exist, create a symbolic link to the MetaX installation path:
|
||||
<span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Same applies to the <span class="bold-text">/opt/maca</span> directory.`,
|
||||
'clusters.addworker.cambriconNotes': `If the <span class="bold-text">/usr/local/neuware</span> directory does not exist, create a symbolic link to the Cambricon installation path:
|
||||
<span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.`,
|
||||
'clusters.addworker.hygonNotes-02':
|
||||
'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.'
|
||||
'If device detection fails, try removing <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||
'clusters.addworker.selectCluster': 'Select Cluster',
|
||||
'clusters.addworker.selectCluster.tips':
|
||||
'For non-Docker clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||
'clusters.addworker.checkEnv': 'Check Environment',
|
||||
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||
'clusters.addworker.runCommand': 'Run Command',
|
||||
'clusters.addworker.specifyWorkerIP': 'Specify Worker IP',
|
||||
'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
||||
'clusters.addworker.enterWorkerIP': 'Enter worker IP',
|
||||
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
|
||||
'clusters.addworker.extraVolume': 'Additional Volume Mount',
|
||||
'clusters.addworker.configSummary': 'Configuration Summary',
|
||||
'clusters.addworker.gpuVendor': 'GPU Vendor',
|
||||
'clusters.addworker.workerIP': 'Worker IP',
|
||||
'clusters.addworker.notSpecified': 'Not Specified',
|
||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'e.g. /data/models (path must start with /)'
|
||||
};
|
||||
|
||||
@@ -104,5 +104,7 @@ export default {
|
||||
'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.'
|
||||
'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.',
|
||||
'resources.worker.noCluster.tips':
|
||||
'No available clusters. Please create a cluster before adding a node.'
|
||||
};
|
||||
|
||||
@@ -43,24 +43,43 @@ export default {
|
||||
'clusters.create.addCommand.tips':
|
||||
'On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||
'cluster.create.checkEnv.tips':
|
||||
'Use the following command to check if the environment is ready',
|
||||
'Use the following command to check if the environment is ready.',
|
||||
'clusters.create.register.tips':
|
||||
' On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.',
|
||||
'cluster.provider.comingsoon': 'Coming soon',
|
||||
'clusters.addworker.nvidiaNotes-01':
|
||||
'If the worker has multiple outbound IP addresses, enter <span class="bold-text">WORKER_IP</span> to ensure that the node uses the desired IP. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'If multiple outbound IPs exist, specify the one you want the worker to use. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'clusters.addworker.nvidiaNotes-02':
|
||||
'If the model directory already exists on the worker, please add the <span class="bold-text">--volume</span> directive to mount it.',
|
||||
'clusters.addworker.hygonNotes':
|
||||
'If the <span class="bold-text">/opt/hyhal</span> directory does not exist, please create a symbolic link pointing to the Hygon installed path: <span class="bold-text">/opt/hyhal</span>. Same as <span class="bold-text">/opt/dtk</span> directory.',
|
||||
'clusters.addworker.corexNotes':
|
||||
'If the <span class="bold-text">/lib/modules</span> directory does not exist, please create a symbolic link pointing to the Iluvatar installed path: <span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Same as <span class="bold-text">/usr/local/corex</span> directory.',
|
||||
'clusters.addworker.metaxNotes':
|
||||
'If the <span class="bold-text">/opt/mxdriver</span> directory does not exist, please create a symbolic link pointing to the MetaX installed path: <span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Same as <span class="bold-text">/opt/maca</span> directory.',
|
||||
'clusters.addworker.cambriconNotes':
|
||||
'If the <span class="bold-text">/usr/local/neuware</span> directory does not exist, please create a symbolic link pointing to the Cambricon installed path: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.',
|
||||
'If a model directory already exists on the worker, you can specify the path to mount it.',
|
||||
'clusters.addworker.hygonNotes': `If the <span class="bold-text">/opt/hyhal</span> directory does not exist, create a symbolic link to the Hygon installation path.
|
||||
Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
||||
'clusters.addworker.corexNotes': `If the <span class="bold-text">/lib/modules</span> directory does not exist, create a symbolic link to the Iluvatar installation path:
|
||||
<span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Same applies to the <span class="bold-text">/usr/local/corex</span> directory.`,
|
||||
'clusters.addworker.metaxNotes': `If the <span class="bold-text">/opt/mxdriver</span> directory does not exist, create a symbolic link to the MetaX installation path:
|
||||
<span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Same applies to the <span class="bold-text">/opt/maca</span> directory.`,
|
||||
'clusters.addworker.cambriconNotes': `If the <span class="bold-text">/usr/local/neuware</span> directory does not exist, create a symbolic link to the Cambricon installation path:
|
||||
<span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.`,
|
||||
'clusters.addworker.hygonNotes-02':
|
||||
'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.'
|
||||
'If device detection fails, try removing <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||
'clusters.addworker.selectCluster': 'Select Cluster',
|
||||
'clusters.addworker.selectCluster.tips':
|
||||
'For non-Docker clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||
'clusters.addworker.checkEnv': 'Check Environment',
|
||||
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||
'clusters.addworker.runCommand': 'Run Command',
|
||||
'clusters.addworker.specifyWorkerIP': 'Specify Worker IP',
|
||||
'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
||||
'clusters.addworker.enterWorkerIP': 'Enter worker IP',
|
||||
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
|
||||
'clusters.addworker.extraVolume': 'Additional Volume Mount',
|
||||
'clusters.addworker.configSummary': 'Configuration Summary',
|
||||
'clusters.addworker.gpuVendor': 'GPU Vendor',
|
||||
'clusters.addworker.workerIP': 'Worker IP',
|
||||
'clusters.addworker.notSpecified': 'Not Specified',
|
||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'e.g. /data/models (path must start with /)'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -104,14 +123,31 @@ export default {
|
||||
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool',
|
||||
// 39. 'clusters.create.addworker.tips': 'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.',
|
||||
// 40. 'clusters.create.addCommand.tips': 'On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||
// 41. 'cluster.create.checkEnv.tips': 'Use the following command to check if the environment is ready',
|
||||
// 41. 'cluster.create.checkEnv.tips': 'Use the following command to check if the environment is ready.',
|
||||
// 42. 'clusters.create.register.tips': ' On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.',
|
||||
// 43. 'cluster.provider.comingsoon': 'Coming soon',
|
||||
// 44. 'clusters.addworker.nvidiaNotes-01': 'If the worker has multiple outbound IP addresses, enter <span class="bold-text">WORKER_IP</span> to ensure that the node uses the desired IP. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
// 45. 'clusters.addworker.nvidiaNotes-02': 'If the model directory already exists on the worker, please add the <span class="bold-text">--volume</span> directive to mount it.',
|
||||
// 46. 'clusters.addworker.hygonNotes': 'If the <span class="bold-text">/opt/hyhal</span> directory does not exist, please create a symbolic link pointing to the Hygon installed path: <span class="bold-text">/opt/hyhal</span>. Same as <span class="bold-text">/opt/dtk</span> directory.',
|
||||
// 47. 'clusters.addworker.corexNotes': 'If the <span class="bold-text">/lib/modules</span> directory does not exist, please create a symbolic link pointing to the Iluvatar installed path: <span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Same as <span class
|
||||
// 48. 'clusters.addworker.metaxNotes': 'If the <span class="bold-text">/opt/mxdriver</span> directory does not exist, please create a symbolic link pointing to the MetaX installed path: <span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Same as <span class="bold-text">/opt/maca</span> directory.',
|
||||
// 49. 'clusters.addworker.cambriconNotes': 'If the <span class="bold-text">/usr/local/neuware</span> directory does not exist, please create a symbolic link pointing to the Cambricon installed path: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.'
|
||||
// 50. 'clusters.addworker.hygonNotes-02': 'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.'
|
||||
// 44. 'clusters.addworker.nvidiaNotes-01': 'If multiple outbound IPs exist, specify the one you want the worker to use. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
// 45. 'clusters.addworker.nvidiaNotes-02': 'If a model directory already exists on the worker, you can specify the path to mount it.',
|
||||
// 46. 'clusters.addworker.hygonNotes': 'If the <span class="bold-text">/opt/hyhal</span> directory does not exist, create a symbolic link to the Hygon installation path. Same applies to the <span class="bold-text">/opt/dtk</span> directory.',
|
||||
// 47. 'clusters.addworker.corexNotes': 'If the <span class="bold-text">/lib/modules</span> directory does not exist, create a symbolic link to the Iluvatar installation path: <span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Same applies to the <span class="bold-text">/usr/local/corex</span> directory.',
|
||||
// 48. 'clusters.addworker.metaxNotes': 'If the <span class="bold-text">/opt/mxdriver</span> directory does not exist, create a symbolic link to the MetaX installation path: <span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Same applies to the <span class="bold-text">/opt/maca</span> directory.',
|
||||
// 49. 'clusters.addworker.cambriconNotes': 'If the <span class="bold-text">/usr/local/neuware</span> directory does not exist, create a symbolic link to the Cambricon installation path: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.'
|
||||
// 50. 'clusters.addworker.hygonNotes-02': 'If device detection fails, try removing <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||
// 51. 'clusters.addworker.selectCluster': 'Select Cluster',
|
||||
// 52. 'clusters.addworker.selectCluster.tips': 'For non-Docker clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||
// 53. 'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||
// 54. 'clusters.addworker.checkEnv': 'Check Environment',
|
||||
// 55. 'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||
// 56. 'clusters.addworker.runCommand': 'Run Command',
|
||||
// 57. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP',
|
||||
// 58. 'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
||||
// 59. 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
|
||||
// 60. 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
|
||||
// 61. 'clusters.addworker.extraVolume': 'Additional Volume Mount',
|
||||
// 62. 'clusters.addworker.configSummary': 'Configuration Summary',
|
||||
// 63. 'clusters.addworker.gpuVendor': 'GPU Vendor',
|
||||
// 64. 'clusters.addworker.workerIP': 'Worker IP',
|
||||
// 65. 'clusters.addworker.notSpecified': 'Not Specified',
|
||||
// 66. 'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
// 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -105,7 +105,9 @@ export default {
|
||||
'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.'
|
||||
'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.',
|
||||
'resources.worker.noCluster.tips':
|
||||
'No available clusters. Please create a cluster before adding a node.'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -131,5 +133,6 @@ export default {
|
||||
// 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.'
|
||||
// 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.',
|
||||
// 24. 'resources.worker.noCluster.tips': 'No available clusters. Please create a cluster before adding a node.'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -48,9 +48,9 @@ export default {
|
||||
'На Kubernetes кластере, который необходимо добавить, выполните следующую команду, чтобы присоединить его узлы к кластеру.',
|
||||
'cluster.provider.comingsoon': 'Скоро будет',
|
||||
'clusters.addworker.nvidiaNotes-01':
|
||||
'Если рабочий узел имеет несколько исходящих IP-адресов, укажите <span class="bold-text">WORKER_IP</span>, чтобы гарантировать использование узлом нужного IP. Перепроверьте с помощью команды <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'If multiple outbound IPs exist, specify the one you want the worker to use. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'clusters.addworker.nvidiaNotes-02':
|
||||
'Если директория с моделью уже существует на рабочем узле, добавьте директиву <span class="bold-text">--volume</span> для её монтирования.',
|
||||
'If a model directory already exists on the worker, you can specify the path to mount it.',
|
||||
'clusters.addworker.hygonNotes':
|
||||
'Если директория <span class="bold-text">/opt/hyhal</span> не существует, создайте символическую ссылку на путь установки Hygon: <span class="bold-text">/opt/hyhal</span>. Аналогично для директории <span class="bold-text">/opt/dtk</span>.',
|
||||
'clusters.addworker.corexNotes':
|
||||
@@ -60,9 +60,47 @@ export default {
|
||||
'clusters.addworker.cambriconNotes':
|
||||
'Если директория <span class="bold-text">/usr/local/neuware</span> не существует, создайте символическую ссылку на путь установки Cambricon: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.',
|
||||
'clusters.addworker.hygonNotes-02':
|
||||
'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.'
|
||||
'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||
'clusters.addworker.selectCluster': 'Select Cluster',
|
||||
'clusters.addworker.selectCluster.tips':
|
||||
'For non-Docker clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||
'clusters.addworker.checkEnv': 'Check Environment',
|
||||
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||
'clusters.addworker.runCommand': 'Run Command',
|
||||
'clusters.addworker.specifyWorkerIP': 'Specify Worker IP',
|
||||
'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
||||
'clusters.addworker.enterWorkerIP': 'Enter worker IP',
|
||||
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
|
||||
'clusters.addworker.extraVolume': 'Additional Volume Mount',
|
||||
'clusters.addworker.configSummary': 'Configuration Summary',
|
||||
'clusters.addworker.gpuVendor': 'GPU Vendor',
|
||||
'clusters.addworker.workerIP': 'Worker IP',
|
||||
'clusters.addworker.notSpecified': 'Not Specified',
|
||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'e.g. /data/models (path must start with /)'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
// 1. 'clusters.addworker.hygonNotes-02': 'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.'
|
||||
// 1. 'clusters.addworker.hygonNotes-02': 'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||
// 2. 'clusters.addworker.selectCluster': 'Select Cluster',
|
||||
// 3. 'clusters.addworker.selectCluster.tips': 'For non-Docker clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||
// 4. 'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||
// 5. 'clusters.addworker.checkEnv': 'Check Environment',
|
||||
// 6. 'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||
// 7. 'clusters.addworker.runCommand': 'Run Command',
|
||||
// 8. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP',
|
||||
// 9. 'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
||||
// 10. 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
|
||||
// 11. 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
|
||||
// 12. 'clusters.addworker.extraVolume': 'Additional Volume Mount',
|
||||
// 13. 'clusters.addworker.configSummary': 'Configuration Summary',
|
||||
// 14. 'clusters.addworker.gpuVendor': 'GPU Vendor',
|
||||
// 15. 'clusters.addworker.workerIP': 'Worker IP',
|
||||
// 16. 'clusters.addworker.notSpecified': 'Not Specified',
|
||||
// 17. 'clusters.addworker.nvidiaNotes-01': 'If multiple outbound IPs exist, specify the one you want the worker to use. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
// 18. 'clusters.addworker.nvidiaNotes-02': 'If a model directory already exists on the worker, you can specify the path to mount it.',
|
||||
// 19. 'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
// 20. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -103,7 +103,9 @@ export default {
|
||||
'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.'
|
||||
'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.',
|
||||
'resources.worker.noCluster.tips':
|
||||
'No available clusters. Please create a cluster before adding a node.'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -112,5 +114,6 @@ export default {
|
||||
// 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.'
|
||||
// 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.',
|
||||
// 8. 'resources.worker.noCluster.tips': 'No available clusters. Please create a cluster before adding a node.'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -40,10 +40,10 @@ export default {
|
||||
'clusters.create.addworker.tips':
|
||||
'在执行以下命令之前,请确保已满足 {label} 的<a href={link} target="_blank">先决条件</a>。',
|
||||
'clusters.create.addCommand.tips':
|
||||
'在需要添加的节点上运行以下命令,将其加入到集群中',
|
||||
'在需要添加的节点上运行以下命令,将其加入到集群中。',
|
||||
'clusters.create.register.tips':
|
||||
'在需要添加的 Kubernetes 集群上运行以下命令,将其中的节点加入到集群中',
|
||||
'cluster.create.checkEnv.tips': '使用以下命令检查环境是否准备妥当',
|
||||
'在需要添加的 Kubernetes 集群上运行以下命令,将其中的节点加入到集群中。',
|
||||
'cluster.create.checkEnv.tips': '使用以下命令检查环境是否准备妥当。',
|
||||
'cluster.provider.comingsoon': '即将推出',
|
||||
'clusters.addworker.nvidiaNotes-01':
|
||||
'如果节点有多个出站 IP 地址,请填写 <span class="bold-text">WORKER_IP</span>,以确保使用指定的 IP。可通过命令 <span class="bold-text">hostname -I | xargs -n1</span> 进行确认。',
|
||||
@@ -58,5 +58,24 @@ export default {
|
||||
'clusters.addworker.cambriconNotes':
|
||||
'如果 <span class="bold-text">/usr/local/neuware</span> 目录不存在,请创建指向 Cambricon 安装路径的符号链接:<span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>。',
|
||||
'clusters.addworker.hygonNotes-02':
|
||||
'如果未能检测到设备,请尝试移除 <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>。'
|
||||
'如果未能检测到设备,请尝试移除 <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>。',
|
||||
'clusters.addworker.selectCluster': '选择集群',
|
||||
'clusters.addworker.selectCluster.tips':
|
||||
'非 Docker 集群请前往集群页面注册集群或管理节点池。',
|
||||
'clusters.addworker.selectGPU': '选择 GPU 厂商',
|
||||
'clusters.addworker.checkEnv': '检查环境',
|
||||
'clusters.addworker.specifyArgs': '指定参数',
|
||||
'clusters.addworker.runCommand': '运行指令',
|
||||
'clusters.addworker.specifyWorkerIP': '指定 Worker IP',
|
||||
'clusters.addworker.detectWorkerIP': '自动检测 Worker IP',
|
||||
'clusters.addworker.enterWorkerIP': '输入节点 IP',
|
||||
'clusters.addworker.enterWorkerIP.error': '请输入节点 IP',
|
||||
'clusters.addworker.extraVolume': '额外卷挂载',
|
||||
'clusters.addworker.configSummary': '配置摘要',
|
||||
'clusters.addworker.gpuVendor': 'GPU 厂商',
|
||||
'clusters.addworker.workerIP': '节点 IP',
|
||||
'clusters.addworker.notSpecified': '未指定',
|
||||
'clusters.addworker.autoDetect': '自动检测',
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'例如:/data/models(路径需以 / 开头)'
|
||||
};
|
||||
|
||||
@@ -101,5 +101,6 @@ export default {
|
||||
'resources.worker.maintenance.remark': '备注',
|
||||
'resources.worker.maintenance.remark.rules': '请输入维护备注',
|
||||
'resources.worker.maintenance.tips':
|
||||
'启用维护后,节点将停止调度新的模型实例部署任务,正在运行的实例不会受到影响。'
|
||||
'启用维护后,节点将停止调度新的模型实例部署任务,正在运行的实例不会受到影响。',
|
||||
'resources.worker.noCluster.tips': '当前无可用集群,请先创建集群再添加节点。'
|
||||
};
|
||||
|
||||
@@ -7,6 +7,10 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { PageContainerInner } from '../_components/page-box';
|
||||
import { createCluster, queryClusterToken, queryCredentialList } from './apis';
|
||||
import {
|
||||
DockerStepsFromCluster,
|
||||
K8sStepsFromCluter
|
||||
} from './components/add-worker/config';
|
||||
import ClusterSteps from './components/cluster-steps';
|
||||
import FooterButtons from './components/footer-buttons';
|
||||
import ProviderCatalog from './components/provider-catalog';
|
||||
@@ -198,6 +202,10 @@ const ClusterCreate = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* this function is used to render the modules in the current step
|
||||
* @returns
|
||||
*/
|
||||
const renderModules = () => {
|
||||
const step = steps[currentStep];
|
||||
const moduleKeys = step?.showModules || [];
|
||||
@@ -209,6 +217,11 @@ const ClusterCreate = () => {
|
||||
return ModuleComponent ? (
|
||||
<ModuleComponent
|
||||
key={key}
|
||||
stepList={
|
||||
extraData.provider === ProviderValueMap.Docker
|
||||
? DockerStepsFromCluster
|
||||
: K8sStepsFromCluter
|
||||
}
|
||||
registrationInfo={registrationInfo}
|
||||
provider={extraData.provider}
|
||||
/>
|
||||
@@ -270,19 +283,21 @@ const ClusterCreate = () => {
|
||||
}}
|
||||
>
|
||||
<ClusterSteps steps={steps} currentStep={currentStep}></ClusterSteps>
|
||||
{currentStep === startStep && (
|
||||
<ProviderCatalog
|
||||
dataList={providerList}
|
||||
height={70}
|
||||
onSelect={handleSelectProvider}
|
||||
clickable={true}
|
||||
current={extraData.provider}
|
||||
/>
|
||||
)}
|
||||
{renderModules()}
|
||||
<Container>
|
||||
<Content>{renderForms()}</Content>
|
||||
</Container>
|
||||
<div style={{ width: 800, margin: '0 auto' }}>
|
||||
{currentStep === startStep && (
|
||||
<ProviderCatalog
|
||||
dataList={providerList}
|
||||
height={70}
|
||||
onSelect={handleSelectProvider}
|
||||
clickable={true}
|
||||
current={extraData.provider}
|
||||
/>
|
||||
)}
|
||||
{renderModules()}
|
||||
<Container>
|
||||
<Content>{renderForms()}</Content>
|
||||
</Container>
|
||||
</div>
|
||||
</PageContainerInner>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { clusterSessionAtom, expandKeysAtom } from '@/atoms/clusters';
|
||||
import { expandKeysAtom } from '@/atoms/clusters';
|
||||
import DeleteModal from '@/components/delete-modal';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { FilterBar } from '@/components/page-tools';
|
||||
@@ -9,7 +9,6 @@ import type { PageActionType } from '@/config/types';
|
||||
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import useWatchList from '@/hooks/use-watch-list';
|
||||
import AddWorker from '@/pages/cluster-management/components/add-worker';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Button, message } from 'antd';
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
createWorkerPool,
|
||||
deleteCluster,
|
||||
queryClusterList,
|
||||
queryClusterToken,
|
||||
queryCredentialList,
|
||||
queryWorkerPools,
|
||||
updateCluster,
|
||||
@@ -30,19 +28,19 @@ import {
|
||||
} from './apis';
|
||||
import AddCluster from './components/add-cluster';
|
||||
import AddPool from './components/add-pool';
|
||||
import PoolRows from './components/pool-rows';
|
||||
import RegisterCluster from './components/register-cluster';
|
||||
import {
|
||||
ClusterStatusValueMap,
|
||||
ProviderType,
|
||||
ProviderValueMap
|
||||
} from './config';
|
||||
DockerStepsFromCluster,
|
||||
K8sStepsFromCluter
|
||||
} from './components/add-worker/config';
|
||||
import PoolRows from './components/pool-rows';
|
||||
import { ProviderType, ProviderValueMap } from './config';
|
||||
import {
|
||||
ClusterListItem,
|
||||
ClusterFormData as FormData,
|
||||
ClusterListItem as ListItem,
|
||||
NodePoolFormData
|
||||
} from './config/types';
|
||||
import useAddWorker from './hooks/use-add-worker';
|
||||
import useClusterColumns from './hooks/use-cluster-columns';
|
||||
|
||||
const Clusters: React.FC = () => {
|
||||
@@ -66,50 +64,12 @@ const Clusters: React.FC = () => {
|
||||
});
|
||||
const { watchDataList: allWorkerPoolList } = useWatchList(WORKER_POOLS_API);
|
||||
const [expandAtom] = useAtom(expandKeysAtom);
|
||||
const [clusterSession, setClusterSession] = useAtom(clusterSessionAtom);
|
||||
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
|
||||
useExpandedRowKeys(expandAtom);
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
const [registerClusterStatus, setRegisterClusterStatus] = useState<{
|
||||
open: boolean;
|
||||
provider: ProviderType;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
}>({
|
||||
open: false,
|
||||
provider: null,
|
||||
registrationInfo: {
|
||||
token: '',
|
||||
image: '',
|
||||
server_url: '',
|
||||
cluster_id: 0
|
||||
}
|
||||
});
|
||||
const { handleAddWorker, AddWorkerModal, setStepList } = useAddWorker({});
|
||||
|
||||
const [openAddWorker, setOpenAddWorker] = useState<{
|
||||
open: boolean;
|
||||
provider: ProviderType;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
}>({
|
||||
open: false,
|
||||
provider: null,
|
||||
registrationInfo: {
|
||||
token: '',
|
||||
image: '',
|
||||
server_url: '',
|
||||
cluster_id: 0
|
||||
}
|
||||
});
|
||||
const [openAddModal, setOpenAddModal] = useState<{
|
||||
open: boolean;
|
||||
action: PageActionType;
|
||||
@@ -206,36 +166,6 @@ const Clusters: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddWorker = async (row: ListItem) => {
|
||||
try {
|
||||
const data = await queryClusterToken({ id: row.id });
|
||||
setOpenAddWorker({
|
||||
open: true,
|
||||
provider: row.provider as ProviderType,
|
||||
registrationInfo: {
|
||||
...data,
|
||||
cluster_id: row.id
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
message.error(error.message || 'Failed to fetch cluster token');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegisterCluster = async (row: ListItem) => {
|
||||
try {
|
||||
const info = await queryClusterToken({ id: row.id });
|
||||
setRegisterClusterStatus({
|
||||
open: true,
|
||||
provider: row.provider as ProviderType,
|
||||
registrationInfo: {
|
||||
...info,
|
||||
cluster_id: row.id
|
||||
}
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {
|
||||
if (val === 'edit') {
|
||||
handleEditCluster(row);
|
||||
@@ -243,10 +173,12 @@ const Clusters: React.FC = () => {
|
||||
handleDelete({ ...row, name: row.name });
|
||||
} else if (val === 'add_worker') {
|
||||
handleAddWorker(row);
|
||||
setStepList(DockerStepsFromCluster);
|
||||
} else if (val === 'addPool') {
|
||||
handleAddPool(row);
|
||||
} else if (val === 'register_cluster') {
|
||||
handleRegisterCluster(row);
|
||||
handleAddWorker(row);
|
||||
setStepList(K8sStepsFromCluter);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -308,28 +240,6 @@ const Clusters: React.FC = () => {
|
||||
fetchCredentialList();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* add worker from workers page redirect
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (
|
||||
clusterSession?.firstAddWorker &&
|
||||
dataSource.loadend &&
|
||||
dataSource.dataList?.length > 0
|
||||
) {
|
||||
const targetCluster = dataSource.dataList.find(
|
||||
(cluster) =>
|
||||
cluster.provider === ProviderValueMap.Docker &&
|
||||
cluster.state === ClusterStatusValueMap.Ready
|
||||
);
|
||||
if (targetCluster) {
|
||||
handleAddWorker(targetCluster);
|
||||
// reset session
|
||||
setClusterSession(null);
|
||||
}
|
||||
}
|
||||
}, [clusterSession, dataSource.loadend, dataSource.dataList]);
|
||||
|
||||
const renderChildren = (
|
||||
list: any,
|
||||
options: { parent?: any; [key: string]: any }
|
||||
@@ -421,41 +331,6 @@ const Clusters: React.FC = () => {
|
||||
onCancel={handleModalCancel}
|
||||
onOk={handleModalOk}
|
||||
></AddCluster>
|
||||
<AddWorker
|
||||
open={openAddWorker.open}
|
||||
provider={openAddWorker.provider}
|
||||
onCancel={() =>
|
||||
setOpenAddWorker({
|
||||
open: false,
|
||||
provider: null,
|
||||
registrationInfo: {
|
||||
token: '',
|
||||
image: '',
|
||||
server_url: '',
|
||||
cluster_id: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
registrationInfo={openAddWorker.registrationInfo}
|
||||
></AddWorker>
|
||||
<RegisterCluster
|
||||
title={intl.formatMessage({ id: 'clusters.button.register' })}
|
||||
open={registerClusterStatus.open}
|
||||
provider={registerClusterStatus.provider}
|
||||
registrationInfo={registerClusterStatus.registrationInfo}
|
||||
onCancel={() => {
|
||||
setRegisterClusterStatus({
|
||||
open: false,
|
||||
provider: null,
|
||||
registrationInfo: {
|
||||
token: '',
|
||||
image: '',
|
||||
server_url: '',
|
||||
cluster_id: 0
|
||||
}
|
||||
});
|
||||
}}
|
||||
></RegisterCluster>
|
||||
<AddPool
|
||||
provider={addPoolStatus.provider}
|
||||
open={addPoolStatus.open}
|
||||
@@ -475,6 +350,7 @@ const Clusters: React.FC = () => {
|
||||
onOk={handleSubmitWorkerPool}
|
||||
></AddPool>
|
||||
<DeleteModal ref={modalRef}></DeleteModal>
|
||||
{AddWorkerModal}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
type ViewModalProps = {
|
||||
currentGPU?: string;
|
||||
workerIP?: string;
|
||||
modelDir?: string;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
@@ -15,19 +16,25 @@ type ViewModalProps = {
|
||||
const AddWorkerCommand: React.FC<ViewModalProps> = ({
|
||||
registrationInfo,
|
||||
workerIP,
|
||||
modelDir,
|
||||
currentGPU
|
||||
}) => {
|
||||
const code = React.useMemo(() => {
|
||||
const commandCode = addWorkerGuide['all'];
|
||||
return commandCode?.registerWorker({
|
||||
gpu: currentGPU || '',
|
||||
server: registrationInfo.server_url || origin,
|
||||
tag: '',
|
||||
workerIP: '${WORKER_IP}',
|
||||
image: registrationInfo.image,
|
||||
token: registrationInfo.token || '${token}'
|
||||
});
|
||||
}, [registrationInfo, currentGPU, workerIP]);
|
||||
return commandCode
|
||||
?.registerWorker({
|
||||
gpu: currentGPU || '',
|
||||
server: registrationInfo.server_url || origin,
|
||||
tag: '',
|
||||
workerIP: workerIP,
|
||||
modelDir: modelDir,
|
||||
image: registrationInfo.image,
|
||||
token: registrationInfo.token || '${token}'
|
||||
})
|
||||
?.trim()
|
||||
.replace(/\\\s+$/gm, '')
|
||||
.replace(/\\+$/, '');
|
||||
}, [registrationInfo, currentGPU, workerIP, modelDir]);
|
||||
|
||||
return (
|
||||
<HighlightCode
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import AlertInfoBlock from '@/components/alert-info/block';
|
||||
import {
|
||||
AddWorkerDockerNotes,
|
||||
GPUDriverMap
|
||||
} from '@/pages/resources/config/gpu-driver';
|
||||
import { BulbOutlined, ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Alert } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ProviderType, ProviderValueMap } from '../config';
|
||||
import AddWorkerCommand from './add-worker-command';
|
||||
import CheckEnvCommand from './check-env-command';
|
||||
import RegisterClusterInner from './register-cluster-inner';
|
||||
import SupportedGPUs from './support-gpus';
|
||||
|
||||
const StyledTag = styled.span`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const NotesWrapper = styled.ol`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
font-weight: 400;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
line-height: 1.25;
|
||||
counter-reset: main 2 sub 0;
|
||||
li {
|
||||
counter-increment: sub;
|
||||
margin-left: 20px !important;
|
||||
&::marker {
|
||||
content: counters(main, '.') '.' counter(sub) ' ';
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Line = styled.div`
|
||||
position: relative;
|
||||
margin: 24px 0;
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--ant-color-border);
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
left: 24px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: rotate(45deg);
|
||||
background-color: var(--ant-color-bg-elevated);
|
||||
border-top: 1px solid var(--ant-color-border);
|
||||
border-left: 1px solid var(--ant-color-border);
|
||||
}
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
.command-info {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
type AddModalProps = {
|
||||
provider: ProviderType;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
};
|
||||
const AddWorkerStep: React.FC<AddModalProps> = ({
|
||||
provider,
|
||||
registrationInfo
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [currentGPU, setCurrentGPU] = React.useState<string>(
|
||||
GPUDriverMap.NVIDIA
|
||||
);
|
||||
const [workerCommand, setWorkerCommand] = React.useState<Record<string, any>>(
|
||||
{
|
||||
label: 'NVIDIA',
|
||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#nvidia-cuda',
|
||||
notes: AddWorkerDockerNotes[GPUDriverMap.NVIDIA]
|
||||
}
|
||||
);
|
||||
|
||||
const handleSelectProvider = (value: string, item: any) => {
|
||||
console.log('selected gpu driver:', value, item);
|
||||
setCurrentGPU(value);
|
||||
setWorkerCommand(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SupportedGPUs
|
||||
onSelect={handleSelectProvider}
|
||||
current={currentGPU}
|
||||
clickable={true}
|
||||
/>
|
||||
|
||||
<Content>
|
||||
<Line></Line>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
icon={<BulbOutlined />}
|
||||
message={
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: intl.formatMessage(
|
||||
{ id: 'clusters.create.addworker.tips' },
|
||||
{ label: workerCommand.label, link: workerCommand.link }
|
||||
)
|
||||
}}
|
||||
></span>
|
||||
}
|
||||
></Alert>
|
||||
</Content>
|
||||
|
||||
<div className="command-info">
|
||||
<StyledTag>1.</StyledTag>
|
||||
{intl.formatMessage({ id: 'cluster.create.checkEnv.tips' })}
|
||||
</div>
|
||||
<CheckEnvCommand provider={provider} currentGPU={currentGPU} />
|
||||
|
||||
{provider === ProviderValueMap.Kubernetes ? (
|
||||
<>
|
||||
<div className="command-info">
|
||||
<StyledTag>2.</StyledTag>
|
||||
{intl.formatMessage({ id: 'clusters.create.register.tips' })}
|
||||
</div>
|
||||
<RegisterClusterInner registrationInfo={registrationInfo} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="command-info">
|
||||
<StyledTag>2.</StyledTag>
|
||||
{intl.formatMessage({ id: 'clusters.create.addCommand.tips' })}
|
||||
</div>
|
||||
<AlertInfoBlock
|
||||
maxHeight={200}
|
||||
style={{ marginBottom: 8 }}
|
||||
type="warning"
|
||||
icon={<ExclamationCircleFilled />}
|
||||
message={
|
||||
workerCommand.notes?.length > 0 ? (
|
||||
<NotesWrapper>
|
||||
{workerCommand.notes.map((note: string, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: intl.formatMessage({ id: note })
|
||||
}}
|
||||
></li>
|
||||
))}
|
||||
</NotesWrapper>
|
||||
) : null
|
||||
}
|
||||
></AlertInfoBlock>
|
||||
<AddWorkerCommand
|
||||
registrationInfo={registrationInfo}
|
||||
currentGPU={currentGPU}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddWorkerStep;
|
||||
@@ -1,43 +0,0 @@
|
||||
import ScrollerModal from '@/components/scroller-modal';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import React from 'react';
|
||||
import { ProviderType } from '../config';
|
||||
import AddWorkerStep from './add-worker-step';
|
||||
|
||||
type ViewModalProps = {
|
||||
open: boolean;
|
||||
provider: ProviderType;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||
const { open, onCancel, registrationInfo, provider } = props || {};
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ScrollerModal
|
||||
title={intl.formatMessage({ id: 'resources.button.create' })}
|
||||
open={open}
|
||||
centered={true}
|
||||
onCancel={onCancel}
|
||||
destroyOnHidden={true}
|
||||
closeIcon={true}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
width={860}
|
||||
style={{}}
|
||||
maxContentHeight={'max(calc(100vh - 200px), 600px)'}
|
||||
footer={null}
|
||||
>
|
||||
<AddWorkerStep registrationInfo={registrationInfo} provider={provider} />
|
||||
</ScrollerModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddWorker;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import { ProviderType } from '../../config';
|
||||
import { ClusterListItem } from '../../config/types';
|
||||
import { SummaryDataKey } from './config';
|
||||
|
||||
interface AddWorkerContextProps {
|
||||
clusterList?: Global.BaseOption<number, ClusterListItem>[];
|
||||
provider: ProviderType;
|
||||
stepList: string[];
|
||||
onClusterChange?: (value: number, row?: any) => void;
|
||||
collapseKey: Set<string>;
|
||||
onToggle: (open: boolean, key: string) => void;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
registerField: (key: SummaryDataKey) => () => void;
|
||||
updateField: (key: SummaryDataKey, value: any) => void;
|
||||
summary: Map<string, any>;
|
||||
}
|
||||
|
||||
export const AddWorkerContext = createContext<AddWorkerContextProps | null>({
|
||||
summary: new Map()
|
||||
} as AddWorkerContextProps);
|
||||
|
||||
export const useAddWorkerContext = () => {
|
||||
const context = useContext(AddWorkerContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useAddWorkerContext must be used within an AddWorkerContext.Provider'
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
import AlertInfoBlock from '@/components/alert-info/block';
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ProviderType, ProviderValueMap } from '../../config';
|
||||
import { ClusterListItem } from '../../config/types';
|
||||
import { AddWorkerContext } from './add-worker-context';
|
||||
import CheckEnvironment from './check-environment';
|
||||
import { StepName, StepNamesMap } from './config';
|
||||
import DockerRunCommand from './docker-run-command';
|
||||
import K8sRunCommand from './k8s-run-command';
|
||||
import SelectCluster from './select-cluster';
|
||||
import SelectVendor from './select-vendor';
|
||||
import SpecifyArguments from './specify-arguments';
|
||||
import useSummaryStatus from './use-summary-status';
|
||||
|
||||
const Container = styled.div`
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
.command-info {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
/**
|
||||
* clusterList and onClusterChange are only required when from worker page.
|
||||
*/
|
||||
type AddWorkerProps = {
|
||||
provider: ProviderType;
|
||||
clusterList?: Global.BaseOption<number, ClusterListItem>[];
|
||||
stepList: StepName[];
|
||||
onClusterChange?: (value: number, row?: any) => void;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* both add worker and register cluster use this component
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
||||
const {
|
||||
registrationInfo,
|
||||
provider,
|
||||
clusterList,
|
||||
stepList = [],
|
||||
onClusterChange
|
||||
} = props || {};
|
||||
const intl = useIntl();
|
||||
const { update, summary, register } = useSummaryStatus();
|
||||
|
||||
const [collapseKey, setCollapseKey] = React.useState<Set<string>>(
|
||||
new Set([stepList[0]])
|
||||
);
|
||||
|
||||
const onToggle = (open: boolean, key: string) => {
|
||||
setCollapseKey(open ? new Set([key]) : new Set());
|
||||
};
|
||||
|
||||
const handleOnClusterChange = (value: number, row?: any) => {
|
||||
onClusterChange?.(value, row);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
// reset collapseKey when stepList changes
|
||||
setCollapseKey(new Set([stepList[0]]));
|
||||
}, [stepList]);
|
||||
|
||||
return (
|
||||
<AddWorkerContext.Provider
|
||||
value={{
|
||||
clusterList,
|
||||
provider,
|
||||
stepList: stepList,
|
||||
collapseKey,
|
||||
onToggle,
|
||||
onClusterChange: handleOnClusterChange,
|
||||
registrationInfo,
|
||||
summary,
|
||||
registerField: register,
|
||||
updateField: update
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
{stepList.includes(StepNamesMap.SelectCluster) && (
|
||||
<SelectCluster></SelectCluster>
|
||||
)}
|
||||
{stepList.includes(StepNamesMap.SelectCluster) &&
|
||||
!clusterList?.length && (
|
||||
<AlertInfoBlock
|
||||
maxHeight={200}
|
||||
style={{ marginBottom: 8 }}
|
||||
type="warning"
|
||||
icon={<ExclamationCircleFilled />}
|
||||
message={intl.formatMessage({
|
||||
id: 'resources.worker.noCluster.tips'
|
||||
})}
|
||||
></AlertInfoBlock>
|
||||
)}
|
||||
|
||||
{/* render the steps only when there is at least one cluster available or cluster selection is not required */}
|
||||
{((clusterList && clusterList.length > 0) ||
|
||||
!stepList.includes(StepNamesMap.SelectCluster)) && (
|
||||
<>
|
||||
<SelectVendor></SelectVendor>
|
||||
<CheckEnvironment></CheckEnvironment>
|
||||
|
||||
{provider === ProviderValueMap.Kubernetes && (
|
||||
<K8sRunCommand></K8sRunCommand>
|
||||
)}
|
||||
|
||||
{provider === ProviderValueMap.Docker && (
|
||||
<>
|
||||
<SpecifyArguments></SpecifyArguments>
|
||||
<DockerRunCommand></DockerRunCommand>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</AddWorkerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddWorkerSteps;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { BulbOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Alert } from 'antd';
|
||||
import CheckEnvCommand from '../check-env-command';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { StepNamesMap } from './config';
|
||||
import { Tips, Title } from './constainers';
|
||||
import StepCollapse from './step-collapse';
|
||||
|
||||
const CheckEnvironment = () => {
|
||||
const { stepList, summary, provider } = useAddWorkerContext();
|
||||
const intl = useIntl();
|
||||
const currentGPU = summary.get('currentGPU');
|
||||
const workerCommand = summary.get('workerCommand') || {
|
||||
label: '',
|
||||
link: '',
|
||||
notes: []
|
||||
};
|
||||
|
||||
const stepIndex = stepList.indexOf(StepNamesMap.CheckEnv) + 1;
|
||||
|
||||
return (
|
||||
<StepCollapse
|
||||
name={StepNamesMap.CheckEnv}
|
||||
title={
|
||||
<Title>
|
||||
{stepIndex}.{' '}
|
||||
{intl.formatMessage({ id: 'clusters.addworker.checkEnv' })}
|
||||
</Title>
|
||||
}
|
||||
>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
icon={<BulbOutlined />}
|
||||
style={{
|
||||
marginBottom: 8
|
||||
}}
|
||||
message={
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: intl.formatMessage(
|
||||
{ id: 'clusters.create.addworker.tips' },
|
||||
{ label: workerCommand.label, link: workerCommand.link }
|
||||
)
|
||||
}}
|
||||
></span>
|
||||
}
|
||||
></Alert>
|
||||
<Tips style={{ marginBottom: 8, color: 'var(--ant-color-text)' }}>
|
||||
{intl.formatMessage({ id: 'cluster.create.checkEnv.tips' })}
|
||||
</Tips>
|
||||
<CheckEnvCommand provider={provider} currentGPU={currentGPU} />
|
||||
</StepCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckEnvironment;
|
||||
@@ -0,0 +1,56 @@
|
||||
export const StepNamesMap = {
|
||||
SelectCluster: 'SelectCluster',
|
||||
SelectGPU: 'SelectGPU',
|
||||
CheckEnv: 'CheckEnv',
|
||||
SpecifyArgs: 'SpecifyArgs',
|
||||
RunCommand: 'RunCommand'
|
||||
} as const;
|
||||
|
||||
export type StepName = keyof typeof StepNamesMap;
|
||||
|
||||
export const DockerStepsFromCluster = [
|
||||
StepNamesMap.SelectGPU,
|
||||
StepNamesMap.CheckEnv,
|
||||
StepNamesMap.SpecifyArgs,
|
||||
StepNamesMap.RunCommand
|
||||
];
|
||||
|
||||
export const DockerStepsFromWorker = [
|
||||
StepNamesMap.SelectCluster,
|
||||
StepNamesMap.SelectGPU,
|
||||
StepNamesMap.CheckEnv,
|
||||
StepNamesMap.SpecifyArgs,
|
||||
StepNamesMap.RunCommand
|
||||
];
|
||||
|
||||
export const K8sStepsFromCluter = [
|
||||
StepNamesMap.SelectGPU,
|
||||
StepNamesMap.CheckEnv,
|
||||
StepNamesMap.RunCommand
|
||||
];
|
||||
|
||||
export interface SummaryDataKeys {
|
||||
currentGPU: string;
|
||||
cluster_id: number;
|
||||
clusterName: string;
|
||||
workerCommand: {
|
||||
label: string;
|
||||
link: string;
|
||||
notes: string[];
|
||||
};
|
||||
modelDirConfig: {
|
||||
enabled: boolean;
|
||||
path: string;
|
||||
};
|
||||
workerIPConfig: {
|
||||
enabled: boolean;
|
||||
ip: string;
|
||||
required: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type SummaryDataMap = {
|
||||
[key in keyof SummaryDataKeys]: SummaryDataKeys[key];
|
||||
};
|
||||
|
||||
export type SummaryDataKey = keyof SummaryDataKeys;
|
||||
@@ -0,0 +1,79 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Title = styled.div`
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
export const ConfigWrapper = styled.div`
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--ant-color-split);
|
||||
border-radius: var(--ant-border-radius);
|
||||
background-color: var(--ant-color-fill-tertiary);
|
||||
background: var(--ant-color-info-bg);
|
||||
border: var(--ant-line-width) var(--ant-line-type)
|
||||
var(--ant-color-info-border);
|
||||
.config-content {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.label {
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SwitchWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
padding: 12px;
|
||||
gap: 8px;
|
||||
.tips {
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Tips = styled.div`
|
||||
margin-top: 0px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
`;
|
||||
|
||||
export const NotesWrapper = styled.ol`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
font-weight: 400;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
line-height: 1.25;
|
||||
li {
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
.command-info {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Content = styled.div`
|
||||
margin-top: 16px;
|
||||
`;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useIntl } from '@umijs/max';
|
||||
import AddWorkerCommand from '../add-worker-command';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { StepNamesMap } from './config';
|
||||
import { Tips, Title } from './constainers';
|
||||
import StepCollapse from './step-collapse';
|
||||
import SummaryData from './summary-data';
|
||||
import VendorNotes from './vendor-notes';
|
||||
|
||||
const DockerRunCommand = () => {
|
||||
const intl = useIntl();
|
||||
const { registrationInfo, stepList, summary, clusterList } =
|
||||
useAddWorkerContext();
|
||||
const workerIPConfig = summary.get('workerIPConfig') || {
|
||||
enable: false,
|
||||
ip: '',
|
||||
required: false
|
||||
};
|
||||
const modelDirConfig = summary.get('modelDirConfig') || {
|
||||
enable: false,
|
||||
path: '',
|
||||
required: false
|
||||
};
|
||||
const currentGPU = summary.get('currentGPU') || '';
|
||||
|
||||
const stepIndex = stepList.indexOf(StepNamesMap.RunCommand) + 1;
|
||||
|
||||
return (
|
||||
<StepCollapse
|
||||
name={StepNamesMap.RunCommand}
|
||||
title={
|
||||
<Title>
|
||||
{stepIndex}.{' '}
|
||||
{intl.formatMessage({ id: 'clusters.addworker.runCommand' })}
|
||||
</Title>
|
||||
}
|
||||
>
|
||||
<SummaryData></SummaryData>
|
||||
<VendorNotes></VendorNotes>
|
||||
<Tips
|
||||
style={{
|
||||
marginBottom: 8,
|
||||
color: 'var(--ant-color-text)'
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'clusters.create.addCommand.tips'
|
||||
})}
|
||||
</Tips>
|
||||
<AddWorkerCommand
|
||||
registrationInfo={registrationInfo}
|
||||
workerIP={workerIPConfig.enable ? workerIPConfig.ip : ''}
|
||||
modelDir={modelDirConfig.enable ? modelDirConfig.path : ''}
|
||||
currentGPU={currentGPU}
|
||||
/>
|
||||
</StepCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default DockerRunCommand;
|
||||
@@ -0,0 +1,110 @@
|
||||
import ScrollerModal from '@/components/scroller-modal';
|
||||
import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { queryClusterToken } from '../../apis';
|
||||
import { ProviderType } from '../../config';
|
||||
import { ClusterListItem } from '../../config/types';
|
||||
import AddWorkerStep from './add-worker-step';
|
||||
import { StepName } from './config';
|
||||
|
||||
const Container = styled.div`
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
.command-info {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
type AddWorkerProps = {
|
||||
open: boolean;
|
||||
provider: ProviderType;
|
||||
title: string;
|
||||
clusterList?: Global.BaseOption<number, ClusterListItem>[];
|
||||
stepList: StepName[];
|
||||
onClusterChange?: (value: number, row?: any) => void;
|
||||
onCancel: () => void;
|
||||
cluster_id: number;
|
||||
registrationInfo?: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* both add worker and register cluster use this component
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
||||
const {
|
||||
open,
|
||||
onCancel,
|
||||
provider,
|
||||
cluster_id,
|
||||
title,
|
||||
clusterList,
|
||||
stepList = []
|
||||
} = props || {};
|
||||
const firstLoad = React.useRef(true);
|
||||
const [registrationInfo, setRegistrationInfo] = React.useState<{
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
}>({
|
||||
token: '',
|
||||
image: '',
|
||||
server_url: '',
|
||||
cluster_id: 0
|
||||
});
|
||||
|
||||
const handleOnClusterChange = async (value: number, row?: any) => {
|
||||
try {
|
||||
const data = await queryClusterToken({ id: value });
|
||||
firstLoad.current = false;
|
||||
setRegistrationInfo({
|
||||
...data,
|
||||
cluster_id: value
|
||||
});
|
||||
} catch (error) {
|
||||
firstLoad.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open && cluster_id && firstLoad.current) {
|
||||
handleOnClusterChange(cluster_id);
|
||||
}
|
||||
}, [open, cluster_id]);
|
||||
|
||||
return (
|
||||
<ScrollerModal
|
||||
title={title}
|
||||
open={open}
|
||||
centered={true}
|
||||
onCancel={onCancel}
|
||||
destroyOnHidden={true}
|
||||
closeIcon={true}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
width={860}
|
||||
style={{}}
|
||||
maxContentHeight={'max(calc(100vh - 200px), 600px)'}
|
||||
footer={null}
|
||||
>
|
||||
<AddWorkerStep
|
||||
stepList={stepList}
|
||||
provider={provider}
|
||||
clusterList={clusterList}
|
||||
onClusterChange={handleOnClusterChange}
|
||||
registrationInfo={registrationInfo}
|
||||
></AddWorkerStep>
|
||||
</ScrollerModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddWorker;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useIntl } from '@umijs/max';
|
||||
import RegisterClusterInner from '../register-cluster-inner';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { StepNamesMap } from './config';
|
||||
import { Tips, Title } from './constainers';
|
||||
import StepCollapse from './step-collapse';
|
||||
|
||||
const K8sRunCommand = () => {
|
||||
const { registrationInfo, stepList } = useAddWorkerContext();
|
||||
const intl = useIntl();
|
||||
|
||||
const stepIndex = stepList.indexOf(StepNamesMap.RunCommand) + 1;
|
||||
|
||||
return (
|
||||
<StepCollapse
|
||||
name={StepNamesMap.RunCommand}
|
||||
title={
|
||||
<Title>
|
||||
{stepIndex}.{' '}
|
||||
{intl.formatMessage({ id: 'clusters.addworker.runCommand' })}
|
||||
</Title>
|
||||
}
|
||||
>
|
||||
<Tips
|
||||
style={{
|
||||
marginBottom: 8,
|
||||
color: 'var(--ant-color-text)'
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'clusters.create.addCommand.tips'
|
||||
})}
|
||||
</Tips>
|
||||
<RegisterClusterInner registrationInfo={registrationInfo} />
|
||||
</StepCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default K8sRunCommand;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Input, Switch } from 'antd';
|
||||
import React from 'react';
|
||||
import { SwitchWrapper, Tips } from './constainers';
|
||||
|
||||
const NetworkConfig = () => {
|
||||
const intl = useIntl();
|
||||
const [networkInterface, setNetworkInterface] = React.useState<{
|
||||
enable: boolean;
|
||||
name?: string;
|
||||
}>({
|
||||
enable: false,
|
||||
name: ''
|
||||
});
|
||||
return (
|
||||
<SwitchWrapper>
|
||||
<div className="button">
|
||||
<span style={{ color: 'var(--ant-color-text)', fontWeight: 500 }}>
|
||||
Network Interface
|
||||
</span>
|
||||
<Switch
|
||||
checked={networkInterface.enable}
|
||||
onChange={(checked) =>
|
||||
setNetworkInterface({
|
||||
...networkInterface,
|
||||
enable: checked
|
||||
})
|
||||
}
|
||||
></Switch>
|
||||
</div>
|
||||
<Tips>
|
||||
Enter the NIC name to use for distributed inference (e.g., mlx5_0).
|
||||
</Tips>
|
||||
{networkInterface.enable && (
|
||||
<>
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
placeholder="Enter network interface"
|
||||
onChange={(e) =>
|
||||
setNetworkInterface({
|
||||
...networkInterface,
|
||||
name: e.target.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SwitchWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkConfig;
|
||||
@@ -0,0 +1,69 @@
|
||||
import BaseSelect from '@/components/seal-form/base/select';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useEffect } from 'react';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { StepNamesMap } from './config';
|
||||
import { Title } from './constainers';
|
||||
import StepCollapse from './step-collapse';
|
||||
|
||||
const SelectCluster = () => {
|
||||
const {
|
||||
clusterList,
|
||||
registrationInfo,
|
||||
stepList,
|
||||
summary,
|
||||
onClusterChange,
|
||||
registerField,
|
||||
updateField
|
||||
} = useAddWorkerContext();
|
||||
const intl = useIntl();
|
||||
|
||||
const clusterId = summary.get('cluster_id');
|
||||
|
||||
const stepIndex = stepList.indexOf(StepNamesMap.SelectCluster) + 1;
|
||||
|
||||
useEffect(() => {
|
||||
const unregister = registerField('cluster_id');
|
||||
return () => {
|
||||
unregister();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateField('cluster_id', registrationInfo.cluster_id);
|
||||
// update cluster name in summary
|
||||
const selectedCluster = clusterList?.find(
|
||||
(item) => item.value === registrationInfo.cluster_id
|
||||
);
|
||||
updateField('clusterName', selectedCluster?.label || '');
|
||||
}, [registrationInfo.cluster_id]);
|
||||
|
||||
return (
|
||||
<StepCollapse
|
||||
name={StepNamesMap.SelectCluster}
|
||||
title={
|
||||
<div>
|
||||
<Title>
|
||||
{stepIndex}.{' '}
|
||||
{intl.formatMessage({ id: 'clusters.addworker.selectCluster' })}
|
||||
</Title>
|
||||
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
|
||||
{intl.formatMessage({
|
||||
id: 'clusters.addworker.selectCluster.tips'
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<BaseSelect
|
||||
defaultValue={registrationInfo.cluster_id}
|
||||
options={clusterList}
|
||||
value={clusterId}
|
||||
onChange={onClusterChange}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</StepCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectCluster;
|
||||
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
AddWorkerDockerNotes,
|
||||
GPUDriverMap
|
||||
} from '@/pages/resources/config/gpu-driver';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import React, { useEffect } from 'react';
|
||||
import SupportedGPUs from '../support-gpus';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { StepNamesMap } from './config';
|
||||
import { Title } from './constainers';
|
||||
import StepCollapse from './step-collapse';
|
||||
|
||||
const SelectVendor = () => {
|
||||
const { stepList, registerField, updateField } = useAddWorkerContext();
|
||||
const intl = useIntl();
|
||||
|
||||
const stepIndex = stepList.indexOf(StepNamesMap.SelectGPU) + 1;
|
||||
const [currentGPU, setCurrentGPU] = React.useState<string>(
|
||||
GPUDriverMap.NVIDIA
|
||||
);
|
||||
|
||||
const handleSelectProvider = (value: string, item: any) => {
|
||||
console.log('selected gpu driver:', value, item);
|
||||
setCurrentGPU(value);
|
||||
|
||||
updateField('currentGPU', value);
|
||||
updateField('workerCommand', item);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const unregisterField = registerField('currentGPU');
|
||||
return () => {
|
||||
unregisterField();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unregisterField = registerField('workerCommand');
|
||||
return () => {
|
||||
unregisterField();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateField('currentGPU', GPUDriverMap.NVIDIA);
|
||||
updateField('workerCommand', {
|
||||
label: 'NVIDIA',
|
||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#nvidia-cuda',
|
||||
notes: AddWorkerDockerNotes[GPUDriverMap.NVIDIA]
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StepCollapse
|
||||
name={StepNamesMap.SelectGPU}
|
||||
title={
|
||||
<Title>
|
||||
{stepIndex}.{' '}
|
||||
{intl.formatMessage({ id: 'clusters.addworker.selectGPU' })}
|
||||
</Title>
|
||||
}
|
||||
>
|
||||
<SupportedGPUs
|
||||
onSelect={handleSelectProvider}
|
||||
current={currentGPU}
|
||||
clickable={true}
|
||||
/>
|
||||
</StepCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectVendor;
|
||||
@@ -0,0 +1,227 @@
|
||||
import AlertInfoBlock from '@/components/alert-info/block';
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Input, Switch } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { StepNamesMap } from './config';
|
||||
import { NotesWrapper, SwitchWrapper, Tips, Title } from './constainers';
|
||||
import StepCollapse from './step-collapse';
|
||||
|
||||
const SpecifyArguments = () => {
|
||||
const intl = useIntl();
|
||||
const { stepList, summary, updateField, registerField } =
|
||||
useAddWorkerContext();
|
||||
const stepIndex = stepList.indexOf(StepNamesMap.SpecifyArgs) + 1;
|
||||
|
||||
const workerIPConfig = summary.get('workerIPConfig') || {
|
||||
enable: false,
|
||||
ip: '',
|
||||
required: false
|
||||
};
|
||||
const modelDirConfig = summary.get('modelDirConfig') || {
|
||||
enable: false,
|
||||
path: ''
|
||||
};
|
||||
|
||||
const setWorkerIPConfig = (config: {
|
||||
enable: boolean;
|
||||
ip?: string;
|
||||
required?: boolean;
|
||||
}) => {
|
||||
updateField('workerIPConfig', {
|
||||
...workerIPConfig,
|
||||
...config
|
||||
});
|
||||
};
|
||||
|
||||
const setModelDirConfig = (config: { enable: boolean; path?: string }) => {
|
||||
updateField('modelDirConfig', {
|
||||
...modelDirConfig,
|
||||
...config
|
||||
});
|
||||
};
|
||||
|
||||
const beforeNext = async () => {
|
||||
if (workerIPConfig.enable && !workerIPConfig.ip) {
|
||||
setWorkerIPConfig({
|
||||
...workerIPConfig,
|
||||
required: true
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const unregister = registerField('workerIPConfig');
|
||||
return () => {
|
||||
unregister();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unregister = registerField('modelDirConfig');
|
||||
return () => {
|
||||
unregister();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateField('workerIPConfig', {
|
||||
enable: true,
|
||||
ip: '',
|
||||
required: false
|
||||
});
|
||||
|
||||
updateField('modelDirConfig', {
|
||||
enable: false,
|
||||
path: ''
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StepCollapse
|
||||
beforeNext={beforeNext}
|
||||
name={StepNamesMap.SpecifyArgs}
|
||||
title={
|
||||
<Title>
|
||||
{stepIndex}.{' '}
|
||||
{intl.formatMessage({ id: 'clusters.addworker.specifyArgs' })}
|
||||
</Title>
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '12px',
|
||||
marginBottom: 8
|
||||
}}
|
||||
>
|
||||
{/* worker IP config */}
|
||||
<SwitchWrapper>
|
||||
<div className="button">
|
||||
<span style={{ color: 'var(--ant-color-text)', fontWeight: 500 }}>
|
||||
{workerIPConfig.enable
|
||||
? intl.formatMessage({
|
||||
id: 'clusters.addworker.specifyWorkerIP'
|
||||
})
|
||||
: intl.formatMessage({
|
||||
id: 'clusters.addworker.detectWorkerIP'
|
||||
})}
|
||||
</span>
|
||||
<Switch
|
||||
checked={workerIPConfig.enable}
|
||||
onChange={(checked) =>
|
||||
setWorkerIPConfig({
|
||||
...workerIPConfig,
|
||||
enable: checked,
|
||||
required: false
|
||||
})
|
||||
}
|
||||
></Switch>
|
||||
</div>
|
||||
{workerIPConfig.enable && (
|
||||
<>
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'clusters.addworker.enterWorkerIP'
|
||||
})}
|
||||
value={workerIPConfig.ip}
|
||||
onChange={(e) =>
|
||||
setWorkerIPConfig({
|
||||
...workerIPConfig,
|
||||
ip: e.target.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
{workerIPConfig.required && !workerIPConfig.ip && (
|
||||
<Tips
|
||||
style={{
|
||||
color: 'var(--ant-color-error)'
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'clusters.addworker.enterWorkerIP.error'
|
||||
})}
|
||||
</Tips>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!workerIPConfig.enable && (
|
||||
<AlertInfoBlock
|
||||
maxHeight={200}
|
||||
contentStyle={{
|
||||
paddingLeft: 0
|
||||
}}
|
||||
style={{ marginBottom: 8 }}
|
||||
type="warning"
|
||||
icon={<ExclamationCircleFilled />}
|
||||
message={
|
||||
<NotesWrapper>
|
||||
<li
|
||||
style={{
|
||||
marginLeft: '0 !important',
|
||||
listStyleType: 'none'
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: intl.formatMessage({
|
||||
id: 'clusters.addworker.nvidiaNotes-01'
|
||||
})
|
||||
}}
|
||||
></li>
|
||||
</NotesWrapper>
|
||||
}
|
||||
></AlertInfoBlock>
|
||||
)}
|
||||
</SwitchWrapper>
|
||||
{/* model directory config */}
|
||||
<SwitchWrapper>
|
||||
<div className="button">
|
||||
<span style={{ color: 'var(--ant-color-text)', fontWeight: 500 }}>
|
||||
{/* optional */}
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'clusters.addworker.extraVolume' })}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<Switch
|
||||
checked={modelDirConfig.enable}
|
||||
onChange={(checked) =>
|
||||
setModelDirConfig({ ...modelDirConfig, enable: checked })
|
||||
}
|
||||
></Switch>
|
||||
</div>
|
||||
<Tips
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: intl.formatMessage({
|
||||
id: 'clusters.addworker.nvidiaNotes-02'
|
||||
})
|
||||
}}
|
||||
></Tips>
|
||||
{modelDirConfig.enable && (
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
value={modelDirConfig.path}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'clusters.addworker.extraVolume.holder'
|
||||
})}
|
||||
onChange={(e) =>
|
||||
setModelDirConfig({
|
||||
...modelDirConfig,
|
||||
path: e.target.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SwitchWrapper>
|
||||
</div>
|
||||
</StepCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecifyArguments;
|
||||
@@ -0,0 +1,84 @@
|
||||
import CollapsibleContainer from '@/components/collapse-container';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
|
||||
interface StepItemProps {
|
||||
title: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
name: string;
|
||||
beforeNext?: () => Promise<boolean> | void;
|
||||
}
|
||||
|
||||
const Box = styled.div`
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: 4px;
|
||||
&.step-collapse-open {
|
||||
border-color: var(--ant-color-primary);
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StepCollapse: React.FC<StepItemProps> = ({
|
||||
title,
|
||||
children,
|
||||
name = '',
|
||||
beforeNext = async () => true,
|
||||
...rest
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { collapseKey, onToggle, stepList } = useAddWorkerContext();
|
||||
|
||||
const handleOnNext = async () => {
|
||||
const res = await beforeNext?.();
|
||||
if (!res) return;
|
||||
// find the next step and open it
|
||||
const nextName = stepList[stepList.indexOf(name) + 1];
|
||||
onToggle(true, nextName);
|
||||
};
|
||||
|
||||
const isLastStep = stepList.indexOf(name) === stepList.length - 1;
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={
|
||||
collapseKey?.has(name) ? 'step-collapse-open' : 'step-collapse'
|
||||
}
|
||||
>
|
||||
<CollapsibleContainer
|
||||
collapsible={true}
|
||||
open={collapseKey?.has(name)}
|
||||
iconPosition="right"
|
||||
styles={{
|
||||
body: collapseKey?.has(name) ? { padding: 16 } : {},
|
||||
content: { paddingTop: 0 },
|
||||
header: {
|
||||
backgroundColor: 'unset'
|
||||
}
|
||||
}}
|
||||
title={title}
|
||||
onToggle={(open) => onToggle?.(open, name || '')}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{!isLastStep && (
|
||||
<ButtonWrapper>
|
||||
<Button type="primary" onClick={handleOnNext}>
|
||||
{intl.formatMessage({ id: 'common.button.next' })}
|
||||
</Button>
|
||||
</ButtonWrapper>
|
||||
)}
|
||||
</CollapsibleContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepCollapse;
|
||||
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
StopOutlined,
|
||||
WarningOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import React from 'react';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { StepNamesMap } from './config';
|
||||
import { ConfigWrapper, Title } from './constainers';
|
||||
|
||||
const SummaryData: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { summary, stepList } = useAddWorkerContext();
|
||||
const clusterName = summary.get('clusterName') || '';
|
||||
const workerCommand = summary.get('workerCommand') || {
|
||||
label: '',
|
||||
link: '',
|
||||
notes: []
|
||||
};
|
||||
const workerIPConfig = summary.get('workerIPConfig') || {
|
||||
enable: false,
|
||||
ip: ''
|
||||
};
|
||||
const modelDirConfig = summary.get('modelDirConfig') || {
|
||||
enable: false,
|
||||
path: ''
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfigWrapper>
|
||||
<Title>
|
||||
{intl.formatMessage({ id: 'clusters.addworker.configSummary' })}
|
||||
</Title>
|
||||
<div className="config-content">
|
||||
{stepList.includes(StepNamesMap.SelectCluster) && (
|
||||
<div className="item">
|
||||
<span className="label">
|
||||
{intl.formatMessage({ id: 'clusters.title' })}:
|
||||
</span>
|
||||
<span className="value">
|
||||
{clusterName}
|
||||
{clusterName ? (
|
||||
<CheckCircleOutlined
|
||||
style={{
|
||||
color: 'var(--ant-color-success)',
|
||||
marginLeft: 4
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<WarningOutlined
|
||||
style={{
|
||||
color: 'var(--ant-color-warning)',
|
||||
marginLeft: 4
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="item">
|
||||
<span className="label">
|
||||
{intl.formatMessage({ id: 'clusters.addworker.gpuVendor' })}:
|
||||
</span>
|
||||
<span className="value">
|
||||
{/* for checked style */}
|
||||
{workerCommand.label}
|
||||
<CheckCircleOutlined
|
||||
style={{
|
||||
color: 'var(--ant-color-success)',
|
||||
marginLeft: 4
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="item">
|
||||
<span className="label">
|
||||
{intl.formatMessage({ id: 'clusters.addworker.workerIP' })}:
|
||||
</span>
|
||||
<span className="value">
|
||||
{/* for invalidate style */}
|
||||
{workerIPConfig.enable
|
||||
? workerIPConfig.ip
|
||||
? workerIPConfig.ip
|
||||
: intl.formatMessage({ id: 'clusters.addworker.notSpecified' })
|
||||
: intl.formatMessage({ id: 'clusters.addworker.autoDetect' })}
|
||||
|
||||
{workerIPConfig.enable && !workerIPConfig.ip && (
|
||||
<WarningOutlined
|
||||
style={{
|
||||
color: 'var(--ant-color-warning)',
|
||||
marginLeft: 4
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(!workerIPConfig.enable || workerIPConfig.ip) && (
|
||||
<CheckCircleOutlined
|
||||
style={{
|
||||
color: 'var(--ant-color-success)',
|
||||
marginLeft: 4
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="item">
|
||||
<span className="label">
|
||||
{intl.formatMessage({ id: 'clusters.addworker.extraVolume' })}:
|
||||
</span>
|
||||
<span className="value">
|
||||
{modelDirConfig.enable && modelDirConfig.path
|
||||
? modelDirConfig.path
|
||||
: ''}
|
||||
{(!modelDirConfig.path || !modelDirConfig.enable) && (
|
||||
<StopOutlined
|
||||
style={{
|
||||
color: 'var(--ant-color-text-tertiary)'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{modelDirConfig.enable && modelDirConfig.path && (
|
||||
<CheckCircleOutlined
|
||||
style={{
|
||||
color: 'var(--ant-color-success)',
|
||||
marginLeft: 4
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ConfigWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SummaryData;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { SummaryDataKey, SummaryDataMap } from './config';
|
||||
|
||||
const useSummaryStatus = () => {
|
||||
const summaryRef = useRef<
|
||||
Map<SummaryDataKey, SummaryDataMap[SummaryDataKey]>
|
||||
>(new Map());
|
||||
const [, forceRender] = useState({});
|
||||
|
||||
// Register a key in the summary map, but do not tigger a render
|
||||
const register = useCallback((key: SummaryDataKey) => {
|
||||
return () => {
|
||||
summaryRef.current.delete(key);
|
||||
forceRender({});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// call it to update a initial value or change value, it will trigger a render
|
||||
const update = useCallback(
|
||||
(key: SummaryDataKey, value: SummaryDataMap[typeof key]) => {
|
||||
const prev = summaryRef.current.get(key);
|
||||
|
||||
if (prev === value) return;
|
||||
|
||||
summaryRef.current.set(key, value);
|
||||
forceRender({});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
summary: summaryRef.current,
|
||||
register,
|
||||
update
|
||||
};
|
||||
};
|
||||
|
||||
export default useSummaryStatus;
|
||||
@@ -0,0 +1,45 @@
|
||||
import AlertInfoBlock from '@/components/alert-info/block';
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useAddWorkerContext } from './add-worker-context';
|
||||
import { NotesWrapper } from './constainers';
|
||||
|
||||
const VendorNotes = () => {
|
||||
const intl = useIntl();
|
||||
const { summary } = useAddWorkerContext();
|
||||
const workerCommand =
|
||||
summary.get('workerCommand') ||
|
||||
({
|
||||
label: 'N/A',
|
||||
notes: []
|
||||
} as { label: string; notes: string[] });
|
||||
|
||||
return (
|
||||
<AlertInfoBlock
|
||||
maxHeight={200}
|
||||
title={`Notes for ${workerCommand.label} Device`}
|
||||
style={{ marginBottom: 8 }}
|
||||
type="warning"
|
||||
contentStyle={{
|
||||
paddingLeft: 16
|
||||
}}
|
||||
icon={<ExclamationCircleFilled />}
|
||||
message={
|
||||
workerCommand.notes?.length > 0 ? (
|
||||
<NotesWrapper>
|
||||
{workerCommand.notes.map((note: string, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: intl.formatMessage({ id: note })
|
||||
}}
|
||||
></li>
|
||||
))}
|
||||
</NotesWrapper>
|
||||
) : null
|
||||
}
|
||||
></AlertInfoBlock>
|
||||
);
|
||||
};
|
||||
|
||||
export default VendorNotes;
|
||||
@@ -19,7 +19,6 @@ const Wrapper = styled.div<{ $cols?: number }>`
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import ScrollerModal from '@/components/scroller-modal/index';
|
||||
import React from 'react';
|
||||
import { ProviderType } from '../config';
|
||||
import AddWorkerStep from './add-worker-step';
|
||||
|
||||
type AddModalProps = {
|
||||
title: string;
|
||||
open: boolean;
|
||||
provider: ProviderType;
|
||||
registrationInfo: {
|
||||
token: string;
|
||||
image: string;
|
||||
server_url: string;
|
||||
cluster_id: number;
|
||||
};
|
||||
onCancel: () => void;
|
||||
};
|
||||
const AddCluster: React.FC<AddModalProps> = ({
|
||||
title,
|
||||
open,
|
||||
provider,
|
||||
registrationInfo,
|
||||
onCancel
|
||||
}) => {
|
||||
const handleCancel = () => {
|
||||
onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollerModal
|
||||
title={title}
|
||||
open={open}
|
||||
onCancel={handleCancel}
|
||||
destroyOnHidden={true}
|
||||
closeIcon={true}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
width={860}
|
||||
footer={false}
|
||||
>
|
||||
<AddWorkerStep registrationInfo={registrationInfo} provider={provider} />
|
||||
</ScrollerModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCluster;
|
||||
@@ -0,0 +1,96 @@
|
||||
import AddWorker from '@/pages/cluster-management/components/add-worker';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { message } from 'antd';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { StepName } from '../components/add-worker/config';
|
||||
import {
|
||||
ClusterStatusValueMap,
|
||||
ProviderType,
|
||||
ProviderValueMap
|
||||
} from '../config';
|
||||
import { ClusterListItem } from '../config/types';
|
||||
|
||||
const useAddWorker = (props: {
|
||||
clusterList?: Global.BaseOption<number, ClusterListItem>[];
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { clusterList } = props || {};
|
||||
const [stepList, setStepList] = useState<StepName[]>([]);
|
||||
const [openAddWorker, setOpenAddWorker] = useState<{
|
||||
open: boolean;
|
||||
provider: ProviderType;
|
||||
title: string;
|
||||
cluster_id: number;
|
||||
}>({
|
||||
open: false,
|
||||
provider: null,
|
||||
title: '',
|
||||
cluster_id: 0
|
||||
});
|
||||
|
||||
const handleAddWorker = async (row: ClusterListItem) => {
|
||||
try {
|
||||
// set the title based on provider type
|
||||
const title =
|
||||
row.provider === ProviderValueMap.Docker
|
||||
? intl.formatMessage({ id: 'resources.button.create' })
|
||||
: intl.formatMessage({ id: 'clusters.button.register' });
|
||||
|
||||
if (!row?.id) return;
|
||||
|
||||
setOpenAddWorker({
|
||||
open: true,
|
||||
title: title,
|
||||
provider: row.provider as ProviderType,
|
||||
cluster_id: row.id
|
||||
});
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || 'Failed to fetch cluster token');
|
||||
}
|
||||
};
|
||||
|
||||
const handleClusterChange = async (value: number, row: ClusterListItem) => {
|
||||
handleAddWorker(row);
|
||||
};
|
||||
|
||||
const clusterDataList = useMemo(() => {
|
||||
return clusterList
|
||||
?.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
disabled:
|
||||
item.state !== ClusterStatusValueMap.Ready ||
|
||||
item.provider !== ProviderValueMap.Docker
|
||||
};
|
||||
})
|
||||
.filter((item) => item.state === ClusterStatusValueMap.Ready);
|
||||
}, [clusterList]);
|
||||
|
||||
const AddWorkerModal = (
|
||||
<AddWorker
|
||||
title={openAddWorker.title}
|
||||
stepList={stepList || []}
|
||||
open={openAddWorker.open}
|
||||
provider={openAddWorker.provider}
|
||||
clusterList={clusterDataList}
|
||||
cluster_id={openAddWorker.cluster_id}
|
||||
onClusterChange={handleClusterChange}
|
||||
onCancel={() =>
|
||||
setOpenAddWorker({
|
||||
open: false,
|
||||
provider: null,
|
||||
title: '',
|
||||
cluster_id: 0
|
||||
})
|
||||
}
|
||||
></AddWorker>
|
||||
);
|
||||
|
||||
return {
|
||||
handleAddWorker,
|
||||
AddWorkerModal,
|
||||
setStepList
|
||||
};
|
||||
};
|
||||
|
||||
export default useAddWorker;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import AddWorkerStep from '../components/add-worker-step';
|
||||
import AddWorkerStep from '../components/add-worker/add-worker-step';
|
||||
import ProviderCatalog from '../components/provider-catalog';
|
||||
import BasicForm from './basic-form';
|
||||
import WorkerPoolForm from './worker-pools-form';
|
||||
|
||||
@@ -25,6 +25,10 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
onClick(data);
|
||||
}, [data, onClick]);
|
||||
|
||||
const handleOnError = (e: any) => {
|
||||
e.target.src = fallbackImg;
|
||||
};
|
||||
|
||||
const description = useMemo(() => {
|
||||
return (
|
||||
<Typography.Paragraph
|
||||
@@ -59,12 +63,13 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
>
|
||||
<div className="content">
|
||||
<div className="title">
|
||||
<div
|
||||
className="img"
|
||||
style={{
|
||||
backgroundImage: `url(${data.icon}),url(${fallbackImg})`
|
||||
}}
|
||||
></div>
|
||||
<div className="img">
|
||||
<img
|
||||
src={data.icon || fallbackImg}
|
||||
alt=""
|
||||
onError={handleOnError}
|
||||
/>
|
||||
</div>
|
||||
<AutoTooltip ghost>{data.name}</AutoTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
height: 32px;
|
||||
border-radius: var(--ant-border-radius);
|
||||
overflow: hidden;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { clusterSessionAtom } from '@/atoms/clusters';
|
||||
import DeleteModal from '@/components/delete-modal';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { FilterBar } from '@/components/page-tools';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import PageBox from '@/pages/_components/page-box';
|
||||
import { queryClusterList } from '@/pages/cluster-management/apis';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { DockerStepsFromWorker } from '@/pages/cluster-management/components/add-worker/config';
|
||||
import {
|
||||
ClusterStatusValueMap,
|
||||
ProviderValueMap
|
||||
} from '@/pages/cluster-management/config';
|
||||
import { ClusterListItem } from '@/pages/cluster-management/config/types';
|
||||
import useAddWorker from '@/pages/cluster-management/hooks/use-add-worker';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Button, ConfigProvider, Table, message } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import NoResult from '../../_components/no-result';
|
||||
import {
|
||||
@@ -46,11 +51,9 @@ const Workers: React.FC = () => {
|
||||
watch: true,
|
||||
API: WORKERS_API
|
||||
});
|
||||
const [, setClusterSession] = useAtom(clusterSessionAtom);
|
||||
const { MaintenanceModal, handleStopMaintenance, setOpenStatus } =
|
||||
useWorkerMaintenance({ fetchData });
|
||||
useWorkerMaintenance({ fetchData: handleSearch });
|
||||
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
const [updateLabelsData, setUpdateLabelsData] = useState<{
|
||||
open: boolean;
|
||||
@@ -60,7 +63,7 @@ const Workers: React.FC = () => {
|
||||
data: {} as ListItem
|
||||
});
|
||||
const [clusterData, setClusterData] = useState<{
|
||||
list: Global.BaseOption<number>[];
|
||||
list: Global.BaseOption<number, ClusterListItem>[];
|
||||
data: Record<number, string>;
|
||||
}>({
|
||||
list: [],
|
||||
@@ -73,6 +76,9 @@ const Workers: React.FC = () => {
|
||||
open: false,
|
||||
currentData: null
|
||||
});
|
||||
const { handleAddWorker, AddWorkerModal, setStepList } = useAddWorker({
|
||||
clusterList: clusterData.list
|
||||
});
|
||||
|
||||
const getClusterList = async () => {
|
||||
try {
|
||||
@@ -89,7 +95,10 @@ const Workers: React.FC = () => {
|
||||
);
|
||||
const list = res?.items?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id
|
||||
value: item.id,
|
||||
id: item.id,
|
||||
state: item.state,
|
||||
provider: item.provider
|
||||
}));
|
||||
setClusterData({
|
||||
list,
|
||||
@@ -170,9 +179,16 @@ const Workers: React.FC = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const handleAddWorker = () => {
|
||||
setClusterSession({ firstAddWorker: true });
|
||||
navigate('/cluster-management/clusters/list');
|
||||
const handleOnAddWorker = () => {
|
||||
let currentData = clusterData.list.find(
|
||||
(item) =>
|
||||
item.provider === ProviderValueMap.Docker &&
|
||||
item.state === ClusterStatusValueMap.Ready
|
||||
);
|
||||
if (!currentData) {
|
||||
currentData = clusterData.list[0];
|
||||
}
|
||||
handleAddWorker(currentData as ClusterListItem);
|
||||
};
|
||||
|
||||
const renderEmpty = (type?: string) => {
|
||||
@@ -188,7 +204,7 @@ const Workers: React.FC = () => {
|
||||
title={intl.formatMessage({ id: 'noresult.workers.title' })}
|
||||
subTitle={intl.formatMessage({ id: 'noresult.workers.subTitle' })}
|
||||
>
|
||||
<Button type="primary" onClick={handleAddWorker}>
|
||||
<Button type="primary" onClick={handleOnAddWorker}>
|
||||
{intl.formatMessage({ id: 'noresult.workers.button.add' })}
|
||||
</Button>
|
||||
</NoResult>
|
||||
@@ -211,6 +227,7 @@ const Workers: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
getClusterList();
|
||||
setStepList(DockerStepsFromWorker);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -225,6 +242,7 @@ const Workers: React.FC = () => {
|
||||
handleDeleteByBatch={handleDeleteBatch}
|
||||
handleSearch={handleSearch}
|
||||
handleSelectChange={handleClusterChange}
|
||||
handleClickPrimary={handleOnAddWorker}
|
||||
handleInputChange={handleNameChange}
|
||||
rowSelection={rowSelection}
|
||||
selectOptions={clusterData.list}
|
||||
@@ -268,6 +286,7 @@ const Workers: React.FC = () => {
|
||||
}
|
||||
/>
|
||||
{MaintenanceModal}
|
||||
{AddWorkerModal}
|
||||
</PageBox>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -96,7 +96,7 @@ const generateHygonDockerEnvCommand = (config: {
|
||||
|
||||
// available for Kubernetes
|
||||
export const generateKubernetesEnvCommand = (gpu: string) => {
|
||||
const config = GPUsConfigs[gpu];
|
||||
const config = GPUsConfigs[gpu] || {};
|
||||
return `kubectl get runtimeclass ${config.runtime} > /dev/null 2>&1 && echo "${config.label} runtimeclass registered" || (echo "${config.label} runtimeclass issue"; exit 1)`;
|
||||
};
|
||||
|
||||
@@ -126,6 +126,20 @@ export const dockerEnvCommandMap = {
|
||||
GPUsConfigs[GPUDriverMap.METAX]
|
||||
)
|
||||
};
|
||||
const setNormalArgs = () => {
|
||||
return `sudo docker run -d --name gpustack-worker \\
|
||||
--restart=unless-stopped \\
|
||||
--privileged \\
|
||||
--network=host \\
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \\
|
||||
--volume gpustack-data:/var/lib/gpustack \\`;
|
||||
};
|
||||
|
||||
const setImageArgs = (params: any) => {
|
||||
return `${params.image} \\
|
||||
--server-url ${params.server} \\
|
||||
--token ${params.token} \\`;
|
||||
};
|
||||
|
||||
// avaliable for NVIDIA、AMD、MThreads
|
||||
const registerWorker = (params: {
|
||||
@@ -135,18 +149,17 @@ const registerWorker = (params: {
|
||||
image: string;
|
||||
gpu: string;
|
||||
workerIP?: string;
|
||||
modelDir?: string;
|
||||
}) => {
|
||||
const config = GPUsConfigs[params.gpu];
|
||||
return `sudo docker run -d --name gpustack-worker \\
|
||||
--restart=unless-stopped \\
|
||||
--privileged \\
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \\
|
||||
--volume gpustack-data:/var/lib/gpustack \\
|
||||
const commonArgs = setNormalArgs();
|
||||
const imageArgs = setImageArgs(params);
|
||||
// remove empty enter lines and trailing backslash
|
||||
return `${commonArgs} \\
|
||||
${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir} \\` : ''}
|
||||
--runtime ${config.runtime} \\
|
||||
${params.image} \\
|
||||
--server-url ${params.server} \\
|
||||
--token ${params.token} \\
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
|
||||
${imageArgs}
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}`;
|
||||
};
|
||||
|
||||
// avaliable for Ascend
|
||||
@@ -157,20 +170,17 @@ const registerAscendWorker = (params: {
|
||||
image: string;
|
||||
gpu: string;
|
||||
workerIP?: string;
|
||||
modelDir?: string;
|
||||
}) => {
|
||||
const config = GPUsConfigs[params.gpu];
|
||||
return `sudo docker run -d --name gpustack-worker \\
|
||||
--restart=unless-stopped \\
|
||||
--privileged \\
|
||||
--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 \\
|
||||
const commonArgs = setNormalArgs();
|
||||
const imageArgs = setImageArgs(params);
|
||||
return `${commonArgs} \\
|
||||
--env "ASCEND_VISIBLE_DEVICES=$(sudo ls /dev/davinci* | head -1 | grep -o '[0-9]\\+' || echo "0") \\
|
||||
--runtime ${config.runtime} \\
|
||||
${params.image} \\
|
||||
--server-url ${params.server} \\
|
||||
--token ${params.token} \\
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
|
||||
${imageArgs}
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}
|
||||
${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir}` : ''}`;
|
||||
};
|
||||
|
||||
const registerHygonWorker = (params: {
|
||||
@@ -180,22 +190,19 @@ const registerHygonWorker = (params: {
|
||||
image: string;
|
||||
gpu: string;
|
||||
workerIP?: string;
|
||||
modelDir?: string;
|
||||
}) => {
|
||||
const config = GPUsConfigs[params.gpu];
|
||||
return `sudo docker run -d --name gpustack-worker \\
|
||||
--restart=unless-stopped \\
|
||||
--privileged \\
|
||||
--network=host \\
|
||||
--env ROCM_PATH=/opt/dtk \\
|
||||
--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib \\
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \\
|
||||
--volume gpustack-data:/var/lib/gpustack \\
|
||||
const commonArgs = setNormalArgs();
|
||||
const imageArgs = setImageArgs(params);
|
||||
return `${commonArgs} \\
|
||||
--volume /opt/hyhal:/opt/hyhal:ro \\
|
||||
--volume /opt/dtk:/opt/dtk:ro \\
|
||||
${params.image} \\
|
||||
--server-url ${params.server} \\
|
||||
--token ${params.token} \\
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
|
||||
--env ROCM_PATH=/opt/dtk \\
|
||||
--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib \\
|
||||
${imageArgs}
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}
|
||||
${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir}` : ''}`;
|
||||
};
|
||||
|
||||
const registerIluvatarWorker = (params: {
|
||||
@@ -205,22 +212,19 @@ const registerIluvatarWorker = (params: {
|
||||
image: string;
|
||||
gpu: string;
|
||||
workerIP?: string;
|
||||
modelDir?: string;
|
||||
}) => {
|
||||
const config = GPUsConfigs[params.gpu];
|
||||
return `sudo docker run -d --name gpustack-worker \\
|
||||
--restart=unless-stopped \\
|
||||
--privileged \\
|
||||
--network=host \\
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \\
|
||||
--volume gpustack-data:/var/lib/gpustack \\
|
||||
const commonArgs = setNormalArgs();
|
||||
const imageArgs = setImageArgs(params);
|
||||
return `${commonArgs} \\
|
||||
--volume /lib/modules:/lib/modules:ro \\
|
||||
--volume /usr/local/corex:/usr/local/corex:ro \\
|
||||
--volume /usr/bin/ixsmi:/usr/bin/ixsmi \\
|
||||
--runtime ${config.runtime} \\
|
||||
${params.image} \\
|
||||
--server-url ${params.server} \\
|
||||
--token ${params.token} \\
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
|
||||
${imageArgs}
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}
|
||||
${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir}` : ''}`;
|
||||
};
|
||||
|
||||
const registerMetaXWorker = (params: {
|
||||
@@ -230,20 +234,17 @@ const registerMetaXWorker = (params: {
|
||||
image: string;
|
||||
gpu: string;
|
||||
workerIP?: string;
|
||||
modelDir?: string;
|
||||
}) => {
|
||||
const config = GPUsConfigs[params.gpu];
|
||||
return `sudo docker run -d --name gpustack-worker \\
|
||||
--restart=unless-stopped \\
|
||||
--privileged \\
|
||||
--network=host \\
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \\
|
||||
--volume gpustack-data:/var/lib/gpustack \\
|
||||
const commonArgs = setNormalArgs();
|
||||
const imageArgs = setImageArgs(params);
|
||||
return `${commonArgs} \\
|
||||
--volume /opt/mxdriver:/opt/mxdriver:ro \\
|
||||
--volume /opt/maca:/opt/maca:ro \\
|
||||
${params.image} \\
|
||||
--server-url ${params.server} \\
|
||||
--token ${params.token} \\
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
|
||||
${imageArgs}
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}
|
||||
${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir}` : ''}`;
|
||||
};
|
||||
|
||||
const registerCambriconWorker = (params: {
|
||||
@@ -253,20 +254,17 @@ const registerCambriconWorker = (params: {
|
||||
image: string;
|
||||
gpu: string;
|
||||
workerIP?: string;
|
||||
modelDir?: string;
|
||||
}) => {
|
||||
const config = GPUsConfigs[params.gpu];
|
||||
return `sudo docker run -d --name gpustack-worker \\
|
||||
--restart=unless-stopped \\
|
||||
--privileged \\
|
||||
--network=host \\
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \\
|
||||
--volume gpustack-data:/var/lib/gpustack \\
|
||||
const commonArgs = setNormalArgs();
|
||||
const imageArgs = setImageArgs(params);
|
||||
return `${commonArgs} \\
|
||||
--volume /usr/local/neuware:/usr/local/neuware:ro \\
|
||||
--volume /usr/bin/cnmon:/usr/bin/cnmon \\
|
||||
${params.image} \\
|
||||
--server-url ${params.server} \\
|
||||
--token ${params.token} \\
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP}` : ''}`.trim();
|
||||
${imageArgs}
|
||||
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}
|
||||
${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir}` : ''}`;
|
||||
};
|
||||
|
||||
export const registerAddWokerCommandMap = {
|
||||
@@ -281,41 +279,15 @@ export const registerAddWokerCommandMap = {
|
||||
};
|
||||
|
||||
export const AddWorkerDockerNotes: Record<string, string[]> = {
|
||||
[GPUDriverMap.NVIDIA]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02'
|
||||
],
|
||||
[GPUDriverMap.AMD]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02'
|
||||
],
|
||||
[GPUDriverMap.MOORE_THREADS]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02'
|
||||
],
|
||||
[GPUDriverMap.ASCEND]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02'
|
||||
],
|
||||
[GPUDriverMap.NVIDIA]: [],
|
||||
[GPUDriverMap.AMD]: [],
|
||||
[GPUDriverMap.MOORE_THREADS]: [],
|
||||
[GPUDriverMap.ASCEND]: [],
|
||||
[GPUDriverMap.HYGON]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02',
|
||||
'clusters.addworker.hygonNotes',
|
||||
'clusters.addworker.hygonNotes-02'
|
||||
],
|
||||
[GPUDriverMap.ILUVATAR]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02',
|
||||
'clusters.addworker.corexNotes'
|
||||
],
|
||||
[GPUDriverMap.CAMBRICON]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02',
|
||||
'clusters.addworker.cambriconNotes'
|
||||
],
|
||||
[GPUDriverMap.METAX]: [
|
||||
'clusters.addworker.nvidiaNotes-01',
|
||||
'clusters.addworker.nvidiaNotes-02',
|
||||
'clusters.addworker.metaxNotes'
|
||||
]
|
||||
[GPUDriverMap.ILUVATAR]: ['clusters.addworker.corexNotes'],
|
||||
[GPUDriverMap.CAMBRICON]: ['clusters.addworker.cambriconNotes'],
|
||||
[GPUDriverMap.METAX]: ['clusters.addworker.metaxNotes']
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user