From b75e8d55a8ac1e6efb11e450901f2db3ada3914d Mon Sep 17 00:00:00 2001 From: Elay Aharoni Date: Mon, 7 Apr 2025 12:32:47 +0300 Subject: [PATCH 1/2] refactor kubeconfig download to credentials download Signed-off-by: Elay Aharoni --- .../clusterWizard/CredentialsDownload.tsx | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx new file mode 100644 index 0000000000..8332ff6b66 --- /dev/null +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { + ClusterCredentials, + ClustersAPI, + ClusterWizardStep, + ClusterWizardStepHeader, + getApiErrorMessage, + handleApiError, + useAlerts, + useTranslation, +} from '../../../common'; +import { useClusterWizardContext } from './ClusterWizardContext'; +import ClusterWizardFooter from './ClusterWizardFooter'; +import ClusterWizardNavigation from './ClusterWizardNavigation'; +import { WithErrorBoundary } from '../../../common/components/ErrorHandling/WithErrorBoundary'; +import { Cluster, Credentials } from '@openshift-assisted/types/assisted-installer-service'; +import { Alert, AlertVariant, Button, ButtonVariant, Checkbox, Grid } from '@patternfly/react-core'; +import { downloadFile, getErrorMessage } from '../../../common/utils'; +import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/js/icons/external-link-alt-icon'; + +const CredentialsDownload: React.FC<{ cluster: Cluster }> = ({ cluster }) => { + const [isChecked, setIsChecked] = React.useState(false); + const clusterWizardContext = useClusterWizardContext(); + const [credentials, setCredentials] = React.useState(); + const [credentialsError, setCredentialsError] = React.useState(''); + const [isDownloading, setIsDownloading] = React.useState(false); + const { addAlert } = useAlerts(); + const { t } = useTranslation(); + + // ========== mocks ============= + const username = 'elay'; + const password = 'password'; + const credentialsData = { username, password }; + + // const fetchCredentials = React.useCallback(() => { + // const fetch = async () => { + // setCredentialsError(''); + // if (!cluster.id) { + // return; + // } + // try { + // const response = await ClustersAPI.getCredentials(cluster.id); + // setCredentials(response.data); + // } catch (err) { + // setCredentialsError(getErrorMessage(err)); + // } + // }; + // void fetch(); + // }, [cluster]); + + // React.useEffect(() => { + // fetchCredentials(); + // }, [fetchCredentials]); + + const downloadFiles = async () => { + setIsDownloading(true); + try { + const response = await ClustersAPI.downloadClusterCredentials(cluster.id, 'kubeconfig'); + downloadFile('', response.data, 'kubeconfig'); + } catch (e) { + handleApiError(e, (e) => { + addAlert({ + title: t('ai:Could not download kubeconfig'), + message: getApiErrorMessage(e), + }); + }); + } + try { + const response2 = await ClustersAPI.downloadClusterCredentials( + cluster.id, + 'kubeadmin-password', + ); + downloadFile('', response2.data, 'credentials'); + } catch (e) { + handleApiError(e, (e) => { + addAlert({ + title: t('ai:Could not download credentials'), + message: getApiErrorMessage(e), + }); + }); + } finally { + setIsDownloading(false); + } + }; + + const handleDownloadClick = () => { + void downloadFiles(); + !isDownloading && clusterWizardContext.moveNext(); + }; + + const footer = ( + clusterWizardContext.moveBack()} + isNextDisabled={!isChecked || isDownloading} + nextButtonText="Download credentials" + /> + ); + + return ( + } footer={footer}> + + Download credentials + console.log('hi')} isInline> + {t('ai:Learn more about Kubeconfig')} + + + } + > + You should use your cluster's Kubeconfig file to gain access to the cluster. +
+ You can use username and password to log-in to your console through the UI. +
+ setIsChecked(!isChecked)} + label={t( + 'ai:I understand that I need to download credentials files prior of proceeding with the cluster installation.', + )} + /> + + + +
+
+ ); +}; + +export default CredentialsDownload; From 18b9681e1ae11d6afbfcedeeb2ef9f7843700446 Mon Sep 17 00:00:00 2001 From: Elay Aharoni Date: Wed, 9 Apr 2025 12:05:35 +0300 Subject: [PATCH 2/2] Download credentials page Signed-off-by: Elay Aharoni --- libs/locales/lib/en/translation.json | 8 + .../clusterDetail/ClusterCredentials.tsx | 14 +- .../lib/common/components/ui/WizardFooter.tsx | 3 + libs/ui-lib/lib/common/config/docs_links.ts | 3 + .../clusterWizard/ClusterWizard.tsx | 6 +- .../ClusterWizardContextProvider.tsx | 2 +- .../clusterWizard/CredentialsDownload.tsx | 165 +++++++++--------- .../ocm/components/clusterWizard/constants.ts | 2 +- .../clusterWizard/wizardTransition.ts | 8 +- 9 files changed, 114 insertions(+), 97 deletions(-) diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index f5dc9c123f..417ccef6fe 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -219,6 +219,7 @@ "ai:Control plane nodes: At least {{master_cpu_cores}} CPU cores, {{master_ram}} RAM, {{master_disksize}} GB disk size for every control plane node.": "Control plane nodes: At least {{master_cpu_cores}} CPU cores, {{master_ram}} RAM, {{master_disksize}} GB disk size for every control plane node.", "ai:Control plane pods": "Control plane pods", "ai:Controller availability policy": "Controller availability policy", + "ai:Could not download {{fileName}} file": "Could not download {{fileName}} file", "ai:Could not download kubeconfig": "Could not download kubeconfig", "ai:Could not fetch infra environments": "Could not fetch infra environments", "ai:Could not fetch pull secret": "Could not fetch pull secret", @@ -268,6 +269,7 @@ "ai:DNS domain": "DNS domain", "ai:DNS wildcard not configured": "DNS wildcard not configured", "ai:Do not use forbidden words, for example: \"localhost\".": "Do not use forbidden words, for example: \"localhost\".", + "ai:Download credentials": "Download credentials", "ai:Download Discovery ISO": "Download Discovery ISO", "ai:Download host discovery ISO dialog": "Download host discovery ISO dialog", "ai:Download host logs": "Download host logs", @@ -327,6 +329,7 @@ "ai:Failed to download the discovery Image": "Failed to download the discovery Image", "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", "ai:Failed to patch assisted-image-service route for new domain.": "Failed to patch assisted-image-service route for new domain.", "ai:Failed to reset cluster installation": "Failed to reset cluster installation", "ai:Failed to save ClusterDeployment": "Failed to save ClusterDeployment", @@ -411,6 +414,7 @@ "ai:HTTPS proxy": "HTTPS proxy", "ai:HTTPS proxy URL": "HTTPS proxy URL", "ai:HTTPS Proxy URL": "HTTPS Proxy URL", + "ai:I understand that I need to download credentials files prior of proceeding with the cluster installation.": "I understand that I need to download credentials files prior of proceeding with the cluster installation.", "ai:I understand, accept, and agree to the limitations associated with using Single Node OpenShift.": "I understand, accept, and agree to the limitations associated with using Single Node OpenShift.", "ai:If hosts are behind a firewall that requires the use of a proxy, provide additional information about the proxy.": "If hosts are behind a firewall that requires the use of a proxy, provide additional information about the proxy.", "ai:If not, please start your VMs with the following configuration:": "If not, start your VMs with the following configuration:", @@ -488,6 +492,7 @@ "ai:Learn more": "Learn more", "ai:Learn more about configuration.": "Learn more about configuration.", "ai:Learn more about enabling CIM on AWS <1>": "Learn more about enabling CIM on AWS <1>", + "ai:Learn more about Kubeconfig": "Learn more about Kubeconfig", "ai:Learn more about MTU (maximum transmission unit)": "Learn more about MTU (maximum transmission unit)", "ai:Learn more about OpenShift releases": "Learn more about OpenShift releases", "ai:Learn more about pull secrets and view examples": "Learn more about pull secrets and view examples", @@ -511,6 +516,7 @@ "ai:Machine networks": "Machine networks", "ai:Make sure that the VIP is unique and not used by any other device on your network.": "Make sure that the VIP is unique and not used by any other device on your network.", "ai:Make sure that you expect and recognize the host before approving.": "Make sure that you expect and recognize the host before approving.", + "ai:Make sure you download and store your credentials files in a safe place": "Make sure you download and store your credentials files in a safe place", "ai:Manage hosts": "Manage hosts", "ai:Management": "Management", "ai:Manually fix the host's NTP configuration or provide additional NTP sources.": "Manually fix the host's NTP configuration or provide additional NTP sources.", @@ -942,7 +948,9 @@ "ai:You are approving multiple hosts. All hosts listed below will be approved to join the infrastructure environment if you continue. Make sure that you expect and recognize the hosts before approving.": "You are approving multiple hosts. All hosts listed will be approved to join the infrastructure environment, if you continue. Make sure that you expect and recognize the hosts before approving.", "ai:You can either wait or investigate. A common issue can be misconfigured storage. Once you fix the issue, you can delete AgentServiceConfig to try again.": "You can either wait or investigate. A common issue can be misconfigured storage. After you fix the issue, delete AgentServiceConfig and try again.", "ai:You can upload additional trusted certificates in PEM-encoded X.509 format if the cluster hosts are in a network with a re-encrypting (MITM) proxy or the cluster needs to trust certificates for other purposes (for example, container image registries).": "You can upload additional trusted certificates in PEM-encoded X.509 format if the cluster hosts are in a network with a re-encrypting (MITM) proxy or the cluster needs to trust certificates for other purposes (for example, container image registries).", + "ai:You can use username and password to log-in to your console through the UI.": "You can use username and password to log-in to your console through the UI.", "ai:You have opted out of formatting bootable disks on some hosts. To ensure the hosts reboot into the expected installation disk, manual user intervention may be required during OpenShift installation.": "You have opted out of formatting bootable disks on some hosts. To ensure the hosts reboot into the expected installation disk, manual user intervention might be required during OpenShift installation.", + "ai:You should use your cluster's Kubeconfig file to gain access to the cluster.": "You should use your cluster's Kubeconfig file to gain access to the cluster.", "ai:You'll first need to have a storage operator in order to create the storage class. If you don't have one installed, we recommend OpenShift Data Foundation operator, but you may use any.": "You need a storage operator installed, such as OpenShift Data Foundation, to create the storage class.", "ai:Your libvirt virtual machines should be configured to restart automatically after a reboot. You can check this by running:": "Your libvirt virtual machines should be configured to restart automatically after a reboot. You can check this by running:", "ai:Your own NTP (Network Time Protocol) sources": "Your own NTP (Network Time Protocol) sources" diff --git a/libs/ui-lib/lib/common/components/clusterDetail/ClusterCredentials.tsx b/libs/ui-lib/lib/common/components/clusterDetail/ClusterCredentials.tsx index 75a138a8a6..45971bfc08 100644 --- a/libs/ui-lib/lib/common/components/clusterDetail/ClusterCredentials.tsx +++ b/libs/ui-lib/lib/common/components/clusterDetail/ClusterCredentials.tsx @@ -1,5 +1,12 @@ import React from 'react'; -import { GridItem, ClipboardCopy, clipboardCopyFunc, Button, Alert } from '@patternfly/react-core'; +import { + GridItem, + ClipboardCopy, + clipboardCopyFunc, + Button, + Alert, + TextInput, +} from '@patternfly/react-core'; import { Credentials, Cluster } from '@openshift-assisted/types/assisted-installer-service'; import { LoadingState, ErrorState } from '../../components/ui/uiState'; import { DetailList, DetailItem } from '../../components/ui/DetailList'; @@ -98,7 +105,10 @@ const ClusterCredentials: React.FC = ({ )} {credentials.username && ( <> - + } + /> = ({ nextButtonText, cluster, onFetchEvents, + isNextButtonLoading, }) => { const { t } = useTranslation(); submittingText = submittingText || t('ai:Saving changes...'); @@ -68,6 +70,7 @@ export const WizardFooter: React.FC = ({ name="next" onClick={onNext} isDisabled={isNextDisabled} + isLoading={isNextButtonLoading} > {nextButtonText || t('ai:Next')} diff --git a/libs/ui-lib/lib/common/config/docs_links.ts b/libs/ui-lib/lib/common/config/docs_links.ts index 2114a14de4..a24140159e 100644 --- a/libs/ui-lib/lib/common/config/docs_links.ts +++ b/libs/ui-lib/lib/common/config/docs_links.ts @@ -182,3 +182,6 @@ export const getServiceMeshLink = (ocpVersion?: string) => export const SERVERLESS_OPERATOR_LINK = 'https://docs.openshift.com/serverless/1.28/install/install-serverless-operator.html#serverless-install-web-console_install-serverless-operator'; + +export const KUBECONFIG_INFO_LINK = + 'https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/cli_tools/openshift-cli-oc#about-switches-between-cli-profiles_managing-cli-profiles'; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizard.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizard.tsx index 2d1201d121..ef5221f033 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizard.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizard.tsx @@ -16,7 +16,7 @@ import { InfraEnv, InfraEnvUpdateParams, } from '@openshift-assisted/types/assisted-installer-service'; -import KubeconfigDownload from './KubeconfigDownload'; +import CredentialsDownload from './CredentialsDownload'; type ClusterWizardProps = { cluster: Cluster; @@ -45,8 +45,8 @@ const ClusterWizard = ({ cluster, infraEnv, updateInfraEnv }: ClusterWizardProps case 'static-ip-network-wide-configurations': case 'static-ip-yaml-view': return ; - case 'kubeconfig-download': - return ; + case 'credentials-download': + return ; case 'cluster-details': default: return ; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx index e3508fe006..5aaace5f11 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx @@ -69,7 +69,7 @@ const getWizardStepIds = ( stepsCopy = removeStepFromClusterWizard(stepsCopy, 'custom-manifests', 1); } if (isSingleClusterFeatureEnabled) { - stepsCopy = addStepToClusterWizard(stepsCopy, 'networking', ['kubeconfig-download']); + stepsCopy = addStepToClusterWizard(stepsCopy, 'networking', ['credentials-download']); } return stepsCopy; diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx index 8332ff6b66..e0ba6da0db 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/CredentialsDownload.tsx @@ -6,6 +6,7 @@ import { ClusterWizardStepHeader, getApiErrorMessage, handleApiError, + KUBECONFIG_INFO_LINK, useAlerts, useTranslation, } from '../../../common'; @@ -14,126 +15,118 @@ import ClusterWizardFooter from './ClusterWizardFooter'; import ClusterWizardNavigation from './ClusterWizardNavigation'; import { WithErrorBoundary } from '../../../common/components/ErrorHandling/WithErrorBoundary'; import { Cluster, Credentials } from '@openshift-assisted/types/assisted-installer-service'; -import { Alert, AlertVariant, Button, ButtonVariant, Checkbox, Grid } from '@patternfly/react-core'; +import { Alert, AlertVariant, Checkbox, Grid, Stack } from '@patternfly/react-core'; import { downloadFile, getErrorMessage } from '../../../common/utils'; import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/js/icons/external-link-alt-icon'; const CredentialsDownload: React.FC<{ cluster: Cluster }> = ({ cluster }) => { - const [isChecked, setIsChecked] = React.useState(false); const clusterWizardContext = useClusterWizardContext(); - const [credentials, setCredentials] = React.useState(); - const [credentialsError, setCredentialsError] = React.useState(''); + const [isChecked, setIsChecked] = React.useState(false); const [isDownloading, setIsDownloading] = React.useState(false); + const [credentials, setCredentials] = React.useState(); + const [credentialsError, setCredentialsError] = React.useState(); const { addAlert } = useAlerts(); const { t } = useTranslation(); - // ========== mocks ============= - const username = 'elay'; - const password = 'password'; - const credentialsData = { username, password }; - - // const fetchCredentials = React.useCallback(() => { - // const fetch = async () => { - // setCredentialsError(''); - // if (!cluster.id) { - // return; - // } - // try { - // const response = await ClustersAPI.getCredentials(cluster.id); - // setCredentials(response.data); - // } catch (err) { - // setCredentialsError(getErrorMessage(err)); - // } - // }; - // void fetch(); - // }, [cluster]); - - // React.useEffect(() => { - // fetchCredentials(); - // }, [fetchCredentials]); - - const downloadFiles = async () => { - setIsDownloading(true); - try { - const response = await ClustersAPI.downloadClusterCredentials(cluster.id, 'kubeconfig'); - downloadFile('', response.data, 'kubeconfig'); - } catch (e) { - handleApiError(e, (e) => { - addAlert({ - title: t('ai:Could not download kubeconfig'), - message: getApiErrorMessage(e), + React.useEffect(() => { + const fetch = async () => { + setCredentialsError(undefined); + if (!cluster.id) { + return; + } + try { + const response = await ClustersAPI.getCredentials(cluster.id); + const data = { username: response.data.username, password: response.data.password }; + setCredentials(data); + } catch (err) { + handleApiError(err, (err) => { + setCredentialsError(`Failed to fetch credentials, ${getErrorMessage(err)}`); }); - }); - } + } + }; + void fetch(); + }, [cluster.id, setCredentials]); + + const downloadSingleFile = async (fileName: string) => { try { - const response2 = await ClustersAPI.downloadClusterCredentials( - cluster.id, - 'kubeadmin-password', - ); - downloadFile('', response2.data, 'credentials'); + const response = await ClustersAPI.downloadClusterCredentials(cluster.id, fileName); + downloadFile('', response.data, fileName); + return true; } catch (e) { handleApiError(e, (e) => { addAlert({ - title: t('ai:Could not download credentials'), + title: t('ai:Could not download {{fileName}} file', { fileName }), message: getApiErrorMessage(e), }); }); - } finally { - setIsDownloading(false); + return false; } }; - const handleDownloadClick = () => { - void downloadFiles(); - !isDownloading && clusterWizardContext.moveNext(); + const handleDownloadClick = async () => { + setIsDownloading(true); + + const configSuccess = await downloadSingleFile('kubeconfig'); + const passwordSuccess = await downloadSingleFile('kubeadmin-password'); + + const hasError = !configSuccess || !passwordSuccess; + + setTimeout(() => { + setIsDownloading(false); + + if (!hasError) { + clusterWizardContext.moveNext(); + } + }, 500); }; const footer = ( void handleDownloadClick()} onBack={() => clusterWizardContext.moveBack()} isNextDisabled={!isChecked || isDownloading} - nextButtonText="Download credentials" + nextButtonText={t('ai:Download credentials')} + isNextButtonLoading={isDownloading} /> ); return ( } footer={footer}> - Download credentials - console.log('hi')} isInline> - {t('ai:Learn more about Kubeconfig')} - - - } - > - You should use your cluster's Kubeconfig file to gain access to the cluster. -
- You can use username and password to log-in to your console through the UI. -
- setIsChecked(!isChecked)} - label={t( - 'ai:I understand that I need to download credentials files prior of proceeding with the cluster installation.', - )} - /> - - + {t('ai:Download credentials')} + + {t('ai:Learn more about Kubeconfig')} + + } + > + {t(`ai:You should use your cluster's Kubeconfig file to gain access to the cluster.`)} +
+ {t(`ai:You can use username and password to log-in to your console through the UI.`)} +
+ setIsChecked(!isChecked)} + label={t( + 'ai:I understand that I need to download credentials files prior of proceeding with the cluster installation.', + )} /> -
+ + + +
); diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts b/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts index 3b9fb2acd5..abe9984c4e 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/constants.ts @@ -11,7 +11,7 @@ export const wizardStepNames: { [key in ClusterWizardStepsType]: string } = { networking: 'Networking', 'custom-manifests': 'Custom manifests', review: 'Review and create', - 'kubeconfig-download': 'Download Kubeconfig', + 'credentials-download': 'Download credentials', }; export const defaultWizardSteps: ClusterWizardStepsType[] = [ diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts b/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts index 1d2c300780..050cd58577 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts @@ -29,7 +29,7 @@ export type ClusterWizardStepsType = | 'networking' | 'review' | 'custom-manifests' - | 'kubeconfig-download'; + | 'credentials-download'; const wizardStepsOrder: ClusterWizardStepsType[] = [ 'cluster-details', @@ -41,7 +41,7 @@ const wizardStepsOrder: ClusterWizardStepsType[] = [ 'storage', 'networking', 'custom-manifests', - 'kubeconfig-download', + 'credentials-download', 'review', ]; @@ -247,7 +247,7 @@ const reviewStepValidationsMap: WizardStepValidationMap = { softValidationIds: [], }; -const kubeconfigValidationMap = buildEmptyValidationsMap(); +const credentialsValidationMap = buildEmptyValidationsMap(); const customManifestsValidationsMap = buildEmptyValidationsMap(); @@ -262,7 +262,7 @@ export const wizardStepsValidationsMap: WizardStepsValidationMap