feat(gpu-service): restrict non-stopped instance edit, drop recreate
- non-stopped edit: only displayName and SSH public keys editable; type/template/storage sections disabled (stopped edit keeps all editable) - remove recreate feature and the realAction prop it required: entry, delete-then-create submit path, warning banner, locale keys
This commit is contained in:
@@ -178,9 +178,6 @@ export default {
|
|||||||
'Only events from the last hour are shown',
|
'Only events from the last hour are shown',
|
||||||
'gpuservice.instance.event.tab.instance': 'Instance Events',
|
'gpuservice.instance.event.tab.instance': 'Instance Events',
|
||||||
'gpuservice.instance.event.tab.volume': 'Volume Events',
|
'gpuservice.instance.event.tab.volume': 'Volume Events',
|
||||||
'gpuservice.instance.recreate.confirm.title': 'Confirm recreation',
|
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
|
||||||
'The current instance will be deleted first, then recreated with the current configuration.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
|
||||||
'gpuservice.storage': 'Storage',
|
'gpuservice.storage': 'Storage',
|
||||||
'gpuservice.storage.add': 'Add Storage',
|
'gpuservice.storage.add': 'Add Storage',
|
||||||
'gpuservice.storage.edit': 'Edit Storage',
|
'gpuservice.storage.edit': 'Edit Storage',
|
||||||
|
|||||||
@@ -177,9 +177,6 @@ export default {
|
|||||||
'直近 1 時間のイベントのみ表示されます',
|
'直近 1 時間のイベントのみ表示されます',
|
||||||
'gpuservice.instance.event.tab.instance': 'インスタンスイベント',
|
'gpuservice.instance.event.tab.instance': 'インスタンスイベント',
|
||||||
'gpuservice.instance.event.tab.volume': 'ボリュームイベント',
|
'gpuservice.instance.event.tab.volume': 'ボリュームイベント',
|
||||||
'gpuservice.instance.recreate.confirm.title': '再作成を確認しますか',
|
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
|
||||||
'現在のインスタンスを削除した後、現在の構成で再作成します。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
|
||||||
'gpuservice.storage': 'ストレージ',
|
'gpuservice.storage': 'ストレージ',
|
||||||
'gpuservice.storage.add': 'ストレージを追加',
|
'gpuservice.storage.add': 'ストレージを追加',
|
||||||
'gpuservice.storage.edit': 'ストレージを編集',
|
'gpuservice.storage.edit': 'ストレージを編集',
|
||||||
|
|||||||
@@ -176,9 +176,6 @@ export default {
|
|||||||
'Отображаются только события за последний час',
|
'Отображаются только события за последний час',
|
||||||
'gpuservice.instance.event.tab.instance': 'События экземпляра',
|
'gpuservice.instance.event.tab.instance': 'События экземпляра',
|
||||||
'gpuservice.instance.event.tab.volume': 'События тома',
|
'gpuservice.instance.event.tab.volume': 'События тома',
|
||||||
'gpuservice.instance.recreate.confirm.title': 'Подтвердить пересоздание',
|
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
|
||||||
'Текущий экземпляр будет сначала удалён, а затем пересоздан с текущей конфигурацией.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
|
||||||
'gpuservice.storage': 'Хранилище',
|
'gpuservice.storage': 'Хранилище',
|
||||||
'gpuservice.storage.add': 'Добавить хранилище',
|
'gpuservice.storage.add': 'Добавить хранилище',
|
||||||
'gpuservice.storage.edit': 'Редактировать хранилище',
|
'gpuservice.storage.edit': 'Редактировать хранилище',
|
||||||
|
|||||||
@@ -172,10 +172,6 @@ export default {
|
|||||||
'Yalnızca son bir saatteki olaylar gösterilir',
|
'Yalnızca son bir saatteki olaylar gösterilir',
|
||||||
'gpuservice.instance.event.tab.instance': 'Örnek Olayları',
|
'gpuservice.instance.event.tab.instance': 'Örnek Olayları',
|
||||||
'gpuservice.instance.event.tab.volume': 'Birim Olayları',
|
'gpuservice.instance.event.tab.volume': 'Birim Olayları',
|
||||||
'gpuservice.instance.recreate.confirm.title':
|
|
||||||
'Yeniden oluşturma onaylansın mı',
|
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
|
||||||
'Mevcut örnek önce silinecek, ardından mevcut yapılandırmayla yeniden oluşturulacaktır.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
|
||||||
'gpuservice.storage': 'Depolama',
|
'gpuservice.storage': 'Depolama',
|
||||||
'gpuservice.storage.add': 'Depolama Ekle',
|
'gpuservice.storage.add': 'Depolama Ekle',
|
||||||
'gpuservice.storage.edit': 'Depolamayı Düzenle',
|
'gpuservice.storage.edit': 'Depolamayı Düzenle',
|
||||||
|
|||||||
@@ -162,9 +162,6 @@ export default {
|
|||||||
'gpuservice.instance.event.recentHourTip': '仅显示最近一小时的事件。',
|
'gpuservice.instance.event.recentHourTip': '仅显示最近一小时的事件。',
|
||||||
'gpuservice.instance.event.tab.instance': '实例事件',
|
'gpuservice.instance.event.tab.instance': '实例事件',
|
||||||
'gpuservice.instance.event.tab.volume': '存储卷事件',
|
'gpuservice.instance.event.tab.volume': '存储卷事件',
|
||||||
'gpuservice.instance.recreate.confirm.title': '确认重新创建',
|
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
|
||||||
'系统将先删除当前实例,然后使用当前配置重新创建。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
|
||||||
'gpuservice.storage': '存储',
|
'gpuservice.storage': '存储',
|
||||||
'gpuservice.storage.add': '添加存储',
|
'gpuservice.storage.add': '添加存储',
|
||||||
'gpuservice.storage.edit': '编辑存储',
|
'gpuservice.storage.edit': '编辑存储',
|
||||||
|
|||||||
@@ -5,12 +5,7 @@ import useUserDirectory from '@/pages/gpu-service/hooks/use-user-directory';
|
|||||||
import Separator from '@/pages/llmodels/components/separator';
|
import Separator from '@/pages/llmodels/components/separator';
|
||||||
import { getGPUStackPlugin } from '@/plugins';
|
import { getGPUStackPlugin } from '@/plugins';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import {
|
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
AlertBlockInfo,
|
|
||||||
ColumnWrapper,
|
|
||||||
GSDrawer,
|
|
||||||
ModalFooter
|
|
||||||
} from '@gpustack/core-ui';
|
|
||||||
import { useIntl, useModel } from '@umijs/max';
|
import { useIntl, useModel } from '@umijs/max';
|
||||||
import { Input, Typography } from 'antd';
|
import { Input, Typography } from 'antd';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@@ -30,7 +25,6 @@ type AddModalProps = {
|
|||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
realAction?: string;
|
|
||||||
clusterList?: Array<{
|
clusterList?: Array<{
|
||||||
label: string;
|
label: string;
|
||||||
value: number;
|
value: number;
|
||||||
@@ -84,8 +78,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
data,
|
data,
|
||||||
onCancel,
|
onCancel,
|
||||||
width,
|
width,
|
||||||
clusterList = [],
|
clusterList = []
|
||||||
realAction
|
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { initialState } = useModel('@@initialState') || {};
|
const { initialState } = useModel('@@initialState') || {};
|
||||||
@@ -173,16 +166,18 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
);
|
);
|
||||||
// const readonly = action === PageAction.VIEW;
|
// const readonly = action === PageAction.VIEW;
|
||||||
const readonly = false;
|
const readonly = false;
|
||||||
const isRecreate = realAction === PageAction.CREATE;
|
const showResourceSelectors = action === PageAction.CREATE;
|
||||||
const showResourceSelectors = action === PageAction.CREATE || isRecreate;
|
|
||||||
const shouldAutoSelectResource = action === PageAction.CREATE && !isRecreate;
|
|
||||||
// Only a stopped instance can be re-typed on edit. It shows the instance-type
|
// Only a stopped instance can be re-typed on edit. It shows the instance-type
|
||||||
// column (but not the template column) beside the form; the create card
|
// column (but not the template column) beside the form; the create card
|
||||||
// columns render for CREATE / recreate.
|
// columns render for CREATE.
|
||||||
const isStoppedEdit =
|
const isStoppedEdit =
|
||||||
action === PageAction.EDIT &&
|
action === PageAction.EDIT &&
|
||||||
data?.status?.phase === InstanceStatusValueMap.Stopped;
|
data?.status?.phase === InstanceStatusValueMap.Stopped;
|
||||||
const showInstanceTypeColumn = showResourceSelectors || isStoppedEdit;
|
const showInstanceTypeColumn = showResourceSelectors || isStoppedEdit;
|
||||||
|
// Editing a non-stopped instance is restricted: only displayName and the
|
||||||
|
// SSH public keys stay editable; the type / template / storage sections
|
||||||
|
// render disabled. A stopped instance edits everything.
|
||||||
|
const isRestrictedEdit = action === PageAction.EDIT && !isStoppedEdit;
|
||||||
|
|
||||||
const findTemplateByManufacturer = (
|
const findTemplateByManufacturer = (
|
||||||
manufacturer: string | undefined,
|
manufacturer: string | undefined,
|
||||||
@@ -266,43 +261,12 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const findAggregateOf = (
|
|
||||||
candidateName: string | undefined,
|
|
||||||
clusterId: number | null | undefined,
|
|
||||||
instanceTypes: InstanceTypeItem[]
|
|
||||||
): InstanceTypeItem | undefined => {
|
|
||||||
if (!candidateName) return undefined;
|
|
||||||
return instanceTypes.find((item) =>
|
|
||||||
(item.status?.tiers ?? []).some((tier) =>
|
|
||||||
(tier.candidates ?? []).some(
|
|
||||||
(c) => c.name === candidateName && Number(c.cluster) === clusterId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// initial for first
|
// initial for first
|
||||||
const applyAutoSelection = (
|
const applyAutoSelection = (
|
||||||
instanceTypes: InstanceTypeItem[],
|
instanceTypes: InstanceTypeItem[],
|
||||||
templates: TemplateItem[],
|
templates: TemplateItem[],
|
||||||
orgId?: number | null
|
orgId?: number | null
|
||||||
) => {
|
) => {
|
||||||
// On edit / view, surface the persisted selection in the card list.
|
|
||||||
if (!shouldAutoSelectResource) {
|
|
||||||
const aggregate = findAggregateOf(
|
|
||||||
data?.spec?.type,
|
|
||||||
data?.clusterId,
|
|
||||||
instanceTypes
|
|
||||||
);
|
|
||||||
if (aggregate) {
|
|
||||||
setInstanceTypeSelection({
|
|
||||||
instanceType: aggregate.name,
|
|
||||||
manufacturer: manufacturerOf(aggregate)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scope to clusters the chosen org owns (admin "All" view).
|
// Scope to clusters the chosen org owns (admin "All" view).
|
||||||
const owned = filterTypesByOwner(instanceTypes, orgId);
|
const owned = filterTypesByOwner(instanceTypes, orgId);
|
||||||
|
|
||||||
@@ -376,7 +340,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
// full instance-type list to re-type a stopped instance.
|
// full instance-type list to re-type a stopped instance.
|
||||||
fetchData({ page: -1 });
|
fetchData({ page: -1 });
|
||||||
}
|
}
|
||||||
}, [open, shouldAutoSelectResource, action]);
|
}, [open, action]);
|
||||||
|
|
||||||
// filter instance types (already scoped to the chosen org's clusters)
|
// filter instance types (already scoped to the chosen org's clusters)
|
||||||
const filteredInstanceTypes = ownedInstanceTypes.filter((item) =>
|
const filteredInstanceTypes = ownedInstanceTypes.filter((item) =>
|
||||||
@@ -664,17 +628,6 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
styles={{ container: { paddingBlock: 0 } }}
|
styles={{ container: { paddingBlock: 0 } }}
|
||||||
footer={
|
footer={
|
||||||
<>
|
<>
|
||||||
{isRecreate && open && (
|
|
||||||
<div style={{ marginInline: 24, paddingTop: 8 }}>
|
|
||||||
<AlertBlockInfo
|
|
||||||
type="warning"
|
|
||||||
contentStyle={{ paddingInline: 0 }}
|
|
||||||
message={intl.formatMessage({
|
|
||||||
id: 'gpuservice.instance.recreate.confirm.content'
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ModalFooter
|
<ModalFooter
|
||||||
onOk={handleSubmit}
|
onOk={handleSubmit}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
@@ -698,9 +651,9 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
<GPUServiceInstanceForm
|
<GPUServiceInstanceForm
|
||||||
ref={form}
|
ref={form}
|
||||||
action={action}
|
action={action}
|
||||||
realAction={realAction}
|
|
||||||
currentData={data}
|
currentData={data}
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
|
restrictedEdit={isRestrictedEdit}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
onFinishFailed={release}
|
onFinishFailed={release}
|
||||||
onScopeChange={handleScopeChange}
|
onScopeChange={handleScopeChange}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ interface InstanceFormProps {
|
|||||||
ref?: any;
|
ref?: any;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
realAction?: PageActionType | string;
|
// Present on edit / view.
|
||||||
currentData?: ListItem | null;
|
currentData?: ListItem | null;
|
||||||
namespace?: string;
|
namespace?: string;
|
||||||
instanceTypeList?: InstanceTypeItem[];
|
instanceTypeList?: InstanceTypeItem[];
|
||||||
@@ -67,6 +67,9 @@ interface InstanceFormProps {
|
|||||||
// surfaces a "no available instance type" message in the scheduling tab.
|
// surfaces a "no available instance type" message in the scheduling tab.
|
||||||
noAvailableInstanceTypes?: boolean;
|
noAvailableInstanceTypes?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
// Editing a non-stopped instance: only displayName and the SSH public keys
|
||||||
|
// stay editable; the type / template / storage sections render disabled.
|
||||||
|
restrictedEdit?: boolean;
|
||||||
// Fired when the create-scope picker retargets the form to another
|
// Fired when the create-scope picker retargets the form to another
|
||||||
// org (or Global). Only emitted on genuine changes — never on the
|
// org (or Global). Only emitted on genuine changes — never on the
|
||||||
// initial mount, and never in builds where the picker isn't mounted
|
// initial mount, and never in builds where the picker isn't mounted
|
||||||
@@ -111,9 +114,9 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const {
|
const {
|
||||||
action,
|
action,
|
||||||
realAction,
|
|
||||||
currentData,
|
currentData,
|
||||||
disabled,
|
disabled,
|
||||||
|
restrictedEdit,
|
||||||
open,
|
open,
|
||||||
instanceTypeList = [],
|
instanceTypeList = [],
|
||||||
noAvailableInstanceTypes,
|
noAvailableInstanceTypes,
|
||||||
@@ -125,8 +128,9 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
const [form] = Form.useForm<InstanceFormValues>();
|
const [form] = Form.useForm<InstanceFormValues>();
|
||||||
const scrollTabsRef = useRef<any>(null);
|
const scrollTabsRef = useRef<any>(null);
|
||||||
const formAction =
|
// Restricted (non-stopped) edit disables the type / template / storage
|
||||||
realAction === PageAction.CREATE ? PageAction.CREATE : action;
|
// sections; displayName and the SSH public keys keep following `disabled`.
|
||||||
|
const sectionDisabled = disabled || restrictedEdit;
|
||||||
const sshEnabled = Form.useWatch('enable_ssh', form);
|
const sshEnabled = Form.useWatch('enable_ssh', form);
|
||||||
const description = Form.useWatch(['description'], form);
|
const description = Form.useWatch(['description'], form);
|
||||||
// `organization_id` is owned by the create-scope picker slot; it only
|
// `organization_id` is owned by the create-scope picker slot; it only
|
||||||
@@ -464,8 +468,8 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Whole-card (exclusive) vs sliced (percentage) mode. Only meaningful for
|
// Whole-card (exclusive) vs sliced (percentage) mode. Only meaningful for
|
||||||
// sliceable accelerator types; derived (no persisted field) — on edit/
|
// sliceable accelerator types; derived (no persisted field) — on edit it
|
||||||
// recreate it is inferred from acceleratorSlicedMemoryPercentage > 0.
|
// is inferred from acceleratorSlicedMemoryPercentage > 0.
|
||||||
const [sliceMode, setSliceMode] = useState<'whole' | 'sliced'>('whole');
|
const [sliceMode, setSliceMode] = useState<'whole' | 'sliced'>('whole');
|
||||||
|
|
||||||
const handleAcceleratorChange = (count: number) => {
|
const handleAcceleratorChange = (count: number) => {
|
||||||
@@ -582,11 +586,8 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
// Prefill from the source row on edit / view.
|
||||||
action === PageAction.EDIT ||
|
if (currentData) {
|
||||||
action === PageAction.VIEW ||
|
|
||||||
realAction === PageAction.CREATE
|
|
||||||
) {
|
|
||||||
console.log('currentData', currentData);
|
console.log('currentData', currentData);
|
||||||
const currentSpec = parseJsonSafe(
|
const currentSpec = parseJsonSafe(
|
||||||
currentData?.description || '{}',
|
currentData?.description || '{}',
|
||||||
@@ -597,8 +598,8 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
? _.toNumber(currentData?.spec?.resources?.accelerator)
|
? _.toNumber(currentData?.spec?.resources?.accelerator)
|
||||||
: _.toNumber(currentData?.spec?.resources?.cpu) || 0;
|
: _.toNumber(currentData?.spec?.resources?.cpu) || 0;
|
||||||
|
|
||||||
// Infer the mode from the persisted slice percentage (recreate keeps
|
// Infer the mode from the persisted slice percentage (edit/view
|
||||||
// the section editable; edit/view render a readonly card).
|
// render a readonly card).
|
||||||
const persistedSliced =
|
const persistedSliced =
|
||||||
_.toNumber(
|
_.toNumber(
|
||||||
currentData?.spec?.resources?.acceleratorSlicedMemoryPercentage
|
currentData?.spec?.resources?.acceleratorSlicedMemoryPercentage
|
||||||
@@ -624,12 +625,12 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// buildResourcesData above filled CPU / RAM for the whole card; rescale
|
// buildResourcesData above filled CPU / RAM for the whole card; rescale
|
||||||
// them off the persisted percentages when recreating a sliced instance.
|
// them off the persisted percentages for a sliced instance.
|
||||||
if (persistedSliced) {
|
if (persistedSliced) {
|
||||||
applySlicedResourceScaling();
|
applySlicedResourceScaling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [action, currentData, form, open, realAction, instanceTypeList]);
|
}, [action, currentData, form, open, instanceTypeList]);
|
||||||
|
|
||||||
const getUnitResources = () => {
|
const getUnitResources = () => {
|
||||||
if (selectedInstanceType?.spec?.unitResourcesParsed) {
|
if (selectedInstanceType?.spec?.unitResourcesParsed) {
|
||||||
@@ -736,7 +737,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
>
|
>
|
||||||
<FormContext.Provider
|
<FormContext.Provider
|
||||||
value={{
|
value={{
|
||||||
action: formAction,
|
action: action,
|
||||||
currentData: currentData,
|
currentData: currentData,
|
||||||
isGPUType: isGPUType
|
isGPUType: isGPUType
|
||||||
}}
|
}}
|
||||||
@@ -780,7 +781,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
storageMode: StorageModeValueMap.Temporary
|
storageMode: StorageModeValueMap.Temporary
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Basic action={formAction} disabled={disabled} />
|
<Basic action={action} disabled={disabled} />
|
||||||
<Form.Item name="clusterId" hidden>
|
<Form.Item name="clusterId" hidden>
|
||||||
<CInput.Input />
|
<CInput.Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -797,8 +798,8 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: (
|
children: (
|
||||||
<InstanceTypeFormItem
|
<InstanceTypeFormItem
|
||||||
action={formAction}
|
action={action}
|
||||||
disabled={disabled}
|
disabled={sectionDisabled}
|
||||||
selectedInstanceType={selectedInstanceType}
|
selectedInstanceType={selectedInstanceType}
|
||||||
currentData={currentData as any}
|
currentData={currentData as any}
|
||||||
onceMaxRequest={onceMaxRequest}
|
onceMaxRequest={onceMaxRequest}
|
||||||
@@ -820,7 +821,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
children: (
|
children: (
|
||||||
<TemplateBasicForm
|
<TemplateBasicForm
|
||||||
page="instance"
|
page="instance"
|
||||||
disabled={disabled}
|
disabled={sectionDisabled}
|
||||||
onceMaxRequest={onceMaxRequest}
|
onceMaxRequest={onceMaxRequest}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -832,7 +833,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
}),
|
}),
|
||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: (
|
children: (
|
||||||
<StorageVolume disabled={disabled} action={formAction} />
|
<StorageVolume disabled={sectionDisabled} action={action} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -136,8 +136,8 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Sliced mode is only offered for sliceable accelerator types, and only when
|
// Sliced mode is only offered for sliceable accelerator types, and only when
|
||||||
// the section is editable (create / recreate, or edit after re-picking a
|
// the section is editable (create, or edit after re-picking a type; a
|
||||||
// type; a not-yet-re-typed edit renders a readonly card).
|
// not-yet-re-typed edit renders a readonly card).
|
||||||
const showModeSwitch =
|
const showModeSwitch =
|
||||||
!readonlyType &&
|
!readonlyType &&
|
||||||
isGPUType &&
|
isGPUType &&
|
||||||
|
|||||||
@@ -15,30 +15,26 @@ const useCreateInstance = () => {
|
|||||||
title: string;
|
title: string;
|
||||||
currentData?: ListItem | null;
|
currentData?: ListItem | null;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
realAction?: string;
|
|
||||||
}>({
|
}>({
|
||||||
action: PageAction.CREATE,
|
action: PageAction.CREATE,
|
||||||
title: '',
|
title: '',
|
||||||
open: false,
|
open: false,
|
||||||
width: undefined,
|
width: undefined,
|
||||||
currentData: null,
|
currentData: null
|
||||||
realAction: undefined
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const openModal = (
|
const openModal = (
|
||||||
action: PageActionType,
|
action: PageActionType,
|
||||||
title: string,
|
title: string,
|
||||||
currentData?: ListItem | null,
|
currentData?: ListItem | null,
|
||||||
width?: number | string,
|
width?: number | string
|
||||||
realAction?: string
|
|
||||||
) => {
|
) => {
|
||||||
setOpenModalStatus({
|
setOpenModalStatus({
|
||||||
action,
|
action,
|
||||||
title,
|
title,
|
||||||
open: true,
|
open: true,
|
||||||
currentData,
|
currentData,
|
||||||
width,
|
width
|
||||||
realAction
|
|
||||||
});
|
});
|
||||||
saveScrollHeight();
|
saveScrollHeight();
|
||||||
};
|
};
|
||||||
@@ -73,23 +69,12 @@ const useCreateInstance = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openRecreateInstanceModal = (row: ListItem) => {
|
|
||||||
openModal(
|
|
||||||
PageAction.EDIT,
|
|
||||||
intl.formatMessage({ id: 'common.button.recreate' }),
|
|
||||||
row,
|
|
||||||
'calc(100vw - 220px)',
|
|
||||||
PageAction.CREATE
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setOpenModalStatus({
|
setOpenModalStatus({
|
||||||
...openModalStatus,
|
...openModalStatus,
|
||||||
title: '',
|
title: '',
|
||||||
open: false,
|
open: false,
|
||||||
currentData: null,
|
currentData: null
|
||||||
realAction: undefined
|
|
||||||
});
|
});
|
||||||
restoreScrollHeight();
|
restoreScrollHeight();
|
||||||
};
|
};
|
||||||
@@ -101,7 +86,6 @@ const useCreateInstance = () => {
|
|||||||
openCreateInstanceModal,
|
openCreateInstanceModal,
|
||||||
openEditInstanceModal,
|
openEditInstanceModal,
|
||||||
openViewInstanceModal,
|
openViewInstanceModal,
|
||||||
openRecreateInstanceModal,
|
|
||||||
closeInstanceModal: closeModal
|
closeInstanceModal: closeModal
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ const GPUService: React.FC = () => {
|
|||||||
openCreateInstanceModal,
|
openCreateInstanceModal,
|
||||||
openEditInstanceModal,
|
openEditInstanceModal,
|
||||||
openViewInstanceModal,
|
openViewInstanceModal,
|
||||||
openRecreateInstanceModal,
|
|
||||||
closeInstanceModal
|
closeInstanceModal
|
||||||
} = useCreateInstance();
|
} = useCreateInstance();
|
||||||
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
|
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
|
||||||
@@ -136,13 +135,7 @@ const GPUService: React.FC = () => {
|
|||||||
|
|
||||||
const handleModalOk = async (data: FormData) => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (openInstanceModalStatus.realAction === PageAction.CREATE) {
|
if (openInstanceModalStatus.action === PageAction.EDIT) {
|
||||||
await deleteGPUServiceInstance(openInstanceModalStatus.currentData!.id);
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 300);
|
|
||||||
});
|
|
||||||
await createInstance({ data });
|
|
||||||
} else if (openInstanceModalStatus.action === PageAction.EDIT) {
|
|
||||||
await updateInstance({
|
await updateInstance({
|
||||||
id: openInstanceModalStatus.currentData!.id,
|
id: openInstanceModalStatus.currentData!.id,
|
||||||
data
|
data
|
||||||
@@ -243,8 +236,6 @@ const GPUService: React.FC = () => {
|
|||||||
openEditInstanceModal(row);
|
openEditInstanceModal(row);
|
||||||
} else if (val === 'delete') {
|
} else if (val === 'delete') {
|
||||||
handleDelete({ ...row });
|
handleDelete({ ...row });
|
||||||
} else if (val === 'recreate') {
|
|
||||||
openRecreateInstanceModal(row);
|
|
||||||
} else if (val === 'viewlog') {
|
} else if (val === 'viewlog') {
|
||||||
openViewLogsModal(row);
|
openViewLogsModal(row);
|
||||||
} else if (val === 'viewevent') {
|
} else if (val === 'viewevent') {
|
||||||
@@ -391,7 +382,6 @@ const GPUService: React.FC = () => {
|
|||||||
title={openInstanceModalStatus.title}
|
title={openInstanceModalStatus.title}
|
||||||
data={openInstanceModalStatus.currentData}
|
data={openInstanceModalStatus.currentData}
|
||||||
width={openInstanceModalStatus.width}
|
width={openInstanceModalStatus.width}
|
||||||
realAction={openInstanceModalStatus.realAction}
|
|
||||||
clusterList={clusterList}
|
clusterList={clusterList}
|
||||||
onCancel={closeInstanceModal}
|
onCancel={closeInstanceModal}
|
||||||
onOk={handleModalOk}
|
onOk={handleModalOk}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ export const buildInstanceTypeSnapshotSpec = (
|
|||||||
|
|
||||||
// Serialize the chosen instance type into the instance's `description` field —
|
// Serialize the chosen instance type into the instance's `description` field —
|
||||||
// a persisted spec snapshot the form reads back to render the type card and
|
// a persisted spec snapshot the form reads back to render the type card and
|
||||||
// derive unit resources. Shared by the create/recreate flow (card selection)
|
// derive unit resources. Shared by the create flow (card selection) and the
|
||||||
// and the edit flow (change-type overlay).
|
// edit flow (change-type overlay).
|
||||||
export const saveInstanceDataInDescription = (
|
export const saveInstanceDataInDescription = (
|
||||||
instanceType: InstanceTypeItem
|
instanceType: InstanceTypeItem
|
||||||
): string => {
|
): string => {
|
||||||
|
|||||||
Reference in New Issue
Block a user