From 11ed7a83328aecbdbb8dd1fc720422b3e7b60e0d Mon Sep 17 00:00:00 2001 From: jgyselov Date: Wed, 6 Aug 2025 11:02:36 +0200 Subject: [PATCH 1/7] Add External platforms field --- libs/locales/lib/en/translation.json | 6 ++ .../ClusterDeploymentReviewStep.tsx | 11 +++ .../ClusterDeploymentWizard.tsx | 3 + .../ClusterDetailsFormFields.tsx | 13 +++- .../lib/cim/components/helpers/toAssisted.ts | 10 ++- .../ExternalPlatformsDropdown.tsx | 74 +++++++++++++++++++ .../components/clusterConfiguration/index.ts | 1 + 7 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index b9ce10423b..37350d634f 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -316,6 +316,8 @@ "ai:Exclude destination domain names, IP addresses, or other network CIDRs from proxying by adding them to this comma-separated list.": "Exclude destination domain names, IP addresses, or other network CIDRs from proxying by adding them to this comma-separated list.", "ai:Exposes the service externally using a cloud provider's load balancer": "Exposes the service externally using a cloud provider's load balancer", "ai:Exposes the service on each node's IP at a static port": "Exposes the service on each node's IP at a static port", + "ai:External cloud provider": "External cloud provider", + "ai:External partner platform": "External partner platform", "ai:Externally provisioned": "Externally provisioned", "ai:Failed": "Failed", "ai:Failed on {{humanizedDataTime}}": "Failed on {{humanizedDataTime}}", @@ -460,6 +462,7 @@ "ai:Installing {{operatorsCountString}}": "Installing {{operatorsCountString}}", "ai:Installing SNO will result in an OpenShift deployment that is not highly available.": "Installing SNO will result in an OpenShift deployment that is not highly available.", "ai:Insufficient": "Insufficient", + "ai:Integrate with external partner platforms": "Integrate with external partner platforms", "ai:IP address block from which Pod IPs are allocated This block must not overlap with existing physical networks. These IP addresses are used for the Pod network, and if you need to access the Pods from an external network, configure load balancers and routers to manage the traffic.": "IP address block from which Pod IPs are allocated. This block must not overlap with existing physical networks. These IP addresses are used for the Pod network, and if you need to access the Pods from an external network, configure load balancers and routers to manage the traffic.", "ai:IP allocation from the DHCP server timed out.": "IP allocation from the DHCP server timed out.", "ai:IPv4 address": "IPv4 address", @@ -584,6 +587,7 @@ "ai:No OpenShift images available for selected CPU architecture {{cpuArchitecture}}.": "No OpenShift images available for selected CPU architecture {{cpuArchitecture}}.", "ai:No options available": "No options available", "ai:No overlapping CIDR": "No overlapping CIDR", + "ai:No platform integration": "No platform integration", "ai:No proxy": "No proxy", "ai:No proxy domains": "No proxy domains", "ai:No release image is available.": "No release image is available.", @@ -617,6 +621,7 @@ "ai:Number of characters between dots (.) must be 1-63": "Number of characters between dots (.) must be 1-63", "ai:Number of control plane nodes": "Number of control plane nodes", "ai:Number of hosts": "Number of hosts", + "ai:Nutanix": "Nutanix", "ai:NVIDIA GPU requirements": "NVIDIA GPU requirements", "ai:OADP requirements": "OADP requirements", "ai:OCS requirements": "OCS requirements", @@ -911,6 +916,7 @@ "ai:View host events": "View host events", "ai:VIP IP allocation from DHCP server has been timed out": "VIP IP allocation from DHCP server has timed out", "ai:Virtual machine": "Virtual machine", + "ai:vSphere": "vSphere", "ai:Vsphere disk uuid enabled": "Vsphere disk uuid enabled", "ai:Waiting for host...": "Waiting for host...", "ai:Waiting for host..._plural": "Waiting for hosts...", diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentReviewStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentReviewStep.tsx index a9a40db220..ec4ba3ad06 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentReviewStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentReviewStep.tsx @@ -24,6 +24,7 @@ import { ClusterValidations, HostsValidations, useAlerts, + getPlatforms, } from '../../../common'; import { getSelectedVersion, getAICluster, getClusterDeploymentCpuArchitecture } from '../helpers'; import { isAgentOfCluster } from './helpers'; @@ -36,6 +37,7 @@ import { import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; import { ClusterDeploymentWizardContext } from './ClusterDeploymentWizardContext'; import { ValidationSection } from './components/ValidationSection'; +import { PlatformType } from '@openshift-assisted/types/assisted-installer-service'; type ClusterDeploymentReviewStepProps = { clusterDeployment: ClusterDeploymentK8sResource; @@ -97,6 +99,10 @@ const ClusterDeploymentReviewStep = ({ useWizardFooter(footer); const openShiftVersion = getSelectedVersion(clusterImages, agentClusterInstall); + const platform = + getPlatforms(t)[ + (agentClusterInstall?.spec?.platformType?.toLowerCase() || 'baremetal') as PlatformType + ]; const cluster = React.useMemo( () => getAICluster({ clusterDeployment, agentClusterInstall, agents: clusterAgents }), @@ -127,6 +133,11 @@ const ClusterDeploymentReviewStep = ({ value={cpuArchitecture} testId="cpu-architecture" /> + + + {/** TODO: Add the custom manifest step here */} + = const { values, setFieldValue } = useFormikContext(); const { name, baseDnsDomain } = values; + console.log('values', values); + React.useEffect(() => { if (!versions.length && !values.openshiftVersion) { const fallbackOpenShiftVersion = allVersions.find((version) => version.default); @@ -167,6 +173,11 @@ export const ClusterDetailsFormFields: React.FC = {!isNutanix && ( )} + + + + {/** TODO: custom manifests checkbox here */} + {extensionAfter?.['openshiftVersion'] && extensionAfter['openshiftVersion']} {!isEditFlow && } {extensionAfter?.['pullSecret'] && extensionAfter['pullSecret']} diff --git a/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts b/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts index 7d0db15e2f..b7ab76e300 100644 --- a/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts +++ b/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts @@ -1,6 +1,11 @@ import cloneDeep from 'lodash-es/cloneDeep.js'; import { AgentK8sResource } from '../../types/k8s/agent'; -import { Cluster, Host, Inventory } from '@openshift-assisted/types/assisted-installer-service'; +import { + Cluster, + Host, + Inventory, + PlatformType, +} from '@openshift-assisted/types/assisted-installer-service'; import { ClusterDeploymentK8sResource } from '../../types/k8s/cluster-deployment'; import { AgentClusterInstallK8sResource } from '../../types/k8s/agent-cluster-install'; import { getAgentStatusKey, getClusterStatus } from './status'; @@ -174,6 +179,9 @@ export const getAICluster = ({ cpuArchitecture: getClusterDeploymentCpuArchitecture(clusterDeployment, infraEnv), networkType: agentClusterInstall?.spec?.networking.networkType, controlPlaneCount: agentClusterInstall?.spec?.provisionRequirements.controlPlaneAgents || 3, + platform: { + type: (agentClusterInstall?.spec?.platformType?.toLowerCase() || 'none') as PlatformType, + }, }; /* aiCluster.agentSelectorMasterLabels = diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx new file mode 100644 index 0000000000..5ca5a86e76 --- /dev/null +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx @@ -0,0 +1,74 @@ +import * as React from 'react'; +import { StaticField } from '../ui'; +import { useTranslation } from '../../hooks'; +import { TFunction } from 'i18next'; +import { PlatformType } from '@openshift-assisted/types/assisted-installer-service'; +import { Dropdown, DropdownItem, FormGroup, MenuToggle } from '@patternfly/react-core'; +import { useField } from 'formik'; + +export const getPlatforms = (t: TFunction): { [key in PlatformType]: string } => ({ + none: t('ai:No platform integration'), + baremetal: t('ai:No platform integration'), + nutanix: t('ai:Nutanix'), + vsphere: t('ai:vSphere'), + external: t('ai:External cloud provider'), +}); + +export const ExternalPlatformsDropdown = ({ isDisabled }: { isDisabled: boolean }) => { + const [isOpen, setIsOpen] = React.useState(false); + const [{ value }, , { setValue }] = useField('platform'); + const { t } = useTranslation(); + + const platforms = getPlatforms(t); + + const options = Object.entries(platforms) + .filter(([key]) => key !== 'baremetal') + .map(([platform, label]) => ( + e.preventDefault()} + > + {label} + + )); + + const onSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined, + ) => { + setValue(value as PlatformType); + setIsOpen(false); + }; + + return isDisabled ? ( + + {platforms[value]} + + ) : ( + + ( + setIsOpen(!isOpen)}> + {value ? platforms[value] : t('ai:Integrate with external partner platforms')} + + )} + onSelect={onSelect} + > + {options} + + + ); +}; diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts b/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts index 80f55b808b..91962f2f1e 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts @@ -9,3 +9,4 @@ export { default as SecurityFields } from './SecurityFields'; export * from './utils'; export * from './DiscoveryTroubleshootingModal'; export * from './DiscoveryImageConfigForm'; +export * from './ExternalPlatformsDropdown'; From 04f0b7cb580afbdf9bfd68b0ca13f784895c679a Mon Sep 17 00:00:00 2001 From: jgyselov Date: Wed, 6 Aug 2025 12:10:51 +0200 Subject: [PATCH 2/7] Restructure files related to CIM cluster deployment wizard --- .../lib/cim/components/Agent/AgentStatus.tsx | 2 +- .../ClusterDeploymentWizard.tsx | 10 +++---- .../ClusterDeployment/ScaleUpForm.tsx | 2 +- .../ACMClusterDeploymentDetailsStep.tsx | 14 ++++------ .../ClusterDeploymentDetailsForm.tsx | 26 ++++++++++------- .../ClusterDeploymentDetailsStep.tsx | 19 ++++++------- .../ClusterDetailsFormFields.tsx | 22 +++++++-------- .../clusterDetails/index.tsx | 4 +++ .../AdditionalNTPSourcesDialogToggle.tsx | 2 +- .../ClusterDeployment/components/index.tsx | 1 + .../ClusterDeploymentHostDiscoveryTable.tsx | 26 ++++++++--------- .../ClusterDeploymentHostsDiscovery.tsx | 16 +++++------ .../ClusterDeploymentHostsDiscoveryStep.tsx | 19 ++++++------- .../ClusterDeployment/hostDiscovery/index.tsx | 1 + .../ClusterDeploymentHostSelectionStep.tsx | 27 ++++++++---------- .../ClusterDeploymentHostsSelection.tsx | 14 +++++----- ...lusterDeploymentHostsSelectionAdvanced.tsx | 22 +++++++-------- .../ClusterDeploymentHostsSelectionBasic.tsx | 18 ++++++------ .../ClusterDeployment/hostSelection/index.tsx | 2 ++ .../cim/components/ClusterDeployment/index.ts | 4 +-- .../ClusterDeploymentHostsNetworkTable.tsx | 24 ++++++++-------- .../ClusterDeploymentNetworkingForm.tsx | 20 ++++++------- .../ClusterDeploymentNetworkingStep.tsx | 22 +++++++-------- .../ClusterDeployment/networking/index.tsx | 1 + .../ClusterDeploymentReviewStep.tsx | 28 ++++++++++--------- .../ClusterDeployment/review/index.tsx | 1 + .../use-networking-formik.ts | 4 +-- .../DetailsStep/DetailsForm.tsx | 2 +- 28 files changed, 177 insertions(+), 176 deletions(-) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => clusterDetails}/ACMClusterDeploymentDetailsStep.tsx (84%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => clusterDetails}/ClusterDeploymentDetailsForm.tsx (85%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => clusterDetails}/ClusterDeploymentDetailsStep.tsx (91%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => clusterDetails}/ClusterDetailsFormFields.tsx (90%) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/index.tsx rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => components}/AdditionalNTPSourcesDialogToggle.tsx (83%) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/components/index.tsx rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => hostDiscovery}/ClusterDeploymentHostDiscoveryTable.tsx (86%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => hostDiscovery}/ClusterDeploymentHostsDiscovery.tsx (91%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => hostDiscovery}/ClusterDeploymentHostsDiscoveryStep.tsx (88%) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/index.tsx rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => hostSelection}/ClusterDeploymentHostSelectionStep.tsx (94%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => hostSelection}/ClusterDeploymentHostsSelection.tsx (89%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => hostSelection}/ClusterDeploymentHostsSelectionAdvanced.tsx (84%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => hostSelection}/ClusterDeploymentHostsSelectionBasic.tsx (74%) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/index.tsx rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => networking}/ClusterDeploymentHostsNetworkTable.tsx (84%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => networking}/ClusterDeploymentNetworkingForm.tsx (89%) rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => networking}/ClusterDeploymentNetworkingStep.tsx (92%) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/networking/index.tsx rename libs/ui-lib/lib/cim/components/ClusterDeployment/{ => review}/ClusterDeploymentReviewStep.tsx (90%) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/review/index.tsx diff --git a/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx b/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx index 99d67b9168..6014245989 100644 --- a/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx +++ b/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx @@ -7,7 +7,7 @@ import { getAIHosts } from '../helpers/toAssisted'; import { getAgentStatus, getWizardStepAgentStatus } from '../helpers/status'; import '@patternfly/react-styles/css/utilities/Text/text.css'; -import { AdditionalNTPSourcesDialogToggle } from '../ClusterDeployment/AdditionalNTPSourcesDialogToggle'; +import { AdditionalNTPSourcesDialogToggle } from '../ClusterDeployment/components'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; import ValidationsRunningAlert from '../common/ValidationsRunningAlert'; import { agentStatus } from '../helpers/agentStatus'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx index 843327f33c..25dc5a66d6 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx @@ -2,17 +2,17 @@ import * as React from 'react'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; import { Grid, GridItem, Wizard, WizardStep } from '@patternfly/react-core'; import { AlertsContextProvider, LoadingState } from '../../../common'; -import ClusterDeploymentDetailsStep from './ClusterDeploymentDetailsStep'; -import ClusterDeploymentNetworkingStep from './ClusterDeploymentNetworkingStep'; -import ClusterDeploymentHostSelectionStep from './ClusterDeploymentHostSelectionStep'; import { ClusterDeploymentWizardProps } from './types'; -import ClusterDeploymentHostsDiscoveryStep from './ClusterDeploymentHostsDiscoveryStep'; import { ACMFeatureSupportLevelProvider } from '../featureSupportLevels'; -import ClusterDeploymentReviewStep from './ClusterDeploymentReviewStep'; import { YamlPreview, useYamlPreview } from '../YamlPreview'; import { wizardStepNames } from './constants'; import { ClusterDeploymentWizardContextProvider } from './ClusterDeploymentWizardContext'; import { isCIMFlow } from './helpers'; +import { ClusterDeploymentDetailsStep } from './clusterDetails'; +import { ClusterDeploymentHostSelectionStep } from './hostSelection'; +import { ClusterDeploymentHostsDiscoveryStep } from './hostDiscovery'; +import { ClusterDeploymentNetworkingStep } from './networking'; +import { ClusterDeploymentReviewStep } from './review'; export const ClusterDeploymentWizard = ({ className, diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ScaleUpForm.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/ScaleUpForm.tsx index 0dd120bb77..f5a7dbf424 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ScaleUpForm.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/ScaleUpForm.tsx @@ -3,7 +3,7 @@ import { useFormikContext } from 'formik'; import { Flex, FlexItem, Form, FormGroup } from '@patternfly/react-core'; import { getAgentsForSelection } from '../helpers/agents'; import { AgentK8sResource } from '../../types/k8s/agent'; -import ClusterDeploymentHostsSelectionAdvanced from './ClusterDeploymentHostsSelectionAdvanced'; +import { ClusterDeploymentHostsSelectionAdvanced } from './hostSelection'; import { ScaleUpFormValues } from './types'; import SwitchField from '../../../common/components/ui/formik/SwitchField'; import ClusterScaleUpAutoHostsSelection from './ClusterScaleUpAutoHostsSelection'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ACMClusterDeploymentDetailsStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ACMClusterDeploymentDetailsStep.tsx similarity index 84% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ACMClusterDeploymentDetailsStep.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ACMClusterDeploymentDetailsStep.tsx index 06c4b6fe08..ed79015910 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ACMClusterDeploymentDetailsStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ACMClusterDeploymentDetailsStep.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { Formik, FormikProps, useFormikContext } from 'formik'; import { Stack } from '@patternfly/react-core'; import noop from 'lodash-es/noop.js'; -import { ClusterDetailsValues, getRichTextValidation } from '../../../common'; -import { ClusterImageSetK8sResource } from '../../types/k8s/cluster-image-set'; -import ClusterDeploymentDetailsForm from './ClusterDeploymentDetailsForm'; +import { ClusterDetailsValues, getRichTextValidation } from '../../../../common'; +import { ClusterImageSetK8sResource } from '../../../types/k8s/cluster-image-set'; import { useDetailsFormik } from './ClusterDeploymentDetailsStep'; import { ClusterDetailsFormFieldsProps } from './ClusterDetailsFormFields'; -import { OsImage } from '../../types'; -import { getOCPVersions } from '../helpers'; +import { OsImage } from '../../../types'; +import { getOCPVersions } from '../../helpers'; +import { ClusterDeploymentDetailsForm } from './ClusterDeploymentDetailsForm'; type DetailsFormBodyProps = { clusterImages: ClusterImageSetK8sResource[]; @@ -49,7 +49,7 @@ type ACMClusterDeploymentDetailsStepProps = DetailsFormBodyProps & { formRef: React.Ref>; }; -const ACMClusterDeploymentDetailsStep: React.FC = ({ +export const ACMClusterDeploymentDetailsStep: React.FC = ({ clusterImages, formRef, usedClusterNames, @@ -80,5 +80,3 @@ const ACMClusterDeploymentDetailsStep: React.FC ); }; - -export default ACMClusterDeploymentDetailsStep; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDeploymentDetailsForm.tsx similarity index 85% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDeploymentDetailsForm.tsx index 560204235c..a373c18ab3 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDeploymentDetailsForm.tsx @@ -8,18 +8,26 @@ import { useWizardFooter, WizardFooter, } from '@patternfly/react-core'; -import { AgentClusterInstallK8sResource, ClusterDeploymentK8sResource, OsImage } from '../../types'; -import { ClusterImageSetK8sResource } from '../../types/k8s/cluster-image-set'; -import { getOCPVersions, getSelectedVersion } from '../helpers'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +import { + AgentClusterInstallK8sResource, + ClusterDeploymentK8sResource, + OsImage, +} from '../../../types'; +import { ClusterImageSetK8sResource } from '../../../types/k8s/cluster-image-set'; +import { getOCPVersions, getSelectedVersion } from '../../helpers'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { ClusterDetailsFormFields, ClusterDetailsFormFieldsProps, } from './ClusterDetailsFormFields'; import { useFormikContext } from 'formik'; -import { ClusterDetailsValues, CpuArchitecture, SupportedCpuArchitecture } from '../../../common'; -import { ClusterDeploymentWizardContext } from './ClusterDeploymentWizardContext'; -import { ValidationSection } from './components/ValidationSection'; +import { + ClusterDetailsValues, + CpuArchitecture, + SupportedCpuArchitecture, +} from '../../../../common'; +import { ClusterDeploymentWizardContext } from '../ClusterDeploymentWizardContext'; +import { ValidationSection } from '../components/ValidationSection'; import { toNumber } from 'lodash-es'; type ClusterDeploymentDetailsFormProps = { @@ -84,7 +92,7 @@ export const ClusterDeploymentDetailsFormWrapper = ({ ); }; -const ClusterDeploymentDetailsForm: React.FC = ({ +export const ClusterDeploymentDetailsForm: React.FC = ({ agentClusterInstall, clusterDeployment, clusterImages, @@ -150,5 +158,3 @@ const ClusterDeploymentDetailsForm: React.FC ); }; - -export default ClusterDeploymentDetailsForm; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDeploymentDetailsStep.tsx similarity index 91% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsStep.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDeploymentDetailsStep.tsx index 4256fce89c..764609a4f6 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDeploymentDetailsStep.tsx @@ -11,21 +11,22 @@ import { ClusterDetailsValues, getRichTextValidation, OpenshiftVersionOptionType, -} from '../../../common'; +} from '../../../../common'; -import { ClusterDeploymentDetailsStepProps, ClusterDeploymentDetailsValues } from './types'; -import { getAICluster, getNetworkType, getOCPVersions } from '../helpers'; +import { ClusterDeploymentDetailsStepProps, ClusterDeploymentDetailsValues } from '../types'; +import { getAICluster, getNetworkType, getOCPVersions } from '../../helpers'; import { AgentClusterInstallK8sResource, AgentK8sResource, ClusterDeploymentK8sResource, InfraEnvK8sResource, -} from '../../types'; -import ClusterDeploymentDetailsForm, { +} from '../../../types'; +import { + ClusterDeploymentDetailsForm, ClusterDeploymentDetailsFormWrapper, } from './ClusterDeploymentDetailsForm'; -import { getGridSpans } from './helpers'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +import { getGridSpans } from '../helpers'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; type UseDetailsFormikArgs = { usedClusterNames: string[]; @@ -96,7 +97,7 @@ export const useDetailsFormik = ({ return [initialValues, validationSchema]; }; -const ClusterDeploymentDetailsStep: React.FC = ({ +export const ClusterDeploymentDetailsStep: React.FC = ({ clusterImages, clusterDeployment, agentClusterInstall, @@ -159,5 +160,3 @@ const ClusterDeploymentDetailsStep: React.FC ); }; - -export default ClusterDeploymentDetailsStep; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDetailsFormFields.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx similarity index 90% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDetailsFormFields.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx index 688b97d729..2f09175581 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDetailsFormFields.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx @@ -6,20 +6,20 @@ import { ExternalPlatformsDropdown, OpenShiftVersionDropdown, OpenShiftVersionModal, -} from '../../../common'; -import { StaticTextField } from '../../../common/components/ui/StaticTextField'; -import { PullSecret } from '../../../common/components/clusters'; -import { OpenshiftVersionOptionType, SupportedCpuArchitecture } from '../../../common/types'; +} from '../../../../common'; +import { StaticTextField } from '../../../../common/components/ui/StaticTextField'; +import { PullSecret } from '../../../../common/components/clusters'; +import { OpenshiftVersionOptionType, SupportedCpuArchitecture } from '../../../../common/types'; import { InputField, RichInputField, acmClusterNameValidationMessages, -} from '../../../common/components/ui/formik'; -import { ClusterDetailsValues } from '../../../common/components/clusterWizard/types'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import CpuArchitectureDropdown from '../common/CpuArchitectureDropdown'; -import ControlPlaneNodesDropdown from '../../../common/components/clusterConfiguration/ControlPlaneNodesDropdown'; -import { getNetworkType } from '../helpers'; +} from '../../../../common/components/ui/formik'; +import { ClusterDetailsValues } from '../../../../common/components/clusterWizard/types'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; +import CpuArchitectureDropdown from '../../common/CpuArchitectureDropdown'; +import ControlPlaneNodesDropdown from '../../../../common/components/clusterConfiguration/ControlPlaneNodesDropdown'; +import { getNetworkType } from '../../helpers'; export type ClusterDetailsFormFieldsProps = { isEditFlow: boolean; @@ -66,8 +66,6 @@ export const ClusterDetailsFormFields: React.FC = const { values, setFieldValue } = useFormikContext(); const { name, baseDnsDomain } = values; - console.log('values', values); - React.useEffect(() => { if (!versions.length && !values.openshiftVersion) { const fallbackOpenShiftVersion = allVersions.find((version) => version.default); diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/index.tsx new file mode 100644 index 0000000000..b39e61f90d --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/index.tsx @@ -0,0 +1,4 @@ +export * from './ClusterDeploymentDetailsStep'; +export * from './ClusterDeploymentDetailsForm'; +export * from './ACMClusterDeploymentDetailsStep'; +export * from './ClusterDetailsFormFields'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/AdditionalNTPSourcesDialogToggle.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/components/AdditionalNTPSourcesDialogToggle.tsx similarity index 83% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/AdditionalNTPSourcesDialogToggle.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/components/AdditionalNTPSourcesDialogToggle.tsx index df5e1e599c..431452cd65 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/AdditionalNTPSourcesDialogToggle.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/components/AdditionalNTPSourcesDialogToggle.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; // TODO we should use hostValidationFailureHints instead of passing this 'action' which is actually a hint export const AdditionalNTPSourcesDialogToggle: React.FC = () => { diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/components/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/components/index.tsx new file mode 100644 index 0000000000..4fae79e7bc --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/components/index.tsx @@ -0,0 +1 @@ +export * from './AdditionalNTPSourcesDialogToggle'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostDiscoveryTable.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx similarity index 86% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostDiscoveryTable.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx index d0f299e03a..cb48784aa6 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostDiscoveryTable.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx @@ -6,11 +6,11 @@ import { agentStatusColumn, useAgentsTable, canChangeHostname, -} from '../Agent/tableUtils'; +} from '../../Agent/tableUtils'; import HostsTable, { DefaultExpandComponent, HostsTableEmptyState, -} from '../../../common/components/hosts/HostsTable'; +} from '../../../../common/components/hosts/HostsTable'; import { cpuCoresColumn, discoveredAtColumn, @@ -18,22 +18,22 @@ import { hostnameColumn, memoryColumn, roleColumn, -} from '../../../common/components/hosts/tableUtils'; +} from '../../../../common/components/hosts/tableUtils'; import { DiscoveryTroubleshootingModal, ChangeHostnameAction, MassChangeHostnameModal, TableToolbar, -} from '../../../common'; -import { ClusterDeploymentHostDiscoveryTableProps } from '../ClusterDeployment/types'; -import MassApproveAgentModal from '../modals/MassApproveAgentModal'; -import MassApproveAction from '../modals/MassApproveAction'; -import { usePagination } from '../../../common/components/hosts/usePagination'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import { ExpandComponent } from '../Agent/AgentsSelectionTable'; -import { HostsTableDetailContextProvider } from '../../../common/components/hosts/HostsTableDetailContext'; -import { agentStatus, bmhStatus } from '../helpers/agentStatus'; -import { onAgentChangeHostname } from '../helpers'; +} from '../../../../common'; +import { ClusterDeploymentHostDiscoveryTableProps } from '../types'; +import MassApproveAgentModal from '../../modals/MassApproveAgentModal'; +import MassApproveAction from '../../modals/MassApproveAction'; +import { usePagination } from '../../../../common/components/hosts/usePagination'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; +import { ExpandComponent } from '../../Agent/AgentsSelectionTable'; +import { HostsTableDetailContextProvider } from '../../../../common/components/hosts/HostsTableDetailContext'; +import { agentStatus, bmhStatus } from '../../helpers/agentStatus'; +import { onAgentChangeHostname } from '../../helpers'; const ClusterDeploymentHostDiscoveryTable: React.FC = ({ agents, diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsDiscovery.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscovery.tsx similarity index 91% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsDiscovery.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscovery.tsx index e1557fade4..0deaef47c8 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsDiscovery.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscovery.tsx @@ -14,15 +14,15 @@ import { FormatDiskWarning, HostsNotShowingLink, VMRebootConfigurationInfo, -} from '../../../common'; -import { getIsSNOCluster, onAgentChangeHostname } from '../helpers'; -import MinimalHWRequirements from '../Agent/MinimalHWRequirements'; -import { ClusterDeploymentHostsDiscoveryProps } from './types'; -import { EditBMHModal, EditAgentModal } from '../modals'; -import { AgentK8sResource, BareMetalHostK8sResource } from '../../types'; +} from '../../../../common'; +import { getIsSNOCluster, onAgentChangeHostname } from '../../helpers'; +import MinimalHWRequirements from '../../Agent/MinimalHWRequirements'; +import { ClusterDeploymentHostsDiscoveryProps } from '../types'; +import { EditBMHModal, EditAgentModal } from '../../modals'; +import { AgentK8sResource, BareMetalHostK8sResource } from '../../../types'; import ClusterDeploymentHostDiscoveryTable from './ClusterDeploymentHostDiscoveryTable'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import AddHostDropdown from '../InfraEnv/AddHostDropdown'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; +import AddHostDropdown from '../../InfraEnv/AddHostDropdown'; const DiscoveryInstructions = () => { const { t } = useTranslation(); diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsDiscoveryStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscoveryStep.tsx similarity index 88% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsDiscoveryStep.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscoveryStep.tsx index e38f051e82..1e8c046b5d 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsDiscoveryStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscoveryStep.tsx @@ -12,17 +12,17 @@ import { useWizardContext, } from '@patternfly/react-core'; -import { Alerts, ClusterWizardStepHeader } from '../../../common'; -import { ClusterDeploymentHostsDiscoveryStepProps } from './types'; +import { Alerts, ClusterWizardStepHeader } from '../../../../common'; +import { ClusterDeploymentHostsDiscoveryStepProps } from '../types'; import ClusterDeploymentHostsDiscovery from './ClusterDeploymentHostsDiscovery'; -import { getAgentsHostsNames, isAgentOfInfraEnv } from './helpers'; -import { getIsSNOCluster, getWizardStepAgentStatus } from '../helpers'; -import { canNextFromHostDiscoveryStep } from './wizardTransition'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import { ValidationSection } from './components/ValidationSection'; -import { ClusterDeploymentWizardContext } from './ClusterDeploymentWizardContext'; +import { getAgentsHostsNames, isAgentOfInfraEnv } from '../helpers'; +import { getIsSNOCluster, getWizardStepAgentStatus } from '../../helpers'; +import { canNextFromHostDiscoveryStep } from '../wizardTransition'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; +import { ValidationSection } from '../components/ValidationSection'; +import { ClusterDeploymentWizardContext } from '../ClusterDeploymentWizardContext'; -const ClusterDeploymentHostsDiscoveryStep = ({ +export const ClusterDeploymentHostsDiscoveryStep = ({ agentClusterInstall, agents: allAgents, infraEnv, @@ -183,4 +183,3 @@ const ClusterDeploymentHostsDiscoveryStep = ({ ); }; -export default ClusterDeploymentHostsDiscoveryStep; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/index.tsx new file mode 100644 index 0000000000..2ccae951e6 --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/index.tsx @@ -0,0 +1 @@ +export * from './ClusterDeploymentHostsDiscoveryStep'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostSelectionStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx similarity index 94% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostSelectionStep.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx index 1f7158e504..602740faf8 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostSelectionStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx @@ -10,28 +10,28 @@ import { useWizardFooter, WizardFooter, } from '@patternfly/react-core'; -import { Alerts, ClusterWizardStepHeader, useAlerts } from '../../../common'; +import { Alerts, ClusterWizardStepHeader, useAlerts } from '../../../../common'; import { AgentClusterInstallK8sResource, AgentK8sResource, ClusterDeploymentK8sResource, -} from '../../types'; +} from '../../../types'; import ClusterDeploymentHostsSelection from './ClusterDeploymentHostsSelection'; import { ClusterDeploymentHostSelectionStepProps, ClusterDeploymentHostsSelectionValues, -} from './types'; -import { hostCountValidationSchema } from './validationSchemas'; +} from '../types'; +import { hostCountValidationSchema } from '../validationSchemas'; import { getAgentSelectorFieldsFromAnnotations, getIsSNOCluster, getWizardStepAgentStatus, -} from '../helpers'; -import { canNextFromHostSelectionStep } from './wizardTransition'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +} from '../../helpers'; +import { canNextFromHostSelectionStep } from '../wizardTransition'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { TFunction } from 'i18next'; -import { ValidationSection } from './components/ValidationSection'; -import { ClusterDeploymentWizardContext } from './ClusterDeploymentWizardContext'; +import { ValidationSection } from '../components/ValidationSection'; +import { ClusterDeploymentWizardContext } from '../ClusterDeploymentWizardContext'; const getInitialValues = ({ agents, @@ -334,10 +334,9 @@ const HostSelectionForm: React.FC = ({ ); }; -const ClusterDeploymentHostSelectionStep: React.FC = ({ - onSaveHostsSelection, - ...rest -}) => { +export const ClusterDeploymentHostSelectionStep: React.FC< + ClusterDeploymentHostSelectionStepProps +> = ({ onSaveHostsSelection, ...rest }) => { const { t } = useTranslation(); const { addAlert } = useAlerts(); @@ -377,5 +376,3 @@ const ClusterDeploymentHostSelectionStep: React.FC ); }; - -export default ClusterDeploymentHostSelectionStep; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelection.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelection.tsx similarity index 89% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelection.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelection.tsx index 4a405d0ccc..bcf1626fb9 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelection.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelection.tsx @@ -1,21 +1,21 @@ import React from 'react'; import { useFormikContext } from 'formik'; import { Grid, GridItem, TextContent, Form } from '@patternfly/react-core'; -import { SwitchField } from '../../../common'; +import { SwitchField } from '../../../../common'; import { ClusterDeploymentHostsSelectionProps, ClusterDeploymentHostsSelectionValues, -} from './types'; +} from '../types'; import ClusterDeploymentHostsSelectionBasic from './ClusterDeploymentHostsSelectionBasic'; -import ClusterDeploymentHostsSelectionAdvanced from './ClusterDeploymentHostsSelectionAdvanced'; +import { ClusterDeploymentHostsSelectionAdvanced } from './ClusterDeploymentHostsSelectionAdvanced'; import { getAgentsForSelection, getClusterDeploymentCpuArchitecture, getIsSNOCluster, -} from '../helpers'; -import MinimalHWRequirements from '../Agent/MinimalHWRequirements'; -import NoAgentsAlert from '../Agent/NoAgentsAlert'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +} from '../../helpers'; +import MinimalHWRequirements from '../../Agent/MinimalHWRequirements'; +import NoAgentsAlert from '../../Agent/NoAgentsAlert'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; const ClusterDeploymentHostsSelection: React.FC = ({ agentClusterInstall, diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelectionAdvanced.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelectionAdvanced.tsx similarity index 84% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelectionAdvanced.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelectionAdvanced.tsx index 0209e079f8..3fb97f8e71 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelectionAdvanced.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelectionAdvanced.tsx @@ -4,15 +4,15 @@ import React from 'react'; import Measure from 'react-measure'; import { Alert, AlertVariant, Grid, GridItem } from '@patternfly/react-core'; -import { AgentK8sResource } from '../../types'; -import { AGENT_LOCATION_LABEL_KEY, AGENT_NOLOCATION_VALUE } from '../common'; -import LocationsSelector from './LocationsSelector'; -import { ClusterDeploymentHostsSelectionValues, ScaleUpFormValues } from './types'; -import LabelsSelector, { infraEnvLabelKeys } from './LabelsSelector'; -import AgentsSelectionTable from '../Agent/AgentsSelectionTable'; -import { AgentTableActions } from './types'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import { CpuArchitecture } from '../../../common'; +import { AgentK8sResource } from '../../../types'; +import { AGENT_LOCATION_LABEL_KEY, AGENT_NOLOCATION_VALUE } from '../../common'; +import LocationsSelector from '../LocationsSelector'; +import { ClusterDeploymentHostsSelectionValues, ScaleUpFormValues } from '../types'; +import LabelsSelector, { infraEnvLabelKeys } from '../LabelsSelector'; +import AgentsSelectionTable from '../../Agent/AgentsSelectionTable'; +import { AgentTableActions } from '../types'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; +import { CpuArchitecture } from '../../../../common'; type ClusterDeploymentHostsSelectionAdvancedProps = { availableAgents: AgentK8sResource[]; @@ -25,7 +25,7 @@ type ClusterDeploymentHostsSelectionAdvancedProps = { type FormValues = ClusterDeploymentHostsSelectionValues | ScaleUpFormValues; -const ClusterDeploymentHostsSelectionAdvanced = ({ +export const ClusterDeploymentHostsSelectionAdvanced = ({ availableAgents, onEditRole, onSetInstallationDiskId, @@ -103,5 +103,3 @@ const ClusterDeploymentHostsSelectionAdvanced = ({ ); }; - -export default ClusterDeploymentHostsSelectionAdvanced; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelectionBasic.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelectionBasic.tsx similarity index 74% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelectionBasic.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelectionBasic.tsx index 6bafdcdde8..08c22d7f99 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsSelectionBasic.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelectionBasic.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Grid, GridItem } from '@patternfly/react-core'; -import { CheckboxField, NumberInputField } from '../../../common'; -import { HOSTS_MAX_COUNT, HOSTS_MIN_COUNT } from './constants'; +import { CheckboxField, NumberInputField } from '../../../../common'; +import { HOSTS_MAX_COUNT, HOSTS_MIN_COUNT } from '../constants'; import { useFormikContext } from 'formik'; -import { ClusterDeploymentHostsSelectionValues } from './types'; -import LocationsSelector from './LocationsSelector'; -import { AgentK8sResource } from '../../types'; -import AgentsSelectionHostCountAlerts from '../Agent/AgentsSelectionHostCountAlerts'; -import AgentsSelectionHostCountLabelIcon from '../Agent/AgentsSelectionHostCountLabelIcon'; -import { useAgentsAutoSelection } from '../Agent/AgentsSelectionUtils'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +import { ClusterDeploymentHostsSelectionValues } from '../types'; +import LocationsSelector from '../LocationsSelector'; +import { AgentK8sResource } from '../../../types'; +import AgentsSelectionHostCountAlerts from '../../Agent/AgentsSelectionHostCountAlerts'; +import AgentsSelectionHostCountLabelIcon from '../../Agent/AgentsSelectionHostCountLabelIcon'; +import { useAgentsAutoSelection } from '../../Agent/AgentsSelectionUtils'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; type ClusterDeploymentHostsSelectionBasicProps = { availableAgents: AgentK8sResource[]; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/index.tsx new file mode 100644 index 0000000000..be6ec8dfd6 --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/index.tsx @@ -0,0 +1,2 @@ +export * from './ClusterDeploymentHostSelectionStep'; +export * from './ClusterDeploymentHostsSelectionAdvanced'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/index.ts b/libs/ui-lib/lib/cim/components/ClusterDeployment/index.ts index 835ddc895d..ce12d846d4 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/index.ts +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/index.ts @@ -1,6 +1,6 @@ export * from './types'; export * from './helpers'; -export * from './AdditionalNTPSourcesDialogToggle'; +export * from './components'; export * from './LogsDownloadButton'; export * from './wizardTransition'; export * from './networkConfigurationValidation'; @@ -13,6 +13,6 @@ export { default as ClusterDeploymentCredentials } from './ClusterDeploymentCred export { default as ClusterDeploymentKubeconfigDownload } from './ClusterDeploymentKubeconfigDownload'; export { default as ClusterInstallationError } from './ClusterInstallationError'; export { default as NetworkConfiguration } from './NetworkConfiguration'; -export { default as ACMClusterDeploymentDetailsStep } from './ACMClusterDeploymentDetailsStep'; export { getTotalCompute } from './ShortCapacitySummary'; +export { ACMClusterDeploymentDetailsStep } from './clusterDetails'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsNetworkTable.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentHostsNetworkTable.tsx similarity index 84% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsNetworkTable.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentHostsNetworkTable.tsx index 7d34ed7633..1dca72d91d 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentHostsNetworkTable.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentHostsNetworkTable.tsx @@ -1,26 +1,26 @@ import * as React from 'react'; import { ConnectedIcon } from '@patternfly/react-icons/dist/js/icons/connected-icon'; -import { EmptyState } from '../../../common'; -import { AgentTableActions } from './types'; +import { EmptyState } from '../../../../common'; +import { AgentTableActions } from '../types'; import { AgentClusterInstallK8sResource, AgentK8sResource, ClusterDeploymentK8sResource, -} from '../../types'; -import { getAICluster, getIsSNOCluster } from '../helpers'; -import { AdditionalNTPSourcesDialogToggle } from './AdditionalNTPSourcesDialogToggle'; -import { agentStatusColumn, useAgentsTable } from '../Agent/tableUtils'; -import HostsTable from '../../../common/components/hosts/HostsTable'; -import { HostDetail } from '../../../common/components/hosts/HostRowDetail'; +} from '../../../types'; +import { getAICluster, getIsSNOCluster } from '../../helpers'; +import { AdditionalNTPSourcesDialogToggle } from '../components/AdditionalNTPSourcesDialogToggle'; +import { agentStatusColumn, useAgentsTable } from '../../Agent/tableUtils'; +import HostsTable from '../../../../common/components/hosts/HostsTable'; +import { HostDetail } from '../../../../common/components/hosts/HostRowDetail'; import { activeNICColumn, hostnameColumn, roleColumn, -} from '../../../common/components/hosts/tableUtils'; -import { usePagination } from '../../../common/components/hosts/usePagination'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +} from '../../../../common/components/hosts/tableUtils'; +import { usePagination } from '../../../../common/components/hosts/usePagination'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { Host } from '@openshift-assisted/types/assisted-installer-service'; -import { agentStatus } from '../helpers/agentStatus'; +import { agentStatus } from '../../helpers/agentStatus'; type ExpandComponentContextType = { onSetInstallationDiskId?: ClusterDeploymentHostsNetworkTableProps['onSetInstallationDiskId']; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentNetworkingForm.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingForm.tsx similarity index 89% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentNetworkingForm.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingForm.tsx index 19bc09accd..79a443281b 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentNetworkingForm.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingForm.tsx @@ -20,25 +20,21 @@ import { SecurityFields, ProxyFields, ProxyInputFields, -} from '../../../common'; -import NetworkConfiguration from './NetworkConfiguration'; +} from '../../../../common'; +import NetworkConfiguration from '../NetworkConfiguration'; import ClusterDeploymentHostsNetworkTable from './ClusterDeploymentHostsNetworkTable'; -import { getAICluster, getIsSNOCluster } from '../helpers'; +import { getAICluster, getIsSNOCluster } from '../../helpers'; import { AgentClusterInstallK8sResource, AgentK8sResource, ClusterDeploymentK8sResource, InfraEnvK8sResource, -} from '../../types'; -import { AgentTableActions, ClusterDeploymentNetworkingValues } from './types'; +} from '../../../types'; +import { AgentTableActions, ClusterDeploymentNetworkingValues } from '../types'; import { useFormikContext } from 'formik'; -import { getGridSpans } from './helpers'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +import { getGridSpans } from '../helpers'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { Trans } from 'react-i18next'; -import { ClusterDefaultConfig } from '@openshift-assisted/types/assisted-installer-service'; - -// TODO(mlibra): So far a constant. Should be queried from somewhere. -export const defaultNetworkSettings: ClusterDefaultConfig = CLUSTER_DEFAULT_NETWORK_SETTINGS_IPV4; type ClusterDeploymentNetworkingFormProps = { clusterDeployment: ClusterDeploymentK8sResource; @@ -140,7 +136,7 @@ const ClusterDeploymentNetworkingForm: React.FC diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentNetworkingStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingStep.tsx similarity index 92% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentNetworkingStep.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingStep.tsx index b5f3b0c5aa..3174b58889 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentNetworkingStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingStep.tsx @@ -17,21 +17,21 @@ import { getFormikErrorFields, clusterFieldLabels, Alerts, -} from '../../../common'; +} from '../../../../common'; import { ClusterDeploymentDetailsNetworkingProps, AgentTableActions, ClusterDeploymentNetworkingValues, -} from './types'; +} from '../types'; import ClusterDeploymentNetworkingForm from './ClusterDeploymentNetworkingForm'; -import { isAgentOfCluster } from './helpers'; -import { useInfraEnvProxies, useNetworkingFormik } from './use-networking-formik'; -import { canNextFromNetworkingStep } from './wizardTransition'; -import { AgentK8sResource } from '../../types'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import { ValidationSection } from './components/ValidationSection'; -import { ClusterDeploymentWizardContext } from './ClusterDeploymentWizardContext'; +import { isAgentOfCluster } from '../helpers'; +import { useInfraEnvProxies, useNetworkingFormik } from '../use-networking-formik'; +import { canNextFromNetworkingStep } from '../wizardTransition'; +import { AgentK8sResource } from '../../../types'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; +import { ValidationSection } from '../components/ValidationSection'; +import { ClusterDeploymentWizardContext } from '../ClusterDeploymentWizardContext'; type NetworkingFormProps = { clusterDeployment: ClusterDeploymentDetailsNetworkingProps['clusterDeployment']; @@ -212,7 +212,7 @@ export const NetworkingForm = ({ ); }; -const ClusterDeploymentNetworkingStep = ({ +export const ClusterDeploymentNetworkingStep = ({ clusterDeployment, agentClusterInstall, agents, @@ -261,5 +261,3 @@ const ClusterDeploymentNetworkingStep = ({ ); }; - -export default ClusterDeploymentNetworkingStep; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/index.tsx new file mode 100644 index 0000000000..9d51d6354b --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/index.tsx @@ -0,0 +1 @@ +export * from './ClusterDeploymentNetworkingStep'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentReviewStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx similarity index 90% rename from libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentReviewStep.tsx rename to libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx index ec4ba3ad06..463951ca35 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentReviewStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx @@ -8,14 +8,14 @@ import { WizardFooter, } from '@patternfly/react-core'; import * as React from 'react'; -import { canNextFromReviewStep } from './wizardTransition'; +import { canNextFromReviewStep } from '../wizardTransition'; import { AgentClusterInstallK8sResource, AgentK8sResource, ClusterDeploymentK8sResource, ClusterImageSetK8sResource, InfraEnvK8sResource, -} from '../../types'; +} from '../../../types'; import { ClusterWizardStepHeader, DetailList, @@ -25,18 +25,22 @@ import { HostsValidations, useAlerts, getPlatforms, -} from '../../../common'; -import { getSelectedVersion, getAICluster, getClusterDeploymentCpuArchitecture } from '../helpers'; -import { isAgentOfCluster } from './helpers'; -import { wizardStepNames } from './constants'; +} from '../../../../common'; +import { + getSelectedVersion, + getAICluster, + getClusterDeploymentCpuArchitecture, +} from '../../helpers'; +import { isAgentOfCluster } from '../helpers'; +import { wizardStepNames } from '../constants'; import { wizardStepsValidationsMap, ClusterWizardStepsType, allClusterWizardSoftValidationIds, -} from './wizardTransition'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import { ClusterDeploymentWizardContext } from './ClusterDeploymentWizardContext'; -import { ValidationSection } from './components/ValidationSection'; +} from '../wizardTransition'; +import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; +import { ClusterDeploymentWizardContext } from '../ClusterDeploymentWizardContext'; +import { ValidationSection } from '../components/ValidationSection'; import { PlatformType } from '@openshift-assisted/types/assisted-installer-service'; type ClusterDeploymentReviewStepProps = { @@ -49,7 +53,7 @@ type ClusterDeploymentReviewStepProps = { infraEnv?: InfraEnvK8sResource; }; -const ClusterDeploymentReviewStep = ({ +export const ClusterDeploymentReviewStep = ({ agentClusterInstall, agents, onFinish, @@ -193,5 +197,3 @@ const ClusterDeploymentReviewStep = ({ ); }; - -export default ClusterDeploymentReviewStep; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/review/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/index.tsx new file mode 100644 index 0000000000..95204bd295 --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/index.tsx @@ -0,0 +1 @@ +export * from './ClusterDeploymentReviewStep'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts b/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts index e3d778d337..834e5b6808 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts @@ -11,7 +11,6 @@ import { InfraEnvK8sResource, } from '../../types'; import { getAICluster } from '../helpers'; -import { defaultNetworkSettings } from './ClusterDeploymentNetworkingForm'; import { useDeepCompareMemoize } from '../../../common/hooks'; import { HostSubnets, @@ -22,6 +21,7 @@ import { hostSubnetValidationSchema, httpProxyValidationSchema, noProxyValidationSchema, + CLUSTER_DEFAULT_NETWORK_SETTINGS_IPV4, } from '../../../common'; import { ClusterDeploymentNetworkingValues } from './types'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; @@ -79,7 +79,7 @@ export const useNetworkingFormik = ({ agents, }); return { - ...getNetworkInitialValues(cluster, defaultNetworkSettings), + ...getNetworkInitialValues(cluster, CLUSTER_DEFAULT_NETWORK_SETTINGS_IPV4), enableProxy: false, editProxy: false, }; diff --git a/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.tsx b/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.tsx index b43a3ef27d..9a8460fb13 100644 --- a/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.tsx +++ b/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.tsx @@ -8,7 +8,7 @@ import { PullSecret, } from '../../../../../common'; import { useTranslation } from '../../../../../common/hooks/use-translation-wrapper'; -import { BaseDnsHelperText } from '../../../ClusterDeployment/ClusterDetailsFormFields'; +import { BaseDnsHelperText } from '../../../ClusterDeployment/clusterDetails/ClusterDetailsFormFields'; import { useTemptiflySync } from '../../hooks/useTemptiflySync'; import { DetailsFormProps, DetailsFormValues } from './types'; From 2203ac215ae2e76dc674ef4d977129570feb712b Mon Sep 17 00:00:00 2001 From: jgyselov Date: Wed, 20 Aug 2025 11:22:19 +0200 Subject: [PATCH 3/7] Create SelectFieldWithSearch component --- libs/locales/lib/en/translation.json | 1 + .../ClusterDetailsFormFields.tsx | 19 +- .../DetailsStep/DetailsForm.tsx | 17 +- .../HostedClusterWizard/DetailsStep/types.ts | 2 +- .../common/components/clusterWizard/types.ts | 3 +- .../ui/OpenShiftSelectWithSearch.tsx | 277 +++-------------- .../ui/OpenShiftVersionDropdown.tsx | 37 +-- .../components/ui/OpenShiftVersionModal.tsx | 28 +- .../ui/formik/SelectFieldWithSearch.tsx | 278 ++++++++++++++++++ .../lib/common/components/ui/formik/index.tsx | 1 + .../OcmOpenShiftVersionSelect.tsx | 31 +- .../OpenshiftVersionHelperText.tsx | 2 +- 12 files changed, 373 insertions(+), 323 deletions(-) create mode 100644 libs/ui-lib/lib/common/components/ui/formik/SelectFieldWithSearch.tsx diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index 37350d634f..68b765238f 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -732,6 +732,7 @@ "ai:Scale down the nodepool or make more hosts available.": "Scale down the nodepool or make more hosts available.", "ai:Secret and keys": "Secret and keys", "ai:Select all": "Select all", + "ai:Select an OpenShift version from the list or use the type ahead to narrow down the list.": "Select an OpenShift version from the list or use the type ahead to narrow down the list.", "ai:Select how you'd like to add hosts (Discovery ISO, iPXE, or BMC form) and follow the instructions that appear.": "Select the method of adding hosts (Discovery ISO, iPXE, or BMC form) and follow the instructions.", "ai:Select none": "Select none", "ai:Select one or more locations to view hosts": "Select one or more locations to view hosts", diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx index 2f09175581..348d19f75c 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx @@ -69,7 +69,7 @@ export const ClusterDetailsFormFields: React.FC = React.useEffect(() => { if (!versions.length && !values.openshiftVersion) { const fallbackOpenShiftVersion = allVersions.find((version) => version.default); - setFieldValue('customOpenshiftSelect', fallbackOpenShiftVersion); + setFieldValue('customOpenshiftSelect', fallbackOpenShiftVersion?.value); setFieldValue('openshiftVersion', fallbackOpenShiftVersion?.value); setFieldValue('networkType', getNetworkType(fallbackOpenShiftVersion)); } @@ -89,20 +89,15 @@ export const ClusterDetailsFormFields: React.FC = [versions], ); - const additionalSelectOptions = React.useMemo(() => { + const additionalSelectOption = React.useMemo(() => { if ( values.customOpenshiftSelect && - !selectOptions.some((option) => option.value === values.customOpenshiftSelect?.value) + !versions.some((version) => version.value === values.customOpenshiftSelect) ) { - return [ - { - value: values.customOpenshiftSelect.value, - label: values.customOpenshiftSelect.label, - }, - ]; + return allVersions.find((option) => option.value === values.customOpenshiftSelect); } - return []; - }, [selectOptions, values.customOpenshiftSelect]); + return undefined; + }, [allVersions, values.customOpenshiftSelect, versions]); const isDiskEncryptionEnabled = values.enableDiskEncryptionOnMasters || values.enableDiskEncryptionOnWorkers; @@ -154,7 +149,7 @@ export const ClusterDetailsFormFields: React.FC = versions={versions} showReleasesLink={false} showOpenshiftVersionModal={() => setOpenshiftVersionModalOpen(true)} - customItems={additionalSelectOptions} + customItem={additionalSelectOption} /> {openshiftVersionModalOpen && ( = ({ [ocpVersions], ); - const additionalSelectOptions = React.useMemo(() => { + const additionalSelectOption = React.useMemo(() => { if ( values.customOpenshiftSelect && - !selectOptions.some((option) => option.value === values.customOpenshiftSelect?.value) + !selectOptions.some((option) => option.value === values.customOpenshiftSelect) ) { - return [ - { - value: values.customOpenshiftSelect.value, - label: values.customOpenshiftSelect.label, - }, - ]; + return allVersions.find((version) => version.value === values.customOpenshiftSelect); } - return []; - }, [selectOptions, values.customOpenshiftSelect]); + return undefined; + }, [allVersions, selectOptions, values.customOpenshiftSelect]); return (
@@ -75,7 +70,7 @@ const DetailsForm: React.FC = ({ versions={ocpVersions} showReleasesLink={false} showOpenshiftVersionModal={() => setOpenshiftVersionModalOpen(true)} - customItems={additionalSelectOptions} + customItem={additionalSelectOption} /> {openshiftVersionModalOpen && ( >; }; export const OpenShiftSelectWithSearch: React.FunctionComponent = ({ versions, getHelperText, - setCustomOpenshiftSelect, }: OpenshiftSelectWithSearchProps) => { + const { t } = useTranslation(); + const [{ value }] = + useField('customOpenshiftSelect'); const initialSelectOptions = React.useMemo( () => versions.map((version) => ({ @@ -43,22 +31,11 @@ export const OpenShiftSelectWithSearch: React.FunctionComponent(''); - const [inputValue, setInputValue] = React.useState(''); const [filterValue, setFilterValue] = React.useState(''); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); - const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); - const textInputRef = React.useRef(); - const { t } = useTranslation(); - React.useEffect(() => { - if (textInputRef.current) { - textInputRef.current.maxLength = 30; - } - }, []); + const helperText = getHelperText && getHelperText(value, true); React.useEffect(() => { let newSelectOptions: SelectOptionProps[] = initialSelectOptions; @@ -74,221 +51,43 @@ export const OpenShiftSelectWithSearch: React.FunctionComponent { - setIsOpen(!isOpen); - }; - - const onSelect = ( - _event: React.MouseEvent | undefined, - value: string | number | undefined, - ) => { - // Check if the specific value exists in newSelectOptions - const foundItem = selectOptions.find((menuItem) => String(menuItem.value) === String(value)); - const newLabel = foundItem?.children; - if (value && value !== 'no results') { - setInputValue(newLabel as string); - setFilterValue(''); - setSelected(value as string); - const filteredVersions = versions.filter((version) => version.value === value); - setCustomOpenshiftSelect({ - label: - filteredVersions[0].supportLevel === 'beta' - ? filteredVersions[0].label + ' - ' + t('ai:Developer preview release') - : filteredVersions[0].label, - value: filteredVersions[0].value, - version: filteredVersions[0].version, - default: filteredVersions[0].default, - supportLevel: filteredVersions[0].supportLevel, - }); - } - setIsOpen(false); - setFocusedItemIndex(null); - setActiveItem(null); - }; - - const onTextInputChange = (_event: React.FormEvent, value: string) => { - setInputValue(value); - setFilterValue(value); - }; - - const handleMenuArrowKeys = (key: string) => { - let indexToFocus; - - if (isOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { - indexToFocus = selectOptions.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; - } - } - - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { - indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; - } - } - - setFocusedItemIndex(indexToFocus ?? 0); - const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus ?? 0]; - setActiveItem(`select-typeahead-${(focusedItem.value as string).replace(' ', '-')}`); } - }; - - const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; - switch (event.key) { - // Select the first available option - case 'Enter': - if (isOpen && focusedItem.value !== 'no results') { - setInputValue(String(focusedItem.children)); - setFilterValue(''); - setSelected(String(focusedItem.children)); - } + const selectOptionsWithDividers = newSelectOptions.map((option, index) => { + const match = (option.value as string).match(/\d+\.(\d+)\.\d+/); + const y = match ? match[1] : null; - setIsOpen((prevIsOpen) => !prevIsOpen); - setFocusedItemIndex(null); - setActiveItem(null); + const previousY = + index > 0 + ? ((newSelectOptions[index - 1].value as string).match(/\d+\.(\d+)\.\d+/) || [])[1] + : null; - break; - case 'Tab': - case 'Escape': - setIsOpen(false); - setActiveItem(null); - break; - case 'ArrowUp': - case 'ArrowDown': - event.preventDefault(); - handleMenuArrowKeys(event.key); - break; - } - }; + return { + ...option, + showDivider: previousY !== null && y !== previousY, + }; + }); - const toggle = (toggleRef: React.Ref) => ( - - - - - - {!!inputValue && ( - - )} - - - - ); - - const helperText = getHelperText && getHelperText(selected, true); + setSelectOptions(selectOptionsWithDividers); + }, [filterValue, initialSelectOptions, t]); return ( - <> - - - - - {helperText ?? - 'Select an OpenShift version from the list or use the type ahead to narrow down the list.'} - - - - + ); }; diff --git a/libs/ui-lib/lib/common/components/ui/OpenShiftVersionDropdown.tsx b/libs/ui-lib/lib/common/components/ui/OpenShiftVersionDropdown.tsx index 430ee1c48b..8b4ff540d1 100644 --- a/libs/ui-lib/lib/common/components/ui/OpenShiftVersionDropdown.tsx +++ b/libs/ui-lib/lib/common/components/ui/OpenShiftVersionDropdown.tsx @@ -21,7 +21,7 @@ import ExternalLink from './ExternalLink'; import { OCP_RELEASES_PAGE } from '../../config'; import { ClusterDetailsValues, ItemDropdown } from '../clusterWizard'; -export type HelperTextType = (value: string | undefined, inModal?: boolean) => JSX.Element | null; +export type HelperTextType = (value: string | null, inModal?: boolean) => JSX.Element | null; type OpenShiftVersionDropdownProps = { name: string; @@ -30,7 +30,7 @@ type OpenShiftVersionDropdownProps = { getHelperText?: HelperTextType; showReleasesLink: boolean; showOpenshiftVersionModal: () => void; - customItems: ItemDropdown; + customItem?: OpenshiftVersionOptionType; }; const getParsedVersions = (items: ItemDropdown) => { @@ -49,7 +49,7 @@ export const OpenShiftVersionDropdown = ({ getHelperText, showReleasesLink, showOpenshiftVersionModal, - customItems, + customItem, }: OpenShiftVersionDropdownProps) => { const [field, , { setValue }] = useField(name); const [isOpen, setOpen] = React.useState(false); @@ -61,9 +61,13 @@ export const OpenShiftVersionDropdown = ({ const [current, setCurrent] = React.useState(); React.useEffect(() => { - const defaultVersion = customOpenshiftSelect - ? customOpenshiftSelect - : versions.find((item) => item.default); + let defaultVersion = versions.find((item) => item.default); + if (customOpenshiftSelect && customItem) { + defaultVersion = customItem; + } else if (customOpenshiftSelect) { + defaultVersion = versions.find((item) => item.value === customOpenshiftSelect); + } + setCurrent(defaultVersion?.label || ''); setValue(defaultVersion?.value || ''); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -85,30 +89,17 @@ export const OpenShiftVersionDropdown = ({ return items; }); - const parsedVersionsForCustomItems = getParsedVersions(customItems); - let lastCustomY: string | undefined = ''; - const customDropdownItems = parsedVersionsForCustomItems.parsedVersions.map(({ y, versions }) => { - const customItems = versions.map(({ value, label }) => ( - - {label} - - )); - if (lastCustomY !== null && y !== lastCustomY) { - customItems.push(); - } - lastCustomY = y; - return customItems; - }); - const dropdownGroup = [ dropdownItems.length && ( {dropdownItems} ), - customDropdownItems.length && ( + customItem && ( - {customDropdownItems} + + {customItem.label} + ), diff --git a/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx b/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx index ab1246d4df..3e1f67f474 100644 --- a/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx +++ b/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Button, ButtonVariant, FormGroup, Modal, ModalVariant } from '@patternfly/react-core'; import { OpenShiftSelectWithSearch } from './OpenShiftSelectWithSearch'; import { HelperTextType } from './OpenShiftVersionDropdown'; @@ -18,14 +18,10 @@ export const OpenShiftVersionModal = ({ getHelperText, }: OpenShiftVersionModalProps) => { const { setFieldValue } = useFormikContext(); - const onClose = () => setOpenshiftVersionModalOpen(false); - const [customOpenshiftSelect, setCustomOpenshiftSelect] = useState(); // Cambiar el tipo según lo que esperes aquí - const handleSelect = () => { - if (customOpenshiftSelect) { - setFieldValue('customOpenshiftSelect', customOpenshiftSelect); - } - onClose(); + const onCancel = () => { + setFieldValue('customOpenshiftSelect', null); + setOpenshiftVersionModalOpen(false); }; return ( @@ -34,14 +30,18 @@ export const OpenShiftVersionModal = ({ id="available-openshift-versions-modal" isOpen actions={[ - , - , ]} - onClose={onClose} + onClose={onCancel} variant={ModalVariant.small} > - + ); diff --git a/libs/ui-lib/lib/common/components/ui/formik/SelectFieldWithSearch.tsx b/libs/ui-lib/lib/common/components/ui/formik/SelectFieldWithSearch.tsx new file mode 100644 index 0000000000..9cf6d5e7b4 --- /dev/null +++ b/libs/ui-lib/lib/common/components/ui/formik/SelectFieldWithSearch.tsx @@ -0,0 +1,278 @@ +import React from 'react'; +import { + Select, + SelectOption, + SelectList, + SelectOptionProps, + MenuToggle, + MenuToggleElement, + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, + Button, + FormGroup, + Bullseye, + Spinner, + FormHelperText, + HelperText, + HelperTextItem, + Divider, +} from '@patternfly/react-core'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; +import { useField } from 'formik'; +import { isEqual } from 'lodash-es'; +import { FieldProps } from './types'; + +const NO_RESULTS = 'no_results'; + +const createItemId = (val: string) => `select-multi-typeahead-${val.replace(' ', '-')}`; + +const SelectFieldWithSearch = ({ + name, + label, + isRequired, + selectOptions, + filterValue, + setFilterValue, + isMultiSelect = false, + isLoading = false, + placeholder, + helperText, +}: { + selectOptions: (SelectOptionProps & { showDivider?: boolean })[]; + filterValue: string; + setFilterValue: (value: string) => void; + isMultiSelect?: boolean; + isLoading?: boolean; + placeholder?: string; + helperText?: React.ReactNode; +} & FieldProps) => { + const [isOpen, setIsOpen] = React.useState(false); + const [{ value }, , { setValue }] = useField(name); + + const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); + const [activeItemId, setActiveItemId] = React.useState(null); + const textInputRef = React.useRef(); + + const setActiveAndFocusedItem = (itemIndex: number) => { + setFocusedItemIndex(itemIndex); + const focusedItem = selectOptions[itemIndex]; + setActiveItemId(createItemId(focusedItem.value as string)); + }; + + const resetActiveAndFocusedItem = () => { + setFocusedItemIndex(null); + setActiveItemId(null); + }; + + const closeMenu = () => { + setIsOpen(false); + resetActiveAndFocusedItem(); + }; + + const onInputClick = () => { + if (!isOpen) { + setIsOpen(true); + } else if (!filterValue) { + closeMenu(); + } + }; + + const handleMenuArrowKeys = (key: string) => { + let indexToFocus = 0; + + if (!isOpen) { + setIsOpen(true); + } + + if (selectOptions.every((option) => option.isDisabled)) { + return; + } + + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus--; + if (indexToFocus === -1) { + indexToFocus = selectOptions.length - 1; + } + } + } + + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus++; + if (indexToFocus === selectOptions.length) { + indexToFocus = 0; + } + } + } + + setActiveAndFocusedItem(indexToFocus); + }; + + const onInputKeyDown = (event: React.KeyboardEvent) => { + const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null; + + switch (event.key) { + case 'Enter': + if ( + isOpen && + focusedItem && + focusedItem.value !== NO_RESULTS && + !focusedItem.isAriaDisabled + ) { + onSelect(focusedItem.value as string); + } + + if (!isOpen) { + setIsOpen(true); + } + + break; + case 'ArrowUp': + case 'ArrowDown': + event.preventDefault(); + handleMenuArrowKeys(event.key); + break; + } + }; + + const onToggleClick = () => { + setIsOpen(!isOpen); + textInputRef?.current?.focus(); + }; + + const onTextInputChange = (_event: React.FormEvent, value: string) => { + setFilterValue(value); + resetActiveAndFocusedItem(); + }; + + const onSelect = React.useCallback( + (val) => { + if (val && val !== NO_RESULTS) { + if (isMultiSelect && Array.isArray(value)) { + setValue( + value.some((v) => isEqual(v, val)) + ? value.filter((v) => !isEqual(v, val)) + : [...(value as unknown[]), val], + ); + } else { + setValue(val); + setFilterValue(selectOptions.find((option) => option.value === val)?.children as string); + } + } + + textInputRef.current?.focus(); + }, + [isMultiSelect, setValue, setFilterValue, selectOptions, value], + ); + + const onClearButtonClick = () => { + setFilterValue(''); + resetActiveAndFocusedItem(); + textInputRef?.current?.focus(); + }; + + const toggle = (toggleRef: React.Ref) => ( + + + + + + + + + ); + + return ( + + + {helperText && ( + + + {helperText} + + + )} + + ); +}; + +export default SelectFieldWithSearch; diff --git a/libs/ui-lib/lib/common/components/ui/formik/index.tsx b/libs/ui-lib/lib/common/components/ui/formik/index.tsx index 738e4406cd..eb78b298d7 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/index.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/index.tsx @@ -17,6 +17,7 @@ export { default as RemovableField } from './RemovableField'; export { default as AddButton } from './AddButton'; export { default as PencilEditField } from './PencilEditField'; export { default as AlertFormikError } from './AlertFormikError'; +export { default as SelectFieldWithSearch } from './SelectFieldWithSearch'; export * from './utils'; export * from './PullSecretField'; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.tsx index 10d16ee816..da32e00202 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.tsx @@ -19,6 +19,9 @@ const OcmOpenShiftVersionSelect = ({ versions }: OcmOpenShiftVersionSelectProps) const { values: { customOpenshiftSelect }, } = useFormikContext(); + const { allVersions } = useOpenShiftVersionsContext(); + const [isOpenshiftVersionModalOpen, setIsOpenshiftVersionModalOpen] = React.useState(false); + const selectOptions = React.useMemo( () => versions.map((version) => ({ @@ -32,33 +35,25 @@ const OcmOpenShiftVersionSelect = ({ versions }: OcmOpenShiftVersionSelectProps) })), [versions, t], ); - const [isOpenshiftVersionModalOpen, setIsOpenshiftVersionModalOpen] = React.useState(false); - - const showOpenshiftVersionModal = () => { - setIsOpenshiftVersionModalOpen(true); - }; const updatedSelectOptions = React.useMemo(() => { if ( customOpenshiftSelect && - !selectOptions.find((option) => option.value === customOpenshiftSelect.value) + !selectOptions.some((version) => version.value === customOpenshiftSelect) ) { - return [ - { - label: customOpenshiftSelect.label, - value: customOpenshiftSelect.value, - }, - ]; + return allVersions.find((version) => version.value === customOpenshiftSelect); } - return []; - }, [selectOptions, customOpenshiftSelect]); - - const { allVersions } = useOpenShiftVersionsContext(); + return undefined; + }, [allVersions, customOpenshiftSelect, selectOptions]); - const getHelperText = (value: string | undefined, inModal?: boolean) => { + const getHelperText = (value: string | null, inModal?: boolean) => { return getOpenshiftVersionHelperText(allVersions, value, t, inModal); }; + const showOpenshiftVersionModal = () => { + setIsOpenshiftVersionModalOpen(true); + }; + return ( <> {isOpenshiftVersionModalOpen && ( { export const getOpenshiftVersionHelperText = ( versions: OpenshiftVersionOptionType[], - selectedVersionValue: string | undefined, + selectedVersionValue: string | null, t: TFunction, isModal?: boolean, ) => { From dce05c924ed8492ecd117fe18ac8f6b8782f404a Mon Sep 17 00:00:00 2001 From: jgyselov Date: Tue, 26 Aug 2025 12:49:27 +0200 Subject: [PATCH 4/7] Update '@openshift-console/dynamic-plugin-sdk' --- libs/ui-lib/lib/ocm/components/Routes.tsx | 16 +-- libs/ui-lib/package.json | 2 +- yarn.lock | 128 +++++++++++++++++++++- 3 files changed, 132 insertions(+), 14 deletions(-) diff --git a/libs/ui-lib/lib/ocm/components/Routes.tsx b/libs/ui-lib/lib/ocm/components/Routes.tsx index 83dda1bbbb..2f28a0273e 100644 --- a/libs/ui-lib/lib/ocm/components/Routes.tsx +++ b/libs/ui-lib/lib/ocm/components/Routes.tsx @@ -14,19 +14,21 @@ import { AssistedUILibVersion } from './ui'; import { storeDay1 } from '../store'; import { useFeatureDetection } from '../hooks/use-feature-detection'; +type UILibRoutesProps = { + allEnabledFeatures: FeatureListType; + children?: React.ReactNode; + history?: HistoryRouterProps['history']; + basename?: string; + additionalComponents?: React.ReactNode; +}; + export const UILibRoutes = ({ allEnabledFeatures, children, history, basename, additionalComponents, -}: { - allEnabledFeatures: FeatureListType; - children?: React.ReactNode; - history?: HistoryRouterProps['history']; - basename?: string; - additionalComponents?: React.ReactNode; -}) => { +}: UILibRoutesProps) => { useFeatureDetection(allEnabledFeatures); const routes = ( diff --git a/libs/ui-lib/package.json b/libs/ui-lib/package.json index 5b29bdd706..c7f4cf8fe1 100644 --- a/libs/ui-lib/package.json +++ b/libs/ui-lib/package.json @@ -2,7 +2,7 @@ "dependencies": { "@openshift-assisted/locales": "workspace:*", "@openshift-assisted/types": "workspace:*", - "@openshift-console/dynamic-plugin-sdk": "0.0.3", + "@openshift-console/dynamic-plugin-sdk": "1.0.0", "@patternfly/patternfly": "5.2.0", "@patternfly/react-code-editor": "5.2.0", "@patternfly/react-core": "5.2.0", diff --git a/yarn.lock b/yarn.lock index 30d895214c..05fa88e31f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -782,7 +782,7 @@ __metadata: dependencies: "@openshift-assisted/locales": "workspace:*" "@openshift-assisted/types": "workspace:*" - "@openshift-console/dynamic-plugin-sdk": 0.0.3 + "@openshift-console/dynamic-plugin-sdk": 1.0.0 "@patternfly/patternfly": 5.2.0 "@patternfly/react-code-editor": 5.2.0 "@patternfly/react-core": 5.2.0 @@ -861,6 +861,28 @@ __metadata: languageName: node linkType: hard +"@openshift-console/dynamic-plugin-sdk@npm:1.0.0": + version: 1.0.0 + resolution: "@openshift-console/dynamic-plugin-sdk@npm:1.0.0" + dependencies: + classnames: 2.x + immutable: 3.x + lodash: ^4.17.21 + react: ^17.0.1 + react-i18next: ^11.7.3 + react-redux: 7.2.2 + react-router: 5.3.x + react-router-dom: 5.3.x + react-router-dom-v5-compat: ^6.11.2 + redux: 4.0.1 + redux-thunk: 2.4.0 + reselect: 4.x + typesafe-actions: ^4.2.1 + whatwg-fetch: 2.x + checksum: 3472d8109a550e7c42b43b67bf041dd739f502c4016ee999c2f9634d6dc203eb713273a8a391065b096b95313e43eace6fac317a302d2f65d1f0624c329e9fb4 + languageName: node + linkType: hard + "@patternfly-6/patternfly@npm:@patternfly/patternfly@6.3.1, @patternfly/patternfly@npm:6.3.1": version: 6.3.1 resolution: "@patternfly/patternfly@npm:6.3.1" @@ -1276,6 +1298,13 @@ __metadata: languageName: node linkType: hard +"@remix-run/router@npm:1.23.0": + version: 1.23.0 + resolution: "@remix-run/router@npm:1.23.0" + checksum: 6a403b7bc740f15185f3b68f90f98d4976fe231e819b44a0f0628783c4f31ca1072e3370c24b98488be3e4f68ecf51b20cb9463f20a5a6cf4c21929fc7721964 + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.50.1": version: 4.50.1 resolution: "@rollup/rollup-android-arm-eabi@npm:4.50.1" @@ -3360,7 +3389,7 @@ __metadata: languageName: node linkType: hard -"classnames@npm:^2.3.1": +"classnames@npm:2.x, classnames@npm:^2.3.1": version: 2.5.1 resolution: "classnames@npm:2.5.1" checksum: da424a8a6f3a96a2e87d01a432ba19315503294ac7e025f9fece656db6b6a0f7b5003bb1fbb51cbb0d9624d964f1b9bb35a51c73af9b2434c7b292c42231c1e5 @@ -6233,6 +6262,13 @@ __metadata: languageName: node linkType: hard +"immutable@npm:3.x": + version: 3.8.2 + resolution: "immutable@npm:3.8.2" + checksum: 41909b386950ff84ca3cfca77c74cfc87d225a914e98e6c57996fa81a328da61a7c32216d6d5abad40f54747ffdc5c4b02b102e6ad1a504c1752efde8041f964 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -9319,6 +9355,27 @@ __metadata: languageName: node linkType: hard +"react-redux@npm:7.2.2": + version: 7.2.2 + resolution: "react-redux@npm:7.2.2" + dependencies: + "@babel/runtime": ^7.12.1 + hoist-non-react-statics: ^3.3.2 + loose-envify: ^1.4.0 + prop-types: ^15.7.2 + react-is: ^16.13.1 + peerDependencies: + react: ^16.8.3 || ^17 + redux: ^2.0.0 || ^3.0.0 || ^4.0.0-0 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: c421df8a888a261aaecd9dab92b6d2c92712a7d4e4eab15a9ed5a47bafd0cac44d66cff6cfd6ef6563820887053af7641ba872012f41a415067274430e860e98 + languageName: node + linkType: hard + "react-redux@npm:^8.0.5": version: 8.1.3 resolution: "react-redux@npm:8.1.3" @@ -9351,6 +9408,21 @@ __metadata: languageName: node linkType: hard +"react-router-dom-v5-compat@npm:^6.11.2": + version: 6.30.1 + resolution: "react-router-dom-v5-compat@npm:6.30.1" + dependencies: + "@remix-run/router": 1.23.0 + history: ^5.3.0 + react-router: 6.30.1 + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + react-router-dom: 4 || 5 + checksum: 4cb99e7199c16e646ab95d4c05e709f3dbcdcf9054ed92d98bb92ac6247ee715879183371555e39c109c2e549f7fd28feb5637ed75daa924a559d00297d85474 + languageName: node + linkType: hard + "react-router-dom-v5-compat@npm:^6.21.2": version: 6.22.3 resolution: "react-router-dom-v5-compat@npm:6.22.3" @@ -9382,7 +9454,7 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^5.3.3": +"react-router-dom@npm:5.3.x, react-router-dom@npm:^5.3.3": version: 5.3.4 resolution: "react-router-dom@npm:5.3.4" dependencies: @@ -9419,7 +9491,7 @@ __metadata: languageName: node linkType: hard -"react-router@npm:5.3.4": +"react-router@npm:5.3.4, react-router@npm:5.3.x": version: 5.3.4 resolution: "react-router@npm:5.3.4" dependencies: @@ -9449,6 +9521,17 @@ __metadata: languageName: node linkType: hard +"react-router@npm:6.30.1": + version: 6.30.1 + resolution: "react-router@npm:6.30.1" + dependencies: + "@remix-run/router": 1.23.0 + peerDependencies: + react: ">=16.8" + checksum: cab6c2ef3e4bc2b7d92c17f9de30d56f169ffe10a082de52a5842a335289d798aec91ff7bc39dfe7d73f6c50ae56e9e5d1597e8edfcdd80910b93383138a7525 + languageName: node + linkType: hard + "react-side-effect@npm:^2.1.0": version: 2.1.2 resolution: "react-side-effect@npm:2.1.2" @@ -9521,6 +9604,15 @@ __metadata: languageName: node linkType: hard +"redux-thunk@npm:2.4.0": + version: 2.4.0 + resolution: "redux-thunk@npm:2.4.0" + peerDependencies: + redux: ^4 + checksum: 250cd88087bb4614052a5175fd6bd4c70b6a4479c357af8628b3a1d5f75d5b0a6c01645acc3257d3ed147a949708dd748a50b00402d548c3331038ed4f296edc + languageName: node + linkType: hard + "redux-thunk@npm:^2.4.2": version: 2.4.2 resolution: "redux-thunk@npm:2.4.2" @@ -9530,6 +9622,16 @@ __metadata: languageName: node linkType: hard +"redux@npm:4.0.1": + version: 4.0.1 + resolution: "redux@npm:4.0.1" + dependencies: + loose-envify: ^1.4.0 + symbol-observable: ^1.2.0 + checksum: f3a4e19b0413cc73ccdbe9f71977292dca9760606ab783aed516c90ca04e931fa1af573c6c55bc506580a2805d4ec0d50edde0b14d7d854f0a66da40f36184b2 + languageName: node + linkType: hard + "redux@npm:^4, redux@npm:^4.0.0, redux@npm:^4.2.1": version: 4.2.1 resolution: "redux@npm:4.2.1" @@ -9724,7 +9826,7 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.8": +"reselect@npm:4.x, reselect@npm:^4.1.8": version: 4.1.8 resolution: "reselect@npm:4.1.8" checksum: a4ac87cedab198769a29be92bc221c32da76cfdad6911eda67b4d3e7136dca86208c3b210e31632eae31ebd2cded18596f0dd230d3ccc9e978df22f233b5583e @@ -10658,6 +10760,13 @@ __metadata: languageName: node linkType: hard +"symbol-observable@npm:^1.2.0": + version: 1.2.0 + resolution: "symbol-observable@npm:1.2.0" + checksum: 48ffbc22e3d75f9853b3ff2ae94a44d84f386415110aea5effc24d84c502e03a4a6b7a8f75ebaf7b585780bda34eb5d6da3121f826a6f93398429d30032971b6 + languageName: node + linkType: hard + "symlink-or-copy@npm:^1.1.8, symlink-or-copy@npm:^1.2.0, symlink-or-copy@npm:^1.3.1": version: 1.3.1 resolution: "symlink-or-copy@npm:1.3.1" @@ -11059,6 +11168,13 @@ __metadata: languageName: node linkType: hard +"typesafe-actions@npm:^4.2.1": + version: 4.4.2 + resolution: "typesafe-actions@npm:4.4.2" + checksum: 43b3a91af74172b42e27098150d84eaa9f02f74a8f9c81225e560d48872f6a3174482651d6e6278f380ed25870edb2b949b4776382cf0393b8299a3723c56d9b + languageName: node + linkType: hard + "typescript@npm:4.9.5, typescript@npm:^4.2.4": version: 4.9.5 resolution: "typescript@npm:4.9.5" @@ -11703,7 +11819,7 @@ __metadata: languageName: node linkType: hard -"whatwg-fetch@npm:^2.0.4": +"whatwg-fetch@npm:2.x, whatwg-fetch@npm:^2.0.4": version: 2.0.4 resolution: "whatwg-fetch@npm:2.0.4" checksum: de7c65a68d7d62e2f144a6b30293370b3ad82b65ebcd68f2ac8e8bbe7ede90febd98ba9486b78c1cbc950e0e8838fa5c2727f939899ab3fc7b71a04be52d33a5 From b899573845449629b15ffa96fc8ed4b369a1be5c Mon Sep 17 00:00:00 2001 From: jgyselov Date: Mon, 1 Sep 2025 12:48:32 +0200 Subject: [PATCH 5/7] CIM custom manifest step --- libs/locales/lib/en/translation.json | 23 +- .../ClusterDeploymentWizard.tsx | 9 +- .../ClusterDeploymentDetailsForm.tsx | 2 +- .../components/ClusterDeployment/constants.ts | 1 + .../ClusterDeploymentCustomManifestsStep.tsx | 121 +++++++++ .../customManifests/ConfigMapDetail.tsx | 191 ++++++++++++++ .../customManifests/ConfigMapForm.tsx | 247 ++++++++++++++++++ .../customManifests/index.tsx | 1 + .../ClusterDeploymentHostsDiscoveryStep.tsx | 2 +- .../ClusterDeploymentHostSelectionStep.tsx | 2 +- .../ClusterDeploymentNetworkingStep.tsx | 2 +- .../review/ClusterDeploymentReviewStep.tsx | 4 +- .../cim/components/ClusterDeployment/types.ts | 1 + .../ClusterDeployment/wizardTransition.ts | 2 + .../cim/components/modals/EditProxyModal.tsx | 2 +- .../cim/components/modals/EditSSHKeyModal.tsx | 2 +- .../modals/MassApproveAgentModal.tsx | 2 +- libs/ui-lib/lib/cim/hooks/index.tsx | 1 + libs/ui-lib/lib/cim/hooks/types.tsx | 8 + libs/ui-lib/lib/cim/hooks/useConfigMaps.tsx | 14 + .../lib/cim/hooks/useK8sWatchResource.tsx | 27 ++ libs/ui-lib/lib/cim/types/index.ts | 1 + .../cim/types/k8s/agent-cluster-install.ts | 1 + libs/ui-lib/lib/cim/types/models.tsx | 12 + libs/ui-lib/lib/cim/utils.ts | 12 + 25 files changed, 677 insertions(+), 13 deletions(-) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ClusterDeploymentCustomManifestsStep.tsx create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapDetail.tsx create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapForm.tsx create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx create mode 100644 libs/ui-lib/lib/cim/hooks/index.tsx create mode 100644 libs/ui-lib/lib/cim/hooks/types.tsx create mode 100644 libs/ui-lib/lib/cim/hooks/useConfigMaps.tsx create mode 100644 libs/ui-lib/lib/cim/hooks/useK8sWatchResource.tsx create mode 100644 libs/ui-lib/lib/cim/types/models.tsx diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index 68b765238f..4b778f0db2 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -11,6 +11,8 @@ "ai:{{count}} control plane node installed_plural": "{{count}} control plane nodes installed", "ai:{{count}} Disk": "{{count}} Disk", "ai:{{count}} Disk_plural": "{{count}} Disks", + "ai:{{count}} entry": "{{count}} entry", + "ai:{{count}} entry_plural": "{{count}} entries", "ai:{{count}} filter applied": "{{count}} filter applied", "ai:{{count}} filter applied_plural": "{{count}} filters applied", "ai:{{count}} filtering label": "{{count}} filtering label", @@ -50,6 +52,7 @@ "ai:Add": "Add", "ai:Add another Tang server": "Add another Tang server", "ai:Add BMC host dialog": "Add BMC host dialog", + "ai:Add custom manifests": "Add custom manifests", "ai:Add host": "Add host", "ai:Add host dialog": "Add host dialog", "ai:Add host using Baseboard Management Controller (BMC)": "Add host using Baseboard Management Controller (BMC)", @@ -82,9 +85,10 @@ "ai:Already approved": "Already approved", "ai:Also note that each host's disk write speed should meet the minimum requirements to run OpenShift. ": "Also note that the disk write speed of each host must meet the minimum requirements to run OpenShift. ", "ai:AMD GPU requirements": "AMD GPU requirements", - "ai:An error occured": "An error occured.", - "ai:An error occured while approving agents": "An error occured while approving agents.", - "ai:An error occured while starting installation.": "An error occured while starting installation.", + "ai:An error occurred": "An error occurred.", + "ai:An error occurred while approving agents": "An error occurred while approving agents.", + "ai:An error occurred while fetching config maps": "An error occurred while fetching config maps", + "ai:An error occurred while starting installation.": "An error occurred while starting installation.", "ai:And verify that this is the output:": "And verify the following output:", "ai:API connectivity failure": "API connectivity failure", "ai:API domain name resolution": "API domain name resolution", @@ -105,6 +109,7 @@ "ai:arm64": "arm64", "ai:arm64 is not supported in this OpenShift version": "arm64 is not supported in this OpenShift version", "ai:At least 3 hosts are required, capable of functioning as control plane nodes.": "At least 3 hosts are required that are capable of functioning as control plane nodes.", + "ai:At least one config map is required": "At least one config map is required", "ai:Authentication is provided by the discovery ISO, therefore when you access your host using SSH, a password is not required. Optional -i parameter can be used to specify the private key that matches the public key provided when generating Discovery ISO.": "Authentication is provided by the Discovery ISO, so a password is not required when you access your host using SSH. The optional -i parameter can be used to specify the private key that matches the public key that is provided when generating Discovery ISO.", "ai:Auto synchronized NTP (Network Time Protocol) sources": "Auto synchronized NTP (Network Time Protocol) sources", "ai:Auto-assign": "Auto-assign", @@ -192,6 +197,9 @@ "ai:Command to download the ISO:": "Command to download the ISO:", "ai:Compatible with cluster platform": "Compatible with cluster platform", "ai:Condition": "Condition", + "ai:Config map name is required": "Config map name is required", + "ai:Config map not found": "Config map not found", + "ai:Config maps": "Config maps", "ai:Configuration is hanging for a long time.": "Configuration is hanging for a long time.", "ai:Configuration may take a few minutes.": "Configuration might take a few minutes.", "ai:Configure": "Configure", @@ -234,8 +242,12 @@ "ai:Create": "Create", "ai:Created at": "Created at", "ai:Currently, adding additional machines to your cluster is not supported.": "Currently, adding additional machines to your cluster is not supported.", + "ai:Custom manifests": "Custom manifests", + "ai:Data": "Data", "ai:Database storage": "Database storage", "ai:Default route to host": "Default route to host", + "ai:Define a config map": "Define a config map", + "ai:Define a config map with your custom manifests and select it in the form below.": "Define a config map with your custom manifests and select it in the form below.", "ai:Define the quantity of worker nodes and nodepools to create for your cluster. Additional worker nodes and nodepools can be added after the cluster is created.": "Define the quantity of worker nodes and nodepools to create for your cluster. Additional worker nodes and nodepools can be added after the cluster is created.", "ai:Deleted hosts": "Deleted hosts", "ai:Deprovisioning": "Deprovisioning", @@ -287,6 +299,7 @@ "ai:Edit BMH": "Edit BMH", "ai:Edit BMH dialog": "Edit BMH dialog", "ai:Edit cluster-wide proxy settings": "Edit cluster-wide proxy settings", + "ai:Edit config map data": "Edit config map data", "ai:Edit ISO configuration": "Edit ISO configuration", "ai:Edit NTP sources": "Edit NTP sources", "ai:Edit Ntp sources dialog": "Edit NTP sources dialog", @@ -517,6 +530,7 @@ "ai:Make sure you download and store your credentials files in a safe place": "Make sure you download and store your credentials files in a safe place", "ai:Manage hosts": "Manage hosts", "ai:Management": "Management", + "ai:Manifest names in each config map should be unique across all referenced config maps.": "Manifest names in each config map should be unique across all referenced config maps.", "ai:Manually fix the host's NTP configuration or provide additional NTP sources.": "Manually fix the host's NTP configuration or provide additional NTP sources.", "ai:Manufacturer": "Manufacturer", "ai:Maximum availability {{maxAgents}}": "Maximum availability {{maxAgents}}", @@ -592,6 +606,7 @@ "ai:No proxy domains": "No proxy domains", "ai:No release image is available.": "No release image is available.", "ai:No results found": "No results found", + "ai:No results found for {{filter}}": "No results found for {{filter}}", "ai:No results match the filter criteria. Clear filters to show results.": "No results match the filter criteria. Clear filters to show results.", "ai:No skip installation disk": "No skip installation disk", "ai:No skip missing disk": "No skip missing disk", @@ -733,6 +748,7 @@ "ai:Secret and keys": "Secret and keys", "ai:Select all": "Select all", "ai:Select an OpenShift version from the list or use the type ahead to narrow down the list.": "Select an OpenShift version from the list or use the type ahead to narrow down the list.", + "ai:Select config maps from the list or use the type ahead to narrow down the list.": "Select config maps from the list or use the type ahead to narrow down the list.", "ai:Select how you'd like to add hosts (Discovery ISO, iPXE, or BMC form) and follow the instructions that appear.": "Select the method of adding hosts (Discovery ISO, iPXE, or BMC form) and follow the instructions.", "ai:Select none": "Select none", "ai:Select one or more locations to view hosts": "Select one or more locations to view hosts", @@ -758,6 +774,7 @@ "ai:Size": "Size", "ai:SNO: One host is required with at least {{sno_cpu_cores}} CPU cores, {{snoRam}} of RAM, and {{sno_disksize}} GB of disk size storage.": "SNO: One host is required with at least {{sno_cpu_cores}} CPU cores, {{snoRam}} of RAM, and {{sno_disksize}} GB of disk size storage.", "ai:Software-Defined Networking (SDN)": "Software-Defined Networking (SDN)", + "ai:Some config map items are invalid": "Some config map items are invalid", "ai:Some details are not editable after the draft cluster was created.": "Some details are not editable after the draft cluster was created.", "ai:Some hosts are in a warning state.": "Some hosts are in a warning state.", "ai:Some hosts are in an error state.": "Some hosts are in an error state.", diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx index 25dc5a66d6..b963b16511 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentWizard.tsx @@ -13,6 +13,7 @@ import { ClusterDeploymentHostSelectionStep } from './hostSelection'; import { ClusterDeploymentHostsDiscoveryStep } from './hostDiscovery'; import { ClusterDeploymentNetworkingStep } from './networking'; import { ClusterDeploymentReviewStep } from './review'; +import { ClusterDeploymentCustomManifestsStep } from './customManifests'; export const ClusterDeploymentWizard = ({ className, @@ -76,6 +77,7 @@ export const ClusterDeploymentWizard = ({ id={'installation-type'} isDisabled /> + + + {isCIMFlow(clusterDeployment) ? ( {agentClusterInstall?.metadata?.name ? ( @@ -131,6 +135,7 @@ export const ClusterDeploymentWizard = ({ )} )} + - {/** TODO: Add the custom manifest step here */} + + + - + {syncError} diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/constants.ts b/libs/ui-lib/lib/cim/components/ClusterDeployment/constants.ts index 4fdbbf7af1..7fdca6af52 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/constants.ts +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/constants.ts @@ -18,6 +18,7 @@ export const wizardStepNames = ( 'hosts-selection': t('ai:Cluster hosts'), 'hosts-discovery': t('ai:Cluster hosts'), networking: t('ai:Networking'), + 'custom-manifests': t('ai:Custom manifests'), review: t('ai:Review and create'), }); diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ClusterDeploymentCustomManifestsStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ClusterDeploymentCustomManifestsStep.tsx new file mode 100644 index 0000000000..4fc1657cfe --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ClusterDeploymentCustomManifestsStep.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import * as Yup from 'yup'; +import { Formik } from 'formik'; +import { k8sPatch, Patch } from '@openshift-console/dynamic-plugin-sdk'; +import { Alert, AlertActionLink, Grid, GridItem, Text, TextVariants } from '@patternfly/react-core'; +import ExternalLinkAltIcon from '@patternfly/react-icons/dist/js/icons/external-link-alt-icon'; +import { ClusterWizardStepHeader, useTranslation } from '../../../../common'; +import { AgentClusterInstallK8sResource, AgentClusterInstallModel } from '../../../types'; +import { ConfigMapForm, CustomManifestFormType } from './ConfigMapForm'; +import { appendPatch } from '../../../utils'; + +export const ClusterDeploymentCustomManifestsStep = ({ + agentClusterInstall, +}: { + agentClusterInstall: AgentClusterInstallK8sResource; +}) => { + const { t } = useTranslation(); + + const initialValues: CustomManifestFormType = { + configMaps: + agentClusterInstall.spec?.manifestsConfigMapRefs?.map(({ name }) => ({ + name, + valid: false, + })) || [], + }; + + const validationSchema = Yup.lazy((values: CustomManifestFormType) => + Yup.object({ + configMaps: Yup.array() + .of( + Yup.object().shape({ + name: Yup.string().required(t('ai:Config map name is required')), + valid: Yup.boolean().isTrue(t('ai:Config map not found')), + manifestNames: Yup.array() + .of(Yup.string()) + .test( + 'unique-manifests', + t( + 'ai:Manifest names in each config map should be unique across all referenced config maps.', + ), + (value) => + // check if another config map has any manifests with the same names as this one + values.configMaps + .flatMap(({ manifestNames }) => manifestNames) + .filter((v) => value?.includes(v)).length === value?.length, + ), + }), + ) + .min( + agentClusterInstall.spec?.platformType === 'External' ? 1 : 0, + t('ai:At least one config map is required'), + ), + }), + ); + + const onSubmit = async (values: CustomManifestFormType) => { + const patches: Patch[] = []; + appendPatch( + patches, + '/spec/manifestsConfigMapRefs', + values.configMaps.map(({ name }) => ({ name })), + agentClusterInstall.spec?.manifestsConfigMapRefs, + ); + + if (patches.length > 0) { + await k8sPatch({ + model: AgentClusterInstallModel, + resource: agentClusterInstall, + data: patches, + }); + } + }; + + return ( + + + {t('ai:Custom manifests')} + + + + {t('ai:Define a config map')} + , + ]} + > + + {t( + 'ai:Define a config map with your custom manifests and select it in the form below.', + )} + + + {t( + 'ai:Manifest names in each config map should be unique across all referenced config maps.', + )} + + + + + + + + + + ); +}; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapDetail.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapDetail.tsx new file mode 100644 index 0000000000..d005dfde49 --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapDetail.tsx @@ -0,0 +1,191 @@ +import React from 'react'; +import { + Alert, + Bullseye, + Button, + CodeBlock, + CodeBlockCode, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Divider, + ExpandableSectionToggle, + Flex, + FlexItem, + Grid, + GridItem, + Label, + Spinner, + Text, + TextVariants, +} from '@patternfly/react-core'; +import { ExternalLink, useTranslation } from '../../../../common'; +import { MinusCircleIcon } from '@patternfly/react-icons/dist/js/icons/minus-circle-icon'; +import InfoCircleIcon from '@patternfly/react-icons/dist/js/icons/info-circle-icon'; +import { ConfigMapK8sResource } from '../../../types'; +import { useField } from 'formik'; + +const ExpandedConfigMap = ({ + index, + configMap, +}: { + index: number; + configMap?: ConfigMapK8sResource; +}) => { + const { t } = useTranslation(); + const [, { error: isValidError }] = useField(`configMaps.${index}.valid`); + const [, { error: manifestNamesError }] = useField(`configMaps.${index}.manifestNames`); + + return isValidError ? ( + + + + ) : ( + <> + + + + {t('ai:Name')} + {configMap?.metadata?.name} + + + + {t('ai:Namespace')} + + {configMap?.metadata?.namespace} + + + + + {t('ai:Data')} + + + + + {t('ai:Edit config map data')} + + + + {Object.entries(configMap?.data || {}).map(([key, val]) => ( + + {key} + + {val} + + + ))} + + + + + + + {manifestNamesError && ( + + + + )} + + ); +}; + +const CollapsedConfigMap = ({ + index, + configMap, +}: { + index: number; + configMap?: ConfigMapK8sResource; +}) => { + const [, { error: isValidError }] = useField(`configMaps.${index}.valid`); + const [, { error: manifestNamesError }] = useField(`configMaps.${index}.manifestNames`); + + const { t } = useTranslation(); + + return ( + + {isValidError || manifestNamesError ? ( + + ) : ( + + )} + + ); +}; + +export const ConfigMapDetail = ({ + index, + configMapName, + configMap, + onRemove, + isLoading, +}: { + index: number; + configMapName: string; + configMap?: ConfigMapK8sResource; + onRemove: () => void; + isLoading: boolean; +}) => { + const [isExpanded, setIsExpanded] = React.useState(false); + const [showRemoveButton, setShowRemoveButton] = React.useState(false); + + return ( + + setShowRemoveButton(true)} + onMouseLeave={() => setShowRemoveButton(false)} + hasGutter + > + + + + setIsExpanded(!isExpanded)} + direction="down" + > + {configMapName} + + + + + + + + + {isLoading ? ( + + + + + + ) : isExpanded ? ( + + ) : ( + + )} + + + + + + + ); +}; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapForm.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapForm.tsx new file mode 100644 index 0000000000..7c2aad708e --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/ConfigMapForm.tsx @@ -0,0 +1,247 @@ +import React from 'react'; +import Fuse from 'fuse.js'; +import { FieldArray, useFormikContext } from 'formik'; +import { + Stack, + StackItem, + Grid, + WizardFooter, + useWizardContext, + useWizardFooter, + SelectOptionProps, + GridItem, + AlertVariant, + Alert, +} from '@patternfly/react-core'; +import { Alerts, SelectFieldWithSearch, useAlerts, useTranslation } from '../../../../common'; +import { ClusterDeploymentWizardContext } from '../ClusterDeploymentWizardContext'; +import { ValidationSection } from '../components/ValidationSection'; +import { ConfigMapDetail } from './ConfigMapDetail'; +import { useConfigMaps } from '../../../hooks'; + +const NO_RESULTS = 'no_results'; + +export type CustomManifestFormType = { + configMaps: { + name: string; + valid: boolean; + manifestNames?: string[]; + }[]; +}; + +export const ConfigMapForm = ({ namespace }: { namespace: string }) => { + const { t } = useTranslation(); + const { syncError } = React.useContext(ClusterDeploymentWizardContext); + const { alerts, addAlert, clearAlerts } = useAlerts(); + const [isSubmitting, setSubmitting] = React.useState(false); + const { activeStep, goToPrevStep, goToNextStep, close } = useWizardContext(); + + const { values, errors, isValid, setFieldValue, submitForm } = + useFormikContext(); + const [filter, setFilter] = React.useState(''); + const [configMaps, setConfigMaps] = React.useState([]); + const [data, loaded, isError] = useConfigMaps({ + namespace, + isList: true, + }); + + React.useEffect(() => { + if (data && loaded && !isError) { + setConfigMaps( + data.map((configMap) => ({ + value: { + name: configMap.metadata?.name as string, + valid: true, + manifestNames: Object.keys(configMap.data || {}), + }, + children: configMap.metadata?.name as string, + })), + ); + } + }, [data, isError, loaded]); + + React.useEffect(() => { + if (loaded && !isError) { + const newConfigMaps = values.configMaps.map((configMap) => { + const map = data.find((item) => item.metadata?.name === configMap.name); + if (map) { + return { ...configMap, valid: true, manifestNames: Object.keys(map.data || {}) }; + } + return { ...configMap, valid: false, manifestNames: [] }; + }); + + setFieldValue('configMaps', newConfigMaps); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data, isError, loaded, setFieldValue]); + + const placeholder = React.useMemo(() => { + if (values.configMaps.length > 0) { + return `${values.configMaps.length} config map${ + values.configMaps.length !== 1 ? 's' : '' + } selected`; + } + + return 'No config map selected'; + }, [values.configMaps]); + + const fuse = React.useMemo( + () => + new Fuse(Object.values(configMaps), { + includeScore: true, + ignoreLocation: true, + threshold: 0.3, + keys: ['value.name'], + }), + [configMaps], + ); + + const selectOptions = React.useMemo(() => { + if (filter) { + const newSelectOptions = fuse + .search(filter) + .sort((a, b) => (a.score || 0) - (b.score || 0)) + .map(({ item }) => item); + + if (!newSelectOptions.length) { + return [ + { + isAriaDisabled: true, + children: t('ai:No results found for {{filter}}', { filter }), + value: NO_RESULTS, + hasCheckbox: false, + }, + ]; + } + return newSelectOptions; + } + return configMaps; + }, [filter, configMaps, fuse, t]); + + const submittingText = React.useMemo(() => { + if (isSubmitting) { + return t('ai:Saving changes...'); + } + return undefined; + }, [isSubmitting, t]); + + const onNext = React.useCallback(async () => { + try { + clearAlerts(); + setSubmitting(true); + + await submitForm(); + await goToNextStep(); + } catch (error) { + addAlert({ + title: t('ai:Failed to save ClusterDeployment'), + message: error instanceof Error ? error.message : undefined, + }); + } finally { + setSubmitting(false); + } + }, [addAlert, clearAlerts, goToNextStep, submitForm, t]); + + const footer = React.useMemo( + () => ( + + ), + [activeStep, onNext, submittingText, t, isValid, loaded, goToPrevStep, close], + ); + + useWizardFooter(footer); + + const errorAlert = + typeof errors.configMaps === 'string' ? ( + + + + ) : ( + + + + ); + + return ( + + + + + + + + + + {({ remove }) => + values.configMaps.map((configMap, index) => ( + item.metadata?.name === configMap.name)} + onRemove={() => remove(index)} + isLoading={!loaded} + /> + )) + } + + + + + + + {!isValid && errorAlert} + + {isError && ( + + + + )} + + {!!alerts.length && ( + + + + )} + + {syncError && ( + + + + {syncError} + + + + )} + + ); +}; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx new file mode 100644 index 0000000000..99579e9a3f --- /dev/null +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx @@ -0,0 +1 @@ +export * from "./ClusterDeploymentCustomManifestsStep"; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscoveryStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscoveryStep.tsx index 1e8c046b5d..f21ff1199c 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscoveryStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostsDiscoveryStep.tsx @@ -174,7 +174,7 @@ export const ClusterDeploymentHostsDiscoveryStep = ({ {!!syncError && ( - + {syncError} diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx index 602740faf8..3f55fab985 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx @@ -255,7 +255,7 @@ const HostSelectionForm: React.FC = ({ const errorsSection = ( {syncError && ( - + {syncError} )} diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingStep.tsx index 3174b58889..b9ec129960 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingStep.tsx @@ -200,7 +200,7 @@ export const NetworkingForm = ({ {syncError && ( - + {syncError} diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx index 463951ca35..37f57ff950 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx @@ -83,7 +83,7 @@ export const ClusterDeploymentReviewStep = ({ await onFinish(); } catch (err) { const error = err as Error; - addAlert({ title: error.message || t('ai:An error occured while starting installation.') }); + addAlert({ title: error.message || t('ai:An error occurred while starting installation.') }); } finally { setSubmitting(false); } @@ -188,7 +188,7 @@ export const ClusterDeploymentReviewStep = ({ {syncError && ( - + {syncError} diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/types.ts b/libs/ui-lib/lib/cim/components/ClusterDeployment/types.ts index 37dd891c02..3a689e3137 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/types.ts +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/types.ts @@ -44,6 +44,7 @@ export type ClusterDeploymentWizardStepsType = | 'hosts-selection' | 'hosts-discovery' | 'networking' + | 'custom-manifests' | 'review'; export type ClusterDeploymentDetailsProps = { diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/wizardTransition.ts b/libs/ui-lib/lib/cim/components/ClusterDeployment/wizardTransition.ts index 9faa5ec87a..4e10b98890 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/wizardTransition.ts +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/wizardTransition.ts @@ -15,6 +15,7 @@ export type ClusterWizardStepsType = | 'hosts-selection' | 'hosts-discovery' | 'networking' + | 'custom-manifests' | 'review'; const clusterDetailsStepValidationsMap: WizardStepValidationMap = { @@ -98,6 +99,7 @@ export const wizardStepsValidationsMap: WizardStepsValidationMap = ({ {error && ( - + {error} )} diff --git a/libs/ui-lib/lib/cim/components/modals/EditSSHKeyModal.tsx b/libs/ui-lib/lib/cim/components/modals/EditSSHKeyModal.tsx index a902856164..9c49c1b3d1 100644 --- a/libs/ui-lib/lib/cim/components/modals/EditSSHKeyModal.tsx +++ b/libs/ui-lib/lib/cim/components/modals/EditSSHKeyModal.tsx @@ -68,7 +68,7 @@ const EditSSHKeyModal: React.FC = ({ onClose(); } catch (err) { const error = err as Error; - setError(error?.message || t('ai:An error occured')); + setError(error?.message || t('ai:An error occurred')); } }} validateOnMount diff --git a/libs/ui-lib/lib/cim/components/modals/MassApproveAgentModal.tsx b/libs/ui-lib/lib/cim/components/modals/MassApproveAgentModal.tsx index 44740b005c..64e045a549 100644 --- a/libs/ui-lib/lib/cim/components/modals/MassApproveAgentModal.tsx +++ b/libs/ui-lib/lib/cim/components/modals/MassApproveAgentModal.tsx @@ -121,7 +121,7 @@ const MassApproveAgentModal: React.FC = ({ onClose(); } catch (err) { setError({ - title: t('ai:An error occured while approving agents'), + title: t('ai:An error occurred while approving agents'), message: getErrorMessage(err), }); setProgress(null); diff --git a/libs/ui-lib/lib/cim/hooks/index.tsx b/libs/ui-lib/lib/cim/hooks/index.tsx new file mode 100644 index 0000000000..7d759556d6 --- /dev/null +++ b/libs/ui-lib/lib/cim/hooks/index.tsx @@ -0,0 +1 @@ +export * from './useConfigMaps'; diff --git a/libs/ui-lib/lib/cim/hooks/types.tsx b/libs/ui-lib/lib/cim/hooks/types.tsx new file mode 100644 index 0000000000..4eee462098 --- /dev/null +++ b/libs/ui-lib/lib/cim/hooks/types.tsx @@ -0,0 +1,8 @@ +import { WatchK8sResource } from '@openshift-console/dynamic-plugin-sdk'; + +export type K8sWatchHookListProps = Omit< + WatchK8sResource, + 'groupVersionKind' | 'name' | 'isList' +> | null; + +export type K8sWatchHookProps = Pick | null; diff --git a/libs/ui-lib/lib/cim/hooks/useConfigMaps.tsx b/libs/ui-lib/lib/cim/hooks/useConfigMaps.tsx new file mode 100644 index 0000000000..4cd96fbcdc --- /dev/null +++ b/libs/ui-lib/lib/cim/hooks/useConfigMaps.tsx @@ -0,0 +1,14 @@ +import { useK8sWatchResource } from './useK8sWatchResource'; +import { ConfigMapK8sResource } from '../types'; +import { K8sWatchHookProps } from './types'; + +export const useConfigMaps = (props: K8sWatchHookProps) => + useK8sWatchResource( + { + groupVersionKind: { + kind: 'ConfigMap', + version: 'v1', + }, + }, + props, + ); diff --git a/libs/ui-lib/lib/cim/hooks/useK8sWatchResource.tsx b/libs/ui-lib/lib/cim/hooks/useK8sWatchResource.tsx new file mode 100644 index 0000000000..bb1a956016 --- /dev/null +++ b/libs/ui-lib/lib/cim/hooks/useK8sWatchResource.tsx @@ -0,0 +1,27 @@ +import { + useK8sWatchResource as consoleWatch, + K8sResourceCommon, + WatchK8sResource, +} from '@openshift-console/dynamic-plugin-sdk'; + +export type WatchK8sResult = [ + R, + boolean, + unknown, +]; + +export const useK8sWatchResource = ( + resource: Pick, + props?: Omit | null, +): WatchK8sResult => { + // eslint-disable-next-line + const [data, loaded, error] = consoleWatch( + props !== null + ? { + ...resource, + ...(props || {}), + } + : null, + ); + return [data, error ? true : loaded, error as unknown]; +}; diff --git a/libs/ui-lib/lib/cim/types/index.ts b/libs/ui-lib/lib/cim/types/index.ts index 0cedb1ebc2..f59221335b 100644 --- a/libs/ui-lib/lib/cim/types/index.ts +++ b/libs/ui-lib/lib/cim/types/index.ts @@ -2,3 +2,4 @@ export * from './fromOCP'; export * from './k8s'; export * from './metal3'; export * from './resources'; +export * from './models'; diff --git a/libs/ui-lib/lib/cim/types/k8s/agent-cluster-install.ts b/libs/ui-lib/lib/cim/types/k8s/agent-cluster-install.ts index 4abc31af8f..20cb08b8d0 100644 --- a/libs/ui-lib/lib/cim/types/k8s/agent-cluster-install.ts +++ b/libs/ui-lib/lib/cim/types/k8s/agent-cluster-install.ts @@ -57,6 +57,7 @@ export type AgentClusterInstallK8sResource = K8sResourceCommon & { }; holdInstallation?: boolean; platformType: string; + manifestsConfigMapRefs?: { name: string }[]; }; status?: { apiVIP?: string; diff --git a/libs/ui-lib/lib/cim/types/models.tsx b/libs/ui-lib/lib/cim/types/models.tsx new file mode 100644 index 0000000000..bbd4f4e134 --- /dev/null +++ b/libs/ui-lib/lib/cim/types/models.tsx @@ -0,0 +1,12 @@ +import { K8sModel } from '@openshift-console/dynamic-plugin-sdk/lib/api/common-types'; + +export const AgentClusterInstallModel: K8sModel = { + label: 'AgentClusterInstall', + apiVersion: 'v1beta1', + plural: 'agentclusterinstalls', + abbr: 'ACI', + namespaced: true, + kind: 'AgentClusterInstall', + labelPlural: 'AgentClusterInstalls', + apiGroup: 'extensions.hive.openshift.io', +}; diff --git a/libs/ui-lib/lib/cim/utils.ts b/libs/ui-lib/lib/cim/utils.ts index 717bc63a7a..1b718c836c 100644 --- a/libs/ui-lib/lib/cim/utils.ts +++ b/libs/ui-lib/lib/cim/utils.ts @@ -1,3 +1,5 @@ +import { Patch } from '@openshift-console/dynamic-plugin-sdk'; +import { isEqual } from 'lodash-es'; import { StatusCondition } from './types'; export function getConditionByType( @@ -6,3 +8,13 @@ export function getConditionByType( ): StatusCondition | undefined { return conditions.find((c) => c.type === type); } + +export const appendPatch = (patches: Patch[], path: string, newVal?: V, existingVal?: V) => { + if (!isEqual(newVal, existingVal)) { + patches.push({ + op: existingVal === undefined ? 'add' : 'replace', + path, + value: newVal, + }); + } +}; From c7646fb055669095a247b6fecb3f407a451a23f4 Mon Sep 17 00:00:00 2001 From: jgyselov Date: Mon, 1 Sep 2025 17:17:29 +0200 Subject: [PATCH 6/7] CIM custom manifests review --- .../customManifests/index.tsx | 2 +- .../review/ClusterDeploymentReviewStep.tsx | 8 +++ .../review/ReviewConfigMapsTable.tsx | 70 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 libs/ui-lib/lib/cim/components/ClusterDeployment/review/ReviewConfigMapsTable.tsx diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx index 99579e9a3f..f47bc0c459 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/customManifests/index.tsx @@ -1 +1 @@ -export * from "./ClusterDeploymentCustomManifestsStep"; +export * from './ClusterDeploymentCustomManifestsStep'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx index 37f57ff950..97c7dfeb5e 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/review/ClusterDeploymentReviewStep.tsx @@ -40,6 +40,7 @@ import { } from '../wizardTransition'; import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { ClusterDeploymentWizardContext } from '../ClusterDeploymentWizardContext'; +import { ReviewConfigMapsTable } from './ReviewConfigMapsTable'; import { ValidationSection } from '../components/ValidationSection'; import { PlatformType } from '@openshift-assisted/types/assisted-installer-service'; @@ -158,6 +159,13 @@ export const ClusterDeploymentReviewStep = ({ value={} /> + {!!agentClusterInstall.spec?.manifestsConfigMapRefs?.length ? ( + } + /> + ) : undefined} + { + const [configMaps, loaded] = useConfigMaps({ + namespace: agentClusterInstall.metadata?.namespace, + isList: true, + }); + + const customManifests = React.useMemo(() => { + const maps = configMaps.filter((configMap) => + agentClusterInstall.spec?.manifestsConfigMapRefs + ?.flatMap((ref) => ref.name) + .includes(configMap.metadata?.name || ''), + ); + + return maps + .map((map, index) => [ + { + rowId: `config-map-${index}`, + cells: [{ title: map.metadata?.name as string, isExpanded: true, level: 1, posInset: 1 }], + }, + ...Object.keys(map.data || {}).map((key) => ({ + rowId: `config-map-${index}-${key}`, + cells: [ + { + title: ( + <> + + {key} + + ), + posInset: 2, + level: 2, + }, + ], + })), + ]) + .flat(); + }, [agentClusterInstall.spec?.manifestsConfigMapRefs, configMaps]); + + return loaded ? ( + + + {customManifests.map((row, i) => ( + + {row.cells.map(({ title, ...props }, j) => ( + + ))} + + ))} + +
+ {title} +
+ ) : ( + + ); +}; From e4fdc91cded1db2362f509a1766a343a2c61a9ec Mon Sep 17 00:00:00 2001 From: jgyselov Date: Tue, 2 Sep 2025 09:01:17 +0200 Subject: [PATCH 7/7] Make 'Baremetal' the default external platform --- .../clusterDetails/ClusterDetailsFormFields.tsx | 2 -- libs/ui-lib/lib/cim/components/helpers/toAssisted.ts | 2 +- .../clusterConfiguration/ExternalPlatformsDropdown.tsx | 2 +- .../common/components/clusterWizard/clusterDetailsValidation.ts | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx index 348d19f75c..a304df2540 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx @@ -169,8 +169,6 @@ export const ClusterDetailsFormFields: React.FC = - {/** TODO: custom manifests checkbox here */} - {extensionAfter?.['openshiftVersion'] && extensionAfter['openshiftVersion']} {!isEditFlow && } {extensionAfter?.['pullSecret'] && extensionAfter['pullSecret']} diff --git a/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts b/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts index b7ab76e300..9f5ba37245 100644 --- a/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts +++ b/libs/ui-lib/lib/cim/components/helpers/toAssisted.ts @@ -180,7 +180,7 @@ export const getAICluster = ({ networkType: agentClusterInstall?.spec?.networking.networkType, controlPlaneCount: agentClusterInstall?.spec?.provisionRequirements.controlPlaneAgents || 3, platform: { - type: (agentClusterInstall?.spec?.platformType?.toLowerCase() || 'none') as PlatformType, + type: (agentClusterInstall?.spec?.platformType?.toLowerCase() || 'baremetal') as PlatformType, }, }; /* diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx index 5ca5a86e76..c0eb97b2bb 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/ExternalPlatformsDropdown.tsx @@ -22,7 +22,7 @@ export const ExternalPlatformsDropdown = ({ isDisabled }: { isDisabled: boolean const platforms = getPlatforms(t); const options = Object.entries(platforms) - .filter(([key]) => key !== 'baremetal') + .filter(([key]) => key !== 'none') .map(([platform, label]) => (