diff --git a/src/AppContext.tsx b/src/AppContext.tsx index ed5c41b..0d0a559 100644 --- a/src/AppContext.tsx +++ b/src/AppContext.tsx @@ -1,20 +1,50 @@ import { createContext } from 'react'; import { Domain } from './Api'; +/** + * It represents the application context so common events and properties + * are shared for many components, making their values accessible. + * @public + */ export interface IAppContext { + /** Represent the current list of domains to be displayed in the listDomains view. */ domains: Domain[]; + /** Callback to set the value of `domains`. */ setDomains: (domains: Domain[]) => void; - // wizardDomain?: Domain; - // setWizardDomain: (domain?: Domain) => void; + /** Encapsulates the context related with the wizard. */ + wizard: { + /** Retrieve the current token, required to register a domain. */ + getToken: () => string; + /** Set the value of the token. */ + setToken: (value: string) => void; + /** Get the ephemeral domain state that manage the wizard. */ + getDomain: () => Domain; + /** Set the ephemeral domain information. */ + setDomain: (value: Domain) => void; + }; } +/** + * Represent the application context. + * @public + */ export const AppContext = createContext({ domains: [], setDomains: (domains: Domain[]) => { throw new Error('Function "setDomains" not implemented: domains=' + domains); }, - // wizardDomain: undefined, - // setWizardDomain: (domain?: Domain) => { - // throw new Error('Function "setWizardDomain" not implemented: domain=' + domain); - // }, + wizard: { + getToken: (): string => { + return ''; + }, + setToken: (value: string) => { + throw new Error('Function "setToken" not implemented: value=' + value); + }, + getDomain: (): Domain => { + return {} as Domain; + }, + setDomain: (value: Domain) => { + throw new Error('Function "setDomain" not implemented: value=' + value); + }, + }, }); diff --git a/src/AppEntry.tsx b/src/AppEntry.tsx index 8bf2bf4..4fe8379 100644 --- a/src/AppEntry.tsx +++ b/src/AppEntry.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import { Provider } from 'react-redux'; import { init } from './store'; @@ -9,14 +9,41 @@ import { AppContext } from './AppContext'; import { Domain } from './Api'; const AppEntry = () => { + const appContext = useContext(AppContext); const [domains, setDomains] = useState([]); + const [wizardToken, setWizardToken] = useState(''); + const [wizardDomain, setWizardDomain] = useState({} as Domain); const cbSetDomains = (domains: Domain[]) => { + appContext.domains = domains; setDomains(domains); }; + const cbGetWizardToken = (): string => { + return wizardToken; + }; + const cbSetWizardToken = (value: string) => { + setWizardToken(value); + }; + const cbGetWizardDomain = (): Domain => { + return wizardDomain; + }; + const cbSetWizardDomain = (value: Domain) => { + setWizardDomain(value); + }; return ( - + diff --git a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss index 219c002..acd9576 100644 --- a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss +++ b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss @@ -1,9 +1 @@ @import '~@redhat-cloud-services/frontend-components-utilities/styles/variables'; - -.domain-type-select { - width: 50%; -} - -.domain-item-margin-left { - margin-left: 16px; -} diff --git a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx index 511024a..d340d19 100644 --- a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx +++ b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx @@ -1,26 +1,81 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import ExternalLinkAltIcon from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; -import { Button, ClipboardCopy, Form, FormGroup, Icon, Select, SelectOption, Stack, TextContent } from '@patternfly/react-core'; +import { Button, ClipboardCopy, Form, FormGroup, Icon, Select, SelectOption, TextContent, Title } from '@patternfly/react-core'; import './PagePreparation.scss'; +import { DomainType, ResourcesApiFactory } from '../../../../Api'; +import { AppContext, IAppContext } from '../../../../AppContext'; -const PagePreparation: React.FC = () => { - // TODO Update links - const firewallConfigurationLink = - 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/using-and-configuring-firewalld_configuring-and-managing-networking'; - const cloudProviderConfigurationLink = - 'https://access.redhat.com/documentation/es-es/red_hat_subscription_management/2023/html-single/red_hat_cloud_access_reference_guide/index'; - const networkConfigurationLink = 'https://www.redhat.com/sysadmin/network-interface-linux'; - const installServerPackagesLink = 'https://freeipa.org/page/Quick_Start_Guide'; +/** Represent the properties for PagePreparation component. */ +interface PagePreparationProps { + token?: string; + onToken?: (token: string, domain_id: string, expiration: number) => void; +} + +/** + * This page provide information and links to prepare the rhel-idm + * servers before the user could proceed to the registration process. + * @param props provide the token value and the onToken event to + * notify when it is created. + * @returns Return the view to inform the user how to prepare for + * the domain registration. + * @public + * @see {@link PagePreparationProps} to know about the properties. + * @see {@link WizardPage} to know about the parent component. + */ +const PagePreparation = (props: PagePreparationProps) => { + // FIXME Update the target link when it is known + const installServerPackagesLink = 'https://duckduckgo.com/?q=freeipa+prerequisites'; + // FIXME Update the target link when it is known + const prerequisitesLink = 'https://www.google.com?q=rhel-idm+pre-requisites'; // States - const [isOpen, setIsOpen] = React.useState(false); + const [isOpen, setIsOpen] = useState(false); + const appContext = useContext(AppContext); + + const base_url = '/api/idmsvc/v1'; + const resources_api = ResourcesApiFactory(undefined, base_url, undefined); + + const token = appContext.wizard.getToken(); + const domain = appContext.wizard.getDomain(); + const domain_id = domain.domain_id ? domain.domain_id : ''; + + /** + * side effect to retrieve a token in the background. + * TODO When more than one type of domain, this callback will + * invoke from 'onRegisterDomainTypeSelect' event. + */ + useEffect(() => { + if (token != '' && domain_id != '') { + return; + } + // NOTE await and async cannot be used directly because EffectCallback cannot be a Promise + resources_api + .createDomainToken({ domain_type: 'rhel-idm' }, undefined, undefined) + .then((value) => { + appContext.wizard.setToken(value.data.domain_token); + const domain_id = value.data.domain_id; + const domain_name = 'My domain'; + const domain_type = value.data.domain_type; + const token = value.data.domain_token; + const expiration = value.data.expiration; + appContext.wizard.setDomain({ + domain_id: domain_id, + domain_name: domain_name, + domain_type: domain_type, + }); + props.onToken?.(token, domain_id, expiration); + }) + .catch((reason) => { + // FIXME handle the error here + console.log('error creating the token by createDomainToken: ' + reason); + }); + }, [token, domain_id]); - // hooks const onRegisterDomainTypeSelect = () => { - // TODO Not implemented - console.debug('onRegisterDomainTypeSelect in WizardPage'); + // TODO Not implemented, currently only support rhel-idm + console.debug('PagePreparation.onRegisterDomainTypeSelect in WizardPage'); return; }; @@ -30,29 +85,29 @@ const PagePreparation: React.FC = () => { const domainOptions = [ { - value: 'rhel-idm', - title: 'Red Hat Enterprise Linux IdM/IPA', + value: 'rhel-idm' as DomainType, + title: 'RHEL IdM (IPA)', }, ]; return ( - + <> + Preparation for your directory and domain service
{ - console.debug('onSubmit WizardPage' + String(value)); + console.debug('TODO onSubmit WizardPage' + String(value)); }} > - - - Only Red Hat Linux IdM/IPA are currently supported. + {' '} + Only RHEL IdM (IPA) are currently supported. } > @@ -63,7 +118,7 @@ const PagePreparation: React.FC = () => { onSelect={onRegisterDomainTypeSelect} // onOpenChange={(isOpen) => setIsOpen(isOpen)} onToggle={onToggleClick} - className="domain-type-select" + className="pf-u-w-100 pf-u-w-50-on-md pf-u-w-50-on-xl" > {domainOptions.map((option) => ( @@ -72,57 +127,10 @@ const PagePreparation: React.FC = () => { ))} - - - There are prerequisites that must be completed to create and use security for Red Hat Linux IdM/IPA. If any prerequisites are already in - place, please skip to the next step: - - -
- -
- -
- - 4. Verify wether or not the package is present on your server(st) with this command: - - dnf list installed ipa-hcc-server - - - If the package is not present on your server(s), follow these steps:{' '} + +
    +
  1. + Complete the{' '} +
  2. +
  3. + Verify whether or not the package is present on your server(s) with this command: - dnf copr enable copr.devel.redhat.com/cheimes/ipa-hcc && dnf install ipa-hcc-server + dnf list installed ipa-hcc-server - The package must be installed on at least one IPA server. For redundency, the package should be installed on two or more IPA servers, - possibly all IPA servers. - - + + If the package is not present on your server(s), follow these{' '} + + + dnf copr enable copr.devel.redhat.com/cheimes/ipa-hcc && dnf install ipa-hcc-server + + The package must be installed on at least one IPA server. For redundency, the package should be installed on two or more IPA servers, + possibly all IPA servers. + +
  4. +
-
+ ); }; diff --git a/src/Routes/WizardPage/WizardPage.tsx b/src/Routes/WizardPage/WizardPage.tsx index a8edb05..7699ed1 100644 --- a/src/Routes/WizardPage/WizardPage.tsx +++ b/src/Routes/WizardPage/WizardPage.tsx @@ -1,13 +1,20 @@ -import React, { useEffect, useState } from 'react'; +/** + * This library implement the WizardPage. + * + * The goal is provide the steps to register and add + * a new domain service. + */ +import React, { useContext, useState } from 'react'; import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; -import { Button, Page, PageSection, PageSectionTypes, PageSectionVariants, Wizard } from '@patternfly/react-core'; +import { Button, Page, PageSection, PageSectionTypes, PageSectionVariants, Wizard, WizardStep } from '@patternfly/react-core'; import { PageHeader, PageHeaderTitle } from '@redhat-cloud-services/frontend-components/PageHeader'; import './WizardPage.scss'; import { useNavigate } from 'react-router-dom'; -import { Domain } from '../../Api/api'; +import { Domain, ResourcesApiFactory } from '../../Api/api'; +import { AppContext } from '../../AppContext'; // Lazy load for the wizard pages const PagePreparation = React.lazy(() => import('./Components/PagePreparation/PagePreparation')); @@ -33,57 +40,119 @@ const initialDomain: Domain = { /** * Wizard page to register a new domain into the service. + * @see {@link PagePreparation} about the preparation page. + * @see {@link PageServiceRegistration} about the registration page. + * @see {@link PageServiceDetails} about the details page. + * @see {@link PageReview} about the review page. */ -const WizardPage: React.FC = () => { +const WizardPage = () => { + const base_url = '/api/idmsvc/v1'; + const resources_api = ResourcesApiFactory(undefined, base_url, undefined); + const appContext = useContext(AppContext); + const domain = appContext.wizard.getDomain(); const navigate = useNavigate(); - // TODO Update the initial state into the context so that - // state can be read/write from the different pages - // into the wizard process. - const [data] = useState(initialDomain); - - useEffect(() => { - insights?.chrome?.appAction?.('default-page'); - }, []); - // FIXME Update the URL with the location for docs const linkLearnMoreAbout = 'https://access.redhat.com/articles/1586893'; - // Event when Close button is clicked + /** Event triggered when Close button is clicked. */ const onCloseClick = () => { - // TODO Not implemented - // What happens on Cancel? (see documentation) + // FIXME A few things pending: + // - Mocal confirmation + // - Confirm => + // - if not registered, dismiss wizard + // - else => DELETE /domains/:domain_id + // - Cancel or close model => Do not dismiss wizard navigate('/domains'); }; + /** Event triggered when Back button is clicked. */ + const onPreviousPage = ( + _newStep: { id?: string | number; name: React.ReactNode }, + _prevStep: { prevId?: string | number; prevName: React.ReactNode } + ) => { + console.log('onPreviousPage fired'); + return; + }; + + /** Event triggered when a specific page is clicked. */ + const onGoToStep = ( + _newStep: { id?: string | number; name: React.ReactNode }, + _prevStep: { prevId?: string | number; prevName: React.ReactNode } + ) => { + console.log('onGoToStep fired'); + return; + }; + + /** Event triggered when the Next button is clicked. */ + const onNextPage = async ({ id }: WizardStep) => { + // FIXME Delete log + console.log('onNextPage fired for id=' + id); + if (id === undefined) { + return; + } + if (typeof id === 'string') { + const [, orderIndex] = id.split('-'); + id = parseInt(orderIndex); + } + }; + + const initCanJumpPage1 = true; + const initCanJumpPage2 = initCanJumpPage1 && domain.domain_id != '' && appContext.wizard.getToken() != ''; + const initCanJumpPage3 = initCanJumpPage2; + const initCanJumpPage4 = initCanJumpPage3 && domain.title !== undefined && domain.title.length > 0; + + const [canJumpPage1] = useState(initCanJumpPage1); + const [canJumpPage2, setCanJumpPage2] = useState(initCanJumpPage2); + const [canJumpPage3, setCanJumpPage3] = useState(initCanJumpPage3); + const [canJumpPage4, setCanJumpPage4] = useState(initCanJumpPage4); + + const onToken = (token: string, domain_id: string, expiration: number) => { + console.log('WizardPage.OnToken fired: token=' + token + '; domain_id=' + domain_id + '; expiration=' + expiration); + if (token != '') { + setCanJumpPage2(true); + } else { + setCanJumpPage2(false); + } + }; + + /** Configure the wizard pages. */ const steps = [ { // This page only display the pre-requisites + id: 1, name: 'Preparation', - component: , + component: , + canJumpTo: canJumpPage1, }, { + id: 2, name: 'Service registration', // FIXME Pass here the 'registering.domain' field from the context // FIXME Pass here the 'registering.token' field from the context - component: , + component: , + canJumpTo: canJumpPage2, }, { + id: 3, name: 'Service details', // FIXME Pass here the 'registering.domain' field from the context - component: , + component: , + canJumpTo: canJumpPage3, }, { + id: 4, name: 'Review', // FIXME Pass here the 'registering.domain' field from the context - component: , + component: , + canJumpTo: canJumpPage4, }, ]; const title = 'Register directory and domain service'; return ( - + <> @@ -98,7 +167,7 @@ const WizardPage: React.FC = () => { iconPosition="right" href={linkLearnMoreAbout} > - Learn more about the directory and domain services. + Learn more about the directory and domain services{' '}

@@ -106,7 +175,7 @@ const WizardPage: React.FC = () => {
-
+ ); };