fix: gpu service issues
This commit is contained in:
@@ -284,6 +284,6 @@ export default {
|
|||||||
'common.image.limit.width': 'Image width must be {width}.',
|
'common.image.limit.width': 'Image width must be {width}.',
|
||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': 'Remaining {count}',
|
'common.remaining': 'Remaining {count}',
|
||||||
'common.max': 'Maximum {count}',
|
'common.max': 'Max {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.validate.group': 'Please complete the {group} configuration'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export default {
|
|||||||
'gpuservice.template.containerDisk': 'Container Disk (GB)',
|
'gpuservice.template.containerDisk': 'Container Disk (GB)',
|
||||||
'gpuservice.template.memory': 'Memory (GB)',
|
'gpuservice.template.memory': 'Memory (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'Container Disk (Remaining {count} GB)',
|
'Container Disk (Max {count} GB)',
|
||||||
'gpuservice.instance.memory.remaining': 'Memory (Remaining {count} GB)',
|
'gpuservice.instance.memory.remaining': 'Memory (Max {count} GB)',
|
||||||
'gpuservice.template.displayName': 'Display Name',
|
'gpuservice.template.displayName': 'Display Name',
|
||||||
'gpuservice.template.displayName.max':
|
'gpuservice.template.displayName.max':
|
||||||
'Display name cannot exceed 63 characters.',
|
'Display name cannot exceed 63 characters.',
|
||||||
@@ -99,6 +99,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Sliced',
|
'gpuservice.instance.sliced': 'Sliced',
|
||||||
'gpuservice.instance.memory': 'Memory',
|
'gpuservice.instance.memory': 'Memory',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.disk': 'Disk',
|
||||||
'gpuservice.instance.search.type.placeholder': 'Search by name',
|
'gpuservice.instance.search.type.placeholder': 'Search by name',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Search by template name, image or mount path',
|
'Search by template name, image or mount path',
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ export default {
|
|||||||
'common.image.limit.width': 'Image width must be {width}.',
|
'common.image.limit.width': 'Image width must be {width}.',
|
||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': '残り {count}',
|
'common.remaining': '残り {count}',
|
||||||
'common.max': 'Maximum {count}',
|
'common.max': '最大 {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.validate.group': 'Please complete the {group} configuration'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export default {
|
|||||||
'gpuservice.template.containerDisk': 'コンテナディスク (GB)',
|
'gpuservice.template.containerDisk': 'コンテナディスク (GB)',
|
||||||
'gpuservice.template.memory': 'メモリ (GB)',
|
'gpuservice.template.memory': 'メモリ (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'コンテナディスク (残り {count} GB)',
|
'コンテナディスク (最大 {count} GB)',
|
||||||
'gpuservice.instance.memory.remaining': 'メモリ (残り {count} GB)',
|
'gpuservice.instance.memory.remaining': 'メモリ (最大 {count} GB)',
|
||||||
'gpuservice.template.displayName': '表示名',
|
'gpuservice.template.displayName': '表示名',
|
||||||
'gpuservice.template.displayName.max':
|
'gpuservice.template.displayName.max':
|
||||||
'表示名は 63 文字以内で入力してください。',
|
'表示名は 63 文字以内で入力してください。',
|
||||||
@@ -98,6 +98,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': '分割',
|
'gpuservice.instance.sliced': '分割',
|
||||||
'gpuservice.instance.memory': 'Memory',
|
'gpuservice.instance.memory': 'Memory',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.disk': 'ディスク',
|
||||||
'gpuservice.instance.search.type.placeholder': '名前で検索',
|
'gpuservice.instance.search.type.placeholder': '名前で検索',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'テンプレート名、イメージまたはマウントパスで検索',
|
'テンプレート名、イメージまたはマウントパスで検索',
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ export default {
|
|||||||
'common.image.limit.width': 'Image width must be {width}.',
|
'common.image.limit.width': 'Image width must be {width}.',
|
||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': 'Остаток {count}',
|
'common.remaining': 'Остаток {count}',
|
||||||
'common.max': 'Maximum {count}',
|
'common.max': 'Макс. {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.validate.group': 'Please complete the {group} configuration'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export default {
|
|||||||
'gpuservice.template.containerDisk': 'Диск контейнера (GB)',
|
'gpuservice.template.containerDisk': 'Диск контейнера (GB)',
|
||||||
'gpuservice.template.memory': 'Память (GB)',
|
'gpuservice.template.memory': 'Память (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'Диск контейнера (Остаток {count} GB)',
|
'Диск контейнера (Макс. {count} GB)',
|
||||||
'gpuservice.instance.memory.remaining': 'Память (Остаток {count} GB)',
|
'gpuservice.instance.memory.remaining': 'Память (Макс. {count} GB)',
|
||||||
'gpuservice.template.displayName': 'Отображаемое имя',
|
'gpuservice.template.displayName': 'Отображаемое имя',
|
||||||
'gpuservice.template.displayName.max':
|
'gpuservice.template.displayName.max':
|
||||||
'Отображаемое имя не должно превышать 63 символа.',
|
'Отображаемое имя не должно превышать 63 символа.',
|
||||||
@@ -99,6 +99,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Разделено',
|
'gpuservice.instance.sliced': 'Разделено',
|
||||||
'gpuservice.instance.memory': 'Память',
|
'gpuservice.instance.memory': 'Память',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.disk': 'Диск',
|
||||||
'gpuservice.instance.search.type.placeholder': 'Поиск по имени',
|
'gpuservice.instance.search.type.placeholder': 'Поиск по имени',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Поиск по имени шаблона, образу или пути монтирования',
|
'Поиск по имени шаблона, образу или пути монтирования',
|
||||||
|
|||||||
@@ -287,6 +287,6 @@ export default {
|
|||||||
'common.image.limit.width': 'Image width must be {width}.',
|
'common.image.limit.width': 'Image width must be {width}.',
|
||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': 'Kalan {count}',
|
'common.remaining': 'Kalan {count}',
|
||||||
'common.max': 'Maksimum {count}',
|
'common.max': 'Maks. {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.validate.group': 'Please complete the {group} configuration'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export default {
|
|||||||
'gpuservice.template.containerDisk': 'Konteyner Diski (GB)',
|
'gpuservice.template.containerDisk': 'Konteyner Diski (GB)',
|
||||||
'gpuservice.template.memory': 'Bellek (GB)',
|
'gpuservice.template.memory': 'Bellek (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'Konteyner Diski (Kalan {count} GB)',
|
'Konteyner Diski (Maks. {count} GB)',
|
||||||
'gpuservice.instance.memory.remaining': 'Bellek (Kalan {count} GB)',
|
'gpuservice.instance.memory.remaining': 'Bellek (Maks. {count} GB)',
|
||||||
'gpuservice.template.displayName': 'Görünen Ad',
|
'gpuservice.template.displayName': 'Görünen Ad',
|
||||||
'gpuservice.template.displayName.max':
|
'gpuservice.template.displayName.max':
|
||||||
'Görünen ad 63 karakterden uzun olamaz.',
|
'Görünen ad 63 karakterden uzun olamaz.',
|
||||||
@@ -98,6 +98,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Bölünmüş',
|
'gpuservice.instance.sliced': 'Bölünmüş',
|
||||||
'gpuservice.instance.memory': 'Bellek',
|
'gpuservice.instance.memory': 'Bellek',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.disk': 'Disk',
|
||||||
'gpuservice.instance.search.type.placeholder': 'Ada göre ara',
|
'gpuservice.instance.search.type.placeholder': 'Ada göre ara',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Şablon adına, imaja veya bağlama yoluna göre ara',
|
'Şablon adına, imaja veya bağlama yoluna göre ara',
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': '切分',
|
'gpuservice.instance.sliced': '切分',
|
||||||
'gpuservice.instance.memory': '显存',
|
'gpuservice.instance.memory': '显存',
|
||||||
'gpuservice.instance.ram': '内存',
|
'gpuservice.instance.ram': '内存',
|
||||||
|
'gpuservice.instance.disk': '磁盘',
|
||||||
'gpuservice.instance.search.type.placeholder': '搜索名称',
|
'gpuservice.instance.search.type.placeholder': '搜索名称',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'搜索模板名称、镜像或挂载路径',
|
'搜索模板名称、镜像或挂载路径',
|
||||||
|
|||||||
@@ -46,13 +46,9 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
|
|||||||
{ length: Math.max(0, maxCount) },
|
{ length: Math.max(0, maxCount) },
|
||||||
(_, i) => i + 1
|
(_, i) => i + 1
|
||||||
);
|
);
|
||||||
const computedItems = showCustomInput
|
const items = presetItems;
|
||||||
? presetItems.filter((n) => n >= min && n <= max)
|
const isItemDisabled = (num: number) =>
|
||||||
: Array.from(
|
!!disabled || num > max || num < min;
|
||||||
{ length: Math.max(0, Math.floor((max - min) / step) + 1) },
|
|
||||||
(_, i) => min + i * step
|
|
||||||
);
|
|
||||||
const items = computedItems.length === 0 ? [0] : computedItems;
|
|
||||||
const [inputValue, setInputValue] = useState<number | null>(() =>
|
const [inputValue, setInputValue] = useState<number | null>(() =>
|
||||||
value !== undefined && value !== null && !presetItems.includes(value)
|
value !== undefined && value !== null && !presetItems.includes(value)
|
||||||
? value
|
? value
|
||||||
@@ -70,7 +66,7 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
|
|||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const handleSelect = (num: number) => {
|
const handleSelect = (num: number) => {
|
||||||
if (disabled || num === value) {
|
if (isItemDisabled(num) || num === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setInputValue(null);
|
setInputValue(null);
|
||||||
@@ -108,15 +104,17 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
|
|||||||
<Flex className={styles.contentWrapper} align="center">
|
<Flex className={styles.contentWrapper} align="center">
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{items.map((num) => {
|
{items.map((num) => {
|
||||||
|
const itemDisabled = isItemDisabled(num);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={num}
|
key={num}
|
||||||
role="radio"
|
role="radio"
|
||||||
aria-checked={num === value}
|
aria-checked={num === value}
|
||||||
aria-disabled={disabled}
|
aria-disabled={itemDisabled}
|
||||||
tabIndex={disabled ? -1 : 0}
|
tabIndex={itemDisabled ? -1 : 0}
|
||||||
className={classNames(styles.numberItem, {
|
className={classNames(styles.numberItem, {
|
||||||
[styles.active]: num === value && !!value
|
[styles.active]: num === value && !!value,
|
||||||
|
[styles.itemDisabled]: itemDisabled && !disabled
|
||||||
})}
|
})}
|
||||||
onClick={() => handleSelect(num)}
|
onClick={() => handleSelect(num)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
|||||||
@@ -85,6 +85,13 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-color: var(--ant-color-split);
|
border-color: var(--ant-color-split);
|
||||||
}
|
}
|
||||||
|
&.itemDisabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: var(--ant-color-text-disabled);
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ const MetaItem: React.FC<{
|
|||||||
|
|
||||||
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
|
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const name = item.name;
|
|
||||||
const specData = item.spec || {};
|
const specData = item.spec || {};
|
||||||
const acceleratable = specData.acceleratable;
|
const acceleratable = specData.acceleratable;
|
||||||
|
|
||||||
@@ -93,11 +92,9 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
|
|||||||
const remainingData = item.status?.remaining || {};
|
const remainingData = item.status?.remaining || {};
|
||||||
|
|
||||||
const renderName = () => {
|
const renderName = () => {
|
||||||
const product = specData.product;
|
const displayName = specData.acceleratable
|
||||||
const cpuCapacity = remainingData.cpu;
|
? specData.product || item.name
|
||||||
const displayName =
|
: 'CPU';
|
||||||
product ||
|
|
||||||
(!acceleratable && cpuCapacity ? `${cpuCapacity} vCPUs` : name);
|
|
||||||
return displayName;
|
return displayName;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -162,7 +159,7 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
|
|||||||
value={toDisplayUnit(convertKiToGi(remainingData.ram)) ?? '-'}
|
value={toDisplayUnit(convertKiToGi(remainingData.ram)) ?? '-'}
|
||||||
></MetaItem>
|
></MetaItem>
|
||||||
<MetaItem
|
<MetaItem
|
||||||
show={acceleratable}
|
show={!!remainingData.cpu}
|
||||||
showDot={true}
|
showDot={true}
|
||||||
icon="icon-cpu"
|
icon="icon-cpu"
|
||||||
label="vCPU"
|
label="vCPU"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PageAction, validateLabelNameRegxFor63 } from '@/config';
|
import { PageAction, validateLabelNameRegxFor63 } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { Input as CInput } from '@gpustack/core-ui';
|
import { Input as CInput, useAppUtils } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import OwnerPrincipalIdField from '../../../_components/owner-principal-id-field';
|
import OwnerPrincipalIdField from '../../../_components/owner-principal-id-field';
|
||||||
@@ -15,6 +15,7 @@ const Basic = ({
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OwnerPrincipalIdField name="owner_principal_id" />
|
<OwnerPrincipalIdField name="owner_principal_id" />
|
||||||
@@ -24,9 +25,7 @@ const Basic = ({
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: intl.formatMessage({
|
message: getRuleMessage('input', 'common.table.name')
|
||||||
id: 'gpuservice.instance.name.required'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: validateLabelNameRegxFor63,
|
pattern: validateLabelNameRegxFor63,
|
||||||
@@ -36,7 +35,7 @@ const Basic = ({
|
|||||||
>
|
>
|
||||||
<CInput.Input
|
<CInput.Input
|
||||||
disabled={disabled || action === PageAction.EDIT}
|
disabled={disabled || action === PageAction.EDIT}
|
||||||
label={intl.formatMessage({ id: 'gpuservice.instance.name' })}
|
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -540,10 +540,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: getRuleMessage(
|
message: getRuleMessage('select', 'gpuservice.publicKey')
|
||||||
'select',
|
|
||||||
'gpuservice.instance.ssh.assignKey'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
|||||||
fontWeight: 400
|
fontWeight: 400
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{description.manufacturer
|
{description.acceleratable
|
||||||
? `${description.product} x ${currentData?.spec?.resources?.accelerator}`
|
? `${description.product} x ${currentData?.spec?.resources?.accelerator}`
|
||||||
: ''}
|
: 'CPU'}
|
||||||
</span>
|
</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</SelectedCard>
|
</SelectedCard>
|
||||||
@@ -185,7 +185,12 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
|||||||
></Alert>
|
></Alert>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })}
|
label={`${intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })} (${intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: 'common.max'
|
||||||
|
},
|
||||||
|
{ count: maxGpuCount }
|
||||||
|
)})`}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { PageAction } from '@/config';
|
|||||||
import {
|
import {
|
||||||
Input as CInput,
|
Input as CInput,
|
||||||
InputNumber as CInputNumber,
|
InputNumber as CInputNumber,
|
||||||
CheckboxField,
|
|
||||||
LabelInfo,
|
LabelInfo,
|
||||||
Select,
|
Select,
|
||||||
useAppUtils
|
useAppUtils
|
||||||
@@ -14,21 +13,17 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
import { FormData as StorageFormData } from '../../storage/config/types';
|
import { FormData as StorageFormData } from '../../storage/config/types';
|
||||||
import useCreateStorage from '../../storage/services/use-create-storage';
|
import useCreateStorage from '../../storage/services/use-create-storage';
|
||||||
import useQueryStorage from '../../storage/services/use-query-storage';
|
import useQueryStorage from '../../storage/services/use-query-storage';
|
||||||
import useQueryStorageTypes from '../../storage-types/services/use-query-storage-types';
|
import { StorageModeValueMap } from '../config';
|
||||||
import { DEFAULT_PV_CAPACITY_GB, StorageModeValueMap } from '../config';
|
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
import StorageOverlay from './storage-overlay';
|
import StorageOverlay from './storage-overlay';
|
||||||
|
|
||||||
const DEFAULT_TEMP_CAPACITY_GB = 50;
|
const DEFAULT_TEMP_CAPACITY_GB = 50;
|
||||||
|
|
||||||
const detectMode = (volume?: FormData['spec']['volume']) => {
|
const detectMode = (volume?: FormData['spec']['volume']) => {
|
||||||
if (volume?.persistentTemplate) {
|
if (volume?.persistent || volume?.persistentTemplate) {
|
||||||
return { mode: StorageModeValueMap.Persistent, releaseWithInstance: true };
|
return StorageModeValueMap.Persistent;
|
||||||
}
|
}
|
||||||
if (volume?.persistent) {
|
return StorageModeValueMap.Temporary;
|
||||||
return { mode: StorageModeValueMap.Persistent, releaseWithInstance: false };
|
|
||||||
}
|
|
||||||
return { mode: StorageModeValueMap.Temporary, releaseWithInstance: false };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const StorageVolume = ({
|
const StorageVolume = ({
|
||||||
@@ -45,25 +40,19 @@ const StorageVolume = ({
|
|||||||
const [storageMode, setStorageMode] = useState<string>(
|
const [storageMode, setStorageMode] = useState<string>(
|
||||||
StorageModeValueMap.Temporary
|
StorageModeValueMap.Temporary
|
||||||
);
|
);
|
||||||
const [releaseWithInstance, setReleaseWithInstance] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentVolume) return;
|
if (!currentVolume) return;
|
||||||
const detected = detectMode(currentVolume);
|
setStorageMode(detectMode(currentVolume));
|
||||||
setStorageMode(detected.mode);
|
|
||||||
setReleaseWithInstance(detected.releaseWithInstance);
|
|
||||||
}, [currentVolume]);
|
}, [currentVolume]);
|
||||||
|
|
||||||
const { fetchData: createStorage } = useCreateStorage();
|
const { fetchData: createStorage } = useCreateStorage();
|
||||||
const { detailData: storageData, fetchData: fetchStorage } =
|
const { detailData: storageData, fetchData: fetchStorage } =
|
||||||
useQueryStorage();
|
useQueryStorage();
|
||||||
const { detailData: storageTypesData, fetchData: fetchStorageTypes } =
|
|
||||||
useQueryStorageTypes();
|
|
||||||
const [overlayOpen, setOverlayOpen] = useState(false);
|
const [overlayOpen, setOverlayOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchStorage({ page: 1, perPage: 100 });
|
fetchStorage({ page: 1, perPage: 100 });
|
||||||
fetchStorageTypes({ page: 1, perPage: 100 });
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const storageOptions = useMemo(
|
const storageOptions = useMemo(
|
||||||
@@ -75,43 +64,19 @@ const StorageVolume = ({
|
|||||||
[storageData]
|
[storageData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const storageTypeOptions = useMemo(
|
const applyMode = (mode: string) => {
|
||||||
() =>
|
|
||||||
(storageTypesData?.items || []).map((item) => ({
|
|
||||||
label: item.displayName || item.name,
|
|
||||||
value: item.name
|
|
||||||
})),
|
|
||||||
[storageTypesData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const applyMode = (mode: string, release: boolean) => {
|
|
||||||
if (mode === StorageModeValueMap.Temporary) {
|
if (mode === StorageModeValueMap.Temporary) {
|
||||||
form.setFieldValue(['spec', 'volume'], {
|
form.setFieldValue(['spec', 'volume'], {
|
||||||
ephemeral: { capacity: `${DEFAULT_TEMP_CAPACITY_GB}Gi` }
|
ephemeral: { capacity: `${DEFAULT_TEMP_CAPACITY_GB}Gi` }
|
||||||
});
|
});
|
||||||
return;
|
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: '' } });
|
form.setFieldValue(['spec', 'volume'], { persistent: { name: '' } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModeChange = (mode: string) => {
|
const handleModeChange = (mode: string) => {
|
||||||
setStorageMode(mode);
|
setStorageMode(mode);
|
||||||
applyMode(mode, releaseWithInstance);
|
applyMode(mode);
|
||||||
};
|
|
||||||
|
|
||||||
const handleReleaseChange = (e: any) => {
|
|
||||||
const next = !!e?.target?.checked;
|
|
||||||
setReleaseWithInstance(next);
|
|
||||||
applyMode(storageMode, next);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateStorage = async (values: StorageFormData) => {
|
const handleCreateStorage = async (values: StorageFormData) => {
|
||||||
@@ -130,48 +95,60 @@ const StorageVolume = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div data-field="storage"></div>
|
<div data-field="storage"></div>
|
||||||
<Radio.Group
|
<Flex align="center" justify="space-between" style={{ marginBlock: 16 }}>
|
||||||
disabled={disabled}
|
<Radio.Group
|
||||||
style={{ marginBottom: 12 }}
|
disabled={disabled}
|
||||||
value={storageMode}
|
value={storageMode}
|
||||||
onChange={(e) => handleModeChange(e.target.value)}
|
style={{ display: 'flex', gap: 12 }}
|
||||||
options={[
|
onChange={(e) => handleModeChange(e.target.value)}
|
||||||
{
|
options={[
|
||||||
label: (
|
{
|
||||||
<LabelInfo
|
label: (
|
||||||
description={intl.formatMessage({
|
<LabelInfo
|
||||||
id: 'gpuservice.storage.temporary.tips'
|
description={intl.formatMessage({
|
||||||
})}
|
id: 'gpuservice.storage.temporary.tips'
|
||||||
label={
|
})}
|
||||||
<span className="text-primary">
|
label={
|
||||||
{intl.formatMessage({
|
<span className="text-primary">
|
||||||
id: 'gpuservice.storage.temporary'
|
{intl.formatMessage({
|
||||||
})}
|
id: 'gpuservice.storage.temporary'
|
||||||
</span>
|
})}
|
||||||
}
|
</span>
|
||||||
/>
|
}
|
||||||
),
|
/>
|
||||||
value: StorageModeValueMap.Temporary
|
),
|
||||||
},
|
value: StorageModeValueMap.Temporary
|
||||||
{
|
},
|
||||||
label: (
|
{
|
||||||
<LabelInfo
|
label: (
|
||||||
description={intl.formatMessage({
|
<LabelInfo
|
||||||
id: 'gpuservice.storage.persistentVolume.tips'
|
description={intl.formatMessage({
|
||||||
})}
|
id: 'gpuservice.storage.persistentVolume.tips'
|
||||||
label={
|
})}
|
||||||
<span className="text-primary">
|
label={
|
||||||
{intl.formatMessage({
|
<span className="text-primary">
|
||||||
id: 'gpuservice.storage.persistentVolume'
|
{intl.formatMessage({
|
||||||
})}
|
id: 'gpuservice.storage.persistentVolume'
|
||||||
</span>
|
})}
|
||||||
}
|
</span>
|
||||||
/>
|
}
|
||||||
),
|
/>
|
||||||
value: StorageModeValueMap.Persistent
|
),
|
||||||
}
|
value: StorageModeValueMap.Persistent
|
||||||
]}
|
}
|
||||||
/>
|
]}
|
||||||
|
/>
|
||||||
|
{action === PageAction.CREATE &&
|
||||||
|
storageMode === StorageModeValueMap.Persistent && (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setOverlayOpen(true)}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
{storageMode === StorageModeValueMap.Temporary && (
|
{storageMode === StorageModeValueMap.Temporary && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -205,106 +182,26 @@ const StorageVolume = ({
|
|||||||
|
|
||||||
{storageMode === StorageModeValueMap.Persistent && (
|
{storageMode === StorageModeValueMap.Persistent && (
|
||||||
<>
|
<>
|
||||||
<Flex
|
<Form.Item
|
||||||
style={{
|
name={['spec', 'volume', 'persistent', 'name']}
|
||||||
justifyContent: 'space-between',
|
rules={[
|
||||||
alignItems: 'center',
|
{
|
||||||
marginBottom: 12
|
required: true,
|
||||||
}}
|
message: intl.formatMessage({
|
||||||
|
id: 'gpuservice.storage.persistentVolume.required'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<CheckboxField
|
<Select
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
checked={releaseWithInstance}
|
|
||||||
onChange={handleReleaseChange}
|
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: 'gpuservice.storage.persistentVolume.releaseWithInstance'
|
id: 'gpuservice.storage.persistentVolume'
|
||||||
})}
|
})}
|
||||||
|
required
|
||||||
|
options={storageOptions}
|
||||||
/>
|
/>
|
||||||
{action === PageAction.CREATE && !releaseWithInstance && (
|
</Form.Item>
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setOverlayOpen(true)}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{releaseWithInstance ? (
|
|
||||||
<>
|
|
||||||
<Form.Item
|
|
||||||
name={['spec', 'volume', 'persistentTemplate', 'spec', 'type']}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('select', 'gpuservice.storageType')
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
required
|
|
||||||
disabled={disabled}
|
|
||||||
label={intl.formatMessage({ id: 'gpuservice.storageType' })}
|
|
||||||
options={storageTypeOptions}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name={[
|
|
||||||
'spec',
|
|
||||||
'volume',
|
|
||||||
'persistentTemplate',
|
|
||||||
'spec',
|
|
||||||
'capacity'
|
|
||||||
]}
|
|
||||||
getValueProps={(val) => ({
|
|
||||||
value: val
|
|
||||||
? Number(String(val).replace(/Gi$/i, '')) || undefined
|
|
||||||
: undefined
|
|
||||||
})}
|
|
||||||
normalize={(val) => (val ? `${val}Gi` : '')}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: intl.formatMessage({
|
|
||||||
id: 'gpuservice.storage.persistentVolume.capacity.required'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<CInputNumber
|
|
||||||
required
|
|
||||||
disabled={disabled}
|
|
||||||
min={1}
|
|
||||||
precision={0}
|
|
||||||
label={intl.formatMessage({
|
|
||||||
id: 'gpuservice.storage.persistentVolume.capacity'
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Form.Item
|
|
||||||
name={['spec', 'volume', 'persistent', 'name']}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: intl.formatMessage({
|
|
||||||
id: 'gpuservice.storage.persistentVolume.required'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
disabled={disabled}
|
|
||||||
label={intl.formatMessage({
|
|
||||||
id: 'gpuservice.storage.persistentVolume'
|
|
||||||
})}
|
|
||||||
required
|
|
||||||
options={storageOptions}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import _ from 'lodash';
|
|||||||
import { Fragment, useMemo } from 'react';
|
import { Fragment, useMemo } from 'react';
|
||||||
import { InstanceStatusLabelMap, rowActionList, status } from '../config';
|
import { InstanceStatusLabelMap, rowActionList, status } from '../config';
|
||||||
import { ListItem } from '../config/types';
|
import { ListItem } from '../config/types';
|
||||||
|
import tableSyles from '../styles/table.module.less';
|
||||||
|
|
||||||
type ConnectEntry =
|
type ConnectEntry =
|
||||||
| {
|
| {
|
||||||
@@ -66,27 +67,6 @@ const getConnectEntries = (record: ListItem): ConnectEntry[] => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderInstanceType = (record: ListItem) => {
|
|
||||||
const description = JSON.parse(record?.description || '{}').spec || {};
|
|
||||||
return (
|
|
||||||
<Flex align="flex-start" orientation="vertical">
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: 'var(--ant-color-text)',
|
|
||||||
fontWeight: 400
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{description.manufacturer
|
|
||||||
? `${description.product} x ${record.spec?.resources?.accelerator}`
|
|
||||||
: ''}
|
|
||||||
</span>
|
|
||||||
<span className="text-tertiary">
|
|
||||||
{`${record.spec.resources?.cpu}C / RAM: ${record.spec.resources?.ram?.replace('Gi', 'GB')} / Disk: ${record.spec.resources?.localStorage?.replace('Gi', 'GB')}`}{' '}
|
|
||||||
</span>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ColumnsHookProps {
|
interface ColumnsHookProps {
|
||||||
handleSelect: (val: string, record: ListItem) => void;
|
handleSelect: (val: string, record: ListItem) => void;
|
||||||
sortOrder: string[];
|
sortOrder: string[];
|
||||||
@@ -97,6 +77,36 @@ const useInstancesColumns = ({
|
|||||||
sortOrder
|
sortOrder
|
||||||
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const renderInstanceType = (record: ListItem) => {
|
||||||
|
const description = JSON.parse(record?.description || '{}').spec || {};
|
||||||
|
return (
|
||||||
|
<Flex align="flex-start" orientation="vertical">
|
||||||
|
<span className="text-primary">
|
||||||
|
{description.acceleratable
|
||||||
|
? `${description.product} x ${record.spec?.resources?.accelerator}`
|
||||||
|
: 'CPU'}
|
||||||
|
</span>
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
style={{ fontSize: 13, color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
>
|
||||||
|
<span>{record.spec.resources?.cpu}C</span>
|
||||||
|
<span className={tableSyles.dot} />
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}:{' '}
|
||||||
|
{record.spec.resources?.ram?.replace('Gi', 'GB')}
|
||||||
|
</span>
|
||||||
|
<span className={tableSyles.dot} />
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'gpuservice.instance.disk' })}:{' '}
|
||||||
|
{record.spec.resources?.localStorage?.replace('Gi', 'GB')}
|
||||||
|
</span>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -116,6 +126,9 @@ const useInstancesColumns = ({
|
|||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.connect' }),
|
title: intl.formatMessage({ id: 'gpuservice.instance.connect' }),
|
||||||
key: 'connect',
|
key: 'connect',
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
render: (_text, record: ListItem) => {
|
render: (_text, record: ListItem) => {
|
||||||
const entries = getConnectEntries(record);
|
const entries = getConnectEntries(record);
|
||||||
|
|
||||||
@@ -199,6 +212,9 @@ const useInstancesColumns = ({
|
|||||||
dataIndex: ['status', 'phase'],
|
dataIndex: ['status', 'phase'],
|
||||||
key: 'status',
|
key: 'status',
|
||||||
sorter: false,
|
sorter: false,
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
render: (value: string, record: ListItem) => {
|
render: (value: string, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<StatusTag
|
<StatusTag
|
||||||
@@ -212,7 +228,7 @@ const useInstancesColumns = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Instance Type',
|
title: intl.formatMessage({ id: 'gpuservice.instance.section.type' }),
|
||||||
dataIndex: ['spec', 'type'],
|
dataIndex: ['spec', 'type'],
|
||||||
key: 'type',
|
key: 'type',
|
||||||
sorter: false,
|
sorter: false,
|
||||||
@@ -220,12 +236,9 @@ const useInstancesColumns = ({
|
|||||||
showTitle: false
|
showTitle: false
|
||||||
},
|
},
|
||||||
render: (_text: string, record: ListItem) => (
|
render: (_text: string, record: ListItem) => (
|
||||||
<AutoTooltip
|
<div style={{ width: 'max-content' }}>
|
||||||
ghost
|
<AutoTooltip ghost>{renderInstanceType(record)}</AutoTooltip>
|
||||||
style={{ maxWidth: 360, color: 'var(--ant-color-text) !important' }}
|
</div>
|
||||||
>
|
|
||||||
{renderInstanceType(record)}
|
|
||||||
</AutoTooltip>
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -246,6 +259,9 @@ const useInstancesColumns = ({
|
|||||||
title: intl.formatMessage({ id: 'common.table.operation' }),
|
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||||
key: 'operation',
|
key: 'operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
render: (_text, record) => (
|
render: (_text, record) => (
|
||||||
<DropdownButtons
|
<DropdownButtons
|
||||||
items={rowActionList}
|
items={rowActionList}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.dot {
|
||||||
|
display: flex;
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
margin-inline: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--ant-color-text-quaternary);
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
@@ -150,6 +150,7 @@ const PortItem: React.FC<PortItemProps & { disabled?: boolean }> = ({
|
|||||||
<div className={styles.port}>
|
<div className={styles.port}>
|
||||||
<CInputNumber
|
<CInputNumber
|
||||||
min={PORT_MIN}
|
min={PORT_MIN}
|
||||||
|
controls={false}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
precision={0}
|
precision={0}
|
||||||
value={item.port}
|
value={item.port}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-wrap: wrap;
|
// flex-wrap: wrap;
|
||||||
.select {
|
.select {
|
||||||
flex: 1 1 120px;
|
flex: 1 1 100px;
|
||||||
min-width: 120px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
.port {
|
.port {
|
||||||
flex: 1 1 120px;
|
flex: 1 1 100px;
|
||||||
min-width: 120px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
flex: 100 1 180px;
|
flex: 100 1 160px;
|
||||||
min-width: 180px;
|
min-width: 160px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user