fix: recreate action
This commit is contained in:
@@ -92,6 +92,10 @@ export default {
|
|||||||
'gpuservice.storage.persistent': 'Persistent',
|
'gpuservice.storage.persistent': 'Persistent',
|
||||||
'gpuservice.storage.temporary': 'Temporary',
|
'gpuservice.storage.temporary': 'Temporary',
|
||||||
'gpuservice.storage.persistentVolume': 'Persistent Volume',
|
'gpuservice.storage.persistentVolume': 'Persistent Volume',
|
||||||
|
'gpuservice.storage.temporary.tips':
|
||||||
|
'Data is cleared when the instance stops.',
|
||||||
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
|
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.',
|
||||||
'gpuservice.storage.persistentVolume.required':
|
'gpuservice.storage.persistentVolume.required':
|
||||||
'Please select a persistent volume',
|
'Please select a persistent volume',
|
||||||
'gpuservice.storage.tempCapacity': 'Capacity (GB)',
|
'gpuservice.storage.tempCapacity': 'Capacity (GB)',
|
||||||
|
|||||||
@@ -96,5 +96,9 @@ export default {
|
|||||||
'gpuservice.storage.tempCapacity.required':
|
'gpuservice.storage.tempCapacity.required':
|
||||||
'一時ストレージ容量を入力してください',
|
'一時ストレージ容量を入力してください',
|
||||||
'gpuservice.form.rule.name':
|
'gpuservice.form.rule.name':
|
||||||
"小文字、数字、'-' のみ使用可能。文字または数字で始まり、文字または数字で終わる必要があり、連続する '-' は不可、最大 63 文字。"
|
"小文字、数字、'-' のみ使用可能。文字または数字で始まり、文字または数字で終わる必要があり、連続する '-' は不可、最大 63 文字。",
|
||||||
|
'gpuservice.storage.temporary.tips':
|
||||||
|
'Data is cleared when the instance stops.',
|
||||||
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
|
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,5 +96,9 @@ export default {
|
|||||||
'gpuservice.storage.tempCapacity.required':
|
'gpuservice.storage.tempCapacity.required':
|
||||||
'Введите объём временного хранилища',
|
'Введите объём временного хранилища',
|
||||||
'gpuservice.form.rule.name':
|
'gpuservice.form.rule.name':
|
||||||
"Строчные буквы, цифры и '-'. Должно начинаться и заканчиваться буквой или цифрой, без подряд идущих '-', максимум 63 символа."
|
"Строчные буквы, цифры и '-'. Должно начинаться и заканчиваться буквой или цифрой, без подряд идущих '-', максимум 63 символа.",
|
||||||
|
'gpuservice.storage.temporary.tips':
|
||||||
|
'Data is cleared when the instance stops.',
|
||||||
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
|
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,5 +97,9 @@ export default {
|
|||||||
'gpuservice.storage.tempCapacity.required':
|
'gpuservice.storage.tempCapacity.required':
|
||||||
'Lütfen geçici depolama kapasitesini girin',
|
'Lütfen geçici depolama kapasitesini girin',
|
||||||
'gpuservice.form.rule.name':
|
'gpuservice.form.rule.name':
|
||||||
"Küçük harfler, rakamlar ve '-'. Harf veya rakamla başlamalı ve bitmeli, ardışık '-' içermemeli, en fazla 63 karakter."
|
"Küçük harfler, rakamlar ve '-'. Harf veya rakamla başlamalı ve bitmeli, ardışık '-' içermemeli, en fazla 63 karakter.",
|
||||||
|
'gpuservice.storage.temporary.tips':
|
||||||
|
'Data is cleared when the instance stops.',
|
||||||
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
|
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ export default {
|
|||||||
'gpuservice.storage.persistent': '持久',
|
'gpuservice.storage.persistent': '持久',
|
||||||
'gpuservice.storage.temporary': '临时',
|
'gpuservice.storage.temporary': '临时',
|
||||||
'gpuservice.storage.persistentVolume': '持久卷',
|
'gpuservice.storage.persistentVolume': '持久卷',
|
||||||
|
'gpuservice.storage.temporary.tips': '实例停止后,数据将被清除。',
|
||||||
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
|
'数据在实例重启后仍会保留,仅在实例被终止时删除。无法被其它实例共享。',
|
||||||
'gpuservice.storage.persistentVolume.required': '请选择持久卷',
|
'gpuservice.storage.persistentVolume.required': '请选择持久卷',
|
||||||
'gpuservice.storage.tempCapacity': '容量(GB)',
|
'gpuservice.storage.tempCapacity': '容量(GB)',
|
||||||
'gpuservice.storage.tempCapacity.required': '请输入临时存储容量',
|
'gpuservice.storage.tempCapacity.required': '请输入临时存储容量',
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ export const GPU_SERVICE_INSTANCES_EVENTS_API = (params: {
|
|||||||
return `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/namespaces/${params.namespace}/instances/${params.name}/events`;
|
return `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/namespaces/${params.namespace}/instances/${params.name}/events`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GPU_SERVICE_INSTANCE_PV_EVENTS_API = (params: {
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
|
clusterID?: number;
|
||||||
|
}) => {
|
||||||
|
return `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/namespaces/${params.namespace}/instancepersistentvolumes/${params.name}/events`;
|
||||||
|
};
|
||||||
|
|
||||||
// =========== Instances ===========
|
// =========== Instances ===========
|
||||||
|
|
||||||
export async function queryGPUServiceInstances(
|
export async function queryGPUServiceInstances(
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import { PageAction } from '@/config';
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import Separator from '@/pages/llmodels/components/separator';
|
import Separator from '@/pages/llmodels/components/separator';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
import {
|
||||||
|
AlertBlockInfo,
|
||||||
|
ColumnWrapper,
|
||||||
|
GSDrawer,
|
||||||
|
ModalFooter
|
||||||
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Empty, Input, Typography } from 'antd';
|
import { Empty, Input, Typography } from 'antd';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
@@ -19,6 +24,8 @@ type AddModalProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
width?: number | string;
|
||||||
|
realAction?: string;
|
||||||
onOk: (values: FormData) => void;
|
onOk: (values: FormData) => void;
|
||||||
data?: ListItem | null;
|
data?: ListItem | null;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
@@ -71,7 +78,9 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
open,
|
open,
|
||||||
onOk,
|
onOk,
|
||||||
data,
|
data,
|
||||||
onCancel
|
onCancel,
|
||||||
|
width,
|
||||||
|
realAction
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const form = useRef<any>(null);
|
const form = useRef<any>(null);
|
||||||
@@ -92,6 +101,10 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
|
|
||||||
const instanceTypeList = detailData?.items || [];
|
const instanceTypeList = detailData?.items || [];
|
||||||
const templateList = templatesData?.items || [];
|
const templateList = templatesData?.items || [];
|
||||||
|
const readonly = action === PageAction.VIEW;
|
||||||
|
const isRecreate = realAction === PageAction.CREATE;
|
||||||
|
const showResourceSelectors = action === PageAction.CREATE || isRecreate;
|
||||||
|
const shouldAutoSelectResource = action === PageAction.CREATE && !isRecreate;
|
||||||
|
|
||||||
const findTemplateByManufacturer = (
|
const findTemplateByManufacturer = (
|
||||||
manufacturer: string | undefined,
|
manufacturer: string | undefined,
|
||||||
@@ -143,7 +156,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
instanceTypes: InstanceTypeItem[],
|
instanceTypes: InstanceTypeItem[],
|
||||||
templates: TemplateItem[]
|
templates: TemplateItem[]
|
||||||
) => {
|
) => {
|
||||||
if (action !== PageAction.CREATE) return;
|
if (!shouldAutoSelectResource) return;
|
||||||
if (!instanceTypes.length) return;
|
if (!instanceTypes.length) return;
|
||||||
|
|
||||||
const first = instanceTypes[0];
|
const first = instanceTypes[0];
|
||||||
@@ -172,7 +185,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
applyAutoSelection(instanceRes?.items || [], templatesRes?.items || []);
|
applyAutoSelection(instanceRes?.items || [], templatesRes?.items || []);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}, [open]);
|
}, [open, shouldAutoSelectResource]);
|
||||||
|
|
||||||
// filter instance types
|
// filter instance types
|
||||||
const filteredInstanceTypes = instanceTypeList.filter((item) =>
|
const filteredInstanceTypes = instanceTypeList.filter((item) =>
|
||||||
@@ -238,102 +251,122 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
}}
|
}}
|
||||||
keyboard={false}
|
keyboard={false}
|
||||||
styles={{
|
styles={{
|
||||||
wrapper: { width: 'calc(100vw - 220px)' },
|
wrapper: { width: width || 'calc(100vw - 220px)' },
|
||||||
body: { overflowY: 'hidden' }
|
body: { overflowY: 'hidden' }
|
||||||
}}
|
}}
|
||||||
footer={false}
|
footer={false}
|
||||||
>
|
>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.colWrapper}>
|
{showResourceSelectors && (
|
||||||
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
<>
|
||||||
<div className={styles.panelBody}>
|
<div className={styles.colWrapper}>
|
||||||
<div
|
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
||||||
style={{
|
<div className={styles.panelBody}>
|
||||||
display: 'flex',
|
<div
|
||||||
flexDirection: 'column',
|
style={{
|
||||||
gap: 16,
|
display: 'flex',
|
||||||
position: 'sticky',
|
flexDirection: 'column',
|
||||||
top: 0,
|
gap: 16,
|
||||||
zIndex: 10,
|
position: 'sticky',
|
||||||
backgroundColor: 'var(--ant-color-bg-elevated)'
|
top: 0,
|
||||||
}}
|
zIndex: 10,
|
||||||
>
|
backgroundColor: 'var(--ant-color-bg-elevated)'
|
||||||
<ColTitle style={{ paddingBottom: 0 }}>
|
}}
|
||||||
{intl.formatMessage({ id: 'gpuservice.instance.types' })}
|
>
|
||||||
</ColTitle>
|
<ColTitle style={{ paddingBottom: 0 }}>
|
||||||
<Input
|
{intl.formatMessage({ id: 'gpuservice.instance.types' })}
|
||||||
allowClear
|
</ColTitle>
|
||||||
prefix={<SearchOutlined className="text-tertiary" />}
|
<Input
|
||||||
placeholder={intl.formatMessage({
|
allowClear
|
||||||
id: 'gpuservice.instance.search.type.placeholder'
|
prefix={<SearchOutlined className="text-tertiary" />}
|
||||||
})}
|
placeholder={intl.formatMessage({
|
||||||
value={instanceKeyword}
|
id: 'gpuservice.instance.search.type.placeholder'
|
||||||
onChange={(e) => setInstanceKeyword(e.target.value)}
|
})}
|
||||||
/>
|
value={instanceKeyword}
|
||||||
</div>
|
onChange={(e) => setInstanceKeyword(e.target.value)}
|
||||||
<InstanceTypeList
|
/>
|
||||||
value={instanceTypeSelection.instanceType}
|
</div>
|
||||||
dataList={filteredInstanceTypes}
|
<InstanceTypeList
|
||||||
loading={instanceTypesLoading}
|
value={instanceTypeSelection.instanceType}
|
||||||
onChange={handleInstanceTypeChange}
|
dataList={filteredInstanceTypes}
|
||||||
/>
|
loading={instanceTypesLoading}
|
||||||
|
onChange={handleInstanceTypeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ColumnWrapper>
|
||||||
|
<Separator></Separator>
|
||||||
</div>
|
</div>
|
||||||
</ColumnWrapper>
|
<div className={styles.colWrapper}>
|
||||||
<Separator></Separator>
|
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
||||||
</div>
|
<div className={styles.panelBody}>
|
||||||
<div className={styles.colWrapper}>
|
<div
|
||||||
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
style={{
|
||||||
<div className={styles.panelBody}>
|
display: 'flex',
|
||||||
<div
|
flexDirection: 'column',
|
||||||
style={{
|
gap: 16,
|
||||||
display: 'flex',
|
position: 'sticky',
|
||||||
flexDirection: 'column',
|
top: 0,
|
||||||
gap: 16,
|
zIndex: 10,
|
||||||
position: 'sticky',
|
backgroundColor: 'var(--ant-color-bg-elevated)'
|
||||||
top: 0,
|
}}
|
||||||
zIndex: 10,
|
>
|
||||||
backgroundColor: 'var(--ant-color-bg-elevated)'
|
<ColTitle style={{ paddingBottom: 0 }}>
|
||||||
}}
|
{intl.formatMessage({
|
||||||
>
|
id: 'gpuservice.instance.templates'
|
||||||
<ColTitle style={{ paddingBottom: 0 }}>
|
})}
|
||||||
{intl.formatMessage({ id: 'gpuservice.instance.templates' })}
|
</ColTitle>
|
||||||
</ColTitle>
|
<Input
|
||||||
<Input
|
allowClear
|
||||||
allowClear
|
prefix={<SearchOutlined className="text-tertiary" />}
|
||||||
prefix={<SearchOutlined className="text-tertiary" />}
|
placeholder={intl.formatMessage({
|
||||||
placeholder={intl.formatMessage({
|
id: 'gpuservice.instance.search.template.placeholder'
|
||||||
id: 'gpuservice.instance.search.template.placeholder'
|
})}
|
||||||
})}
|
value={templateKeyword}
|
||||||
value={templateKeyword}
|
onChange={(e) => setTemplateKeyword(e.target.value)}
|
||||||
onChange={(e) => setTemplateKeyword(e.target.value)}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
{filteredTemplates.length > 0 ? (
|
||||||
{filteredTemplates.length > 0 ? (
|
<TemplateSelector
|
||||||
<TemplateSelector
|
value={templateId}
|
||||||
value={templateId}
|
dataList={filteredTemplates}
|
||||||
dataList={filteredTemplates}
|
onChange={handleTemplateChange}
|
||||||
onChange={handleTemplateChange}
|
/>
|
||||||
/>
|
) : (
|
||||||
) : (
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
)}
|
||||||
)}
|
</div>
|
||||||
|
</ColumnWrapper>
|
||||||
|
<Separator></Separator>
|
||||||
</div>
|
</div>
|
||||||
</ColumnWrapper>
|
</>
|
||||||
<Separator></Separator>
|
)}
|
||||||
</div>
|
|
||||||
<div className={styles.formWrapper}>
|
<div className={styles.formWrapper}>
|
||||||
<ColumnWrapper
|
<ColumnWrapper
|
||||||
styles={{ container: { paddingBlock: 0 } }}
|
styles={{ container: { paddingBlock: 0 } }}
|
||||||
footer={
|
footer={
|
||||||
<ModalFooter
|
<>
|
||||||
onOk={handleSubmit}
|
{isRecreate && open && (
|
||||||
onCancel={handleCancel}
|
<div style={{ marginInline: 24, paddingTop: 8 }}>
|
||||||
style={{
|
<AlertBlockInfo
|
||||||
padding: '16px 24px 8px',
|
type="warning"
|
||||||
display: 'flex',
|
contentStyle={{ paddingInline: 0 }}
|
||||||
justifyContent: 'flex-end'
|
message={intl.formatMessage({
|
||||||
}}
|
id: 'gpuservice.instance.recreate.confirm.content'
|
||||||
/>
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ModalFooter
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
showOkBtn={!readonly}
|
||||||
|
style={{
|
||||||
|
padding: '16px 24px 8px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@@ -343,7 +376,9 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
<GPUServiceInstanceForm
|
<GPUServiceInstanceForm
|
||||||
ref={form}
|
ref={form}
|
||||||
action={action}
|
action={action}
|
||||||
|
realAction={realAction}
|
||||||
currentData={data}
|
currentData={data}
|
||||||
|
disabled={readonly}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
open={open}
|
open={open}
|
||||||
instanceTypeList={instanceTypeList}
|
instanceTypeList={instanceTypeList}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ export const status: Record<string, StatusType> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const rowActionList = [
|
export const rowActionList = [
|
||||||
|
{
|
||||||
|
label: 'common.button.view',
|
||||||
|
key: 'view',
|
||||||
|
locale: true,
|
||||||
|
icon: icons.DetailInfo
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'common.button.viewlog',
|
label: 'common.button.viewlog',
|
||||||
key: 'viewlog',
|
key: 'viewlog',
|
||||||
@@ -58,12 +64,12 @@ export const rowActionList = [
|
|||||||
locale: true,
|
locale: true,
|
||||||
icon: icons.ProfileOutlined
|
icon: icons.ProfileOutlined
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: 'common.button.edit',
|
// label: 'common.button.edit',
|
||||||
key: 'edit',
|
// key: 'edit',
|
||||||
locale: true,
|
// locale: true,
|
||||||
icon: icons.EditOutlined
|
// icon: icons.EditOutlined
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
label: 'common.button.recreate',
|
label: 'common.button.recreate',
|
||||||
key: 'recreate',
|
key: 'recreate',
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
const Basic = ({ action }: { action: PageActionType }) => {
|
const Basic = ({
|
||||||
|
action,
|
||||||
|
disabled
|
||||||
|
}: {
|
||||||
|
action: PageActionType;
|
||||||
|
disabled?: boolean;
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -26,13 +32,14 @@ const Basic = ({ action }: { action: PageActionType }) => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<CInput.Input
|
<CInput.Input
|
||||||
disabled={action === PageAction.EDIT}
|
disabled={disabled || action === PageAction.EDIT}
|
||||||
label={intl.formatMessage({ id: 'gpuservice.instance.name' })}
|
label={intl.formatMessage({ id: 'gpuservice.instance.name' })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item<FormData> name={['spec', 'description']}>
|
<Form.Item<FormData> name={['spec', 'description']}>
|
||||||
<CInput.TextArea
|
<CInput.TextArea
|
||||||
|
disabled={disabled}
|
||||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||||
scaleSize
|
scaleSize
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import {
|
import {
|
||||||
Input as CInput,
|
CheckboxField,
|
||||||
CollapsePanel,
|
CollapsePanel,
|
||||||
IconFont,
|
IconFont,
|
||||||
ScrollSpyTabs,
|
ScrollSpyTabs,
|
||||||
|
Select,
|
||||||
useFinishFailed,
|
useFinishFailed,
|
||||||
useScrollActiveChange,
|
useScrollActiveChange,
|
||||||
useWrapperContext
|
useWrapperContext
|
||||||
@@ -39,9 +40,11 @@ interface InstanceFormProps {
|
|||||||
ref?: any;
|
ref?: any;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
|
realAction?: PageActionType | string;
|
||||||
currentData?: ListItem | null;
|
currentData?: ListItem | null;
|
||||||
namespace?: string;
|
namespace?: string;
|
||||||
instanceTypeList?: InstanceTypeItem[];
|
instanceTypeList?: InstanceTypeItem[];
|
||||||
|
disabled?: boolean;
|
||||||
onFinish: (values: FormData) => Promise<void>;
|
onFinish: (values: FormData) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +86,9 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const {
|
const {
|
||||||
action,
|
action,
|
||||||
|
realAction,
|
||||||
currentData,
|
currentData,
|
||||||
|
disabled,
|
||||||
open,
|
open,
|
||||||
namespace = 'default',
|
namespace = 'default',
|
||||||
instanceTypeList = [],
|
instanceTypeList = [],
|
||||||
@@ -92,6 +97,9 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm<InstanceFormValues>();
|
const [form] = Form.useForm<InstanceFormValues>();
|
||||||
const scrollTabsRef = useRef<any>(null);
|
const scrollTabsRef = useRef<any>(null);
|
||||||
|
const formAction =
|
||||||
|
realAction === PageAction.CREATE ? PageAction.CREATE : action;
|
||||||
|
const sshEnabled = Form.useWatch('enable_ssh', form);
|
||||||
const { detailData: sshKeyData, fetchData: fetchSSHData } = useGetSshkey();
|
const { detailData: sshKeyData, fetchData: fetchSSHData } = useGetSshkey();
|
||||||
const { getScrollElementScrollableHeight } = useWrapperContext();
|
const { getScrollElementScrollableHeight } = useWrapperContext();
|
||||||
const {
|
const {
|
||||||
@@ -186,9 +194,6 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
updateActiveKey
|
updateActiveKey
|
||||||
});
|
});
|
||||||
|
|
||||||
// useFinishFailed only matches errorFields[].name[0] against `fields`,
|
|
||||||
// but our form fields are nested (e.g. ['spec','type']), so collapse each
|
|
||||||
// error path into a single dotted string before delegating.
|
|
||||||
const handleOnFinishFailed = (errorInfo: any) => {
|
const handleOnFinishFailed = (errorInfo: any) => {
|
||||||
const errorFields = (errorInfo?.errorFields || []).map((field: any) => ({
|
const errorFields = (errorInfo?.errorFields || []).map((field: any) => ({
|
||||||
...field,
|
...field,
|
||||||
@@ -205,7 +210,12 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === PageAction.EDIT && currentData) {
|
if (
|
||||||
|
currentData &&
|
||||||
|
(action === PageAction.EDIT ||
|
||||||
|
action === PageAction.VIEW ||
|
||||||
|
realAction === PageAction.CREATE)
|
||||||
|
) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
metadata: {
|
metadata: {
|
||||||
name: currentData.metadata?.name,
|
name: currentData.metadata?.name,
|
||||||
@@ -220,7 +230,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [action, currentData, form, open, namespace]);
|
}, [action, currentData, form, open, namespace, realAction]);
|
||||||
|
|
||||||
const handleFinish = async (values: InstanceFormValues) => {
|
const handleFinish = async (values: InstanceFormValues) => {
|
||||||
await onFinish({
|
await onFinish({
|
||||||
@@ -261,6 +271,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
<Form
|
<Form
|
||||||
name="gpuServiceInstanceForm"
|
name="gpuServiceInstanceForm"
|
||||||
form={form}
|
form={form}
|
||||||
|
disabled={disabled}
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
onFinishFailed={handleOnFinishFailed}
|
onFinishFailed={handleOnFinishFailed}
|
||||||
preserve={false}
|
preserve={false}
|
||||||
@@ -292,7 +303,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
enable_ssh: false
|
enable_ssh: false
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Basic action={action} />
|
<Basic action={formAction} disabled={disabled} />
|
||||||
<CollapsePanel
|
<CollapsePanel
|
||||||
activeKey={collapseKeys}
|
activeKey={collapseKeys}
|
||||||
accordion={false}
|
accordion={false}
|
||||||
@@ -304,7 +315,12 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
id: 'gpuservice.instance.section.type'
|
id: 'gpuservice.instance.section.type'
|
||||||
}),
|
}),
|
||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: <InstanceTypeFormItem action={action} />
|
children: (
|
||||||
|
<InstanceTypeFormItem
|
||||||
|
action={formAction}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: TABKeysMap.TEMPLATE,
|
key: TABKeysMap.TEMPLATE,
|
||||||
@@ -315,7 +331,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
children: (
|
children: (
|
||||||
<TemplateBasicForm
|
<TemplateBasicForm
|
||||||
page="instance"
|
page="instance"
|
||||||
disabled={action === PageAction.EDIT}
|
disabled={disabled || formAction === PageAction.EDIT}
|
||||||
onceMaxRequest={onceMaxRequest}
|
onceMaxRequest={onceMaxRequest}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -328,16 +344,46 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: (
|
children: (
|
||||||
<StorageVolume
|
<StorageVolume
|
||||||
disabled={action === PageAction.EDIT}
|
disabled={disabled || formAction === PageAction.EDIT}
|
||||||
action={action}
|
action={formAction}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{hasSshPort && (
|
|
||||||
<Form.Item<FormData> hidden name={['spec', 'sshPublicKey', 'name']}>
|
<Form.Item<FormData>
|
||||||
<CInput.Input />
|
name="enable_ssh"
|
||||||
|
valuePropName="checked"
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
>
|
||||||
|
<CheckboxField
|
||||||
|
disabled={disabled}
|
||||||
|
label={'启用 SSH 访问'}
|
||||||
|
></CheckboxField>
|
||||||
|
</Form.Item>
|
||||||
|
{sshEnabled && (
|
||||||
|
<Form.Item<FormData> name={['spec', 'sshPublicKey', 'name']}>
|
||||||
|
<Select
|
||||||
|
disabled={disabled}
|
||||||
|
mode="multiple"
|
||||||
|
maxTagCount={1}
|
||||||
|
label={'SSH 公钥'}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: 'SSH-key-1',
|
||||||
|
value: 'gpustack-ssh-public-key'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SSH-key-2',
|
||||||
|
value: 'gpustack-ssh-public-key-2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SSH-key-3',
|
||||||
|
value: 'gpustack-ssh-public-key-3'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
></Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -49,10 +49,12 @@ const InstanceTypePicker: React.FC<InstanceTypePickerProps> = ({
|
|||||||
|
|
||||||
interface InstanceTypeFormItemProps {
|
interface InstanceTypeFormItemProps {
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
||||||
action
|
action,
|
||||||
|
disabled
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const form = Form.useFormInstance<FormData>();
|
const form = Form.useFormInstance<FormData>();
|
||||||
@@ -116,7 +118,7 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
|||||||
<InstanceTypePicker dataList={dataList} value={typeName} />
|
<InstanceTypePicker dataList={dataList} value={typeName} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</FieldBlock>
|
</FieldBlock>
|
||||||
{!selectedInstanceType?.spec?.acceleratable && (
|
{selectedInstanceType?.spec?.acceleratable && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={['spec', 'resources', 'accelerator']}
|
name={['spec', 'resources', 'accelerator']}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -156,7 +158,8 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
|||||||
min={1}
|
min={1}
|
||||||
max={maxGpuCount}
|
max={maxGpuCount}
|
||||||
step={1}
|
step={1}
|
||||||
disabled={action === PageAction.EDIT}
|
required
|
||||||
|
disabled={disabled || action === PageAction.EDIT}
|
||||||
label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })}
|
label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { currentClusterAtom } from '@/atoms/gpuservice';
|
import { currentClusterAtom } from '@/atoms/gpuservice';
|
||||||
import { getCurrentOrgNamespace } from '@/atoms/user';
|
import { getCurrentOrgNamespace } from '@/atoms/user';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { InputNumber as CInputNumber, Select } from '@gpustack/core-ui';
|
import {
|
||||||
|
InputNumber as CInputNumber,
|
||||||
|
LabelInfo,
|
||||||
|
Select
|
||||||
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Flex, Form, Radio } from 'antd';
|
import { Button, Flex, Form, Radio } from 'antd';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
@@ -95,13 +99,37 @@ const StorageVolume = ({
|
|||||||
onChange={(e) => handleModeChange(e.target.value)}
|
onChange={(e) => handleModeChange(e.target.value)}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({ id: 'gpuservice.storage.temporary' }),
|
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
|
value: StorageModeValueMap.Temporary
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: (
|
||||||
id: 'gpuservice.storage.persistent'
|
<LabelInfo
|
||||||
}),
|
description={intl.formatMessage({
|
||||||
|
id: 'gpuservice.storage.persistentVolume.tips'
|
||||||
|
})}
|
||||||
|
label={
|
||||||
|
<span className="text-primary">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'gpuservice.storage.persistentVolume'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
value: StorageModeValueMap.Existing
|
value: StorageModeValueMap.Existing
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { PageAction } from '@/config';
|
||||||
|
import type { PageActionType } from '@/config/types';
|
||||||
|
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
const useCreateInstance = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
|
const [openModalStatus, setOpenModalStatus] = useState<{
|
||||||
|
action: PageActionType;
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
currentData?: ListItem | null;
|
||||||
|
width?: number | string;
|
||||||
|
realAction?: string;
|
||||||
|
}>({
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
open: false,
|
||||||
|
width: undefined,
|
||||||
|
currentData: null,
|
||||||
|
realAction: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const openModal = (
|
||||||
|
action: PageActionType,
|
||||||
|
title: string,
|
||||||
|
currentData?: ListItem | null,
|
||||||
|
width?: number | string,
|
||||||
|
realAction?: string
|
||||||
|
) => {
|
||||||
|
setOpenModalStatus({
|
||||||
|
action,
|
||||||
|
title,
|
||||||
|
open: true,
|
||||||
|
currentData,
|
||||||
|
width,
|
||||||
|
realAction
|
||||||
|
});
|
||||||
|
saveScrollHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateInstanceModal = () => {
|
||||||
|
openModal(
|
||||||
|
PageAction.CREATE,
|
||||||
|
intl.formatMessage({ id: 'gpuservice.instance.add' }),
|
||||||
|
null,
|
||||||
|
'calc(100vw - 220px)'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditInstanceModal = (row: ListItem) => {
|
||||||
|
openModal(
|
||||||
|
PageAction.EDIT,
|
||||||
|
intl.formatMessage({ id: 'gpuservice.instance.edit' }),
|
||||||
|
row,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openViewInstanceModal = (row: ListItem) => {
|
||||||
|
openModal(
|
||||||
|
PageAction.VIEW,
|
||||||
|
intl.formatMessage({ id: 'common.button.view' }),
|
||||||
|
row,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
restoreScrollHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
openInstanceModalStatus: openModalStatus,
|
||||||
|
setOpenInstanceModalStatus: setOpenModalStatus,
|
||||||
|
openInstanceModal: openModal,
|
||||||
|
openCreateInstanceModal,
|
||||||
|
openEditInstanceModal,
|
||||||
|
openViewInstanceModal,
|
||||||
|
openRecreateInstanceModal,
|
||||||
|
closeInstanceModal: closeModal
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCreateInstance;
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
StatusTag
|
StatusTag
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button } from 'antd';
|
import { Button, Flex } from 'antd';
|
||||||
import type { ColumnsType } from 'antd/lib/table';
|
import type { ColumnsType } from 'antd/lib/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -190,6 +190,44 @@ const useInstancesColumns = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Instance Type',
|
||||||
|
dataIndex: ['spec', 'type'],
|
||||||
|
key: 'type',
|
||||||
|
sorter: false,
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
width: 230,
|
||||||
|
render: (text: string) => (
|
||||||
|
<AutoTooltip
|
||||||
|
ghost
|
||||||
|
style={{ maxWidth: 360, color: 'var(--ant-color-text) !important' }}
|
||||||
|
>
|
||||||
|
<Flex align="center">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontWeight: 400
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
NVIDIA A100 (x2)
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="text-secondary"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: 4,
|
||||||
|
height: 4,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'var(--ant-color-text-secondary)',
|
||||||
|
margin: '0 8px'
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
<span className="text-tertiary">4C / 16GB</span>
|
||||||
|
</Flex>
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||||
dataIndex: ['metadata', 'creationTimestamp'],
|
dataIndex: ['metadata', 'creationTimestamp'],
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { currentClusterAtom } from '@/atoms/gpuservice';
|
|||||||
import { getCurrentOrgNamespace } from '@/atoms/user';
|
import { getCurrentOrgNamespace } from '@/atoms/user';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
||||||
import type { PageActionType } from '@/config/types';
|
|
||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
||||||
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
||||||
@@ -18,7 +17,7 @@ import { useMemoizedFn } from 'ahooks';
|
|||||||
import { ConfigProvider, Divider, Flex, message, Modal, Table } from 'antd';
|
import { ConfigProvider, Divider, Flex, message, Modal, Table } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { PageContainerInner } from '../../_components/page-box';
|
import { PageContainerInner } from '../../_components/page-box';
|
||||||
import {
|
import {
|
||||||
deleteGPUServiceInstance,
|
deleteGPUServiceInstance,
|
||||||
@@ -29,10 +28,11 @@ import AddModal from './components/add-modal';
|
|||||||
import ViewEventsModal from './components/view-events-modal';
|
import ViewEventsModal from './components/view-events-modal';
|
||||||
import ViewLogsModal from './components/view-logs-modal';
|
import ViewLogsModal from './components/view-logs-modal';
|
||||||
import { FormData, ListItem } from './config/types';
|
import { FormData, ListItem } from './config/types';
|
||||||
|
import useCreateInstance from './hooks/use-create-instance';
|
||||||
import useInstancesColumns from './hooks/use-instances-columns';
|
import useInstancesColumns from './hooks/use-instances-columns';
|
||||||
import useViewEvents from './hooks/use-view-events';
|
import useViewEvents from './hooks/use-view-events';
|
||||||
import useViewLogs from './hooks/use-view-logs';
|
import useViewLogs from './hooks/use-view-logs';
|
||||||
import useCreateInstance from './services/use-create-instance';
|
import useCreateInstanceRequest from './services/use-create-instance';
|
||||||
import useUpdateInstance from './services/use-update-instance';
|
import useUpdateInstance from './services/use-update-instance';
|
||||||
|
|
||||||
const GPUService: React.FC = () => {
|
const GPUService: React.FC = () => {
|
||||||
@@ -108,8 +108,16 @@ const GPUService: React.FC = () => {
|
|||||||
contentForDelete: intl.formatMessage({ id: 'gpuservice.instance' })
|
contentForDelete: intl.formatMessage({ id: 'gpuservice.instance' })
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fetchData: createInstance } = useCreateInstance();
|
const { fetchData: createInstance } = useCreateInstanceRequest();
|
||||||
const { fetchData: updateInstance } = useUpdateInstance();
|
const { fetchData: updateInstance } = useUpdateInstance();
|
||||||
|
const {
|
||||||
|
openInstanceModalStatus,
|
||||||
|
openCreateInstanceModal,
|
||||||
|
openEditInstanceModal,
|
||||||
|
openViewInstanceModal,
|
||||||
|
openRecreateInstanceModal,
|
||||||
|
closeInstanceModal
|
||||||
|
} = useCreateInstance();
|
||||||
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
|
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
|
||||||
useViewLogs();
|
useViewLogs();
|
||||||
const {
|
const {
|
||||||
@@ -131,18 +139,6 @@ const GPUService: React.FC = () => {
|
|||||||
[clusterList]
|
[clusterList]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [openAddModalStatus, setOpenAddModalStatus] = useState<{
|
|
||||||
action: PageActionType;
|
|
||||||
open: boolean;
|
|
||||||
title: string;
|
|
||||||
currentData?: ListItem | null;
|
|
||||||
}>({
|
|
||||||
action: PageAction.CREATE,
|
|
||||||
title: '',
|
|
||||||
open: false,
|
|
||||||
currentData: null
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchClusterList({ page: -1 }).then((clusters) => {
|
fetchClusterList({ page: -1 }).then((clusters) => {
|
||||||
const k8sClusters = clusters.filter(
|
const k8sClusters = clusters.filter(
|
||||||
@@ -172,38 +168,19 @@ const GPUService: React.FC = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAddInstance = () => {
|
|
||||||
setOpenAddModalStatus({
|
|
||||||
action: PageAction.CREATE,
|
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.add' }),
|
|
||||||
open: true,
|
|
||||||
currentData: null
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditInstance = (row: ListItem) => {
|
|
||||||
setOpenAddModalStatus({
|
|
||||||
action: PageAction.EDIT,
|
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.edit' }),
|
|
||||||
open: true,
|
|
||||||
currentData: row
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
setOpenAddModalStatus({
|
|
||||||
action: PageAction.CREATE,
|
|
||||||
title: '',
|
|
||||||
open: false,
|
|
||||||
currentData: null
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleModalOk = async (data: FormData) => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (openAddModalStatus.action === PageAction.EDIT) {
|
if (openInstanceModalStatus.realAction === PageAction.CREATE) {
|
||||||
|
await deleteInstance(
|
||||||
|
openInstanceModalStatus.currentData!.metadata?.name as any
|
||||||
|
);
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 500);
|
||||||
|
});
|
||||||
|
await createInstance({ data });
|
||||||
|
} else if (openInstanceModalStatus.action === PageAction.EDIT) {
|
||||||
await updateInstance({
|
await updateInstance({
|
||||||
id: openAddModalStatus.currentData!.id,
|
id: openInstanceModalStatus.currentData!.id,
|
||||||
data
|
data
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -211,7 +188,7 @@ const GPUService: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
closeModal();
|
closeInstanceModal();
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(intl.formatMessage({ id: 'common.message.fail' }));
|
message.error(intl.formatMessage({ id: 'common.message.fail' }));
|
||||||
@@ -249,8 +226,10 @@ const GPUService: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSelect = useMemoizedFn((val: string, row: ListItem) => {
|
const handleSelect = useMemoizedFn((val: string, row: ListItem) => {
|
||||||
if (val === 'edit') {
|
if (val === 'view') {
|
||||||
handleEditInstance(row);
|
openViewInstanceModal(row);
|
||||||
|
} else if (val === 'edit') {
|
||||||
|
openEditInstanceModal(row);
|
||||||
} else if (val === 'delete') {
|
} else if (val === 'delete') {
|
||||||
handleDelete({
|
handleDelete({
|
||||||
...row,
|
...row,
|
||||||
@@ -258,7 +237,9 @@ const GPUService: React.FC = () => {
|
|||||||
id: row.metadata?.name as any
|
id: row.metadata?.name as any
|
||||||
});
|
});
|
||||||
} else if (val === 'recreate') {
|
} else if (val === 'recreate') {
|
||||||
handleRecreate(row);
|
// Keep handleRecreate above for the old confirm-modal flow. The current
|
||||||
|
// UX opens the editable form and recreates after submit.
|
||||||
|
openRecreateInstanceModal(row);
|
||||||
} else if (val === 'viewlog') {
|
} else if (val === 'viewlog') {
|
||||||
openViewLogsModal(row);
|
openViewLogsModal(row);
|
||||||
} else if (val === 'viewevent') {
|
} else if (val === 'viewevent') {
|
||||||
@@ -295,7 +276,7 @@ const GPUService: React.FC = () => {
|
|||||||
subTitle={intl.formatMessage({
|
subTitle={intl.formatMessage({
|
||||||
id: 'noresult.gpuservice.instance.subTitle'
|
id: 'noresult.gpuservice.instance.subTitle'
|
||||||
})}
|
})}
|
||||||
onClick={handleAddInstance}
|
onClick={openCreateInstanceModal}
|
||||||
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -340,7 +321,7 @@ const GPUService: React.FC = () => {
|
|||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
handleSelectChange={handleClusterChange}
|
handleSelectChange={handleClusterChange}
|
||||||
handleDeleteByBatch={handleDeleteBatch}
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
handleClickPrimary={handleAddInstance}
|
handleClickPrimary={openCreateInstanceModal}
|
||||||
handleInputChange={handleNameChange}
|
handleInputChange={handleNameChange}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
widths={{ input: 300 }}
|
widths={{ input: 300 }}
|
||||||
@@ -362,11 +343,13 @@ const GPUService: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
<AddModal
|
<AddModal
|
||||||
open={openAddModalStatus.open}
|
open={openInstanceModalStatus.open}
|
||||||
action={openAddModalStatus.action}
|
action={openInstanceModalStatus.action}
|
||||||
title={openAddModalStatus.title}
|
title={openInstanceModalStatus.title}
|
||||||
data={openAddModalStatus.currentData}
|
data={openInstanceModalStatus.currentData}
|
||||||
onCancel={closeModal}
|
width={openInstanceModalStatus.width}
|
||||||
|
realAction={openInstanceModalStatus.realAction}
|
||||||
|
onCancel={closeInstanceModal}
|
||||||
onOk={handleModalOk}
|
onOk={handleModalOk}
|
||||||
/>
|
/>
|
||||||
<ViewLogsModal
|
<ViewLogsModal
|
||||||
|
|||||||
@@ -22,6 +22,5 @@
|
|||||||
.formWrapper {
|
.formWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 34%;
|
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user