diff --git a/src/locales/en-US/common.ts b/src/locales/en-US/common.ts index 68b6b31d..8540681e 100644 --- a/src/locales/en-US/common.ts +++ b/src/locales/en-US/common.ts @@ -284,6 +284,6 @@ export default { 'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.height': 'Image height must be {height}.', 'common.remaining': 'Remaining {count}', - 'common.max': 'Maximum {count}', + 'common.max': 'Max {count}', 'common.validate.group': 'Please complete the {group} configuration' }; diff --git a/src/locales/en-US/gpuservice.ts b/src/locales/en-US/gpuservice.ts index 11d0f861..771f4e65 100644 --- a/src/locales/en-US/gpuservice.ts +++ b/src/locales/en-US/gpuservice.ts @@ -16,8 +16,8 @@ export default { 'gpuservice.template.containerDisk': 'Container Disk (GB)', 'gpuservice.template.memory': 'Memory (GB)', 'gpuservice.instance.containerDisk.remaining': - 'Container Disk (Remaining {count} GB)', - 'gpuservice.instance.memory.remaining': 'Memory (Remaining {count} GB)', + 'Container Disk (Max {count} GB)', + 'gpuservice.instance.memory.remaining': 'Memory (Max {count} GB)', 'gpuservice.template.displayName': 'Display Name', 'gpuservice.template.displayName.max': 'Display name cannot exceed 63 characters.', @@ -99,6 +99,7 @@ export default { 'gpuservice.instance.sliced': 'Sliced', 'gpuservice.instance.memory': 'Memory', 'gpuservice.instance.ram': 'RAM', + 'gpuservice.instance.disk': 'Disk', 'gpuservice.instance.search.type.placeholder': 'Search by name', 'gpuservice.instance.search.template.placeholder': 'Search by template name, image or mount path', diff --git a/src/locales/ja-JP/common.ts b/src/locales/ja-JP/common.ts index 97d6a965..ee09d90f 100644 --- a/src/locales/ja-JP/common.ts +++ b/src/locales/ja-JP/common.ts @@ -284,7 +284,7 @@ export default { 'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.height': 'Image height must be {height}.', 'common.remaining': '残り {count}', - 'common.max': 'Maximum {count}', + 'common.max': '最大 {count}', 'common.validate.group': 'Please complete the {group} configuration' }; diff --git a/src/locales/ja-JP/gpuservice.ts b/src/locales/ja-JP/gpuservice.ts index 447d2fb5..dd0f5b6f 100644 --- a/src/locales/ja-JP/gpuservice.ts +++ b/src/locales/ja-JP/gpuservice.ts @@ -16,8 +16,8 @@ export default { 'gpuservice.template.containerDisk': 'コンテナディスク (GB)', 'gpuservice.template.memory': 'メモリ (GB)', 'gpuservice.instance.containerDisk.remaining': - 'コンテナディスク (残り {count} GB)', - 'gpuservice.instance.memory.remaining': 'メモリ (残り {count} GB)', + 'コンテナディスク (最大 {count} GB)', + 'gpuservice.instance.memory.remaining': 'メモリ (最大 {count} GB)', 'gpuservice.template.displayName': '表示名', 'gpuservice.template.displayName.max': '表示名は 63 文字以内で入力してください。', @@ -98,6 +98,7 @@ export default { 'gpuservice.instance.sliced': '分割', 'gpuservice.instance.memory': 'Memory', 'gpuservice.instance.ram': 'RAM', + 'gpuservice.instance.disk': 'ディスク', 'gpuservice.instance.search.type.placeholder': '名前で検索', 'gpuservice.instance.search.template.placeholder': 'テンプレート名、イメージまたはマウントパスで検索', diff --git a/src/locales/ru-RU/common.ts b/src/locales/ru-RU/common.ts index a703143e..fcd25e2d 100644 --- a/src/locales/ru-RU/common.ts +++ b/src/locales/ru-RU/common.ts @@ -283,7 +283,7 @@ export default { 'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.height': 'Image height must be {height}.', 'common.remaining': 'Остаток {count}', - 'common.max': 'Maximum {count}', + 'common.max': 'Макс. {count}', 'common.validate.group': 'Please complete the {group} configuration' }; diff --git a/src/locales/ru-RU/gpuservice.ts b/src/locales/ru-RU/gpuservice.ts index 9ca24dc5..866843e9 100644 --- a/src/locales/ru-RU/gpuservice.ts +++ b/src/locales/ru-RU/gpuservice.ts @@ -17,8 +17,8 @@ export default { 'gpuservice.template.containerDisk': 'Диск контейнера (GB)', 'gpuservice.template.memory': 'Память (GB)', 'gpuservice.instance.containerDisk.remaining': - 'Диск контейнера (Остаток {count} GB)', - 'gpuservice.instance.memory.remaining': 'Память (Остаток {count} GB)', + 'Диск контейнера (Макс. {count} GB)', + 'gpuservice.instance.memory.remaining': 'Память (Макс. {count} GB)', 'gpuservice.template.displayName': 'Отображаемое имя', 'gpuservice.template.displayName.max': 'Отображаемое имя не должно превышать 63 символа.', @@ -99,6 +99,7 @@ export default { 'gpuservice.instance.sliced': 'Разделено', 'gpuservice.instance.memory': 'Память', 'gpuservice.instance.ram': 'RAM', + 'gpuservice.instance.disk': 'Диск', 'gpuservice.instance.search.type.placeholder': 'Поиск по имени', 'gpuservice.instance.search.template.placeholder': 'Поиск по имени шаблона, образу или пути монтирования', diff --git a/src/locales/tr-TR/common.ts b/src/locales/tr-TR/common.ts index 92b0ce8a..8d4908c7 100644 --- a/src/locales/tr-TR/common.ts +++ b/src/locales/tr-TR/common.ts @@ -287,6 +287,6 @@ export default { 'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.height': 'Image height must be {height}.', 'common.remaining': 'Kalan {count}', - 'common.max': 'Maksimum {count}', + 'common.max': 'Maks. {count}', 'common.validate.group': 'Please complete the {group} configuration' }; diff --git a/src/locales/tr-TR/gpuservice.ts b/src/locales/tr-TR/gpuservice.ts index 436651c3..e3c978f0 100644 --- a/src/locales/tr-TR/gpuservice.ts +++ b/src/locales/tr-TR/gpuservice.ts @@ -16,8 +16,8 @@ export default { 'gpuservice.template.containerDisk': 'Konteyner Diski (GB)', 'gpuservice.template.memory': 'Bellek (GB)', 'gpuservice.instance.containerDisk.remaining': - 'Konteyner Diski (Kalan {count} GB)', - 'gpuservice.instance.memory.remaining': 'Bellek (Kalan {count} GB)', + 'Konteyner Diski (Maks. {count} GB)', + 'gpuservice.instance.memory.remaining': 'Bellek (Maks. {count} GB)', 'gpuservice.template.displayName': 'Görünen Ad', 'gpuservice.template.displayName.max': 'Görünen ad 63 karakterden uzun olamaz.', @@ -98,6 +98,7 @@ export default { 'gpuservice.instance.sliced': 'Bölünmüş', 'gpuservice.instance.memory': 'Bellek', 'gpuservice.instance.ram': 'RAM', + 'gpuservice.instance.disk': 'Disk', 'gpuservice.instance.search.type.placeholder': 'Ada göre ara', 'gpuservice.instance.search.template.placeholder': 'Şablon adına, imaja veya bağlama yoluna göre ara', diff --git a/src/locales/zh-CN/gpuservice.ts b/src/locales/zh-CN/gpuservice.ts index cf2eeb0e..1146961f 100644 --- a/src/locales/zh-CN/gpuservice.ts +++ b/src/locales/zh-CN/gpuservice.ts @@ -92,6 +92,7 @@ export default { 'gpuservice.instance.sliced': '切分', 'gpuservice.instance.memory': '显存', 'gpuservice.instance.ram': '内存', + 'gpuservice.instance.disk': '磁盘', 'gpuservice.instance.search.type.placeholder': '搜索名称', 'gpuservice.instance.search.template.placeholder': '搜索模板名称、镜像或挂载路径', diff --git a/src/pages/_components/number-selection/index.tsx b/src/pages/_components/number-selection/index.tsx index 053cb31a..a679ee2e 100644 --- a/src/pages/_components/number-selection/index.tsx +++ b/src/pages/_components/number-selection/index.tsx @@ -46,13 +46,9 @@ const NumberSelection: React.FC = ({ { length: Math.max(0, maxCount) }, (_, i) => i + 1 ); - const computedItems = showCustomInput - ? presetItems.filter((n) => n >= min && n <= max) - : Array.from( - { length: Math.max(0, Math.floor((max - min) / step) + 1) }, - (_, i) => min + i * step - ); - const items = computedItems.length === 0 ? [0] : computedItems; + const items = presetItems; + const isItemDisabled = (num: number) => + !!disabled || num > max || num < min; const [inputValue, setInputValue] = useState(() => value !== undefined && value !== null && !presetItems.includes(value) ? value @@ -70,7 +66,7 @@ const NumberSelection: React.FC = ({ }, [value]); const handleSelect = (num: number) => { - if (disabled || num === value) { + if (isItemDisabled(num) || num === value) { return; } setInputValue(null); @@ -108,15 +104,17 @@ const NumberSelection: React.FC = ({
{items.map((num) => { + const itemDisabled = isItemDisabled(num); return (
handleSelect(num)} onKeyDown={(e) => { diff --git a/src/pages/_components/number-selection/styles.less b/src/pages/_components/number-selection/styles.less index ae69881f..1b60d54d 100644 --- a/src/pages/_components/number-selection/styles.less +++ b/src/pages/_components/number-selection/styles.less @@ -85,6 +85,13 @@ font-weight: 500; border-color: var(--ant-color-split); } + &.itemDisabled { + cursor: not-allowed; + color: var(--ant-color-text-disabled); + &:hover { + background-color: transparent; + } + } } &.disabled { cursor: not-allowed; diff --git a/src/pages/gpu-service/instances/components/instance-type-item.tsx b/src/pages/gpu-service/instances/components/instance-type-item.tsx index cb39dd38..0cc7aa89 100644 --- a/src/pages/gpu-service/instances/components/instance-type-item.tsx +++ b/src/pages/gpu-service/instances/components/instance-type-item.tsx @@ -80,7 +80,6 @@ const MetaItem: React.FC<{ const InstanceTypeItem: React.FC = ({ item }) => { const intl = useIntl(); - const name = item.name; const specData = item.spec || {}; const acceleratable = specData.acceleratable; @@ -93,11 +92,9 @@ const InstanceTypeItem: React.FC = ({ item }) => { const remainingData = item.status?.remaining || {}; const renderName = () => { - const product = specData.product; - const cpuCapacity = remainingData.cpu; - const displayName = - product || - (!acceleratable && cpuCapacity ? `${cpuCapacity} vCPUs` : name); + const displayName = specData.acceleratable + ? specData.product || item.name + : 'CPU'; return displayName; }; @@ -162,7 +159,7 @@ const InstanceTypeItem: React.FC = ({ item }) => { value={toDisplayUnit(convertKiToGi(remainingData.ram)) ?? '-'} > { const intl = useIntl(); + const { getRuleMessage } = useAppUtils(); return ( <> @@ -24,9 +25,7 @@ const Basic = ({ rules={[ { required: true, - message: intl.formatMessage({ - id: 'gpuservice.instance.name.required' - }) + message: getRuleMessage('input', 'common.table.name') }, { pattern: validateLabelNameRegxFor63, @@ -36,7 +35,7 @@ const Basic = ({ > diff --git a/src/pages/gpu-service/instances/forms/index.tsx b/src/pages/gpu-service/instances/forms/index.tsx index 2cb83f66..fc45caab 100644 --- a/src/pages/gpu-service/instances/forms/index.tsx +++ b/src/pages/gpu-service/instances/forms/index.tsx @@ -540,10 +540,7 @@ const GPUServiceInstanceForm: React.FC = forwardRef( rules={[ { required: true, - message: getRuleMessage( - 'select', - 'gpuservice.instance.ssh.assignKey' - ) + message: getRuleMessage('select', 'gpuservice.publicKey') } ]} > diff --git a/src/pages/gpu-service/instances/forms/instance-type.tsx b/src/pages/gpu-service/instances/forms/instance-type.tsx index 90a120fb..2859ab6c 100644 --- a/src/pages/gpu-service/instances/forms/instance-type.tsx +++ b/src/pages/gpu-service/instances/forms/instance-type.tsx @@ -97,9 +97,9 @@ const InstanceTypeFormItem: React.FC = ({ fontWeight: 400 }} > - {description.manufacturer + {description.acceleratable ? `${description.product} x ${currentData?.spec?.resources?.accelerator}` - : ''} + : 'CPU'} @@ -185,7 +185,12 @@ const InstanceTypeFormItem: React.FC = ({ > ) } - label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })} + label={`${intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })} (${intl.formatMessage( + { + id: 'common.max' + }, + { count: maxGpuCount } + )})`} /> )} diff --git a/src/pages/gpu-service/instances/forms/storage-volume.tsx b/src/pages/gpu-service/instances/forms/storage-volume.tsx index 0ed55854..97786298 100644 --- a/src/pages/gpu-service/instances/forms/storage-volume.tsx +++ b/src/pages/gpu-service/instances/forms/storage-volume.tsx @@ -2,7 +2,6 @@ import { PageAction } from '@/config'; import { Input as CInput, InputNumber as CInputNumber, - CheckboxField, LabelInfo, Select, useAppUtils @@ -14,21 +13,17 @@ import { useEffect, useMemo, useState } from 'react'; import { FormData as StorageFormData } from '../../storage/config/types'; import useCreateStorage from '../../storage/services/use-create-storage'; import useQueryStorage from '../../storage/services/use-query-storage'; -import useQueryStorageTypes from '../../storage-types/services/use-query-storage-types'; -import { DEFAULT_PV_CAPACITY_GB, StorageModeValueMap } from '../config'; +import { StorageModeValueMap } from '../config'; import { FormData } from '../config/types'; import StorageOverlay from './storage-overlay'; const DEFAULT_TEMP_CAPACITY_GB = 50; const detectMode = (volume?: FormData['spec']['volume']) => { - if (volume?.persistentTemplate) { - return { mode: StorageModeValueMap.Persistent, releaseWithInstance: true }; + if (volume?.persistent || volume?.persistentTemplate) { + return StorageModeValueMap.Persistent; } - if (volume?.persistent) { - return { mode: StorageModeValueMap.Persistent, releaseWithInstance: false }; - } - return { mode: StorageModeValueMap.Temporary, releaseWithInstance: false }; + return StorageModeValueMap.Temporary; }; const StorageVolume = ({ @@ -45,25 +40,19 @@ const StorageVolume = ({ const [storageMode, setStorageMode] = useState( StorageModeValueMap.Temporary ); - const [releaseWithInstance, setReleaseWithInstance] = useState(false); useEffect(() => { if (!currentVolume) return; - const detected = detectMode(currentVolume); - setStorageMode(detected.mode); - setReleaseWithInstance(detected.releaseWithInstance); + setStorageMode(detectMode(currentVolume)); }, [currentVolume]); const { fetchData: createStorage } = useCreateStorage(); const { detailData: storageData, fetchData: fetchStorage } = useQueryStorage(); - const { detailData: storageTypesData, fetchData: fetchStorageTypes } = - useQueryStorageTypes(); const [overlayOpen, setOverlayOpen] = useState(false); useEffect(() => { fetchStorage({ page: 1, perPage: 100 }); - fetchStorageTypes({ page: 1, perPage: 100 }); }, []); const storageOptions = useMemo( @@ -75,43 +64,19 @@ const StorageVolume = ({ [storageData] ); - const storageTypeOptions = useMemo( - () => - (storageTypesData?.items || []).map((item) => ({ - label: item.displayName || item.name, - value: item.name - })), - [storageTypesData] - ); - - const applyMode = (mode: string, release: boolean) => { + const applyMode = (mode: string) => { if (mode === StorageModeValueMap.Temporary) { form.setFieldValue(['spec', 'volume'], { ephemeral: { capacity: `${DEFAULT_TEMP_CAPACITY_GB}Gi` } }); return; } - if (release) { - form.setFieldValue(['spec', 'volume'], { - persistentTemplate: { - spec: { type: '', capacity: `${DEFAULT_PV_CAPACITY_GB}Gi` }, - releaseWithInstance: true - } - }); - return; - } form.setFieldValue(['spec', 'volume'], { persistent: { name: '' } }); }; const handleModeChange = (mode: string) => { setStorageMode(mode); - applyMode(mode, releaseWithInstance); - }; - - const handleReleaseChange = (e: any) => { - const next = !!e?.target?.checked; - setReleaseWithInstance(next); - applyMode(storageMode, next); + applyMode(mode); }; const handleCreateStorage = async (values: StorageFormData) => { @@ -130,48 +95,60 @@ const StorageVolume = ({ return ( <>
- handleModeChange(e.target.value)} - options={[ - { - label: ( - - {intl.formatMessage({ - id: 'gpuservice.storage.temporary' - })} - - } - /> - ), - value: StorageModeValueMap.Temporary - }, - { - label: ( - - {intl.formatMessage({ - id: 'gpuservice.storage.persistentVolume' - })} - - } - /> - ), - value: StorageModeValueMap.Persistent - } - ]} - /> + + handleModeChange(e.target.value)} + options={[ + { + label: ( + + {intl.formatMessage({ + id: 'gpuservice.storage.temporary' + })} + + } + /> + ), + value: StorageModeValueMap.Temporary + }, + { + label: ( + + {intl.formatMessage({ + id: 'gpuservice.storage.persistentVolume' + })} + + } + /> + ), + value: StorageModeValueMap.Persistent + } + ]} + /> + {action === PageAction.CREATE && + storageMode === StorageModeValueMap.Persistent && ( + + )} + {storageMode === StorageModeValueMap.Temporary && ( - - - {action === PageAction.CREATE && !releaseWithInstance && ( - - )} - - - {releaseWithInstance ? ( - <> - - - - )} + )} diff --git a/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx b/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx index 6d6fe54b..0ccc14c8 100644 --- a/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx +++ b/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx @@ -13,6 +13,7 @@ import _ from 'lodash'; import { Fragment, useMemo } from 'react'; import { InstanceStatusLabelMap, rowActionList, status } from '../config'; import { ListItem } from '../config/types'; +import tableSyles from '../styles/table.module.less'; type ConnectEntry = | { @@ -66,27 +67,6 @@ const getConnectEntries = (record: ListItem): ConnectEntry[] => { }); }; -const renderInstanceType = (record: ListItem) => { - const description = JSON.parse(record?.description || '{}').spec || {}; - return ( - - - {description.manufacturer - ? `${description.product} x ${record.spec?.resources?.accelerator}` - : ''} - - - {`${record.spec.resources?.cpu}C / RAM: ${record.spec.resources?.ram?.replace('Gi', 'GB')} / Disk: ${record.spec.resources?.localStorage?.replace('Gi', 'GB')}`}{' '} - - - ); -}; - interface ColumnsHookProps { handleSelect: (val: string, record: ListItem) => void; sortOrder: string[]; @@ -97,6 +77,36 @@ const useInstancesColumns = ({ sortOrder }: ColumnsHookProps): ColumnsType => { const intl = useIntl(); + + const renderInstanceType = (record: ListItem) => { + const description = JSON.parse(record?.description || '{}').spec || {}; + return ( + + + {description.acceleratable + ? `${description.product} x ${record.spec?.resources?.accelerator}` + : 'CPU'} + + + {record.spec.resources?.cpu}C + + + {intl.formatMessage({ id: 'gpuservice.instance.ram' })}:{' '} + {record.spec.resources?.ram?.replace('Gi', 'GB')} + + + + {intl.formatMessage({ id: 'gpuservice.instance.disk' })}:{' '} + {record.spec.resources?.localStorage?.replace('Gi', 'GB')} + + + + ); + }; + return useMemo(() => { return [ { @@ -116,6 +126,9 @@ const useInstancesColumns = ({ { title: intl.formatMessage({ id: 'gpuservice.instance.connect' }), key: 'connect', + ellipsis: { + showTitle: false + }, render: (_text, record: ListItem) => { const entries = getConnectEntries(record); @@ -199,6 +212,9 @@ const useInstancesColumns = ({ dataIndex: ['status', 'phase'], key: 'status', sorter: false, + ellipsis: { + showTitle: false + }, render: (value: string, record: ListItem) => { return ( ( - - {renderInstanceType(record)} - +
+ {renderInstanceType(record)} +
) }, { @@ -246,6 +259,9 @@ const useInstancesColumns = ({ title: intl.formatMessage({ id: 'common.table.operation' }), key: 'operation', dataIndex: 'operation', + ellipsis: { + showTitle: false + }, render: (_text, record) => ( = ({