fix: using select handler for provider hint
This commit is contained in:
@@ -5,7 +5,13 @@ import { ColumnWrapper } from '@gpustack/core-ui';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
createCluster,
|
createCluster,
|
||||||
@@ -55,9 +61,6 @@ const MainWrapper = styled.div`
|
|||||||
|
|
||||||
const ClusterCreate: React.FC<{
|
const ClusterCreate: React.FC<{
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
// Preselect a provider and skip the provider-catalog step. Set by
|
|
||||||
// empty-state CTAs that already know which kind of cluster the user
|
|
||||||
// is heading for (e.g. GPU Service's "Add a Kubernetes Cluster").
|
|
||||||
providerHint?: string;
|
providerHint?: string;
|
||||||
setCurrentTitle?: (title: string) => void;
|
setCurrentTitle?: (title: string) => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
@@ -68,9 +71,7 @@ const ClusterCreate: React.FC<{
|
|||||||
const [credentialList, setCredentialList] = useState<
|
const [credentialList, setCredentialList] = useState<
|
||||||
Global.BaseOption<number, { provider: ProviderType }>[]
|
Global.BaseOption<number, { provider: ProviderType }>[]
|
||||||
>([]);
|
>([]);
|
||||||
// When the caller already picked a provider for us, start one step
|
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||||
// in — provider catalog is step 0; configure is step 1.
|
|
||||||
const [currentStep, setCurrentStep] = useState<number>(providerHint ? 1 : 0);
|
|
||||||
const [registrationInfo, setRegistrationInfo] = useState<{
|
const [registrationInfo, setRegistrationInfo] = useState<{
|
||||||
token: string;
|
token: string;
|
||||||
image: string;
|
image: string;
|
||||||
@@ -84,7 +85,7 @@ const ClusterCreate: React.FC<{
|
|||||||
cluster_id: 0
|
cluster_id: 0
|
||||||
});
|
});
|
||||||
const [extraData, setExtraData] = useState<ClusterFormData>({
|
const [extraData, setExtraData] = useState<ClusterFormData>({
|
||||||
provider: (providerHint as ProviderType) ?? ProviderValueMap.Docker
|
provider: ProviderValueMap.Docker
|
||||||
} as ClusterFormData);
|
} as ClusterFormData);
|
||||||
const [formValues, setFormValues] = useState<Record<string, any>>({});
|
const [formValues, setFormValues] = useState<Record<string, any>>({});
|
||||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
|
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
|
||||||
@@ -217,6 +218,12 @@ const ClusterCreate: React.FC<{
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (providerHint) {
|
||||||
|
handleSelectProvider(providerHint);
|
||||||
|
}
|
||||||
|
}, [providerHint]);
|
||||||
|
|
||||||
const renderForms = () => {
|
const renderForms = () => {
|
||||||
const step = steps[currentStep];
|
const step = steps[currentStep];
|
||||||
const formKeys = step?.showForms || [];
|
const formKeys = step?.showForms || [];
|
||||||
@@ -331,7 +338,11 @@ const ClusterCreate: React.FC<{
|
|||||||
<MainWrapper>
|
<MainWrapper>
|
||||||
{!isAddWorkerStep && (
|
{!isAddWorkerStep && (
|
||||||
<StepWrapper>
|
<StepWrapper>
|
||||||
<ClusterSteps steps={steps} currentStep={currentStep}></ClusterSteps>
|
<ClusterSteps
|
||||||
|
steps={steps}
|
||||||
|
currentStep={currentStep}
|
||||||
|
selectedProvider={extraData.provider}
|
||||||
|
></ClusterSteps>
|
||||||
</StepWrapper>
|
</StepWrapper>
|
||||||
)}
|
)}
|
||||||
<ColumnWrapper
|
<ColumnWrapper
|
||||||
@@ -357,11 +368,6 @@ const ClusterCreate: React.FC<{
|
|||||||
>
|
>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
{currentStep === 0 && (
|
{currentStep === 0 && (
|
||||||
// Catalog belongs to step 0 only. The previous gate used
|
|
||||||
// ``startStep`` which is 1 when ``providerHint`` skips the
|
|
||||||
// catalog — that wrongly re-rendered it on top of the
|
|
||||||
// configure form for entry points like "Add a Kubernetes
|
|
||||||
// Cluster".
|
|
||||||
<ProviderCatalog
|
<ProviderCatalog
|
||||||
cols={2}
|
cols={2}
|
||||||
dataList={providerList}
|
dataList={providerList}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { Steps } from 'antd';
|
import { Steps, Typography } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { ProviderType } from '../config';
|
||||||
|
|
||||||
// `description` is intentionally omitted: the upstream step list ships
|
const { Text } = Typography;
|
||||||
// hardcoded English copy that isn't translated. Keeping it would cause the
|
|
||||||
// step to render both the localized title and the English description side
|
const ANTD_STEP_KEYS = [
|
||||||
// by side. Same reason we don't surface `subTitle`.
|
'title',
|
||||||
const ANTD_STEP_KEYS = ['title', 'icon', 'status', 'disabled'] as const;
|
'icon',
|
||||||
|
'status',
|
||||||
|
'disabled',
|
||||||
|
'subTitle'
|
||||||
|
] as const;
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -31,6 +36,11 @@ const Box = styled.div`
|
|||||||
.ant-steps-item-rail-wait {
|
.ant-steps-item-rail-wait {
|
||||||
--steps-item-solid-line-color: var(--ant-color-split);
|
--steps-item-solid-line-color: var(--ant-color-split);
|
||||||
}
|
}
|
||||||
|
.ant-steps-item-subtitle {
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
}
|
||||||
&:not(.ant-steps-panel) {
|
&:not(.ant-steps-panel) {
|
||||||
.ant-steps-item-finish {
|
.ant-steps-item-finish {
|
||||||
--steps-item-icon-bg-color: var(--ant-color-primary);
|
--steps-item-icon-bg-color: var(--ant-color-primary);
|
||||||
@@ -43,17 +53,18 @@ const ClusterSteps: React.FC<{
|
|||||||
currentStep: number;
|
currentStep: number;
|
||||||
onChange?: (step: number) => void;
|
onChange?: (step: number) => void;
|
||||||
steps: any[];
|
steps: any[];
|
||||||
|
selectedProvider?: ProviderType;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { steps, currentStep = 0, onChange } = props;
|
const { steps, currentStep = 0, onChange } = props;
|
||||||
|
|
||||||
// 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
|
const visibleSteps = steps
|
||||||
.filter((step) => !step.hideInSteps)
|
.filter((step) => !step.hideInSteps)
|
||||||
.map((step) => _.pick(step, ANTD_STEP_KEYS));
|
.map((step, index) => {
|
||||||
|
return {
|
||||||
|
..._.pick(step, ANTD_STEP_KEYS),
|
||||||
|
subTitle: index === 0 ? <span>[{props.selectedProvider}]</span> : ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const styles: Record<string, any> = {
|
const styles: Record<string, any> = {
|
||||||
root: {
|
root: {
|
||||||
|
|||||||
@@ -97,14 +97,10 @@ const GPUService: React.FC = () => {
|
|||||||
fetchClusterList({ page: -1 });
|
fetchClusterList({ page: -1 });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// GPU Service today is Kubernetes-only — Docker / cloud clusters
|
const hasK8sCluster = useMemo(
|
||||||
// can't host the CRDs. Filter so the page reflects scheduling
|
() => clusterList.some((c) => c.provider === ProviderValueMap.Kubernetes),
|
||||||
// reality even when the caller owns non-K8s clusters.
|
|
||||||
const k8sClusterList = useMemo(
|
|
||||||
() => clusterList.filter((c) => c.provider === ProviderValueMap.Kubernetes),
|
|
||||||
[clusterList]
|
[clusterList]
|
||||||
);
|
);
|
||||||
const hasK8sCluster = k8sClusterList.length > 0;
|
|
||||||
|
|
||||||
const handleModalOk = async (data: FormData) => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
try {
|
try {
|
||||||
@@ -157,11 +153,6 @@ const GPUService: React.FC = () => {
|
|||||||
|
|
||||||
const renderEmpty = (type?: string) => {
|
const renderEmpty = (type?: string) => {
|
||||||
if (type !== 'Table') return;
|
if (type !== 'Table') return;
|
||||||
// No K8s cluster the caller can schedule on — replace the "no
|
|
||||||
// instances" empty state with a cluster-bootstrap prompt. The
|
|
||||||
// "Add cluster" CTA is reserved for callers who can actually
|
|
||||||
// create one (platform admin / Org owner); members see the
|
|
||||||
// explanation without a misleading button.
|
|
||||||
if (!clusterLoading && !hasK8sCluster) {
|
if (!clusterLoading && !hasK8sCluster) {
|
||||||
return (
|
return (
|
||||||
<NoResult
|
<NoResult
|
||||||
|
|||||||
Reference in New Issue
Block a user