fix: ux issues
This commit is contained in:
@@ -31,13 +31,6 @@ type AddModalProps = {
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
type InstanceTypeSelection = {
|
||||
instanceType?: string;
|
||||
manufacturer?: string;
|
||||
};
|
||||
|
||||
const EMPTY_INSTANCE_TYPE_SELECTION: InstanceTypeSelection = {};
|
||||
|
||||
const matchKeyword = (fields: Array<unknown>, keyword: string) => {
|
||||
const trimmed = keyword.trim().toLowerCase();
|
||||
if (!trimmed) return true;
|
||||
@@ -85,11 +78,17 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
const intl = useIntl();
|
||||
const form = useRef<any>(null);
|
||||
const sessionRef = useRef(0);
|
||||
const [instanceTypeSelection, setInstanceTypeSelection] =
|
||||
useState<InstanceTypeSelection>(EMPTY_INSTANCE_TYPE_SELECTION);
|
||||
const [instanceTypeSelection, setInstanceTypeSelection] = useState<{
|
||||
instanceType?: string;
|
||||
manufacturer?: string;
|
||||
}>({
|
||||
instanceType: undefined,
|
||||
manufacturer: undefined
|
||||
});
|
||||
const [templateId, setTemplateId] = useState<number | undefined>();
|
||||
const [instanceKeyword, setInstanceKeyword] = useState('');
|
||||
const [templateKeyword, setTemplateKeyword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const {
|
||||
detailData,
|
||||
@@ -116,6 +115,20 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const saveInstanceDataInDescription = (instanceType: InstanceTypeItem) => {
|
||||
return JSON.stringify(
|
||||
{
|
||||
name: instanceType.name,
|
||||
spec: {
|
||||
...instanceType.spec
|
||||
}
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
};
|
||||
|
||||
// apply the selection of instance type and template
|
||||
const applySelection = (
|
||||
instanceType: InstanceTypeItem,
|
||||
template: TemplateItem | undefined
|
||||
@@ -126,37 +139,29 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
instanceType: instanceType.name,
|
||||
manufacturer
|
||||
});
|
||||
|
||||
setTemplateId(template?.id);
|
||||
|
||||
if (template) {
|
||||
const currentSpec = form.current?.getFieldsValue()?.spec || {};
|
||||
const formValues = form.current?.getFieldsValue();
|
||||
form.current?.setFieldsValue({
|
||||
manufacturer: template.manufacturer,
|
||||
description: JSON.stringify(
|
||||
{
|
||||
name: instanceType.name,
|
||||
spec: {
|
||||
...instanceType.spec
|
||||
}
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
description: saveInstanceDataInDescription(instanceType),
|
||||
spec: {
|
||||
...currentSpec,
|
||||
...formValues?.spec,
|
||||
...template.spec,
|
||||
resources: {
|
||||
...(currentSpec.resources || {}),
|
||||
...(template.spec?.resources || {})
|
||||
sshPublicKeys: formValues?.spec?.sshPublicKeys,
|
||||
volume: {
|
||||
...formValues?.spec?.volume
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
form.current?.setFieldsValue({
|
||||
manufacturer: undefined,
|
||||
description: JSON.stringify(instanceType.spec, null, 2)
|
||||
description: saveInstanceDataInDescription(instanceType)
|
||||
});
|
||||
}
|
||||
|
||||
// update form
|
||||
form.current?.applyInstanceType?.(instanceType);
|
||||
};
|
||||
|
||||
@@ -175,6 +180,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
// initial
|
||||
const applyAutoSelection = (
|
||||
instanceTypes: InstanceTypeItem[],
|
||||
templates: TemplateItem[]
|
||||
@@ -208,7 +214,10 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
sessionRef.current += 1;
|
||||
setInstanceTypeSelection(EMPTY_INSTANCE_TYPE_SELECTION);
|
||||
setInstanceTypeSelection({
|
||||
instanceType: undefined,
|
||||
manufacturer: undefined
|
||||
});
|
||||
setTemplateId(undefined);
|
||||
setInstanceKeyword('');
|
||||
setTemplateKeyword('');
|
||||
@@ -240,6 +249,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return matchKeyword(
|
||||
[item.name, item.spec?.image, item.spec?.volumeMount],
|
||||
templateKeyword
|
||||
@@ -256,9 +266,14 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
};
|
||||
|
||||
const onFinish = async (values: FormData) => {
|
||||
onOk({
|
||||
...values
|
||||
});
|
||||
setLoading(true);
|
||||
try {
|
||||
await onOk({
|
||||
...values
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstanceTypeChange = (item: InstanceTypeItem) => {
|
||||
@@ -271,10 +286,19 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
|
||||
const handleTemplateChange = (id: number, item: TemplateItem) => {
|
||||
setTemplateId(id);
|
||||
const formValues = form.current?.getFieldsValue();
|
||||
form.current?.setFieldsValue({
|
||||
spec: {
|
||||
...form.current?.getFieldsValue()?.spec,
|
||||
...item.spec
|
||||
...formValues?.spec,
|
||||
...item.spec,
|
||||
sshPublicKeys: formValues?.spec?.sshPublicKeys,
|
||||
resources: {
|
||||
...item?.spec?.resources,
|
||||
accelerator: formValues?.spec?.resources?.accelerator
|
||||
},
|
||||
volume: {
|
||||
...formValues?.spec?.volume
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -400,6 +424,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
onOk={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
showOkBtn={!readonly}
|
||||
loading={loading}
|
||||
style={{
|
||||
padding: '16px 24px 8px',
|
||||
display: 'flex',
|
||||
|
||||
@@ -34,7 +34,7 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
||||
loading
|
||||
}) => {
|
||||
const handleSelect = (item: InstanceTypeItemModel) => {
|
||||
if (!isAvailable(item)) return;
|
||||
if (!isAvailable(item) || value === item.name) return;
|
||||
onChange?.(item);
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ type ViewEventsModalProps = {
|
||||
name: string;
|
||||
namespace: string;
|
||||
clusterID?: number;
|
||||
hasPersistentVolume?: boolean;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
@@ -31,7 +32,8 @@ const eventTypeStatus: Record<string, 'success' | 'warning' | 'error'> = {
|
||||
|
||||
const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const { open, onCancel, name, namespace, clusterID } = props || {};
|
||||
const { open, onCancel, name, namespace, clusterID, hasPersistentVolume } =
|
||||
props || {};
|
||||
const [activeKey, setActiveKey] = useState('instance');
|
||||
|
||||
const {
|
||||
@@ -54,7 +56,9 @@ const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
||||
const refreshAll = () => {
|
||||
if (!name || !namespace || !clusterID) return;
|
||||
fetchInstanceEvents({ name, namespace, clusterID });
|
||||
fetchVolumeEvents({ name, namespace, clusterID });
|
||||
if (hasPersistentVolume) {
|
||||
fetchVolumeEvents({ name, namespace, clusterID });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -184,16 +188,20 @@ const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
||||
}),
|
||||
children: renderTable(instanceEvents, instanceLoading)
|
||||
},
|
||||
{
|
||||
key: 'volume',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.event.tab.volume'
|
||||
}),
|
||||
children: renderTable(volumeEvents, volumeLoading)
|
||||
}
|
||||
...(hasPersistentVolume
|
||||
? [
|
||||
{
|
||||
key: 'volume',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.event.tab.volume'
|
||||
}),
|
||||
children: renderTable(volumeEvents, volumeLoading)
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
const isLoading = instanceLoading || volumeLoading;
|
||||
const isLoading = instanceLoading || (hasPersistentVolume && volumeLoading);
|
||||
|
||||
return (
|
||||
<ScrollerModal
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { Button, Empty, Form } from 'antd';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
@@ -229,6 +230,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
instanceType.status?.acceleratorTiers,
|
||||
count
|
||||
);
|
||||
|
||||
setSelectedInstanceType(instanceType);
|
||||
setOnceMaxRequest({
|
||||
cpu: parseQuantityToNumber(candidate?.cpu?.onceMaxRequest),
|
||||
@@ -237,11 +239,11 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
});
|
||||
|
||||
form.setFieldsValue({
|
||||
clusterId: candidate?.cluster ? Number(candidate.cluster) : null,
|
||||
clusterId: candidate?.cluster ? _.toNumber(candidate.cluster) : null,
|
||||
spec: {
|
||||
type: candidate?.name || '',
|
||||
...(options.writeAccelerator
|
||||
? { resources: { accelerator: count } }
|
||||
? { resources: { accelerator: _.toString(count) } }
|
||||
: {})
|
||||
} as any
|
||||
});
|
||||
@@ -278,91 +280,23 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
}
|
||||
|
||||
if (
|
||||
currentData &&
|
||||
(action === PageAction.EDIT ||
|
||||
action === PageAction.VIEW ||
|
||||
realAction === PageAction.CREATE)
|
||||
action === PageAction.EDIT ||
|
||||
action === PageAction.VIEW ||
|
||||
realAction === PageAction.CREATE
|
||||
) {
|
||||
// The server returns `accelerator` as a string; coerce to a number
|
||||
// so NumberSelection's strict equality picks up the active item.
|
||||
const persistedAccelerator = currentData.spec?.resources?.accelerator;
|
||||
const acceleratorAsNumber =
|
||||
persistedAccelerator != null && persistedAccelerator !== ''
|
||||
? Number(persistedAccelerator)
|
||||
: undefined;
|
||||
form.setFieldsValue({
|
||||
name: currentData.name,
|
||||
displayName: currentData.displayName,
|
||||
description: currentData.description,
|
||||
clusterId: currentData.clusterId,
|
||||
spec: {
|
||||
...currentData.spec,
|
||||
imagePullPolicy:
|
||||
currentData.spec?.imagePullPolicy || DefaultImagePullPolicy,
|
||||
resources: {
|
||||
...currentData.spec?.resources,
|
||||
accelerator: acceleratorAsNumber
|
||||
},
|
||||
sshPublicKeys:
|
||||
currentData.spec?.sshPublicKeys?.map((k) => k.name) ?? []
|
||||
} as any,
|
||||
enable_ssh: !!currentData.spec?.sshPublicKeys?.length
|
||||
...currentData,
|
||||
enable_ssh: !!currentData?.spec?.sshPublicKeys?.length
|
||||
});
|
||||
|
||||
const candidateName = currentData.spec?.type;
|
||||
const clusterId = currentData.clusterId;
|
||||
const aggregate = instanceTypeList.find((item) =>
|
||||
(item.status?.acceleratorTiers ?? []).some((tier) =>
|
||||
(tier.candidates ?? []).some(
|
||||
(c) => c.name === candidateName && Number(c.cluster) === clusterId
|
||||
)
|
||||
)
|
||||
);
|
||||
if (aggregate) {
|
||||
const count =
|
||||
Number(currentData.spec?.resources?.accelerator) ||
|
||||
(aggregate.spec?.acceleratable ? 1 : 0);
|
||||
// No accelerator rewrite — the form already has the persisted value.
|
||||
resolveAndApply(aggregate, count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [action, currentData, form, open, realAction, instanceTypeList]);
|
||||
|
||||
const handleFinish = async (values: InstanceFormValues) => {
|
||||
const selectedKeys = (values.spec as any)?.sshPublicKeys as
|
||||
| string[]
|
||||
| undefined;
|
||||
const volume = values.spec?.volume ?? {};
|
||||
const normalizedVolume = volume.persistentTemplate
|
||||
? {
|
||||
persistentTemplate: {
|
||||
...volume.persistentTemplate,
|
||||
name: volume.persistentTemplate.name || values.name || ''
|
||||
}
|
||||
}
|
||||
: volume.persistent
|
||||
? { persistent: volume.persistent }
|
||||
: volume.ephemeral
|
||||
? { ephemeral: volume.ephemeral }
|
||||
: {};
|
||||
|
||||
// `spec.type` and `clusterId` are kept in sync with the resolved
|
||||
// candidate via `resolveAndApply`, so submission is a straight pass
|
||||
// -through. The API expects `accelerator` as a string per the schema.
|
||||
const acceleratorValue = values.spec?.resources?.accelerator;
|
||||
const normalizedResources = {
|
||||
...values.spec?.resources,
|
||||
accelerator:
|
||||
acceleratorValue != null && acceleratorValue !== ''
|
||||
? String(acceleratorValue)
|
||||
: undefined
|
||||
};
|
||||
|
||||
const submittedPorts = [...(values.spec?.ports ?? [])];
|
||||
const submittedHasSSHPort = submittedPorts.some(
|
||||
(item: any) => item?.protocol === 'TCP' && item?.port === SSH_PORT
|
||||
);
|
||||
|
||||
if (values.enable_ssh && !submittedHasSSHPort) {
|
||||
submittedPorts.push({
|
||||
protocol: 'TCP',
|
||||
@@ -370,17 +304,12 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
name: 'SSH'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('submit values', values, submittedPorts);
|
||||
await onFinish({
|
||||
...values,
|
||||
..._.omit(values, ['enable_ssh']),
|
||||
spec: {
|
||||
...values.spec,
|
||||
ports: submittedPorts,
|
||||
resources: normalizedResources,
|
||||
volume: normalizedVolume,
|
||||
sshPublicKeys: values.enable_ssh
|
||||
? (selectedKeys ?? []).map((name) => ({ name }))
|
||||
: []
|
||||
ports: submittedPorts
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -451,7 +380,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
},
|
||||
volume: {
|
||||
ephemeral: {
|
||||
capacity: '20Gi'
|
||||
capacity: '50Gi'
|
||||
},
|
||||
persistent: {
|
||||
name: ''
|
||||
@@ -462,8 +391,6 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
}}
|
||||
>
|
||||
<Basic action={formAction} disabled={disabled} />
|
||||
{/* Hidden form field: the candidate.cluster resolved from the
|
||||
selected aggregate + accelerator count. */}
|
||||
<Form.Item name="clusterId" hidden>
|
||||
<CInput.Input />
|
||||
</Form.Item>
|
||||
@@ -537,6 +464,16 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
style={{
|
||||
marginBottom: 12
|
||||
}}
|
||||
normalize={(value) =>
|
||||
Array.isArray(value)
|
||||
? value?.map((item) => ({ name: item }))
|
||||
: []
|
||||
}
|
||||
getValueProps={(value) => ({
|
||||
value: Array.isArray(value)
|
||||
? value.map((item) => item?.name ?? item)
|
||||
: []
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
|
||||
@@ -79,13 +79,6 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
||||
return selectedInstanceType?.spec?.acceleratable;
|
||||
}, [selectedInstanceType, action]);
|
||||
|
||||
const selectedInstanceData = useMemo(() => {
|
||||
if (action === PageAction.EDIT) {
|
||||
return JSON.parse(currentData?.description || '{}') || {};
|
||||
}
|
||||
return selectedInstanceType;
|
||||
}, [selectedInstanceType, action, currentData]);
|
||||
|
||||
const renderInstanceType = () => {
|
||||
const description = JSON.parse(currentData?.description || '{}').spec || {};
|
||||
return (
|
||||
@@ -130,6 +123,10 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
||||
<Form.Item<FormData>
|
||||
name={['spec', 'resources', 'accelerator']}
|
||||
hidden={action === PageAction.EDIT}
|
||||
normalize={(value) => (value ? _.toString(value) : undefined)}
|
||||
getValueProps={(value) => ({
|
||||
value: value ? _.toNumber(value) : undefined
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { PageAction } from '@/config';
|
||||
import FormOverlayView from '@/pages/_components/form-overlay-view';
|
||||
import { FormContext } from '@/pages/gpu-service/storage/config/form-context';
|
||||
import useQueryStorageClass from '@/pages/gpu-service/storage/services/use-query-storage-class';
|
||||
import { ModalFooter } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { FormData as StorageFormData } from '../../storage/config/types';
|
||||
import GPUServiceStorageForm from '../../storage/forms';
|
||||
|
||||
@@ -18,8 +20,17 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
||||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { storageClassList, fetchData: fetchStorageClass } =
|
||||
useQueryStorageClass();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const formRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
fetchStorageClass({ page: -1 });
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
formRef.current?.submit();
|
||||
};
|
||||
@@ -30,7 +41,12 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
||||
};
|
||||
|
||||
const handleFinish = async (values: StorageFormData) => {
|
||||
await onSubmit(values);
|
||||
setLoading(true);
|
||||
try {
|
||||
await onSubmit(values);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getOverlayContainer = useCallback(() => {
|
||||
@@ -52,6 +68,7 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
||||
<ModalFooter
|
||||
onOk={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
loading={loading}
|
||||
style={{
|
||||
padding: '16px 24px 24px',
|
||||
display: 'flex',
|
||||
@@ -60,12 +77,14 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<GPUServiceStorageForm
|
||||
ref={formRef}
|
||||
action={PageAction.CREATE}
|
||||
open={open}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
<FormContext.Provider value={{ storageClassList }}>
|
||||
<GPUServiceStorageForm
|
||||
ref={formRef}
|
||||
action={PageAction.CREATE}
|
||||
open={open}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</FormContext.Provider>
|
||||
</FormOverlayView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -66,12 +66,18 @@ const StorageVolume = ({
|
||||
|
||||
const applyMode = (mode: string) => {
|
||||
if (mode === StorageModeValueMap.Temporary) {
|
||||
form.setFieldValue(['spec', 'volume'], {
|
||||
ephemeral: { capacity: `${DEFAULT_TEMP_CAPACITY_GB}Gi` }
|
||||
});
|
||||
form.setFieldValue(
|
||||
['spec', 'volume', 'ephemeral', 'capacity'],
|
||||
form.getFieldValue(['spec', 'volume', 'ephemeral', 'capacity']) ||
|
||||
DEFAULT_TEMP_CAPACITY_GB
|
||||
);
|
||||
return;
|
||||
}
|
||||
form.setFieldValue(['spec', 'volume'], { persistent: { name: '' } });
|
||||
form.setFieldValue(
|
||||
['spec', 'volume', 'persistent', 'name'],
|
||||
form.getFieldValue(['spec', 'volume', 'persistent', 'name']) ||
|
||||
(storageOptions[0]?.value as string)
|
||||
);
|
||||
};
|
||||
|
||||
const handleModeChange = (mode: string) => {
|
||||
@@ -82,10 +88,8 @@ const StorageVolume = ({
|
||||
const handleCreateStorage = async (values: StorageFormData) => {
|
||||
try {
|
||||
await createStorage({ data: values });
|
||||
await fetchStorage({ page: 1, perPage: 100 });
|
||||
form.setFieldValue(['spec', 'volume'], {
|
||||
persistent: { name: values.name }
|
||||
});
|
||||
await fetchStorage({ page: -1 });
|
||||
form.setFieldValue(['spec', 'volume', 'persistent', 'name'], values.name);
|
||||
setOverlayOpen(false);
|
||||
} catch (error) {
|
||||
// ignore
|
||||
|
||||
@@ -53,6 +53,12 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
||||
dataList = []
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const handleSelect = (item: TemplateItem) => {
|
||||
if (value === item.id) return;
|
||||
onChange?.(item.id, item);
|
||||
};
|
||||
|
||||
return (
|
||||
<TemplateGrid>
|
||||
{dataList.map((item: TemplateItem) => (
|
||||
@@ -63,7 +69,7 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
||||
hoverable
|
||||
height={102}
|
||||
active={value === item.id}
|
||||
onClick={() => onChange?.(item.id, item)}
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
<TemplateContent>
|
||||
<div className="name">
|
||||
|
||||
@@ -9,19 +9,23 @@ const useViewEvents = () => {
|
||||
name: string;
|
||||
namespace: string;
|
||||
clusterID?: number;
|
||||
hasPersistentVolume: boolean;
|
||||
}>({
|
||||
open: false,
|
||||
name: '',
|
||||
namespace: '',
|
||||
clusterID: undefined
|
||||
clusterID: undefined,
|
||||
hasPersistentVolume: false
|
||||
});
|
||||
|
||||
const openModal = (row?: ListItem) => {
|
||||
const volume = row?.spec?.volume;
|
||||
setOpenModalStatus({
|
||||
open: true,
|
||||
name: row?.name || '',
|
||||
namespace: row?.status?.namespace || '',
|
||||
clusterID: row?.clusterId ?? undefined
|
||||
clusterID: row?.clusterId ?? undefined,
|
||||
hasPersistentVolume: !!volume?.persistent?.name
|
||||
});
|
||||
};
|
||||
|
||||
@@ -30,7 +34,8 @@ const useViewEvents = () => {
|
||||
open: false,
|
||||
name: '',
|
||||
namespace: '',
|
||||
clusterID: undefined
|
||||
clusterID: undefined,
|
||||
hasPersistentVolume: false
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ const GPUService: React.FC = () => {
|
||||
if (openInstanceModalStatus.realAction === PageAction.CREATE) {
|
||||
await deleteGPUServiceInstance(openInstanceModalStatus.currentData!.id);
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
setTimeout(resolve, 300);
|
||||
});
|
||||
await createInstance({ data });
|
||||
} else if (openInstanceModalStatus.action === PageAction.EDIT) {
|
||||
@@ -233,6 +233,7 @@ const GPUService: React.FC = () => {
|
||||
name={openViewEventsModalStatus.name}
|
||||
namespace={openViewEventsModalStatus.namespace}
|
||||
clusterID={openViewEventsModalStatus.clusterID}
|
||||
hasPersistentVolume={openViewEventsModalStatus.hasPersistentVolume}
|
||||
onCancel={closeViewEventsModal}
|
||||
/>
|
||||
<DeleteModal ref={modalRef} />
|
||||
|
||||
Reference in New Issue
Block a user