diff --git a/README.md b/README.md index 17f148da..1413c7e7 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ https://github.com/RedHatInsights/image-builder-frontend/blob/c84b493eba82ce83a7 ##### Mocking flags for tests Flags can be mocked for the unit tests to access some feature. Checkout: -https://github.com/RedHatInsights/image-builder-frontend/blob/c84b493eba82ce83a7844943943d91112ffe8322/src/test/Components/CreateImageWizard/CreateImageWizard.test.js#L74 +https://github.com/osbuild/image-builder-frontend/blob/9a464e416bc3769cfc8e23b62f1dd410eb0e0455/src/test/Components/CreateImageWizardV2/CreateImageWizard.test.tsx#L49 If the two possible code path accessible via the toggles are defined in the code base, then it's good practice to test the two of them. If not, only test what's diff --git a/config/dev.webpack.config.js b/config/dev.webpack.config.js index 88fe3dfd..5d6e708f 100644 --- a/config/dev.webpack.config.js +++ b/config/dev.webpack.config.js @@ -106,29 +106,6 @@ if (process.env.MSW) { plugins[definePluginIndex] = newDefinePlugin; } -if (process.env.EXPERIMENTAL) { - /* - We would like the client to be able to determine whether or not it is in - experimental mode at run time based on the value of process.env.EXPERIMENTAL. - We can add that variable to process.env via the DefinesPlugin plugin, but - DefinePlugin has already been added by config() to the default webpackConfig. - - Therefore, we find it in the `plugins` array based on its type, then update - it to add our new process.env.EXPERIMENTAL variable. - */ - const definePluginIndex = plugins.findIndex( - (plugin) => plugin instanceof webpack.DefinePlugin - ); - const definePlugin = plugins[definePluginIndex]; - - const newDefinePlugin = new webpack.DefinePlugin({ - ...definePlugin.definitions, - 'process.env.EXPERIMENTAL': true, - }); - - plugins[definePluginIndex] = newDefinePlugin; -} - module.exports = { ...webpackConfig, plugins, diff --git a/package.json b/package.json index b7ba823f..25f34613 100644 --- a/package.json +++ b/package.json @@ -127,9 +127,7 @@ "prod-stable": "PROXY=true webpack serve --config config/dev.webpack.config.js", "stage-stable": "STAGE=true npm run prod-stable", "stage-beta": "STAGE=true npm run prod-beta", - "stage-beta:experimental": "EXPERIMENTAL=TRUE npm run stage-beta", "stage-beta:msw": "MSW=TRUE npm run stage-beta", - "stage-beta:msw+experimental": "EXPERIMENTAL=TRUE npm run stage-beta:msw", "test": "TZ=UTC jest --verbose --no-cache", "test:single": "jest --verbose -w 1", "build": "webpack --config config/prod.webpack.config.js", diff --git a/src/Components/Blueprints/ImportBlueprintModal.test.tsx b/src/Components/Blueprints/ImportBlueprintModal.test.tsx index 29e287b9..e374898c 100644 --- a/src/Components/Blueprints/ImportBlueprintModal.test.tsx +++ b/src/Components/Blueprints/ImportBlueprintModal.test.tsx @@ -22,8 +22,6 @@ jest.mock('@unleash/proxy-client-react', () => ({ switch (flag) { case 'image-builder.import.enabled': return true; - case 'image-builder.new-wizard.enabled': - return true; default: return false; } @@ -243,17 +241,15 @@ describe('Import model', () => { expect(helperText).toBeInTheDocument(); }); - test('should enable button on correct blueprint', async () => { + test('should enable button on correct blueprint and go to wizard', async () => { await setUp(); await uploadFile(`blueprints.json`, BLUEPRINT_JSON); const reviewButton = screen.getByTestId('import-blueprint-finish'); await waitFor(() => expect(reviewButton).not.toHaveClass('pf-m-disabled')); + await user.click(reviewButton); - await userEvent.click(reviewButton); - await waitFor(() => { - expect( - screen.getByRole('heading', { name: 'Image output' }) - ).toBeInTheDocument(); - }); + expect( + await screen.findByText('Image output', { selector: 'h1' }) + ).toBeInTheDocument(); }); }); diff --git a/src/Components/CreateImageWizard/CreateImageWizard.js b/src/Components/CreateImageWizard/CreateImageWizard.js deleted file mode 100644 index bdf572a0..00000000 --- a/src/Components/CreateImageWizard/CreateImageWizard.js +++ /dev/null @@ -1,716 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import { useFlag } from '@unleash/proxy-client-react'; -import { useNavigate, useParams } from 'react-router-dom'; - -import ImageCreator from './ImageCreator'; -import { - awsTarget, - fileSystemConfiguration, - googleCloudTarget, - imageName, - imageOutput, - msAzureTarget, - packages, - packagesContentSources, - registration, - repositories, - review, - oscap, -} from './steps'; -import { - fileSystemConfigurationValidator, - targetEnvironmentValidator, -} from './validators'; - -import './CreateImageWizard.scss'; -import { - CDN_PROD_URL, - CDN_STAGE_URL, - UNIT_GIB, - UNIT_KIB, - UNIT_MIB, -} from '../../constants'; -import { - useComposeImageMutation, - useGetComposeStatusQuery, -} from '../../store/imageBuilderApi'; -import isRhel from '../../Utilities/isRhel'; -import { resolveRelPath } from '../../Utilities/path'; -import { useGetEnvironment } from '../../Utilities/useGetEnvironment'; -import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader'; - -const handleKeyDown = (e, handleClose) => { - if (e.key === 'Escape') { - handleClose(); - } -}; - -const onSave = (values) => { - const customizations = { - packages: values['selected-packages']?.map((p) => p.name), - }; - - if (values['payload-repositories']?.length > 0) { - customizations['payload_repositories'] = [ - ...values['payload-repositories'], - ]; - } - - if (values['custom-repositories']?.length > 0) { - customizations['custom_repositories'] = [...values['custom-repositories']]; - } - - if (values['register-system'] === 'register-now-rhc') { - customizations.subscription = { - 'activation-key': values['subscription-activation-key'], - insights: true, - rhc: true, - organization: Number(values['subscription-organization-id']), - 'server-url': values['subscription-server-url'], - 'base-url': values['subscription-base-url'], - }; - } else if (values['register-system'] === 'register-now-insights') { - customizations.subscription = { - 'activation-key': values['subscription-activation-key'], - insights: true, - organization: Number(values['subscription-organization-id']), - 'server-url': values['subscription-server-url'], - 'base-url': values['subscription-base-url'], - }; - } else if (values['register-system'] === 'register-now') { - customizations.subscription = { - 'activation-key': values['subscription-activation-key'], - insights: false, - organization: Number(values['subscription-organization-id']), - 'server-url': values['subscription-server-url'], - 'base-url': values['subscription-base-url'], - }; - } - - if (values['file-system-config-radio'] === 'manual') { - customizations.filesystem = []; - for (const fsc of values['file-system-configuration']) { - customizations.filesystem.push({ - mountpoint: fsc.mountpoint, - min_size: fsc.size * fsc.unit, - }); - } - } - - if (values['oscap-profile']) { - customizations.openscap = { - profile_id: values['oscap-profile'], - }; - if (values['kernel']) { - customizations.kernel = values['kernel']; - } - if ( - (Array.isArray(values['enabledServices']) && - values['enabledServices'].length > 0) || - (Array.isArray(values['maskedServices']) && - values['maskedServices'].length > 0) - ) { - customizations.services = {}; - if (values['enabledServices'].length > 0) { - customizations.services.enabled = values['enabledServices']; - } - if (values['maskedServices'].length > 0) { - customizations.services.masked = values['maskedServices']; - } - } - } - - const requests = []; - if (values['target-environment']?.aws) { - const options = - values['aws-target-type'] === 'aws-target-type-source' - ? { share_with_sources: [values['aws-sources-select']] } - : { share_with_accounts: [values['aws-account-id']] }; - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'aws', - upload_request: { - type: 'aws', - options, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - if (values['target-environment']?.gcp) { - let share = ''; - if (values['image-sharing'] === 'gcp-account') { - switch (values['google-account-type']) { - case 'googleAccount': - share = `user:${values['google-email']}`; - break; - case 'serviceAccount': - share = `serviceAccount:${values['google-email']}`; - break; - case 'googleGroup': - share = `group:${values['google-email']}`; - break; - case 'domain': - share = `domain:${values['google-domain']}`; - break; - // no default - } - } - - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'gcp', - upload_request: { - type: 'gcp', - options: {}, - }, - }, - ], - customizations, - client_id: 'ui', - }; - - if (share !== '') { - request.image_requests[0].upload_request.options.share_with_accounts = [ - share, - ]; - } - requests.push(request); - } - - if (values['target-environment']?.azure) { - const upload_options = - values['azure-type'] === 'azure-type-source' - ? { source_id: values['azure-sources-select'] } - : { - tenant_id: values['azure-tenant-id'], - subscription_id: values['azure-subscription-id'], - }; - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'azure', - upload_request: { - type: 'azure', - options: { - ...upload_options, - resource_group: values['azure-resource-group'], - }, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - if (values['target-environment']?.oci) { - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'oci', - upload_request: { - type: 'oci.objectstorage', - options: {}, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - if (values['target-environment']?.vsphere) { - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'vsphere', - upload_request: { - type: 'aws.s3', - options: {}, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - if (values['target-environment']?.['vsphere-ova']) { - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'vsphere-ova', - upload_request: { - type: 'aws.s3', - options: {}, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - if (values['target-environment']?.['guest-image']) { - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'guest-image', - upload_request: { - type: 'aws.s3', - options: {}, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - if (values['target-environment']?.['image-installer']) { - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'image-installer', - upload_request: { - type: 'aws.s3', - options: {}, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - if (values['target-environment']?.wsl) { - const request = { - distribution: values.release, - image_name: values?.['image-name'], - image_description: values?.['image-description'], - image_requests: [ - { - architecture: values['arch'], - image_type: 'wsl', - upload_request: { - type: 'aws.s3', - options: {}, - }, - }, - ], - customizations, - client_id: 'ui', - }; - requests.push(request); - } - - return requests; -}; - -const parseSizeUnit = (bytesize) => { - let size; - let unit; - - if (bytesize % UNIT_GIB === 0) { - size = bytesize / UNIT_GIB; - unit = UNIT_GIB; - } else if (bytesize % UNIT_MIB === 0) { - size = bytesize / UNIT_MIB; - unit = UNIT_MIB; - } else if (bytesize % UNIT_KIB === 0) { - size = bytesize / UNIT_KIB; - unit = UNIT_KIB; - } - - return [size, unit]; -}; - -// map the compose request object to the expected form state -const requestToState = (composeRequest, isProd, enableOscap) => { - if (composeRequest) { - const imageRequest = composeRequest.image_requests[0]; - const uploadRequest = imageRequest.upload_request; - const formState = {}; - - formState['image-name'] = composeRequest.image_name; - formState['image-description'] = composeRequest.image_description; - - formState.release = composeRequest?.distribution; - formState.arch = imageRequest.architecture; - // set defaults for target environment first - formState['target-environment'] = { - aws: false, - azure: false, - gcp: false, - oci: false, - 'guest-image': false, - }; - // then select the one from the request - // if the image type is to a cloud provider we use the upload_request.type - // or if the image is intended for download we use the image_type - let targetEnvironment; - if ( - uploadRequest.type === 'aws.s3' || - uploadRequest.type === 'oci.objectstorage' - ) { - targetEnvironment = imageRequest.image_type; - } else { - targetEnvironment = uploadRequest.type; - } - - formState['target-environment'][targetEnvironment] = true; - - if (targetEnvironment === 'aws') { - const shareWithSource = uploadRequest?.options?.share_with_sources?.[0]; - const shareWithAccount = uploadRequest?.options?.share_with_accounts?.[0]; - formState['aws-sources-select'] = shareWithSource; - formState['aws-account-id'] = shareWithAccount; - if (shareWithAccount && !shareWithSource) { - formState['aws-target-type'] = 'aws-target-type-account-id'; - } else { - // if both shareWithAccount & shareWithSource are present, set radio - // to sources - this is essentially an arbitrary decision - // additionally, note that the source is not validated against the actual - // sources - formState['aws-target-type'] = 'aws-target-type-source'; - } - } else if (targetEnvironment === 'azure') { - if (uploadRequest?.options?.source_id) { - formState['azure-type'] = 'azure-type-source'; - formState['azure-sources-select'] = uploadRequest?.options?.source_id; - } else { - formState['azure-type'] = 'azure-type-manual'; - formState['azure-tenant-id'] = uploadRequest?.options?.tenant_id; - formState['azure-subscription-id'] = - uploadRequest?.options?.subscription_id; - } - formState['azure-resource-group'] = - uploadRequest?.options?.resource_group; - } else if (targetEnvironment === 'gcp') { - // parse google account info - // roughly in the format `accountType:accountEmail` - const accountInfo = uploadRequest?.options?.share_with_accounts[0]; - const [accountTypePrefix, account] = accountInfo.split(':'); - - switch (accountTypePrefix) { - case 'user': - formState['google-account-type'] = 'googleAccount'; - formState['google-email'] = account; - break; - case 'serviceAccount': - formState['google-account-type'] = 'serviceAccount'; - formState['google-email'] = account; - break; - case 'group': - formState['google-account-type'] = 'googleGroup'; - formState['google-email'] = account; - break; - case 'domain': - formState['google-account-type'] = 'domain'; - formState['google-domain'] = account; - break; - // no default - } - } - - // customizations - // packages - const packages = composeRequest?.customizations?.packages?.map((name) => { - return { - name: name, - summary: undefined, - }; - }); - formState['selected-packages'] = packages; - - // repositories - // 'original-payload-repositories' is treated as read-only and is used to populate - // the table in the repositories step - // This is necessary because there may be repositories present in the request's - // json blob that are not managed using the content sources API. In that case, - // they are still displayed in the table of repositories but without any information - // from the content sources API (in other words, only the URL of the repository is - // displayed). This information needs to persist throughout the lifetime of the - // Wizard as it is needed every time the repositories step is visited. - formState['original-payload-repositories'] = - composeRequest?.customizations?.payload_repositories; - // 'payload-repositories' is mutable and is used to generate the request - // sent to image-builder - formState['payload-repositories'] = - composeRequest?.customizations?.payload_repositories; - - // these will be overwritten by the repositories step if revisited, and generated from the payload repositories added there - formState['custom-repositories'] = - composeRequest?.customizations?.custom_repositories; - - // filesystem - const fs = composeRequest?.customizations?.filesystem; - if (fs) { - formState['file-system-config-radio'] = 'manual'; - const fileSystemConfiguration = []; - for (const fsc of fs) { - const [size, unit] = parseSizeUnit(fsc.min_size); - fileSystemConfiguration.push({ - mountpoint: fsc.mountpoint.includes('/usr/') - ? '/usr' - : fsc.mountpoint, - size, - unit, - }); - } - - formState['file-system-configuration'] = fileSystemConfiguration; - } - - // subscription - const subscription = composeRequest?.customizations?.subscription; - if (subscription) { - if (subscription.rhc) { - formState['register-system'] = 'register-now-rhc'; - } else if (subscription.insights) { - formState['register-system'] = 'register-now-insights'; - } else { - formState['register-system'] = 'register-now'; - } - - formState['subscription-activation-key'] = subscription['activation-key']; - formState['subscription-organization-id'] = subscription.organization; - - if (isProd) { - formState['subscription-server-url'] = 'subscription.rhsm.redhat.com'; - formState['subscription-base-url'] = CDN_PROD_URL; - } else { - formState['subscription-server-url'] = - 'subscription.rhsm.stage.redhat.com'; - formState['subscription-base-url'] = CDN_STAGE_URL; - } - } else { - formState['register-system'] = 'register-later'; - } - - if (enableOscap) { - formState['oscap-profile'] = - composeRequest?.customizations?.openscap?.profile_id; - formState['kernel'] = composeRequest?.customizations?.kernel; - formState['enabledServices'] = - composeRequest?.customizations?.services?.enabled; - formState['maskedServices'] = - composeRequest?.customizations?.services?.masked; - } - - return formState; - } else { - return; - } -}; - -const formStepHistory = ( - composeRequest, - contentSourcesEnabled, - enableOscap -) => { - if (composeRequest) { - const imageRequest = composeRequest.image_requests[0]; - const uploadRequest = imageRequest.upload_request; - // the order of steps must match the order of the steps in the Wizard - const steps = ['image-output']; - - if (uploadRequest.type === 'aws') { - steps.push('aws-target-env'); - } else if (uploadRequest.type === 'azure') { - steps.push('ms-azure-target-env'); - } else if (uploadRequest.type === 'gcp') { - steps.push('google-cloud-target-env'); - } - - if (isRhel(composeRequest?.distribution)) { - steps.push('registration'); - } - - if (enableOscap) { - steps.push('Compliance'); - } - - if (contentSourcesEnabled) { - steps.push('File system configuration', 'packages', 'repositories'); - - const customRepositories = - composeRequest.customizations?.payload_repositories; - if (customRepositories) { - steps.push('packages-content-sources'); - } - } else { - steps.push('File system configuration', 'packages'); - } - steps.push('details'); - - return steps; - } else { - return []; - } -}; - -const CreateImageWizard = () => { - const [composeImage] = useComposeImageMutation(); - const navigate = useNavigate(); - // composeId is an optional param that is used for Recreate image - const { composeId } = useParams(); - - const { data } = useGetComposeStatusQuery( - { composeId: composeId }, - { - skip: composeId ? false : true, - } - ); - const composeRequest = composeId ? data?.request : undefined; - const contentSourcesEnabled = useFlag('image-builder.enable-content-sources'); - - // Assume that if a request is available that we should start on review step - // This will occur if 'Recreate image' is clicked - const initialStep = composeRequest ? 'review' : undefined; - - const { isBeta, isProd } = useGetEnvironment(); - - // Only allow oscap to be used in Beta even if the flag says the feature is - // activated. - const oscapFeatureFlag = useFlag('image-builder.wizard.oscap.enabled'); - let initialState = requestToState(composeRequest, isProd(), oscapFeatureFlag); - const stepHistory = formStepHistory( - composeRequest, - contentSourcesEnabled, - oscapFeatureFlag - ); - - if (initialState) { - initialState.isBeta = isBeta(); - initialState.contentSourcesEnabled = contentSourcesEnabled; - initialState.enableOscap = oscapFeatureFlag; - } else { - initialState = { - isBeta: isBeta(), - enableOscap: oscapFeatureFlag, - contentSourcesEnabled, - }; - } - - const handleClose = () => navigate(resolveRelPath('')); - - return ( - <> - -
- { - setIsSaving(true); - const requests = onSave(values); - await Promise.allSettled( - requests.map((composeRequest) => composeImage({ composeRequest })) - ); - navigate(resolveRelPath('')); - }} - defaultArch="x86_64" - customValidatorMapper={{ - fileSystemConfigurationValidator, - targetEnvironmentValidator, - }} - schema={{ - fields: [ - { - component: componentTypes.WIZARD, - name: 'image-builder-wizard', - className: 'imageBuilder', - isDynamic: true, - inModal: false, - onKeyDown: (e) => { - handleKeyDown(e, handleClose); - }, - buttonLabels: { - submit: 'Create image', - }, - showTitles: true, - crossroads: [ - 'target-environment', - 'release', - 'payload-repositories', - ], - // order in this array does not reflect order in wizard nav, this order is managed inside - // of each step by `nextStep` property! - fields: [ - imageOutput, - awsTarget, - googleCloudTarget, - msAzureTarget, - registration, - packages, - packagesContentSources, - repositories, - fileSystemConfiguration, - imageName, - review, - oscap, - ], - initialState: { - activeStep: initialStep || 'image-output', // name of the active step - activeStepIndex: stepHistory.length, // active index - maxStepIndex: stepHistory.length, // max achieved index - prevSteps: stepHistory, // array with names of previously visited steps - }, - }, - ], - }} - initialValues={initialState} - /> -
- - ); -}; - -export default CreateImageWizard; diff --git a/src/Components/CreateImageWizard/CreateImageWizard.scss b/src/Components/CreateImageWizard/CreateImageWizard.scss deleted file mode 100644 index 16b0ff4c..00000000 --- a/src/Components/CreateImageWizard/CreateImageWizard.scss +++ /dev/null @@ -1,66 +0,0 @@ -.pf-v5-c-wizard__nav-list { - padding-right: 0px; -} - -.pf-v5-c-wizard__nav { - overflow-y: unset; -} - -.pf-c-popover[data-popper-reference-hidden="true"] { - font-weight: initial; - visibility: initial; - pointer-events: initial; -} - -.pf-v5-c-dual-list-selector { - --pf-v5-c-dual-list-selector__menu--MinHeight: 18rem; - --pf-v5-c-dual-list-selector--GridTemplateColumns--pane--MinMax--max: 100vw; -} - -.pf-c-form { - --pf-c-form--GridGap: var(--pf-global--spacer--md); -} - -.pf-c-form__group-label { - --pf-c-form__group-label--PaddingBottom: var(--pf-global--spacer--xs); -} - -.tiles { - display: flex; -} - -.tile { - flex: 1 0 0px; - max-width: 250px; -} - -.pf-c-tile:focus { - --pf-c-tile__title--Color: var(--pf-c-tile__title--Color); - --pf-c-tile__icon--Color: var(---pf-global--Color--100); - --pf-c-tile--before--BorderWidth: var(--pf-global--BorderWidth--sm); - --pf-c-tile--before--BorderColor: var(--pf-global--BorderColor--100); -} - -.pf-c-tile.pf-m-selected:focus { - --pf-c-tile__title--Color: var(--pf-c-tile--focus__title--Color); - --pf-c-tile__icon--Color: var(--pf-c-tile--focus__icon--Color); -} - -.provider-icon { - width: 1em; - height: 1em; -} - -.pf-u-min-width { - --pf-u-min-width--MinWidth: 18ch; -} - -.pf-u-max-width { - --pf-u-max-width--MaxWidth: 26rem; -} - -ul.pf-m-plain { - list-style: none; - padding-left: 0; - margin-left: 0; -} diff --git a/src/Components/CreateImageWizard/ImageCreator.js b/src/Components/CreateImageWizard/ImageCreator.js deleted file mode 100644 index 673e885c..00000000 --- a/src/Components/CreateImageWizard/ImageCreator.js +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; - -import { componentMapper } from '@data-driven-forms/pf4-component-mapper'; -import Pf4FormTemplate from '@data-driven-forms/pf4-component-mapper/form-template'; -import Select from '@data-driven-forms/pf4-component-mapper/select'; -import FormRenderer from '@data-driven-forms/react-form-renderer/form-renderer'; -import { Spinner } from '@patternfly/react-core'; -import PropTypes from 'prop-types'; - -import ActivationKeys from './formComponents/ActivationKeys'; -import ArchSelect from './formComponents/ArchSelect'; -import { AWSSourcesSelect } from './formComponents/AWSSourcesSelect'; -import AzureAuthButton from './formComponents/AzureAuthButton'; -import AzureResourceGroups from './formComponents/AzureResourceGroups'; -import AzureSourcesSelect from './formComponents/AzureSourcesSelect'; -import CentOSAcknowledgement from './formComponents/CentOSAcknowledgement'; -import FileSystemConfiguration from './formComponents/FileSystemConfiguration'; -import GalleryLayout from './formComponents/GalleryLayout'; -import ImageOutputReleaseSelect from './formComponents/ImageOutputReleaseSelect'; -import { Oscap } from './formComponents/Oscap'; -import { - ContentSourcesPackages, - RedHatPackages, -} from './formComponents/Packages'; -import RadioWithPopover from './formComponents/RadioWithPopover'; -import Registration from './formComponents/Registration'; -import RegistrationKeyInformation from './formComponents/RegistrationKeyInformation'; -import ReleaseLifecycle from './formComponents/ReleaseLifecycle'; -import Repositories from './formComponents/Repositories'; -import Review from './formComponents/ReviewStep'; -import TargetEnvironment from './formComponents/TargetEnvironment'; - -const ImageCreator = ({ - schema, - onSubmit, - onClose, - customComponentMapper, - customValidatorMapper, - defaultArch, - className, - ...props -}) => { - return schema ? ( - ( - - )} - onSubmit={(formValues) => onSubmit(formValues)} - validatorMapper={{ ...customValidatorMapper }} - componentMapper={{ - ...componentMapper, - review: Review, - output: TargetEnvironment, - select: Select, - 'package-selector': { - component: RedHatPackages, - defaultArch, - }, - 'package-selector-content-sources': { - component: ContentSourcesPackages, - }, - 'radio-popover': RadioWithPopover, - 'azure-auth-button': AzureAuthButton, - 'activation-keys': ActivationKeys, - 'activation-key-information': RegistrationKeyInformation, - 'file-system-configuration': FileSystemConfiguration, - 'image-output-release-select': ImageOutputReleaseSelect, - 'centos-acknowledgement': CentOSAcknowledgement, - 'repositories-table': Repositories, - 'aws-sources-select': AWSSourcesSelect, - 'azure-sources-select': AzureSourcesSelect, - 'azure-resource-groups': AzureResourceGroups, - 'gallery-layout': GalleryLayout, - 'oscap-profile-selector': Oscap, - 'image-output-arch-select': ArchSelect, - 'image-output-release-lifecycle': ReleaseLifecycle, - registration: Registration, - ...customComponentMapper, - }} - onCancel={onClose} - {...props} - /> - ) : ( - - ); -}; - -ImageCreator.propTypes = { - schema: PropTypes.object, - onSubmit: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - customComponentMapper: PropTypes.shape({ - [PropTypes.string]: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.shape({ - component: PropTypes.node, - }), - ]), - }), - customValidatorMapper: PropTypes.shape({ - [PropTypes.string]: PropTypes.func, - }), - defaultArch: PropTypes.string, - className: PropTypes.string, - initialValues: PropTypes.object, -}; - -export default ImageCreator; diff --git a/src/Components/CreateImageWizard/formComponents/AWSSourcesSelect.js b/src/Components/CreateImageWizard/formComponents/AWSSourcesSelect.js deleted file mode 100644 index dd3a99db..00000000 --- a/src/Components/CreateImageWizard/formComponents/AWSSourcesSelect.js +++ /dev/null @@ -1,165 +0,0 @@ -import React, { useEffect, 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 { Alert } from '@patternfly/react-core'; -import { FormGroup, Spinner } from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import PropTypes from 'prop-types'; - -import { extractProvisioningList } from '../../../store/helpers'; -import { - useGetSourceListQuery, - useGetSourceUploadInfoQuery, -} from '../../../store/provisioningApi'; - -export const AWSSourcesSelect = ({ - label, - isRequired, - className, - ...props -}) => { - const { change, getState } = useFormApi(); - const { input } = useFieldApi(props); - const [isOpen, setIsOpen] = useState(false); - const [selectedSourceId, setSelectedSourceId] = useState( - getState()?.values?.['aws-sources-select'] - ); - - const { - data: rawSources, - isFetching, - isSuccess, - isError, - refetch, - } = useGetSourceListQuery({ provider: 'aws' }); - const sources = extractProvisioningList(rawSources); - - const { - data: sourceDetails, - isFetching: isFetchingDetails, - isSuccess: isSuccessDetails, - isError: isErrorDetails, - } = useGetSourceUploadInfoQuery( - { id: selectedSourceId }, - { - skip: !selectedSourceId, - } - ); - - useEffect(() => { - if (isFetchingDetails || !isSuccessDetails) return; - change('aws-associated-account-id', sourceDetails?.aws?.account_id); - }, [ - isFetchingDetails, - isSuccessDetails, - change, - sourceDetails?.aws?.account_id, - ]); - - const onFormChange = ({ values }) => { - if ( - values['aws-target-type'] !== 'aws-target-type-source' || - values[input.name] === undefined - ) { - change(input.name, undefined); - change('aws-associated-account-id', undefined); - } - }; - - const handleSelect = (_, sourceName) => { - const sourceId = sources.find((source) => source.name === sourceName).id; - setSelectedSourceId(sourceId); - setIsOpen(false); - change(input.name, sourceId); - }; - - const handleClear = () => { - setSelectedSourceId(); - change(input.name, undefined); - }; - - const handleToggle = () => { - // Refetch upon opening (but not upon closing) - if (!isOpen) { - refetch(); - } - - setIsOpen(!isOpen); - }; - - return ( - <> - - - - - <> - {isError && ( - - Sources cannot be reached, try again later or enter an AWS account - ID manually. - - )} - {!isError && isErrorDetails && ( - - The AWS account ID for the selected source could not be resolved. - There might be a problem with the source. Verify that the source is - valid in Sources or select a different source. - - )} - - - ); -}; - -AWSSourcesSelect.propTypes = { - className: PropTypes.string, - label: PropTypes.node, - isRequired: PropTypes.bool, -}; diff --git a/src/Components/CreateImageWizard/formComponents/ActivationKeyInformation.tsx b/src/Components/CreateImageWizard/formComponents/ActivationKeyInformation.tsx deleted file mode 100644 index 8d72b420..00000000 --- a/src/Components/CreateImageWizard/formComponents/ActivationKeyInformation.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { useContext } from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context'; -import { - Alert, - Spinner, - Text, - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - TextVariants, -} from '@patternfly/react-core'; -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'; - -const ActivationKeyInformation = (): JSX.Element => { - const { getState } = useFormApi(); - const { currentStep } = useContext(WizardContext); - - const activationKey = getState()?.values?.['subscription-activation-key']; - - const { - data: activationKeyInfo, - isFetching: isFetchingActivationKeyInfo, - isSuccess: isSuccessActivationKeyInfo, - isError: isErrorActivationKeyInfo, - } = useShowActivationKeyQuery( - { name: activationKey }, - { - skip: !activationKey, - } - ); - - return ( - <> - {isFetchingActivationKeyInfo && } - {isSuccessActivationKeyInfo && ( - - - - Name: - - - {activationKey} - - - Role: - - - {activationKeyInfo.body?.role || 'Not defined'} - - - SLA: - - - {activationKeyInfo.body?.serviceLevel || 'Not defined'} - - - Usage: - - - {activationKeyInfo.body?.usage || 'Not defined'} - - - Additional repositories: - - - The core repositories for your operating system version - are always enabled and do not need to be explicitly added - to the activation key. - - - } - > - - - - - {activationKeyInfo.body?.additionalRepositories && - activationKeyInfo.body?.additionalRepositories?.length > 0 ? ( - - - Additional repositories - - - - - - - - - {activationKeyInfo.body?.additionalRepositories?.map( - (repo, index) => ( - - - - ) - )} - -
Name
{repo.repositoryLabel}
- - } - > - -
- ) : ( - 'None' - )} -
- - - )} - {isErrorActivationKeyInfo && ( - - - - Name: - - - {activationKey} - - - - )} - {isErrorActivationKeyInfo && currentStep.name === 'registration' && ( - <> -
- - Information about the activation key cannot be loaded. Please check - the key was not removed and try again later. - - - )} - - ); -}; - -export default ActivationKeyInformation; diff --git a/src/Components/CreateImageWizard/formComponents/ActivationKeys.js b/src/Components/CreateImageWizard/formComponents/ActivationKeys.js deleted file mode 100644 index 45758640..00000000 --- a/src/Components/CreateImageWizard/formComponents/ActivationKeys.js +++ /dev/null @@ -1,200 +0,0 @@ -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, - Spinner, - EmptyState, - Button, - EmptyStateIcon, - EmptyStateBody, - EmptyStateHeader, - EmptyStateFooter, - EmptyStateActions, -} from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import { WrenchIcon, AddCircleOIcon } from '@patternfly/react-icons'; -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; -import PropTypes from 'prop-types'; -import { useDispatch } from 'react-redux'; - -import { CDN_PROD_URL, CDN_STAGE_URL } from '../../../constants'; -import { - useListActivationKeysQuery, - useCreateActivationKeysMutation, -} from '../../../store/rhsmApi'; -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; - -const EmptyActivationsKeyState = ({ handleActivationKeyFn, isLoading }) => ( - - } - /> - - Get started by building a default key, which will be generated and present - for you. - - - - - - - -); - -EmptyActivationsKeyState.propTypes = { - handleActivationKeyFn: PropTypes.func.isRequired, - isLoading: PropTypes.bool, -}; - -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 dispatch = useDispatch(); - - const { - data: activationKeys, - isFetching: isFetchingActivationKeys, - isSuccess: isSuccessActivationKeys, - isError: isErrorActivationKeys, - refetch, - } = useListActivationKeysQuery(); - - const [createActivationKey, { isLoading: isLoadingActivationKey }] = - useCreateActivationKeysMutation(); - useEffect(() => { - if (isProd()) { - change('subscription-server-url', 'subscription.rhsm.redhat.com'); - change('subscription-base-url', CDN_PROD_URL); - } else { - change('subscription-server-url', 'subscription.rhsm.stage.redhat.com'); - change('subscription-base-url', CDN_STAGE_URL); - } - }, [isProd, change]); - - const setActivationKey = (_, selection) => { - selectActivationKey(selection); - setIsOpen(false); - change(input.name, selection); - }; - - const handleClear = () => { - selectActivationKey(); - change(input.name, undefined); - }; - - const handleToggle = () => { - if (!isOpen) { - refetch(); - } - setIsOpen(!isOpen); - }; - - const handleCreateActivationKey = async () => { - const res = await createActivationKey({ - body: { - name: 'activation-key-default', - serviceLevel: 'Self-Support', - }, - }); - refetch(); - if (res.error) { - dispatch( - addNotification({ - variant: 'danger', - title: 'Error creating activation key', - description: res.error?.data?.error?.message, - }) - ); - } - }; - - const isActivationKeysEmpty = - isSuccessActivationKeys && activationKeys.body.length === 0; - - return ( - <> - - - - {isErrorActivationKeys && ( - - Activation keys cannot be reached, try again later. - - )} - - ); -}; - -ActivationKeys.propTypes = { - label: PropTypes.node, - isRequired: PropTypes.bool, -}; - -ActivationKeys.defaultProps = { - label: '', - isRequired: false, -}; - -export default ActivationKeys; diff --git a/src/Components/CreateImageWizard/formComponents/ArchSelect.js b/src/Components/CreateImageWizard/formComponents/ArchSelect.js deleted file mode 100644 index 1c28426a..00000000 --- a/src/Components/CreateImageWizard/formComponents/ArchSelect.js +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect, 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 { FormGroup } from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import PropTypes from 'prop-types'; -import { useSearchParams } from 'react-router-dom'; - -import { ARCHS, AARCH64 } from '../../../constants'; - -const ArchSelect = ({ label, isRequired, ...props }) => { - const { change, getState } = useFormApi(); - const { input } = useFieldApi(props); - const [isOpen, setIsOpen] = useState(false); - - const [searchParams] = useSearchParams(); - - // Set the architecture via search parameter - // Used by Insights assistant or external hyperlinks (access.redhat.com, developers.redhat.com) - const preloadArch = searchParams.get('arch'); - useEffect(() => { - preloadArch === AARCH64 && change(input.name, AARCH64); - }, [change, input.name, preloadArch]); - - const setArch = (_, selection) => { - change(input.name, selection); - setIsOpen(false); - }; - - const setSelectOptions = () => { - var options = []; - ARCHS.forEach((arch) => { - options.push( - - {arch} - - ); - }); - - return options; - }; - - return ( - - {() => ( - - - - )} - - ); -}; - -ArchSelect.propTypes = { - label: PropTypes.node, - isRequired: PropTypes.bool, -}; - -export default ArchSelect; diff --git a/src/Components/CreateImageWizard/formComponents/AwsAccountId.tsx b/src/Components/CreateImageWizard/formComponents/AwsAccountId.tsx deleted file mode 100644 index d2e3eaa7..00000000 --- a/src/Components/CreateImageWizard/formComponents/AwsAccountId.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -import { useGetSourceUploadInfoQuery } from '../../../store/provisioningApi'; - -type AwsAccountIdProps = { - sourceId: number; -}; - -export const AwsAccountId = ({ sourceId }: AwsAccountIdProps) => { - const { data } = useGetSourceUploadInfoQuery({ id: sourceId }); - return <>{data?.aws?.account_id}; -}; diff --git a/src/Components/CreateImageWizard/formComponents/AzureAuthButton.js b/src/Components/CreateImageWizard/formComponents/AzureAuthButton.js deleted file mode 100644 index f5c87d66..00000000 --- a/src/Components/CreateImageWizard/formComponents/AzureAuthButton.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; -import { Button, FormGroup } from '@patternfly/react-core'; - -const AzureAuthButton = () => { - const { getState } = useFormApi(); - - const tenantId = getState()?.values?.['azure-tenant-id']; - const guidRegex = new RegExp( - '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', - 'i' - ); - - return ( - - - - ); -}; - -export default AzureAuthButton; diff --git a/src/Components/CreateImageWizard/formComponents/AzureResourceGroups.js b/src/Components/CreateImageWizard/formComponents/AzureResourceGroups.js deleted file mode 100644 index 9089ce82..00000000 --- a/src/Components/CreateImageWizard/formComponents/AzureResourceGroups.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useState } from 'react'; - -import FormSpy from '@data-driven-forms/react-form-renderer/form-spy'; -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 { FormGroup, Spinner } from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import PropTypes from 'prop-types'; - -import { useGetSourceUploadInfoQuery } from '../../../store/provisioningApi'; - -const AzureResourceGroups = ({ label, isRequired, className, ...props }) => { - const { change, getState } = useFormApi(); - const { input } = useFieldApi(props); - const [isOpen, setIsOpen] = useState(false); - const [sourceId, setSourceId] = useState( - getState()?.values?.['azure-sources-select'] - ); - const onFormChange = ({ values }) => { - setSourceId(values['azure-sources-select']); - }; - - const { data: sourceDetails, isFetching } = useGetSourceUploadInfoQuery( - { id: sourceId }, - { - skip: !sourceId, - } - ); - const resourceGroups = - (sourceId && sourceDetails?.azure?.resource_groups) || []; - - const setResourceGroup = (_, selection) => { - setIsOpen(false); - change(input.name, selection); - }; - - const handleClear = () => { - change(input.name, undefined); - }; - - return ( - - - - - ); -}; - -AzureResourceGroups.propTypes = { - label: PropTypes.node, - isRequired: PropTypes.bool, - className: PropTypes.string, -}; - -AzureResourceGroups.defaultProps = { - label: '', - isRequired: false, - className: '', -}; - -export default AzureResourceGroups; diff --git a/src/Components/CreateImageWizard/formComponents/AzureSourcesSelect.js b/src/Components/CreateImageWizard/formComponents/AzureSourcesSelect.js deleted file mode 100644 index c27fcf83..00000000 --- a/src/Components/CreateImageWizard/formComponents/AzureSourcesSelect.js +++ /dev/null @@ -1,165 +0,0 @@ -import React, { useState, useEffect } from 'react'; - -import FormSpy from '@data-driven-forms/react-form-renderer/form-spy'; -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 } from '@patternfly/react-core'; -import { FormGroup, Spinner } from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import PropTypes from 'prop-types'; - -import { extractProvisioningList } from '../../../store/helpers'; -import { - useGetSourceListQuery, - useGetSourceUploadInfoQuery, -} from '../../../store/provisioningApi'; - -const AzureSourcesSelect = ({ label, isRequired, className, ...props }) => { - const { change } = useFormApi(); - const { input } = useFieldApi(props); - const [isOpen, setIsOpen] = useState(false); - const selectedSourceId = input.value; - - const { - data: rawSources, - isFetching, - isSuccess, - isError, - refetch, - } = useGetSourceListQuery({ provider: 'azure' }); - const sources = extractProvisioningList(rawSources); - - const { - data: sourceDetails, - isFetching: isFetchingDetails, - isSuccess: isSuccessDetails, - isError: isErrorDetails, - } = useGetSourceUploadInfoQuery( - { id: selectedSourceId }, - { - skip: !selectedSourceId, - } - ); - - useEffect(() => { - if (isFetchingDetails || !isSuccessDetails) return; - change('azure-tenant-id', sourceDetails?.azure?.tenant_id); - change('azure-subscription-id', sourceDetails?.azure?.subscription_id); - }, [ - isFetchingDetails, - isSuccessDetails, - sourceDetails?.azure?.subscription_id, - sourceDetails?.azure?.tenant_id, - change, - ]); - - const onFormChange = ({ values }) => { - if ( - values['azure-type'] !== 'azure-type-source' || - values[input.name] === undefined - ) { - change(input.name, undefined); - change('azure-tenant-id', undefined); - change('azure-subscription-id', undefined); - change('azure-resource-group', undefined); - } - }; - - const handleSelect = (_, sourceName) => { - const sourceId = sources.find((source) => source.name === sourceName).id; - change(input.name, sourceId); - setIsOpen(false); - }; - - const handleClear = () => { - change(input.name, undefined); - change('azure-resource-group', undefined); - }; - - const handleToggle = () => { - // Refetch upon opening (but not upon closing) - if (!isOpen) { - refetch(); - } - - setIsOpen(!isOpen); - }; - - return ( - <> - - - - - <> - {isError && ( - - Sources cannot be reached, try again later or enter an account info - for upload manually. - - )} - {!isError && isErrorDetails && ( - - Could not fetch Tenant ID and Subscription ID from Azure for given - Source. Check Sources page for the source availability or select a - different Source. - - )} - - - ); -}; - -AzureSourcesSelect.propTypes = { - className: PropTypes.string, - label: PropTypes.node, - isRequired: PropTypes.bool, -}; - -export default AzureSourcesSelect; diff --git a/src/Components/CreateImageWizard/formComponents/CentOSAcknowledgement.js b/src/Components/CreateImageWizard/formComponents/CentOSAcknowledgement.js deleted file mode 100644 index d149da8d..00000000 --- a/src/Components/CreateImageWizard/formComponents/CentOSAcknowledgement.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; - -import { Alert, Button } from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; - -import { DEVELOPERS_URL } from '../../../constants'; - -const DeveloperProgramButton = () => { - return ( - - ); -}; - -const CentOSAcknowledgement = () => { - return ( - - CentOS Stream builds are intended for the development of future - versions of RHEL and are not supported for production workloads or - other use cases. - - } - > -

- Join the to learn about paid and no-cost RHEL - subscription options. -

-
- ); -}; - -export default CentOSAcknowledgement; diff --git a/src/Components/CreateImageWizard/formComponents/CustomButtons.js b/src/Components/CreateImageWizard/formComponents/CustomButtons.js deleted file mode 100644 index 7d3a571e..00000000 --- a/src/Components/CreateImageWizard/formComponents/CustomButtons.js +++ /dev/null @@ -1,121 +0,0 @@ -import React, { useContext, useState } from 'react'; - -import { FormSpy, useFormApi } from '@data-driven-forms/react-form-renderer'; -import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context'; -import { Button } from '@patternfly/react-core'; -import PropTypes from 'prop-types'; - -import { contentSourcesApi } from '../../../store/contentSourcesApi'; -import { rhsmApi } from '../../../store/rhsmApi'; -import { useCheckRepositoriesAvailability } from '../../../Utilities/checkRepositoriesAvailability'; -import { releaseToVersion } from '../../../Utilities/releaseToVersion'; - -const CustomButtons = ({ - buttonLabels: { cancel, next, submit, back }, - handleNext, - handlePrev, - nextStep, -}) => { - const { getState } = useFormApi(); - const [isSaving, setIsSaving] = useState(false); - const { currentStep, formOptions } = useContext(WizardContext); - const prefetchActivationKeys = rhsmApi.usePrefetch('listActivationKeys'); - const prefetchRepositories = - contentSourcesApi.usePrefetch('listRepositories'); - const hasUnavailableRepo = useCheckRepositoriesAvailability(); - - const onNextOrSubmit = () => { - if (currentStep.id === 'wizard-review') { - formOptions.onSubmit({ - values: formOptions.getState().values, - setIsSaving, - }); - } else { - if (typeof nextStep === 'function') { - handleNext(nextStep({ values: formOptions.getState().values })); - } else { - handleNext(nextStep); - } - } - }; - - const onMouseEnter = () => { - if (currentStep.id === 'wizard-imageoutput') { - prefetchActivationKeys(); - } - if (currentStep.id === 'wizard-systemconfiguration-packages') { - const arch = getState().values?.arch; - const release = getState().values?.release; - const version = releaseToVersion(release); - prefetchRepositories({ - availableForArch: arch, - availableForVersion: version, - contentType: 'rpm', - origin: 'external', - }); - } - }; - - return ( - - {() => ( - - - -
- -
-
- )} -
- ); -}; - -CustomButtons.propTypes = { - buttonLabels: PropTypes.shape({ - cancel: PropTypes.node, - submit: PropTypes.node, - back: PropTypes.node, - next: PropTypes.node, - }), - handleNext: PropTypes.func, - handlePrev: PropTypes.func, - nextStep: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), -}; - -export default CustomButtons; diff --git a/src/Components/CreateImageWizard/formComponents/FileSystemConfigButtons.js b/src/Components/CreateImageWizard/formComponents/FileSystemConfigButtons.js deleted file mode 100644 index 1a76b494..00000000 --- a/src/Components/CreateImageWizard/formComponents/FileSystemConfigButtons.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context'; -import { Button } from '@patternfly/react-core'; -import PropTypes from 'prop-types'; - -import { imageBuilderApi } from '../../../store/imageBuilderApi'; - -// FileSystemconfigButtons are defined separately to display errors inside of the button footer -const FileSystemConfigButtons = ({ handleNext, handlePrev, nextStep }) => { - const { currentStep, formOptions } = useContext(WizardContext); - const { change, getState } = useFormApi(); - const [hasErrors, setHasErrors] = useState( - getState()?.errors?.['file-system-configuration'] ? true : false - ); - const [nextHasBeenClicked, setNextHasBeenClicked] = useState(false); - const prefetchArchitectures = imageBuilderApi.usePrefetch('getArchitectures'); - const errors = getState()?.errors?.['file-system-configuration']; - - useEffect(() => { - errors ? setHasErrors(true) : setHasErrors(false); - - if (!errors) { - setNextHasBeenClicked(false); - change('file-system-config-show-errors', false); - } - }, [errors, change]); - - const handleClick = () => { - if (!hasErrors) { - handleNext(nextStep); - } - - setNextHasBeenClicked(true); - change('file-system-config-show-errors', true); - }; - - const handleMouseEnter = () => { - const distribution = getState()?.values?.release; - prefetchArchitectures({ distribution }); - }; - - return ( - <> - - -
- -
- - ); -}; - -FileSystemConfigButtons.propTypes = { - handleNext: PropTypes.func, - handlePrev: PropTypes.func, - nextStep: PropTypes.string, -}; - -export default FileSystemConfigButtons; diff --git a/src/Components/CreateImageWizard/formComponents/FileSystemConfiguration.js b/src/Components/CreateImageWizard/formComponents/FileSystemConfiguration.js deleted file mode 100644 index 1be96d4e..00000000 --- a/src/Components/CreateImageWizard/formComponents/FileSystemConfiguration.js +++ /dev/null @@ -1,495 +0,0 @@ -import React, { useEffect, useRef, 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 { - Alert, - Button, - Popover, - Spinner, - Text, - TextContent, - TextVariants, -} from '@patternfly/react-core'; -import { - HelpIcon, - MinusCircleIcon, - PlusCircleIcon, -} from '@patternfly/react-icons'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import styles from '@patternfly/react-styles/css/components/Table/table'; -import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; -import { v4 as uuidv4 } from 'uuid'; - -import MountPoint, { MountPointValidPrefixes } from './MountPoint'; -import SizeUnit from './SizeUnit'; -import UsrSubDirectoriesDisabled from './UsrSubDirectoriesDisabled'; - -import { - FILE_SYSTEM_CUSTOMIZATION_URL, - UNIT_GIB, - UNIT_MIB, -} from '../../../constants'; -import { useGetOscapCustomizationsQuery } from '../../../store/imageBuilderApi'; - -const initialRow = { - id: uuidv4(), - mountpoint: '/', - fstype: 'xfs', - size: 10, - unit: UNIT_GIB, -}; - -const FileSystemConfiguration = ({ ...props }) => { - const { change, getState } = useFormApi(); - const { input } = useFieldApi(props); - const [draggedItemId, setDraggedItemId] = useState(null); - const [draggingToItemIndex, setDraggingToItemIndex] = useState(null); - const [isDragging, setIsDragging] = useState(false); - const [itemOrder, setItemOrder] = useState([initialRow.id]); - const [tempItemOrder, setTempItemOrder] = useState([]); - const bodyref = useRef(); - const [rows, setRows] = useState([initialRow]); - - const oscapProfile = getState()?.values?.['oscap-profile']; - const hasNoOscapProfile = !oscapProfile; - const hasCustomizations = !( - getState()?.values?.['file-system-configuration'] === undefined || - getState().values['file-system-configuration'].length === 1 - ); - - const { - data: customizations, - isFetching: isFetchingCustomizations, - isSuccess, - } = useGetOscapCustomizationsQuery( - { - distribution: getState()?.values?.['release'], - profile: oscapProfile, - }, - { - // Don't override the user's data if they made customizations - skip: hasNoOscapProfile || hasCustomizations, - } - ); - - useEffect(() => { - if (hasCustomizations || rows.length > 1) { - return; - } - if (customizations && customizations.filesystem && isSuccess) { - const newRows = rows; - - // The filesystem customizations required by the OpenSCAP profile may - // contain some unsupported mountpoints. They need to be filtered out to - // prevent undefined behaviour in the frontend and the backend - const fss = customizations.filesystem.filter((fs) => - MountPointValidPrefixes.includes( - '/'.concat(fs.mountpoint.split('/')[1]) - ) - ); - - // And add them all to the list. - for (const fs of fss) { - newRows.push({ - id: uuidv4(), - mountpoint: fs.mountpoint, - fstype: 'xfs', - size: fs.min_size / UNIT_MIB, // the unit from the customizations are in bytes - unit: UNIT_MIB, // and using MIB seems sensible here instead - }); - } - setRows(newRows); - setItemOrder(newRows.map((row) => row.id)); - change('file-system-config-radio', 'manual'); - } - }, [customizations, isSuccess, change, hasCustomizations, rows]); - - useEffect(() => { - const fsc = getState()?.values?.['file-system-configuration']; - if (!fsc) { - return; - } - - const newRows = []; - const newOrder = []; - fsc.forEach((r) => { - const id = uuidv4(); - newRows.push({ - id, - mountpoint: r.mountpoint, - fstype: 'xfs', - size: r.size, - unit: r.unit, - }); - newOrder.push(id); - }); - setRows(newRows); - setItemOrder(newOrder); - }, [getState]); - - const showErrors = () => - getState()?.values?.['file-system-config-show-errors']; - - useEffect(() => { - change( - input.name, - itemOrder.map((r) => { - for (const r2 of rows) { - if (r2.id === r) { - return { - mountpoint: r2.mountpoint, - size: r2.size, - unit: r2.unit, - }; - } - } - return null; - }) - ); - }, [rows, itemOrder, change, input.name]); - - const addRow = () => { - const id = uuidv4(); - setRows( - rows.concat([ - { - id, - mountpoint: '/home', - fstype: 'xfs', - size: 1, - unit: UNIT_GIB, - }, - ]) - ); - setItemOrder(itemOrder.concat([id])); - }; - - const removeRow = (id) => { - const removeIndex = rows.map((e) => e.id).indexOf(id); - const newRows = [...rows]; - newRows.splice(removeIndex, 1); - - const removeOrderIndex = itemOrder.indexOf(id); - const newOrder = [...itemOrder]; - newOrder.splice(removeOrderIndex, 1); - - setRows(newRows); - setItemOrder(newOrder); - }; - - const moveItem = (arr, i1, toIndex) => { - const fromIndex = arr.indexOf(i1); - if (fromIndex === toIndex) { - return arr; - } - - const temp = arr.splice(fromIndex, 1); - arr.splice(toIndex, 0, temp[0]); - return arr; - }; - - const move = (itemOrder) => { - const ulNode = bodyref.current; - const nodes = Array.from(ulNode.children); - if (nodes.map((node) => node.id).every((id, i) => id === itemOrder[i])) { - return; - } - - while (ulNode.firstChild) { - ulNode.removeChild(ulNode.lastChild); - } - - itemOrder.forEach((id) => { - ulNode.appendChild(nodes.find((n) => n.id === id)); - }); - }; - - const onDragOver = (evt) => { - evt.preventDefault(); - - const curListItem = evt.target.closest('tr'); - if (!curListItem || !bodyref.current.contains(curListItem)) { - return null; - } - - const dragId = curListItem.id; - const newDraggingToItemIndex = Array.from( - bodyref.current.children - ).findIndex((item) => item.id === dragId); - if (newDraggingToItemIndex !== draggingToItemIndex) { - const tempItemOrder = moveItem( - [...itemOrder], - draggedItemId, - newDraggingToItemIndex - ); - move(tempItemOrder); - setDraggingToItemIndex(newDraggingToItemIndex); - setTempItemOrder(tempItemOrder); - } - }; - - const isValidDrop = (evt) => { - const ulRect = bodyref.current.getBoundingClientRect(); - return ( - evt.clientX > ulRect.x && - evt.clientX < ulRect.x + ulRect.width && - evt.clientY > ulRect.y && - evt.clientY < ulRect.y + ulRect.height - ); - }; - - const onDragLeave = (evt) => { - if (!isValidDrop(evt)) { - move(itemOrder); - setDraggingToItemIndex(null); - } - }; - - const onDrop = (evt) => { - if (isValidDrop(evt)) { - setItemOrder(tempItemOrder); - } - }; - - const onDragStart = (evt) => { - evt.dataTransfer.effectAllowed = 'move'; - evt.dataTransfer.setData('text/plain', evt.currentTarget.id); - evt.currentTarget.classList.add(styles.modifiers.ghostRow); - evt.currentTarget.setAttribute('aria-pressed', 'true'); - setDraggedItemId(evt.currentTarget.id); - setIsDragging(true); - }; - - const onDragEnd = (evt) => { - evt.target.classList.remove(styles.modifiers.ghostRow); - evt.target.setAttribute('aria-pressed', 'false'); - setDraggedItemId(null); - setDraggingToItemIndex(null); - setIsDragging(false); - }; - - const setMountpoint = (id, mp) => { - const newRows = [...rows]; - for (let i = 0; i < newRows.length; i++) { - if (newRows[i].id === id) { - const newRow = { ...newRows[i] }; - newRow.mountpoint = mp; - newRows.splice(i, 1, newRow); - break; - } - } - - setRows(newRows); - }; - - const setSize = (id, s, u) => { - const newRows = [...rows]; - for (let i = 0; i < newRows.length; i++) { - if (newRows[i].id === id) { - const newRow = { ...newRows[i] }; - newRow.size = s; - newRow.unit = u; - newRows.splice(i, 1, newRow); - break; - } - } - - setRows(newRows); - }; - - // Don't let the user interact with the partitions while we are getting the - // customizations. Having a customizations added by the user first would mess - // up the logic. - if (isFetchingCustomizations) { - return ; - } - - const hasIsoTarget = () => { - const isoTarget = - getState().values['target-environment']?.['image-installer']; - return isoTarget; - }; - - return ( - - {() => ( - <> - - Configure partitions - - {getState()?.values?.['file-system-configuration']?.find((mp) => - mp.mountpoint.includes('/usr') - ) && } - {rows.length > 1 && - getState()?.errors?.['file-system-configuration']?.duplicates - ?.length !== 0 && - showErrors() && ( - - )} - {rows.length >= 1 && - getState()?.errors?.['file-system-configuration']?.root === false && - showErrors() && ( - - )} - - - Create partitions for your image by defining mount points and - minimum sizes. Image builder creates partitions with a logical - volume (LVM) device type. - - - The order of partitions may change when the image is installed in - order to conform to best practices and ensure functionality. -

- -
-
- {hasIsoTarget() && ( - - )} - - - - - - - - - - {rows.map((row, rowIndex) => ( - - - - - - - ))} - -
- Mount pointType - Minimum size - - - Image Builder may extend this size based on - requirements, selected packages, and configurations. - - - } - > - - - -
- - setMountpoint(row.id, mp)} - /> - {getState().errors['file-system-configuration']?.duplicates - .length !== 0 && - getState().errors[ - 'file-system-configuration' - ]?.duplicates.indexOf(row.mountpoint) !== -1 && - showErrors() && ( - - )} - - {/* always xfs */} - {row.fstype} - - setSize(row.id, s, u)} - /> - -
- - - - - )} -
- ); -}; - -export default FileSystemConfiguration; diff --git a/src/Components/CreateImageWizard/formComponents/GalleryLayout.js b/src/Components/CreateImageWizard/formComponents/GalleryLayout.js deleted file mode 100644 index 2e32bc29..00000000 --- a/src/Components/CreateImageWizard/formComponents/GalleryLayout.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import { Gallery, GalleryItem } from '@patternfly/react-core'; -import PropTypes from 'prop-types'; - -const GalleryLayout = ({ fields, minWidths, maxWidths }) => { - const { renderForm } = useFormApi(); - - return ( - - {fields.map((field) => ( - {renderForm([field])} - ))} - - ); -}; - -GalleryLayout.propTypes = { - fields: PropTypes.array, - maxWidths: PropTypes.object, - minWidths: PropTypes.object, -}; - -export default GalleryLayout; diff --git a/src/Components/CreateImageWizard/formComponents/ImageOutputReleaseSelect.js b/src/Components/CreateImageWizard/formComponents/ImageOutputReleaseSelect.js deleted file mode 100644 index b903c665..00000000 --- a/src/Components/CreateImageWizard/formComponents/ImageOutputReleaseSelect.js +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useEffect, 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 { FormGroup } from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import PropTypes from 'prop-types'; -import { useSearchParams } from 'react-router-dom'; - -import { - RELEASES, - RHEL_8, - RHEL_8_FULL_SUPPORT, - RHEL_8_MAINTENANCE_SUPPORT, - RHEL_9, - RHEL_9_FULL_SUPPORT, - RHEL_9_MAINTENANCE_SUPPORT, -} from '../../../constants'; -import isRhel from '../../../Utilities/isRhel'; -import { toMonthAndYear } from '../../../Utilities/time'; - -const ImageOutputReleaseSelect = ({ label, isRequired, ...props }) => { - const { change, getState } = useFormApi(); - const { input } = useFieldApi(props); - const [isOpen, setIsOpen] = useState(false); - const [showDevelopmentOptions, setShowDevelopmentOptions] = useState(false); - - const [searchParams] = useSearchParams(); - - // Used to set release to RHEL 8 via search parameter, used by Insights assistant - const preloadRelease = searchParams.get('release'); - useEffect(() => { - preloadRelease === 'rhel8' && change(input.name, RHEL_8); - }, [change, input.name, preloadRelease]); - - const setRelease = (_, selection) => { - change(input.name, selection); - setIsOpen(false); - }; - - const handleExpand = () => { - setShowDevelopmentOptions(true); - }; - - const setDescription = (key) => { - let fullSupportEnd = ''; - let maintenanceSupportEnd = ''; - - if (key === RHEL_8) { - fullSupportEnd = toMonthAndYear(RHEL_8_FULL_SUPPORT[1]); - maintenanceSupportEnd = toMonthAndYear(RHEL_8_MAINTENANCE_SUPPORT[1]); - } - - if (key === RHEL_9) { - fullSupportEnd = toMonthAndYear(RHEL_9_FULL_SUPPORT[1]); - maintenanceSupportEnd = toMonthAndYear(RHEL_9_MAINTENANCE_SUPPORT[1]); - } - - if (isRhel(key)) { - return `Full support ends: ${fullSupportEnd} | Maintenance support ends: ${maintenanceSupportEnd}`; - } - }; - - const setSelectOptions = () => { - var options = []; - const filteredRhel = new Map( - [...RELEASES].filter(([key]) => { - // Only show non-RHEL distros if expanded - if (showDevelopmentOptions) { - return true; - } - return isRhel(key); - }) - ); - - filteredRhel.forEach((value, key) => { - options.push( - - {RELEASES.get(key)} - - ); - }); - - return options; - }; - - return ( - - {() => ( - - - - )} - - ); -}; - -ImageOutputReleaseSelect.propTypes = { - label: PropTypes.node, - isRequired: PropTypes.bool, -}; - -export default ImageOutputReleaseSelect; diff --git a/src/Components/CreateImageWizard/formComponents/MountPoint.js b/src/Components/CreateImageWizard/formComponents/MountPoint.js deleted file mode 100644 index 40c69ab1..00000000 --- a/src/Components/CreateImageWizard/formComponents/MountPoint.js +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import path from 'path'; - -import { Grid, GridItem, TextInput } from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import PropTypes from 'prop-types'; - -export const MountPointValidPrefixes = [ - '/app', - '/boot', - '/data', - '/home', - '/opt', - '/srv', - '/tmp', - '/usr', - '/var', -]; - -const MountPoint = ({ ...props }) => { - const [isOpen, setIsOpen] = useState(false); - const [prefix, setPrefix] = useState('/'); - const [suffix, setSuffix] = useState(''); - - // split - useEffect(() => { - for (const p of MountPointValidPrefixes) { - if (props.mountpoint.startsWith(p)) { - setPrefix(p); - setSuffix(props.mountpoint.substring(p.length)); - return; - } - } - }, [props.mountpoint]); - - useEffect(() => { - let suf = suffix; - let mp = prefix; - if (suf) { - if (mp !== '/' && suf[0] !== '/') { - suf = '/' + suf; - } - - mp += suf; - } - - props.onChange(path.normalize(mp)); - }, [prefix, suffix]); - - const onToggle = (isOpen) => { - setIsOpen(isOpen); - }; - - const onSelect = (event, selection) => { - setPrefix(selection); - setIsOpen(false); - }; - - return ( - // TODO make these stack vertically for xs viewport - - - - - - {prefix !== '/' && - !prefix.startsWith('/boot') && - !prefix.startsWith('/usr') && ( - setSuffix(v)} - /> - )} - - - ); -}; - -MountPoint.propTypes = { - mountpoint: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, -}; - -export default MountPoint; diff --git a/src/Components/CreateImageWizard/formComponents/Oscap.tsx b/src/Components/CreateImageWizard/formComponents/Oscap.tsx deleted file mode 100644 index dd06a975..00000000 --- a/src/Components/CreateImageWizard/formComponents/Oscap.tsx +++ /dev/null @@ -1,322 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import useFieldApi, { - UseFieldApiConfig, -} 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, - Spinner, - Popover, - TextContent, - Text, - Button, -} from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import { HelpIcon } from '@patternfly/react-icons'; - -import OscapProfileInformation from './OscapProfileInformation'; - -import { - DistributionProfileItem, - useGetOscapCustomizationsQuery, - useGetOscapProfilesQuery, -} from '../../../store/imageBuilderApi'; -import { reinitFileSystemConfiguratioStep } from '../steps/fileSystemConfiguration'; -import { reinitPackagesStep } from '../steps/packages'; - -type ChangeType = >( - name: F, - value?: Record[F] -) => void; - -/** - * Every time there is change on this form step's state, reinitialise the steps - * that are depending on it. This will ensure that if the user goes back and - * change their mind, going forward again leaves them in a coherent and workable - * form state. - */ -const reinitDependingSteps = (change: ChangeType) => { - reinitFileSystemConfiguratioStep(change); - reinitPackagesStep(change); -}; -type ProfileSelectorProps = { - input: { name: string }; -}; -/** - * Component for the user to select the profile to apply to their image. - * The selected profile will be stored in the `oscap-profile` form state variable. - * The Component is shown or not depending on the ShowSelector variable. - */ - -const ProfileSelector = ({ input }: ProfileSelectorProps) => { - const { change, getState } = useFormApi(); - const [profileName, setProfileName] = useState('None'); - const [profile, setProfile] = useState(getState()?.values?.['oscap-profile']); - const [isOpen, setIsOpen] = useState(false); - const { - data: profiles, - isFetching, - isSuccess, - isError, - refetch, - } = useGetOscapProfilesQuery({ - distribution: getState()?.values?.['release'], - }); - - const { data } = useGetOscapCustomizationsQuery( - { - distribution: getState()?.values?.['release'], - profile: profile, - }, - { - skip: !profile, - } - ); - - useEffect(() => { - if ( - data && - data.openscap && - typeof data.openscap.profile_name === 'string' - ) { - setProfileName(data.openscap.profile_name); - } - if (data?.kernel) { - change('kernel', data.kernel); - } - if (data?.services?.enabled) { - change('enabledServices', data.services.enabled); - } - if (data?.services?.masked) { - change('maskedServices', data.services.masked); - } - }, [data, change]); - - const handleToggle = () => { - if (!isOpen) { - refetch(); - } - setIsOpen(!isOpen); - }; - - const handleClear = () => { - setProfile(undefined); - change(input.name, undefined); - change('kernel', undefined); - change('enabledServices', undefined); - change('maskedServices', undefined); - setProfileName(''); - reinitDependingSteps(change); - }; - - const handleSelect = (_: React.MouseEvent, selection: string) => { - setProfile(selection); - setIsOpen(false); - change(input.name, selection); - reinitDependingSteps(change); - change('file-system-config-radio', 'manual'); - }; - - const options = [ - , - ]; - if (isSuccess) { - options.concat( - profiles.map((profile_id) => { - return ( - - ); - }) - ); - } - - if (isFetching) { - options.push( - - - - ); - } - - return ( - - OpenSCAP profile - - - To run a manual compliance scan in OpenSCAP, download this - image. - - - } - > - - - - } - > - - {isError && ( - - Cannot get the list of profiles - - )} - - ); -}; - -type OScapNoneOptionPropType = { - setProfileName: (name: string) => void; -}; - -const OScapNoneOption = ({ setProfileName }: OScapNoneOptionPropType) => { - return ( - { - setProfileName('None'); - }} - > -

{'None'}

-
- ); -}; - -type OScapSelectOptionPropType = { - profile_id: DistributionProfileItem; - setProfileName: (name: string) => void; - input?: string; -}; - -const OScapSelectOption = ({ - profile_id, - setProfileName, - input, -}: OScapSelectOptionPropType) => { - const { getState } = useFormApi(); - const { data } = useGetOscapCustomizationsQuery({ - distribution: getState()?.values?.['release'], - profile: profile_id, - }); - - if ( - input && - !data?.openscap?.profile_name?.toLowerCase().includes(input.toLowerCase()) - ) { - return null; - } - - return ( - { - if (data?.openscap?.profile_name) { - setProfileName(data.openscap.profile_name); - } - }} - > -

{data?.openscap?.profile_name}

-
- ); -}; - -type ProfileTypeProp = { - input: { name: string }; -}; - -/** - * Component to prompt the use with two choices: - * - to add a profile, in which case the ProfileSelector will allow the user to - * pick a profile to be stored in the `oscap-profile` variable. - * - to not add a profile, in which case the `oscap-profile` form state goes - * undefined. - */ -const AddProfile = ({ input }: ProfileTypeProp) => { - return ( - <> - - - - ); -}; - -interface OscapProps extends UseFieldApiConfig {} - -export const Oscap = (props: OscapProps) => { - const { input } = useFieldApi(props); - return ; -}; diff --git a/src/Components/CreateImageWizard/formComponents/OscapProfileInformation.tsx b/src/Components/CreateImageWizard/formComponents/OscapProfileInformation.tsx deleted file mode 100644 index f6975d64..00000000 --- a/src/Components/CreateImageWizard/formComponents/OscapProfileInformation.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import { - Spinner, - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - CodeBlock, - CodeBlockCode, - Alert, -} from '@patternfly/react-core'; - -import { RELEASES } from '../../../constants'; -import { useGetOscapCustomizationsQuery } from '../../../store/imageBuilderApi'; - -const OscapProfileInformation = (): JSX.Element => { - const { getState } = useFormApi(); - - const oscapProfile = getState()?.values?.['oscap-profile']; - - const { - data: oscapProfileInfo, - isFetching: isFetchingOscapProfileInfo, - isSuccess: isSuccessOscapProfileInfo, - } = useGetOscapCustomizationsQuery( - { distribution: getState()?.values?.['release'], profile: oscapProfile }, - { - skip: !oscapProfile, - } - ); - - const enabledServicesDisplayString = - oscapProfileInfo?.services?.enabled?.join(' '); - const maskedServicesDisplayString = - oscapProfileInfo?.services?.masked?.join(' '); - - return ( - <> - {isFetchingOscapProfileInfo && } - {isSuccessOscapProfileInfo && ( - <> - -
- - - Profile description: - - - {oscapProfileInfo.openscap?.profile_description} - - - Operating system: - - - {RELEASES.get(getState()?.values?.['release'])} - - - Reference ID: - - - {oscapProfileInfo.openscap?.profile_id} - - - Kernel arguments: - - - - - {oscapProfileInfo?.kernel?.append} - - - - - Disabled services: - - - - {maskedServicesDisplayString} - - - - Enabled services: - - - - {enabledServicesDisplayString} - - - -
- - - Selecting an OpenSCAP profile will cause the appropriate packages, - file system configuration, kernel arguments, and services to be - added to your image. - - - )} - - ); -}; - -export default OscapProfileInformation; diff --git a/src/Components/CreateImageWizard/formComponents/Packages.js b/src/Components/CreateImageWizard/formComponents/Packages.js deleted file mode 100644 index d56f4d61..00000000 --- a/src/Components/CreateImageWizard/formComponents/Packages.js +++ /dev/null @@ -1,533 +0,0 @@ -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; - -import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; -import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context'; -import { - Alert, - Divider, - DualListSelector, - DualListSelectorControl, - DualListSelectorControlsWrapper, - DualListSelectorList, - DualListSelectorListItem, - DualListSelectorPane, - SearchInput, - TextContent, -} from '@patternfly/react-core'; -import { - AngleDoubleLeftIcon, - AngleDoubleRightIcon, - AngleLeftIcon, - AngleRightIcon, -} from '@patternfly/react-icons'; -import PropTypes from 'prop-types'; - -import api from '../../../api'; -import { - useGetArchitecturesQuery, - useGetOscapCustomizationsQuery, -} from '../../../store/imageBuilderApi'; - -const ExactMatch = ({ - pkgList, - search, - chosenPackages, - selectedAvailablePackages, - handleSelectAvailableFunc, -}) => { - const match = pkgList.find((pkg) => pkg.name === search); - return ( - handleSelectAvailableFunc(e, match.name)} - > - - Exact match - {match.name} - {match.summary} - - - - ); -}; - -export const RedHatPackages = ({ defaultArch }) => { - const { getState } = useFormApi(); - const distribution = getState()?.values?.release; - const arch = getState()?.values?.arch; - const { data: distributionInformation, isSuccess: isSuccessDistroInfo } = - useGetArchitecturesQuery({ distribution }); - - const getAllPackages = async (packagesSearchName) => { - // if the env is stage beta then use content-sources api - // else use image-builder api - if (getState()?.values?.contentSourcesEnabled) { - const filteredByArch = distributionInformation.find( - (info) => info.arch === arch - ); - const repoUrls = filteredByArch.repositories.map((repo) => repo.baseurl); - return await api.getPackagesContentSources(repoUrls, packagesSearchName); - } else { - const args = [ - getState()?.values?.release, - getState()?.values?.architecture || defaultArch, - packagesSearchName, - ]; - const response = await api.getPackages(...args); - let { data } = response; - const { meta } = response; - if (data?.length === meta.count) { - return data; - } else if (data) { - ({ data } = await api.getPackages(...args, meta.count)); - return data; - } - } - }; - - return ( - - ); -}; - -export const ContentSourcesPackages = () => { - const { getState } = useFormApi(); - - const getAllPackages = async (packagesSearchName) => { - const repos = getState()?.values?.['payload-repositories']; - const repoUrls = repos?.map((repo) => repo.baseurl); - return await api.getPackagesContentSources(repoUrls, packagesSearchName); - }; - - return ; -}; - -const Packages = ({ getAllPackages, isSuccess }) => { - const { currentStep } = useContext(WizardContext); - const { change, getState } = useFormApi(); - const [packagesSearchName, setPackagesSearchName] = useState(undefined); - const [filterChosen, setFilterChosen] = useState(''); - const [chosenPackages, setChosenPackages] = useState({}); - const [focus, setFocus] = useState(''); - const selectedPackages = getState()?.values?.['selected-packages']; - const [availablePackages, setAvailablePackages] = useState(undefined); - const [selectedAvailablePackages, setSelectedAvailablePackages] = useState( - new Set() - ); - const [selectedChosenPackages, setSelectedChosenPackages] = useState( - new Set() - ); - const firstInputElement = useRef(null); - - const oscapProfile = getState()?.values?.['oscap-profile']; - - const { data: customizations, isSuccess: isSuccessCustomizations } = - useGetOscapCustomizationsQuery( - { - distribution: getState()?.values?.['release'], - profile: oscapProfile, - }, - { - skip: !oscapProfile, - } - ); - useEffect(() => { - if (customizations && customizations.packages && isSuccessCustomizations) { - const oscapPackages = {}; - for (const pkg of customizations.packages) { - oscapPackages[pkg] = { name: pkg }; - } - updateState(oscapPackages); - } - }, [customizations, isSuccessCustomizations, updateState]); - - // this effect only triggers on mount - useEffect(() => { - if (selectedPackages) { - const newChosenPackages = {}; - for (const pkg of selectedPackages) { - newChosenPackages[pkg.name] = pkg; - } - setChosenPackages(newChosenPackages); - } - }, []); - - useEffect(() => { - if (isSuccess) { - firstInputElement.current?.focus(); - } - }, [isSuccess]); - - const searchResultsComparator = useCallback((searchTerm) => { - return (a, b) => { - a = a.name.toLowerCase(); - b = b.name.toLowerCase(); - - // check exact match first - if (a === searchTerm) { - return -1; - } - - if (b === searchTerm) { - return 1; - } - - // check for packages that start with the search term - if (a.startsWith(searchTerm) && !b.startsWith(searchTerm)) { - return -1; - } - - if (b.startsWith(searchTerm) && !a.startsWith(searchTerm)) { - return 1; - } - - // if both (or neither) start with the search term - // sort alphabetically - if (a < b) { - return -1; - } - - if (b < a) { - return 1; - } - - return 0; - }; - }, []); - - const availablePackagesDisplayList = useMemo(() => { - if (availablePackages === undefined) { - return []; - } - const availablePackagesList = Object.values(availablePackages).sort( - searchResultsComparator(packagesSearchName) - ); - return availablePackagesList; - }, [availablePackages, packagesSearchName, searchResultsComparator]); - - const chosenPackagesDisplayList = useMemo(() => { - const chosenPackagesList = Object.values(chosenPackages) - .filter((pkg) => (pkg.name.includes(filterChosen) ? true : false)) - .sort(searchResultsComparator(filterChosen)); - return chosenPackagesList; - }, [chosenPackages, filterChosen, searchResultsComparator]); - - // call api to list available packages - const handleAvailablePackagesSearch = async () => { - const packageList = await getAllPackages(packagesSearchName); - // If no packages are found, Image Builder returns null, while - // Content Sources returns an empty array []. - if (packageList) { - const newAvailablePackages = {}; - for (const pkg of packageList) { - newAvailablePackages[pkg.name] = pkg; - } - setAvailablePackages(newAvailablePackages); - } else { - setAvailablePackages([]); - } - }; - - const keydownHandler = (event) => { - if (event.key === 'Enter') { - if (focus === 'available') { - event.stopPropagation(); - handleAvailablePackagesSearch(); - } - } - }; - - useEffect(() => { - document.addEventListener('keydown', keydownHandler, true); - - return () => { - document.removeEventListener('keydown', keydownHandler, true); - }; - }, []); - - const updateState = useCallback( - (newChosenPackages) => { - setSelectedAvailablePackages(new Set()); - setSelectedChosenPackages(new Set()); - setChosenPackages(newChosenPackages); - change('selected-packages', Object.values(newChosenPackages)); - }, - [change] - ); - - const moveSelectedToChosen = () => { - const newChosenPackages = { ...chosenPackages }; - for (const pkgName of selectedAvailablePackages) { - newChosenPackages[pkgName] = { ...availablePackages[pkgName] }; - } - updateState(newChosenPackages); - }; - - const moveAllToChosen = () => { - const newChosenPackages = { ...chosenPackages, ...availablePackages }; - updateState(newChosenPackages); - }; - - const removeSelectedFromChosen = () => { - const newChosenPackages = {}; - for (const pkgName in chosenPackages) { - if (!selectedChosenPackages.has(pkgName)) { - newChosenPackages[pkgName] = { ...chosenPackages[pkgName] }; - } - } - updateState(newChosenPackages); - }; - - const removeAllFromChosen = () => { - const newChosenPackages = {}; - updateState(newChosenPackages); - }; - - const handleSelectAvailable = (event, pkgName) => { - const newSelected = new Set(selectedAvailablePackages); - newSelected.has(pkgName) - ? newSelected.delete(pkgName) - : newSelected.add(pkgName); - setSelectedAvailablePackages(newSelected); - }; - - const handleSelectChosen = (event, pkgName) => { - const newSelected = new Set(selectedChosenPackages); - newSelected.has(pkgName) - ? newSelected.delete(pkgName) - : newSelected.add(pkgName); - setSelectedChosenPackages(newSelected); - }; - - const handleClearAvailableSearch = () => { - setPackagesSearchName(''); - setAvailablePackages(undefined); - }; - - const handleClearChosenSearch = () => { - setFilterChosen(''); - }; - - return ( - - - setFocus('available')} - onBlur={() => setFocus('')} - onChange={(_, val) => setPackagesSearchName(val)} - submitSearchButtonLabel="Search button for available packages" - onSearch={handleAvailablePackagesSearch} - resetButtonLabel="Clear available packages search" - onClear={handleClearAvailableSearch} - isDisabled={currentStep.name === 'packages' ? !isSuccess : false} - /> - {availablePackagesDisplayList.length >= 100 && ( - - )} - - } - status={ - selectedAvailablePackages.size > 0 - ? `${selectedAvailablePackages.size} - of ${availablePackagesDisplayList.length} items` - : `${availablePackagesDisplayList.length} items` - } - > - - {availablePackages === undefined ? ( -

- Search above to add additional -
- packages to your image -

- ) : availablePackagesDisplayList.length === 0 ? ( - <> -

- No results found -

-
-

- Adjust your search and try again -

- - ) : availablePackagesDisplayList.length >= 100 ? ( - <> - {availablePackagesDisplayList.some( - (pkg) => pkg.name === packagesSearchName - ) && ( - - )} -

- Too many results to display -

-
-

- Please make the search more specific -
- and try again -

- - ) : ( - availablePackagesDisplayList.map((pkg) => { - return ( - handleSelectAvailable(e, pkg.name)} - > - - - {pkg.name} - - {pkg.summary} - - - ); - }) - )} -
-
- - moveSelectedToChosen()} - aria-label="Add selected" - tooltipContent="Add selected" - > - - - = 100 - } - onClick={() => moveAllToChosen()} - aria-label="Add all" - tooltipContent="Add all" - > - - - removeAllFromChosen()} - aria-label="Remove all" - tooltipContent="Remove all" - > - - - removeSelectedFromChosen()} - isDisabled={selectedChosenPackages.size === 0} - aria-label="Remove selected" - tooltipContent="Remove selected" - > - - - - setFocus('chosen')} - onBlur={() => setFocus('')} - onChange={(_, val) => setFilterChosen(val)} - resetButtonLabel="Clear chosen packages search" - onClear={handleClearChosenSearch} - /> - } - status={ - selectedChosenPackages.size > 0 - ? `${selectedChosenPackages.size} - of ${chosenPackagesDisplayList.length} items` - : `${chosenPackagesDisplayList.length} items` - } - isChosen - > - - {Object.values(chosenPackages).length === 0 ? ( -

- No packages added -

- ) : chosenPackagesDisplayList.length === 0 ? ( -

- No packages found -

- ) : ( - chosenPackagesDisplayList.map((pkg) => { - return ( - handleSelectChosen(e, pkg.name)} - > - - - {pkg.name} - - {pkg.summary} - - - ); - }) - )} -
-
-
- ); -}; - -ExactMatch.propTypes = { - pkgList: PropTypes.arrayOf(PropTypes.object), - search: PropTypes.string, - chosenPackages: PropTypes.object, - selectedAvailablePackages: PropTypes.object, - handleSelectAvailableFunc: PropTypes.func, -}; - -RedHatPackages.propTypes = { - defaultArch: PropTypes.string, -}; - -Packages.propTypes = { - getAllPackages: PropTypes.func, - isSuccess: PropTypes.bool, -}; diff --git a/src/Components/CreateImageWizard/formComponents/RadioWithPopover.js b/src/Components/CreateImageWizard/formComponents/RadioWithPopover.js deleted file mode 100644 index 89f96c27..00000000 --- a/src/Components/CreateImageWizard/formComponents/RadioWithPopover.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useRef } from 'react'; - -import Radio from '@data-driven-forms/pf4-component-mapper/radio'; -import PropTypes from 'prop-types'; - -const RadioWithPopover = ({ Popover, ...props }) => { - const ref = useRef(); - return ( - - {props.label} - - - } - /> - ); -}; - -RadioWithPopover.propTypes = { - Popover: PropTypes.elementType.isRequired, - label: PropTypes.node, -}; - -export default RadioWithPopover; diff --git a/src/Components/CreateImageWizard/formComponents/Registration.js b/src/Components/CreateImageWizard/formComponents/Registration.js deleted file mode 100644 index 1e9481fe..00000000 --- a/src/Components/CreateImageWizard/formComponents/Registration.js +++ /dev/null @@ -1,258 +0,0 @@ -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, - FormGroup, - Popover, - Radio, - Text, - TextContent, -} from '@patternfly/react-core'; -import { HelpIcon, ExternalLinkAltIcon } from '@patternfly/react-icons'; -import PropTypes from 'prop-types'; - -import { - INSIGHTS_URL, - RHC_URL, - SUBSCRIPTION_MANAGEMENT_URL, -} from '../../../constants'; - -const RHSMPopover = () => { - return ( - - - Registered systems are entitled to support services, errata, - patches, and upgrades. - - - - } - > - - - ); -}; - -const InsightsPopover = () => { - return ( - - - Red Hat Insights client provides actionable intelligence about your - Red Hat Enterprise Linux environments, helping to identify and - address operational and vulnerability risks before an issue results - in downtime. - - - - } - > - - - ); -}; - -const RhcPopover = () => { - return ( - - - Remote host configuration allows Red Hat Enterprise Linux hosts to - connect to Red Hat Insights. Remote host configuration is required - to use the Red Hat Insights Remediations service. - - - - } - > - - - ); -}; - -const Registration = ({ label, ...props }) => { - const { change, getState } = useFormApi(); - const { input } = useFieldApi(props); - const registerSystem = getState()?.values?.['register-system']; - const [showOptions, setShowOptions] = useState( - registerSystem === 'register-now-insights' || - registerSystem === 'register-now' - ); - - return ( - - {() => ( - - - Monitor & manage subscriptions and access to Red Hat content - - - ) - } - 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 && ( - - ) - } - body={ - showOptions && ( - - Enable predictive analytics and management capabilities - - - } - data-testid="registration-checkbox-insights" - isChecked={ - registerSystem === 'register-now-insights' || - registerSystem === 'register-now-rhc' - } - onChange={(_event, checked) => { - if (checked) { - change(input.name, 'register-now-insights'); - } else { - change(input.name, 'register-now'); - } - }} - id="register-system-now-insights" - name="register-system-insights" - body={ - - Enable remote remediations and system management with - automation - - - } - 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" - /> - } - /> - ) - } - /> - { - setShowOptions(false); - change(input.name, 'register-later'); - }} - /> - - )} - - ); -}; - -Registration.propTypes = { - label: PropTypes.node, -}; - -export default Registration; diff --git a/src/Components/CreateImageWizard/formComponents/RegistrationKeyInformation.js b/src/Components/CreateImageWizard/formComponents/RegistrationKeyInformation.js deleted file mode 100644 index e8ea87ed..00000000 --- a/src/Components/CreateImageWizard/formComponents/RegistrationKeyInformation.js +++ /dev/null @@ -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 ( - - {({ values }) => - isEmpty(values[valueReference]) ? null : ( - - - - ) - } - - ); -}; - -RegistrationKeyInformation.propTypes = { - label: PropTypes.node, - valueReference: PropTypes.node, -}; - -export default RegistrationKeyInformation; diff --git a/src/Components/CreateImageWizard/formComponents/ReleaseLifecycle.tsx b/src/Components/CreateImageWizard/formComponents/ReleaseLifecycle.tsx deleted file mode 100644 index cb72de1d..00000000 --- a/src/Components/CreateImageWizard/formComponents/ReleaseLifecycle.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import React, { useContext } from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context'; -import { - Button, - ExpandableSection, - FormGroup, - Panel, - PanelMain, - Text, -} from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { Chart, registerables } from 'chart.js'; -import annotationPlugin from 'chartjs-plugin-annotation'; -import { Bar } from 'react-chartjs-2'; - -import { - RELEASES, - RELEASE_LIFECYCLE_URL, - RHEL_8, - RHEL_8_FULL_SUPPORT, - RHEL_8_MAINTENANCE_SUPPORT, - RHEL_9, - RHEL_9_FULL_SUPPORT, - RHEL_9_MAINTENANCE_SUPPORT, -} from '../../../constants'; -import 'chartjs-adapter-moment'; -import { toMonthAndYear } from '../../../Utilities/time'; - -Chart.register(annotationPlugin); -Chart.register(...registerables); - -const currentDate = new Date().toISOString(); - -export const chartMajorVersionCfg = { - data: { - labels: ['RHEL 9', 'RHEL 8'], - datasets: [ - { - label: 'Full support', - backgroundColor: '#0066CC', - data: [ - { - x: RHEL_9_FULL_SUPPORT, - y: 'RHEL 9', - }, - { - x: RHEL_8_FULL_SUPPORT, - y: 'RHEL 8', - }, - ], - }, - { - label: 'Maintenance support', - backgroundColor: '#8BC1F7', - data: [ - { - x: RHEL_9_MAINTENANCE_SUPPORT, - y: 'RHEL 9', - }, - { - x: RHEL_8_MAINTENANCE_SUPPORT, - y: 'RHEL 8', - }, - ], - }, - ], - }, - options: { - indexAxis: 'y' as const, - scales: { - x: { - type: 'time' as const, - time: { - unit: 'year' as const, - }, - min: '2019-01-01' as const, - max: '2033-01-01' as const, - }, - y: { - stacked: true, - }, - }, - responsive: true, - maintainAspectRatio: false, - plugins: { - tooltip: { - enabled: false, - }, - legend: { - position: 'bottom' as const, - }, - annotation: { - annotations: { - today: { - type: 'line' as const, - xMin: currentDate, - xMax: currentDate, - borderColor: 'black', - borderWidth: 2, - borderDash: [8, 2], - }, - }, - }, - }, - }, -}; - -export const MajorReleasesLifecyclesChart = () => { - return ( - - - - - - ); -}; - -const ReleaseLifecycle = () => { - const { getState } = useFormApi(); - const { currentStep } = useContext(WizardContext); - const release = getState().values.release; - const [isExpanded, setIsExpanded] = React.useState(true); - - const onToggle = (_event: React.MouseEvent, isExpanded: boolean) => { - setIsExpanded(isExpanded); - }; - - if (release === RHEL_8) { - if (currentStep.name === 'image-output') { - return ( - - - - -
- -
- ); - } else if (currentStep.name === 'review') { - return ( - <> - - {RELEASES.get(release)} will be supported through{' '} - {toMonthAndYear(RHEL_8_FULL_SUPPORT[1])}, with optional ELS support - through {toMonthAndYear(RHEL_8_MAINTENANCE_SUPPORT[1])}. Consider - building an image with {RELEASES.get(RHEL_9)} to extend the support - period. - - - - -
- - ); - } - } -}; - -export default ReleaseLifecycle; diff --git a/src/Components/CreateImageWizard/formComponents/Repositories.js b/src/Components/CreateImageWizard/formComponents/Repositories.js deleted file mode 100644 index 323d6124..00000000 --- a/src/Components/CreateImageWizard/formComponents/Repositories.js +++ /dev/null @@ -1,612 +0,0 @@ -import React, { useMemo, useState } from 'react'; - -import { - useFieldApi, - useFormApi, -} from '@data-driven-forms/react-form-renderer'; -import { - Alert, - Button, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - EmptyStateVariant, - Pagination, - Panel, - PanelMain, - SearchInput, - Spinner, - Toolbar, - ToolbarContent, - ToolbarItem, - EmptyStateHeader, - EmptyStateFooter, - ToggleGroup, - ToggleGroupItem, - PaginationVariant, -} from '@patternfly/react-core'; -import { - Dropdown, - DropdownItem, - DropdownToggle, - DropdownToggleCheckbox, -} from '@patternfly/react-core/deprecated'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { RepositoryIcon } from '@patternfly/react-icons'; -import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; -import PropTypes from 'prop-types'; - -import RepositoriesStatus from './RepositoriesStatus'; -import RepositoryUnavailable from './RepositoryUnavailable'; - -import { useListRepositoriesQuery } from '../../../store/contentSourcesApi'; -import { releaseToVersion } from '../../../Utilities/releaseToVersion'; -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; - -const BulkSelect = ({ - selected, - count, - filteredCount, - perPage, - handleSelectAll, - handleSelectPage, - handleDeselectAll, - isDisabled, -}) => { - const [dropdownIsOpen, setDropdownIsOpen] = useState(false); - - const numSelected = selected.length; - const allSelected = count !== 0 ? numSelected === count : undefined; - const anySelected = numSelected > 0; - const someChecked = anySelected ? null : false; - const isChecked = allSelected ? true : someChecked; - - const items = [ - {`Select none (0 items)`}, - {`Select page (${ - perPage > filteredCount ? filteredCount : perPage - } items)`}, - {`Select all (${count} items)`}, - ]; - - const handleDropdownSelect = () => {}; - - const toggleDropdown = () => setDropdownIsOpen(!dropdownIsOpen); - - return ( - { - anySelected ? handleDeselectAll() : handleSelectAll(); - }} - />, - ]} - onToggle={toggleDropdown} - > - {numSelected !== 0 ? `${numSelected} selected` : null} - - } - isOpen={dropdownIsOpen} - dropdownItems={items} - /> - ); -}; - -// Utility function to convert from Content Sources to Image Builder payload repo API schema -const convertSchemaToIBPayloadRepo = (repo) => { - const imageBuilderRepo = { - baseurl: repo.url, - rhsm: false, - check_gpg: false, - }; - // only include the flag if enabled - if (repo.module_hotfixes) { - imageBuilderRepo.module_hotfixes = repo.module_hotfixes; - } - if (repo.gpg_key) { - imageBuilderRepo.gpgkey = repo.gpg_key; - imageBuilderRepo.check_gpg = true; - imageBuilderRepo.check_repo_gpg = repo.metadata_verification; - } - - return imageBuilderRepo; -}; - -// Utility function to convert from Content Sources to Image Builder custom repo API schema -const convertSchemaToIBCustomRepo = (repo) => { - const imageBuilderRepo = { - id: repo.uuid, - name: repo.name, - baseurl: [repo.url], - check_gpg: false, - }; - // only include the flag if enabled - if (repo.module_hotfixes) { - imageBuilderRepo.module_hotfixes = repo.module_hotfixes; - } - if (repo.gpg_key) { - imageBuilderRepo.gpgkey = [repo.gpg_key]; - imageBuilderRepo.check_gpg = true; - imageBuilderRepo.check_repo_gpg = repo.metadata_verification; - } - - return imageBuilderRepo; -}; - -// Utility function to convert from Image Builder to Content Sources API schema -const convertSchemaToContentSources = (repo) => { - const contentSourcesRepo = { - url: repo.baseurl, - rhsm: false, - }; - if (repo.gpgkey) { - contentSourcesRepo.gpg_key = repo.gpgkey; - contentSourcesRepo.metadata_verification = repo.check_repo_gpg; - } - - return contentSourcesRepo; -}; - -const Repositories = (props) => { - const initializeRepositories = (contentSourcesReposList) => { - // Convert list of repositories into an object where key is repo URL - const contentSourcesRepos = contentSourcesReposList.reduce( - (accumulator, currentValue) => { - accumulator[currentValue.url] = currentValue; - return accumulator; - }, - {} - ); - - // Repositories in the form state can be present when 'Recreate image' is used - // to open the wizard that are not necessarily in content sources. - const formStateReposList = - getState()?.values?.['original-payload-repositories']; - - const mergeRepositories = (contentSourcesRepos, formStateReposList) => { - const formStateRepos = {}; - - for (const repo of formStateReposList) { - formStateRepos[repo.baseurl] = convertSchemaToContentSources(repo); - formStateRepos[repo.baseurl].name = ''; - } - - // In case of duplicate repo urls, the repo from Content Sources overwrites the - // repo from the form state. - const mergedRepos = { ...formStateRepos, ...contentSourcesRepos }; - - return mergedRepos; - }; - - const repositories = formStateReposList - ? mergeRepositories(contentSourcesRepos, formStateReposList) - : contentSourcesRepos; - - return repositories; - }; - - const { getState, change } = useFormApi(); - const { input } = useFieldApi(props); - const [filterValue, setFilterValue] = useState(''); - const [perPage, setPerPage] = useState(10); - const [page, setPage] = useState(1); - const [toggleSelected, setToggleSelected] = useState('toggle-group-all'); - const [selected, setSelected] = useState( - getState()?.values?.['payload-repositories'] - ? getState().values['payload-repositories'].map((repo) => repo.baseurl) - : [] - ); - - const arch = getState().values?.arch; - const release = getState().values?.release; - const version = releaseToVersion(release); - - const firstRequest = useListRepositoriesQuery( - { - availableForArch: arch, - availableForVersion: version, - contentType: 'rpm', - origin: 'external', - limit: 100, - offset: 0, - }, - // The cached repos may be incorrect, for now refetch on mount to ensure that - // they are accurate when this step loads. Future PR will implement prefetching - // and this can be removed. - { refetchOnMountOrArgChange: true } - ); - - const skip = - firstRequest?.data?.meta?.count === undefined || - firstRequest?.data?.meta?.count <= 100; - - // Fetch *all* repositories if there are more than 100 so that typeahead filter works - const followupRequest = useListRepositoriesQuery( - { - availableForArch: arch, - availableForVersion: version, - contentType: 'rpm', - origin: 'external', - limit: firstRequest?.data?.meta?.count, - offset: 0, - }, - { - refetchOnMountOrArgChange: true, - skip: skip, - } - ); - - const { data, isError, isFetching, isLoading, isSuccess, refetch } = - useMemo(() => { - if (firstRequest?.data?.meta?.count > 100) { - return { ...followupRequest }; - } - return { ...firstRequest }; - }, [firstRequest, followupRequest]); - - const repositories = useMemo(() => { - return data ? initializeRepositories(data.data) : {}; - }, [firstRequest.data, followupRequest.data]); - - const handleToggleClick = (event) => { - const id = event.currentTarget.id; - setPage(1); - setToggleSelected(id); - }; - - const isRepoSelected = (repoURL) => selected.includes(repoURL); - - const handlePerPageSelect = (event, newPerPage, newPage) => { - setPerPage(newPerPage); - setPage(newPage); - }; - - const handleSetPage = (event, newPage) => { - setPage(newPage); - }; - - // filter displayed selected packages - const handleFilterRepositories = (_, value) => { - setPage(1); - setFilterValue(value); - }; - - const filteredRepositoryURLs = useMemo(() => { - const repoUrls = Object.values(repositories).filter((repo) => - repo.name.toLowerCase().includes(filterValue.toLowerCase()) - ); - if (toggleSelected === 'toggle-group-all') { - return repoUrls.map((repo) => repo.url); - } else if (toggleSelected === 'toggle-group-selected') { - return repoUrls - .filter((repo) => isRepoSelected(repo.url)) - .map((repo) => repo.url); - } - }, [filterValue, repositories, toggleSelected]); - - const handleClearFilter = () => { - setFilterValue(''); - }; - - const updateFormState = (selectedRepoURLs) => { - // repositories is stored as an object with repoURLs as keys - const selectedRepos = []; - for (const repoURL of selectedRepoURLs) { - selectedRepos.push(repositories[repoURL]); - } - - const payloadRepositories = selectedRepos.map((repo) => - convertSchemaToIBPayloadRepo(repo) - ); - - const customRepositories = selectedRepos.map((repo) => - convertSchemaToIBCustomRepo(repo) - ); - - input.onChange(payloadRepositories); - change('custom-repositories', customRepositories); - }; - - const updateSelected = (selectedRepos) => { - setSelected(selectedRepos); - updateFormState(selectedRepos); - }; - - const handleSelect = (repoURL, rowIndex, isSelecting) => { - if (isSelecting === true) { - updateSelected([...selected, repoURL]); - } else if (isSelecting === false) { - updateSelected( - selected.filter((selectedRepoId) => selectedRepoId !== repoURL) - ); - } - }; - - const handleSelectAll = () => { - updateSelected(Object.keys(repositories)); - }; - - const computeStart = () => perPage * (page - 1); - const computeEnd = () => perPage * page; - - const handleSelectPage = () => { - const pageRepos = filteredRepositoryURLs.slice( - computeStart(), - computeEnd() - ); - - // Filter to avoid adding duplicates - const newSelected = [ - ...pageRepos.filter((repoId) => !selected.includes(repoId)), - ]; - - updateSelected([...selected, ...newSelected]); - }; - - const handleDeselectAll = () => { - updateSelected([]); - }; - - return ( - (isError && ) || - (isLoading && ) || - (isSuccess && ( - <> - {Object.values(repositories).length === 0 ? ( - - ) : ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {filteredRepositoryURLs - .sort((a, b) => { - if (repositories[a].name < repositories[b].name) { - return -1; - } else if ( - repositories[b].name < repositories[a].name - ) { - return 1; - } else { - return 0; - } - }) - .slice(computeStart(), computeEnd()) - .map((repoURL, rowIndex) => { - const repo = repositories[repoURL]; - const repoExists = repo.name ? true : false; - return ( - - - - - - - - ); - })} - -
- NameArchitectureVersionPackagesStatus
- handleSelect(repo.url, rowIndex, isSelecting), - isDisabled: - isFetching || repo.status !== 'Valid', - }} - /> - - {repoExists - ? repo.name - : 'Repository with the following url is no longer available:'} -
- -
- {repoExists ? repo.distribution_arch : '-'} - - {repoExists ? repo.distribution_versions : '-'} - - {repoExists ? repo.package_count : '-'} - - -
-
-
- - - )} - - )) - ); -}; - -const Error = () => { - return ( - - Repositories cannot be reached, try again later. - - ); -}; - -const Loading = () => { - return ( - - } - headingLevel="h4" - /> - - ); -}; - -const Empty = ({ isFetching, refetch }) => { - const { isBeta } = useGetEnvironment(); - return ( - - } - headingLevel="h4" - /> - - Repositories can be added in the "Repositories" area of the - console. Once added, refresh this page to see them. - - - - - - - ); -}; - -BulkSelect.propTypes = { - selected: PropTypes.array, - count: PropTypes.number, - filteredCount: PropTypes.number, - perPage: PropTypes.number, - handleSelectAll: PropTypes.func, - handleSelectPage: PropTypes.func, - handleDeselectAll: PropTypes.func, - isDisabled: PropTypes.bool, -}; - -Empty.propTypes = { - isFetching: PropTypes.bool, - refetch: PropTypes.func, -}; - -export default Repositories; diff --git a/src/Components/CreateImageWizard/formComponents/RepositoriesStatus.tsx b/src/Components/CreateImageWizard/formComponents/RepositoriesStatus.tsx deleted file mode 100644 index e1297eb5..00000000 --- a/src/Components/CreateImageWizard/formComponents/RepositoriesStatus.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React from 'react'; - -import { - Alert, - Button, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Popover, -} from '@patternfly/react-core'; -import { - CheckCircleIcon, - ExclamationCircleIcon, - ExclamationTriangleIcon, - ExternalLinkAltIcon, - InProgressIcon, -} from '@patternfly/react-icons'; - -import { ApiRepositoryResponse } from '../../../store/contentSourcesApi'; -import { - convertStringToDate, - timestampToDisplayString, -} from '../../../Utilities/time'; -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; - -const getLastIntrospection = ( - repoIntrospections: RepositoryStatusProps['repoIntrospections'] -) => { - const currentDate = Date.now(); - const lastIntrospectionDate = convertStringToDate(repoIntrospections); - const timeDeltaInSeconds = Math.floor( - (currentDate - lastIntrospectionDate) / 1000 - ); - - if (timeDeltaInSeconds <= 60) { - return 'A few seconds ago'; - } else if (timeDeltaInSeconds <= 60 * 60) { - return 'A few minutes ago'; - } else if (timeDeltaInSeconds <= 60 * 60 * 24) { - return 'A few hours ago'; - } else { - return timestampToDisplayString(repoIntrospections); - } -}; - -type RepositoryStatusProps = { - repoStatus: ApiRepositoryResponse['status']; - repoUrl: ApiRepositoryResponse['url']; - repoIntrospections: ApiRepositoryResponse['last_introspection_time']; - repoFailCount: ApiRepositoryResponse['failed_introspections_count']; -}; - -const RepositoriesStatus = ({ - repoStatus, - repoUrl, - repoIntrospections, - repoFailCount, -}: RepositoryStatusProps) => { - const { isBeta } = useGetEnvironment(); - if (repoStatus === 'Valid') { - return ( - <> - {repoStatus} - - ); - } else if (repoStatus === 'Invalid' || repoStatus === 'Unavailable') { - return ( - <> - - -

Cannot fetch {repoUrl}

- {(repoIntrospections || repoFailCount) && ( - <> - - {repoIntrospections && ( - - - Last introspection - - - {getLastIntrospection(repoIntrospections)} - - - )} - {repoFailCount && ( - - - Failed attempts - - - {repoFailCount} - - - )} - -
- - )} - - - } - > - -
- - ); - } else if (repoStatus === 'Pending') { - return ( - <> - {repoStatus} - - ); - } -}; - -export default RepositoriesStatus; diff --git a/src/Components/CreateImageWizard/formComponents/RepositoryUnavailable.js b/src/Components/CreateImageWizard/formComponents/RepositoryUnavailable.js deleted file mode 100644 index 39f95ca4..00000000 --- a/src/Components/CreateImageWizard/formComponents/RepositoryUnavailable.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; - -import { Alert, Button } from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; - -import { useCheckRepositoriesAvailability } from '../../../Utilities/checkRepositoriesAvailability'; -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; - -const RepositoryUnavailable = () => { - const { isBeta } = useGetEnvironment(); - - if (useCheckRepositoriesAvailability()) { - return ( - - A repository that was used to build this image previously is not - available. Address the error found in the last introspection and - validate that the repository is still accessible. -
-
- -
- ); - } else { - return; - } -}; - -export default RepositoryUnavailable; diff --git a/src/Components/CreateImageWizard/formComponents/ReviewStep.js b/src/Components/CreateImageWizard/formComponents/ReviewStep.js deleted file mode 100644 index 17572a2f..00000000 --- a/src/Components/CreateImageWizard/formComponents/ReviewStep.js +++ /dev/null @@ -1,211 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; -import { - ExpandableSection, - Text, - TextContent, - TextVariants, -} from '@patternfly/react-core'; -import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; - -import OscapProfileInformation from './OscapProfileInformation'; -import RepositoryUnavailable from './RepositoryUnavailable'; -import { - ContentList, - FSCList, - ImageDetailsList, - ImageOutputList, - RegisterLaterList, - RegisterNowList, - TargetEnvAWSList, - TargetEnvAzureList, - TargetEnvGCPList, - TargetEnvOciList, - TargetEnvOtherList, -} from './ReviewStepTextLists'; -import UsrSubDirectoriesDisabled from './UsrSubDirectoriesDisabled'; - -import isRhel from '../../../Utilities/isRhel'; - -const ReviewStep = () => { - const { auth } = useChrome(); - const [isExpandedImageOutput, setIsExpandedImageOutput] = useState(false); - const [isExpandedTargetEnvs, setIsExpandedTargetEnvs] = useState(false); - const [isExpandedFSC, setIsExpandedFSC] = useState(false); - const [isExpandedContent, setIsExpandedContent] = useState(false); - const [isExpandedRegistration, setIsExpandedRegistration] = useState(false); - const [isExpandedImageDetail, setIsExpandedImageDetail] = useState(false); - const [isExpandedOscapDetail, setIsExpandedOscapDetail] = useState(false); - const { change, getState } = useFormApi(); - - useEffect(() => { - const registerSystem = getState()?.values?.['register-system']; - if (registerSystem?.startsWith('register-now')) { - (async () => { - const userData = await auth?.getUser(); - const id = userData?.identity?.internal?.org_id; - change('subscription-organization-id', id); - })(); - } - }); - - const onToggleImageOutput = (isExpandedImageOutput) => - setIsExpandedImageOutput(isExpandedImageOutput); - const onToggleTargetEnvs = (isExpandedTargetEnvs) => - setIsExpandedTargetEnvs(isExpandedTargetEnvs); - const onToggleFSC = (isExpandedFSC) => setIsExpandedFSC(isExpandedFSC); - const onToggleContent = (isExpandedContent) => - setIsExpandedContent(isExpandedContent); - const onToggleRegistration = (isExpandedRegistration) => - setIsExpandedRegistration(isExpandedRegistration); - const onToggleImageDetail = (isExpandedImageDetail) => - setIsExpandedImageDetail(isExpandedImageDetail); - const onToggleOscapDetails = (isExpandedOscapDetail) => - setIsExpandedOscapDetail(isExpandedOscapDetail); - - return ( - <> - - {getState()?.values?.['file-system-configuration']?.find((mp) => - mp.mountpoint.includes('/usr') - ) && } - - onToggleImageOutput(isExpandedImageOutput) - } - isExpanded={isExpandedImageOutput} - isIndented - data-testid="image-output-expandable" - > - - - - onToggleTargetEnvs(isExpandedTargetEnvs) - } - isExpanded={isExpandedTargetEnvs} - isIndented - data-testid="target-environments-expandable" - > - {getState()?.values?.['target-environment']?.aws && ( - - )} - {getState()?.values?.['target-environment']?.gcp && ( - - )} - {getState()?.values?.['target-environment']?.azure && ( - - )} - {getState()?.values?.['target-environment']?.oci && ( - - )} - {getState()?.values?.['target-environment']?.vsphere && ( - - VMware vSphere (.vmdk) - - - )} - {getState()?.values?.['target-environment']?.['vsphere-ova'] && ( - - VMware vSphere (.ova) - - - )} - {getState()?.values?.['target-environment']?.['guest-image'] && ( - - - Virtualization - Guest image (.qcow2) - - - - )} - {getState()?.values?.['target-environment']?.['image-installer'] && ( - - - Bare metal - Installer (.iso) - - - - )} - {getState()?.values?.['target-environment']?.wsl && ( - - - WSL - Windows Subsystem for Linux (.tar.gz) - - - - )} - - onToggleFSC(isExpandedFSC)} - isExpanded={isExpandedFSC} - isIndented - data-testid="file-system-configuration-expandable" - > - - - - onToggleContent(isExpandedContent) - } - isExpanded={isExpandedContent} - isIndented - data-testid="content-expandable" - > - - - {isRhel(getState()?.values?.release) && ( - - onToggleRegistration(isExpandedRegistration) - } - isExpanded={isExpandedRegistration} - isIndented - data-testid="registration-expandable" - > - {getState()?.values?.['register-system'] === 'register-later' && ( - - )} - {getState()?.values?.['register-system']?.startsWith( - 'register-now' - ) && } - - )} - {(getState()?.values?.['image-name'] || - getState()?.values?.['image-description']) && ( - - onToggleImageDetail(isExpandedImageDetail) - } - isExpanded={isExpandedImageDetail} - isIndented - data-testid="image-details-expandable" - > - - - )} - {getState()?.values?.['oscap-profile'] && ( - - onToggleOscapDetails(isExpandedOscapDetail) - } - isExpanded={isExpandedOscapDetail} - isIndented - data-testid="oscap-detail-expandable" - > - - - )} - - ); -}; - -export default ReviewStep; diff --git a/src/Components/CreateImageWizard/formComponents/ReviewStepTables.js b/src/Components/CreateImageWizard/formComponents/ReviewStepTables.js deleted file mode 100644 index fb84e64f..00000000 --- a/src/Components/CreateImageWizard/formComponents/ReviewStepTables.js +++ /dev/null @@ -1,138 +0,0 @@ -import React from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import { Alert, Panel, PanelMain, Spinner } from '@patternfly/react-core'; -import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; -import PropTypes from 'prop-types'; - -import { UNIT_GIB, UNIT_MIB } from '../../../constants'; -import { useListRepositoriesQuery } from '../../../store/contentSourcesApi'; - -const RepoName = ({ repoUrl }) => { - const { data, isSuccess, isFetching, isError } = useListRepositoriesQuery({ - url: repoUrl, - contentType: 'rpm', - origin: 'external', - }); - - const errorLoading = () => { - return ( - - ); - }; - - return ( - <> - {/* - this might be a tad bit hacky - "isSuccess" indicates only that the query fetched successfuly, but it - doesn't differentiate between a scenario when the repository was found - in the response and when it was not - for this reason I've split the "isSuccess" into two paths: - - query finished and the repo was found -> render the name of the repo - - query finished, but the repo was not found -> render an error - */} - {isSuccess && data.data?.[0]?.name &&

{data.data?.[0].name}

} - {isSuccess && !data.data?.[0]?.name && errorLoading()} - {isFetching && } - {isError && errorLoading()} - - ); -}; - -export const FSReviewTable = () => { - const { getState } = useFormApi(); - const fsc = getState().values['file-system-configuration']; - return ( - - - - - - - - - - - - {fsc.map((partition, partitionIndex) => ( - - - - - - ))} - -
Mount pointFile system typeMinimum size
{partition.mountpoint}xfs - {partition.size}{' '} - {partition.unit === UNIT_GIB - ? 'GiB' - : partition.unit === UNIT_MIB - ? 'MiB' - : 'KiB'} -
-
-
- ); -}; - -export const PackagesTable = () => { - const { getState } = useFormApi(); - const packages = getState()?.values['selected-packages']; - return ( - - - - - - - - - - {packages.map((pkg, pkgIndex) => ( - - - - ))} - -
Name
{pkg.name}
-
-
- ); -}; - -export const RepositoriesTable = () => { - const { getState } = useFormApi(); - const repositories = getState()?.values?.['payload-repositories']; - return ( - - - - - - - - - - {repositories.map((repo, repoIndex) => ( - - - - ))} - -
Name
- -
-
-
- ); -}; - -RepoName.propTypes = { - repoUrl: PropTypes.string, -}; diff --git a/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js b/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js deleted file mode 100644 index d0f48bbf..00000000 --- a/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js +++ /dev/null @@ -1,616 +0,0 @@ -import React from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import { - Alert, - Button, - Popover, - Spinner, - Text, - TextContent, - TextList, - TextListItem, - TextListVariants, - TextListItemVariants, - TextVariants, -} from '@patternfly/react-core'; -import { ExclamationTriangleIcon, HelpIcon } from '@patternfly/react-icons'; -import PropTypes from 'prop-types'; - -import ActivationKeyInformation from './ActivationKeyInformation'; -import { AwsAccountId } from './AwsAccountId'; -import ReleaseLifecycle from './ReleaseLifecycle'; -import { - FSReviewTable, - PackagesTable, - RepositoriesTable, -} from './ReviewStepTables'; - -import { RELEASES, UNIT_GIB } from '../../../constants'; -import { extractProvisioningList } from '../../../store/helpers'; -import { useGetSourceListQuery } from '../../../store/provisioningApi'; -import { useShowActivationKeyQuery } from '../../../store/rhsmApi'; -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; -import { googleAccType } from '../steps/googleCloud'; - -const ExpirationWarning = () => { - return ( -
- Expires 14 days after creation -
- ); -}; - -export const ImageOutputList = () => { - const { getState } = useFormApi(); - return ( - - - - - Release - - - {RELEASES.get(getState()?.values?.release)} - - - Architecture - - - {getState()?.values?.arch} - - -
-
- ); -}; - -export const TargetEnvAWSList = () => { - const { data: rawAWSSources, isSuccess } = useGetSourceListQuery({ - provider: 'aws', - }); - const awsSources = extractProvisioningList(rawAWSSources); - const { isBeta } = useGetEnvironment(); - - const { getState } = useFormApi(); - return ( - - AWS - - - Image type - - - Red Hat hosted image -
- -
- - Shared to account - - - {!isBeta() && getState()?.values?.['aws-account-id']} - {isBeta() && - getState()?.values?.['aws-target-type'] === - 'aws-target-type-source' && - isSuccess && ( - - )} - {isBeta() && - getState()?.values?.['aws-target-type'] === - 'aws-target-type-account-id' && - getState()?.values?.['aws-account-id']} - - - {getState()?.values?.['aws-target-type'] === 'aws-target-type-source' - ? 'Source' - : null} - - - {isSuccess && - getState()?.values?.['aws-target-type'] === 'aws-target-type-source' - ? awsSources.find( - (source) => - source.id === getState()?.values?.['aws-sources-select'] - )?.name - : null} - - - Default region - - - us-east-1 - -
-
-
- ); -}; - -export const TargetEnvGCPList = () => { - const { getState } = useFormApi(); - return ( - - GCP - - - Image type - - - Red Hat hosted image -
- -
- - Account type - - - {googleAccType?.[getState()?.values?.['google-account-type']]} - - - {googleAccType?.[getState()?.values?.['google-account-type']] === - 'Domain' - ? 'Domain' - : 'Principal'} - - - {getState()?.values?.['google-email'] || - getState()?.values?.['google-domain']} - -
-
-
- ); -}; - -export const TargetEnvAzureList = () => { - const { getState } = useFormApi(); - const { data: rawAzureSources, isSuccess: isSuccessAzureSources } = - useGetSourceListQuery({ provider: 'azure' }); - const azureSources = extractProvisioningList(rawAzureSources); - return ( - - Microsoft Azure - - - Image type - - - Red Hat hosted image -
- -
- {getState()?.values?.['azure-type'] === 'azure-type-source' && - isSuccessAzureSources && ( - <> - - Azure Source - - - { - azureSources.find( - (source) => - source.id === getState()?.values?.['azure-sources-select'] - )?.name - } - - - )} - {getState()?.values?.['azure-type'] === 'azure-type-manual' && ( - <> - - Azure Tenant ID - - - {getState()?.values?.['azure-tenant-id']} - - - Subscription ID - - - {getState()?.values?.['azure-subscription-id']} - - - )} - - Resource group - - - {getState()?.values?.['azure-resource-group']} - -
-
-
- ); -}; - -export const TargetEnvOciList = () => { - return ( - - Oracle Cloud Infrastructure - - - Object Storage URL - - - The URL for the built image will be ready to copy -
-
-
-
-
- ); -}; - -export const TargetEnvOtherList = () => { - return ( - <> - - - Image type - - - Built image will be available for download - - -
- - ); -}; - -export const FSCList = () => { - const { getState } = useFormApi(); - const isManual = - getState()?.values?.['file-system-config-radio'] === 'manual'; - const partitions = getState()?.values?.['file-system-configuration']; - - return ( - - - - Configuration type - - - {isManual ? 'Manual' : 'Automatic'} - {isManual && ( - <> - {' '} - } - > - - - - )} - - {isManual && ( - <> - - Image size (minimum) - - - Image Builder may extend this size based on requirements, - selected packages, and configurations. - - - } - > - -
- - - - )} - -
- - ); -}; - -export const MinSize = ({ isManual, partitions }) => { - let minSize = ''; - if (isManual && partitions) { - let size = 0; - for (const partition of partitions) { - size += partition.size * partition.unit; - } - - size = (size / UNIT_GIB).toFixed(1); - if (size < 1) { - minSize = `Less than 1 GiB`; - } else { - minSize = `${size} GiB`; - } - } - - return ( - {minSize} - ); -}; - -MinSize.propTypes = { - isManual: PropTypes.bool, - partitions: PropTypes.arrayOf(PropTypes.object), -}; - -export const ContentList = () => { - const { getState } = useFormApi(); - return ( - - - - Additional Red Hat -
- and 3rd party packages -
- - {getState()?.values?.['selected-packages']?.length > 0 ? ( - } - > - - - ) : ( - 0 - )} - - - Custom repositories - - - {getState()?.values?.['payload-repositories']?.length > 0 ? ( - } - > - - - ) : ( - 0 - )} - -
-
-
- ); -}; - -export const RegisterLaterList = () => { - return ( - - - - Registration type - - - Register the system later - - -
-
- ); -}; - -export const RegisterNowList = () => { - const { getState } = useFormApi(); - const activationKey = getState()?.values?.['subscription-activation-key']; - const { isError } = useShowActivationKeyQuery( - { name: activationKey }, - { - skip: !activationKey, - } - ); - return ( - <> - - - - Registration type - - - - {getState()?.values?.['register-system']?.startsWith( - 'register-now' - ) && ( - - Register with Red Hat Subscription Manager (RHSM) -
-
- )} - {(getState()?.values?.['register-system'] === - 'register-now-insights' || - getState()?.values?.['register-system'] === - 'register-now-rhc') && ( - - Connect to Red Hat Insights -
-
- )} - {getState()?.values?.['register-system'] === - 'register-now-rhc' && ( - - Use remote host configuration (rhc) utility -
-
- )} -
-
- - Activation key - - - Activation keys enable you to register a system with - appropriate subscriptions, system purpose, and repositories - attached. -
-
- If using an activation key with command line registration, - you must provide your organization's ID. Your - organization's ID is{' '} - {getState()?.values?.['subscription-organization-id'] !== - undefined ? ( - getState()?.values?.['subscription-organization-id'] - ) : ( - - )} -
-
- } - > - - - - - - - -
- - {isError && ( - - Information about the activation key cannot be loaded. Please check - the key was not removed and try again later. - - )} - - ); -}; - -export const ImageDetailsList = () => { - const { getState } = useFormApi(); - const imageName = getState()?.values?.['image-name']; - const imageDescription = getState()?.values?.['image-description']; - - return ( - - - {imageName && ( - <> - - Image name - - - {imageName} - - - )} - {imageDescription && ( - <> - - Description - - - {imageDescription} - - - )} - -
-
- ); -}; diff --git a/src/Components/CreateImageWizard/formComponents/SizeUnit.js b/src/Components/CreateImageWizard/formComponents/SizeUnit.js deleted file mode 100644 index 0e4c671e..00000000 --- a/src/Components/CreateImageWizard/formComponents/SizeUnit.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import { Grid, GridItem, TextInput } from '@patternfly/react-core'; -import { - Select, - SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import PropTypes from 'prop-types'; - -import { UNIT_GIB, UNIT_KIB, UNIT_MIB } from '../../../constants'; - -const SizeUnit = ({ ...props }) => { - const [isOpen, setIsOpen] = useState(false); - const [unit, setUnit] = useState(props.unit || UNIT_GIB); - const [size, setSize] = useState(props.size || 1); - - useEffect(() => { - props.onChange(size, unit); - }, [unit, size]); - - const onToggle = (isOpen) => { - setIsOpen(isOpen); - }; - - const onSelect = (event, selection) => { - switch (selection) { - case 'KiB': - setUnit(UNIT_KIB); - break; - case 'MiB': - setUnit(UNIT_MIB); - break; - case 'GiB': - setUnit(UNIT_GIB); - break; - // no default - } - - setIsOpen(false); - }; - - return ( - // TODO make these stack vertically for xs viewport - - - - setSize(isNaN(parseInt(v)) ? 0 : parseInt(v)) - } - /> - - - - - - ); -}; - -SizeUnit.propTypes = { - size: PropTypes.number.isRequired, - unit: PropTypes.number.isRequired, - onChange: PropTypes.func.isRequired, -}; - -export default SizeUnit; diff --git a/src/Components/CreateImageWizard/formComponents/TargetEnvironment.js b/src/Components/CreateImageWizard/formComponents/TargetEnvironment.js deleted file mode 100644 index b9f15881..00000000 --- a/src/Components/CreateImageWizard/formComponents/TargetEnvironment.js +++ /dev/null @@ -1,401 +0,0 @@ -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, - Bullseye, - Checkbox, - FormGroup, - Popover, - Radio, - Spinner, - Text, - TextContent, - TextVariants, - Tile, -} from '@patternfly/react-core'; -import { HelpIcon } from '@patternfly/react-icons'; -import PropTypes from 'prop-types'; -import { useField } from 'react-final-form'; -import { useSearchParams } from 'react-router-dom'; - -import { useGetArchitecturesQuery } from '../../../store/imageBuilderApi'; -import { provisioningApi } from '../../../store/provisioningApi'; -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; - -const useGetAllowedTargets = ({ architecture, release }) => { - const { data, isFetching, isSuccess, isError } = useGetArchitecturesQuery({ - distribution: release, - }); - - let image_types = []; - if (isSuccess && data) { - data.forEach((elem) => { - if (elem.arch === architecture) { - image_types = elem.image_types; - } - }); - } - - return { - data: image_types, - isFetching: isFetching, - isSuccess: isSuccess, - isError: isError, - }; -}; - -const TargetEnvironment = ({ label, isRequired, ...props }) => { - const { getState, change } = useFormApi(); - const { input } = useFieldApi({ label, isRequired, ...props }); - const [environment, setEnvironment] = useState({ - aws: false, - azure: false, - gcp: false, - oci: false, - 'vsphere-ova': false, - vsphere: false, - 'guest-image': false, - 'image-installer': false, - wsl: false, - }); - const prefetchSources = provisioningApi.usePrefetch('getSourceList'); - const { isBeta } = useGetEnvironment(); - const release = getState()?.values?.release; - - const [searchParams] = useSearchParams(); - - // Set the target via search parameter - // Used by Insights assistant or external hyperlinks (access.redhat.com, developers.redhat.com) - const preloadTarget = searchParams.get('target'); - useEffect(() => { - preloadTarget === 'iso' && handleSetEnvironment('image-installer', true); - preloadTarget === 'qcow2' && handleSetEnvironment('guest-image', true); - }, [preloadTarget]); - - useEffect(() => { - if (getState()?.values?.[input.name]) { - setEnvironment(getState().values[input.name]); - } - }, [getState, input.name]); - - const handleSetEnvironment = (env, checked) => - setEnvironment((prevEnv) => { - const newEnv = { - ...prevEnv, - [env]: checked, - }; - change(input.name, newEnv); - return newEnv; - }); - - const handleKeyDown = (e, env, checked) => { - if (e.key === ' ') { - handleSetEnvironment(env, checked); - } - }; - - // Load all the allowed targets from the backend - const architecture = useField('arch').input.value; - - const { - data: allowedTargets, - isFetching, - isSuccess, - isError, - } = useGetAllowedTargets({ - architecture: architecture, - release: release, - }); - - if (isFetching) { - return ( - - - - ); - } - - if (isError || !isSuccess) { - return ( - - Allowed targets cannot be reached, try again later. - - ); - } - - // If the user already made a choice for some targets but then changes their - // architecture or distribution, only keep the target choices that are still - // compatible. - const allTargets = [ - 'aws', - 'gcp', - 'azure', - 'vsphere', - 'vsphere-ova', - 'guest-image', - 'image-installer', - 'wsl', - ]; - allTargets.forEach((target) => { - if (environment[target] && !allowedTargets.includes(target)) { - handleSetEnvironment(target, false); - } - }); - - // each item the user can select is depending on what's compatible with the - // architecture and the distribution they previously selected - return ( - - Public cloud} - data-testid="target-public" - > -
- {allowedTargets.includes('aws') && ( - - } - onClick={() => handleSetEnvironment('aws', !environment.aws)} - onKeyDown={(e) => handleKeyDown(e, 'aws', !environment.aws)} - onMouseEnter={() => prefetchSources({ provider: 'aws' })} - isSelected={environment.aws} - isStacked - isDisplayLarge - /> - )} - {allowedTargets.includes('gcp') && ( - - } - onClick={() => handleSetEnvironment('gcp', !environment.gcp)} - isSelected={environment.gcp} - onKeyDown={(e) => handleKeyDown(e, 'gcp', !environment.gcp)} - onMouseEnter={() => prefetchSources({ provider: 'gcp' })} - isStacked - isDisplayLarge - /> - )} - {allowedTargets.includes('azure') && ( - - } - onClick={() => handleSetEnvironment('azure', !environment.azure)} - onKeyDown={(e) => handleKeyDown(e, 'azure', !environment.azure)} - onMouseEnter={() => prefetchSources({ provider: 'azure' })} - isSelected={environment.azure} - isStacked - isDisplayLarge - /> - )} - {allowedTargets.includes('oci') && ( - - } - onClick={() => handleSetEnvironment('oci', !environment.oci)} - onKeyDown={(e) => handleKeyDown(e, 'oci', !environment.oci)} - isSelected={environment.oci} - isStacked - isDisplayLarge - /> - )} -
-
- {allowedTargets.includes('vsphere') && ( - Private cloud} - className="pf-u-mt-sm" - data-testid="target-private" - > - { - handleSetEnvironment('vsphere-ova', checked); - handleSetEnvironment('vsphere', false); - }} - aria-label="VMware checkbox" - id="checkbox-vmware" - name="VMware" - data-testid="checkbox-vmware" - /> - - )} - {allowedTargets.includes('vsphere') && ( - - {allowedTargets.includes('vsphere-ova') && ( - - Open virtualization format (.ova) - - - An OVA file is a virtual appliance used by - virtualization platforms such as VMware vSphere. It is - a package that contains files used to describe a - virtual machine, which includes a VMDK image, OVF - descriptor file and a manifest file. - - - } - > - - - - } - onChange={(_event, checked) => { - handleSetEnvironment('vsphere-ova', checked); - handleSetEnvironment('vsphere', !checked); - }} - isChecked={environment['vsphere-ova']} - isDisabled={!(environment.vsphere || environment['vsphere-ova'])} - /> - )} - - Virtual disk (.vmdk) - - - A VMDK file is a virtual disk that stores the contents - of a virtual machine. This disk has to be imported into - vSphere using govc import.vmdk, use the OVA version when - using the vSphere UI. - - - } - > - - - - } - onChange={(_event, checked) => { - handleSetEnvironment('vsphere-ova', !checked); - handleSetEnvironment('vsphere', checked); - }} - isChecked={environment.vsphere} - isDisabled={!(environment.vsphere || environment['vsphere-ova'])} - /> - - )} - Other} - data-testid="target-other" - > - {allowedTargets.includes('guest-image') && ( - - handleSetEnvironment('guest-image', checked) - } - aria-label="Virtualization guest image checkbox" - id="checkbox-guest-image" - name="Virtualization guest image" - data-testid="checkbox-guest-image" - /> - )} - {allowedTargets.includes('image-installer') && ( - - handleSetEnvironment('image-installer', checked) - } - aria-label="Bare metal installer checkbox" - id="checkbox-image-installer" - name="Bare metal installer" - data-testid="checkbox-image-installer" - /> - )} - {allowedTargets.includes('wsl') && isBeta() && ( - handleSetEnvironment('wsl', checked)} - aria-label="windows subsystem for linux checkbox" - id="checkbox-wsl" - name="WSL" - data-testid="checkbox-wsl" - /> - )} - -
- ); -}; - -TargetEnvironment.propTypes = { - label: PropTypes.node, - isRequired: PropTypes.bool, -}; - -TargetEnvironment.defaultProps = { - label: '', - isRequired: false, -}; - -export default TargetEnvironment; diff --git a/src/Components/CreateImageWizard/formComponents/UsrSubDirectoriesDisabled.js b/src/Components/CreateImageWizard/formComponents/UsrSubDirectoriesDisabled.js deleted file mode 100644 index b7b912b1..00000000 --- a/src/Components/CreateImageWizard/formComponents/UsrSubDirectoriesDisabled.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -import { Alert } from '@patternfly/react-core'; - -const UsrSubDirectoriesDisabled = () => { - return ( - - Please note that including sub-directories in the /usr path is no longer - supported. Previously included mount points with /usr sub-directory are - replaced by /usr when recreating an image. - - ); -}; - -export default UsrSubDirectoriesDisabled; diff --git a/src/Components/CreateImageWizard/steps/aws.js b/src/Components/CreateImageWizard/steps/aws.js deleted file mode 100644 index 497d03a7..00000000 --- a/src/Components/CreateImageWizard/steps/aws.js +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; -import { - Button, - HelperText, - HelperTextItem, - Title, -} from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; - -import nextStepMapper from './imageOutputStepMapper'; -import StepTemplate from './stepTemplate'; - -import { DEFAULT_AWS_REGION } from '../../../constants'; -import CustomButtons from '../formComponents/CustomButtons'; - -const SourcesButton = () => { - return ( - - ); -}; - -const awsStep = { - StepTemplate, - id: 'wizard-target-aws', - title: 'Amazon Web Services', - customTitle: ( - - Target environment - Amazon Web Services - - ), - name: 'aws-target-env', - substepOf: 'Target environment', - nextStep: ({ values }) => nextStepMapper(values, { skipAws: true }), - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'plain-text-component', - label: ( -

- Your image will be uploaded to AWS and shared with the account you - provide below. -

- ), - }, - { - component: componentTypes.PLAIN_TEXT, - name: 'plain-text-component', - label: ( -

- The shared image will expire within 14 days. To permanently - access the image, copy the image, which will be shared to your account - by Red Hat, to your own AWS account. -

- ), - }, - { - component: componentTypes.RADIO, - label: 'Share method:', - name: 'aws-target-type', - initialValue: 'aws-target-type-source', - autoFocus: true, - options: [ - { - label: 'Use an account configured from Sources.', - description: - 'Use a configured source to launch environments directly from the console.', - value: 'aws-target-type-source', - 'data-testid': 'aws-radio-source', - autoFocus: true, - }, - { - label: 'Manually enter an account ID.', - value: 'aws-target-type-account-id', - 'data-testid': 'aws-radio-account-id', - className: 'pf-u-mt-sm', - }, - ], - }, - { - component: 'aws-sources-select', - name: 'aws-sources-select', - className: 'pf-u-max-width', - label: 'Source Name', - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - condition: { - when: 'aws-target-type', - is: 'aws-target-type-source', - }, - }, - { - component: componentTypes.PLAIN_TEXT, - name: 'aws-sources-select-description', - label: , - condition: { - when: 'aws-target-type', - is: 'aws-target-type-source', - }, - }, - { - component: componentTypes.TEXT_FIELD, - name: 'aws-account-id', - className: 'pf-u-w-25', - 'data-testid': 'aws-account-id', - type: 'text', - label: 'AWS account ID', - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - { - type: validatorTypes.EXACT_LENGTH, - threshold: 12, - }, - ], - condition: { - when: 'aws-target-type', - is: 'aws-target-type-account-id', - }, - }, - { - name: 'gallery-layout', - component: 'gallery-layout', - minWidths: { default: '12.5rem' }, - maxWidths: { default: '12.5rem' }, - fields: [ - { - component: componentTypes.TEXT_FIELD, - name: 'aws-default-region', - value: DEFAULT_AWS_REGION, - 'data-testid': 'aws-default-region', - type: 'text', - label: 'Default Region', - isReadOnly: true, - isRequired: true, - helperText: ( - - - Images are built in the default region but can be copied to - other regions later. - - - ), - }, - { - component: componentTypes.TEXT_FIELD, - name: 'aws-associated-account-id', - 'data-testid': 'aws-associated-account-id', - type: 'text', - label: 'Associated Account ID', - isReadOnly: true, - isRequired: true, - helperText: ( - - - This is the account associated with the source. - - - ), - condition: { - when: 'aws-target-type', - is: 'aws-target-type-source', - }, - }, - ], - }, - ], -}; - -export default awsStep; diff --git a/src/Components/CreateImageWizard/steps/fileSystemConfiguration.js b/src/Components/CreateImageWizard/steps/fileSystemConfiguration.js deleted file mode 100644 index 87976499..00000000 --- a/src/Components/CreateImageWizard/steps/fileSystemConfiguration.js +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; -import { - Button, - Label, - Text, - TextContent, - TextVariants, -} from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; - -import StepTemplate from './stepTemplate'; - -import { FILE_SYSTEM_CUSTOMIZATION_URL } from '../../../constants'; -import FileSystemConfigButtons from '../formComponents/FileSystemConfigButtons'; - -export const reinitFileSystemConfiguratioStep = (change) => { - change('file-system-configuration', undefined); - change('file-system-config-radio', 'automatic'); -}; - -const fileSystemConfigurationStep = { - StepTemplate, - id: 'wizard-systemconfiguration-filesystem', - title: 'File system configuration', - name: 'File system configuration', - buttons: FileSystemConfigButtons, - nextStep: 'packages', - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'file-system-configuration-text-component', - label: ( - <> - Define the partitioning of the image - - ), - }, - { - component: componentTypes.RADIO, - name: 'file-system-config-radio', - initialValue: 'automatic', - options: [ - { - label: ( - <> - - {' '} - Use automatic partitioning - - - ), - description: - 'Automatically partition your image to what is best, depending on the target environment(s)', - value: 'automatic', - 'data-testid': 'file-system-config-radio-automatic', - autoFocus: true, - }, - { - label: 'Manually configure partitions', - description: - 'Manually configure the file system of your image by adding, removing, and editing partitions', - value: 'manual', - 'data-testid': 'file-system-config-radio-manual', - className: 'pf-u-mt-sm', - }, - ], - condition: { - when: 'oscap-profile', - is: undefined, - }, - }, - { - component: 'file-system-configuration', - name: 'file-system-configuration', - label: 'File system configurations', - validate: [ - { type: 'fileSystemConfigurationValidator' }, - { type: validatorTypes.REQUIRED }, - ], - condition: { - or: [ - { - when: 'file-system-config-radio', - is: 'manual', - }, - { not: [{ when: 'oscap-profile', is: undefined }] }, - ], - }, - }, - { - component: componentTypes.PLAIN_TEXT, - name: 'automatic-partitioning-info', - label: ( - - Automatic partitioning - - Red Hat will automatically partition your image to what is best, - depending on the target environment(s). - - - The target environment sometimes dictates the partitioning scheme or - parts of it, and sometimes the target environment is unknown (e.g., - for the .qcow2 generic cloud image). - - - Using automatic partitioning will apply the most current supported - configuration. -

- -
-
- ), - condition: { - when: 'file-system-config-radio', - is: 'automatic', - }, - }, - ], -}; - -export default fileSystemConfigurationStep; diff --git a/src/Components/CreateImageWizard/steps/googleCloud.js b/src/Components/CreateImageWizard/steps/googleCloud.js deleted file mode 100644 index 52395ff9..00000000 --- a/src/Components/CreateImageWizard/steps/googleCloud.js +++ /dev/null @@ -1,225 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; -import { - Button, - Popover, - Text, - TextContent, - TextList, - TextListItem, - Title, -} from '@patternfly/react-core'; -import { HelpIcon } from '@patternfly/react-icons'; -import PropTypes from 'prop-types'; - -import nextStepMapper from './imageOutputStepMapper'; -import StepTemplate from './stepTemplate'; - -import CustomButtons from '../formComponents/CustomButtons'; - -export const googleAccType = { - googleAccount: 'Google account', - serviceAccount: 'Service account', - googleGroup: 'Google group', - domain: 'Domain', -}; - -const PopoverInfo = ({ appendTo }) => { - return ( - - - The following account types can have an image shared with them: - - - - Google account: A Google account represents a - developer, an administrator, or any other person who interacts - with Google Cloud. For example: `alice@gmail.com`. - - - Service account: A service account is an account - for an application instead of an individual end user. For example:{' '} - `myapp@appspot.gserviceaccount.com`. - - - Google group: A Google group is a named - collection of Google accounts and service accounts. For example:{' '} - `admins@example.com`. - - - Google Workspace domain or Cloud Identity domain:{' '} - A Google workspace or cloud identity domain represents a virtual - group of all the Google accounts in an organization. These domains - represent your organization's internet domain name. For - example: `mycompany.com`. - - - - } - > - - - ); -}; - -PopoverInfo.propTypes = { - appendTo: PropTypes.any, -}; - -const googleCloudStep = { - StepTemplate, - id: 'wizard-target-gcp', - title: 'Google Cloud Platform', - customTitle: ( - - Target environment - Google Cloud Platform - - ), - name: 'google-cloud-target-env', - substepOf: 'Target environment', - nextStep: ({ values }) => - nextStepMapper(values, { skipGoogle: true, skipAws: true }), - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'google-cloud-text-component', - label: ( -

- Select how to share your image. The image you create can be used to - launch instances on GCP, regardless of which method you select. -

- ), - }, - { - component: componentTypes.RADIO, - label: 'Select image sharing', - isRequired: true, - name: 'image-sharing', - initialValue: 'gcp-account', - autoFocus: true, - options: [ - { - label: 'Share image with a Google account', - 'data-testid': 'account-sharing', - autoFocus: true, - description: ( -

- Your image will be uploaded to GCP and shared with the account you - provide below. - The image expires in 14 days. To keep permanent access to - your image, copy it to your GCP project. -

- ), - value: 'gcp-account', - }, - { - label: 'Share image with Red Hat Insights only', - 'data-testid': 'insights-only-sharing', - description: ( -

- Your image will be uploaded to GCP and shared with Red Hat - Insights. - The image expires in 14 days. You cannot access or - recreate this image in your GCP project. -

- ), - value: 'insights', - autoFocus: true, - }, - ], - }, - { - component: 'radio-popover', - label: 'Account type', - isRequired: true, - Popover: PopoverInfo, - name: 'google-account-type', - initialValue: 'googleAccount', - options: Object.entries(googleAccType).map(([value, label]) => ({ - label: - value === 'domain' - ? 'Google Workspace domain or Cloud Identity domain' - : label, - value, - autoFocus: value === 'googleAccount' ? true : false, - })), - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - condition: { - when: 'image-sharing', - is: 'gcp-account', - }, - }, - { - component: componentTypes.TEXT_FIELD, - name: 'google-email', - 'data-testid': 'input-google-email', - type: 'text', - label: 'Principal (e.g. e-mail address)', - condition: { - and: [ - { when: 'image-sharing', is: 'gcp-account' }, - { - or: [ - { when: 'google-account-type', is: 'googleAccount' }, - { when: 'google-account-type', is: 'serviceAccount' }, - { when: 'google-account-type', is: 'googleGroup' }, - { when: 'google-account-type', is: null }, - ], - }, - ], - }, - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - { - type: validatorTypes.PATTERN, - pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,}$', - message: 'Please enter a valid email address', - }, - ], - }, - { - component: componentTypes.TEXT_FIELD, - name: 'google-domain', - type: 'text', - label: 'Domain', - condition: { - and: [ - { when: 'image-sharing', is: 'gcp-account' }, - { when: 'google-account-type', is: 'domain' }, - ], - }, - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - }, - ], -}; - -export default googleCloudStep; diff --git a/src/Components/CreateImageWizard/steps/imageName.js b/src/Components/CreateImageWizard/steps/imageName.js deleted file mode 100644 index d77b6020..00000000 --- a/src/Components/CreateImageWizard/steps/imageName.js +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; - -import { useFormApi } from '@data-driven-forms/react-form-renderer'; -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; -import { Flex, FlexItem, Text } from '@patternfly/react-core'; - -import StepTemplate from './stepTemplate'; - -import CustomButtons from '../formComponents/CustomButtons'; - -const CharacterCount = () => { - const { getState } = useFormApi(); - const description = getState().values?.['image-description']; - return

{description?.length || 0}/250

; -}; - -const imageNameStep = { - StepTemplate, - id: 'wizard-details', - name: 'details', - title: 'Details', - nextStep: 'review', - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'plain-text-component', - label: ( -

- Optionally enter a name to identify your image later quickly. If you - do not provide one, the UUID will be used as the name. -

- ), - }, - { - component: componentTypes.TEXT_FIELD, - name: 'image-name', - type: 'text', - label: 'Image Name', - placeholder: 'Image Name', - helperText: - 'The image name can be 3-63 characters long. It can contain lowercase letters, digits and hyphens, has to start with a letter and cannot end with a hyphen.', - autoFocus: true, - validate: [ - { - type: validatorTypes.PATTERN, - pattern: /^[a-z][-a-z0-9]{1,61}[a-z0-9]$/, - message: - 'The image name can be 3-63 characters long. It can contain lowercase letters, digits and hyphens, has to start with a letter and cannot end with a hyphen.', - }, - ], - }, - { - component: componentTypes.TEXTAREA, - name: 'image-description', - type: 'text', - label: ( - - - Description - - - - - - ), - placeholder: 'Add Description', - resizeOrientation: 'vertical', - validate: [{ type: validatorTypes.MAX_LENGTH, threshold: 250 }], - }, - ], -}; - -export default imageNameStep; diff --git a/src/Components/CreateImageWizard/steps/imageOutput.js b/src/Components/CreateImageWizard/steps/imageOutput.js deleted file mode 100644 index 31ac5ccb..00000000 --- a/src/Components/CreateImageWizard/steps/imageOutput.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; -import { Text } from '@patternfly/react-core'; - -import nextStepMapper from './imageOutputStepMapper'; -import StepTemplate from './stepTemplate'; - -import { RHEL_9, X86_64 } from '../../../constants'; -import DocumentationButton from '../../sharedComponents/DocumentationButton'; -import CustomButtons from '../formComponents/CustomButtons'; - -const imageOutputStep = { - StepTemplate, - id: 'wizard-imageoutput', - title: 'Image output', - name: 'image-output', - nextStep: ({ values }) => nextStepMapper(values), - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'image-output-plain-text', - label: ( - - Image builder allows you to create a custom image and push it to - target environments. -
- -
- ), - }, - { - component: 'image-output-release-select', - label: 'Release', - name: 'release', - initialValue: RHEL_9, - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - }, - { - component: 'image-output-release-lifecycle', - label: 'Release lifecycle', - name: 'release-lifecycle', - }, - { - component: 'image-output-arch-select', - label: 'Architecture', - name: 'arch', - initialValue: X86_64, - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - }, - { - component: 'centos-acknowledgement', - name: 'centos-acknowledgement', - condition: { - when: 'release', - pattern: /centos-*/, - then: { set: { 'register-system': null } }, - else: { visible: false }, - }, - }, - { - component: 'output', - name: 'target-environment', - label: 'Select target environments', - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - { - type: 'targetEnvironmentValidator', - }, - ], - }, - ], -}; - -export default imageOutputStep; diff --git a/src/Components/CreateImageWizard/steps/imageOutputStepMapper.js b/src/Components/CreateImageWizard/steps/imageOutputStepMapper.js deleted file mode 100644 index 96738656..00000000 --- a/src/Components/CreateImageWizard/steps/imageOutputStepMapper.js +++ /dev/null @@ -1,28 +0,0 @@ -import isRhel from '../../../Utilities/isRhel'; - -const imageOutputStepMapper = ( - { 'target-environment': targetEnv, release, enableOscap } = {}, - { skipAws, skipGoogle, skipAzure } = {} -) => { - if (!skipAws && targetEnv?.aws) { - return 'aws-target-env'; - } - - if (!skipGoogle && targetEnv?.gcp) { - return 'google-cloud-target-env'; - } - - if (!skipAzure && targetEnv?.azure) { - return 'ms-azure-target-env'; - } - - if (isRhel(release)) { - return 'registration'; - } - if (enableOscap) { - return 'Compliance'; - } - return 'File system configuration'; -}; - -export default imageOutputStepMapper; diff --git a/src/Components/CreateImageWizard/steps/index.js b/src/Components/CreateImageWizard/steps/index.js deleted file mode 100644 index 91480725..00000000 --- a/src/Components/CreateImageWizard/steps/index.js +++ /dev/null @@ -1,13 +0,0 @@ -export { default as awsTarget } from './aws'; -export { default as googleCloudTarget } from './googleCloud'; -export { default as msAzureTarget } from './msAzure'; -export { default as oscap } from './oscap'; -export { default as packages } from './packages'; -export { default as packagesContentSources } from './packagesContentSources'; -export { default as registration } from './registration'; -export { default as repositories } from './repositories'; -export { default as review } from './review'; -export { default as imageOutput } from './imageOutput'; -export { default as nextStepMapper } from './imageOutputStepMapper'; -export { default as fileSystemConfiguration } from './fileSystemConfiguration'; -export { default as imageName } from './imageName'; diff --git a/src/Components/CreateImageWizard/steps/msAzure.js b/src/Components/CreateImageWizard/steps/msAzure.js deleted file mode 100644 index 6aac49de..00000000 --- a/src/Components/CreateImageWizard/steps/msAzure.js +++ /dev/null @@ -1,265 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; -import { Button, Text, TextContent, Title } from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; - -import nextStepMapper from './imageOutputStepMapper'; -import StepTemplate from './stepTemplate'; - -import { AZURE_AUTH_URL } from '../../../constants'; -import CustomButtons from '../formComponents/CustomButtons'; - -const SourcesButton = () => { - return ( - - ); -}; - -const msAzureStep = { - StepTemplate, - id: 'wizard-target-msazure', - title: 'Microsoft Azure', - customTitle: ( - - Target environment - Microsoft Azure - - ), - name: 'ms-azure-target-env', - substepOf: 'Target environment', - nextStep: ({ values }) => - nextStepMapper(values, { - skipAws: true, - skipGoogle: true, - skipAzure: true, - }), - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'azure-description', - label: ( - - - Upon build, Image Builder sends the image to the selected authorized - Azure account. The image will be uploaded to the resource group in - the subscription you specify. - - - To authorize Image Builder to push images to Microsoft Azure, the - account owner must configure Image Builder as an authorized - application for a specific tenant ID and give it the role of - "Contributor" for the resource group you want to upload - to. This applies even when defining target by Source selection. -
- -
-
- ), - }, - { - component: componentTypes.RADIO, - label: 'Share method:', - name: 'azure-type', - initialValue: 'azure-type-source', - autoFocus: true, - options: [ - { - label: 'Use an account configured from Sources.', - description: - 'Use a configured source to launch environments directly from the console.', - value: 'azure-type-source', - 'data-testid': 'azure-radio-source', - autoFocus: true, - }, - { - label: 'Manually enter the account information.', - value: 'azure-type-manual', - 'data-testid': 'azure-radio-manual', - className: 'pf-u-mt-sm', - }, - ], - }, - { - component: 'azure-sources-select', - name: 'azure-sources-select', - className: 'pf-u-max-width', - label: 'Source Name', - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - condition: { - when: 'azure-type', - is: 'azure-type-source', - }, - }, - { - component: componentTypes.PLAIN_TEXT, - name: 'azure-sources-select-description', - label: , - condition: { - when: 'azure-type', - is: 'azure-type-source', - }, - }, - { - name: 'gallery-layout', - component: 'gallery-layout', - minWidths: { default: '12.5rem' }, - maxWidths: { default: '12.5rem' }, - fields: [ - { - component: componentTypes.TEXT_FIELD, - name: 'azure-tenant-id', - 'data-testid': 'azure-tenant-id-source', - type: 'text', - label: 'Azure Tenant GUID', - isRequired: true, - isReadOnly: true, - }, - { - component: componentTypes.TEXT_FIELD, - name: 'azure-subscription-id', - 'data-testid': 'azure-subscription-id-source', - type: 'text', - label: 'Subscription ID', - isRequired: true, - isReadOnly: true, - condition: { - when: 'azure-type', - is: 'azure-type-source', - }, - }, - ], - condition: { - when: 'azure-type', - is: 'azure-type-source', - }, - }, - { - component: componentTypes.TEXT_FIELD, - name: 'azure-tenant-id', - className: 'pf-u-w-50', - 'data-testid': 'azure-tenant-id-manual', - type: 'text', - label: 'Azure Tenant GUID', - isRequired: true, - autoFocus: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - { - type: validatorTypes.PATTERN, - pattern: - /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, - message: 'Please enter a valid tenant ID', - }, - ], - condition: { - when: 'azure-type', - is: 'azure-type-manual', - }, - }, - { - component: 'azure-auth-button', - name: 'azure-auth-button', - 'data-testid': 'azure-auth-button', - required: true, - isRequired: true, - }, - { - component: componentTypes.TEXT_FIELD, - name: 'azure-subscription-id', - className: 'pf-u-w-50', - 'data-testid': 'azure-subscription-id-manual', - type: 'text', - label: 'Subscription ID', - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - { - type: validatorTypes.PATTERN, - pattern: - /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, - message: 'Please enter a valid subscription ID', - }, - ], - condition: { - when: 'azure-type', - is: 'azure-type-manual', - }, - }, - { - component: 'azure-resource-groups', - name: 'azure-resource-group', - className: 'pf-u-max-width', - 'data-testid': 'azure-resource-group-select', - label: 'Resource group', - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - condition: { - when: 'azure-type', - is: 'azure-type-source', - }, - }, - { - component: componentTypes.TEXT_FIELD, - name: 'azure-resource-group', - className: 'pf-u-w-50', - 'data-testid': 'azure-resource-group-manual', - type: 'text', - label: 'Resource group', - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - { - type: validatorTypes.PATTERN, - pattern: /^[-\w._()]+[-\w_()]$/, - message: - 'Resource group names only allow alphanumeric characters, ' + - 'periods, underscores, hyphens, and parenthesis and cannot end in a period', - }, - ], - condition: { - when: 'azure-type', - is: 'azure-type-manual', - }, - }, - // TODO check oauth2 thing too here? - ], -}; - -export default msAzureStep; diff --git a/src/Components/CreateImageWizard/steps/oscap.js b/src/Components/CreateImageWizard/steps/oscap.js deleted file mode 100644 index 5ac015d2..00000000 --- a/src/Components/CreateImageWizard/steps/oscap.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import { Text, Title } from '@patternfly/react-core'; - -import StepTemplate from './stepTemplate'; - -import DocumentationButton from '../../sharedComponents/DocumentationButton'; -import CustomButtons from '../formComponents/CustomButtons'; - -const oscapStep = { - StepTemplate, - id: 'wizard-systemconfiguration-oscap', - title: 'OpenSCAP', - name: 'Compliance', - customTitle: ( - - OpenSCAP profile - - ), - nextStep: 'File system configuration', - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'oscap-text-component', - label: ( - - Use OpenSCAP to monitor the adherence of your registered RHEL systems - to a selected regulatory compliance profile. - - ), - }, - { - component: 'oscap-profile-selector', - name: 'oscap-profile', - label: 'Available profiles for the distribution', - }, - ], -}; - -export default oscapStep; diff --git a/src/Components/CreateImageWizard/steps/packages.js b/src/Components/CreateImageWizard/steps/packages.js deleted file mode 100644 index 8fe07e43..00000000 --- a/src/Components/CreateImageWizard/steps/packages.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import { Text } from '@patternfly/react-core'; - -import StepTemplate from './stepTemplate'; - -import CustomButtons from '../formComponents/CustomButtons'; - -export const reinitPackagesStep = (change) => { - change('selected-packages', undefined); -}; - -const packagesStep = { - StepTemplate, - id: 'wizard-systemconfiguration-packages', - title: 'Additional Red Hat packages', - name: 'packages', - substepOf: 'Content', - nextStep: ({ values }) => { - if (values.contentSourcesEnabled) { - return 'repositories'; - } else { - return 'details'; - } - }, - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'packages-text-component', - label: ( - - Images built with Image Builder include all required packages. - - ), - }, - { - component: 'package-selector', - name: 'selected-packages', - label: 'Available options', - }, - ], -}; - -export default packagesStep; diff --git a/src/Components/CreateImageWizard/steps/packagesContentSources.js b/src/Components/CreateImageWizard/steps/packagesContentSources.js deleted file mode 100644 index 77e9c495..00000000 --- a/src/Components/CreateImageWizard/steps/packagesContentSources.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import { Text } from '@patternfly/react-core'; - -import StepTemplate from './stepTemplate'; - -import CustomButtons from '../formComponents/CustomButtons'; - -const packagesContentSourcesStep = { - StepTemplate, - id: 'wizard-systemconfiguration-content-sources-packages', - title: 'Additional custom packages', - name: 'packages-content-sources', - substepOf: 'Content', - nextStep: 'details', - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'packages-text-component', - label: ( - - The available packages will return results from all repositories - chosen on the previous page. - - ), - }, - { - component: 'package-selector-content-sources', - name: 'selected-packages-content-sources', - label: 'Available options', - }, - ], -}; - -export default packagesContentSourcesStep; diff --git a/src/Components/CreateImageWizard/steps/registration.js b/src/Components/CreateImageWizard/steps/registration.js deleted file mode 100644 index 131bb377..00000000 --- a/src/Components/CreateImageWizard/steps/registration.js +++ /dev/null @@ -1,206 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; -import { - Button, - Popover, - Text, - TextContent, - TextVariants, - Title, -} from '@patternfly/react-core'; -import { ExternalLinkAltIcon, HelpIcon } from '@patternfly/react-icons'; -import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; - -import StepTemplate from './stepTemplate'; - -import { - ACTIVATION_KEYS_PROD_URL, - ACTIVATION_KEYS_STAGE_URL, - RHC_URL, -} from '../../../constants'; -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; -import CustomButtons from '../formComponents/CustomButtons'; - -const ManageKeysButton = () => { - const { isProd } = useGetEnvironment(); - return ( - - ); -}; - -const PopoverActivation = () => { - const [orgId, setOrgId] = useState(null); - const { auth } = useChrome(); - - useEffect(() => { - (async () => { - const userData = await auth?.getUser(); - const id = userData?.identity?.internal?.org_id; - setOrgId(id); - })(); - }); - return ( - - - Activation keys enable you to register a system with appropriate - subscriptions, system purpose, and repositories attached. - - - If using an activation key with command line registration, you must - provide your organization's ID. - {orgId &&
} - {orgId && "Your organization's ID is " + orgId} -
- - } - > - -
- ); -}; - -const registrationStep = { - StepTemplate, - id: 'wizard-registration', - title: 'Register', - customTitle: ( - - Register systems using this image - - ), - name: 'registration', - nextStep: ({ values }) => { - if (values.enableOscap) { - return 'Compliance'; - } else { - return 'File system configuration'; - } - }, - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'registration-general-description', - label: - 'Automatically register your systems with Red Hat to enhance security and track your spending.', - }, - { - name: 'register-system', - component: 'registration', - label: 'Registration method', - initialValue: 'register-now-rhc', - }, - { - component: 'activation-keys', - name: 'subscription-activation-key', - required: true, - label: ( - <> - Activation key to use for this image - - - ), - condition: { - or: [ - { when: 'register-system', is: 'register-now-rhc' }, - { when: 'register-system', is: 'register-now-insights' }, - { when: 'register-system', is: 'register-now' }, - ], - }, - isRequired: true, - validate: [ - { - type: validatorTypes.REQUIRED, - }, - ], - }, - { - component: componentTypes.PLAIN_TEXT, - name: 'subscription-activation-description', - label: ( - - By default, activation key is generated and preset for you. Admins can - create and manage keys by visiting the  - - - ), - condition: { - or: [ - { when: 'register-system', is: 'register-now-rhc' }, - { when: 'register-system', is: 'register-now-insights' }, - { when: 'register-system', is: 'register-now' }, - ], - }, - }, - { - component: componentTypes.PLAIN_TEXT, - name: 'subscription-register-later', - label: ( - - Register Later - - 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. - - - If you prefer to register later, review the instructions for manual - registration with remote host configuration. - - - - ), - condition: { - or: [{ when: 'register-system', is: 'register-later' }], - }, - }, - { - component: 'activation-key-information', - name: 'subscription-activation-key-information', - label: 'Selected activation key', - valueReference: 'subscription-activation-key', - condition: { - or: [ - { when: 'register-system', is: 'register-now-rhc' }, - { when: 'register-system', is: 'register-now-insights' }, - { when: 'register-system', is: 'register-now' }, - ], - }, - }, - ], -}; - -export default registrationStep; diff --git a/src/Components/CreateImageWizard/steps/repositories.js b/src/Components/CreateImageWizard/steps/repositories.js deleted file mode 100644 index 0688c12a..00000000 --- a/src/Components/CreateImageWizard/steps/repositories.js +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; - -import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; -import { Button, Text } from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; - -import nextStepMapper from './repositoriesStepMapper'; -import StepTemplate from './stepTemplate'; - -import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; -import CustomButtons from '../formComponents/CustomButtons'; - -const VisitButton = () => { - const { isBeta } = useGetEnvironment(); - return ( - - ); -}; - -const repositoriesStep = { - StepTemplate, - id: 'wizard-repositories', - title: 'Custom repositories', - name: 'repositories', - substepOf: 'Content', - nextStep: ({ values }) => nextStepMapper(values), - buttons: CustomButtons, - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'packages-text-component', - label: ( - - Select from linked custom repositories from which to search and add - packages to this image. -
- -
- ), - }, - { - component: 'repositories-table', - name: 'payload-repositories', - label: 'Custom repositories', - }, - ], -}; - -export default repositoriesStep; diff --git a/src/Components/CreateImageWizard/steps/repositoriesStepMapper.js b/src/Components/CreateImageWizard/steps/repositoriesStepMapper.js deleted file mode 100644 index c01bee79..00000000 --- a/src/Components/CreateImageWizard/steps/repositoriesStepMapper.js +++ /dev/null @@ -1,11 +0,0 @@ -const repositoriesStepMapper = ({ - 'payload-repositories': customRepositories, -} = {}) => { - if (customRepositories?.length > 0) { - return 'packages-content-sources'; - } - - return 'details'; -}; - -export default repositoriesStepMapper; diff --git a/src/Components/CreateImageWizard/steps/review.js b/src/Components/CreateImageWizard/steps/review.js deleted file mode 100644 index d1df2ace..00000000 --- a/src/Components/CreateImageWizard/steps/review.js +++ /dev/null @@ -1,19 +0,0 @@ -import StepTemplate from './stepTemplate'; - -import CustomButtons from '../formComponents/CustomButtons'; - -const reviewStep = { - StepTemplate, - id: 'wizard-review', - name: 'review', - title: 'Review', - buttons: CustomButtons, - fields: [ - { - name: 'review', - component: 'review', - }, - ], -}; - -export default reviewStep; diff --git a/src/Components/CreateImageWizard/steps/stepTemplate.js b/src/Components/CreateImageWizard/steps/stepTemplate.js deleted file mode 100644 index 07602364..00000000 --- a/src/Components/CreateImageWizard/steps/stepTemplate.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; - -import { Title } from '@patternfly/react-core'; -import PropTypes from 'prop-types'; - -const StepTemplate = ({ - id, - formFields, - formRef, - title, - customTitle, - showTitle, - showTitles, -}) => ( -
- {((showTitles && showTitle !== false) || showTitle) && - (customTitle ? ( - customTitle - ) : ( - - {title} - - ))} - {formFields} -
-); - -StepTemplate.propTypes = { - id: PropTypes.string, - title: PropTypes.node, - customTitle: PropTypes.node, - formFields: PropTypes.array.isRequired, - formOptions: PropTypes.shape({ - renderForm: PropTypes.func.isRequired, - }).isRequired, - showTitles: PropTypes.bool, - showTitle: PropTypes.bool, - formRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({ current: PropTypes.instanceOf(Element) }), - ]), -}; - -export default StepTemplate; diff --git a/src/Components/CreateImageWizard/validators/fileSystemConfigurationValidator.js b/src/Components/CreateImageWizard/validators/fileSystemConfigurationValidator.js deleted file mode 100644 index be686511..00000000 --- a/src/Components/CreateImageWizard/validators/fileSystemConfigurationValidator.js +++ /dev/null @@ -1,32 +0,0 @@ -const FileSystemConfigurationValidator = () => (fsc) => { - if (!fsc) { - return undefined; - } - - const mpFreqs = {}; - for (const fs of fsc) { - const mp = fs.mountpoint; - if (mp in mpFreqs) { - mpFreqs[mp]++; - } else { - mpFreqs[mp] = 1; - } - } - - const duplicates = []; - for (const [k, v] of Object.entries(mpFreqs)) { - if (v > 1) { - duplicates.push(k); - } - } - - const root = mpFreqs['/'] >= 1; - return duplicates.length === 0 && root - ? undefined - : { - duplicates: duplicates === [] ? undefined : duplicates, - root, - }; -}; - -export default FileSystemConfigurationValidator; diff --git a/src/Components/CreateImageWizard/validators/index.js b/src/Components/CreateImageWizard/validators/index.js deleted file mode 100644 index 967624bf..00000000 --- a/src/Components/CreateImageWizard/validators/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as fileSystemConfigurationValidator } from './fileSystemConfigurationValidator'; -export { default as targetEnvironmentValidator } from './targetEnvironmentValidator'; diff --git a/src/Components/CreateImageWizard/validators/targetEnvironmentValidator.js b/src/Components/CreateImageWizard/validators/targetEnvironmentValidator.js deleted file mode 100644 index cb95678a..00000000 --- a/src/Components/CreateImageWizard/validators/targetEnvironmentValidator.js +++ /dev/null @@ -1,17 +0,0 @@ -const TargetEnvironmentValidator = () => (targets) => { - if (!targets) { - return undefined; - } - - // at least one of the target environments must - // be set to true. This reduces the value to - // a single boolean which is a flag for whether - // at least one target has been selected or not - const valid = Object.values(targets).reduce( - (prev, curr) => curr || prev, - false - ); - return !valid ? 'Please select an image' : undefined; -}; - -export default TargetEnvironmentValidator; diff --git a/src/Components/CreateImageWizardV2/CreateImageWizard.tsx b/src/Components/CreateImageWizardV2/CreateImageWizard.tsx index 9ba9d7f9..15cd63b5 100644 --- a/src/Components/CreateImageWizardV2/CreateImageWizard.tsx +++ b/src/Components/CreateImageWizardV2/CreateImageWizard.tsx @@ -191,7 +191,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => { return ( <> - +
( ); const EmptyImagesTable = () => { - const experimentalFlag = useExperimentalFlag(); return ( - {experimentalFlag ? ( - <> - } - headingLevel="h4" - /> - - Images are BLANK. Create blueprints to create images. - - - ) : ( - <> - } - headingLevel="h4" - /> - - - Image builder is a tool for creating deployment-ready customized - system images: installation disks, virtual machines, cloud - vendor-specific images, and others. By using image builder, you - can create these images faster than with manual procedures - because it eliminates the specific configurations required for - each output type. - -
- - With RPM-DNF, you can manage the system software by using the - DNF package manager and updated RPM packages. This is a simple - and adaptive method of managing and modifying the system over - its lifecycle. - -
- - - -
- - + } + headingLevel="h4" + /> + + + Image builder is a tool for creating deployment-ready customized + system images: installation disks, virtual machines, cloud + vendor-specific images, and others. By using image builder, you + can create these images faster than with manual procedures because + it eliminates the specific configurations required for each output + type. + + + There are no images yet. Create a blueprint to create images. + + + - - - - )} + Learn more about managing images with DNF + + + + + + + + +
); diff --git a/src/Components/ImagesTable/ImagesTable.tsx b/src/Components/ImagesTable/ImagesTable.tsx index 8d0e993a..01682e62 100644 --- a/src/Components/ImagesTable/ImagesTable.tsx +++ b/src/Components/ImagesTable/ImagesTable.tsx @@ -68,7 +68,6 @@ import { timestampToDisplayString, timestampToDisplayStringDetailed, } from '../../Utilities/time'; -import { useExperimentalFlag } from '../../Utilities/useExperimentalFlag'; const ImagesTable = () => { const [page, setPage] = useState(1); @@ -93,7 +92,6 @@ const ImagesTable = () => { }), } ); - const experimentalFlag = useExperimentalFlag(); const onSetPage: OnSetPage = (_, page) => setPage(page); const onPerPageSelect: OnSetPage = (_, perPage) => { @@ -198,7 +196,7 @@ const ImagesTable = () => { Updated OS Target - {experimentalFlag && Version} + Version Status Instance @@ -407,7 +405,6 @@ type AwsRowPropTypes = { const AwsRow = ({ compose, composeStatus, rowIndex }: AwsRowPropTypes) => { const navigate = useNavigate(); - const experimentalFlag = useExperimentalFlag(); const target = ; @@ -418,9 +415,7 @@ const AwsRow = ({ compose, composeStatus, rowIndex }: AwsRowPropTypes) => { const details = ; const actions = ( - + ); return ( @@ -457,8 +452,6 @@ const Row = ({ }: RowPropTypes) => { const [isExpanded, setIsExpanded] = useState(false); const handleToggle = () => setIsExpanded(!isExpanded); - const experimentalFlag = useExperimentalFlag(); - const navigate = useNavigate(); return ( @@ -483,20 +476,16 @@ const Row = ({ {target ? target : } - {experimentalFlag && ( - - {compose.blueprint_version || 'N/A'} - - )} + + {compose.blueprint_version || 'N/A'} + {status} {instance} {actions ? ( actions ) : ( - + )} @@ -509,21 +498,7 @@ const Row = ({ ); }; -const defaultActions = ( - compose: ComposesResponseItem, - navigate: NavigateFunction, - experimentalFlag: boolean -) => [ - ...(experimentalFlag - ? [] - : [ - { - title: 'Recreate image', - onClick: () => { - navigate(resolveRelPath(`imagewizard/${compose.id}`)); - }, - }, - ]), +const defaultActions = (compose: ComposesResponseItem) => [ { title: ( [ { title: 'Share to new region', onClick: () => navigate(resolveRelPath(`share/${compose.id}`)), isDisabled: status?.image_status.status === 'success' ? false : true, }, - ...defaultActions(compose, navigate, experimentalFlag), + ...defaultActions(compose), ]; export default ImagesTable; diff --git a/src/Components/ImagesTable/ImagesTableToolbar.tsx b/src/Components/ImagesTable/ImagesTableToolbar.tsx index 027e77ad..d552e3c7 100644 --- a/src/Components/ImagesTable/ImagesTableToolbar.tsx +++ b/src/Components/ImagesTable/ImagesTableToolbar.tsx @@ -8,7 +8,6 @@ import { ToolbarItem, Title, } from '@patternfly/react-core'; -import { Link } from 'react-router-dom'; import { selectSelectedBlueprintId, @@ -21,8 +20,6 @@ import { useGetBlueprintsQuery, useGetBlueprintComposesQuery, } from '../../store/imageBuilderApi'; -import { resolveRelPath } from '../../Utilities/path'; -import { useExperimentalFlag } from '../../Utilities/useExperimentalFlag'; import { BlueprintActionsMenu } from '../Blueprints/BlueprintActionsMenu'; import BlueprintVersionFilter from '../Blueprints/BlueprintVersionFilter'; import { BuildImagesButton } from '../Blueprints/BuildImagesButton'; @@ -44,7 +41,6 @@ const ImagesTableToolbar: React.FC = ({ setPage, onPerPageSelect, }: imagesTableToolbarProps) => { - const experimentalFlag = useExperimentalFlag(); const [showDeleteModal, setShowDeleteModal] = useState(false); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput); @@ -98,27 +94,6 @@ const ImagesTableToolbar: React.FC = ({ /> ); - if (!experimentalFlag) { - return ( - - - - - Create image - - - - {pagination} - - - - ); - } - const isBlueprintDistroCentos8 = () => { if (isSuccessBlueprintsCompose) { return blueprintsComposes.data[0].request.distribution === 'centos-8'; @@ -139,7 +114,7 @@ const ImagesTableToolbar: React.FC = ({ : 'All images'} - {itemCount > 0 && experimentalFlag && isBlueprintOutSync && ( + {itemCount > 0 && isBlueprintOutSync && ( = ({ ouiaId="centos-8-blueprint-alert" /> )} - {selectedBlueprintId && ( - - - setPage(1)} /> - - - - - - - - - - - - {pagination} - - - )} + + + {selectedBlueprintId && ( + <> + + setPage(1)} /> + + + + + + + + + + + + )} + + {pagination} + + ); diff --git a/src/Components/ImagesTable/Instance.tsx b/src/Components/ImagesTable/Instance.tsx index e94c079c..44afaeb8 100644 --- a/src/Components/ImagesTable/Instance.tsx +++ b/src/Components/ImagesTable/Instance.tsx @@ -40,7 +40,6 @@ import { isOciUploadStatus, } from '../../store/typeGuards'; import { resolveRelPath } from '../../Utilities/path'; -import { useExperimentalFlag } from '../../Utilities/useExperimentalFlag'; import useProvisioningPermissions from '../../Utilities/useProvisioningPermissions'; type CloudInstancePropTypes = { @@ -347,9 +346,6 @@ export const AwsS3Instance = ({ composeId: compose.id, }); - const navigate = useNavigate(); - const experimentalFlag = useExperimentalFlag(); - if (!isSuccess) { return ; } @@ -388,18 +384,6 @@ export const AwsS3Instance = ({ ) ); - } else if (isExpired && !experimentalFlag) { - return ( - - ); } else { return (