feat: extend cluster k8s_options with multi-vendor manifest support

- New k8s_pod_spec form sections (image credentials, node selector,
  gpu vendor overrides) under k8s_options, replacing the legacy flat
  k8s_volume_mounts list. UI keys aligned to the backend wire shape
  (snake_case k8s_options + camelCase inside).
- System default container registry is pre-filled into the first image
  credential when creating a new cluster; empty username/password are
  coerced to null on submit to match the Optional[str] backend schema.
- Register cluster flow supports multi-runtime selection gated by the
  cluster's gpuVendorOverrides: non-override vendors stay single-select
  with an inline hint; multi-add only opens once an override vendor is
  picked, and non-override cards become disabled in that state.
- Manifest URL emits multiple ?runtime= params; check-env step combines
  per-vendor commands; downstream steps are disabled when no vendor is
  selected.
- Pre-validate gpuVendorOverrides at save time (non-empty selector, no
  duplicates across vendors, no key clash with base nodeSelector) so
  the user sees the error before hitting the manifest endpoint.
- Misc: dark-mode background of the k8s_pod_spec / volume mount titles
  no longer clashes with the drawer; cluster Steps no longer leaks the
  internal showModules/showForms props to the DOM.
This commit is contained in:
Yuxing Deng
2026-05-26 16:54:14 +08:00
parent 91c3d0b814
commit acb90531b2
21 changed files with 1023 additions and 112 deletions
+15 -2
View File
@@ -44,13 +44,26 @@ export const ProviderLabelMap = {
};
export const generateK8sRegisterCommand = (params: {
// Either a single GPU driver key (legacy single-select) or an array of
// keys (multi-vendor mode). Both feed into a list of runtimes for the
// ?runtime=... query parameters the backend accepts (repeatable).
currentGPU?: string;
currentGPUs?: string[];
server: string;
clusterId: number | null;
registrationToken: string;
}) => {
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 -`;
};
+21 -2
View File
@@ -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<string, string>;
gpuVendorOverrides?: Record<
string,
{ nodeSelector?: Record<string, string> }
>;
}
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<string, any>;
worker_pools?: NodePoolFormData[];
k8s_volume_mounts?: VolumeMount[];
k8s_options?: K8sOptions;
}
export interface SystemConfig {