V2Wizard: Add components to the Registration step
This migrates all the needed components from Javascript and DDF to Typescript and RTK. New slices were added for registration type and activation key. Disabling the "Next" button should work correctly now.
This commit is contained in:
parent
9ce438295d
commit
360984df7c
8 changed files with 513 additions and 304 deletions
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ImageOutputStep from './steps/ImageOutput';
|
||||
import RegistrationStep from './steps/Registration';
|
||||
import Aws from './steps/TargetEnvironment/Aws';
|
||||
import Gcp from './steps/TargetEnvironment/Gcp';
|
||||
import { isAwsAccountIdValid, isGcpEmailValid } from './validators';
|
||||
|
|
@ -18,12 +19,14 @@ import { useAppDispatch, useAppSelector } from '../../store/hooks';
|
|||
import './CreateImageWizard.scss';
|
||||
import {
|
||||
initializeWizard,
|
||||
selectActivationKey,
|
||||
selectAwsAccountId,
|
||||
selectAwsShareMethod,
|
||||
selectAwsSource,
|
||||
selectGcpEmail,
|
||||
selectGcpShareMethod,
|
||||
selectImageTypes,
|
||||
selectRegistrationType,
|
||||
} from '../../store/wizardSlice';
|
||||
import { resolveRelPath } from '../../Utilities/path';
|
||||
import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader';
|
||||
|
|
@ -77,6 +80,11 @@ const CreateImageWizard = () => {
|
|||
const gcpShareMethod = useAppSelector((state) => selectGcpShareMethod(state));
|
||||
const gcpEmail = useAppSelector((state) => selectGcpEmail(state));
|
||||
|
||||
const registrationType = useAppSelector((state) =>
|
||||
selectRegistrationType(state)
|
||||
);
|
||||
const activationKey = useAppSelector((state) => selectActivationKey(state));
|
||||
|
||||
return (
|
||||
<>
|
||||
<ImageBuilderHeader />
|
||||
|
|
@ -138,6 +146,19 @@ const CreateImageWizard = () => {
|
|||
</WizardStep>,
|
||||
]}
|
||||
/>
|
||||
<WizardStep
|
||||
name="Register"
|
||||
id="step-register"
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={
|
||||
registrationType !== 'register-later' && !activationKey
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RegistrationStep />
|
||||
</WizardStep>
|
||||
<WizardStep
|
||||
name="Review"
|
||||
id="step-review"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { useFormApi } from '@data-driven-forms/react-form-renderer';
|
||||
import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context';
|
||||
import {
|
||||
Alert,
|
||||
FormGroup,
|
||||
Spinner,
|
||||
Text,
|
||||
TextContent,
|
||||
|
|
@ -17,13 +16,12 @@ import { Button, Popover } from '@patternfly/react-core';
|
|||
import { HelpIcon } from '@patternfly/react-icons';
|
||||
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
|
||||
|
||||
import { useShowActivationKeyQuery } from '../../../store/rhsmApi';
|
||||
import { useAppSelector } from '../../../../store/hooks';
|
||||
import { useShowActivationKeyQuery } from '../../../../store/rhsmApi';
|
||||
import { selectActivationKey } from '../../../../store/wizardSlice';
|
||||
|
||||
const ActivationKeyInformation = (): JSX.Element => {
|
||||
const { getState } = useFormApi();
|
||||
const { currentStep } = useContext(WizardContext);
|
||||
|
||||
const activationKey = getState()?.values?.['subscription-activation-key'];
|
||||
const activationKey = useAppSelector((state) => selectActivationKey(state));
|
||||
|
||||
const {
|
||||
data: activationKeyInfo,
|
||||
|
|
@ -31,7 +29,7 @@ const ActivationKeyInformation = (): JSX.Element => {
|
|||
isSuccess: isSuccessActivationKeyInfo,
|
||||
isError: isErrorActivationKeyInfo,
|
||||
} = useShowActivationKeyQuery(
|
||||
{ name: activationKey },
|
||||
{ name: activationKey! },
|
||||
{
|
||||
skip: !activationKey,
|
||||
}
|
||||
|
|
@ -41,105 +39,113 @@ const ActivationKeyInformation = (): JSX.Element => {
|
|||
<>
|
||||
{isFetchingActivationKeyInfo && <Spinner size="lg" />}
|
||||
{isSuccessActivationKeyInfo && (
|
||||
<TextContent>
|
||||
<TextList component={TextListVariants.dl}>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Name:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKey}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Role:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKeyInfo.body?.role || 'Not defined'}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
SLA:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKeyInfo.body?.serviceLevel || 'Not defined'}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Usage:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKeyInfo.body?.usage || 'Not defined'}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Additional repositories:
|
||||
<Popover
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
The core repositories for your operating system version
|
||||
are always enabled and do not need to be explicitly added
|
||||
to the activation key.
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="About additional repositories"
|
||||
className="pf-u-pl-sm pf-u-pt-0 pf-u-pb-0"
|
||||
size="sm"
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
</TextListItem>
|
||||
<TextListItem
|
||||
component={TextListItemVariants.dd}
|
||||
className="pf-u-display-flex pf-u-align-items-flex-end"
|
||||
>
|
||||
{activationKeyInfo.body?.additionalRepositories &&
|
||||
activationKeyInfo.body?.additionalRepositories?.length > 0 ? (
|
||||
<FormGroup
|
||||
isRequired={true}
|
||||
label={'Selected activation key'}
|
||||
data-testid="selected-activation-key"
|
||||
>
|
||||
<TextContent>
|
||||
<TextList component={TextListVariants.dl}>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Name:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKey}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Role:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKeyInfo.body?.role || 'Not defined'}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
SLA:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKeyInfo.body?.serviceLevel || 'Not defined'}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Usage:
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dd}>
|
||||
{activationKeyInfo.body?.usage || 'Not defined'}
|
||||
</TextListItem>
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Additional repositories:
|
||||
<Popover
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text component={TextVariants.h3}>
|
||||
Additional repositories
|
||||
<Text>
|
||||
The core repositories for your operating system version
|
||||
are always enabled and do not need to be explicitly
|
||||
added to the activation key.
|
||||
</Text>
|
||||
<Table
|
||||
aria-label="Additional repositories table"
|
||||
variant="compact"
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody data-testid="additional-repositories-table">
|
||||
{activationKeyInfo.body?.additionalRepositories?.map(
|
||||
(repo, index) => (
|
||||
<Tr key={index}>
|
||||
<Td>{repo.repositoryLabel}</Td>
|
||||
</Tr>
|
||||
)
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
data-testid="repositories-popover-button"
|
||||
variant="link"
|
||||
aria-label="Show additional repositories"
|
||||
className="pf-u-pl-0 pf-u-pt-0 pf-u-pb-0"
|
||||
variant="plain"
|
||||
aria-label="About additional repositories"
|
||||
className="pf-u-pl-sm pf-u-pt-0 pf-u-pb-0"
|
||||
size="sm"
|
||||
>
|
||||
{activationKeyInfo.body?.additionalRepositories?.length}{' '}
|
||||
repositories
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
) : (
|
||||
'None'
|
||||
)}
|
||||
</TextListItem>
|
||||
</TextList>
|
||||
</TextContent>
|
||||
</TextListItem>
|
||||
<TextListItem
|
||||
component={TextListItemVariants.dd}
|
||||
className="pf-u-display-flex pf-u-align-items-flex-end"
|
||||
>
|
||||
{activationKeyInfo.body?.additionalRepositories &&
|
||||
activationKeyInfo.body?.additionalRepositories?.length > 0 ? (
|
||||
<Popover
|
||||
position="right"
|
||||
minWidth="30rem"
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text component={TextVariants.h3}>
|
||||
Additional repositories
|
||||
</Text>
|
||||
<Table
|
||||
aria-label="Additional repositories table"
|
||||
variant="compact"
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody data-testid="additional-repositories-table">
|
||||
{activationKeyInfo.body?.additionalRepositories?.map(
|
||||
(repo, index) => (
|
||||
<Tr key={index}>
|
||||
<Td>{repo.repositoryLabel}</Td>
|
||||
</Tr>
|
||||
)
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
data-testid="repositories-popover-button"
|
||||
variant="link"
|
||||
aria-label="Show additional repositories"
|
||||
className="pf-u-pl-0 pf-u-pt-0 pf-u-pb-0"
|
||||
>
|
||||
{activationKeyInfo.body?.additionalRepositories?.length}{' '}
|
||||
repositories
|
||||
</Button>
|
||||
</Popover>
|
||||
) : (
|
||||
'None'
|
||||
)}
|
||||
</TextListItem>
|
||||
</TextList>
|
||||
</TextContent>
|
||||
</FormGroup>
|
||||
)}
|
||||
{isErrorActivationKeyInfo && (
|
||||
<TextContent>
|
||||
|
|
@ -151,10 +157,6 @@ const ActivationKeyInformation = (): JSX.Element => {
|
|||
{activationKey}
|
||||
</TextListItem>
|
||||
</TextList>
|
||||
</TextContent>
|
||||
)}
|
||||
{isErrorActivationKeyInfo && currentStep.name === 'registration' && (
|
||||
<>
|
||||
<br />
|
||||
<Alert
|
||||
title="Information about the activation key unavailable"
|
||||
|
|
@ -165,7 +167,7 @@ const ActivationKeyInformation = (): JSX.Element => {
|
|||
Information about the activation key cannot be loaded. Please check
|
||||
the key was not removed and try again later.
|
||||
</Alert>
|
||||
</>
|
||||
</TextContent>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import useFieldApi from '@data-driven-forms/react-form-renderer/use-field-api';
|
||||
import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api';
|
||||
import {
|
||||
Alert,
|
||||
FormGroup,
|
||||
|
|
@ -13,6 +11,9 @@ import {
|
|||
EmptyStateHeader,
|
||||
EmptyStateFooter,
|
||||
EmptyStateActions,
|
||||
Text,
|
||||
TextContent,
|
||||
Popover,
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
Select,
|
||||
|
|
@ -20,17 +21,74 @@ import {
|
|||
SelectVariant,
|
||||
} from '@patternfly/react-core/deprecated';
|
||||
import { WrenchIcon, AddCircleOIcon } from '@patternfly/react-icons';
|
||||
import { ExternalLinkAltIcon, HelpIcon } from '@patternfly/react-icons';
|
||||
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
|
||||
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
useListActivationKeysQuery,
|
||||
useCreateActivationKeysMutation,
|
||||
} from '../../../store/rhsmApi';
|
||||
import { useGetEnvironment } from '../../../Utilities/useGetEnvironment';
|
||||
} from '../../../../store/rhsmApi';
|
||||
import {
|
||||
changeActivationKey,
|
||||
changeBaseUrl,
|
||||
changeServerUrl,
|
||||
selectActivationKey,
|
||||
} from '../../../../store/wizardSlice';
|
||||
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
|
||||
|
||||
const EmptyActivationsKeyState = ({ handleActivationKeyFn, isLoading }) => (
|
||||
const PopoverActivation = () => {
|
||||
const [orgId, setOrgId] = useState<string | undefined>(undefined);
|
||||
const { auth } = useChrome();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const userData = await auth?.getUser();
|
||||
const id = userData?.identity?.internal?.org_id;
|
||||
setOrgId(id);
|
||||
})();
|
||||
});
|
||||
return (
|
||||
<Popover
|
||||
hasAutoWidth
|
||||
maxWidth="35rem"
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
Activation keys enable you to register a system with appropriate
|
||||
subscriptions, system purpose, and repositories attached.
|
||||
</Text>
|
||||
<Text>
|
||||
If using an activation key with command line registration, you must
|
||||
provide your organization's ID.
|
||||
{orgId && <br />}
|
||||
{orgId && "Your organization's ID is " + orgId}
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="Activation key popover"
|
||||
aria-describedby="subscription-activation-key"
|
||||
className="pf-c-form__group-label-help"
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
type EmptyActivationsKeyStateProps = {
|
||||
handleActivationKeyFn: Function;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const EmptyActivationsKeyState = ({
|
||||
handleActivationKeyFn,
|
||||
isLoading,
|
||||
}: EmptyActivationsKeyStateProps) => (
|
||||
<EmptyState variant="xs">
|
||||
<EmptyStateHeader
|
||||
titleText="No activation keys found"
|
||||
|
|
@ -44,7 +102,7 @@ const EmptyActivationsKeyState = ({ handleActivationKeyFn, isLoading }) => (
|
|||
<EmptyStateFooter>
|
||||
<EmptyStateActions>
|
||||
<Button
|
||||
onClick={handleActivationKeyFn}
|
||||
onClick={() => handleActivationKeyFn()}
|
||||
icon={<AddCircleOIcon />}
|
||||
isLoading={isLoading}
|
||||
iconPosition="left"
|
||||
|
|
@ -57,21 +115,34 @@ const EmptyActivationsKeyState = ({ handleActivationKeyFn, isLoading }) => (
|
|||
</EmptyState>
|
||||
);
|
||||
|
||||
EmptyActivationsKeyState.propTypes = {
|
||||
handleActivationKeyFn: PropTypes.func.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
const ManageKeysButton = () => {
|
||||
const { isProd } = useGetEnvironment();
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={
|
||||
isProd()
|
||||
? 'https://console.redhat.com/insights/connector/activation-keys'
|
||||
: 'https://console.stage.redhat.com/insights/connector/activation-keys'
|
||||
}
|
||||
>
|
||||
Activation keys page
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const ActivationKeys = ({ label, isRequired, ...props }) => {
|
||||
const { isProd } = useGetEnvironment();
|
||||
const { change, getState } = useFormApi();
|
||||
const { input } = useFieldApi(props);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [activationKeySelected, selectActivationKey] = useState(
|
||||
getState()?.values?.['subscription-activation-key']
|
||||
);
|
||||
const ActivationKeysList = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const activationKey = useAppSelector((state) => selectActivationKey(state));
|
||||
|
||||
const { isProd } = useGetEnvironment();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const {
|
||||
data: activationKeys,
|
||||
|
|
@ -83,25 +154,27 @@ const ActivationKeys = ({ label, isRequired, ...props }) => {
|
|||
|
||||
const [createActivationKey, { isLoading: isLoadingActivationKey }] =
|
||||
useCreateActivationKeysMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isProd()) {
|
||||
change('subscription-server-url', 'subscription.rhsm.redhat.com');
|
||||
change('subscription-base-url', 'https://cdn.redhat.com/');
|
||||
dispatch(changeServerUrl('subscription.rhsm.redhat.com'));
|
||||
dispatch(changeBaseUrl('https://cdn.redhat.com/'));
|
||||
} else {
|
||||
change('subscription-server-url', 'subscription.rhsm.stage.redhat.com');
|
||||
change('subscription-base-url', 'https://cdn.stage.redhat.com/');
|
||||
dispatch(changeServerUrl('subscription.rhsm.stage.redhat.com'));
|
||||
dispatch(changeBaseUrl('https://cdn.stage.redhat.com/'));
|
||||
}
|
||||
}, [isProd, change]);
|
||||
}, [dispatch, isProd]);
|
||||
|
||||
const setActivationKey = (_, selection) => {
|
||||
selectActivationKey(selection);
|
||||
const setActivationKey = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
selection: string
|
||||
) => {
|
||||
setIsOpen(false);
|
||||
change(input.name, selection);
|
||||
dispatch(changeActivationKey(selection));
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
selectActivationKey();
|
||||
change(input.name, undefined);
|
||||
dispatch(changeActivationKey(undefined));
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
|
|
@ -112,32 +185,68 @@ const ActivationKeys = ({ label, isRequired, ...props }) => {
|
|||
};
|
||||
|
||||
const handleCreateActivationKey = async () => {
|
||||
const res = await createActivationKey({
|
||||
body: {
|
||||
name: 'activation-key-default',
|
||||
serviceLevel: 'Self-Support',
|
||||
},
|
||||
});
|
||||
refetch();
|
||||
if (res.error) {
|
||||
try {
|
||||
await createActivationKey({
|
||||
body: {
|
||||
name: 'activation-key-default',
|
||||
serviceLevel: 'Self-Support',
|
||||
},
|
||||
});
|
||||
refetch();
|
||||
} catch (error) {
|
||||
dispatch(
|
||||
addNotification({
|
||||
variant: 'danger',
|
||||
title: 'Error creating activation key',
|
||||
description: res.error?.data?.error?.message,
|
||||
description: error?.data?.error?.message,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const setSelectOptions = () => {
|
||||
const selectOptions = [];
|
||||
if (isActivationKeysEmpty) {
|
||||
selectOptions.push(
|
||||
<EmptyActivationsKeyState
|
||||
handleActivationKeyFn={handleCreateActivationKey}
|
||||
isLoading={isLoadingActivationKey}
|
||||
key={'Empty'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isSuccessActivationKeys) {
|
||||
activationKeys.body?.map((key, index) =>
|
||||
selectOptions.push(<SelectOption key={index} value={key.name} />)
|
||||
);
|
||||
}
|
||||
if (!isSuccessActivationKeys && isFetchingActivationKeys) {
|
||||
selectOptions.push(
|
||||
<SelectOption
|
||||
key={'Fetching'}
|
||||
isNoResultsOption={true}
|
||||
data-testid="activation-keys-loading"
|
||||
>
|
||||
<Spinner size="md" />
|
||||
</SelectOption>
|
||||
);
|
||||
}
|
||||
|
||||
return selectOptions;
|
||||
};
|
||||
|
||||
const isActivationKeysEmpty =
|
||||
isSuccessActivationKeys && activationKeys.body.length === 0;
|
||||
isSuccessActivationKeys && activationKeys.body?.length === 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormGroup
|
||||
isRequired={isRequired}
|
||||
label={label}
|
||||
isRequired={true}
|
||||
label={
|
||||
<>
|
||||
Activation key to use for this image <PopoverActivation />
|
||||
</>
|
||||
}
|
||||
data-testid="subscription-activation-key"
|
||||
>
|
||||
<Select
|
||||
|
|
@ -146,31 +255,20 @@ const ActivationKeys = ({ label, isRequired, ...props }) => {
|
|||
onToggle={handleToggle}
|
||||
onSelect={setActivationKey}
|
||||
onClear={handleClear}
|
||||
selections={activationKeySelected}
|
||||
selections={activationKey}
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select activation key"
|
||||
typeAheadAriaLabel="Select activation key"
|
||||
isDisabled={!isSuccessActivationKeys}
|
||||
>
|
||||
{isActivationKeysEmpty && (
|
||||
<EmptyActivationsKeyState
|
||||
handleActivationKeyFn={handleCreateActivationKey}
|
||||
isLoading={isLoadingActivationKey}
|
||||
/>
|
||||
)}
|
||||
{isSuccessActivationKeys &&
|
||||
activationKeys.body.map((key, index) => (
|
||||
<SelectOption key={index} value={key.name} />
|
||||
))}
|
||||
{!isSuccessActivationKeys && isFetchingActivationKeys && (
|
||||
<SelectOption
|
||||
isNoResultsOption={true}
|
||||
data-testid="activation-keys-loading"
|
||||
>
|
||||
<Spinner size="md" />
|
||||
</SelectOption>
|
||||
)}
|
||||
{setSelectOptions()}
|
||||
</Select>
|
||||
<TextContent>
|
||||
<Text>
|
||||
By default, activation key is generated and preset for you. Admins
|
||||
can create and manage keys by visiting the <ManageKeysButton />
|
||||
</Text>
|
||||
</TextContent>
|
||||
</FormGroup>
|
||||
{isErrorActivationKeys && (
|
||||
<Alert
|
||||
|
|
@ -186,14 +284,4 @@ const ActivationKeys = ({ label, isRequired, ...props }) => {
|
|||
);
|
||||
};
|
||||
|
||||
ActivationKeys.propTypes = {
|
||||
label: PropTypes.node,
|
||||
isRequired: PropTypes.bool,
|
||||
};
|
||||
|
||||
ActivationKeys.defaultProps = {
|
||||
label: '',
|
||||
isRequired: false,
|
||||
};
|
||||
|
||||
export default ActivationKeys;
|
||||
export default ActivationKeysList;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Text,
|
||||
TextContent,
|
||||
TextVariants,
|
||||
} from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
|
||||
const RegisterLaterInformation = () => {
|
||||
return (
|
||||
<TextContent>
|
||||
<Text component={TextVariants.h3}>Register Later</Text>
|
||||
<Text>
|
||||
On initial boot, systems will need to be registered manually before
|
||||
having access to updates or Red Hat services. Registering and connecting
|
||||
your systems during the image creation is recommended.
|
||||
</Text>
|
||||
<Text>
|
||||
If you prefer to register later, review the instructions for manual
|
||||
registration with remote host configuration.
|
||||
</Text>
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href="https://access.redhat.com/articles/rhc"
|
||||
>
|
||||
Registering with remote host configuration
|
||||
</Button>
|
||||
</TextContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterLaterInformation;
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import { FormSpy } from '@data-driven-forms/react-form-renderer';
|
||||
import useFieldApi from '@data-driven-forms/react-form-renderer/use-field-api';
|
||||
import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
|
|
@ -13,7 +10,13 @@ import {
|
|||
TextContent,
|
||||
} from '@patternfly/react-core';
|
||||
import { HelpIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
changeActivationKey,
|
||||
changeRegistrationType,
|
||||
selectRegistrationType,
|
||||
} from '../../../../store/wizardSlice';
|
||||
|
||||
const RHSMPopover = () => {
|
||||
return (
|
||||
|
|
@ -132,121 +135,117 @@ const RhcPopover = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const Registration = ({ label, ...props }) => {
|
||||
const { change, getState } = useFormApi();
|
||||
const { input } = useFieldApi(props);
|
||||
const registerSystem = getState()?.values?.['register-system'];
|
||||
const Registration = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const registrationType = useAppSelector((state) =>
|
||||
selectRegistrationType(state)
|
||||
);
|
||||
|
||||
const [showOptions, setShowOptions] = useState(
|
||||
registerSystem === 'register-now-insights' ||
|
||||
registerSystem === 'register-now'
|
||||
registrationType === 'register-now-insights' ||
|
||||
registrationType === 'register-now'
|
||||
);
|
||||
|
||||
return (
|
||||
<FormSpy>
|
||||
{() => (
|
||||
<FormGroup label={label}>
|
||||
<Radio
|
||||
autoFocus
|
||||
label={
|
||||
(!showOptions &&
|
||||
'Automatically register and enable advanced capabilities') || (
|
||||
<FormGroup label="Registration method">
|
||||
<Radio
|
||||
autoFocus
|
||||
label={
|
||||
(!showOptions &&
|
||||
'Automatically register and enable advanced capabilities') || (
|
||||
<>
|
||||
Monitor & manage subscriptions and access to Red Hat content
|
||||
<RHSMPopover />
|
||||
</>
|
||||
)
|
||||
}
|
||||
data-testid="registration-radio-now"
|
||||
name="register-system"
|
||||
id="register-system-now"
|
||||
isChecked={registrationType.startsWith('register-now')}
|
||||
onChange={() => {
|
||||
dispatch(changeRegistrationType('register-now-rhc'));
|
||||
}}
|
||||
description={
|
||||
!showOptions && (
|
||||
<Button
|
||||
component="a"
|
||||
data-testid="registration-additional-options"
|
||||
variant="link"
|
||||
isDisabled={!registrationType.startsWith('register-now')}
|
||||
isInline
|
||||
onClick={() => setShowOptions(!showOptions)}
|
||||
>
|
||||
Show additional connection options
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
body={
|
||||
showOptions && (
|
||||
<Checkbox
|
||||
className="pf-u-ml-lg"
|
||||
label={
|
||||
<>
|
||||
Monitor & manage subscriptions and access to Red Hat content
|
||||
<RHSMPopover />
|
||||
Enable predictive analytics and management capabilities
|
||||
<InsightsPopover />
|
||||
</>
|
||||
)
|
||||
}
|
||||
data-testid="registration-radio-now"
|
||||
name="register-system"
|
||||
id="register-system-now"
|
||||
isChecked={registerSystem.startsWith('register-now')}
|
||||
onChange={() => {
|
||||
change(input.name, 'register-now-rhc');
|
||||
}}
|
||||
description={
|
||||
!showOptions && (
|
||||
<Button
|
||||
component="a"
|
||||
data-testid="registration-additional-options"
|
||||
variant="link"
|
||||
isDisabled={!registerSystem.startsWith('register-now')}
|
||||
isInline
|
||||
onClick={() => setShowOptions(!showOptions)}
|
||||
>
|
||||
Show additional connection options
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
body={
|
||||
showOptions && (
|
||||
}
|
||||
data-testid="registration-checkbox-insights"
|
||||
isChecked={
|
||||
registrationType === 'register-now-insights' ||
|
||||
registrationType === 'register-now-rhc'
|
||||
}
|
||||
onChange={(_event, checked) => {
|
||||
if (checked) {
|
||||
dispatch(changeRegistrationType('register-now-insights'));
|
||||
} else {
|
||||
dispatch(changeRegistrationType('register-now'));
|
||||
}
|
||||
}}
|
||||
id="register-system-now-insights"
|
||||
name="register-system-insights"
|
||||
body={
|
||||
<Checkbox
|
||||
className="pf-u-ml-lg"
|
||||
label={
|
||||
<>
|
||||
Enable predictive analytics and management capabilities
|
||||
<InsightsPopover />
|
||||
Enable remote remediations and system management with
|
||||
automation
|
||||
<RhcPopover />
|
||||
</>
|
||||
}
|
||||
data-testid="registration-checkbox-insights"
|
||||
isChecked={
|
||||
registerSystem === 'register-now-insights' ||
|
||||
registerSystem === 'register-now-rhc'
|
||||
}
|
||||
data-testid="registration-checkbox-rhc"
|
||||
isChecked={registrationType === 'register-now-rhc'}
|
||||
onChange={(_event, checked) => {
|
||||
if (checked) {
|
||||
change(input.name, 'register-now-insights');
|
||||
dispatch(changeRegistrationType('register-now-rhc'));
|
||||
} else {
|
||||
change(input.name, 'register-now');
|
||||
dispatch(changeRegistrationType('register-now-insights'));
|
||||
}
|
||||
}}
|
||||
id="register-system-now-insights"
|
||||
name="register-system-insights"
|
||||
body={
|
||||
<Checkbox
|
||||
label={
|
||||
<>
|
||||
Enable remote remediations and system management with
|
||||
automation
|
||||
<RhcPopover />
|
||||
</>
|
||||
}
|
||||
data-testid="registration-checkbox-rhc"
|
||||
isChecked={registerSystem === 'register-now-rhc'}
|
||||
onChange={(_event, checked) => {
|
||||
if (checked) {
|
||||
change(input.name, 'register-now-rhc');
|
||||
} else {
|
||||
change(input.name, 'register-now-insights');
|
||||
}
|
||||
}}
|
||||
id="register-system-now-rhc"
|
||||
name="register-system-rhc"
|
||||
/>
|
||||
}
|
||||
id="register-system-now-rhc"
|
||||
name="register-system-rhc"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Radio
|
||||
name="register-system"
|
||||
className="pf-u-mt-md"
|
||||
data-testid="registration-radio-later"
|
||||
id="register-system-later"
|
||||
label="Register later"
|
||||
isChecked={registerSystem === 'register-later'}
|
||||
onChange={() => {
|
||||
setShowOptions(false);
|
||||
change(input.name, 'register-later');
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FormSpy>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Radio
|
||||
name="register-system"
|
||||
className="pf-u-mt-md"
|
||||
data-testid="registration-radio-later"
|
||||
id="register-system-later"
|
||||
label="Register later"
|
||||
isChecked={registrationType === 'register-later'}
|
||||
onChange={() => {
|
||||
setShowOptions(false);
|
||||
dispatch(changeRegistrationType('register-later'));
|
||||
dispatch(changeActivationKey(undefined));
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
Registration.propTypes = {
|
||||
label: PropTypes.node,
|
||||
};
|
||||
|
||||
export default Registration;
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { FormSpy } from '@data-driven-forms/react-form-renderer';
|
||||
import { FormGroup } from '@patternfly/react-core';
|
||||
import { isEmpty } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ActivationKeyInformation from './ActivationKeyInformation';
|
||||
|
||||
const RegistrationKeyInformation = ({ label, valueReference }) => {
|
||||
return (
|
||||
<FormSpy>
|
||||
{({ values }) =>
|
||||
isEmpty(values[valueReference]) ? null : (
|
||||
<FormGroup label={label}>
|
||||
<ActivationKeyInformation />
|
||||
</FormGroup>
|
||||
)
|
||||
}
|
||||
</FormSpy>
|
||||
);
|
||||
};
|
||||
|
||||
RegistrationKeyInformation.propTypes = {
|
||||
label: PropTypes.node,
|
||||
valueReference: PropTypes.node,
|
||||
};
|
||||
|
||||
export default RegistrationKeyInformation;
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Text, Form, Title } from '@patternfly/react-core';
|
||||
|
||||
import ActivationKeyInformation from './ActivationKeyInformation';
|
||||
import ActivationKeysList from './ActivationKeysList';
|
||||
import RegisterLaterInformation from './RegisterLaterInformation';
|
||||
import Registration from './Registration';
|
||||
|
||||
import { useAppSelector } from '../../../../store/hooks';
|
||||
import { selectRegistrationType } from '../../../../store/wizardSlice';
|
||||
|
||||
const RegistrationStep = () => {
|
||||
const registrationType = useAppSelector((state) =>
|
||||
selectRegistrationType(state)
|
||||
);
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h2">Register systems using this image</Title>
|
||||
<Text>
|
||||
Automatically register your systems with Red Hat to enhance security and
|
||||
track your spending.
|
||||
</Text>
|
||||
<Registration />
|
||||
{registrationType !== 'register-later' ? (
|
||||
<>
|
||||
<ActivationKeysList />
|
||||
<ActivationKeyInformation />
|
||||
</>
|
||||
) : (
|
||||
<RegisterLaterInformation />
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegistrationStep;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { Distributions, ImageRequest, ImageTypes } from './imageBuilderApi';
|
||||
import { ActivationKeys } from './rhsmApi';
|
||||
|
||||
import {
|
||||
AwsShareMethod,
|
||||
|
|
@ -15,6 +16,10 @@ import { RHEL_9, X86_64 } from '../constants';
|
|||
import { RootState } from '.';
|
||||
|
||||
type wizardState = {
|
||||
env: {
|
||||
serverUrl: string | undefined;
|
||||
baseUrl: string | undefined;
|
||||
};
|
||||
architecture: ImageRequest['architecture'];
|
||||
distribution: Distributions;
|
||||
imageTypes: ImageTypes[];
|
||||
|
|
@ -28,9 +33,17 @@ type wizardState = {
|
|||
accountType: GcpAccountType;
|
||||
email: string | undefined;
|
||||
};
|
||||
registration: {
|
||||
registrationType: string;
|
||||
activationKey: ActivationKeys['name'];
|
||||
};
|
||||
};
|
||||
|
||||
const initialState: wizardState = {
|
||||
env: {
|
||||
serverUrl: undefined,
|
||||
baseUrl: undefined,
|
||||
},
|
||||
architecture: X86_64,
|
||||
distribution: RHEL_9,
|
||||
imageTypes: [],
|
||||
|
|
@ -44,6 +57,18 @@ const initialState: wizardState = {
|
|||
accountType: 'google',
|
||||
email: undefined,
|
||||
},
|
||||
registration: {
|
||||
registrationType: 'register-now-rhc',
|
||||
activationKey: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const selectServerUrl = (state: RootState) => {
|
||||
return state.wizard.env.serverUrl;
|
||||
};
|
||||
|
||||
export const selectBaseUrl = (state: RootState) => {
|
||||
return state.wizard.env.baseUrl;
|
||||
};
|
||||
|
||||
export const selectArchitecture = (state: RootState) => {
|
||||
|
|
@ -84,11 +109,25 @@ export const selectGcpEmail = (state: RootState) => {
|
|||
return state.wizard.gcp.email;
|
||||
};
|
||||
|
||||
export const selectRegistrationType = (state: RootState) => {
|
||||
return state.wizard.registration.registrationType;
|
||||
};
|
||||
|
||||
export const selectActivationKey = (state: RootState) => {
|
||||
return state.wizard.registration.activationKey;
|
||||
};
|
||||
|
||||
export const wizardSlice = createSlice({
|
||||
name: 'wizard',
|
||||
initialState,
|
||||
reducers: {
|
||||
initializeWizard: () => initialState,
|
||||
changeServerUrl: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.env.serverUrl = action.payload;
|
||||
},
|
||||
changeBaseUrl: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.env.baseUrl = action.payload;
|
||||
},
|
||||
changeArchitecture: (
|
||||
state,
|
||||
action: PayloadAction<ImageRequest['architecture']>
|
||||
|
|
@ -142,11 +181,22 @@ export const wizardSlice = createSlice({
|
|||
changeGcpEmail: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.gcp.email = action.payload;
|
||||
},
|
||||
changeRegistrationType: (state, action: PayloadAction<string>) => {
|
||||
state.registration.registrationType = action.payload;
|
||||
},
|
||||
changeActivationKey: (
|
||||
state,
|
||||
action: PayloadAction<ActivationKeys['name']>
|
||||
) => {
|
||||
state.registration.activationKey = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
initializeWizard,
|
||||
changeServerUrl,
|
||||
changeBaseUrl,
|
||||
changeArchitecture,
|
||||
changeDistribution,
|
||||
addImageType,
|
||||
|
|
@ -158,5 +208,7 @@ export const {
|
|||
changeGcpShareMethod,
|
||||
changeGcpAccountType,
|
||||
changeGcpEmail,
|
||||
changeRegistrationType,
|
||||
changeActivationKey,
|
||||
} = wizardSlice.actions;
|
||||
export default wizardSlice.reducer;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue