style: cluster selector

This commit is contained in:
jialin
2025-12-24 20:37:20 +08:00
parent b25189feea
commit 84c2bf130c
9 changed files with 151 additions and 40 deletions
+10
View File
@@ -12,6 +12,15 @@ const Wrapper = styled.div<{ $maxHeight?: number | string }>`
width: 100%; width: 100%;
`; `;
const Title = styled.div`
display: flex;
align-items: center;
max-width: 360px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
`;
const ScrollerModal = ( const ScrollerModal = (
props: ModalProps & { maxContentHeight?: number | string } props: ModalProps & { maxContentHeight?: number | string }
) => { ) => {
@@ -48,6 +57,7 @@ const ScrollerModal = (
return ( return (
<Modal <Modal
{...props} {...props}
title={<Title>{props.title}</Title>}
destroyOnHidden={true} destroyOnHidden={true}
styles={{ styles={{
content: { content: {
+8 -4
View File
@@ -68,15 +68,19 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.addworker.checkEnv': 'Check Environment', 'clusters.addworker.checkEnv': 'Check Environment',
'clusters.addworker.specifyArgs': 'Specify Arguments', 'clusters.addworker.specifyArgs': 'Specify Arguments',
'clusters.addworker.runCommand': 'Run Command', '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.detectWorkerIP': 'Auto-detect Worker IP',
'clusters.addworker.specifyWorkerAddress': 'Specify Worker External Address', 'clusters.addworker.specifyWorkerAddress': 'Worker External Address',
'clusters.addworker.detectWorkerAddress': 'clusters.addworker.detectWorkerAddress': 'Worker External Address',
'Specify Worker External Address (default to Worker IP)', 'clusters.addworker.detectWorkerAddress.tips':
'Defaults to Worker IP if not specified.',
'clusters.addworker.externalIP.tips': 'clusters.addworker.externalIP.tips':
'If running in a VPC or private network, please specify the Worker external address reachable by the GPUStack Server.', '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': 'Enter worker IP',
'clusters.addworker.enterWorkerIP.error': 'Please enter the 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.extraVolume': 'Additional Volume Mount',
'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount',
'clusters.addworker.cacheVolume.tips': 'clusters.addworker.cacheVolume.tips':
+12 -6
View File
@@ -68,15 +68,19 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.addworker.checkEnv': 'Check Environment', 'clusters.addworker.checkEnv': 'Check Environment',
'clusters.addworker.specifyArgs': 'Specify Arguments', 'clusters.addworker.specifyArgs': 'Specify Arguments',
'clusters.addworker.runCommand': 'Run Command', 'clusters.addworker.runCommand': 'Run Command',
'clusters.addworker.specifyWorkerIP': 'Worker IP を指定', 'clusters.addworker.specifyWorkerIP': 'Worker IP',
'clusters.addworker.detectWorkerIP': 'Worker IP を自動検出', 'clusters.addworker.detectWorkerIP': 'Worker IP を自動検出',
'clusters.addworker.specifyWorkerAddress': 'Worker 外部アドレスを指定', 'clusters.addworker.specifyWorkerAddress': 'Worker 外部アドレス',
'clusters.addworker.detectWorkerAddress': 'clusters.addworker.detectWorkerAddress': 'Worker 外部アドレス',
'Worker 外部アドレスを指定(デフォルトはWorker IP', 'clusters.addworker.detectWorkerAddress.tips':
'指定がない場合、デフォルトで Worker IP が使用されます。',
'clusters.addworker.externalIP.tips': 'clusters.addworker.externalIP.tips':
'VPCやプライベートネットワークで動作している場合は、サーバーから到達可能なWorkerの外部アドレスを指定してください。', 'VPCやプライベートネットワークで動作している場合は、サーバーから到達可能なWorkerの外部アドレスを指定してください。',
'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
'clusters.addworker.enterWorkerIP.error': 'Please enter the 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.extraVolume': 'Additional Volume Mount',
'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount',
'clusters.addworker.cacheVolume.tips': 'clusters.addworker.cacheVolume.tips':
@@ -172,7 +176,7 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
// 54. 'clusters.addworker.checkEnv': 'Check Environment', // 54. 'clusters.addworker.checkEnv': 'Check Environment',
// 55. 'clusters.addworker.specifyArgs': 'Specify Arguments', // 55. 'clusters.addworker.specifyArgs': 'Specify Arguments',
// 56. 'clusters.addworker.runCommand': 'Run Command', // 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', // 58. 'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
// 59. 'clusters.addworker.enterWorkerIP': 'Enter worker IP', // 59. 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
// 60. 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', // 60. 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
@@ -203,5 +207,7 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
// 81. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.', // 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.', // 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', // 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 ========== // ========== End of To-Do List ==========
+8 -2
View File
@@ -71,12 +71,16 @@ export default {
'clusters.addworker.specifyWorkerIP': 'Указать IP воркера', 'clusters.addworker.specifyWorkerIP': 'Указать IP воркера',
'clusters.addworker.detectWorkerIP': 'Автоматически определить IP воркера', 'clusters.addworker.detectWorkerIP': 'Автоматически определить IP воркера',
'clusters.addworker.specifyWorkerAddress': 'Указать внешний адрес воркера', 'clusters.addworker.specifyWorkerAddress': 'Указать внешний адрес воркера',
'clusters.addworker.detectWorkerAddress': 'clusters.addworker.detectWorkerAddress': 'Указать внешний адрес воркера',
'Указать внешний адрес воркера (по умолчанию используется IP воркера)', 'clusters.addworker.detectWorkerAddress.tips':
'По умолчанию используется IP воркера, если не указано иное.',
'clusters.addworker.externalIP.tips': 'clusters.addworker.externalIP.tips':
'Если работаете в VPC или частной сети, укажите внешний адрес воркера, доступный для сервера.', 'Если работаете в VPC или частной сети, укажите внешний адрес воркера, доступный для сервера.',
'clusters.addworker.enterWorkerIP': 'Введите IP воркера', 'clusters.addworker.enterWorkerIP': 'Введите IP воркера',
'clusters.addworker.enterWorkerIP.error': 'Пожалуйста, введите 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.extraVolume': 'Дополнительное монтирование тома',
'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount',
'clusters.addworker.cacheVolume.tips': 'clusters.addworker.cacheVolume.tips':
@@ -134,4 +138,6 @@ export default {
// 13. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP <span class="text-tertiary">{type}</span>', // 13. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP <span class="text-tertiary">{type}</span>',
// 14. 'clusters.form.setDefault': 'Set as Default', // 14. 'clusters.form.setDefault': 'Set as Default',
// 15. 'clusters.form.setDefault.tips': 'Default for deployment', // 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.',
// ================================================================ // ================================================================
+8 -5
View File
@@ -66,19 +66,22 @@ export default {
'clusters.addworker.checkEnv': '检查环境', 'clusters.addworker.checkEnv': '检查环境',
'clusters.addworker.specifyArgs': '指定参数', 'clusters.addworker.specifyArgs': '指定参数',
'clusters.addworker.runCommand': '运行指令', 'clusters.addworker.runCommand': '运行指令',
'clusters.addworker.specifyWorkerIP': '指定节点 IP', 'clusters.addworker.specifyWorkerIP': '节点 IP',
'clusters.addworker.detectWorkerIP': '自动检测节点 IP', 'clusters.addworker.detectWorkerIP': '自动检测节点 IP',
'clusters.addworker.specifyWorkerAddress': '指定节点外部地址', 'clusters.addworker.specifyWorkerAddress': '节点外部地址',
'clusters.addworker.detectWorkerAddress': 'clusters.addworker.detectWorkerAddress': '节点外部地址',
'指定节点外部地址(默认使用节点 IP)', 'clusters.addworker.detectWorkerAddress.tips':
'如果未指定,则默认为节点 IP。',
'clusters.addworker.externalIP.tips': 'clusters.addworker.externalIP.tips':
'如运行在 VPC 或私有网络时,请指定 GPUStack Server 可达的节点外部地址。', '如运行在 VPC 或私有网络时,请指定 GPUStack Server 可达的节点外部地址。',
'clusters.addworker.enterWorkerIP': '输入节点 IP', 'clusters.addworker.enterWorkerIP': '输入节点 IP',
'clusters.addworker.enterWorkerIP.error': '请输入节点 IP', 'clusters.addworker.enterWorkerIP.error': '请输入节点 IP',
'clusters.addworker.enterWorkerAddress': '输入节点外部地址',
'clusters.addworker.enterWorkerAddress.error': '请输入节点外部地址',
'clusters.addworker.extraVolume': '额外卷挂载', 'clusters.addworker.extraVolume': '额外卷挂载',
'clusters.addworker.cacheVolume': '缓存卷挂载', 'clusters.addworker.cacheVolume': '缓存卷挂载',
'clusters.addworker.cacheVolume.tips': 'clusters.addworker.cacheVolume.tips':
'如果你想自定义模型缓存目录,可以指定路径进行挂载。', '如果自定义模型缓存目录,可以指定路径进行挂载。',
'clusters.addworker.configSummary': '配置摘要', 'clusters.addworker.configSummary': '配置摘要',
'clusters.addworker.gpuVendor': 'GPU 厂商', 'clusters.addworker.gpuVendor': 'GPU 厂商',
'clusters.addworker.workerIP': '节点 IP', 'clusters.addworker.workerIP': '节点 IP',
@@ -5,7 +5,7 @@ export const Title = styled.div`
`; `;
export const ConfigWrapper = styled.div` export const ConfigWrapper = styled.div`
padding: 16px; padding: 12px 16px;
margin-bottom: 16px; margin-bottom: 16px;
border: 1px solid var(--ant-color-split); border: 1px solid var(--ant-color-split);
border-radius: var(--ant-border-radius); border-radius: var(--ant-border-radius);
@@ -238,22 +238,12 @@ const SpecifyArguments = () => {
<span <span
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: workerIPConfig.enable __html: workerIPConfig.enable
? intl.formatMessage( ? intl.formatMessage({
{ id: 'clusters.addworker.specifyWorkerIP'
id: 'clusters.addworker.specifyWorkerIP' })
}, : intl.formatMessage({
{ id: 'clusters.addworker.detectWorkerIP'
type: ` (${intl.formatMessage({ id: 'clusters.table.ip.internal' })})` })
}
)
: intl.formatMessage(
{
id: 'clusters.addworker.detectWorkerIP'
},
{
type: ` (${intl.formatMessage({ id: 'clusters.table.ip.internal' })})`
}
)
}} }}
></span> ></span>
} }
@@ -325,8 +315,11 @@ const SpecifyArguments = () => {
}} }}
></span> ></span>
} }
tips={intl.formatMessage({
id: 'clusters.addworker.detectWorkerAddress.tips'
})}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: 'clusters.addworker.enterWorkerIP' id: 'clusters.addworker.enterWorkerAddress'
})} })}
value={externalWorkerIPConfig.ip} value={externalWorkerIPConfig.ip}
checked={externalWorkerIPConfig.enable} checked={externalWorkerIPConfig.enable}
@@ -334,7 +327,7 @@ const SpecifyArguments = () => {
externalWorkerIPConfig.required && externalWorkerIPConfig.required &&
!externalWorkerIPConfig.ip && !externalWorkerIPConfig.ip &&
intl.formatMessage({ intl.formatMessage({
id: 'clusters.addworker.enterWorkerIP.error' id: 'clusters.addworker.enterWorkerAddress.error'
}) })
} }
onChange={(checked) => onChange={(checked) =>
+85 -2
View File
@@ -1,3 +1,4 @@
import AutoTooltip from '@/components/auto-tooltip';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import { modelNameReg } from '@/config'; import { modelNameReg } from '@/config';
@@ -10,6 +11,7 @@ import {
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import { useMemo } from 'react'; import { useMemo } from 'react';
import styled from 'styled-components';
import { sourceOptions } from '../config'; import { sourceOptions } from '../config';
import { FormData } from '../config/types'; import { FormData } from '../config/types';
import BackendForm from './backend'; import BackendForm from './backend';
@@ -19,11 +21,59 @@ import LocalPathSource from './local-path-source';
import ModeField from './mode-field'; import ModeField from './mode-field';
import OnlineSource from './online-source'; 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 { interface BasicFormProps {
fields?: string[]; fields?: string[];
sourceDisable?: boolean; sourceDisable?: boolean;
sourceList?: Global.BaseOption<string>[]; sourceList?: Global.BaseOption<string>[];
clusterList: Global.BaseOption<number, { provider: string; state: string }>[]; clusterList: Global.BaseOption<
number,
{
provider: string;
state: string;
is_default: boolean;
workers: number;
ready_workers: number;
gpus: number;
}
>[];
handleClusterChange: (value: number) => void; handleClusterChange: (value: number) => void;
onSourceChange?: (value: string) => void; onSourceChange?: (value: string) => void;
} }
@@ -51,11 +101,43 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
item.state === ClusterStatusValueMap.Ready item.state === ClusterStatusValueMap.Ready
? item.label ? item.label
: `${item.label} [${ClusterStatusLabelMap[item.state as string]}]`, : `${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]); }, [clusterList]);
const clusterOptionRender = (option: any) => {
const { data } = option;
return (
<ClusterOption>
<span className="label">
<AutoTooltip ghost maxWidth={'100%'}>
{data.label}
</AutoTooltip>
</span>
<span className={`item ${data.ready_workers > 0 ? 'ready' : ''}`}>
<span
className={`dot ${data.ready_workers > 0 ? 'ready' : ''}`}
></span>
<span className="flex-center gap-4 text-tertiary">
<span>{intl.formatMessage({ id: 'resources.nodes' })}:</span>
<span>
{data.ready_workers} / {data.workers}
</span>
</span>
<span className="flex-center gap-4 text-tertiary">
<span>GPUs:</span>
<span>{data.gpus}</span>
</span>
</span>
</ClusterOption>
);
};
return ( return (
<> <>
<Form.Item<FormData> <Form.Item<FormData>
@@ -120,6 +202,7 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
onChange={handleClusterChange} onChange={handleClusterChange}
label={intl.formatMessage({ id: 'clusters.title' })} label={intl.formatMessage({ id: 'clusters.title' })}
options={clusterOptions} options={clusterOptions}
optionRender={clusterOptionRender}
required required
></SealSelect> ></SealSelect>
} }
@@ -218,7 +218,10 @@ export const useGenerateWorkerOptions = () => {
value: item.id, value: item.id,
provider: item.provider as string, provider: item.provider as string,
state: item.state, 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, value: item.id,
provider: item.provider as string, provider: item.provider as string,
state: item.state, 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); setClusterList(list);
setClusterListAtom(list); setClusterListAtom(list);