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
@@ -1,7 +1,14 @@
import { Steps } from 'antd';
import _ from 'lodash';
import React from 'react';
import styled from 'styled-components';
// `description` is intentionally omitted: the upstream step list ships
// hardcoded English copy that isn't translated. Keeping it would cause the
// step to render both the localized title and the English description side
// by side. Same reason we don't surface `subTitle`.
const ANTD_STEP_KEYS = ['title', 'icon', 'status', 'disabled'] as const;
const Wrapper = styled.div`
display: flex;
flex-direction: column;
@@ -39,7 +46,14 @@ const ClusterSteps: React.FC<{
}> = (props) => {
const { steps, currentStep = 0, onChange } = props;
const visibleSteps = steps.filter((step) => !step.hideInSteps);
// Pick only props antd's Step accepts — the upstream step objects carry
// custom keys (showModules/showForms/showButtons/...) that would otherwise
// be forwarded to the DOM and trigger "React does not recognize the X
// prop on a DOM element" warnings. _.pick keeps missing keys missing
// (rather than explicitly `undefined`) so antd's defaults still kick in.
const visibleSteps = steps
.filter((step) => !step.hideInSteps)
.map((step) => _.pick(step, ANTD_STEP_KEYS));
const styles: Record<string, any> = {
root: {