@openshift-assisted/ui-lib 2.47.4 → 2.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/build/@types/cim/components/ClusterDeployment/wizardTransition.d.ts +1 -1
  2. package/build/@types/cim/components/ClusterDeployment/wizardTransition.d.ts.map +1 -1
  3. package/build/@types/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.d.ts.map +1 -1
  4. package/build/@types/common/api/assisted-service/ClustersAPI.d.ts +4 -0
  5. package/build/@types/common/api/assisted-service/ClustersAPI.d.ts.map +1 -1
  6. package/build/@types/common/api/assisted-service/HostsAPI.d.ts +2 -1
  7. package/build/@types/common/api/assisted-service/HostsAPI.d.ts.map +1 -1
  8. package/build/@types/common/api/assisted-service/InfraEnvsAPI.d.ts +2 -1
  9. package/build/@types/common/api/assisted-service/InfraEnvsAPI.d.ts.map +1 -1
  10. package/build/@types/common/components/clusterWizard/ReviewValidations.d.ts.map +1 -1
  11. package/build/@types/common/components/operators/operatorDescriptions.d.ts +2 -0
  12. package/build/@types/common/components/operators/operatorDescriptions.d.ts.map +1 -1
  13. package/build/@types/common/components/operators/operatorSpecs.d.ts.map +1 -1
  14. package/build/@types/common/components/ui/formik/PullSecretField.d.ts.map +1 -1
  15. package/build/@types/common/config/constants.d.ts +8 -0
  16. package/build/@types/common/config/constants.d.ts.map +1 -1
  17. package/build/@types/common/config/docs_links.d.ts +2 -0
  18. package/build/@types/common/config/docs_links.d.ts.map +1 -1
  19. package/build/@types/common/reducers/alertsSlice.d.ts +1 -0
  20. package/build/@types/common/reducers/alertsSlice.d.ts.map +1 -1
  21. package/build/@types/ocm/components/Routes.d.ts.map +1 -1
  22. package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.d.ts.map +1 -1
  23. package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.d.ts.map +1 -1
  24. package/build/@types/ocm/components/clusterConfiguration/operators/utils.d.ts.map +1 -1
  25. package/build/@types/ocm/components/clusterWizard/ClusterDetails.d.ts.map +1 -1
  26. package/build/@types/ocm/components/clusterWizard/ClusterWizardContext.d.ts +3 -0
  27. package/build/@types/ocm/components/clusterWizard/ClusterWizardContext.d.ts.map +1 -1
  28. package/build/@types/ocm/components/clusterWizard/ClusterWizardContextProvider.d.ts.map +1 -1
  29. package/build/@types/ocm/components/clusterWizard/ClusterWizardFooter.d.ts +2 -1
  30. package/build/@types/ocm/components/clusterWizard/ClusterWizardFooter.d.ts.map +1 -1
  31. package/build/@types/ocm/components/clusterWizard/HostDiscovery.d.ts.map +1 -1
  32. package/build/@types/ocm/components/clusterWizard/NewClusterWizard.d.ts.map +1 -1
  33. package/build/@types/ocm/components/clusterWizard/constants.d.ts.map +1 -1
  34. package/build/@types/ocm/components/clusterWizard/disconnected/BasicStep.d.ts.map +1 -1
  35. package/build/@types/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.d.ts +4 -0
  36. package/build/@types/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.d.ts.map +1 -0
  37. package/build/@types/ocm/components/clusterWizard/disconnected/ReviewStep.d.ts.map +1 -1
  38. package/build/@types/ocm/components/clusterWizard/index.d.ts +1 -0
  39. package/build/@types/ocm/components/clusterWizard/index.d.ts.map +1 -1
  40. package/build/@types/ocm/components/clusterWizard/wizardTransition.d.ts +2 -2
  41. package/build/@types/ocm/components/clusterWizard/wizardTransition.d.ts.map +1 -1
  42. package/build/@types/ocm/components/hosts/HostsDiscoveryTable.d.ts +3 -2
  43. package/build/@types/ocm/components/hosts/HostsDiscoveryTable.d.ts.map +1 -1
  44. package/build/@types/ocm/hooks/index.d.ts +1 -0
  45. package/build/@types/ocm/hooks/index.d.ts.map +1 -1
  46. package/build/@types/ocm/hooks/useInfraEnvHosts.d.ts +9 -0
  47. package/build/@types/ocm/hooks/useInfraEnvHosts.d.ts.map +1 -0
  48. package/build/@types/ocm/hooks/useInfraEnvId.d.ts.map +1 -1
  49. package/build/@types/ocm/hooks/useLateBinding.d.ts +4 -0
  50. package/build/@types/ocm/hooks/useLateBinding.d.ts.map +1 -0
  51. package/build/@types/ocm/services/ClustersService.d.ts +5 -1
  52. package/build/@types/ocm/services/ClustersService.d.ts.map +1 -1
  53. package/build/@types/ocm/services/HostsService.d.ts +2 -1
  54. package/build/@types/ocm/services/HostsService.d.ts.map +1 -1
  55. package/build/@types/ocm/services/InfraEnvsService.d.ts +1 -1
  56. package/build/@types/ocm/services/InfraEnvsService.d.ts.map +1 -1
  57. package/build/cjs/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.js +3 -1
  58. package/build/cjs/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.js.map +1 -1
  59. package/build/cjs/common/api/assisted-service/ClustersAPI.js +3 -0
  60. package/build/cjs/common/api/assisted-service/ClustersAPI.js.map +1 -1
  61. package/build/cjs/common/api/assisted-service/HostsAPI.js +8 -0
  62. package/build/cjs/common/api/assisted-service/HostsAPI.js.map +1 -1
  63. package/build/cjs/common/api/assisted-service/InfraEnvsAPI.js +5 -0
  64. package/build/cjs/common/api/assisted-service/InfraEnvsAPI.js.map +1 -1
  65. package/build/cjs/common/components/clusterWizard/ReviewValidations.js +2 -1
  66. package/build/cjs/common/components/clusterWizard/ReviewValidations.js.map +1 -1
  67. package/build/cjs/common/components/operators/operatorDescriptions.js +3 -1
  68. package/build/cjs/common/components/operators/operatorDescriptions.js.map +1 -1
  69. package/build/cjs/common/components/operators/operatorSpecs.js +24 -1
  70. package/build/cjs/common/components/operators/operatorSpecs.js.map +1 -1
  71. package/build/cjs/common/components/ui/WizardFooter.js +1 -1
  72. package/build/cjs/common/components/ui/WizardFooter.js.map +1 -1
  73. package/build/cjs/common/components/ui/formik/PullSecretField.js.map +1 -1
  74. package/build/cjs/common/config/constants.js +14 -2
  75. package/build/cjs/common/config/constants.js.map +1 -1
  76. package/build/cjs/common/config/docs_links.js +5 -1
  77. package/build/cjs/common/config/docs_links.js.map +1 -1
  78. package/build/cjs/common/reducers/alertsSlice.js +1 -1
  79. package/build/cjs/common/reducers/alertsSlice.js.map +1 -1
  80. package/build/cjs/ocm/components/Routes.js +2 -1
  81. package/build/cjs/ocm/components/Routes.js.map +1 -1
  82. package/build/cjs/ocm/components/clusterConfiguration/HostInventory.js +1 -1
  83. package/build/cjs/ocm/components/clusterConfiguration/HostInventory.js.map +1 -1
  84. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.js +1 -1
  85. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.js.map +1 -1
  86. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.js +2 -1
  87. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.js.map +1 -1
  88. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.js +2 -2
  89. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.js.map +1 -1
  90. package/build/cjs/ocm/components/clusterConfiguration/operators/utils.js +7 -4
  91. package/build/cjs/ocm/components/clusterConfiguration/operators/utils.js.map +1 -1
  92. package/build/cjs/ocm/components/clusterWizard/ClusterDetails.js +12 -2
  93. package/build/cjs/ocm/components/clusterWizard/ClusterDetails.js.map +1 -1
  94. package/build/cjs/ocm/components/clusterWizard/ClusterWizardContext.js.map +1 -1
  95. package/build/cjs/ocm/components/clusterWizard/ClusterWizardContextProvider.js +4 -0
  96. package/build/cjs/ocm/components/clusterWizard/ClusterWizardContextProvider.js.map +1 -1
  97. package/build/cjs/ocm/components/clusterWizard/ClusterWizardFooter.js +21 -6
  98. package/build/cjs/ocm/components/clusterWizard/ClusterWizardFooter.js.map +1 -1
  99. package/build/cjs/ocm/components/clusterWizard/HostDiscovery.js +3 -0
  100. package/build/cjs/ocm/components/clusterWizard/HostDiscovery.js.map +1 -1
  101. package/build/cjs/ocm/components/clusterWizard/NewClusterWizard.js +3 -0
  102. package/build/cjs/ocm/components/clusterWizard/NewClusterWizard.js.map +1 -1
  103. package/build/cjs/ocm/components/clusterWizard/constants.js +1 -0
  104. package/build/cjs/ocm/components/clusterWizard/constants.js.map +1 -1
  105. package/build/cjs/ocm/components/clusterWizard/disconnected/BasicStep.js +45 -1
  106. package/build/cjs/ocm/components/clusterWizard/disconnected/BasicStep.js.map +1 -1
  107. package/build/cjs/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.js +128 -0
  108. package/build/cjs/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.js.map +1 -0
  109. package/build/cjs/ocm/components/clusterWizard/disconnected/ReviewStep.js +40 -5
  110. package/build/cjs/ocm/components/clusterWizard/disconnected/ReviewStep.js.map +1 -1
  111. package/build/cjs/ocm/components/clusterWizard/index.js +3 -1
  112. package/build/cjs/ocm/components/clusterWizard/index.js.map +1 -1
  113. package/build/cjs/ocm/components/clusterWizard/wizardTransition.js +3 -0
  114. package/build/cjs/ocm/components/clusterWizard/wizardTransition.js.map +1 -1
  115. package/build/cjs/ocm/components/hosts/HostsDiscoveryTable.js +1 -2
  116. package/build/cjs/ocm/components/hosts/HostsDiscoveryTable.js.map +1 -1
  117. package/build/cjs/ocm/hooks/index.js +3 -1
  118. package/build/cjs/ocm/hooks/index.js.map +1 -1
  119. package/build/cjs/ocm/hooks/useInfraEnvHosts.js +47 -0
  120. package/build/cjs/ocm/hooks/useInfraEnvHosts.js.map +1 -0
  121. package/build/cjs/ocm/hooks/useInfraEnvId.js +11 -2
  122. package/build/cjs/ocm/hooks/useInfraEnvId.js.map +1 -1
  123. package/build/cjs/ocm/hooks/useLateBinding.js +61 -0
  124. package/build/cjs/ocm/hooks/useLateBinding.js.map +1 -0
  125. package/build/cjs/ocm/services/ClustersService.js +10 -2
  126. package/build/cjs/ocm/services/ClustersService.js.map +1 -1
  127. package/build/cjs/ocm/services/HostsService.js +6 -0
  128. package/build/cjs/ocm/services/HostsService.js.map +1 -1
  129. package/build/cjs/ocm/services/InfraEnvsService.js +2 -2
  130. package/build/cjs/ocm/services/InfraEnvsService.js.map +1 -1
  131. package/lib/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.tsx +11 -9
  132. package/lib/common/api/assisted-service/ClustersAPI.ts +6 -0
  133. package/lib/common/api/assisted-service/HostsAPI.ts +13 -0
  134. package/lib/common/api/assisted-service/InfraEnvsAPI.ts +6 -0
  135. package/lib/common/components/clusterWizard/ReviewValidations.tsx +4 -1
  136. package/lib/common/components/operators/operatorDescriptions.tsx +6 -0
  137. package/lib/common/components/operators/operatorSpecs.tsx +34 -1
  138. package/lib/common/components/ui/WizardFooter.tsx +1 -1
  139. package/lib/common/components/ui/formik/PullSecretField.tsx +1 -0
  140. package/lib/common/config/constants.ts +12 -0
  141. package/lib/common/config/docs_links.ts +10 -0
  142. package/lib/common/reducers/alertsSlice.ts +2 -1
  143. package/lib/ocm/components/Routes.tsx +3 -1
  144. package/lib/ocm/components/clusterConfiguration/HostInventory.tsx +1 -1
  145. package/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.tsx +1 -1
  146. package/lib/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.tsx +4 -1
  147. package/lib/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.tsx +0 -2
  148. package/lib/ocm/components/clusterConfiguration/operators/utils.tsx +9 -5
  149. package/lib/ocm/components/clusterWizard/ClusterDetails.tsx +16 -2
  150. package/lib/ocm/components/clusterWizard/ClusterWizardContext.tsx +3 -0
  151. package/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx +6 -0
  152. package/lib/ocm/components/clusterWizard/ClusterWizardFooter.tsx +26 -5
  153. package/lib/ocm/components/clusterWizard/HostDiscovery.tsx +3 -0
  154. package/lib/ocm/components/clusterWizard/NewClusterWizard.tsx +3 -0
  155. package/lib/ocm/components/clusterWizard/constants.ts +1 -0
  156. package/lib/ocm/components/clusterWizard/disconnected/BasicStep.tsx +52 -1
  157. package/lib/ocm/components/clusterWizard/disconnected/OptionalConfigurationsStep.tsx +200 -0
  158. package/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx +39 -7
  159. package/lib/ocm/components/clusterWizard/index.ts +1 -0
  160. package/lib/ocm/components/clusterWizard/wizardTransition.ts +5 -1
  161. package/lib/ocm/components/hosts/HostsDiscoveryTable.tsx +3 -2
  162. package/lib/ocm/hooks/index.ts +1 -0
  163. package/lib/ocm/hooks/useInfraEnvHosts.ts +60 -0
  164. package/lib/ocm/hooks/useInfraEnvId.ts +15 -2
  165. package/lib/ocm/hooks/useLateBinding.ts +71 -0
  166. package/lib/ocm/services/ClustersService.ts +14 -2
  167. package/lib/ocm/services/HostsService.ts +9 -0
  168. package/lib/ocm/services/InfraEnvsService.ts +4 -1
  169. package/package.json +3 -3
@@ -15,10 +15,53 @@ import ClusterWizardNavigation from '../ClusterWizardNavigation';
15
15
  import { WithErrorBoundary } from '../../../../common/components/ErrorHandling/WithErrorBoundary';
16
16
  import InstallDisconnectedSwitch from './InstallDisconnectedSwitch';
17
17
  import { Formik } from 'formik';
18
+ import ClustersService from '../../../services/ClustersService';
19
+ import { handleApiError, getApiErrorMessage } from '../../../../common/api';
20
+ import { useAlerts } from '../../../../common/components/AlertsContextProvider';
21
+ import { AlertVariant } from '@patternfly/react-core';
22
+ import { ClusterWizardFlowStateNew } from '../wizardTransition';
23
+ import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
24
+ import { AxiosResponse } from 'axios';
25
+ import { Cluster } from '@openshift-assisted/types//assisted-installer-service';
18
26
 
19
27
  const BasicStep = () => {
20
28
  const { t } = useTranslation();
21
29
  const { moveNext } = useClusterWizardContext();
30
+ const { addAlert } = useAlerts();
31
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
32
+ const navigate = useNavigate();
33
+ const location = useLocation();
34
+ const currentPath = location.pathname;
35
+ let disconnectedClusterId: string | undefined;
36
+
37
+ const onNext = async () => {
38
+ try {
39
+ setIsSubmitting(true);
40
+ // Create cluster only - infraEnv will be created in OptionalConfigurationsStep
41
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
42
+ const { data: disconnectedCluster }: AxiosResponse<Cluster> =
43
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
44
+ await ClustersService.registerDisconnected({
45
+ name: 'disconnected-cluster',
46
+ openshiftVersion: '4.19',
47
+ });
48
+ disconnectedClusterId = disconnectedCluster.id;
49
+ navigate(`${currentPath}/${disconnectedClusterId}`, {
50
+ state: ClusterWizardFlowStateNew,
51
+ });
52
+ moveNext();
53
+ } catch (error: unknown) {
54
+ handleApiError(error, () => {
55
+ addAlert({
56
+ title: 'Failed to create disconnected cluster',
57
+ message: getApiErrorMessage(error),
58
+ variant: AlertVariant.danger,
59
+ });
60
+ });
61
+ } finally {
62
+ setIsSubmitting(false);
63
+ }
64
+ };
22
65
 
23
66
  return (
24
67
  <Formik
@@ -29,7 +72,15 @@ const BasicStep = () => {
29
72
  >
30
73
  <ClusterWizardStep
31
74
  navigation={<ClusterWizardNavigation />}
32
- footer={<ClusterWizardFooter onNext={moveNext} />}
75
+ footer={
76
+ <ClusterWizardFooter
77
+ onNext={() => {
78
+ void onNext();
79
+ }}
80
+ isSubmitting={isSubmitting}
81
+ disconnectedClusterId={disconnectedClusterId}
82
+ />
83
+ }
33
84
  >
34
85
  <WithErrorBoundary title="Failed to load Basic step">
35
86
  <Grid hasGutter>
@@ -0,0 +1,200 @@
1
+ import * as React from 'react';
2
+ import { Formik, useFormikContext } from 'formik';
3
+ import * as Yup from 'yup';
4
+ import {
5
+ ClusterWizardStep,
6
+ DeveloperPreview,
7
+ getRichTextValidation,
8
+ sshPublicKeyValidationSchema,
9
+ pullSecretValidationSchema,
10
+ getFormikErrorFields,
11
+ } from '../../../../common';
12
+ import { Split, SplitItem, Grid, GridItem, Form, Content, Checkbox } from '@patternfly/react-core';
13
+ import { useClusterWizardContext } from '../ClusterWizardContext';
14
+ import ClusterWizardFooter from '../ClusterWizardFooter';
15
+ import ClusterWizardNavigation from '../ClusterWizardNavigation';
16
+ import { WithErrorBoundary } from '../../../../common/components/ErrorHandling/WithErrorBoundary';
17
+ import UploadSSH from '../../../../common/components/clusterConfiguration/UploadSSH';
18
+ import PullSecretField from '../../../../common/components/ui/formik/PullSecretField';
19
+ import { isInOcm, handleApiError, getApiErrorMessage } from '../../../../common/api';
20
+ import { useAlerts } from '../../../../common/components/AlertsContextProvider';
21
+ import { AlertVariant } from '@patternfly/react-core';
22
+ import InfraEnvsService from '../../../services/InfraEnvsService';
23
+ import { InfraEnvsAPI } from '../../../services/apis';
24
+ import usePullSecret from '../../../hooks/usePullSecret';
25
+ import { useParams } from 'react-router-dom-v5-compat';
26
+ import ClustersService from '../../../services/ClustersService';
27
+
28
+ type OptionalConfigurationsFormValues = {
29
+ sshPublicKey?: string;
30
+ pullSecret?: string;
31
+ };
32
+
33
+ const PullSecretSync: React.FC<{ pullSecret?: string }> = ({ pullSecret }) => {
34
+ const { setFieldValue } = useFormikContext<OptionalConfigurationsFormValues>();
35
+
36
+ React.useEffect(() => {
37
+ if (pullSecret !== undefined) {
38
+ setFieldValue('pullSecret', pullSecret);
39
+ }
40
+ }, [pullSecret, setFieldValue]);
41
+
42
+ return null;
43
+ };
44
+
45
+ const OptionalConfigurationsStep = () => {
46
+ const pullSecret = usePullSecret() || '';
47
+ const { clusterId } = useParams<{ clusterId: string }>();
48
+ const [cluster, setCluster] = React.useState<{ id: string } | null>(null);
49
+
50
+ const { moveNext, moveBack, setDisconnectedInfraEnv, disconnectedInfraEnv } =
51
+ useClusterWizardContext();
52
+ const { addAlert } = useAlerts();
53
+ const [editPullSecret, setEditPullSecret] = React.useState(false);
54
+
55
+ React.useEffect(() => {
56
+ const fetchCluster = async () => {
57
+ if (!clusterId) {
58
+ return;
59
+ }
60
+ try {
61
+ const fetchedCluster = await ClustersService.get(clusterId);
62
+ setCluster(fetchedCluster);
63
+ } catch (error) {
64
+ handleApiError(error, () => {
65
+ addAlert({
66
+ title: 'Failed to fetch cluster',
67
+ message: getApiErrorMessage(error),
68
+ variant: AlertVariant.danger,
69
+ });
70
+ });
71
+ }
72
+ };
73
+ void fetchCluster();
74
+ }, [clusterId, addAlert]);
75
+
76
+ const validationSchema = React.useMemo(
77
+ () =>
78
+ Yup.object<OptionalConfigurationsFormValues>().shape({
79
+ sshPublicKey: sshPublicKeyValidationSchema,
80
+ pullSecret: pullSecretValidationSchema,
81
+ }),
82
+ [],
83
+ );
84
+
85
+ const initialValues: OptionalConfigurationsFormValues = {
86
+ sshPublicKey: '',
87
+ pullSecret: pullSecret,
88
+ };
89
+
90
+ return (
91
+ <Formik
92
+ initialValues={initialValues}
93
+ validate={getRichTextValidation<OptionalConfigurationsFormValues>(validationSchema)}
94
+ onSubmit={async (values) => {
95
+ if (!cluster || !cluster.id) {
96
+ addAlert({
97
+ title: 'Missing cluster',
98
+ message: 'Cluster must be created before configuring infrastructure environment',
99
+ variant: AlertVariant.danger,
100
+ });
101
+ return;
102
+ }
103
+
104
+ try {
105
+ // Check if infraEnv already exists
106
+ if (disconnectedInfraEnv && disconnectedInfraEnv.id) {
107
+ // Update existing infraEnv
108
+ const updateParams = {
109
+ pullSecret: values.pullSecret || pullSecret,
110
+ ...(values.sshPublicKey && { sshAuthorizedKey: values.sshPublicKey }),
111
+ };
112
+ const { data: updatedInfraEnv } = await InfraEnvsAPI.update(
113
+ disconnectedInfraEnv.id,
114
+ updateParams,
115
+ );
116
+ setDisconnectedInfraEnv(updatedInfraEnv);
117
+ moveNext();
118
+ } else {
119
+ // Create infraEnv with all params
120
+ const createParams = {
121
+ name: `disconnected-cluster_infra-env`,
122
+ pullSecret: values.pullSecret || pullSecret,
123
+ clusterId: cluster.id,
124
+ openshiftVersion: '4.19',
125
+ cpuArchitecture: 'x86_64' as const,
126
+ ...(values.sshPublicKey && { sshAuthorizedKey: values.sshPublicKey }),
127
+ };
128
+ const createdInfraEnv = await InfraEnvsService.create(createParams);
129
+ setDisconnectedInfraEnv(createdInfraEnv);
130
+ moveNext();
131
+ }
132
+ } catch (error) {
133
+ handleApiError(error, () => {
134
+ addAlert({
135
+ title:
136
+ disconnectedInfraEnv && disconnectedInfraEnv.id
137
+ ? 'Failed to update infrastructure environment'
138
+ : 'Failed to create infrastructure environment',
139
+ message: getApiErrorMessage(error),
140
+ variant: AlertVariant.danger,
141
+ });
142
+ });
143
+ }
144
+ }}
145
+ >
146
+ {({ submitForm, isValid, errors, touched, isSubmitting }) => {
147
+ const errorFields = getFormikErrorFields(errors, touched);
148
+ const handleNext = () => {
149
+ void submitForm(); // This will trigger onSubmit
150
+ };
151
+
152
+ return (
153
+ <ClusterWizardStep
154
+ navigation={<ClusterWizardNavigation />}
155
+ footer={
156
+ <ClusterWizardFooter
157
+ disconnectedClusterId={clusterId}
158
+ onNext={handleNext} // Changed from moveNext
159
+ onBack={moveBack}
160
+ isNextDisabled={!isValid}
161
+ errorFields={errorFields}
162
+ isSubmitting={isSubmitting}
163
+ />
164
+ }
165
+ >
166
+ <WithErrorBoundary title="Failed to load Optional Configurations step">
167
+ <PullSecretSync pullSecret={pullSecret} />
168
+ <Grid hasGutter>
169
+ <GridItem>
170
+ <Split>
171
+ <SplitItem>
172
+ <Content component="h2">Optional configurations</Content>
173
+ </SplitItem>
174
+ <SplitItem>
175
+ <DeveloperPreview />
176
+ </SplitItem>
177
+ </Split>
178
+ </GridItem>
179
+ <GridItem>
180
+ <Form id="wizard-cluster-optional-configurations__form">
181
+ <UploadSSH />
182
+ <Checkbox
183
+ label="Edit pull secret"
184
+ isChecked={editPullSecret}
185
+ onChange={(_, checked) => setEditPullSecret(checked)}
186
+ id="edit-pull-secret-checkbox"
187
+ />
188
+ {(editPullSecret || !pullSecret) && <PullSecretField isOcm={isInOcm} />}
189
+ </Form>
190
+ </GridItem>
191
+ </Grid>
192
+ </WithErrorBoundary>
193
+ </ClusterWizardStep>
194
+ );
195
+ }}
196
+ </Formik>
197
+ );
198
+ };
199
+
200
+ export default OptionalConfigurationsStep;
@@ -26,15 +26,18 @@ import {
26
26
  } from '@patternfly/react-core';
27
27
  import { Formik } from 'formik';
28
28
  import { saveAs } from 'file-saver';
29
- import { useNavigate } from 'react-router-dom-v5-compat';
29
+ import { useNavigate, useParams } from 'react-router-dom-v5-compat';
30
30
 
31
31
  import { getOperatorSpecs } from '../../../../common/components/operators/operatorSpecs';
32
-
33
- const downloadUrl =
34
- 'https://mirror.openshift.com/pub/cgw/assisted-installer-disconnected/latest/agent-ove.x86_64.iso';
32
+ import ClustersService from '../../../services/ClustersService';
33
+ import { handleApiError, getApiErrorMessage } from '../../../../common/api';
34
+ import { useAlerts } from '../../../../common/components/AlertsContextProvider';
35
+ import { AlertVariant } from '@patternfly/react-core';
35
36
 
36
37
  const ReviewStep = () => {
37
- const { moveBack } = useClusterWizardContext();
38
+ const { moveBack, disconnectedInfraEnv, setDisconnectedInfraEnv } = useClusterWizardContext();
39
+ const { clusterId } = useParams<{ clusterId: string }>();
40
+ const { addAlert } = useAlerts();
38
41
  const opSpecs = getOperatorSpecs(() => undefined);
39
42
  const navigate = useNavigate();
40
43
 
@@ -50,11 +53,40 @@ const ReviewStep = () => {
50
53
  footer={
51
54
  <ClusterWizardFooter
52
55
  onNext={() => {
53
- downloadUrl && saveAs(downloadUrl);
54
- navigate('/cluster-list');
56
+ void (async () => {
57
+ if (disconnectedInfraEnv?.downloadUrl) {
58
+ saveAs(disconnectedInfraEnv.downloadUrl);
59
+ }
60
+ if (clusterId) {
61
+ try {
62
+ await ClustersService.remove(clusterId);
63
+ // Remove infraEnv from wizard context after successful deregistration
64
+ setDisconnectedInfraEnv(undefined);
65
+ // Navigate to cluster-list only after successful deregistration
66
+ navigate('/cluster-list');
67
+ } catch (error) {
68
+ handleApiError(error, () => {
69
+ addAlert({
70
+ title: 'Failed to deregister cluster',
71
+ message: getApiErrorMessage(error),
72
+ variant: AlertVariant.danger,
73
+ });
74
+ });
75
+ // Error handling: continue with navigation even if deregistration fails
76
+ // Still clear the context to avoid stale data
77
+ setDisconnectedInfraEnv(undefined);
78
+ // Navigate even on error to avoid getting stuck
79
+ navigate('/cluster-list');
80
+ }
81
+ } else {
82
+ // If there's no cluster to deregister, navigate immediately
83
+ navigate('/cluster-list');
84
+ }
85
+ })();
55
86
  }}
56
87
  onBack={moveBack}
57
88
  nextButtonText="Download ISO"
89
+ disconnectedClusterId={clusterId}
58
90
  />
59
91
  }
60
92
  >
@@ -2,4 +2,5 @@ export { default as ClusterDetailsForm } from './ClusterDetailsForm';
2
2
  export { default as NewClusterWizard } from './NewClusterWizard';
3
3
  export { default as ClusterWizard } from './ClusterWizard';
4
4
  export { default as ClusterWizardContextProvider } from './ClusterWizardContextProvider';
5
+ export { useClusterWizardContext } from './ClusterWizardContext';
5
6
  export { OpenShiftVersionsContextProvider } from './OpenShiftVersionsContext';
@@ -31,7 +31,8 @@ export type ClusterWizardStepsType =
31
31
  | 'custom-manifests'
32
32
  | 'credentials-download'
33
33
  | 'disconnected-basic'
34
- | 'disconnected-review';
34
+ | 'disconnected-review'
35
+ | 'disconnected-optional-configurations';
35
36
 
36
37
  const wizardStepsOrder: ClusterWizardStepsType[] = [
37
38
  'cluster-details',
@@ -49,6 +50,7 @@ const wizardStepsOrder: ClusterWizardStepsType[] = [
49
50
 
50
51
  export const disconnectedSteps: ClusterWizardStepsType[] = [
51
52
  'disconnected-basic',
53
+ 'disconnected-optional-configurations',
52
54
  'disconnected-review',
53
55
  ];
54
56
 
@@ -260,6 +262,7 @@ const credentialsValidationMap = buildEmptyValidationsMap();
260
262
  const customManifestsValidationsMap = buildEmptyValidationsMap();
261
263
  const disconnectedReviewValidationsMap = buildEmptyValidationsMap();
262
264
  const disconnectedBasicValidationsMap = buildEmptyValidationsMap();
265
+ const disconnectedOptionalConfigurationsValidationsMap = buildEmptyValidationsMap();
263
266
 
264
267
  export const wizardStepsValidationsMap: WizardStepsValidationMap<ClusterWizardStepsType> = {
265
268
  'cluster-details': clusterDetailsStepValidationsMap,
@@ -275,6 +278,7 @@ export const wizardStepsValidationsMap: WizardStepsValidationMap<ClusterWizardSt
275
278
  'credentials-download': credentialsValidationMap,
276
279
  'disconnected-review': disconnectedReviewValidationsMap,
277
280
  'disconnected-basic': disconnectedBasicValidationsMap,
281
+ 'disconnected-optional-configurations': disconnectedOptionalConfigurationsValidationsMap,
278
282
  };
279
283
 
280
284
  export const allClusterWizardSoftValidationIds =
@@ -40,9 +40,10 @@ const HostRowDetailExpand = ({ obj: host }: ExpandComponentProps<Host>) => (
40
40
 
41
41
  type HostsDiscoveryTableProps = {
42
42
  cluster: Cluster;
43
+ hosts: Host[];
43
44
  };
44
45
 
45
- const HostsDiscoveryTable = ({ cluster }: HostsDiscoveryTableProps) => {
46
+ const HostsDiscoveryTable = ({ cluster, hosts }: HostsDiscoveryTableProps) => {
46
47
  const {
47
48
  onEditHost,
48
49
  actionChecks,
@@ -55,6 +56,7 @@ const HostsDiscoveryTable = ({ cluster }: HostsDiscoveryTableProps) => {
55
56
  onMassDeleteHost,
56
57
  ...modalProps
57
58
  } = useHostsTable(cluster);
59
+
58
60
  const { wizardPerPage, setWizardPerPage } = useClusterWizardContext();
59
61
 
60
62
  const { isViewerMode } = useSelector(selectCurrentClusterPermissionsState);
@@ -83,7 +85,6 @@ const HostsDiscoveryTable = ({ cluster }: HostsDiscoveryTableProps) => {
83
85
  [t, onEditHost, actionChecks.canEditHostname, actionChecks.canEditRole, onEditRole, cluster],
84
86
  );
85
87
 
86
- const hosts = cluster.hosts || [];
87
88
  const paginationProps = usePagination(hosts.length, wizardPerPage, setWizardPerPage);
88
89
  const itemIDs = hosts.map((h) => h.id);
89
90
  const showBulkActions = !(isViewerMode || isSNOCluster);
@@ -5,4 +5,5 @@ export { default as usePullSecret } from './usePullSecret';
5
5
  export { default as useClusterPreflightRequirements } from './useClusterPreflightRequirements';
6
6
  export { default as useUISettings } from './useUISettings';
7
7
  export { default as useInfraEnv } from './useInfraEnv';
8
+ export { default as useInfraEnvHosts } from './useInfraEnvHosts';
8
9
  export { useFeatureDetection } from './use-feature-detection';
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import useInfraEnvId from './useInfraEnvId';
3
+ import { CpuArchitecture, DEFAULT_POLLING_INTERVAL } from '../../common';
4
+ import { getErrorMessage } from '../../common/utils';
5
+ import { InfraEnvsAPI } from '../services/apis';
6
+ import InfraEnvIdsCacheService from '../services/InfraEnvIdsCacheService';
7
+ import { Cluster, Host } from '@openshift-assisted/types/assisted-installer-service';
8
+
9
+ const useInfraEnvHosts = (
10
+ clusterId: Cluster['id'],
11
+ cpuArchitecture: CpuArchitecture,
12
+ clusterName?: string,
13
+ pullSecret?: string,
14
+ openshiftVersion?: string,
15
+ ) => {
16
+ const [hosts, setHosts] = React.useState<Host[]>();
17
+ const [error, setError] = React.useState('');
18
+ const { infraEnvId, error: infraEnvIdError } = useInfraEnvId(
19
+ clusterId,
20
+ cpuArchitecture,
21
+ clusterName,
22
+ pullSecret,
23
+ openshiftVersion,
24
+ );
25
+
26
+ const getHosts = React.useCallback(async () => {
27
+ try {
28
+ if (infraEnvId) {
29
+ const { data: hostsData } = await InfraEnvsAPI.getHosts(infraEnvId);
30
+ setHosts(hostsData);
31
+ }
32
+ } catch (e) {
33
+ // Invalidate this cluster's cached data
34
+ InfraEnvIdsCacheService.removeInfraEnvId(clusterId, cpuArchitecture);
35
+ setError(getErrorMessage(e));
36
+ }
37
+ }, [clusterId, cpuArchitecture, infraEnvId]);
38
+
39
+ React.useEffect(() => {
40
+ if (infraEnvIdError) {
41
+ setHosts(undefined);
42
+ setError(infraEnvIdError);
43
+ } else {
44
+ if (infraEnvId) {
45
+ // Initial fetch
46
+ void getHosts();
47
+
48
+ // Set up polling to refetch hosts periodically
49
+ const intervalId = setInterval(() => {
50
+ void getHosts();
51
+ }, DEFAULT_POLLING_INTERVAL);
52
+
53
+ return () => clearInterval(intervalId);
54
+ }
55
+ }
56
+ }, [getHosts, infraEnvId, infraEnvIdError]);
57
+
58
+ return { hosts, error, isLoading: !hosts && !error };
59
+ };
60
+ export default useInfraEnvHosts;
@@ -7,6 +7,7 @@ import {
7
7
  InfraEnv,
8
8
  InfraEnvCreateParams,
9
9
  } from '@openshift-assisted/types/assisted-installer-service';
10
+ import { useFeature } from './use-feature';
10
11
 
11
12
  export default function useInfraEnvId(
12
13
  clusterId: Cluster['id'],
@@ -17,10 +18,15 @@ export default function useInfraEnvId(
17
18
  ) {
18
19
  const [infraEnvId, setInfraEnv] = React.useState<InfraEnv['id']>();
19
20
  const [error, setError] = React.useState('');
21
+ const isSingleClusterFeatureEnabled = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE');
20
22
 
21
23
  const findInfraEnvId = React.useCallback(async () => {
22
24
  try {
23
- const infraEnvId = await InfraEnvsService.getInfraEnvId(clusterId, cpuArchitecture);
25
+ const infraEnvId = await InfraEnvsService.getInfraEnvId(
26
+ clusterId,
27
+ cpuArchitecture,
28
+ isSingleClusterFeatureEnabled,
29
+ );
24
30
  if (infraEnvId && !(infraEnvId instanceof Error)) {
25
31
  setInfraEnv(infraEnvId);
26
32
  } else {
@@ -39,7 +45,14 @@ export default function useInfraEnvId(
39
45
  } catch (e) {
40
46
  setError(getErrorMessage(e));
41
47
  }
42
- }, [clusterId, cpuArchitecture, clusterName, pullSecret, openshiftVersion]);
48
+ }, [
49
+ clusterId,
50
+ cpuArchitecture,
51
+ isSingleClusterFeatureEnabled,
52
+ pullSecret,
53
+ clusterName,
54
+ openshiftVersion,
55
+ ]);
43
56
 
44
57
  React.useEffect(() => {
45
58
  if (!clusterId) {
@@ -0,0 +1,71 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { Cluster } from '@openshift-assisted/types/assisted-installer-service';
3
+ import { CpuArchitecture, useAlerts } from '../../common';
4
+ import useInfraEnvHosts from './useInfraEnvHosts';
5
+ import HostsService from '../services/HostsService';
6
+ import { getErrorMessage } from '../../common/utils';
7
+ import { useFeature } from './use-feature';
8
+
9
+ const useLateBinding = (cluster: Cluster): boolean => {
10
+ const [isBinding, setIsBinding] = useState(false);
11
+ const { addAlert, removeAlert, alerts } = useAlerts();
12
+ const isSingleClusterFeatureEnabled = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE');
13
+
14
+ const {
15
+ hosts: infraEnvHosts,
16
+ error: infraEnvError,
17
+ isLoading: infraEnvLoading,
18
+ } = useInfraEnvHosts(
19
+ isSingleClusterFeatureEnabled ? cluster.id : '',
20
+ cluster.cpuArchitecture
21
+ ? (cluster.cpuArchitecture as CpuArchitecture)
22
+ : CpuArchitecture.USE_DAY1_ARCHITECTURE,
23
+ cluster.name,
24
+ undefined,
25
+ cluster.openshiftVersion,
26
+ );
27
+
28
+ const bindHosts = useCallback(async () => {
29
+ if (infraEnvHosts && !infraEnvError && !infraEnvLoading && isSingleClusterFeatureEnabled) {
30
+ for (const host of infraEnvHosts) {
31
+ if (host.clusterId !== cluster.id) {
32
+ setIsBinding(true);
33
+ try {
34
+ const alertKey = alerts.find((alert) => alert.key === host.id)?.key;
35
+ if (alertKey) {
36
+ removeAlert(alertKey);
37
+ }
38
+ await HostsService.bind(host, cluster.id);
39
+ } catch (error) {
40
+ addAlert({
41
+ title: `Failed to bind host ${host.requestedHostname || ''}`,
42
+ message: getErrorMessage(error),
43
+ key: host.id,
44
+ });
45
+ } finally {
46
+ setIsBinding(false);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ }, [
53
+ infraEnvHosts,
54
+ infraEnvError,
55
+ infraEnvLoading,
56
+ isSingleClusterFeatureEnabled,
57
+ cluster.id,
58
+ addAlert,
59
+ removeAlert,
60
+ ]);
61
+
62
+ useEffect(() => {
63
+ if (isSingleClusterFeatureEnabled) {
64
+ void bindHosts();
65
+ }
66
+ }, [isSingleClusterFeatureEnabled, bindHosts]);
67
+
68
+ return infraEnvLoading || isBinding;
69
+ };
70
+
71
+ export default useLateBinding;
@@ -26,7 +26,11 @@ const ClustersService = {
26
26
  return hosts.find((host) => host.id === hostId);
27
27
  },
28
28
 
29
- async create(params: ClusterCreateParamsWithStaticNetworking, isAssistedMigration?: boolean) {
29
+ async create(
30
+ params: ClusterCreateParamsWithStaticNetworking,
31
+ isAssistedMigration?: boolean,
32
+ isSingleClusterFeatureEnabled?: boolean,
33
+ ) {
30
34
  if (isAssistedMigration) {
31
35
  params.tags = AI_ASSISTED_MIGRATION_TAG;
32
36
  }
@@ -44,7 +48,9 @@ const ClustersService = {
44
48
  infraEnvCreateParams.imageType = 'minimal-iso';
45
49
  }
46
50
 
47
- await InfraEnvsService.create(infraEnvCreateParams);
51
+ if (!isSingleClusterFeatureEnabled) {
52
+ await InfraEnvsService.create(infraEnvCreateParams);
53
+ }
48
54
 
49
55
  return cluster;
50
56
  },
@@ -165,5 +171,11 @@ const ClustersService = {
165
171
  this.getUpdateManifestParams(existingManifest, updatedManifest),
166
172
  );
167
173
  },
174
+ registerDisconnected(params: { name: string; openshiftVersion: string }) {
175
+ return ClustersAPI.registerDisconnected({
176
+ name: params.name,
177
+ openshiftVersion: params.openshiftVersion,
178
+ });
179
+ },
168
180
  };
169
181
  export default ClustersService;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ Cluster,
2
3
  Disk,
3
4
  DiskRole,
4
5
  Host,
@@ -84,6 +85,14 @@ const HostsService = {
84
85
  return HostsAPI.installHost(host.infraEnvId, host.id);
85
86
  },
86
87
 
88
+ bind(host: Host, clusterId: Cluster['id']) {
89
+ if (!host.infraEnvId) {
90
+ throw new Error(`Cannot bind host ${host.id}, missing infraEnvId`);
91
+ }
92
+
93
+ return HostsAPI.bindHost(host.infraEnvId, host.id, clusterId);
94
+ },
95
+
87
96
  installAll(hosts: Host[]) {
88
97
  const promises = [];
89
98
  for (const host of hosts) {
@@ -14,10 +14,13 @@ const InfraEnvsService = {
14
14
  async getInfraEnvId(
15
15
  clusterId: Cluster['id'],
16
16
  cpuArchitecture: CpuArchitecture,
17
+ isSingleClusterFeatureEnabled?: boolean,
17
18
  ): Promise<string | Error> {
18
19
  let infraEnvId = InfraEnvCache.getInfraEnvId(clusterId, cpuArchitecture);
19
20
  if (infraEnvId instanceof Error) {
20
- const { data: infraEnvs } = await InfraEnvsAPI.list(clusterId);
21
+ const { data: infraEnvs } = await InfraEnvsAPI.list(
22
+ !isSingleClusterFeatureEnabled ? clusterId : '',
23
+ );
21
24
  if (infraEnvs.length > 0) {
22
25
  InfraEnvCache.updateInfraEnvs(clusterId, infraEnvs);
23
26
  infraEnvId = InfraEnvCache.getInfraEnvId(clusterId, cpuArchitecture);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
- "@openshift-assisted/locales": "2.47.4",
4
- "@openshift-assisted/types": "2.47.4",
3
+ "@openshift-assisted/locales": "2.48.0",
4
+ "@openshift-assisted/types": "2.48.0",
5
5
  "@openshift-console/dynamic-plugin-sdk": "0.0.3",
6
6
  "@patternfly/patternfly": "6.2.3",
7
7
  "@patternfly/react-code-editor": "6.2.2",
@@ -140,5 +140,5 @@
140
140
  ]
141
141
  }
142
142
  },
143
- "version": "2.47.4"
143
+ "version": "2.48.0"
144
144
  }