diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json
index 46a686d971..df70d29feb 100644
--- a/libs/locales/lib/en/translation.json
+++ b/libs/locales/lib/en/translation.json
@@ -183,6 +183,7 @@
"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 CIDR": "Cluster network CIDR",
@@ -214,6 +215,7 @@
"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.",
@@ -346,9 +348,11 @@
"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",
@@ -358,6 +362,7 @@
"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",
@@ -562,6 +567,7 @@
"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",
@@ -670,6 +676,7 @@
"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",
@@ -739,6 +746,7 @@
"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",
@@ -840,6 +848,7 @@
"ai:The hosts you selected are using different proxy settings. Configure a proxy that will be applied for these hosts. Configure at least one of the proxy settings below.": "The hosts you selected are using different proxy settings. Configure a proxy that will be applied for these hosts. Configure at least one of the following proxy settings.",
"ai:The HTTP proxy URL that agents should use to access the discovery service.": "The HTTP proxy URL that agents should use to access the discovery service.",
"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 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 resource has been saved and the YAML is now read only.": "The resource has been saved and the YAML is now read only.",
diff --git a/libs/types/assisted-installer-service.d.ts b/libs/types/assisted-installer-service.d.ts
index 39e585e2cb..e1b70348f5 100644
--- a/libs/types/assisted-installer-service.d.ts
+++ b/libs/types/assisted-installer-service.d.ts
@@ -1734,7 +1734,7 @@ export interface ImageInfo {
staticNetworkConfig?: string;
type?: ImageType;
}
-export type ImageType = 'full-iso' | 'minimal-iso';
+export type ImageType = 'full-iso' | 'minimal-iso' | 'disconnected-iso';
export interface ImportClusterParams {
/**
* OpenShift cluster name.
@@ -1830,6 +1830,14 @@ export interface InfraEnv {
* certificates in this bundle.
*/
additionalTrustBundle?: string;
+ /**
+ * The IP address that hosts will use to communicate with the bootstrap node during installation.
+ */
+ rendezvousIp?: string;
+ /**
+ * The type of network configuration for hosts: 'dhcp' for DHCP only, 'static' for static IP configuration.
+ */
+ hostsNetworkConfigurationType?: 'dhcp' | 'static';
}
export interface InfraEnvCreateParams {
/**
@@ -1875,6 +1883,14 @@ export interface InfraEnvCreateParams {
* certificates in this bundle.
*/
additionalTrustBundle?: string;
+ /**
+ * The IP address that hosts will use to communicate with the bootstrap node during installation.
+ */
+ rendezvousIp?: string;
+ /**
+ * The type of network configuration for hosts: 'dhcp' for DHCP only, 'static' for static IP configuration.
+ */
+ hostsNetworkConfigurationType?: 'dhcp' | 'static';
}
export type InfraEnvList = InfraEnv[];
export interface InfraEnvUpdateParams {
@@ -1906,6 +1922,14 @@ export interface InfraEnvUpdateParams {
* Version of the OS image
*/
openshiftVersion?: string;
+ /**
+ * The IP address that hosts will use to communicate with the bootstrap node during installation.
+ */
+ rendezvousIp?: string;
+ /**
+ * The type of network configuration for hosts: 'dhcp' for DHCP only, 'static' for static IP configuration.
+ */
+ hostsNetworkConfigurationType?: 'dhcp' | 'static';
}
export interface InfraError {
/**
diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx
index 3ac9cd21b7..fa7e2f1ff0 100644
--- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx
+++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/DiscoveryImageTypeDropdown.tsx
@@ -20,6 +20,7 @@ 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 7775c681a8..a29be2fdf3 100644
--- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx
+++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx
@@ -15,7 +15,6 @@ import {
import { Formik, FormikHelpers } from 'formik';
import {
HostStaticNetworkConfig,
- ImageType,
InfraEnv,
Proxy,
} from '@openshift-assisted/types/assisted-installer-service';
@@ -70,7 +69,7 @@ type OcmDiscoveryImageConfigFormProps = Proxy & {
formikActions: FormikHelpers,
) => Promise;
sshPublicKey?: string;
- imageType?: ImageType;
+ imageType?: DiscoveryImageType;
isIpxeSelected?: boolean;
enableCertificate?: boolean;
trustBundle?: InfraEnv['additionalTrustBundle'];
@@ -104,7 +103,7 @@ export const OcmDiscoveryImageConfigForm = ({
httpsProxy: httpsProxy || '',
noProxy: noProxy || '',
enableProxy: !!(httpProxy || httpsProxy || noProxy),
- imageType: imageTypeValue as ImageType,
+ imageType: imageType,
enableCertificate: enableCertificate || false,
trustBundle: trustBundle || '',
};
diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx
index 40c3c83991..86863ed601 100644
--- a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx
+++ b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx
@@ -76,6 +76,33 @@ 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,
@@ -89,6 +116,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 [customManifestsStep, setCustomManifestsStep] = React.useState(false);
const [installDisconnected, setInstallDisconnected] = React.useState(false);
@@ -106,7 +135,7 @@ const ClusterWizardContextProvider = ({
const { clearAlerts, addAlert, alerts } = useAlerts();
const setClusterPermissions = useSetClusterPermissions();
- const wizardStepIds = installDisconnected ? disconnectedSteps : connectedWizardStepIds;
+ const wizardStepIds = installDisconnected ? disconnectedWizardStepIds : connectedWizardStepIds;
React.useEffect(() => {
if (!UISettingsLoading) {
@@ -165,17 +194,29 @@ 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 staticIpInfo = infraEnv ? getStaticIpInfo(infraEnv) : undefined;
+ const currentInfraEnv = installDisconnected ? disconnectedInfraEnv : infraEnv;
+ const staticIpInfo = currentInfraEnv ? getStaticIpInfo(currentInfraEnv) : undefined;
if (!staticIpInfo) {
throw `Wizard step is currently ${currentStepId}, but no static ip info is defined`;
}
- const newStepIds = getWizardStepIds(
- wizardStepIds,
- staticIpInfo.view,
- customManifestsStep,
- isSingleClusterFeatureEnabled,
- );
- setWizardStepIds(newStepIds);
+
+ 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,
+ customManifestsStep,
+ isSingleClusterFeatureEnabled,
+ );
+ setWizardStepIds(newStepIds);
+ }
};
const onSetCurrentStepId = (stepId: ClusterWizardStepsType) => {
@@ -219,31 +260,57 @@ const ClusterWizardContextProvider = ({
} else {
setCurrentStepId('static-ip-network-wide-configurations');
}
- setWizardStepIds(
- getWizardStepIds(wizardStepIds, view, customManifestsStep, isSingleClusterFeatureEnabled),
- );
- },
- onUpdateHostNetworkConfigType(type: HostsNetworkConfigurationType): void {
- if (type === HostsNetworkConfigurationType.STATIC) {
- setWizardStepIds(
- getWizardStepIds(
- wizardStepIds,
- StaticIpView.FORM,
- customManifestsStep,
- isSingleClusterFeatureEnabled,
- ),
+ if (installDisconnected) {
+ // For disconnected wizard, update wizard steps (same pattern as connected)
+ setDisconnectedWizardStepIds(
+ getDisconnectedWizardStepIds(disconnectedWizardStepIds, view),
);
} else {
setWizardStepIds(
getWizardStepIds(
wizardStepIds,
- 'dhcp-selected',
+ view,
customManifestsStep,
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,
+ customManifestsStep,
+ isSingleClusterFeatureEnabled,
+ ),
+ );
+ } else {
+ setWizardStepIds(
+ getWizardStepIds(
+ wizardStepIds,
+ 'dhcp-selected',
+ customManifestsStep,
+ isSingleClusterFeatureEnabled,
+ ),
+ );
+ }
+ }
+ },
wizardStepIds: wizardStepIds,
currentStepId,
setCurrentStepId: onSetCurrentStepId,
@@ -276,8 +343,8 @@ const ClusterWizardContextProvider = ({
uiSettings,
updateUISettings,
installDisconnected,
- setInstallDisconnected,
connectedWizardStepIds,
+ disconnectedWizardStepIds,
disconnectedInfraEnv,
]);
diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx
index bb657e9dd3..7891e2edc5 100644
--- a/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx
+++ b/libs/ui-lib/lib/ocm/components/clusterWizard/NewClusterWizard.tsx
@@ -5,7 +5,9 @@ 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,6 +17,10 @@ const getCurrentStep = (currentStepId: ClusterWizardStepsType) => {
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 ;
}
@@ -23,9 +29,11 @@ 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/disconnected/BasicStep.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx
index 184134c343..a4fc8a7556 100644
--- a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx
+++ b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx
@@ -43,7 +43,7 @@ const BasicStep = () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
await ClustersService.registerDisconnected({
name: 'disconnected-cluster',
- openshiftVersion: '4.20',
+ openshiftVersion: '4.22',
});
disconnectedClusterId = disconnectedCluster.id;
navigate(`${currentPath}/${disconnectedClusterId}`, {
@@ -98,7 +98,7 @@ const BasicStep = () => {
+ }
+ 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 */}
+
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 8bc7035e35..5cb806632d 100644
--- a/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx
+++ b/libs/ui-lib/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx
@@ -25,7 +25,6 @@ import {
Content,
} from '@patternfly/react-core';
import { Formik } from 'formik';
-import { saveAs } from 'file-saver';
import { useNavigate, useParams } from 'react-router-dom-v5-compat';
import { getOperatorSpecs } from '../../../../common/components/operators/operatorSpecs';
@@ -55,7 +54,26 @@ const ReviewStep = () => {
onNext={() => {
void (async () => {
if (disconnectedInfraEnv?.downloadUrl) {
- saveAs(disconnectedInfraEnv.downloadUrl);
+ // Open download in new tab - we need the window reference to detect when download starts
+ const downloadWindow = window.open(disconnectedInfraEnv.downloadUrl, '_blank');
+
+ // Wait for the download tab to close (indicates download has started)
+ // The tab closes automatically when browser initiates the file download
+ if (downloadWindow) {
+ await new Promise((resolve) => {
+ const checkClosed = setInterval(() => {
+ if (downloadWindow.closed) {
+ clearInterval(checkClosed);
+ resolve();
+ }
+ }, 200);
+ // Fallback timeout in case the window doesn't close (e.g., popup blocker)
+ setTimeout(() => {
+ clearInterval(checkClosed);
+ resolve();
+ }, 10000);
+ });
+ }
}
if (clusterId) {
try {
@@ -128,9 +146,17 @@ const ReviewStep = () => {
+ {disconnectedInfraEnv?.rendezvousIp && (
+
+ Controller Ip
+
+ {disconnectedInfraEnv?.rendezvousIp}
+
+
+ )}
OpenShift version
- 4.20
+ 4.22
CPU architecture