diff --git a/src/components/scroller-modal/index.tsx b/src/components/scroller-modal/index.tsx index d6dc3061..2cff9642 100644 --- a/src/components/scroller-modal/index.tsx +++ b/src/components/scroller-modal/index.tsx @@ -12,6 +12,15 @@ const Wrapper = styled.div<{ $maxHeight?: number | string }>` width: 100%; `; +const Title = styled.div` + display: flex; + align-items: center; + max-width: 360px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +`; + const ScrollerModal = ( props: ModalProps & { maxContentHeight?: number | string } ) => { @@ -48,6 +57,7 @@ const ScrollerModal = ( return ( {props.title}} destroyOnHidden={true} styles={{ content: { diff --git a/src/locales/en-US/clusters.ts b/src/locales/en-US/clusters.ts index 655bc725..dbf92c84 100644 --- a/src/locales/en-US/clusters.ts +++ b/src/locales/en-US/clusters.ts @@ -68,15 +68,19 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.checkEnv': 'Check Environment', 'clusters.addworker.specifyArgs': 'Specify Arguments', 'clusters.addworker.runCommand': 'Run Command', - 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP', + 'clusters.addworker.specifyWorkerIP': 'Worker IP', 'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP', - 'clusters.addworker.specifyWorkerAddress': 'Specify Worker External Address', - 'clusters.addworker.detectWorkerAddress': - 'Specify Worker External Address (default to Worker IP)', + 'clusters.addworker.specifyWorkerAddress': 'Worker External Address', + 'clusters.addworker.detectWorkerAddress': 'Worker External Address', + 'clusters.addworker.detectWorkerAddress.tips': + 'Defaults to Worker IP if not specified.', 'clusters.addworker.externalIP.tips': 'If running in a VPC or private network, please specify the Worker external address reachable by the GPUStack Server.', 'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', + 'clusters.addworker.enterWorkerAddress': 'Enter worker external address', + 'clusters.addworker.enterWorkerAddress.error': + 'Please enter the worker external address.', 'clusters.addworker.extraVolume': 'Additional Volume Mount', 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', 'clusters.addworker.cacheVolume.tips': diff --git a/src/locales/ja-JP/clusters.ts b/src/locales/ja-JP/clusters.ts index 72db62e5..afde3a11 100644 --- a/src/locales/ja-JP/clusters.ts +++ b/src/locales/ja-JP/clusters.ts @@ -68,15 +68,19 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.checkEnv': 'Check Environment', 'clusters.addworker.specifyArgs': 'Specify Arguments', 'clusters.addworker.runCommand': 'Run Command', - 'clusters.addworker.specifyWorkerIP': 'Worker IP を指定', + 'clusters.addworker.specifyWorkerIP': 'Worker IP', 'clusters.addworker.detectWorkerIP': 'Worker IP を自動検出', - 'clusters.addworker.specifyWorkerAddress': 'Worker 外部アドレスを指定', - 'clusters.addworker.detectWorkerAddress': - 'Worker 外部アドレスを指定(デフォルトはWorker IP)', + 'clusters.addworker.specifyWorkerAddress': 'Worker 外部アドレス', + 'clusters.addworker.detectWorkerAddress': 'Worker 外部アドレス', + 'clusters.addworker.detectWorkerAddress.tips': + '指定がない場合、デフォルトで Worker IP が使用されます。', 'clusters.addworker.externalIP.tips': 'VPCやプライベートネットワークで動作している場合は、サーバーから到達可能なWorkerの外部アドレスを指定してください。', 'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', + 'clusters.addworker.enterWorkerAddress': 'Enter worker external address', + 'clusters.addworker.enterWorkerAddress.error': + 'Please enter the worker external address.', 'clusters.addworker.extraVolume': 'Additional Volume Mount', 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', 'clusters.addworker.cacheVolume.tips': @@ -172,7 +176,7 @@ Same applies to the /opt/dtk directory.`, // 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', +// 57. 'clusters.addworker.specifyWorkerIP': '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.', @@ -203,5 +207,7 @@ Same applies to the /opt/dtk directory.`, // 81. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.', // 82. 'clusters.addworker.externalIP.tips': 'Specify an external IP if the worker is in a VPC or private network.', // 83. 'clusters.form.setDefault': 'Set as 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', +// 86. 'clusters.addworker.enterWorkerAddress.error': 'Please enter the worker external address.', // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/clusters.ts b/src/locales/ru-RU/clusters.ts index 09e5ec3c..5223d743 100644 --- a/src/locales/ru-RU/clusters.ts +++ b/src/locales/ru-RU/clusters.ts @@ -71,12 +71,16 @@ export default { 'clusters.addworker.specifyWorkerIP': 'Указать IP воркера', 'clusters.addworker.detectWorkerIP': 'Автоматически определить IP воркера', 'clusters.addworker.specifyWorkerAddress': 'Указать внешний адрес воркера', - 'clusters.addworker.detectWorkerAddress': - 'Указать внешний адрес воркера (по умолчанию используется IP воркера)', + 'clusters.addworker.detectWorkerAddress': 'Указать внешний адрес воркера', + 'clusters.addworker.detectWorkerAddress.tips': + 'По умолчанию используется IP воркера, если не указано иное.', 'clusters.addworker.externalIP.tips': 'Если работаете в VPC или частной сети, укажите внешний адрес воркера, доступный для сервера.', 'clusters.addworker.enterWorkerIP': 'Введите IP воркера', 'clusters.addworker.enterWorkerIP.error': 'Пожалуйста, введите IP воркера.', + 'clusters.addworker.enterWorkerAddress': 'Enter worker external address', + 'clusters.addworker.enterWorkerAddress.error': + 'Please enter the worker external address.', 'clusters.addworker.extraVolume': 'Дополнительное монтирование тома', 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', 'clusters.addworker.cacheVolume.tips': @@ -134,4 +138,6 @@ export default { // 13. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP {type}', // 14. 'clusters.form.setDefault': 'Set as Default', // 15. 'clusters.form.setDefault.tips': 'Default for deployment', +// 16. 'clusters.addworker.enterWorkerAddress': 'Enter worker external address', +// 17. 'clusters.addworker.enterWorkerAddress.error': 'Please enter the worker external address.', // ================================================================ diff --git a/src/locales/zh-CN/clusters.ts b/src/locales/zh-CN/clusters.ts index e5aef7df..146bafe1 100644 --- a/src/locales/zh-CN/clusters.ts +++ b/src/locales/zh-CN/clusters.ts @@ -66,19 +66,22 @@ export default { 'clusters.addworker.checkEnv': '检查环境', 'clusters.addworker.specifyArgs': '指定参数', 'clusters.addworker.runCommand': '运行指令', - 'clusters.addworker.specifyWorkerIP': '指定节点 IP', + 'clusters.addworker.specifyWorkerIP': '节点 IP', 'clusters.addworker.detectWorkerIP': '自动检测节点 IP', - 'clusters.addworker.specifyWorkerAddress': '指定节点外部地址', - 'clusters.addworker.detectWorkerAddress': - '指定节点外部地址(默认使用节点 IP)', + 'clusters.addworker.specifyWorkerAddress': '节点外部地址', + 'clusters.addworker.detectWorkerAddress': '节点外部地址', + 'clusters.addworker.detectWorkerAddress.tips': + '如果未指定,则默认为节点 IP。', 'clusters.addworker.externalIP.tips': '如运行在 VPC 或私有网络时,请指定 GPUStack Server 可达的节点外部地址。', 'clusters.addworker.enterWorkerIP': '输入节点 IP', 'clusters.addworker.enterWorkerIP.error': '请输入节点 IP', + 'clusters.addworker.enterWorkerAddress': '输入节点外部地址', + 'clusters.addworker.enterWorkerAddress.error': '请输入节点外部地址', 'clusters.addworker.extraVolume': '额外卷挂载', 'clusters.addworker.cacheVolume': '缓存卷挂载', 'clusters.addworker.cacheVolume.tips': - '如果你想自定义模型缓存目录,可以指定路径进行挂载。', + '如果要自定义模型缓存目录,可以指定路径进行挂载。', 'clusters.addworker.configSummary': '配置摘要', 'clusters.addworker.gpuVendor': 'GPU 厂商', 'clusters.addworker.workerIP': '节点 IP', diff --git a/src/pages/cluster-management/components/add-worker/constainers.tsx b/src/pages/cluster-management/components/add-worker/constainers.tsx index b3cf65f3..850e9df9 100644 --- a/src/pages/cluster-management/components/add-worker/constainers.tsx +++ b/src/pages/cluster-management/components/add-worker/constainers.tsx @@ -5,7 +5,7 @@ export const Title = styled.div` `; export const ConfigWrapper = styled.div` - padding: 16px; + padding: 12px 16px; margin-bottom: 16px; border: 1px solid var(--ant-color-split); border-radius: var(--ant-border-radius); diff --git a/src/pages/cluster-management/components/add-worker/specify-arguments.tsx b/src/pages/cluster-management/components/add-worker/specify-arguments.tsx index a7601961..9d12e571 100644 --- a/src/pages/cluster-management/components/add-worker/specify-arguments.tsx +++ b/src/pages/cluster-management/components/add-worker/specify-arguments.tsx @@ -238,22 +238,12 @@ const SpecifyArguments = () => { } @@ -325,8 +315,11 @@ const SpecifyArguments = () => { }} > } + tips={intl.formatMessage({ + id: 'clusters.addworker.detectWorkerAddress.tips' + })} placeholder={intl.formatMessage({ - id: 'clusters.addworker.enterWorkerIP' + id: 'clusters.addworker.enterWorkerAddress' })} value={externalWorkerIPConfig.ip} checked={externalWorkerIPConfig.enable} @@ -334,7 +327,7 @@ const SpecifyArguments = () => { externalWorkerIPConfig.required && !externalWorkerIPConfig.ip && intl.formatMessage({ - id: 'clusters.addworker.enterWorkerIP.error' + id: 'clusters.addworker.enterWorkerAddress.error' }) } onChange={(checked) => diff --git a/src/pages/llmodels/forms/basic.tsx b/src/pages/llmodels/forms/basic.tsx index 1f5de91f..5785cc94 100644 --- a/src/pages/llmodels/forms/basic.tsx +++ b/src/pages/llmodels/forms/basic.tsx @@ -1,3 +1,4 @@ +import AutoTooltip from '@/components/auto-tooltip'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { modelNameReg } from '@/config'; @@ -10,6 +11,7 @@ import { import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { useMemo } from 'react'; +import styled from 'styled-components'; import { sourceOptions } from '../config'; import { FormData } from '../config/types'; import BackendForm from './backend'; @@ -19,11 +21,59 @@ import LocalPathSource from './local-path-source'; import ModeField from './mode-field'; import OnlineSource from './online-source'; +const ClusterOption = styled.span` + display: flex; + padding: 8px 0; + width: 100%; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--ant-color-split); + gap: 8px; + .label { + display: flex; + align-items: center; + gap: 8px; + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + .dot { + display: flex; + width: 8px; + height: 8px; + background-color: var(--ant-color-warning); + border-radius: 50%; + &.ready { + background-color: var(--ant-color-success); + } + } + .item { + width: max-content; + display: flex; + align-items: center; + padding: 2px 0px; + border-radius: 4px; + font-weight: 400; + gap: 12px; + } +`; + interface BasicFormProps { fields?: string[]; sourceDisable?: boolean; sourceList?: Global.BaseOption[]; - clusterList: Global.BaseOption[]; + clusterList: Global.BaseOption< + number, + { + provider: string; + state: string; + is_default: boolean; + workers: number; + ready_workers: number; + gpus: number; + } + >[]; handleClusterChange: (value: number) => void; onSourceChange?: (value: string) => void; } @@ -51,11 +101,43 @@ const BasicForm: React.FC = (props) => { item.state === ClusterStatusValueMap.Ready ? item.label : `${item.label} [${ClusterStatusLabelMap[item.state as string]}]`, - value: item.value + value: item.value, + workers: item.workers, + ready_workers: item.ready_workers, + gpus: item.gpus }; }); }, [clusterList]); + const clusterOptionRender = (option: any) => { + const { data } = option; + + return ( + + + + {data.label} + + + 0 ? 'ready' : ''}`}> + 0 ? 'ready' : ''}`} + > + + {intl.formatMessage({ id: 'resources.nodes' })}: + + {data.ready_workers} / {data.workers} + + + + GPUs: + {data.gpus} + + + + ); + }; + return ( <> @@ -120,6 +202,7 @@ const BasicForm: React.FC = (props) => { onChange={handleClusterChange} label={intl.formatMessage({ id: 'clusters.title' })} options={clusterOptions} + optionRender={clusterOptionRender} required > } diff --git a/src/pages/llmodels/hooks/use-form-initial-values.ts b/src/pages/llmodels/hooks/use-form-initial-values.ts index 00efd192..2e9eb9d0 100644 --- a/src/pages/llmodels/hooks/use-form-initial-values.ts +++ b/src/pages/llmodels/hooks/use-form-initial-values.ts @@ -218,7 +218,10 @@ export const useGenerateWorkerOptions = () => { value: item.id, provider: item.provider as string, state: item.state, - is_default: item.is_default + is_default: item.is_default, + workers: item.workers, + ready_workers: item.ready_workers, + gpus: item.gpus })) ); }; @@ -254,7 +257,10 @@ export default function useFormInitialValues() { value: item.id, provider: item.provider as string, state: item.state, - is_default: item.is_default + is_default: item.is_default, + workers: item.workers, + ready_workers: item.ready_workers, + gpus: item.gpus })); setClusterList(list); setClusterListAtom(list);