import React, { useEffect, useState } from 'react';
import {
Button,
Wizard,
WizardFooterWrapper,
WizardNavItem,
WizardStep,
useWizardContext,
PageSection,
PageSectionTypes,
Flex,
} from '@patternfly/react-core';
import { WizardStepType } from '@patternfly/react-core/dist/esm/components/Wizard';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { useNavigate, useSearchParams } from 'react-router-dom';
import DetailsStep from './steps/Details';
import FileSystemStep from './steps/FileSystem';
import { FileSystemContext } from './steps/FileSystem/FileSystemTable';
import FirewallStep from './steps/Firewall';
import FirstBootStep from './steps/FirstBoot';
import HostnameStep from './steps/Hostname';
import ImageOutputStep from './steps/ImageOutput';
import KernelStep from './steps/Kernel';
import LocaleStep from './steps/Locale';
import OscapStep from './steps/Oscap';
import PackagesStep from './steps/Packages';
import RegistrationStep from './steps/Registration';
import RepositoriesStep from './steps/Repositories';
import ReviewStep from './steps/Review';
import ReviewWizardFooter from './steps/Review/Footer/Footer';
import ServicesStep from './steps/Services';
import SnapshotStep from './steps/Snapshot';
import Aws from './steps/TargetEnvironment/Aws';
import Azure from './steps/TargetEnvironment/Azure';
import Gcp from './steps/TargetEnvironment/Gcp';
import TimezoneStep from './steps/Timezone';
import UsersStep from './steps/Users';
import { getHostArch, getHostDistro } from './utilities/getHostInfo';
import {
useFilesystemValidation,
useSnapshotValidation,
useFirstBootValidation,
useDetailsValidation,
useRegistrationValidation,
useHostnameValidation,
useKernelValidation,
useUsersValidation,
useTimezoneValidation,
useFirewallValidation,
useServicesValidation,
useLocaleValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
isAzureTenantGUIDValid,
isAzureSubscriptionIdValid,
isAzureResourceGroupValid,
isGcpEmailValid,
} from './validators';
import {
RHEL_8,
RHEL_10_BETA,
RHEL_10,
AARCH64,
CENTOS_9,
AMPLITUDE_MODULE_NAME,
RHEL_9,
} from '../../constants';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import './CreateImageWizard.scss';
import {
changeDistribution,
changeArchitecture,
initializeWizard,
selectAwsAccountId,
selectAwsShareMethod,
selectAwsSourceId,
selectAzureResourceGroup,
selectAzureShareMethod,
selectAzureSource,
selectAzureSubscriptionId,
selectAzureTenantId,
selectDistribution,
selectGcpEmail,
selectGcpShareMethod,
selectImageTypes,
addImageType,
changeRegistrationType,
} from '../../store/wizardSlice';
import isRhel from '../../Utilities/isRhel';
import { resolveRelPath } from '../../Utilities/path';
import { useFlag, useGetEnvironment } from '../../Utilities/useGetEnvironment';
import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader';
type CustomWizardFooterPropType = {
disableBack?: boolean;
disableNext: boolean;
beforeNext?: () => boolean;
optional?: boolean;
};
export const CustomWizardFooter = ({
disableBack: disableBack,
disableNext: disableNext,
beforeNext,
optional: optional,
}: CustomWizardFooterPropType) => {
const { goToNextStep, goToPrevStep, goToStepById, close, activeStep } =
useWizardContext();
const { analytics } = useChrome();
const nextBtnID = 'wizard-next-btn';
const backBtnID = 'wizard-back-btn';
const reviewAndFinishBtnID = 'wizard-review-and-finish-btn';
const cancelBtnID = 'wizard-cancel-btn';
return (
{optional && (
)}
);
};
type CreateImageWizardProps = {
isEdit?: boolean;
};
const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const { analytics, isBeta } = useChrome();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [searchParams] = useSearchParams();
const { isFedoraEnv } = useGetEnvironment();
// Feature flags
const complianceEnabled = useFlag('image-builder.compliance.enabled');
// IMPORTANT: Ensure the wizard starts with a fresh initial state
useEffect(() => {
dispatch(initializeWizard());
if (isFedoraEnv) {
dispatch(changeDistribution(CENTOS_9));
dispatch(changeRegistrationType('register-later'));
}
if (searchParams.get('release') === 'rhel8') {
dispatch(changeDistribution(RHEL_8));
}
if (searchParams.get('release') === 'rhel9') {
dispatch(changeDistribution(RHEL_9));
}
if (searchParams.get('release') === 'rhel10beta') {
dispatch(changeDistribution(RHEL_10_BETA));
}
if (searchParams.get('release') === 'rhel10') {
dispatch(changeDistribution(RHEL_10));
}
if (searchParams.get('arch') === AARCH64) {
dispatch(changeArchitecture(AARCH64));
}
if (searchParams.get('target') === 'iso') {
dispatch(addImageType('image-installer'));
}
if (searchParams.get('target') === 'qcow2') {
dispatch(addImageType('guest-image'));
}
const initializeHostDistro = async () => {
const distro = await getHostDistro();
dispatch(changeDistribution(distro));
};
const initializeHostArch = async () => {
const arch = await getHostArch();
dispatch(changeArchitecture(arch));
};
if (process.env.IS_ON_PREMISE && !isEdit) {
if (!searchParams.get('release')) {
initializeHostDistro();
}
initializeHostArch();
}
// This useEffect hook should run *only* on mount and therefore has an empty
// dependency array. eslint's exhaustive-deps rule does not support this use.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/* *
* Selectors *
* */
const distribution = useAppSelector(selectDistribution);
// Image Output
const targetEnvironments = useAppSelector(selectImageTypes);
// AWS
const awsShareMethod = useAppSelector(selectAwsShareMethod);
const awsAccountId = useAppSelector(selectAwsAccountId);
const awsSourceId = useAppSelector(selectAwsSourceId);
// GCP
const gcpShareMethod = useAppSelector(selectGcpShareMethod);
const gcpEmail = useAppSelector(selectGcpEmail);
// AZURE
const azureShareMethod = useAppSelector(selectAzureShareMethod);
const azureTenantId = useAppSelector(selectAzureTenantId);
const azureSubscriptionId = useAppSelector(selectAzureSubscriptionId);
const azureResourceGroup = useAppSelector(selectAzureResourceGroup);
const azureSource = useAppSelector(selectAzureSource);
// Registration
const registrationValidation = useRegistrationValidation();
// Snapshots
const snapshotValidation = useSnapshotValidation();
// Filesystem
const [filesystemPristine, setFilesystemPristine] = useState(true);
const fileSystemValidation = useFilesystemValidation();
// Timezone
const timezoneValidation = useTimezoneValidation();
// Locale
const localeValidation = useLocaleValidation();
// Hostname
const hostnameValidation = useHostnameValidation();
// Kernel
const kernelValidation = useKernelValidation();
// Firewall
const firewallValidation = useFirewallValidation();
// Services
const servicesValidation = useServicesValidation();
// Firstboot
const firstBootValidation = useFirstBootValidation();
// Details
const detailsValidation = useDetailsValidation();
// Users
const usersValidation = useUsersValidation();
let startIndex = 1; // default index
if (isEdit) {
startIndex = 22;
}
const [wasRegisterVisited, setWasRegisterVisited] = useState(false);
// Duplicating some of the logic from the Wizard component to allow for custom nav items status
// for original code see https://github.com/patternfly/patternfly-react/blob/184c55f8d10e1d94ffd72e09212db56c15387c5e/packages/react-core/src/components/Wizard/WizardNavInternal.tsx#L128
const CustomStatusNavItem = (
step: WizardStepType,
activeStep: WizardStepType,
steps: WizardStepType[],
goToStepByIndex: (index: number) => void
) => {
const isVisitOptional =
'parentId' in step && step.parentId === 'step-optional-steps';
useEffect(() => {
if (process.env.IS_ON_PREMISE) {
if (step.id === 'step-oscap' && step.isVisited) {
setWasRegisterVisited(true);
}
} else if (step.id === 'step-register' && step.isVisited) {
setWasRegisterVisited(true);
}
}, [step.id, step.isVisited]);
const hasVisitedNextStep = steps.some(
(s) => s.index > step.index && s.isVisited
);
// Only this code is different from the original
const status = (step?.id !== activeStep?.id && step?.status) || 'default';
return (
{
goToStepByIndex(step.index);
if (isEdit && step.id === 'wizard-additional-packages') {
analytics.track(
`${AMPLITUDE_MODULE_NAME} - Additional Packages Revisited in Edit`,
{
module: AMPLITUDE_MODULE_NAME,
isPreview: isBeta(),
}
);
}
}}
status={status}
/>
);
};
return (
<>
navigate(resolveRelPath(''))}
isVisitRequired
>
}
>
target === 'aws' || target === 'gcp' || target === 'azure'
)
}
steps={[
}
isHidden={!targetEnvironments.includes('aws')}
>
,
}
isHidden={!targetEnvironments.includes('gcp')}
>
,
}
isHidden={!targetEnvironments.includes('azure')}
>
,
]}
/>
}
>
,
}
>
,
{
if (fileSystemValidation.disabledNext) {
setFilesystemPristine(false);
return false;
}
return true;
}}
disableNext={
!filesystemPristine && fileSystemValidation.disabledNext
}
optional={true}
/>
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
}
>
,
]}
/>
}
>
}
>
>
);
};
export default CreateImageWizard;