diff --git a/src/locales/en-US/gpuservice.ts b/src/locales/en-US/gpuservice.ts index e7fd2d46..c01190be 100644 --- a/src/locales/en-US/gpuservice.ts +++ b/src/locales/en-US/gpuservice.ts @@ -178,9 +178,6 @@ export default { 'Only events from the last hour are shown', 'gpuservice.instance.event.tab.instance': 'Instance 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 {name}', 'gpuservice.storage': 'Storage', 'gpuservice.storage.add': 'Add Storage', 'gpuservice.storage.edit': 'Edit Storage', diff --git a/src/locales/ja-JP/gpuservice.ts b/src/locales/ja-JP/gpuservice.ts index eb330dbc..670a23d0 100644 --- a/src/locales/ja-JP/gpuservice.ts +++ b/src/locales/ja-JP/gpuservice.ts @@ -177,9 +177,6 @@ export default { '直近 1 時間のイベントのみ表示されます', 'gpuservice.instance.event.tab.instance': 'インスタンスイベント', 'gpuservice.instance.event.tab.volume': 'ボリュームイベント', - 'gpuservice.instance.recreate.confirm.title': '再作成を確認しますか', - 'gpuservice.instance.recreate.confirm.content': - '現在のインスタンスを削除した後、現在の構成で再作成します。\n {name}', 'gpuservice.storage': 'ストレージ', 'gpuservice.storage.add': 'ストレージを追加', 'gpuservice.storage.edit': 'ストレージを編集', diff --git a/src/locales/ru-RU/gpuservice.ts b/src/locales/ru-RU/gpuservice.ts index 2087b850..49135941 100644 --- a/src/locales/ru-RU/gpuservice.ts +++ b/src/locales/ru-RU/gpuservice.ts @@ -176,9 +176,6 @@ export default { 'Отображаются только события за последний час', 'gpuservice.instance.event.tab.instance': 'События экземпляра', 'gpuservice.instance.event.tab.volume': 'События тома', - 'gpuservice.instance.recreate.confirm.title': 'Подтвердить пересоздание', - 'gpuservice.instance.recreate.confirm.content': - 'Текущий экземпляр будет сначала удалён, а затем пересоздан с текущей конфигурацией.\n {name}', 'gpuservice.storage': 'Хранилище', 'gpuservice.storage.add': 'Добавить хранилище', 'gpuservice.storage.edit': 'Редактировать хранилище', diff --git a/src/locales/tr-TR/gpuservice.ts b/src/locales/tr-TR/gpuservice.ts index 62a0199e..a427b17d 100644 --- a/src/locales/tr-TR/gpuservice.ts +++ b/src/locales/tr-TR/gpuservice.ts @@ -172,10 +172,6 @@ export default { 'Yalnızca son bir saatteki olaylar gösterilir', 'gpuservice.instance.event.tab.instance': 'Örnek 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 {name}', 'gpuservice.storage': 'Depolama', 'gpuservice.storage.add': 'Depolama Ekle', 'gpuservice.storage.edit': 'Depolamayı Düzenle', diff --git a/src/locales/zh-CN/gpuservice.ts b/src/locales/zh-CN/gpuservice.ts index 1de8ff2c..e2e28a0a 100644 --- a/src/locales/zh-CN/gpuservice.ts +++ b/src/locales/zh-CN/gpuservice.ts @@ -162,9 +162,6 @@ export default { 'gpuservice.instance.event.recentHourTip': '仅显示最近一小时的事件。', 'gpuservice.instance.event.tab.instance': '实例事件', 'gpuservice.instance.event.tab.volume': '存储卷事件', - 'gpuservice.instance.recreate.confirm.title': '确认重新创建', - 'gpuservice.instance.recreate.confirm.content': - '系统将先删除当前实例,然后使用当前配置重新创建。\n {name}', 'gpuservice.storage': '存储', 'gpuservice.storage.add': '添加存储', 'gpuservice.storage.edit': '编辑存储', diff --git a/src/pages/gpu-service/instances/components/add-modal.tsx b/src/pages/gpu-service/instances/components/add-modal.tsx index ac91e9ef..ea3e69b5 100644 --- a/src/pages/gpu-service/instances/components/add-modal.tsx +++ b/src/pages/gpu-service/instances/components/add-modal.tsx @@ -5,12 +5,7 @@ import useUserDirectory from '@/pages/gpu-service/hooks/use-user-directory'; import Separator from '@/pages/llmodels/components/separator'; import { getGPUStackPlugin } from '@/plugins'; import { SearchOutlined } from '@ant-design/icons'; -import { - AlertBlockInfo, - ColumnWrapper, - GSDrawer, - ModalFooter -} from '@gpustack/core-ui'; +import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui'; import { useIntl, useModel } from '@umijs/max'; import { Input, Typography } from 'antd'; import { useEffect, useMemo, useRef, useState } from 'react'; @@ -30,7 +25,6 @@ type AddModalProps = { action: PageActionType; open: boolean; width?: number | string; - realAction?: string; clusterList?: Array<{ label: string; value: number; @@ -84,8 +78,7 @@ const AddModal: React.FC = ({ data, onCancel, width, - clusterList = [], - realAction + clusterList = [] }) => { const intl = useIntl(); const { initialState } = useModel('@@initialState') || {}; @@ -173,16 +166,18 @@ const AddModal: React.FC = ({ ); // const readonly = action === PageAction.VIEW; const readonly = false; - const isRecreate = realAction === PageAction.CREATE; - const showResourceSelectors = action === PageAction.CREATE || isRecreate; - const shouldAutoSelectResource = action === PageAction.CREATE && !isRecreate; + const showResourceSelectors = action === PageAction.CREATE; // 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 - // columns render for CREATE / recreate. + // columns render for CREATE. const isStoppedEdit = action === PageAction.EDIT && data?.status?.phase === InstanceStatusValueMap.Stopped; 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 = ( manufacturer: string | undefined, @@ -266,43 +261,12 @@ const AddModal: React.FC = ({ ); }; - 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 const applyAutoSelection = ( instanceTypes: InstanceTypeItem[], templates: TemplateItem[], 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). const owned = filterTypesByOwner(instanceTypes, orgId); @@ -376,7 +340,7 @@ const AddModal: React.FC = ({ // full instance-type list to re-type a stopped instance. fetchData({ page: -1 }); } - }, [open, shouldAutoSelectResource, action]); + }, [open, action]); // filter instance types (already scoped to the chosen org's clusters) const filteredInstanceTypes = ownedInstanceTypes.filter((item) => @@ -664,17 +628,6 @@ const AddModal: React.FC = ({ styles={{ container: { paddingBlock: 0 } }} footer={ <> - {isRecreate && open && ( -
- -
- )} = ({ = forwardRef( (props, ref) => { const { action, - realAction, currentData, disabled, + restrictedEdit, open, instanceTypeList = [], noAvailableInstanceTypes, @@ -125,8 +128,9 @@ const GPUServiceInstanceForm: React.FC = forwardRef( const { getRuleMessage } = useAppUtils(); const [form] = Form.useForm(); const scrollTabsRef = useRef(null); - const formAction = - realAction === PageAction.CREATE ? PageAction.CREATE : action; + // Restricted (non-stopped) edit disables the type / template / storage + // sections; displayName and the SSH public keys keep following `disabled`. + const sectionDisabled = disabled || restrictedEdit; const sshEnabled = Form.useWatch('enable_ssh', form); const description = Form.useWatch(['description'], form); // `organization_id` is owned by the create-scope picker slot; it only @@ -464,8 +468,8 @@ const GPUServiceInstanceForm: React.FC = forwardRef( }; // Whole-card (exclusive) vs sliced (percentage) mode. Only meaningful for - // sliceable accelerator types; derived (no persisted field) — on edit/ - // recreate it is inferred from acceleratorSlicedMemoryPercentage > 0. + // sliceable accelerator types; derived (no persisted field) — on edit it + // is inferred from acceleratorSlicedMemoryPercentage > 0. const [sliceMode, setSliceMode] = useState<'whole' | 'sliced'>('whole'); const handleAcceleratorChange = (count: number) => { @@ -582,11 +586,8 @@ const GPUServiceInstanceForm: React.FC = forwardRef( return; } - if ( - action === PageAction.EDIT || - action === PageAction.VIEW || - realAction === PageAction.CREATE - ) { + // Prefill from the source row on edit / view. + if (currentData) { console.log('currentData', currentData); const currentSpec = parseJsonSafe( currentData?.description || '{}', @@ -597,8 +598,8 @@ const GPUServiceInstanceForm: React.FC = forwardRef( ? _.toNumber(currentData?.spec?.resources?.accelerator) : _.toNumber(currentData?.spec?.resources?.cpu) || 0; - // Infer the mode from the persisted slice percentage (recreate keeps - // the section editable; edit/view render a readonly card). + // Infer the mode from the persisted slice percentage (edit/view + // render a readonly card). const persistedSliced = _.toNumber( currentData?.spec?.resources?.acceleratorSlicedMemoryPercentage @@ -624,12 +625,12 @@ const GPUServiceInstanceForm: React.FC = forwardRef( }); // 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) { applySlicedResourceScaling(); } } - }, [action, currentData, form, open, realAction, instanceTypeList]); + }, [action, currentData, form, open, instanceTypeList]); const getUnitResources = () => { if (selectedInstanceType?.spec?.unitResourcesParsed) { @@ -736,7 +737,7 @@ const GPUServiceInstanceForm: React.FC = forwardRef( > = forwardRef( storageMode: StorageModeValueMap.Temporary }} > - + @@ -797,8 +798,8 @@ const GPUServiceInstanceForm: React.FC = forwardRef( forceRender: true, children: ( = forwardRef( children: ( ) @@ -832,7 +833,7 @@ const GPUServiceInstanceForm: React.FC = forwardRef( }), forceRender: true, children: ( - + ) } ]} diff --git a/src/pages/gpu-service/instances/forms/instance-type.tsx b/src/pages/gpu-service/instances/forms/instance-type.tsx index b06ee7f2..cff90506 100644 --- a/src/pages/gpu-service/instances/forms/instance-type.tsx +++ b/src/pages/gpu-service/instances/forms/instance-type.tsx @@ -136,8 +136,8 @@ const InstanceTypeFormItem: React.FC = ({ }; // Sliced mode is only offered for sliceable accelerator types, and only when - // the section is editable (create / recreate, or edit after re-picking a - // type; a not-yet-re-typed edit renders a readonly card). + // the section is editable (create, or edit after re-picking a type; a + // not-yet-re-typed edit renders a readonly card). const showModeSwitch = !readonlyType && isGPUType && diff --git a/src/pages/gpu-service/instances/hooks/use-create-instance.ts b/src/pages/gpu-service/instances/hooks/use-create-instance.ts index dcac55fe..775841a8 100644 --- a/src/pages/gpu-service/instances/hooks/use-create-instance.ts +++ b/src/pages/gpu-service/instances/hooks/use-create-instance.ts @@ -15,30 +15,26 @@ const useCreateInstance = () => { title: string; currentData?: ListItem | null; width?: number | string; - realAction?: string; }>({ action: PageAction.CREATE, title: '', open: false, width: undefined, - currentData: null, - realAction: undefined + currentData: null }); const openModal = ( action: PageActionType, title: string, currentData?: ListItem | null, - width?: number | string, - realAction?: string + width?: number | string ) => { setOpenModalStatus({ action, title, open: true, currentData, - width, - realAction + width }); 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 = () => { setOpenModalStatus({ ...openModalStatus, title: '', open: false, - currentData: null, - realAction: undefined + currentData: null }); restoreScrollHeight(); }; @@ -101,7 +86,6 @@ const useCreateInstance = () => { openCreateInstanceModal, openEditInstanceModal, openViewInstanceModal, - openRecreateInstanceModal, closeInstanceModal: closeModal }; }; diff --git a/src/pages/gpu-service/instances/index.tsx b/src/pages/gpu-service/instances/index.tsx index 51fcf583..54390c04 100644 --- a/src/pages/gpu-service/instances/index.tsx +++ b/src/pages/gpu-service/instances/index.tsx @@ -86,7 +86,6 @@ const GPUService: React.FC = () => { openCreateInstanceModal, openEditInstanceModal, openViewInstanceModal, - openRecreateInstanceModal, closeInstanceModal } = useCreateInstance(); const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } = @@ -136,13 +135,7 @@ const GPUService: React.FC = () => { const handleModalOk = async (data: FormData) => { try { - if (openInstanceModalStatus.realAction === PageAction.CREATE) { - await deleteGPUServiceInstance(openInstanceModalStatus.currentData!.id); - await new Promise((resolve) => { - setTimeout(resolve, 300); - }); - await createInstance({ data }); - } else if (openInstanceModalStatus.action === PageAction.EDIT) { + if (openInstanceModalStatus.action === PageAction.EDIT) { await updateInstance({ id: openInstanceModalStatus.currentData!.id, data @@ -243,8 +236,6 @@ const GPUService: React.FC = () => { openEditInstanceModal(row); } else if (val === 'delete') { handleDelete({ ...row }); - } else if (val === 'recreate') { - openRecreateInstanceModal(row); } else if (val === 'viewlog') { openViewLogsModal(row); } else if (val === 'viewevent') { @@ -391,7 +382,6 @@ const GPUService: React.FC = () => { title={openInstanceModalStatus.title} data={openInstanceModalStatus.currentData} width={openInstanceModalStatus.width} - realAction={openInstanceModalStatus.realAction} clusterList={clusterList} onCancel={closeInstanceModal} onOk={handleModalOk} diff --git a/src/pages/gpu-service/instances/utils/instance-description.ts b/src/pages/gpu-service/instances/utils/instance-description.ts index 43c9bf24..0e293abd 100644 --- a/src/pages/gpu-service/instances/utils/instance-description.ts +++ b/src/pages/gpu-service/instances/utils/instance-description.ts @@ -23,8 +23,8 @@ export const buildInstanceTypeSnapshotSpec = ( // 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 -// derive unit resources. Shared by the create/recreate flow (card selection) -// and the edit flow (change-type overlay). +// derive unit resources. Shared by the create flow (card selection) and the +// edit flow (change-type overlay). export const saveInstanceDataInDescription = ( instanceType: InstanceTypeItem ): string => {