Wizard: reintroduce registration step for cockpit
We can re-enable the registration step for cockpit by allowing users to pass in an activation and org id pair so that they can build an image that is automatically registered.
This commit is contained in:
parent
a5aa15cbcb
commit
c5de94250a
14 changed files with 241 additions and 47 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { expect, FrameLocator, type Page } from '@playwright/test';
|
||||
|
||||
export const togglePreview = async (page: Page) => {
|
||||
const toggleSwitch = page.locator('#preview-toggle');
|
||||
|
|
@ -85,3 +85,15 @@ export const getHostDistroName = (): string => {
|
|||
export const getHostArch = (): string => {
|
||||
return execSync('uname -m').toString('utf-8').replace(/\s/g, '');
|
||||
};
|
||||
|
||||
export const isRhel = async (frame: Page | FrameLocator) => {
|
||||
if (isHosted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const registerHeading = frame.getByRole('heading', {
|
||||
name: 'Register systems using this image',
|
||||
});
|
||||
|
||||
return registerHeading.isVisible();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, FrameLocator, type Page, test } from '@playwright/test';
|
||||
|
||||
import { closePopupsIfExist, isHosted } from './helpers';
|
||||
import { closePopupsIfExist, isHosted, isRhel } from './helpers';
|
||||
import { ibFrame, navigateToLandingPage } from './navHelpers';
|
||||
|
||||
/**
|
||||
|
|
@ -45,11 +45,12 @@ export const fillInDetails = async (
|
|||
|
||||
/**
|
||||
* Select "Register later" option in the wizard
|
||||
* This function executes only on the hosted service
|
||||
* This function executes only on if the registration
|
||||
* step is visible (it won't be visible for Fedora)
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const registerLater = async (page: Page | FrameLocator) => {
|
||||
if (isHosted()) {
|
||||
if (await isRhel(page)) {
|
||||
await page.getByRole('button', { name: 'Register' }).click();
|
||||
await page.getByRole('radio', { name: 'Register later' }).click();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import TOML from '@ltd/j-toml';
|
|||
import { expect, test } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { closePopupsIfExist, isHosted } from './helpers/helpers';
|
||||
import { closePopupsIfExist, isHosted, isRhel } from './helpers/helpers';
|
||||
import { ensureAuthenticated } from './helpers/login';
|
||||
import { ibFrame, navigateToLandingPage } from './helpers/navHelpers';
|
||||
|
||||
|
|
@ -27,11 +27,8 @@ test.describe.serial('test', () => {
|
|||
.click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
if (isHosted()) {
|
||||
frame.getByRole('heading', {
|
||||
name: 'Register systems using this image',
|
||||
});
|
||||
await page.getByRole('radio', { name: /Register later/i }).click();
|
||||
if (await isRhel(frame)) {
|
||||
await frame.getByRole('radio', { name: /Register later/i }).click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +273,12 @@ test.describe.serial('test', () => {
|
|||
// the first card should be the AWS card
|
||||
await frame.locator('.pf-v6-c-card').first().click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
// Just use the default region
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
if (await isRhel(frame)) {
|
||||
await frame.getByRole('radio', { name: /Register later/i }).click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
}
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame.getByRole('button', { name: 'Back', exact: true }).click();
|
||||
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
'parentId' in step && step.parentId === 'step-optional-steps';
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.IS_ON_PREMISE) {
|
||||
if (!isRhel(distribution)) {
|
||||
if (step.id === 'step-oscap' && step.isVisited) {
|
||||
setWasRegisterVisited(true);
|
||||
}
|
||||
|
|
@ -460,7 +460,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
name='Register'
|
||||
id='step-register'
|
||||
key='step-register'
|
||||
isHidden={!!process.env.IS_ON_PREMISE || !isRhel(distribution)}
|
||||
isHidden={!isRhel(distribution)}
|
||||
navItem={CustomStatusNavItem}
|
||||
status={
|
||||
wasRegisterVisited
|
||||
|
|
|
|||
|
|
@ -26,10 +26,14 @@ const ActivationKeyInformation = (): JSX.Element => {
|
|||
} = useShowActivationKeyQuery(
|
||||
{ name: activationKey! },
|
||||
{
|
||||
skip: !activationKey,
|
||||
skip: !activationKey || !!process.env.IS_ON_PREMISE,
|
||||
},
|
||||
);
|
||||
|
||||
if (process.env.IS_ON_PREMISE) {
|
||||
return <Content component={ContentVariants.dd}>{activationKey}</Content>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isFetchingActivationKeyInfo && <Spinner size='lg' />}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
import React, { MutableRefObject, useEffect, useRef } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Content,
|
||||
ContentVariants,
|
||||
FormGroup,
|
||||
FormGroupLabelHelp,
|
||||
Popover,
|
||||
} from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { ACTIVATION_KEYS_URL, CDN_PROD_URL } from '../../../../../constants';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
changeActivationKey,
|
||||
changeBaseUrl,
|
||||
changeOrgId,
|
||||
changeServerUrl,
|
||||
selectActivationKey,
|
||||
selectOrgId,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
import { ValidatedInput } from '../../../ValidatedInput';
|
||||
|
||||
const ManualRegistrationPopover = ({
|
||||
ref,
|
||||
}: {
|
||||
ref: MutableRefObject<null>;
|
||||
}) => {
|
||||
return (
|
||||
<Popover
|
||||
triggerRef={ref}
|
||||
headerContent='About Activation Keys & Organization ID'
|
||||
position='right'
|
||||
minWidth='30rem'
|
||||
bodyContent={
|
||||
<Content>
|
||||
<Content component={ContentVariants.p}>
|
||||
Activation keys assist you in registering and configuring systems.
|
||||
Metadata such as role, system purpose, and usage can be
|
||||
automatically attached to systems via an activation key, and
|
||||
monitored with Subscription Watch.
|
||||
</Content>
|
||||
<Content component={ContentVariants.p}>
|
||||
The Organization ID is the numeric identifier for your organization
|
||||
and is separate from your account number. Your organization's
|
||||
activation keys and organization ID are displayed on the Activation
|
||||
Keys page in the Hybrid Cloud Console.
|
||||
</Content>
|
||||
<Button
|
||||
component='a'
|
||||
target='_blank'
|
||||
variant='link'
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition='right'
|
||||
isInline
|
||||
href={ACTIVATION_KEYS_URL}
|
||||
>
|
||||
View activation keys in Hybrid Cloud Console
|
||||
</Button>
|
||||
</Content>
|
||||
}
|
||||
>
|
||||
<FormGroupLabelHelp
|
||||
ref={ref}
|
||||
aria-label='About Activation Keys & Organization ID'
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export const ManualActivationKey = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const orgId = useAppSelector(selectOrgId);
|
||||
const activationKey = useAppSelector(selectActivationKey);
|
||||
const orgIdRef = useRef(null);
|
||||
const activationKeyRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(changeServerUrl('subscription.rhsm.redhat.com'));
|
||||
dispatch(changeBaseUrl(CDN_PROD_URL));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormGroup
|
||||
label={'Activation key'}
|
||||
labelHelp={<ManualRegistrationPopover ref={activationKeyRef} />}
|
||||
>
|
||||
<ValidatedInput
|
||||
placeholder='Activation key'
|
||||
ariaLabel='Activation key'
|
||||
value={activationKey || ''}
|
||||
onChange={(_, value) => {
|
||||
dispatch(changeActivationKey(value.trim()));
|
||||
}}
|
||||
validator={(v) => v !== undefined && v !== ''}
|
||||
helperText='The activation key cannot be empty'
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={'Organization ID'}
|
||||
labelHelp={<ManualRegistrationPopover ref={orgIdRef} />}
|
||||
>
|
||||
<ValidatedInput
|
||||
placeholder='Organization ID'
|
||||
ariaLabel='Organization ID'
|
||||
value={orgId || ''}
|
||||
onChange={(_, value) => {
|
||||
dispatch(changeOrgId(value.trim()));
|
||||
}}
|
||||
validator={(v) => {
|
||||
if (v === undefined || v === '') {
|
||||
return false;
|
||||
}
|
||||
return /^\d+$/.test(v.trim());
|
||||
}}
|
||||
helperText='Please enter a valid Organization ID'
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -13,6 +13,7 @@ import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
|
|||
|
||||
import ActivationKeyInformation from './components/ActivationKeyInformation';
|
||||
import ActivationKeysList from './components/ActivationKeysList';
|
||||
import { ManualActivationKey } from './components/ManualActivationKey';
|
||||
import Registration from './components/Registration';
|
||||
import SatelliteRegistration from './components/SatelliteRegistration';
|
||||
|
||||
|
|
@ -44,23 +45,25 @@ const RegistrationStep = () => {
|
|||
<Title headingLevel='h1' size='xl'>
|
||||
Register systems using this image
|
||||
</Title>
|
||||
<FormGroup label='Organization ID'>
|
||||
<ClipboardCopy
|
||||
hoverTip='Copy to clipboard'
|
||||
clickTip='Successfully copied to clipboard!'
|
||||
variant='inline-compact'
|
||||
>
|
||||
{orgId || ''}
|
||||
</ClipboardCopy>
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem>
|
||||
If using an activation key with command line registration, you
|
||||
must provide your organization's ID
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
{!process.env.IS_ON_PREMISE && (
|
||||
<FormGroup label='Organization ID'>
|
||||
<ClipboardCopy
|
||||
hoverTip='Copy to clipboard'
|
||||
clickTip='Successfully copied to clipboard!'
|
||||
variant='inline-compact'
|
||||
>
|
||||
{orgId || ''}
|
||||
</ClipboardCopy>
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem>
|
||||
If using an activation key with command line registration, you
|
||||
must provide your organization's ID
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
)}
|
||||
<Registration />
|
||||
{registrationType === 'register-satellite' && <SatelliteRegistration />}
|
||||
{!process.env.IS_ON_PREMISE &&
|
||||
|
|
@ -76,6 +79,9 @@ const RegistrationStep = () => {
|
|||
<ActivationKeyInformation />
|
||||
</FormGroup>
|
||||
)}
|
||||
{process.env.IS_ON_PREMISE && registrationType !== 'register-later' && (
|
||||
<ManualActivationKey />
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import {
|
|||
useCreateBPWithNotification as useCreateBlueprintMutation,
|
||||
useUpdateBPWithNotification as useUpdateBlueprintMutation,
|
||||
} from '../../../../../Hooks';
|
||||
import { useAppSelector } from '../../../../../store/hooks';
|
||||
import { selectOrgId } from '../../../../../store/wizardSlice';
|
||||
import { resolveRelPath } from '../../../../../Utilities/path';
|
||||
import { mapRequestFromState } from '../../../utilities/requestMapper';
|
||||
import { useIsBlueprintValid } from '../../../utilities/useValidation';
|
||||
|
|
@ -41,6 +43,7 @@ const ReviewWizardFooter = () => {
|
|||
};
|
||||
const navigate = useNavigate();
|
||||
const isValid = useIsBlueprintValid();
|
||||
const orgId = useAppSelector(selectOrgId);
|
||||
|
||||
useEffect(() => {
|
||||
if (isUpdateSuccess || isCreateSuccess) {
|
||||
|
|
@ -58,9 +61,7 @@ const ReviewWizardFooter = () => {
|
|||
return requestBody;
|
||||
}
|
||||
|
||||
// NOTE: This should be fine on-prem, we should
|
||||
// be able to ignore the `org-id`
|
||||
return mapRequestFromState(store, '');
|
||||
return mapRequestFromState(store, orgId ?? '');
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ import {
|
|||
selectKeyboard,
|
||||
selectLanguages,
|
||||
selectNtpServers,
|
||||
selectOrgId,
|
||||
selectPackages,
|
||||
selectPartitions,
|
||||
selectRecommendedRepositories,
|
||||
|
|
@ -704,6 +705,7 @@ export const RegisterAapList = () => {
|
|||
};
|
||||
|
||||
export const RegisterNowList = () => {
|
||||
const orgId = useAppSelector(selectOrgId);
|
||||
const activationKey = useAppSelector(selectActivationKey);
|
||||
const registrationType = useAppSelector(selectRegistrationType);
|
||||
|
||||
|
|
@ -711,7 +713,7 @@ export const RegisterNowList = () => {
|
|||
// @ts-ignore type of 'activationKey' might not be strictly compatible with the expected type for 'name'.
|
||||
{ name: activationKey },
|
||||
{
|
||||
skip: !activationKey,
|
||||
skip: !activationKey || process.env.IS_ON_PREMISE,
|
||||
},
|
||||
);
|
||||
return (
|
||||
|
|
@ -753,6 +755,12 @@ export const RegisterNowList = () => {
|
|||
<Content component={ContentVariants.dd}>
|
||||
<ActivationKeyInformation />
|
||||
</Content>
|
||||
{process.env.IS_ON_PREMISE && (
|
||||
<>
|
||||
<Content component={ContentVariants.dt}>Organization ID</Content>
|
||||
<Content component={ContentVariants.dd}>{orgId}</Content>
|
||||
</>
|
||||
)}
|
||||
</Content>
|
||||
</Content>
|
||||
{isError && (
|
||||
|
|
|
|||
|
|
@ -397,6 +397,9 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => {
|
|||
activationKey: isRhel(request.distribution)
|
||||
? request.customizations.subscription?.['activation-key']
|
||||
: undefined,
|
||||
orgId: isRhel(request.distribution)
|
||||
? request.customizations.subscription?.['organization']?.toString()
|
||||
: undefined,
|
||||
satelliteRegistration: {
|
||||
command: getSatelliteCommand(request.customizations.files),
|
||||
caCert: request.customizations.cacerts?.pem_certs[0],
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
selectKeyboard,
|
||||
selectLanguages,
|
||||
selectNtpServers,
|
||||
selectOrgId,
|
||||
selectPartitions,
|
||||
selectRegistrationType,
|
||||
selectSatelliteCaCertificate,
|
||||
|
|
@ -123,6 +124,7 @@ type ValidationState = {
|
|||
export function useRegistrationValidation(): StepValidation {
|
||||
const registrationType = useAppSelector(selectRegistrationType);
|
||||
const activationKey = useAppSelector(selectActivationKey);
|
||||
const orgId = useAppSelector(selectOrgId);
|
||||
const registrationCommand = useAppSelector(
|
||||
selectSatelliteRegistrationCommand,
|
||||
);
|
||||
|
|
@ -132,15 +134,37 @@ export function useRegistrationValidation(): StepValidation {
|
|||
useShowActivationKeyQuery(
|
||||
{ name: activationKey! },
|
||||
{
|
||||
skip: !activationKey,
|
||||
skip: !activationKey || !!process.env.IS_ON_PREMISE,
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
registrationType !== 'register-later' &&
|
||||
registrationType !== 'register-satellite' &&
|
||||
!activationKey
|
||||
) {
|
||||
if (registrationType === 'register-later') {
|
||||
return { errors: {}, disabledNext: false };
|
||||
}
|
||||
|
||||
if (process.env.IS_ON_PREMISE) {
|
||||
const errors: Record<string, string> = {};
|
||||
let disabledNext = false;
|
||||
|
||||
if (!activationKey?.trim()) {
|
||||
errors.activationKey = 'Activation Key not set';
|
||||
disabledNext = true;
|
||||
}
|
||||
|
||||
if (!orgId?.trim()) {
|
||||
errors.orgId = 'Organization ID not set';
|
||||
disabledNext = true;
|
||||
} else if (!/^\d+$/.test(orgId.trim())) {
|
||||
errors.orgId = 'Organization ID should be a numeric value';
|
||||
disabledNext = true;
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
return { errors, disabledNext };
|
||||
}
|
||||
}
|
||||
|
||||
if (registrationType !== 'register-satellite' && !activationKey) {
|
||||
return {
|
||||
errors: { activationKey: 'No activation key selected' },
|
||||
disabledNext: true,
|
||||
|
|
@ -148,10 +172,10 @@ export function useRegistrationValidation(): StepValidation {
|
|||
}
|
||||
|
||||
if (
|
||||
registrationType !== 'register-later' &&
|
||||
registrationType !== 'register-satellite' &&
|
||||
activationKey &&
|
||||
(isFetchingKeyInfo || isErrorKeyInfo)
|
||||
(isFetchingKeyInfo || isErrorKeyInfo) &&
|
||||
!process.env.IS_ON_PREMISE
|
||||
) {
|
||||
return {
|
||||
errors: { activationKey: 'Invalid activation key' },
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ export const RELEASE_LIFECYCLE_URL =
|
|||
export const AZURE_AUTH_URL =
|
||||
'https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow';
|
||||
export const COMPLIANCE_URL = '/insights/compliance/scappolicies';
|
||||
export const ACTIVATION_KEYS_URL = '/insights/connector/activation-keys';
|
||||
export const ACTIVATION_KEYS_URL = !process.env.IS_ON_PREMISE
|
||||
? '/insights/connector/activation-keys'
|
||||
: 'https://console.redhat.com/settings/connector/activation-keys';
|
||||
export const COMPLIANCE_AND_VULN_SCANNING_URL =
|
||||
'https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/security_guide/chap-compliance_and_vulnerability_scanning';
|
||||
export const CREATING_IMAGES_WITH_IB_URL =
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ export type wizardState = {
|
|||
registration: {
|
||||
registrationType: RegistrationType;
|
||||
activationKey: ActivationKeys['name'];
|
||||
orgId: string | undefined;
|
||||
satelliteRegistration: {
|
||||
command: string | undefined;
|
||||
caCert: string | undefined;
|
||||
|
|
@ -226,6 +227,7 @@ export const initialState: wizardState = {
|
|||
? 'register-later'
|
||||
: 'register-now-rhc',
|
||||
activationKey: undefined,
|
||||
orgId: undefined,
|
||||
satelliteRegistration: {
|
||||
command: undefined,
|
||||
caCert: undefined,
|
||||
|
|
@ -381,6 +383,10 @@ export const selectActivationKey = (state: RootState) => {
|
|||
return state.wizard.registration.activationKey;
|
||||
};
|
||||
|
||||
export const selectOrgId = (state: RootState) => {
|
||||
return state.wizard.registration.orgId;
|
||||
};
|
||||
|
||||
export const selectSatelliteRegistrationCommand = (state: RootState) => {
|
||||
return state.wizard.registration.satelliteRegistration?.command;
|
||||
};
|
||||
|
|
@ -682,6 +688,9 @@ export const wizardSlice = createSlice({
|
|||
) => {
|
||||
state.registration.activationKey = action.payload;
|
||||
},
|
||||
changeOrgId: (state, action: PayloadAction<string>) => {
|
||||
state.registration.orgId = action.payload;
|
||||
},
|
||||
changeComplianceType: (state, action: PayloadAction<ComplianceType>) => {
|
||||
state.compliance.complianceType = action.payload;
|
||||
},
|
||||
|
|
@ -1223,6 +1232,7 @@ export const {
|
|||
reinitializeGcp,
|
||||
changeRegistrationType,
|
||||
changeActivationKey,
|
||||
changeOrgId,
|
||||
changeCompliance,
|
||||
changeComplianceType,
|
||||
changeFileSystemConfiguration,
|
||||
|
|
|
|||
|
|
@ -30,10 +30,8 @@ const goToHostnameStep = async () => {
|
|||
});
|
||||
await waitFor(() => user.click(guestImageCheckBox));
|
||||
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
await clickNext(); // Registration
|
||||
await clickRegisterLater();
|
||||
}
|
||||
await clickNext(); // Registration
|
||||
await clickRegisterLater();
|
||||
await goToStep(/Hostname/);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue