diff --git a/src/pages/cluster-management/components/support-gpus.tsx b/src/pages/cluster-management/components/support-gpus.tsx
index 4709e953..30acd5f2 100644
--- a/src/pages/cluster-management/components/support-gpus.tsx
+++ b/src/pages/cluster-management/components/support-gpus.tsx
@@ -76,14 +76,19 @@ const ProviderImage = ({ src, height }: { src: string; height?: number }) => {
interface SupportedHardwareProps {
onSelect?: (provider: string, item: any) => void;
- current?: string;
+ // Single (legacy) or array of selected GPU driver keys (multi-select).
+ current?: string | string[];
clickable?: boolean;
+ // Set of GPU driver keys that are valid to pick. When provided, items
+ // outside this set render as disabled. Undefined means "no restriction".
+ availableKeys?: Set
;
}
const SupportedHardware: React.FC = ({
onSelect,
clickable,
- current
+ current,
+ availableKeys
}) => {
const intl = useIntl();
const { userSettings } = useUserSettings();
@@ -199,13 +204,20 @@ const SupportedHardware: React.FC = ({
}
];
+ const platformsWithDisabled = availableKeys
+ ? supportedHardPlatforms.map((p) => ({
+ ...p,
+ disabled: !availableKeys.has(p.value)
+ }))
+ : supportedHardPlatforms;
+
return (
{
- const runtime = GPUsConfigs[params.currentGPU || '']?.runtime || '';
- return `curl -k -L '${params.server}/${GPUSTACK_API_BASE_URL}/clusters/${params.clusterId}/manifests${runtime ? `?runtime=${runtime}` : ''}' \\
+ const keys =
+ params.currentGPUs && params.currentGPUs.length > 0
+ ? params.currentGPUs
+ : params.currentGPU
+ ? [params.currentGPU]
+ : [];
+ const runtimes = keys
+ .map((k) => GPUsConfigs[k]?.runtime)
+ .filter((r): r is string => !!r);
+ const query = runtimes.map((r) => `runtime=${r}`).join('&');
+ return `curl -k -L '${params.server}/${GPUSTACK_API_BASE_URL}/clusters/${params.clusterId}/manifests${query ? `?${query}` : ''}' \\
--header 'Authorization: Bearer ${params.registrationToken}' | kubectl apply -f -`;
};
diff --git a/src/pages/cluster-management/config/types.ts b/src/pages/cluster-management/config/types.ts
index fa48277c..0217f369 100644
--- a/src/pages/cluster-management/config/types.ts
+++ b/src/pages/cluster-management/config/types.ts
@@ -68,6 +68,25 @@ export interface VolumeMount {
};
}
+export interface ImageCredential {
+ registry: string;
+ username: string;
+ password: string;
+}
+
+export interface K8sOptions {
+ // Backend serializes K8sOptions with camelCase aliases (by_alias=True on
+ // the SQL JSON column). The top-level `k8s_options` field on the cluster
+ // stays snake_case, but everything inside follows the backend wire shape.
+ volumeMounts?: VolumeMount[];
+ imageCredentials?: ImageCredential[];
+ nodeSelector?: Record;
+ gpuVendorOverrides?: Record<
+ string,
+ { nodeSelector?: Record }
+ >;
+}
+
export interface ClusterListItem {
name: string;
display_name: string;
@@ -87,7 +106,7 @@ export interface ClusterListItem {
state: ClusterStatusType;
state_message: string;
worker_pools: NodePoolListItem[];
- k8s_volume_mounts?: VolumeMount[];
+ k8s_options?: K8sOptions;
// Backend ClusterPublic carries this; admin-"All" namespace
// resolution falls back to the cluster's owner Org name.
owner_principal_id?: number;
@@ -104,7 +123,7 @@ export interface ClusterFormData {
server_url?: string;
worker_config?: Record;
worker_pools?: NodePoolFormData[];
- k8s_volume_mounts?: VolumeMount[];
+ k8s_options?: K8sOptions;
}
export interface SystemConfig {
diff --git a/src/pages/cluster-management/step-forms/advance-config.tsx b/src/pages/cluster-management/step-forms/advance-config.tsx
index d9d52e00..e1ff7ce3 100644
--- a/src/pages/cluster-management/step-forms/advance-config.tsx
+++ b/src/pages/cluster-management/step-forms/advance-config.tsx
@@ -7,9 +7,13 @@ import { useIntl } from '@umijs/max';
import { Button, Form } from 'antd';
import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
import styled from 'styled-components';
+import K8sPodSpec from '../components/k8s-pod-spec';
import K8SVolumeMount from '../components/k8s-volume-mount';
import { ProviderType, ProviderValueMap } from '../config';
-import { ClusterFormData as FormData } from '../config/types';
+import {
+ ClusterFormData as FormData,
+ ClusterListItem as ListItem
+} from '../config/types';
import schema from '../config/worker-config.json';
import { dockerConfig, kubernetesConfig } from '../config/yaml-template';
@@ -28,8 +32,9 @@ const Title = styled.div`
const ClusterAdvanceConfig: React.FC<{
action: PageActionType;
provider: ProviderType;
+ currentData?: ListItem;
ref?: any;
-}> = forwardRef(({ action, provider }, ref) => {
+}> = forwardRef(({ action, provider, currentData }, ref) => {
const [form] = Form.useForm();
const intl = useIntl();
const editorRef = React.useRef(null);
@@ -75,7 +80,12 @@ const ClusterAdvanceConfig: React.FC<{
{provider === ProviderValueMap.Kubernetes && (
-
+ <>
+
+
+ >
)}
{intl.formatMessage({ id: 'clusters.create.workerConfig' })}