fix: no clusters tips in adding worker modal

This commit is contained in:
jialin
2026-01-06 17:21:32 +08:00
parent f8ae7680ad
commit 0ba8300959
18 changed files with 91 additions and 63 deletions
@@ -27,6 +27,12 @@ const CardStyled = styled(Card)`
} }
} }
} }
&.disabled {
.ant-card-head {
cursor: not-allowed;
background-color: var(--ant-color-fill-quaternary) !important;
}
}
`; `;
const useStyles = createStyles(({ css, token }) => { const useStyles = createStyles(({ css, token }) => {
+2 -1
View File
@@ -115,5 +115,6 @@ export default {
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Specify the server URL accessible from your cloud provider.', 'Specify the server URL accessible from your cloud provider.',
'clusters.form.setDefault': 'Set as Default', 'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.' 'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available clusters'
}; };
+3 -1
View File
@@ -115,7 +115,8 @@ export default {
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Specify the server URL accessible from your cloud provider.', 'Specify the server URL accessible from your cloud provider.',
'clusters.form.setDefault': 'Set as Default', 'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.' 'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available clusters'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -209,4 +210,5 @@ export default {
// 84. 'clusters.form.setDefault.tips': 'Default for deployment', // 84. 'clusters.form.setDefault.tips': 'Default for deployment',
// 85. 'clusters.addworker.enterWorkerAddress': 'Enter worker external address', // 85. 'clusters.addworker.enterWorkerAddress': 'Enter worker external address',
// 86. 'clusters.addworker.enterWorkerAddress.error': 'Please enter the worker external address.', // 86. 'clusters.addworker.enterWorkerAddress.error': 'Please enter the worker external address.',
// 87. 'clusters.addworker.noClusters': 'No available clusters'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+3 -1
View File
@@ -116,9 +116,11 @@ export default {
'Укажите URL сервера, доступный из вашего облачного провайдера.', 'Укажите URL сервера, доступный из вашего облачного провайдера.',
'clusters.form.setDefault': 'Установить по умолчанию', 'clusters.form.setDefault': 'Установить по умолчанию',
'clusters.form.setDefault.tips': 'clusters.form.setDefault.tips':
'Использовать по умолчанию для развертывания.' 'Использовать по умолчанию для развертывания.',
'clusters.addworker.noClusters': 'No available clusters'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'clusters.addworker.hygonNotes': `If <span class="bold-text">/opt/hyhal</span> or <span class="bold-text">/opt/dtk</span> does not exist, create symbolic links pointing to the corresponding Hygon installation paths, for example: <span class="desc-fill">ln -s /path/to/hyhal /opt/hyhal</span> <span class="desc-fill">ln -s /path/to/dtk /opt/dtk</span>.`, // 1. 'clusters.addworker.hygonNotes': `If <span class="bold-text">/opt/hyhal</span> or <span class="bold-text">/opt/dtk</span> does not exist, create symbolic links pointing to the corresponding Hygon installation paths, for example: <span class="desc-fill">ln -s /path/to/hyhal /opt/hyhal</span> <span class="desc-fill">ln -s /path/to/dtk /opt/dtk</span>.`,
// 2. 'clusters.addworker.noClusters': 'No available clusters'
// ================================================================ // ================================================================
+2 -1
View File
@@ -112,5 +112,6 @@ export default {
'clusters.table.ip.external': '外', 'clusters.table.ip.external': '外',
'clusters.form.serverUrl.tips': '指定可从您的云服务提供商访问的服务器地址。', 'clusters.form.serverUrl.tips': '指定可从您的云服务提供商访问的服务器地址。',
'clusters.form.setDefault': '设为默认', 'clusters.form.setDefault': '设为默认',
'clusters.form.setDefault.tips': '部署时的默认集群。' 'clusters.form.setDefault.tips': '部署时的默认集群。',
'clusters.addworker.noClusters': '无可用集群'
}; };
@@ -3,7 +3,7 @@ import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker
import { ExclamationCircleFilled } from '@ant-design/icons'; import { ExclamationCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Alert } from 'antd'; import { Alert } from 'antd';
import React from 'react'; import React, { useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { ProviderType, ProviderValueMap } from '../../config'; import { ProviderType, ProviderValueMap } from '../../config';
import { ClusterListItem } from '../../config/types'; import { ClusterListItem } from '../../config/types';
@@ -107,6 +107,12 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
); );
}; };
const disabled = useMemo(() => {
return (
stepList.includes(StepNamesMap.SelectCluster) && !clusterList?.length
);
}, [clusterList, stepList, StepNamesMap]);
return ( return (
<AddWorkerContext.Provider <AddWorkerContext.Provider
value={{ value={{
@@ -127,7 +133,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
> >
<Container> <Container>
{stepList.includes(StepNamesMap.SelectCluster) && ( {stepList.includes(StepNamesMap.SelectCluster) && (
<SelectCluster></SelectCluster> <SelectCluster disabled={disabled}></SelectCluster>
)} )}
{stepList.includes(StepNamesMap.SelectCluster) && {stepList.includes(StepNamesMap.SelectCluster) &&
!clusterList?.length && ( !clusterList?.length && (
@@ -141,22 +147,21 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
})} })}
></AlertBlockInfo> ></AlertBlockInfo>
)} )}
{/* render the steps only when there is at least one cluster available or cluster selection is not required */} {/* render the steps only when there is at least one cluster available or cluster selection is not required */}
{((clusterList && clusterList.length > 0) || {((clusterList && clusterList.length > 0) ||
!stepList.includes(StepNamesMap.SelectCluster)) && ( !stepList.includes(StepNamesMap.SelectCluster)) && (
<> <>
<SelectVendor></SelectVendor> <SelectVendor disabled={disabled}></SelectVendor>
<CheckEnvironment></CheckEnvironment> <CheckEnvironment disabled={disabled}></CheckEnvironment>
{provider === ProviderValueMap.Kubernetes && ( {provider === ProviderValueMap.Kubernetes && (
<K8sRunCommand></K8sRunCommand> <K8sRunCommand disabled={disabled}></K8sRunCommand>
)} )}
{provider === ProviderValueMap.Docker && ( {provider === ProviderValueMap.Docker && (
<> <>
<SpecifyArguments></SpecifyArguments> <SpecifyArguments disabled={disabled}></SpecifyArguments>
<DockerRunCommand></DockerRunCommand> <DockerRunCommand disabled={disabled}></DockerRunCommand>
</> </>
)} )}
</> </>
@@ -1,13 +1,13 @@
import { BulbOutlined } from '@ant-design/icons'; import { BulbOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Alert } from 'antd'; import { Alert, Typography } from 'antd';
import CheckEnvCommand from '../check-env-command'; import CheckEnvCommand from '../check-env-command';
import { useAddWorkerContext } from './add-worker-context'; import { useAddWorkerContext } from './add-worker-context';
import { StepNamesMap } from './config'; import { AddWorkerStepProps, StepNamesMap } from './config';
import { Tips, Title } from './constainers'; import { Title } from './constainers';
import StepCollapse from './step-collapse'; import StepCollapse from './step-collapse';
const CheckEnvironment = () => { const CheckEnvironment: React.FC<AddWorkerStepProps> = ({ disabled }) => {
const { stepList, summary, provider } = useAddWorkerContext(); const { stepList, summary, provider } = useAddWorkerContext();
const intl = useIntl(); const intl = useIntl();
const currentGPU = summary.get('currentGPU'); const currentGPU = summary.get('currentGPU');
@@ -21,6 +21,7 @@ const CheckEnvironment = () => {
return ( return (
<StepCollapse <StepCollapse
disabled={disabled}
name={StepNamesMap.CheckEnv} name={StepNamesMap.CheckEnv}
title={ title={
<Title> <Title>
@@ -47,9 +48,9 @@ const CheckEnvironment = () => {
></span> ></span>
} }
></Alert> ></Alert>
<Tips style={{ marginBottom: 8, color: 'var(--ant-color-text)' }}> <Typography.Paragraph style={{ marginBottom: 8 }}>
{intl.formatMessage({ id: 'cluster.create.checkEnv.tips' })} {intl.formatMessage({ id: 'cluster.create.checkEnv.tips' })}
</Tips> </Typography.Paragraph>
<CheckEnvCommand provider={provider} currentGPU={currentGPU} /> <CheckEnvCommand provider={provider} currentGPU={currentGPU} />
</StepCollapse> </StepCollapse>
); );
@@ -71,3 +71,7 @@ export type SummaryDataMap = {
}; };
export type SummaryDataKey = keyof SummaryDataKeys; export type SummaryDataKey = keyof SummaryDataKeys;
export interface AddWorkerStepProps {
disabled?: boolean;
}
@@ -48,7 +48,7 @@ export const SwitchWrapper = styled.div`
export const Tips = styled.div` export const Tips = styled.div`
margin-top: 0px; margin-top: 0px;
color: var(--ant-color-text-secondary); color: var(--ant-color-text-tertiary);
`; `;
export const NotesWrapper = styled.ol` export const NotesWrapper = styled.ol`
@@ -1,13 +1,14 @@
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Typography } from 'antd';
import AddWorkerCommand from '../add-worker-command'; import AddWorkerCommand from '../add-worker-command';
import { useAddWorkerContext } from './add-worker-context'; import { useAddWorkerContext } from './add-worker-context';
import { StepNamesMap } from './config'; import { AddWorkerStepProps, StepNamesMap } from './config';
import { Tips, Title } from './constainers'; import { Title } from './constainers';
import StepCollapse from './step-collapse'; import StepCollapse from './step-collapse';
import SummaryData from './summary-data'; import SummaryData from './summary-data';
import VendorNotes from './vendor-notes'; import VendorNotes from './vendor-notes';
const DockerRunCommand = () => { const DockerRunCommand: React.FC<AddWorkerStepProps> = ({ disabled }) => {
const intl = useIntl(); const intl = useIntl();
const { registrationInfo, stepList, summary } = useAddWorkerContext(); const { registrationInfo, stepList, summary } = useAddWorkerContext();
const workerIPConfig = summary.get('workerIPConfig') || { const workerIPConfig = summary.get('workerIPConfig') || {
@@ -44,6 +45,7 @@ const DockerRunCommand = () => {
return ( return (
<StepCollapse <StepCollapse
disabled={disabled}
name={StepNamesMap.RunCommand} name={StepNamesMap.RunCommand}
title={ title={
<Title> <Title>
@@ -54,16 +56,15 @@ const DockerRunCommand = () => {
> >
<SummaryData></SummaryData> <SummaryData></SummaryData>
<VendorNotes></VendorNotes> <VendorNotes></VendorNotes>
<Tips <Typography.Paragraph
style={{ style={{
marginBottom: 8, marginBottom: 8
color: 'var(--ant-color-text)'
}} }}
> >
{intl.formatMessage({ {intl.formatMessage({
id: 'clusters.create.addCommand.tips' id: 'clusters.create.addCommand.tips'
})} })}
</Tips> </Typography.Paragraph>
<AddWorkerCommand <AddWorkerCommand
registrationInfo={registrationInfo} registrationInfo={registrationInfo}
advertisAddress={ advertisAddress={
@@ -1,11 +1,12 @@
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Typography } from 'antd';
import RegisterClusterInner from '../register-cluster-inner'; import RegisterClusterInner from '../register-cluster-inner';
import { useAddWorkerContext } from './add-worker-context'; import { useAddWorkerContext } from './add-worker-context';
import { StepNamesMap } from './config'; import { AddWorkerStepProps, StepNamesMap } from './config';
import { Tips, Title } from './constainers'; import { Title } from './constainers';
import StepCollapse from './step-collapse'; import StepCollapse from './step-collapse';
const K8sRunCommand = () => { const K8sRunCommand: React.FC<AddWorkerStepProps> = ({ disabled }) => {
const { registrationInfo, stepList } = useAddWorkerContext(); const { registrationInfo, stepList } = useAddWorkerContext();
const intl = useIntl(); const intl = useIntl();
@@ -13,6 +14,7 @@ const K8sRunCommand = () => {
return ( return (
<StepCollapse <StepCollapse
disabled={disabled}
name={StepNamesMap.RunCommand} name={StepNamesMap.RunCommand}
title={ title={
<Title> <Title>
@@ -21,16 +23,15 @@ const K8sRunCommand = () => {
</Title> </Title>
} }
> >
<Tips <Typography.Paragraph
style={{ style={{
marginBottom: 8, marginBottom: 8
color: 'var(--ant-color-text)'
}} }}
> >
{intl.formatMessage({ {intl.formatMessage({
id: 'clusters.create.addCommand.tips' id: 'clusters.create.addCommand.tips'
})} })}
</Tips> </Typography.Paragraph>
<RegisterClusterInner registrationInfo={registrationInfo} /> <RegisterClusterInner registrationInfo={registrationInfo} />
</StepCollapse> </StepCollapse>
); );
@@ -1,7 +1,7 @@
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Input, Switch } from 'antd'; import { Input, Switch } from 'antd';
import React from 'react'; import React from 'react';
import { SwitchWrapper, Tips } from './constainers'; import { SwitchWrapper } from './constainers';
const NetworkConfig = () => { const NetworkConfig = () => {
const intl = useIntl(); const intl = useIntl();
@@ -28,9 +28,6 @@ const NetworkConfig = () => {
} }
></Switch> ></Switch>
</div> </div>
<Tips>
Enter the NIC name to use for distributed inference (e.g., mlx5_0).
</Tips>
{networkInterface.enable && ( {networkInterface.enable && (
<> <>
<Input <Input
@@ -1,13 +1,13 @@
import BaseSelect from '@/components/seal-form/base/select'; import BaseSelect from '@/components/seal-form/base/select';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Spin } from 'antd'; import { Spin, Typography } from 'antd';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useAddWorkerContext } from './add-worker-context'; import { useAddWorkerContext } from './add-worker-context';
import { StepNamesMap } from './config'; import { AddWorkerStepProps, StepNamesMap } from './config';
import { Title } from './constainers'; import { Title } from './constainers';
import StepCollapse from './step-collapse'; import StepCollapse from './step-collapse';
const SelectCluster = () => { const SelectCluster: React.FC<AddWorkerStepProps> = ({ disabled }) => {
const { const {
clusterList, clusterList,
clusterLoading, clusterLoading,
@@ -42,6 +42,7 @@ const SelectCluster = () => {
return ( return (
<StepCollapse <StepCollapse
disabled={disabled}
name={StepNamesMap.SelectCluster} name={StepNamesMap.SelectCluster}
title={ title={
<div> <div>
@@ -65,6 +66,11 @@ const SelectCluster = () => {
onChange={onClusterChange} onChange={onClusterChange}
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
{!clusterLoading && !clusterList?.length && (
<Typography.Text type="danger">
{intl.formatMessage({ id: 'clusters.addworker.noClusters' })}
</Typography.Text>
)}
</Spin> </Spin>
</StepCollapse> </StepCollapse>
); );
@@ -6,11 +6,11 @@ import { useIntl } from '@umijs/max';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import SupportedGPUs from '../support-gpus'; import SupportedGPUs from '../support-gpus';
import { useAddWorkerContext } from './add-worker-context'; import { useAddWorkerContext } from './add-worker-context';
import { StepNamesMap } from './config'; import { AddWorkerStepProps, StepNamesMap } from './config';
import { Title } from './constainers'; import { Title } from './constainers';
import StepCollapse from './step-collapse'; import StepCollapse from './step-collapse';
const SelectVendor = () => { const SelectVendor: React.FC<AddWorkerStepProps> = ({ disabled }) => {
const { stepList, registerField, updateField } = useAddWorkerContext(); const { stepList, registerField, updateField } = useAddWorkerContext();
const intl = useIntl(); const intl = useIntl();
@@ -52,6 +52,7 @@ const SelectVendor = () => {
return ( return (
<StepCollapse <StepCollapse
disabled={disabled}
name={StepNamesMap.SelectGPU} name={StepNamesMap.SelectGPU}
title={ title={
<Title> <Title>
@@ -1,12 +1,12 @@
import AlertInfoBlock from '@/components/alert-info/block'; import AlertInfoBlock from '@/components/alert-info/block';
import { ExclamationCircleFilled } from '@ant-design/icons'; import { ExclamationCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Input, Switch } from 'antd'; import { Input, Switch, Typography } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useAddWorkerContext } from './add-worker-context'; import { useAddWorkerContext } from './add-worker-context';
import { StepNamesMap } from './config'; import { AddWorkerStepProps, StepNamesMap } from './config';
import { NotesWrapper, SwitchWrapper, Tips, Title } from './constainers'; import { NotesWrapper, SwitchWrapper, Title } from './constainers';
import StepCollapse from './step-collapse'; import StepCollapse from './step-collapse';
const ButtonWrapper = styled.div` const ButtonWrapper = styled.div`
@@ -47,14 +47,16 @@ const SwitchSetting: React.FC<{
{showSwitch && <Switch checked={checked} onChange={onChange}></Switch>} {showSwitch && <Switch checked={checked} onChange={onChange}></Switch>}
</ButtonWrapper> </ButtonWrapper>
{tips && ( {tips && (
<Tips <Typography.Text type="secondary">
dangerouslySetInnerHTML={{ <div
__html: tips dangerouslySetInnerHTML={{
}} __html: tips
></Tips> }}
></div>
</Typography.Text>
)} )}
{checked && ( {checked && (
<> <div>
<Input <Input
style={{ width: '100%' }} style={{ width: '100%' }}
value={value} value={value}
@@ -62,22 +64,16 @@ const SwitchSetting: React.FC<{
onChange={(e) => onInputChange?.(e.target.value)} onChange={(e) => onInputChange?.(e.target.value)}
/> />
{errorMessage && ( {errorMessage && (
<Tips <Typography.Text type="danger">{errorMessage}</Typography.Text>
style={{
color: 'var(--ant-color-error)'
}}
>
{errorMessage}
</Tips>
)} )}
</> </div>
)} )}
{extra} {extra}
</SwitchWrapper> </SwitchWrapper>
); );
}; };
const SpecifyArguments = () => { const SpecifyArguments: React.FC<AddWorkerStepProps> = ({ disabled }) => {
const intl = useIntl(); const intl = useIntl();
const { stepList, summary, updateField, registerField } = const { stepList, summary, updateField, registerField } =
useAddWorkerContext(); useAddWorkerContext();
@@ -215,6 +211,7 @@ const SpecifyArguments = () => {
return ( return (
<StepCollapse <StepCollapse
disabled={disabled}
beforeNext={beforeNext} beforeNext={beforeNext}
name={StepNamesMap.SpecifyArgs} name={StepNamesMap.SpecifyArgs}
title={ title={
@@ -9,6 +9,7 @@ interface StepItemProps {
title: React.ReactNode; title: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
name: string; name: string;
disabled?: boolean;
beforeNext?: () => Promise<boolean> | void; beforeNext?: () => Promise<boolean> | void;
} }
@@ -39,6 +40,7 @@ const StepCollapse: React.FC<StepItemProps> = ({
title, title,
children, children,
name = '', name = '',
disabled = false,
beforeNext = async () => true, beforeNext = async () => true,
...rest ...rest
}) => { }) => {
@@ -82,6 +84,7 @@ const StepCollapse: React.FC<StepItemProps> = ({
} }
}} }}
title={title} title={title}
disabled={disabled}
onToggle={(open) => onToggle?.(open, name || '')} onToggle={(open) => onToggle?.(open, name || '')}
{...rest} {...rest}
> >
@@ -95,7 +98,7 @@ const StepCollapse: React.FC<StepItemProps> = ({
)} )}
{!isLastStep && ( {!isLastStep && (
<Button type="primary" onClick={handleOnNext}> <Button type="primary" onClick={handleOnNext} disabled={disabled}>
{intl.formatMessage({ id: 'common.button.next' })} {intl.formatMessage({ id: 'common.button.next' })}
</Button> </Button>
)} )}
@@ -7,7 +7,7 @@ type AddModalProps = {
token: string; token: string;
image: string; image: string;
server_url: string; server_url: string;
cluster_id: number; cluster_id: number | null;
}; };
}; };
const AddCluster: React.FC<AddModalProps> = ({ registrationInfo }) => { const AddCluster: React.FC<AddModalProps> = ({ registrationInfo }) => {
@@ -98,7 +98,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
icon: ( icon: (
<IconFont <IconFont
type="icon-amd-logo" type="icon-amd-logo"
style={{ fontSize: 60, color: 'var(--ant-color-text)' }} style={{ fontSize: 64, color: 'var(--ant-color-text)' }}
/> />
) )
}, },