diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index ecc2fa5cd9..51f701d5a8 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -133,7 +133,6 @@ "ai:Bare metal host not found": "Bare metal host not found", "ai:Bare Metal Host related": "Bare Metal Host related", "ai:Base domain": "Base domain", - "ai:Base domain can only contain letters, digits, hyphens, and dots. Example: example.com": "Base domain can only contain letters, digits, hyphens, and dots. Example: example.com", "ai:Baseboard Management Controller (BMC)": "Baseboard Management Controller (BMC)", "ai:Baseboard Management Controller Address": "Baseboard Management Controller Address", "ai:Belongs to machine CIDR": "Belongs to machine CIDR", @@ -192,7 +191,6 @@ "ai:Cluster installation is still in-progress.": "Cluster installation is still in-progress.", "ai:Cluster installation process": "Cluster installation process", "ai:Cluster installation was cancelled": "Cluster installation was cancelled.", - "ai:Cluster must be created before configuring infrastructure environment": "Cluster must be created before configuring infrastructure environment", "ai:Cluster must have at least 3 hosts.": "Cluster must have at least 3 hosts.", "ai:Cluster name": "Cluster name", "ai:cluster network": "cluster network", @@ -225,7 +223,6 @@ "ai:Configure environment": "Configure environment", "ai:Configure host inventory settings": "Configure host inventory settings", "ai:Configure load balancer on Amazon Web Services for me.": "Configure load balancer on Amazon Web Services for me.", - "ai:Configure proxy settings": "Configure proxy settings", "ai:Configure the SSH key and proxy settings after the modal appears (optional).": "Configure the SSH key and proxy settings after the modal appears (optional).", "ai:Configure your own NTP sources to sychronize the time between the hosts that will be added to this infrastructure environment.": "Configure your own NTP sources to sychronize the time between the hosts that will be added to this infrastructure environment.", "ai:Configure your own NTP sources to synchronize the time between the hosts that will be added to this infrastructure environment.": "Configure your own NTP sources to synchronize the time between the hosts that will be added to this infrastructure environment.", @@ -363,11 +360,9 @@ "ai:Failed to add hosts to the cluster": "Failed to add hosts to the cluster", "ai:Failed to configure provisioning to enable registering hosts via BMC.": "Failed to configure provisioning to enable registering hosts via BMC.", "ai:Failed to create AgentServiceConfig": "Failed to create AgentServiceConfig", - "ai:Failed to create infrastructure environment": "Failed to create infrastructure environment", "ai:Failed to create IngressController": "Failed to create IngressController", "ai:Failed to delete host": "Failed to delete host", "ai:Failed to download the discovery Image": "Failed to download the discovery Image", - "ai:Failed to fetch cluster": "Failed to fetch cluster", "ai:Failed to fetch cluster credentials.": "Failed to fetch cluster credentials.", "ai:Failed to get Provisioning Configuration": "Failed to get Provisioning Configuration", "ai:Failed to load Credentials Download step": "Failed to load Credentials Download step", @@ -377,7 +372,6 @@ "ai:Failed to save configuration": "Failed to save configuration", "ai:Failed to save host selection.": "Failed to save host selection.", "ai:Failed to update host": "Failed to update host", - "ai:Failed to update infrastructure environment": "Failed to update infrastructure environment", "ai:Failed validations:": "Failed validations:", "ai:Failing infrastructure environment": "Failing infrastructure environment", "ai:Fence Agents Remediation requirements": "Fence Agents Remediation requirements", @@ -599,7 +593,6 @@ "ai:Minimum Memory": "Minimum Memory", "ai:Minimum memory for selected role": "Minimum memory for selected role", "ai:Minimum number of hosts": "Minimum number of hosts", - "ai:Missing cluster": "Missing cluster", "ai:Model": "Model", "ai:Modify your platform configuration to access your platform's features directly in OpenShift.": "Modify your platform configuration to access your platform's features directly in OpenShift.", "ai:More info for configure storage sizes": "More information for configure storage sizes", @@ -710,7 +703,6 @@ "ai:Operators": "Operators", "ai:Option 1: Add the following records to your DNS server (recommended)": "Option 1: Add the following records to your DNS server (recommended)", "ai:Option 2: Update your local /etc/hosts or /etc/resolv.conf files": "Option 2: Update your local /etc/hosts or /etc/resolv.conf files", - "ai:Optional configurations": "Optional configurations", "ai:Otherwise, the VMs will not be able to reboot during the installation process.": "Otherwise, the VMs will not be able to reboot during the installation process.", "ai:OVN separates the physical network topology from the logical one and is recommended if you're using new or telco features.": "OVN separates the physical network topology from the logical one and is recommended if you are using new or telco features.", "ai:Packet loss": "Packet loss", @@ -784,7 +776,6 @@ "ai:Removing {{name}} will remove the association with {{count}} host. These hosts will become available for other nodepools._plural": "Removing {{name}} will remove the association with {{count}} hosts. These hosts will become available for other nodepools.", "ai:Removing from cluster": "Removing from cluster", "ai:Rename hostnames using the custom template:": "Rename hostnames using the custom template:", - "ai:Rendezvous IP": "Rendezvous IP", "ai:Report a bug": "Report a bug", "ai:Required field": "Required field", "ai:Requirements for Two Node control plane OpenShift": "Requirements for Two Node control plane OpenShift", @@ -886,7 +877,6 @@ "ai:The following fields are invalid or missing": "The following fields are invalid or missing", "ai:The following requirement must be met:": "The following requirement must be met:", "ai:The following requirement must be met:_plural": "The following requirements must be met:", - "ai:The full cluster address [Cluster name].[Base domain] must be a valid DNS name (e.g. mycluster.example.com).": "The full cluster address [Cluster name].[Base domain] must be a valid DNS name (e.g. mycluster.example.com).", "ai:The generated discovery ISO will contain everything needed to boot.": "The generated discovery ISO will contain everything that is needed to boot.", "ai:The host has been discovered and needs to be approved to before further use.": "The host has been discovered and needs to be approved to before further use.", "ai:The host machine is powered on": "The host machine is powered on", @@ -898,7 +888,6 @@ "ai:The IP address cannot be a network or broadcast address": "The IP address cannot be a network or broadcast address", "ai:The IP address pool to use for service IP addresses. You can enter only one IP address pool. If you need to access the services from an external network, configure load balancers and routers to manage the traffic.": "The IP address pool to use for service IP addresses. You can enter only one IP address pool. If you need to access the services from an external network, configure load balancers and routers to manage the traffic.", "ai:The IP address pool used for service IP addresses.": "The IP address pool used for service IP addresses.", - "ai:The IP address that hosts will use to communicate with the bootstrap node during installation.": "The IP address that hosts will use to communicate with the bootstrap node during installation.", "ai:The MAC address of the host's network connected NIC that will be used to provision the host.": "The MAC address of the host's network connected NIC that will be used to provision the host.", "ai:The output displays the following:": "The output displays the following:", "ai:The primary Ingress and API IP addresses cannot be the same.": "The primary Ingress and API IP addresses cannot be the same.", diff --git a/libs/types/assisted-installer-service.d.ts b/libs/types/assisted-installer-service.d.ts index e13a478270..c23ae117a5 100644 --- a/libs/types/assisted-installer-service.d.ts +++ b/libs/types/assisted-installer-service.d.ts @@ -1806,7 +1806,7 @@ export interface ImageInfo { staticNetworkConfig?: string; type?: ImageType; } -export type ImageType = 'full-iso' | 'minimal-iso' | 'disconnected-iso'; +export type ImageType = 'full-iso' | 'minimal-iso'; export interface ImportClusterParams { /** * OpenShift cluster name. @@ -1913,10 +1913,6 @@ export interface InfraEnv { * certificates in this bundle. */ additionalTrustBundle?: string; - /** - * The pull secret obtained from Red Hat OpenShift Cluster Manager at console.redhat.com/openshift/install/pull-secret. - */ - pullSecret?: string; } export interface InfraEnvCreateParams { /** diff --git a/libs/ui-lib/lib/common/api/assisted-service/ClustersAPI.ts b/libs/ui-lib/lib/common/api/assisted-service/ClustersAPI.ts index 5a9ccc8ae4..18ddb622f0 100644 --- a/libs/ui-lib/lib/common/api/assisted-service/ClustersAPI.ts +++ b/libs/ui-lib/lib/common/api/assisted-service/ClustersAPI.ts @@ -245,12 +245,6 @@ const ClustersAPI = { headers: { 'Content-Type': 'application/json' }, }); }, - registerDisconnected(params: { name: string; openshiftVersion: string }) { - return client.post, { name: string; openshiftVersion: string }>( - `${ClustersAPI.makeBaseURI()}disconnected`, - params, - ); - }, }; export default ClustersAPI; diff --git a/libs/ui-lib/lib/common/components/clusterWizard/clusterDetailsValidation.ts b/libs/ui-lib/lib/common/components/clusterWizard/clusterDetailsValidation.ts index cbe0e78ef0..5476f2b8d6 100644 --- a/libs/ui-lib/lib/common/components/clusterWizard/clusterDetailsValidation.ts +++ b/libs/ui-lib/lib/common/components/clusterWizard/clusterDetailsValidation.ts @@ -10,25 +10,12 @@ import { TangServer } from '../clusterConfiguration/DiskEncryptionFields/DiskEnc import { getDefaultOpenShiftVersion } from '../ui'; import { baseDomainValidationSchema, - isValidFullClusterAddress, + dnsNameValidationSchema, nameValidationSchema, pullSecretValidationSchema, } from '../../validationSchemas'; import { ClusterDetailsValues } from './types'; -const fullClusterAddressTest = (t: TFunction) => ({ - name: 'full-cluster-address', - message: t( - 'ai:The full cluster address [Cluster name].[Base domain] must be a valid DNS name (e.g. mycluster.example.com).', - ), - test: (values: { name?: string; baseDnsDomain?: string } | undefined) => { - const name = values?.name?.trim() ?? ''; - const base = values?.baseDnsDomain?.trim() ?? ''; - if (!name || !base) return true; - return isValidFullClusterAddress(`${name}.${base}`); - }, -}); - const emptyTangServers = (): TangServer[] => { return [ { @@ -111,7 +98,6 @@ export const getClusterDetailsValidationSchema = ({ t: TFunction; }) => Yup.lazy((values: { baseDnsDomain: string; isSNODevPreview: boolean }) => { - const fullAddressTest = fullClusterAddressTest(t); if (pullSecretSet) { return Yup.object({ name: nameValidationSchema( @@ -121,8 +107,10 @@ export const getClusterDetailsValidationSchema = ({ validateUniqueName, isOcm, ), - baseDnsDomain: baseDomainValidationSchema(t).required(t('ai:Required field')), - }).test(fullAddressTest.name, fullAddressTest.message, fullAddressTest.test); + baseDnsDomain: isOcm + ? baseDomainValidationSchema(t).required(t('ai:Required field')) + : dnsNameValidationSchema(t).required(t('ai:Required field')), + }); } return Yup.object({ name: nameValidationSchema( @@ -132,7 +120,9 @@ export const getClusterDetailsValidationSchema = ({ validateUniqueName, isOcm, ), - baseDnsDomain: baseDomainValidationSchema(t).required(t('ai:Required field')), + baseDnsDomain: isOcm + ? baseDomainValidationSchema(t).required(t('ai:Required field')) + : dnsNameValidationSchema(t).required(t('ai:Required field')), pullSecret: pullSecretValidationSchema(t).required(t('ai:Required field')), diskEncryptionTangServers: Yup.array().when('diskEncryptionMode', { is: (diskEncryptionMode: DiskEncryption['mode']) => { @@ -150,5 +140,5 @@ export const getClusterDetailsValidationSchema = ({ }), ), }), - }).test(fullAddressTest.name, fullAddressTest.message, fullAddressTest.test); + }); }); diff --git a/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx b/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx index 7b5b212d84..0809b30213 100644 --- a/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx +++ b/libs/ui-lib/lib/common/components/ui/OpenShiftVersionModal.tsx @@ -51,9 +51,7 @@ export const OpenShiftVersionModal = ({ key="select-custom-ocp" variant={ButtonVariant.primary} onClick={() => { - if (values.customOpenshiftSelect !== null) { - setFieldValue('openshiftVersion', values.customOpenshiftSelect); - } + setFieldValue('openshiftVersion', values.customOpenshiftSelect); onClose(); }} > diff --git a/libs/ui-lib/lib/common/components/ui/WizardFooter.tsx b/libs/ui-lib/lib/common/components/ui/WizardFooter.tsx index 54f2b6871f..b4bae5e9bc 100644 --- a/libs/ui-lib/lib/common/components/ui/WizardFooter.tsx +++ b/libs/ui-lib/lib/common/components/ui/WizardFooter.tsx @@ -69,7 +69,7 @@ export const WizardFooter: React.FC = ({ variant={ButtonVariant.primary} name="next" onClick={onNext} - isDisabled={isNextDisabled || isSubmitting} + isDisabled={isNextDisabled} isLoading={isNextButtonLoading} > {nextButtonText || t('ai:Next')} diff --git a/libs/ui-lib/lib/common/components/ui/formik/PullSecretField.tsx b/libs/ui-lib/lib/common/components/ui/formik/PullSecretField.tsx index dfcf5462f6..77dd0a308e 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/PullSecretField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/PullSecretField.tsx @@ -67,7 +67,6 @@ const GetPullSecretHelperText: React.FC<{ isOcm: boolean }> = ({ isOcm }) => { const PullSecretField: React.FC<{ isOcm: boolean }> = ({ isOcm }) => { const { t } = useTranslation(); - return ( machineNetworks: t('ai:Machine networks'), clusterNetworks: t('ai:Cluster networks'), serviceNetworks: t('ai:Service networks'), - rendezvousIp: t('ai:Rendezvous IP'), }); export const hostValidationGroupLabels = ( @@ -394,7 +393,6 @@ export const OPERATOR_NAME_METALLB = 'metallb'; export const singleClusterOperators = [ OPERATOR_NAME_CNV, - OPERATOR_NAME_LSO, OPERATOR_NAME_MTV, OPERATOR_NAME_NMSTATE, OPERATOR_NAME_NODE_HEALTHCHECK, @@ -404,9 +402,6 @@ export const singleClusterOperators = [ OPERATOR_NAME_CLUSTER_OBSERVABILITY, OPERATOR_NAME_LOKI, OPERATOR_NAME_OPENSHIFT_LOGGING, - OPERATOR_NAME_NUMA_RESOURCES, - OPERATOR_NAME_OADP, - OPERATOR_NAME_METALLB, ]; export const singleClusterBundles = ['virtualization']; diff --git a/libs/ui-lib/lib/common/validationSchemas/addressValidation.tsx b/libs/ui-lib/lib/common/validationSchemas/addressValidation.tsx index a8b1467a2c..374ee4d505 100644 --- a/libs/ui-lib/lib/common/validationSchemas/addressValidation.tsx +++ b/libs/ui-lib/lib/common/validationSchemas/addressValidation.tsx @@ -29,10 +29,7 @@ export const ipNoSuffixValidationSchema = (t: TFunction) => export const macAddressValidationSchema = (t: TFunction) => Yup.string().matches(MAC_REGEX, { - message: (params: { value?: unknown }) => - t('ai:Value "{{value}}" is not valid MAC address.', { - value: params?.value !== undefined ? String(params.value) : '', - }), + message: (value) => t('ai:Value "{{value}}" is not valid MAC address.', { value }), excludeEmptyString: true, }); diff --git a/libs/ui-lib/lib/common/validationSchemas/clusterDetailsValidation.tsx b/libs/ui-lib/lib/common/validationSchemas/clusterDetailsValidation.tsx index c6d1037255..55dde9bcfe 100644 --- a/libs/ui-lib/lib/common/validationSchemas/clusterDetailsValidation.tsx +++ b/libs/ui-lib/lib/common/validationSchemas/clusterDetailsValidation.tsx @@ -123,55 +123,23 @@ export const dnsNameValidationSchema = (t: TFunction) => excludeEmptyString: true, }); -const MAX_DNS_NAME_LENGTH = 253; -/** Only letters, digits, hyphen, and dot (valid DNS/hostname characters). */ -const DNS_CHARS_REGEX = /^[a-z0-9.-]+$/i; - -/** - * Validates the full cluster address [clusterName].[baseDomain] as a single DNS/hostname - * (e.g. "doma.ca" is valid even though "ca" alone might not pass standalone base-domain rules). - */ -export const isValidFullClusterAddress = (full: string): boolean => { - if (!full || full === '.' || /\s/.test(full)) { - return false; - } - if (full.length > MAX_DNS_NAME_LENGTH) { - return false; - } - if (!DNS_CHARS_REGEX.test(full)) { - return false; - } - const labels = full.split('.'); - if (labels.some((label) => label.length === 0 || label.length > 63)) { - return false; - } - return HOST_NAME_REGEX.test(full); -}; - export const baseDomainValidationSchema = (t: TFunction) => - Yup.string() - .test( - 'dns-name-label-length', - t( - 'ai:Every single host component in the base domain name cannot contain more than 63 characters and must not contain spaces.', - ), - (value?: string) => { - // Check if the value contains any spaces - if (/\s/.test(value as string)) { - return false; // Value contains spaces, validation fails - } + Yup.string().test( + 'dns-name-label-length', + t( + 'ai:Every single host component in the base domain name cannot contain more than 63 characters and must not contain spaces.', + ), + (value?: string) => { + // Check if the value contains any spaces + if (/\s/.test(value as string)) { + return false; // Value contains spaces, validation fails + } - // Check the label lengths - const labels = (value || '').split('.'); - return labels.every((label: string) => label.length <= 63); - }, - ) - .matches(NAME_CHARS_REGEX, { - message: t( - 'ai:Base domain can only contain letters, digits, hyphens, and dots. Example: example.com', - ), - excludeEmptyString: true, - }); + // Check the label lengths + const labels = (value || '').split('.'); + return labels.every((label: string) => label.length <= 63); + }, + ); export const locationValidationSchema = (t: TFunction) => Yup.string() diff --git a/libs/ui-lib/lib/ocm/components/Routes.tsx b/libs/ui-lib/lib/ocm/components/Routes.tsx index f821eae63a..69855b063d 100644 --- a/libs/ui-lib/lib/ocm/components/Routes.tsx +++ b/libs/ui-lib/lib/ocm/components/Routes.tsx @@ -34,9 +34,7 @@ export const UILibRoutes = ({ <> }> - }> - } /> - + } /> } /> } /> diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx index fa7e2f1ff0..3ac9cd21b7 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx @@ -20,7 +20,6 @@ export const discoveryImageTypes: Record = { 'minimal-iso': 'Minimal image file - Download an ISO that fetches content on boot', 'full-iso': 'Full image file - Download a self-contained ISO', 'discovery-image-ipxe': 'iPXE - Provision from your network server', - 'disconnected-iso': 'Disconnected ISO - Provision from a local file', }; type DiscoveryImageTypeDropdownProps = { diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx index 4cd77a1248..80d27a9a49 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx @@ -16,6 +16,7 @@ import { Formik, FormikHelpers } from 'formik'; import { TFunction } from 'i18next'; import { HostStaticNetworkConfig, + ImageType, InfraEnv, Proxy, } from '@openshift-assisted/types/assisted-installer-service'; @@ -71,7 +72,7 @@ type OcmDiscoveryImageConfigFormProps = Proxy & { formikActions: FormikHelpers, ) => Promise; sshPublicKey?: string; - imageType?: DiscoveryImageType; + imageType?: ImageType; isIpxeSelected?: boolean; enableCertificate?: boolean; trustBundle?: InfraEnv['additionalTrustBundle']; @@ -105,7 +106,7 @@ export const OcmDiscoveryImageConfigForm = ({ httpsProxy: httpsProxy || '', noProxy: noProxy || '', enableProxy: !!(httpProxy || httpsProxy || noProxy), - imageType: imageType, + imageType: imageTypeValue as ImageType, enableCertificate: enableCertificate || false, trustBundle: trustBundle || '', }; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/commonValidationSchemas.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/commonValidationSchemas.tsx index 41ae0aa2b9..ad5cb633cb 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/commonValidationSchemas.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/commonValidationSchemas.tsx @@ -92,17 +92,11 @@ export const getIpAddressValidationSchema = (protocolVersion: ProtocolVersion) = const protocolVersionLabel = protocolVersion === ProtocolVersion.ipv4 ? 'IPv4' : 'IPv6'; return Yup.string().test( protocolVersion, - (params: { value?: unknown }) => - `Value "${ - params?.value !== undefined ? String(params.value) : '' - }" is not a valid ${protocolVersionLabel} address`, + `Value \${value} is not a valid ${protocolVersionLabel} address`, (value?: string) => { - if (value === undefined || value === '') { + if (!value) { return true; } - if (typeof value !== 'string') { - return false; - } return isValidAddress(value, protocolVersion); }, ); diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx index 5c910d0a79..ae9ad14184 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx @@ -96,7 +96,6 @@ const getExpandedHostComponent = (protocolType: StaticProtocolType) => { label={`IP address (${getProtocolVersionLabel(protocolVersion)})`} fieldId={getFieldId(`${fieldName}.ips.${protocolVersion}`, 'input')} key={protocolVersion} - isRequired > = ({ infraEnv, ... setFormProps({ infraEnv, ...props, - validationSchema: getNetworkWideValidationSchema(infraEnv.rendezvousIp), + validationSchema: networkWideValidationSchema, getInitialValues: (infraEnv: InfraEnv) => { return getFormViewNetworkWideValues(infraEnv); }, diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.tsx index 86091cff4f..07d5fe3da7 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.tsx @@ -95,71 +95,32 @@ const getAddressDataValidationSchema = (protocolVersion: ProtocolVersion, ipConf }); }; -/** - * Validates that Rendezvous IP (when set) is within the IPv4 subnet. - * Default gateway is already validated by getAddressDataValidationSchema (getInMachineNetworkValidationSchema). - */ -const getRendezvousIpCoherenceTest = (rendezvousIp: string | undefined) => { - return Yup.object().test( - 'rendezvous-ip-in-subnet', - 'Rendezvous IP must be within the IPv4 subnet.', - (value: unknown, context: Yup.TestContext) => { - const values = value as FormViewNetworkWideValues | undefined; - const machineNetwork = values?.ipConfigs?.ipv4?.machineNetwork; - if ( - !rendezvousIp?.trim() || - !machineNetwork?.ip?.trim() || - machineNetwork.prefixLength === '' || - typeof machineNetwork.prefixLength !== 'number' - ) { - return true; - } - const inSubnetSchema = getInMachineNetworkValidationSchema( - ProtocolVersion.ipv4, - machineNetwork, - ); - if (!inSubnetSchema.isValidSync(rendezvousIp)) { - return context.createError({ - path: 'ipConfigs.ipv4.machineNetwork.ip', - message: 'Rendezvous IP must be within the IPv4 subnet.', - }); - } - return true; - }, - ); -}; - -export const getNetworkWideValidationSchema = (rendezvousIp?: string) => - Yup.lazy((values: FormViewNetworkWideValues) => { - const ipConfigsValidationSchemas = Yup.object({ - ipv4: getAddressDataValidationSchema(ProtocolVersion.ipv4, values.ipConfigs.ipv4), - ipv6: showProtocolVersion(values.protocolType, ProtocolVersion.ipv6) - ? getAddressDataValidationSchema(ProtocolVersion.ipv6, values.ipConfigs.ipv6) - : Yup.object(), - }); - - const baseSchema = Yup.object({ - useVlan: Yup.boolean(), - vlanId: Yup.mixed().when('useVlan', { - is: (useVlan: boolean) => useVlan, - then: () => - Yup.number() - .required(MUST_BE_A_NUMBER) - .min(1, `Must be more than or equal to 1`) - .max(MAX_VLAN_ID, `Must be less than or equal to ${MAX_VLAN_ID}`) - .test('not-number', MUST_BE_A_NUMBER, () => validateNumber(values.vlanId)) - .nullable() - .transform(transformNumber) as Yup.NumberSchema, - }), - protocolType: Yup.string(), - dns: getDNSValidationSchema(values.protocolType), - ipConfigs: ipConfigsValidationSchemas, - }); - - return baseSchema.concat(getRendezvousIpCoherenceTest(rendezvousIp)); +export const networkWideValidationSchema = Yup.lazy((values: FormViewNetworkWideValues) => { + const ipConfigsValidationSchemas = Yup.object({ + ipv4: getAddressDataValidationSchema(ProtocolVersion.ipv4, values.ipConfigs.ipv4), + ipv6: showProtocolVersion(values.protocolType, ProtocolVersion.ipv6) + ? getAddressDataValidationSchema(ProtocolVersion.ipv6, values.ipConfigs.ipv6) + : Yup.object(), }); -export const networkWideValidationSchema = getNetworkWideValidationSchema(); + return Yup.object({ + useVlan: Yup.boolean(), + vlanId: Yup.mixed().when('useVlan', { + is: (useVlan: boolean) => useVlan, + then: () => + Yup.number() + .required(MUST_BE_A_NUMBER) + .min(1, `Must be more than or equal to 1`) + .max(MAX_VLAN_ID, `Must be less than or equal to ${MAX_VLAN_ID}`) + .test('not-number', MUST_BE_A_NUMBER, () => validateNumber(values.vlanId)) + .nullable() + .transform(transformNumber) as Yup.NumberSchema, + }), + protocolType: Yup.string(), + dns: getDNSValidationSchema(values.protocolType), + ipConfigs: ipConfigsValidationSchemas, + }); +}); export const validateNumber = (vlanId: FormViewNetworkWideValues['vlanId']) => { //We need to validate that value is a number(without letters) and is not an exponential number (ex: 1e2) diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterDetails.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterDetails.tsx index b59a54e2ff..eca61df28b 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterDetails.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterDetails.tsx @@ -39,6 +39,7 @@ const ClusterDetails = ({ cluster, infraEnv }: ClusterDetailsProps) => { const navigate = useNavigate(); const dispatch = useDispatch(); const { usedClusterNames } = useUsedClusterNames(cluster?.id || ''); + const pullSecret = usePullSecret(); const { error: errorOCPVersions, loading: loadingOCPVersions, @@ -46,8 +47,6 @@ const ClusterDetails = ({ cluster, infraEnv }: ClusterDetailsProps) => { } = useOpenShiftVersionsContext(); const location = useLocation(); const isSingleClusterFeatureEnabled = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE'); - const defaultPullSecret = usePullSecret(); - const pullSecret = isSingleClusterFeatureEnabled ? infraEnv?.pullSecret || '' : defaultPullSecret; const handleClusterUpdate = React.useCallback( async (clusterId: Cluster['id'], params: ClusterDetailsUpdateParams) => { diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContext.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContext.tsx index b196aca486..d18483a82f 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContext.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContext.tsx @@ -3,7 +3,6 @@ import { HostsNetworkConfigurationType } from '../../services'; import { StaticIpView } from '../clusterConfiguration/staticIp/data/dataTypes'; import { ClusterWizardStepsType } from './wizardTransition'; import { UISettingsValues } from '../../../common'; -import { InfraEnv } from '@openshift-assisted/types/assisted-installer-service'; export type ClusterWizardContextType = { currentStepId: ClusterWizardStepsType; @@ -19,8 +18,6 @@ export type ClusterWizardContextType = { uiSettings?: UISettingsValues; installDisconnected: boolean; setInstallDisconnected: (enabled: boolean) => void; - disconnectedInfraEnv?: InfraEnv; - setDisconnectedInfraEnv: (infraEnv: InfraEnv | undefined) => void; }; export const ClusterWizardContext = React.createContext(null); diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx index c9b6c70959..6e0c1e5e0e 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx @@ -72,33 +72,6 @@ const getWizardStepIds = ( return stepsCopy; }; -// Same logic as getWizardStepIds but for disconnected flow -// Adds static IP steps after 'disconnected-optional-configurations' instead of 'cluster-details' -const getDisconnectedWizardStepIds = ( - wizardStepIds: ClusterWizardStepsType[] | undefined, - staticIpView?: StaticIpView | 'dhcp-selected', -): ClusterWizardStepsType[] => { - let stepsCopy = wizardStepIds ? [...wizardStepIds] : [...disconnectedSteps]; - if (staticIpView === StaticIpView.YAML) { - stepsCopy = removeStepFromClusterWizard(stepsCopy, 'static-ip-network-wide-configurations', 2); - stepsCopy = addStepToClusterWizard(stepsCopy, 'disconnected-optional-configurations', [ - 'static-ip-yaml-view', - ]); - } else if (staticIpView === StaticIpView.FORM) { - stepsCopy = removeStepFromClusterWizard(stepsCopy, 'static-ip-yaml-view', 1); - stepsCopy = addStepToClusterWizard( - stepsCopy, - 'disconnected-optional-configurations', - staticIpFormViewSubSteps, - ); - } else if (staticIpView === 'dhcp-selected') { - stepsCopy = removeStepFromClusterWizard(stepsCopy, 'static-ip-network-wide-configurations', 2); - stepsCopy = removeStepFromClusterWizard(stepsCopy, 'static-ip-yaml-view', 1); - } - - return stepsCopy; -}; - const ClusterWizardContextProvider = ({ children, cluster, @@ -112,13 +85,8 @@ const ClusterWizardContextProvider = ({ const isSingleClusterFeatureEnabled = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE'); const [currentStepId, setCurrentStepId] = React.useState(); const [connectedWizardStepIds, setWizardStepIds] = React.useState(); - const [disconnectedWizardStepIds, setDisconnectedWizardStepIds] = - React.useState(disconnectedSteps); const [wizardPerPage, setWizardPerPage] = React.useState(10); const [installDisconnected, setInstallDisconnected] = React.useState(false); - const [disconnectedInfraEnv, setDisconnectedInfraEnv] = React.useState( - infraEnv, - ); const location = useLocation(); const locationState = location.state as ClusterWizardFlowStateType | undefined; const { @@ -130,7 +98,7 @@ const ClusterWizardContextProvider = ({ const { clearAlerts, addAlert, alerts } = useAlerts(); const setClusterPermissions = useSetClusterPermissions(); - const wizardStepIds = installDisconnected ? disconnectedWizardStepIds : connectedWizardStepIds; + const wizardStepIds = installDisconnected ? disconnectedSteps : connectedWizardStepIds; React.useEffect(() => { if (!UISettingsLoading) { @@ -190,29 +158,18 @@ const ClusterWizardContextProvider = ({ const handleMoveFromStaticIp = () => { //if static ip view change wasn't persisted, moving from static ip step should change the wizard steps to match the view in the infra env - const currentInfraEnv = installDisconnected ? disconnectedInfraEnv : infraEnv; - const staticIpInfo = currentInfraEnv ? getStaticIpInfo(currentInfraEnv) : undefined; + const staticIpInfo = infraEnv ? getStaticIpInfo(infraEnv) : undefined; if (!staticIpInfo) { throw `Wizard step is currently ${currentStepId}, but no static ip info is defined`; } - if (installDisconnected) { - // For disconnected wizard, update wizard steps directly (same pattern as connected) - const newStepIds = getDisconnectedWizardStepIds( - disconnectedWizardStepIds, - staticIpInfo.view, - ); - setDisconnectedWizardStepIds(newStepIds); - } else { - // For connected wizard, update wizard steps directly - const newStepIds = getWizardStepIds( - wizardStepIds, - staticIpInfo.view, + const newStepIds = getWizardStepIds( + wizardStepIds, + staticIpInfo.view, - isSingleClusterFeatureEnabled, - ); - setWizardStepIds(newStepIds); - } + isSingleClusterFeatureEnabled, + ); + setWizardStepIds(newStepIds); }; const onSetCurrentStepId = (stepId: ClusterWizardStepsType) => { @@ -244,43 +201,22 @@ const ClusterWizardContextProvider = ({ } else { setCurrentStepId('static-ip-network-wide-configurations'); } - if (installDisconnected) { - // For disconnected wizard, update wizard steps (same pattern as connected) - setDisconnectedWizardStepIds( - getDisconnectedWizardStepIds(disconnectedWizardStepIds, view), - ); - } else { - setWizardStepIds(getWizardStepIds(wizardStepIds, view, isSingleClusterFeatureEnabled)); - } + setWizardStepIds(getWizardStepIds(wizardStepIds, view, isSingleClusterFeatureEnabled)); }, onUpdateHostNetworkConfigType(type: HostsNetworkConfigurationType): void { - if (installDisconnected) { - // For disconnected wizard, update wizard steps (same pattern as connected) - if (type === HostsNetworkConfigurationType.STATIC) { - setDisconnectedWizardStepIds( - getDisconnectedWizardStepIds(disconnectedWizardStepIds, StaticIpView.FORM), - ); - } else { - setDisconnectedWizardStepIds( - getDisconnectedWizardStepIds(disconnectedWizardStepIds, 'dhcp-selected'), - ); - } - } else { - // For connected wizard, update wizard steps directly - if (type === HostsNetworkConfigurationType.STATIC) { - setWizardStepIds( - getWizardStepIds( - wizardStepIds, - StaticIpView.FORM, + if (type === HostsNetworkConfigurationType.STATIC) { + setWizardStepIds( + getWizardStepIds( + wizardStepIds, + StaticIpView.FORM, - isSingleClusterFeatureEnabled, - ), - ); - } else { - setWizardStepIds( - getWizardStepIds(wizardStepIds, 'dhcp-selected', isSingleClusterFeatureEnabled), - ); - } + isSingleClusterFeatureEnabled, + ), + ); + } else { + setWizardStepIds( + getWizardStepIds(wizardStepIds, 'dhcp-selected', isSingleClusterFeatureEnabled), + ); } }, wizardStepIds: wizardStepIds, @@ -299,8 +235,6 @@ const ClusterWizardContextProvider = ({ connectedWizardStepIds?.length && onSetCurrentStepId(connectedWizardStepIds[0]); } }, - disconnectedInfraEnv, - setDisconnectedInfraEnv, }; }, [ wizardStepIds, @@ -313,8 +247,6 @@ const ClusterWizardContextProvider = ({ updateUISettings, installDisconnected, connectedWizardStepIds, - disconnectedWizardStepIds, - disconnectedInfraEnv, ]); if (!contextValue) { diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardFooter.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardFooter.tsx index c714ea4073..1925c56de7 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardFooter.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardFooter.tsx @@ -17,8 +17,6 @@ import { onFetchEvents } from '../fetching/fetchEvents'; import { Cluster } from '@openshift-assisted/types/assisted-installer-service'; import { useFeature } from '../../hooks/use-feature'; import { useModalDialogsContext } from '../hosts/ModalDialogsContext'; -import ClustersService from '../../services/ClustersService'; -import { handleApiError, getApiErrorMessage } from '../../../common/api'; type ClusterValidationSectionProps = { cluster?: Cluster; @@ -73,7 +71,6 @@ type ClusterWizardFooterProps = WizardFooterGenericProps & { errorFields?: string[]; alertTitle?: string; alertContent?: string | null; - disconnectedClusterId?: string; }; const ClusterWizardFooter = ({ @@ -83,37 +80,19 @@ const ClusterWizardFooter = ({ alertTitle, alertContent, onCancel, - disconnectedClusterId, ...rest }: ClusterWizardFooterProps) => { - const { alerts, addAlert } = useAlerts(); + const { alerts } = useAlerts(); const navigate = useNavigate(); const isSingleClusterFeatureEnabled = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE'); const { currentStepId } = useClusterWizardContext(); const { resetSingleClusterDialog } = useModalDialogsContext(); - const { setDisconnectedInfraEnv } = useClusterWizardContext(); - const handleCancel = React.useCallback(async () => { - if (disconnectedClusterId) { - try { - await ClustersService.remove(disconnectedClusterId); - } catch (e) { - handleApiError(e, () => - addAlert({ - title: 'Failed to remove cluster', - message: getApiErrorMessage(e), - }), - ); - } - } - setDisconnectedInfraEnv(undefined); - navigate('/cluster-list'); - }, [navigate, setDisconnectedInfraEnv, addAlert, disconnectedClusterId]); + const handleCancel = React.useCallback(() => navigate('/cluster-list'), [navigate]); - const handleReset = React.useCallback( - () => resetSingleClusterDialog.open({ cluster }), - [resetSingleClusterDialog, cluster], - ); + const handleReset = React.useCallback(() => { + resetSingleClusterDialog.open({ cluster }); + }, [resetSingleClusterDialog, cluster]); const alertsSection = alerts.length ? : undefined; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx index 7891e2edc5..a2f3943d12 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx @@ -4,10 +4,7 @@ import ClusterDetails from './ClusterDetails'; import { useClusterWizardContext } from './ClusterWizardContext'; import ReviewStep from './disconnected/ReviewStep'; import BasicStep from './disconnected/BasicStep'; -import OptionalConfigurationsStep from './disconnected/OptionalConfigurationsStep'; -import DisconnectedStaticIp from './disconnected/DisconnectedStaticIp'; import { ClusterWizardStepsType } from './wizardTransition'; -import { ModalDialogsContextProvider } from '../hosts/ModalDialogsContext'; const getCurrentStep = (currentStepId: ClusterWizardStepsType) => { switch (currentStepId) { @@ -15,12 +12,6 @@ const getCurrentStep = (currentStepId: ClusterWizardStepsType) => { return ; case 'disconnected-basic': return ; - case 'disconnected-optional-configurations': - return ; - case 'static-ip-yaml-view': - case 'static-ip-network-wide-configurations': - case 'static-ip-host-configurations': - return ; default: return ; } @@ -29,11 +20,9 @@ const getCurrentStep = (currentStepId: ClusterWizardStepsType) => { const NewClusterWizard: React.FC = () => { const { currentStepId } = useClusterWizardContext(); return ( - -
- {getCurrentStep(currentStepId)} -
-
+
+ {getCurrentStep(currentStepId)} +
); }; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts b/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts index 12f66bed8a..82a38edf93 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts @@ -14,7 +14,6 @@ export const wizardStepNames: { [key in ClusterWizardStepsType]: string } = { 'credentials-download': 'Download credentials', 'disconnected-review': 'Review and download ISO', 'disconnected-basic': 'Basic information', - 'disconnected-optional-configurations': 'Optional configurations', }; export const defaultWizardSteps: ClusterWizardStepsType[] = [ diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx index 7ac86f3cad..04d8e0b6ab 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx @@ -1,168 +1,68 @@ import * as React from 'react'; import { ClusterWizardStep, - TechnologyPreview, + DeveloperPreview, ExternalLink, OCP_RELEASES_PAGE, StaticTextField, useTranslation, } from '../../../../common'; -import { Flex, Grid, GridItem, Form, Content, Spinner } from '@patternfly/react-core'; +import { Split, SplitItem, Grid, GridItem, Form, Content } from '@patternfly/react-core'; import OcmOpenShiftVersion from '../../clusterConfiguration/OcmOpenShiftVersion'; import { useClusterWizardContext } from '../ClusterWizardContext'; import ClusterWizardFooter from '../ClusterWizardFooter'; import ClusterWizardNavigation from '../ClusterWizardNavigation'; import { WithErrorBoundary } from '../../../../common/components/ErrorHandling/WithErrorBoundary'; import InstallDisconnectedSwitch from './InstallDisconnectedSwitch'; -import { Formik, useFormikContext } from 'formik'; -import ClustersService from '../../../services/ClustersService'; -import { handleApiError, getApiErrorMessage } from '../../../../common/api'; -import { useAlerts } from '../../../../common/components/AlertsContextProvider'; -import { AlertVariant } from '@patternfly/react-core'; -import { ClusterWizardFlowStateNew } from '../wizardTransition'; -import { useLocation, useNavigate, useParams } from 'react-router-dom-v5-compat'; -import { AxiosResponse } from 'axios'; -import { Cluster } from '@openshift-assisted/types/assisted-installer-service'; -import OcmOpenShiftVersionSelect from '../../clusterConfiguration/OcmOpenShiftVersionSelect'; +import { Formik } from 'formik'; -type BasicStepFormikValues = { - openshiftVersion: string; - customOpenshiftSelect: string | null; -}; - -const BasicStepForm = () => { - const { clusterId } = useParams<{ clusterId: string }>(); - const [isLoading, setIsLoading] = React.useState(true); - const [cluster, setCluster] = React.useState(); - - const { values } = useFormikContext(); +const BasicStep = () => { const { t } = useTranslation(); const { moveNext } = useClusterWizardContext(); - const { addAlert } = useAlerts(); - const [isSubmitting, setIsSubmitting] = React.useState(false); - const navigate = useNavigate(); - const location = useLocation(); - const currentPath = location.pathname; - - React.useEffect(() => { - if (!clusterId) { - setIsLoading(false); - } else { - void (async () => { - setIsLoading(true); - try { - setCluster(await ClustersService.get(clusterId)); - } catch (error) { - handleApiError(error, () => { - addAlert({ - title: 'Failed to fetch disconnected cluster', - message: getApiErrorMessage(error), - variant: AlertVariant.danger, - }); - }); - } finally { - setIsLoading(false); - } - })(); - } - }, [clusterId, addAlert]); - - const createCluster = async () => { - try { - setIsSubmitting(true); - // Create cluster only - infraEnv will be created in OptionalConfigurationsStep - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data: disconnectedCluster }: AxiosResponse = - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await ClustersService.registerDisconnected({ - name: 'disconnected-cluster', - openshiftVersion: values.openshiftVersion, - }); - navigate(`${currentPath}/${disconnectedCluster.id}`, { - state: ClusterWizardFlowStateNew, - }); - moveNext(); - } catch (error: unknown) { - handleApiError(error, () => { - addAlert({ - title: 'Failed to create disconnected cluster', - message: getApiErrorMessage(error), - variant: AlertVariant.danger, - }); - }); - } finally { - setIsSubmitting(false); - } - }; return ( - } - footer={ - { - clusterId ? moveNext() : void createCluster(); - }} - isSubmitting={isSubmitting} - isNextDisabled={isLoading || (cluster ? false : !values.openshiftVersion)} - disconnectedClusterId={clusterId} - /> - } + { + // nothing to do + }} > - - - - Basic information - - - - - - {t("ai:I'm installing on a disconnected/air-gapped/secured environment")} - - - -
- {isLoading ? ( - - ) : cluster ? ( - + } + footer={} + > + + + + + + Basic information + + + + + + + + + + + + {t('ai:Learn more about OpenShift releases')} - ) : ( - - )} - - x86_64 - - - - - - - ); -}; - -const BasicStep = () => { - return ( - - initialValues={{ - openshiftVersion: '', - customOpenshiftSelect: null, - }} - onSubmit={() => { - // nothing to do - }} - > - + + x86_64 + + +
+
+
+
); }; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/DisconnectedStaticIp.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/DisconnectedStaticIp.tsx deleted file mode 100644 index 86e4e1068b..0000000000 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/DisconnectedStaticIp.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import { ClusterWizardStep, getFormikErrorFields, useAlerts } from '../../../../common'; -import { useClusterWizardContext } from '../ClusterWizardContext'; -import ClusterWizardFooter from '../ClusterWizardFooter'; -import ClusterWizardNavigation from '../ClusterWizardNavigation'; -import { StaticIpFormState } from '../../clusterConfiguration/staticIp/components/propTypes'; -import { StaticIpPage } from '../../clusterConfiguration/staticIp/components/StaticIpPage'; -import { WithErrorBoundary } from '../../../../common/components/ErrorHandling/WithErrorBoundary'; -import { InfraEnvsAPI } from '../../../services/apis'; -import { InfraEnvUpdateParams } from '@openshift-assisted/types/assisted-installer-service'; - -const getInitialFormStateProps = (): StaticIpFormState => { - return { - isValid: true, - isSubmitting: false, - isAutoSaveRunning: false, - errors: {}, - touched: {}, - isEmpty: true, - }; -}; - -const DisconnectedStaticIp: React.FC = () => { - const { moveNext, moveBack, disconnectedInfraEnv, setDisconnectedInfraEnv } = - useClusterWizardContext(); - const { alerts } = useAlerts(); - const [formState, setFormStateProps] = React.useState( - getInitialFormStateProps(), - ); - - const onFormStateChange = (formState: StaticIpFormState) => { - setFormStateProps(formState); - }; - - const updateInfraEnv = async (params: InfraEnvUpdateParams) => { - if (!disconnectedInfraEnv?.id) { - throw new Error('No disconnected infraEnv available'); - } - const { data: updatedInfraEnv } = await InfraEnvsAPI.update(disconnectedInfraEnv.id, params); - setDisconnectedInfraEnv(updatedInfraEnv); - return updatedInfraEnv; - }; - - const isNextDisabled = - formState.isAutoSaveRunning || !formState.isValid || !!alerts.length || formState.isSubmitting; - const errorFields = getFormikErrorFields(formState.errors, formState.touched); - - const footer = ( - moveNext()} - onBack={() => moveBack()} - isNextDisabled={isNextDisabled} - isBackDisabled={formState.isSubmitting || formState.isAutoSaveRunning} - /> - ); - - if (!disconnectedInfraEnv) { - return null; - } - - return ( - } footer={footer}> - - - - - ); -}; - -export default DisconnectedStaticIp; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.tsx deleted file mode 100644 index e044b89d44..0000000000 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import * as React from 'react'; -import { Formik } from 'formik'; -import { TFunction } from 'i18next'; -import * as Yup from 'yup'; -import { - ClusterWizardStep, - TechnologyPreview, - sshPublicKeyValidationSchema, - getFormikErrorFields, - httpProxyValidationSchema, - noProxyValidationSchema, - ipValidationSchema, - InputField, - CheckboxField, - AdditionalNTPSourcesField, - ProxyFieldsType, - PullSecret, - pullSecretValidationSchema, -} from '../../../../common'; -import { Split, SplitItem, Grid, GridItem, Form, Content } from '@patternfly/react-core'; -import { useClusterWizardContext } from '../ClusterWizardContext'; -import ClusterWizardFooter from '../ClusterWizardFooter'; -import ClusterWizardNavigation from '../ClusterWizardNavigation'; -import { WithErrorBoundary } from '../../../../common/components/ErrorHandling/WithErrorBoundary'; -import UploadSSH from '../../../../common/components/clusterConfiguration/UploadSSH'; -import { ProxyInputFields } from '../../../../common/components/clusterConfiguration/ProxyFields'; -import { handleApiError, getApiErrorMessage } from '../../../../common/api'; -import { useAlerts } from '../../../../common/components/AlertsContextProvider'; -import { AlertVariant } from '@patternfly/react-core'; -import InfraEnvsService from '../../../services/InfraEnvsService'; -import { InfraEnvsAPI } from '../../../services/apis'; -import usePullSecret from '../../../hooks/usePullSecret'; -import { useParams } from 'react-router-dom-v5-compat'; -import ClustersService from '../../../services/ClustersService'; -import { - Cluster, - InfraEnv, - InfraEnvCreateParams, - InfraEnvUpdateParams, -} from '@openshift-assisted/types/assisted-installer-service'; -import { HostsNetworkConfigurationControlGroup } from '../../clusterConfiguration/HostsNetworkConfigurationControlGroup'; -import { HostsNetworkConfigurationType } from '../../../services/types'; -import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; -import { getDummyInfraEnvField } from '../../clusterConfiguration/staticIp/data/dummyData'; -import { ntpSourceValidationSchema } from '../../../../common/validationSchemas/ntpValidation'; -import { isInOcm } from '../../../../common/api'; - -const DEFAULT_CPU_ARCHITECTURE = 'x86_64' as const; -const DISCONNECTED_IMAGE_TYPE = 'disconnected-iso' as const; - -type OptionalConfigurationsFormValues = ProxyFieldsType & { - sshPublicKey?: string; - enableNtpSources: boolean; - additionalNtpSources?: string; - hostsNetworkConfigurationType: HostsNetworkConfigurationType; - rendezvousIp?: string; - pullSecret?: string; -}; - -const DEFAULT_INITIAL_VALUES: OptionalConfigurationsFormValues = { - sshPublicKey: '', - enableProxy: false, - httpProxy: '', - httpsProxy: '', - noProxy: '', - enableNtpSources: false, - additionalNtpSources: '', - hostsNetworkConfigurationType: HostsNetworkConfigurationType.DHCP, - rendezvousIp: '', - pullSecret: '', -}; - -/** - * Rehydrate form values from infraEnv when navigating back to this step. - */ -const infraEnvToFormValues = (infraEnv: InfraEnv): OptionalConfigurationsFormValues => ({ - ...DEFAULT_INITIAL_VALUES, - sshPublicKey: infraEnv.sshAuthorizedKey ?? '', - enableProxy: !!(infraEnv.proxy?.httpProxy || infraEnv.proxy?.httpsProxy), - httpProxy: infraEnv.proxy?.httpProxy ?? '', - httpsProxy: infraEnv.proxy?.httpsProxy ?? '', - noProxy: infraEnv.proxy?.noProxy ?? '', - enableNtpSources: !!infraEnv.additionalNtpSources, - additionalNtpSources: infraEnv.additionalNtpSources ?? '', - hostsNetworkConfigurationType: - (infraEnv.staticNetworkConfig?.length ?? 0) > 0 - ? HostsNetworkConfigurationType.STATIC - : HostsNetworkConfigurationType.DHCP, - rendezvousIp: infraEnv.rendezvousIp ?? '', -}); - -/** - * Builds common infrastructure environment params from form values - */ -const buildInfraEnvParams = (values: OptionalConfigurationsFormValues) => { - // Build proxy object - only include fields that have values - const proxy = { - ...(values.httpProxy && { httpProxy: values.httpProxy }), - ...(values.httpsProxy && { httpsProxy: values.httpsProxy }), - ...(values.noProxy && { noProxy: values.noProxy }), - }; - const hasProxy = Object.keys(proxy).length > 0; - - return { - ...(values.sshPublicKey && { sshAuthorizedKey: values.sshPublicKey }), - ...(hasProxy && { proxy }), - ...(values.additionalNtpSources && { - additionalNtpSources: values.additionalNtpSources, - }), - ...(values.rendezvousIp && { rendezvousIp: values.rendezvousIp }), - // Initialize with dummy static network config when static IP is selected - // This is required for the StaticIpPage to render properly - ...(values.hostsNetworkConfigurationType === HostsNetworkConfigurationType.STATIC && { - staticNetworkConfig: getDummyInfraEnvField(), - }), - }; -}; - -const getValidationSchema = (t: TFunction, requirePullSecret: boolean) => - Yup.lazy((values: OptionalConfigurationsFormValues) => - Yup.object().shape({ - sshPublicKey: sshPublicKeyValidationSchema(t), - enableProxy: Yup.boolean().required(), - httpProxy: httpProxyValidationSchema({ - values, - pairValueName: 'httpsProxy', - allowEmpty: true, - t, - }), - httpsProxy: httpProxyValidationSchema({ - values, - pairValueName: 'httpProxy', - allowEmpty: true, - t, - }), - noProxy: noProxyValidationSchema(t), - enableNtpSources: Yup.boolean().required(), - additionalNtpSources: ntpSourceValidationSchema(t), - hostsNetworkConfigurationType: Yup.string() - .oneOf(Object.values(HostsNetworkConfigurationType)) - .required(), - rendezvousIp: Yup.string() - .max(45, 'IP address must be at most 45 characters') - .test( - 'ip-validation', - 'Not a valid IP address', - (value) => !value || ipValidationSchema(t).isValidSync(value), - ), - ...(requirePullSecret && { - pullSecret: pullSecretValidationSchema(t).required(t('ai:Required field')), - }), - }), - ); - -const OptionalConfigurationsStep = () => { - const { clusterId } = useParams<{ clusterId: string }>(); - const [cluster, setCluster] = React.useState(null); - const { t } = useTranslation(); - const defaultPullSecret = usePullSecret(); - - const { moveNext, moveBack, setDisconnectedInfraEnv, disconnectedInfraEnv } = - useClusterWizardContext(); - const { addAlert, clearAlerts } = useAlerts(); - - React.useEffect(() => { - const fetchCluster = async () => { - if (!clusterId) { - return; - } - try { - const fetchedCluster = await ClustersService.get(clusterId); - setCluster(fetchedCluster); - } catch (error) { - handleApiError(error, () => { - addAlert({ - title: t('ai:Failed to fetch cluster'), - message: getApiErrorMessage(error), - variant: AlertVariant.danger, - }); - }); - } - }; - void fetchCluster(); - }, [clusterId, addAlert, t]); - - const initialValues: OptionalConfigurationsFormValues = disconnectedInfraEnv - ? infraEnvToFormValues(disconnectedInfraEnv) - : DEFAULT_INITIAL_VALUES; - - return ( - { - clearAlerts(); - if (!cluster?.id) { - addAlert({ - title: t('ai:Missing cluster'), - message: t('ai:Cluster must be created before configuring infrastructure environment'), - variant: AlertVariant.danger, - }); - return; - } - - const commonParams = buildInfraEnvParams(values); - // Default pull secret only exists in OCM; when !isInOcm the user must provide it via the field - const pullSecretToUse = isInOcm ? defaultPullSecret : values.pullSecret; - - try { - if (disconnectedInfraEnv?.id) { - // Update existing infraEnv - const updateParams: InfraEnvUpdateParams = { - ...commonParams, - imageType: DISCONNECTED_IMAGE_TYPE, - pullSecret: pullSecretToUse, - }; - const { data: updatedInfraEnv } = await InfraEnvsAPI.update( - disconnectedInfraEnv.id, - updateParams, - ); - setDisconnectedInfraEnv({ - ...updatedInfraEnv, - // infraEnv does not return the whole OCP version - openshiftVersion: cluster.openshiftVersion, - }); - } else { - // Create new infraEnv - const createParams: InfraEnvCreateParams = { - name: InfraEnvsService.makeInfraEnvName(DEFAULT_CPU_ARCHITECTURE, cluster.name), - clusterId: cluster.id, - openshiftVersion: cluster.openshiftVersion, - cpuArchitecture: DEFAULT_CPU_ARCHITECTURE, - imageType: DISCONNECTED_IMAGE_TYPE, - pullSecret: pullSecretToUse ?? '', - ...commonParams, - }; - const createdInfraEnv = await InfraEnvsService.create(createParams); - setDisconnectedInfraEnv({ - ...createdInfraEnv, - // infraEnv does not return the whole OCP version - openshiftVersion: cluster.openshiftVersion, - }); - } - moveNext(); - } catch (error) { - handleApiError(error, () => { - addAlert({ - title: disconnectedInfraEnv?.id - ? t('ai:Failed to update infrastructure environment') - : t('ai:Failed to create infrastructure environment'), - message: getApiErrorMessage(error), - variant: AlertVariant.danger, - }); - }); - } - }} - > - {({ submitForm, isValid, errors, touched, isSubmitting, values }) => { - const errorFields = getFormikErrorFields(errors, touched); - const handleNext = () => { - void submitForm(); // This will trigger onSubmit - }; - - return ( - } - footer={ - - } - > - - - - - - {t('ai:Optional configurations')} - - - - - - - -
- {/* Pull secret (!OCM only) */} - {!isInOcm && } - - {/* Rendezvous IP */} - - - - - {/* Proxy Settings */} - - {t( - 'ai:If hosts are behind a firewall that requires the use of a proxy, provide additional information about the proxy.', - )} -

- } - body={values.enableProxy && } - /> - - {/* NTP Configuration */} - - {t( - 'ai:Configure your own NTP sources to synchronize the time between the hosts that will be added to this infrastructure environment.', - )} -

- } - body={ - values.enableNtpSources && ( - - - - ) - } - /> - - {/* Network Configuration */} - - -
-
-
-
- ); - }} -
- ); -}; - -export default OptionalConfigurationsStep; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx index 5f0dfec645..9c98edb589 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx @@ -25,13 +25,16 @@ import { Content, } from '@patternfly/react-core'; import { Formik } from 'formik'; -import { useNavigate, useParams } from 'react-router-dom-v5-compat'; +import { saveAs } from 'file-saver'; +import { useNavigate } from 'react-router-dom-v5-compat'; import { getOperatorSpecs } from '../../../../common/components/operators/operatorSpecs'; +const downloadUrl = + 'https://mirror.openshift.com/pub/cgw/assisted-installer-disconnected/latest/agent-ove.x86_64.iso'; + const ReviewStep = () => { - const { moveBack, disconnectedInfraEnv } = useClusterWizardContext(); - const { clusterId } = useParams<{ clusterId: string }>(); + const { moveBack } = useClusterWizardContext(); const opSpecs = getOperatorSpecs(() => undefined); const navigate = useNavigate(); @@ -47,14 +50,11 @@ const ReviewStep = () => { footer={ { - if (disconnectedInfraEnv?.downloadUrl) { - window.open(disconnectedInfraEnv.downloadUrl, '_blank'); - } + downloadUrl && saveAs(downloadUrl); navigate('/cluster-list'); }} onBack={moveBack} nextButtonText="Download ISO" - disconnectedClusterId={clusterId} /> } > @@ -96,19 +96,9 @@ const ReviewStep = () => { - {disconnectedInfraEnv?.rendezvousIp && ( - - Rendezvous IP - - {disconnectedInfraEnv?.rendezvousIp} - - - )} OpenShift version - - {disconnectedInfraEnv?.openshiftVersion || ''} - + 4.20 CPU architecture @@ -116,7 +106,7 @@ const ReviewStep = () => { ISO size - approx. 50 GB + approx. 43.5GB diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/index.ts b/libs/ui-lib/lib/ocm/components/clusterWizard/index.ts index 223c844670..f1e9e41f5b 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/index.ts +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/index.ts @@ -2,5 +2,4 @@ export { default as ClusterDetailsForm } from './ClusterDetailsForm'; export { default as NewClusterWizard } from './NewClusterWizard'; export { default as ClusterWizard } from './ClusterWizard'; export { default as ClusterWizardContextProvider } from './ClusterWizardContextProvider'; -export { useClusterWizardContext } from './ClusterWizardContext'; export { OpenShiftVersionsContextProvider } from './OpenShiftVersionsContext'; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts b/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts index 0587c308aa..84b0ce0467 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts @@ -31,8 +31,7 @@ export type ClusterWizardStepsType = | 'custom-manifests' | 'credentials-download' | 'disconnected-basic' - | 'disconnected-review' - | 'disconnected-optional-configurations'; + | 'disconnected-review'; const wizardStepsOrder: ClusterWizardStepsType[] = [ 'cluster-details', @@ -50,7 +49,6 @@ const wizardStepsOrder: ClusterWizardStepsType[] = [ export const disconnectedSteps: ClusterWizardStepsType[] = [ 'disconnected-basic', - 'disconnected-optional-configurations', 'disconnected-review', ]; @@ -270,7 +268,6 @@ const credentialsValidationMap = buildEmptyValidationsMap(); const customManifestsValidationsMap = buildEmptyValidationsMap(); const disconnectedReviewValidationsMap = buildEmptyValidationsMap(); const disconnectedBasicValidationsMap = buildEmptyValidationsMap(); -const disconnectedOptionalConfigurationsValidationsMap = buildEmptyValidationsMap(); export const wizardStepsValidationsMap: WizardStepsValidationMap = { 'cluster-details': clusterDetailsStepValidationsMap, @@ -286,7 +283,6 @@ export const wizardStepsValidationsMap: WizardStepsValidationMap { - if (!clusterId && !isSingleClusterFeatureEnabled) { + if (!clusterId) { setError('Missing clusterId to load infrastructure environment'); } else if (!infraEnvId) { void findInfraEnvId(); } - }, [clusterId, findInfraEnvId, infraEnvId, isSingleClusterFeatureEnabled]); + }, [clusterId, findInfraEnvId, infraEnvId]); return { infraEnvId, error }; } diff --git a/libs/ui-lib/lib/ocm/services/ClustersService.ts b/libs/ui-lib/lib/ocm/services/ClustersService.ts index b3467041cc..f6c5c94e9e 100644 --- a/libs/ui-lib/lib/ocm/services/ClustersService.ts +++ b/libs/ui-lib/lib/ocm/services/ClustersService.ts @@ -171,11 +171,5 @@ const ClustersService = { this.getUpdateManifestParams(existingManifest, updatedManifest), ); }, - registerDisconnected(params: { name: string; openshiftVersion: string }) { - return ClustersAPI.registerDisconnected({ - name: params.name, - openshiftVersion: params.openshiftVersion, - }); - }, }; export default ClustersService; diff --git a/libs/ui-lib/lib/ocm/services/InfraEnvsService.ts b/libs/ui-lib/lib/ocm/services/InfraEnvsService.ts index 35d5f7ceae..d122399700 100644 --- a/libs/ui-lib/lib/ocm/services/InfraEnvsService.ts +++ b/libs/ui-lib/lib/ocm/services/InfraEnvsService.ts @@ -21,10 +21,7 @@ const InfraEnvsService = { const { data: infraEnvs } = await InfraEnvsAPI.list( !isSingleClusterFeatureEnabled ? clusterId : '', ); - if (isSingleClusterFeatureEnabled) { - return infraEnvs[0].id; - } - if (infraEnvs.length > 0 && clusterId) { + if (infraEnvs.length > 0) { InfraEnvCache.updateInfraEnvs(clusterId, infraEnvs); infraEnvId = InfraEnvCache.getInfraEnvId(clusterId, cpuArchitecture); }