= ({ item, onChange }) => {
= ({ item, onChange }) => {
);
};
-const Env: React.FC = () => {
+const Env: React.FC<{ disabled?: boolean }> = ({ disabled = false }) => {
const intl = useIntl();
const form = Form.useFormInstance();
const env = Form.useWatch(['spec', 'env'], form) || [];
@@ -103,6 +109,7 @@ const Env: React.FC = () => {
]}
>
{
>
{(item, index) => (
= {
- 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 = ({
+const PortNameInput: React.FC = ({
value,
onChange,
- placeholder
+ placeholder,
+ disabled = false
}) => {
const [localValue, setLocalValue] = useState(value ?? '');
const isComposingRef = useRef(false);
@@ -85,6 +90,7 @@ const PortNameInput: React.FC = ({
return (
= ({
);
};
-const PortItem: React.FC = ({ item, index, onChange }) => {
+const PortItem: React.FC = ({ item, index, disabled = false, onChange }) => {
const intl = useIntl();
return (
{
@@ -136,8 +143,9 @@ const PortItem: React.FC = ({ item, index, onChange }) => {
{
@@ -160,6 +168,7 @@ const PortItem: React.FC = ({ item, index, onChange }) => {
= ({ item, index, onChange }) => {
);
};
-const Ports: React.FC = () => {
+const Ports: React.FC<{ disabled?: boolean }> = ({ disabled = false }) => {
const intl = useIntl();
const form = Form.useFormInstance();
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();
}
}
]}
>
{
>
{(item, index) => (
{
() => [
...Object.values(GPUsConfigs).map((item) => ({
label: item.label,
- value: item.value
+ value: item.gpuVendor as string
})),
{ label: 'CPU', value: 'cpu' }
],