fix: instance form ux

This commit is contained in:
jialin
2026-05-25 22:42:02 +08:00
committed by jialin
parent 5195b1e5fc
commit 921a2a0d81
14 changed files with 92 additions and 44 deletions
+2
View File
@@ -42,6 +42,8 @@ export default {
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.', 'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'On the Worker that needs to be added, run the following command to join it to the cluster.', 'On the Worker that needs to be added, run the following command to join it to the cluster.',
'clusters.create.addCommand.k8s.tips':
'On the Kubernetes cluster that needs to be registered, run the following command to create the Kubernetes resources and register the cluster.',
'clusters.create.register.tips': 'clusters.create.register.tips':
'On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.', 'On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.',
'cluster.create.checkEnv.tips': 'cluster.create.checkEnv.tips':
+2
View File
@@ -42,6 +42,8 @@ export default {
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.', 'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'On the Worker that needs to be added, run the following command to join it to the cluster.', 'On the Worker that needs to be added, run the following command to join it to the cluster.',
'clusters.create.addCommand.k8s.tips':
'登録する Kubernetes クラスターで以下のコマンドを実行し、Kubernetes リソースを作成してクラスターを登録します。',
'cluster.create.checkEnv.tips': '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': 'clusters.create.register.tips':
+2
View File
@@ -42,6 +42,8 @@ export default {
'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> для {label} перед выполнением следующей команды.', 'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> для {label} перед выполнением следующей команды.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.', 'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.',
'clusters.create.addCommand.k8s.tips':
'На Kubernetes-кластере, который необходимо зарегистрировать, выполните следующую команду, чтобы создать ресурсы Kubernetes и зарегистрировать этот кластер.',
'cluster.create.checkEnv.tips': 'cluster.create.checkEnv.tips':
'Используйте следующую команду для проверки готовности окружения', 'Используйте следующую команду для проверки готовности окружения',
'clusters.create.register.tips': 'clusters.create.register.tips':
+2
View File
@@ -42,6 +42,8 @@ export default {
'Aşağıdaki komutu çalıştırmadan önce lütfen {label} için <a href={link} target="_blank">ön koşulların</a> karşılandığından emin olun.', 'Aşağıdaki komutu çalıştırmadan önce lütfen {label} için <a href={link} target="_blank">ön koşulların</a> karşılandığından emin olun.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'Eklenmesi gereken İşçi Düğümde, kümeye katılması için aşağıdaki komutu çalıştırın.', 'Eklenmesi gereken İşçi Düğümde, kümeye katılması için aşağıdaki komutu çalıştırın.',
'clusters.create.addCommand.k8s.tips':
'Kaydedilmesi gereken Kubernetes kümesinde, Kubernetes kaynaklarını oluşturmak ve kümeyi kaydetmek için aşağıdaki komutu çalıştırın.',
'clusters.create.register.tips': 'clusters.create.register.tips':
'Eklenmesi gereken Kubernetes kümesinde, düğümlerini kümeye katılması için aşağıdaki komutu çalıştırın.', 'Eklenmesi gereken Kubernetes kümesinde, düğümlerini kümeye katılması için aşağıdaki komutu çalıştırın.',
'cluster.create.checkEnv.tips': 'cluster.create.checkEnv.tips':
+2
View File
@@ -41,6 +41,8 @@ export default {
'在执行以下命令之前,请确保已满足 {label} 的<a href={link} target="_blank">先决条件</a>。', '在执行以下命令之前,请确保已满足 {label} 的<a href={link} target="_blank">先决条件</a>。',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'在需要添加的节点上运行以下命令,将其加入到集群中。', '在需要添加的节点上运行以下命令,将其加入到集群中。',
'clusters.create.addCommand.k8s.tips':
'在需要注册的 Kubernetes 集群中运行以下命令,创建 Kubernetes 资源,注册该集群。',
'clusters.create.register.tips': 'clusters.create.register.tips':
'在需要添加的 Kubernetes 集群上运行以下命令,将其中的节点加入到集群中。', '在需要添加的 Kubernetes 集群上运行以下命令,将其中的节点加入到集群中。',
'cluster.create.checkEnv.tips': '使用以下命令检查环境是否准备妥当。', 'cluster.create.checkEnv.tips': '使用以下命令检查环境是否准备妥当。',
@@ -113,7 +113,7 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
aria-disabled={disabled} aria-disabled={disabled}
tabIndex={disabled ? -1 : 0} tabIndex={disabled ? -1 : 0}
className={classNames(styles.numberItem, { className={classNames(styles.numberItem, {
[styles.active]: num === value [styles.active]: num === value && !!value
})} })}
onClick={() => handleSelect(num)} onClick={() => handleSelect(num)}
onKeyDown={(e) => { onKeyDown={(e) => {
@@ -30,7 +30,7 @@ const K8sRunCommand: React.FC<AddWorkerStepProps> = ({ disabled }) => {
}} }}
> >
{intl.formatMessage({ {intl.formatMessage({
id: 'clusters.create.addCommand.tips' id: 'clusters.create.addCommand.k8s.tips'
})} })}
</Typography.Paragraph> </Typography.Paragraph>
<RegisterClusterInner <RegisterClusterInner
@@ -2,6 +2,7 @@ import { AutoTooltip, IconFont, StatusTag, ThemeTag } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Flex } from 'antd'; import { Flex } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
import { manufactureColorMap } from '../../templates/config';
import { import {
convertKiToGi, convertKiToGi,
InstanceTypePhaseLabelMap, InstanceTypePhaseLabelMap,
@@ -67,7 +68,10 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
const name = item.metadata?.name; const name = item.metadata?.name;
const acceleratable = item.spec?.acceleratable; const acceleratable = item.spec?.acceleratable;
const manufacturer = item.spec?.manufacturer?.toUpperCase(); const manufacturerKey = item.spec?.manufacturer;
const manufacturer = manufacturerKey?.toUpperCase();
const manufacturerColor =
(manufacturerKey && manufactureColorMap[manufacturerKey]) ?? 'purple';
const renderName = () => { const renderName = () => {
const product = item.spec?.product; const product = item.spec?.product;
@@ -91,7 +95,7 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
fontWeight: 400 fontWeight: 400
}} }}
> >
<ThemeTag color="purple">{manufacturer}</ThemeTag> <ThemeTag color={manufacturerColor}>{manufacturer}</ThemeTag>
</span> </span>
)} )}
</Flex> </Flex>
@@ -6,6 +6,7 @@ export interface FormData {
name: string; name: string;
namespace: string; namespace: string;
}; };
enable_ssh?: boolean;
spec: { spec: {
type: string; type: string;
image: string; image: string;
+48 -23
View File
@@ -4,8 +4,8 @@ import {
CheckboxField, CheckboxField,
CollapsePanel, CollapsePanel,
IconFont, IconFont,
MultipleSelect,
ScrollSpyTabs, ScrollSpyTabs,
Select,
useFinishFailed, useFinishFailed,
useScrollActiveChange, useScrollActiveChange,
useWrapperContext useWrapperContext
@@ -25,6 +25,7 @@ import TemplateBasicForm, {
BasicResourceMax BasicResourceMax
} from '../../templates/forms/basic'; } from '../../templates/forms/basic';
import { FormData, InstanceTypeItem, ListItem } from '../config/types'; import { FormData, InstanceTypeItem, ListItem } from '../config/types';
import instanceStyles from '../styles/instances.module.less';
import Basic from './basic'; import Basic from './basic';
import InstanceTypeFormItem from './instance-type'; import InstanceTypeFormItem from './instance-type';
import StorageVolume from './storage-volume'; import StorageVolume from './storage-volume';
@@ -123,6 +124,22 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
} }
}, [open, action]); }, [open, action]);
const handleSSHEnableChange = (e: any) => {
if (!e?.target?.checked) {
return;
}
const currentPorts = form.getFieldValue(['spec', 'ports']) || [];
const hasSSHPort = currentPorts.some(
(item: any) => item?.protocol === 'TCP' && item?.port === SSH_PORT
);
if (!hasSSHPort) {
form.setFieldValue(
['spec', 'ports'],
[...currentPorts, { protocol: 'TCP', port: SSH_PORT, name: 'SSH' }]
);
}
};
const segmentOptions = useMemo( const segmentOptions = useMemo(
() => [ () => [
{ {
@@ -363,31 +380,39 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
<CheckboxField <CheckboxField
disabled={disabled} disabled={disabled}
label={'启用 SSH 访问'} label={'启用 SSH 访问'}
onChange={handleSSHEnableChange}
></CheckboxField> ></CheckboxField>
</Form.Item> </Form.Item>
{sshEnabled && ( {sshEnabled && (
<Form.Item<FormData> name={['spec', 'sshPublicKey', 'name']}> <div className={instanceStyles.sshkeySelection}>
<Select <Form.Item<FormData> name={['spec', 'sshPublicKey', 'name']}>
disabled={disabled} <MultipleSelect
mode="multiple" disabled={disabled}
maxTagCount={1} mode="multiple"
label={'SSH 公钥'} maxTagCount={1}
options={[ label={'SSH 公钥'}
{ styles={{
label: 'SSH-key-1', wrapper: {
value: 'gpustack-ssh-public-key' width: '100%'
}, }
{ }}
label: 'SSH-key-2', options={[
value: 'gpustack-ssh-public-key-2' {
}, label: 'SSH-key-1',
{ value: 'gpustack-ssh-public-key'
label: 'SSH-key-3', },
value: 'gpustack-ssh-public-key-3' {
} label: 'SSH-key-2',
]} value: 'gpustack-ssh-public-key-2'
></Select> },
</Form.Item> {
label: 'SSH-key-3',
value: 'gpustack-ssh-public-key-3'
}
]}
></MultipleSelect>
</Form.Item>
</div>
)} )}
</Form> </Form>
</ScrollSpyTabs> </ScrollSpyTabs>
@@ -87,7 +87,8 @@ const StorageVolume = ({
}; };
return ( return (
<FieldBlock> <>
<div data-field="storage"></div>
<Flex <Flex
style={{ style={{
justifyContent: 'space-between', justifyContent: 'space-between',
@@ -149,7 +150,6 @@ const StorageVolume = ({
</Button> </Button>
)} )}
</Flex> </Flex>
<div data-field="storage"></div>
{storageMode === StorageModeValueMap.Existing && ( {storageMode === StorageModeValueMap.Existing && (
<Form.Item <Form.Item
@@ -230,7 +230,7 @@ const StorageVolume = ({
onCancel={() => setOverlayOpen(false)} onCancel={() => setOverlayOpen(false)}
onSubmit={handleCreateStorage} onSubmit={handleCreateStorage}
/> />
</FieldBlock> </>
); );
}; };
@@ -32,3 +32,11 @@
} }
} }
} }
.sshkeySelection {
:global {
.ant-select-content .ant-tag {
font-size: 12px !important;
}
}
}
@@ -27,7 +27,7 @@ import {
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Tag } from 'antd'; import { Button, Tag } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
import { templateActions } from '../config'; import { manufactureColorMap, templateActions } from '../config';
import { ListItem } from '../config/types'; import { ListItem } from '../config/types';
const imageLogoMap = { const imageLogoMap = {
@@ -40,19 +40,6 @@ const imageLogoMap = {
ubuntu: ubuntuLogo ubuntu: ubuntuLogo
} as const; } as const;
export const manufactureColorMap: Record<string, string> = {
nvidia: 'green',
amd: 'volcano',
ascend: 'orange',
hygon: 'magenta',
moorthreads: 'cyan',
iluvatar: 'purple',
metax: 'geekblue',
cambricon: 'gold',
thead: 'red',
cpu: 'blue'
};
const manufacturerLabelMap: Record<string, string> = Object.values( const manufacturerLabelMap: Record<string, string> = Object.values(
GPUsConfigs GPUsConfigs
).reduce( ).reduce(
@@ -35,6 +35,19 @@ export const ImagePullPolicyOptions: {
export const DefaultImagePullPolicy: ImagePullPolicy = 'IfNotPresent'; export const DefaultImagePullPolicy: ImagePullPolicy = 'IfNotPresent';
export const manufactureColorMap: Record<string, string> = {
nvidia: 'green',
amd: 'volcano',
ascend: 'orange',
hygon: 'magenta',
moorthreads: 'cyan',
iluvatar: 'purple',
metax: 'geekblue',
cambricon: 'gold',
thead: 'red',
cpu: 'blue'
};
export const normalizeCommand = (value: string): string[] => { export const normalizeCommand = (value: string): string[] => {
if (!value) return []; if (!value) return [];
const tokens: string[] = []; const tokens: string[] = [];