feat: add instance recreate action
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { validateLabelNameRegxFor63 } from '@/config';
|
||||
import {
|
||||
Input as CInput,
|
||||
InputNumber,
|
||||
@@ -26,10 +27,15 @@ export interface BasicResourceMax {
|
||||
|
||||
interface BasicProps {
|
||||
page?: 'template' | 'instance';
|
||||
disabled?: boolean;
|
||||
onceMaxRequest?: BasicResourceMax;
|
||||
}
|
||||
|
||||
const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
const Basic: React.FC<BasicProps> = ({
|
||||
page = 'template',
|
||||
onceMaxRequest,
|
||||
disabled
|
||||
}) => {
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
const intl = useIntl();
|
||||
|
||||
@@ -68,10 +74,17 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('input', 'common.table.name')
|
||||
},
|
||||
{
|
||||
pattern: validateLabelNameRegxFor63,
|
||||
message: intl.formatMessage({
|
||||
id: 'gpuservice.form.rule.name'
|
||||
})
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CInput.Input
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||
required
|
||||
/>
|
||||
@@ -105,6 +118,7 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({ id: 'resources.table.vendor' })}
|
||||
required
|
||||
options={[...manufacturerOptions, { label: 'CPU', value: 'cpu' }]}
|
||||
@@ -123,12 +137,14 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
]}
|
||||
>
|
||||
<CInput.Input
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({ id: 'gpuservice.template.image' })}
|
||||
required
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['spec', 'imagePullPolicy']}>
|
||||
<SealSelect
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.template.imagePullPolicy'
|
||||
})}
|
||||
@@ -144,6 +160,7 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
getValueProps={(value) => ({ value: stringifyCommand(value) })}
|
||||
>
|
||||
<Textarea
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({ id: 'gpuservice.template.command' })}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'gpuservice.template.command.placeholder'
|
||||
@@ -164,6 +181,7 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'clusters.volume.mountPath.format'
|
||||
})}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
@@ -181,6 +199,7 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
onceMaxRequest?.localStorage
|
||||
)}
|
||||
max={onceMaxRequest?.localStorage ?? undefined}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
@@ -195,6 +214,7 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
<InputNumber
|
||||
label={renderMaxLabel('CPU', onceMaxRequest?.cpu)}
|
||||
max={onceMaxRequest?.cpu ?? undefined}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
@@ -212,13 +232,14 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
||||
onceMaxRequest?.memory
|
||||
)}
|
||||
max={onceMaxRequest?.memory ?? undefined}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<Ports />
|
||||
<Env />
|
||||
<Ports disabled={disabled} />
|
||||
<Env disabled={disabled} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,12 +9,17 @@ interface EnvItemProps {
|
||||
onChange: (item: EnvItemType) => void;
|
||||
}
|
||||
|
||||
const EnvItem: React.FC<EnvItemProps> = ({ item, onChange }) => {
|
||||
const EnvItem: React.FC<EnvItemProps & { disabled?: boolean }> = ({
|
||||
item,
|
||||
onChange,
|
||||
disabled = false
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<CInput.Input
|
||||
disabled={disabled}
|
||||
value={item.name}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'gpuservice.template.env.name'
|
||||
@@ -30,6 +35,7 @@ const EnvItem: React.FC<EnvItemProps> = ({ item, onChange }) => {
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<CInput.Input
|
||||
disabled={disabled}
|
||||
value={item.value}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'gpuservice.template.env.value'
|
||||
@@ -47,7 +53,7 @@ const EnvItem: React.FC<EnvItemProps> = ({ item, onChange }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Env: React.FC = () => {
|
||||
const Env: React.FC<{ disabled?: boolean }> = ({ disabled = false }) => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const env = Form.useWatch(['spec', 'env'], form) || [];
|
||||
@@ -103,6 +109,7 @@ const Env: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<MetadataList
|
||||
disabled={disabled}
|
||||
dataList={env}
|
||||
btnText={intl.formatMessage({ id: 'gpuservice.template.env.add' })}
|
||||
label={intl.formatMessage({ id: 'gpuservice.template.env' })}
|
||||
@@ -111,6 +118,7 @@ const Env: React.FC = () => {
|
||||
>
|
||||
{(item, index) => (
|
||||
<EnvItem
|
||||
disabled={disabled}
|
||||
key={index}
|
||||
index={index}
|
||||
item={item}
|
||||
|
||||
@@ -6,12 +6,15 @@ import {
|
||||
} from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FormData, PortItem as PortItemType } from '../config/types';
|
||||
|
||||
type PortProtocol = PortItemType['protocol'];
|
||||
|
||||
const PORT_NAME_MAX = 16;
|
||||
const PORT_MIN = 1;
|
||||
const PORT_MAX = 65535;
|
||||
|
||||
const protocolOptions = [
|
||||
{
|
||||
@@ -25,9 +28,9 @@ const protocolOptions = [
|
||||
];
|
||||
|
||||
const wellKnownTcpPortNames: Record<number, string> = {
|
||||
22: 'SSH',
|
||||
80: 'HTTP',
|
||||
443: 'HTTPS'
|
||||
22: 'ssh',
|
||||
80: 'http',
|
||||
443: 'https'
|
||||
};
|
||||
|
||||
const getAutoFilledName = (
|
||||
@@ -57,6 +60,7 @@ const getAutoFilledName = (
|
||||
};
|
||||
|
||||
interface PortItemProps {
|
||||
disabled?: boolean;
|
||||
item: PortItemType;
|
||||
index: number;
|
||||
onChange: (item: PortItemType) => void;
|
||||
@@ -68,10 +72,11 @@ interface PortNameInputProps {
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
const PortNameInput: React.FC<PortNameInputProps> = ({
|
||||
const PortNameInput: React.FC<PortNameInputProps & { disabled?: boolean }> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder
|
||||
placeholder,
|
||||
disabled = false
|
||||
}) => {
|
||||
const [localValue, setLocalValue] = useState<string>(value ?? '');
|
||||
const isComposingRef = useRef(false);
|
||||
@@ -85,6 +90,7 @@ const PortNameInput: React.FC<PortNameInputProps> = ({
|
||||
return (
|
||||
<CInput.Input
|
||||
showCount
|
||||
disabled={disabled}
|
||||
maxLength={PORT_NAME_MAX}
|
||||
value={localValue}
|
||||
placeholder={placeholder}
|
||||
@@ -109,12 +115,13 @@ const PortNameInput: React.FC<PortNameInputProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const PortItem: React.FC<PortItemProps> = ({ item, index, onChange }) => {
|
||||
const PortItem: React.FC<PortItemProps & { disabled?: boolean }> = ({ item, index, disabled = false, onChange }) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
|
||||
<div style={{ width: 120 }}>
|
||||
<SealSelect
|
||||
disabled={disabled}
|
||||
value={item.protocol}
|
||||
options={protocolOptions}
|
||||
onChange={(value) => {
|
||||
@@ -136,8 +143,9 @@ const PortItem: React.FC<PortItemProps> = ({ item, index, onChange }) => {
|
||||
</div>
|
||||
<div style={{ width: 120 }}>
|
||||
<CInputNumber
|
||||
min={1}
|
||||
max={65535}
|
||||
min={PORT_MIN}
|
||||
max={PORT_MAX}
|
||||
disabled={disabled}
|
||||
precision={0}
|
||||
value={item.port}
|
||||
onChange={(value) => {
|
||||
@@ -160,6 +168,7 @@ const PortItem: React.FC<PortItemProps> = ({ item, index, onChange }) => {
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<PortNameInput
|
||||
disabled={disabled}
|
||||
value={item.name}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'gpuservice.template.ports.name'
|
||||
@@ -176,13 +185,15 @@ const PortItem: React.FC<PortItemProps> = ({ item, index, onChange }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Ports: React.FC = () => {
|
||||
const Ports: React.FC<{ disabled?: boolean }> = ({ disabled = false }) => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const ports = Form.useWatch(['spec', 'ports'], form) || [];
|
||||
|
||||
const updatePorts = (list: PortItemType[]) => {
|
||||
form.setFieldValue(['spec', 'ports'], list);
|
||||
// setFieldValue doesn't run validators — re-run them so users see errors live.
|
||||
form.validateFields([['spec', 'ports']]).catch(() => {});
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
@@ -213,27 +224,31 @@ const Ports: React.FC = () => {
|
||||
name={['spec', 'ports']}
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, value) => {
|
||||
validator: async (_rule, value) => {
|
||||
if (!value?.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const hasInvalidPort = value.some(
|
||||
(item: PortItemType) => !item.protocol || !item.port
|
||||
);
|
||||
if (hasInvalidPort) {
|
||||
|
||||
const items = value as PortItemType[];
|
||||
|
||||
if (_.some(items, (item) => !item.protocol || !item.port)) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
intl.formatMessage({
|
||||
id: 'gpuservice.template.ports.invalid'
|
||||
})
|
||||
intl.formatMessage(
|
||||
{ id: 'common.validate.value' },
|
||||
{
|
||||
name: intl.formatMessage({
|
||||
id: 'gpuservice.template.ports'
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
const hasInvalidName = value.some(
|
||||
(item: PortItemType) =>
|
||||
item.name && item.name.length > PORT_NAME_MAX
|
||||
);
|
||||
if (hasInvalidName) {
|
||||
|
||||
const namedItems = items.filter((item) => !!item.name);
|
||||
|
||||
if (_.some(namedItems, (item) => item.name!.length > PORT_NAME_MAX)) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
intl.formatMessage({
|
||||
@@ -242,12 +257,25 @@ const Ports: React.FC = () => {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const names = namedItems.map((item) => item.name);
|
||||
if (_.uniq(names).length !== names.length) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
intl.formatMessage({
|
||||
id: 'gpuservice.template.ports.name.duplicate'
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]}
|
||||
>
|
||||
<MetadataList
|
||||
disabled={disabled}
|
||||
dataList={ports}
|
||||
btnText={intl.formatMessage({ id: 'gpuservice.template.ports.add' })}
|
||||
label={intl.formatMessage({ id: 'gpuservice.template.ports' })}
|
||||
@@ -256,6 +284,7 @@ const Ports: React.FC = () => {
|
||||
>
|
||||
{(item, index) => (
|
||||
<PortItem
|
||||
disabled={disabled}
|
||||
key={index}
|
||||
index={index}
|
||||
item={item}
|
||||
|
||||
@@ -11,8 +11,8 @@ import { useIntl } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import _ from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { GPUsConfigs } from '../../resources/config/gpu-driver';
|
||||
import PageBox from '../../_components/page-box';
|
||||
import { GPUsConfigs } from '../../resources/config/gpu-driver';
|
||||
import {
|
||||
createGPUServiceTemplate,
|
||||
deleteGPUServiceTemplate,
|
||||
@@ -55,7 +55,7 @@ const GPUServiceTemplates: React.FC = () => {
|
||||
() => [
|
||||
...Object.values(GPUsConfigs).map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value
|
||||
value: item.gpuVendor as string
|
||||
})),
|
||||
{ label: 'CPU', value: 'cpu' }
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user