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.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'
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
|
||||
@@ -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':
|
||||
'テンプレート名、イメージまたはマウントパスで検索',
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
|
||||
@@ -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':
|
||||
'Поиск по имени шаблона, образу или пути монтирования',
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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':
|
||||
'搜索模板名称、镜像或挂载路径',
|
||||
|
||||
@@ -46,13 +46,9 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
|
||||
{ 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<number | null>(() =>
|
||||
value !== undefined && value !== null && !presetItems.includes(value)
|
||||
? value
|
||||
@@ -70,7 +66,7 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
|
||||
}, [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<NumberSelectionProps> = ({
|
||||
<Flex className={styles.contentWrapper} align="center">
|
||||
<div className={styles.content}>
|
||||
{items.map((num) => {
|
||||
const itemDisabled = isItemDisabled(num);
|
||||
return (
|
||||
<div
|
||||
key={num}
|
||||
role="radio"
|
||||
aria-checked={num === value}
|
||||
aria-disabled={disabled}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
aria-disabled={itemDisabled}
|
||||
tabIndex={itemDisabled ? -1 : 0}
|
||||
className={classNames(styles.numberItem, {
|
||||
[styles.active]: num === value && !!value
|
||||
[styles.active]: num === value && !!value,
|
||||
[styles.itemDisabled]: itemDisabled && !disabled
|
||||
})}
|
||||
onClick={() => handleSelect(num)}
|
||||
onKeyDown={(e) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -80,7 +80,6 @@ const MetaItem: React.FC<{
|
||||
|
||||
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ item }) => {
|
||||
const intl = useIntl();
|
||||
const name = item.name;
|
||||
const specData = item.spec || {};
|
||||
const acceleratable = specData.acceleratable;
|
||||
|
||||
@@ -93,11 +92,9 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({ 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<InstanceTypeItemProps> = ({ item }) => {
|
||||
value={toDisplayUnit(convertKiToGi(remainingData.ram)) ?? '-'}
|
||||
></MetaItem>
|
||||
<MetaItem
|
||||
show={acceleratable}
|
||||
show={!!remainingData.cpu}
|
||||
showDot={true}
|
||||
icon="icon-cpu"
|
||||
label="vCPU"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PageAction, validateLabelNameRegxFor63 } from '@/config';
|
||||
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 { Form } from 'antd';
|
||||
import OwnerPrincipalIdField from '../../../_components/owner-principal-id-field';
|
||||
@@ -15,6 +15,7 @@ const Basic = ({
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
return (
|
||||
<>
|
||||
<OwnerPrincipalIdField name="owner_principal_id" />
|
||||
@@ -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 = ({
|
||||
>
|
||||
<CInput.Input
|
||||
disabled={disabled || action === PageAction.EDIT}
|
||||
label={intl.formatMessage({ id: 'gpuservice.instance.name' })}
|
||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||
required
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -540,10 +540,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage(
|
||||
'select',
|
||||
'gpuservice.instance.ssh.assignKey'
|
||||
)
|
||||
message: getRuleMessage('select', 'gpuservice.publicKey')
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -97,9 +97,9 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{description.manufacturer
|
||||
{description.acceleratable
|
||||
? `${description.product} x ${currentData?.spec?.resources?.accelerator}`
|
||||
: ''}
|
||||
: 'CPU'}
|
||||
</span>
|
||||
</Flex>
|
||||
</SelectedCard>
|
||||
@@ -185,7 +185,12 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
||||
></Alert>
|
||||
)
|
||||
}
|
||||
label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })}
|
||||
label={`${intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })} (${intl.formatMessage(
|
||||
{
|
||||
id: 'common.max'
|
||||
},
|
||||
{ count: maxGpuCount }
|
||||
)})`}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
@@ -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<string>(
|
||||
StorageModeValueMap.Temporary
|
||||
);
|
||||
const [releaseWithInstance, setReleaseWithInstance] = useState<boolean>(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 (
|
||||
<>
|
||||
<div data-field="storage"></div>
|
||||
<Radio.Group
|
||||
disabled={disabled}
|
||||
style={{ marginBottom: 12 }}
|
||||
value={storageMode}
|
||||
onChange={(e) => handleModeChange(e.target.value)}
|
||||
options={[
|
||||
{
|
||||
label: (
|
||||
<LabelInfo
|
||||
description={intl.formatMessage({
|
||||
id: 'gpuservice.storage.temporary.tips'
|
||||
})}
|
||||
label={
|
||||
<span className="text-primary">
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.storage.temporary'
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
),
|
||||
value: StorageModeValueMap.Temporary
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<LabelInfo
|
||||
description={intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume.tips'
|
||||
})}
|
||||
label={
|
||||
<span className="text-primary">
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume'
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
),
|
||||
value: StorageModeValueMap.Persistent
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<Flex align="center" justify="space-between" style={{ marginBlock: 16 }}>
|
||||
<Radio.Group
|
||||
disabled={disabled}
|
||||
value={storageMode}
|
||||
style={{ display: 'flex', gap: 12 }}
|
||||
onChange={(e) => handleModeChange(e.target.value)}
|
||||
options={[
|
||||
{
|
||||
label: (
|
||||
<LabelInfo
|
||||
description={intl.formatMessage({
|
||||
id: 'gpuservice.storage.temporary.tips'
|
||||
})}
|
||||
label={
|
||||
<span className="text-primary">
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.storage.temporary'
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
),
|
||||
value: StorageModeValueMap.Temporary
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<LabelInfo
|
||||
description={intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume.tips'
|
||||
})}
|
||||
label={
|
||||
<span className="text-primary">
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume'
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
),
|
||||
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 && (
|
||||
<Form.Item
|
||||
@@ -205,106 +182,26 @@ const StorageVolume = ({
|
||||
|
||||
{storageMode === StorageModeValueMap.Persistent && (
|
||||
<>
|
||||
<Flex
|
||||
style={{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12
|
||||
}}
|
||||
<Form.Item
|
||||
name={['spec', 'volume', 'persistent', 'name']}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume.required'
|
||||
})
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CheckboxField
|
||||
<Select
|
||||
disabled={disabled}
|
||||
checked={releaseWithInstance}
|
||||
onChange={handleReleaseChange}
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume.releaseWithInstance'
|
||||
id: 'gpuservice.storage.persistentVolume'
|
||||
})}
|
||||
required
|
||||
options={storageOptions}
|
||||
/>
|
||||
{action === PageAction.CREATE && !releaseWithInstance && (
|
||||
<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>
|
||||
)}
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<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 {
|
||||
handleSelect: (val: string, record: ListItem) => void;
|
||||
sortOrder: string[];
|
||||
@@ -97,6 +77,36 @@ const useInstancesColumns = ({
|
||||
sortOrder
|
||||
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
||||
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 [
|
||||
{
|
||||
@@ -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 (
|
||||
<StatusTag
|
||||
@@ -212,7 +228,7 @@ const useInstancesColumns = ({
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Instance Type',
|
||||
title: intl.formatMessage({ id: 'gpuservice.instance.section.type' }),
|
||||
dataIndex: ['spec', 'type'],
|
||||
key: 'type',
|
||||
sorter: false,
|
||||
@@ -220,12 +236,9 @@ const useInstancesColumns = ({
|
||||
showTitle: false
|
||||
},
|
||||
render: (_text: string, record: ListItem) => (
|
||||
<AutoTooltip
|
||||
ghost
|
||||
style={{ maxWidth: 360, color: 'var(--ant-color-text) !important' }}
|
||||
>
|
||||
{renderInstanceType(record)}
|
||||
</AutoTooltip>
|
||||
<div style={{ width: 'max-content' }}>
|
||||
<AutoTooltip ghost>{renderInstanceType(record)}</AutoTooltip>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -246,6 +259,9 @@ const useInstancesColumns = ({
|
||||
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||
key: 'operation',
|
||||
dataIndex: 'operation',
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
render: (_text, record) => (
|
||||
<DropdownButtons
|
||||
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}>
|
||||
<CInputNumber
|
||||
min={PORT_MIN}
|
||||
controls={false}
|
||||
disabled={disabled}
|
||||
precision={0}
|
||||
value={item.port}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
// flex-wrap: wrap;
|
||||
.select {
|
||||
flex: 1 1 120px;
|
||||
min-width: 120px;
|
||||
flex: 1 1 100px;
|
||||
min-width: 100px;
|
||||
}
|
||||
.port {
|
||||
flex: 1 1 120px;
|
||||
min-width: 120px;
|
||||
flex: 1 1 100px;
|
||||
min-width: 100px;
|
||||
}
|
||||
.name {
|
||||
flex: 100 1 180px;
|
||||
min-width: 180px;
|
||||
flex: 100 1 160px;
|
||||
min-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user