src: Rename "V2" folders to just Wizard
This replaces all occurences of "CreateImageWizardV2" with just "CreateImageWizard" as it is the only version now.
This commit is contained in:
parent
b1e5a8c7c6
commit
4fb37c187e
93 changed files with 20 additions and 22 deletions
3
src/Components/CreateImageWizard/utilities/betaPath.ts
Normal file
3
src/Components/CreateImageWizard/utilities/betaPath.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const betaPath = (path: string, beta: boolean) => {
|
||||
return beta ? `/preview${path}` : path;
|
||||
};
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { useListRepositoriesQuery } from '../../../store/contentSourcesApi';
|
||||
import { useAppSelector } from '../../../store/hooks';
|
||||
import {
|
||||
selectArchitecture,
|
||||
selectDistribution,
|
||||
selectCustomRepositories,
|
||||
} from '../../../store/wizardSlice';
|
||||
import { releaseToVersion } from '../../../Utilities/releaseToVersion';
|
||||
|
||||
/**
|
||||
* This checks the list of the custom repositories against a list of repos freshly
|
||||
* fetched from content source API and returns true whether there are some
|
||||
* repositories that are no longer available in the Repositories service.
|
||||
*/
|
||||
export const useCheckRepositoriesAvailability = () => {
|
||||
const arch = useAppSelector(selectArchitecture);
|
||||
const distribution = useAppSelector(selectDistribution);
|
||||
const version = releaseToVersion(distribution);
|
||||
|
||||
// There needs to be two requests because the default limit for the
|
||||
// useListRepositoriesQuery is a 100 elements, and a first request is
|
||||
// necessary to know the total amount of elements to fetch.
|
||||
const firstRequest = useListRepositoriesQuery({
|
||||
availableForArch: arch,
|
||||
availableForVersion: version,
|
||||
contentType: 'rpm',
|
||||
origin: 'external',
|
||||
});
|
||||
|
||||
const skip =
|
||||
firstRequest?.data?.meta?.count === undefined ||
|
||||
firstRequest?.data?.meta?.count <= 100;
|
||||
|
||||
// Fetch *all* repositories if there are more than 100
|
||||
const followupRequest = useListRepositoriesQuery(
|
||||
{
|
||||
availableForArch: arch,
|
||||
availableForVersion: version,
|
||||
contentType: 'rpm',
|
||||
origin: 'external',
|
||||
limit: firstRequest?.data?.meta?.count,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
skip: skip,
|
||||
}
|
||||
);
|
||||
|
||||
const { data: freshRepos, isSuccess } = useMemo(() => {
|
||||
if (firstRequest?.data?.meta?.count) {
|
||||
if (firstRequest?.data?.meta?.count > 100) {
|
||||
return { ...followupRequest };
|
||||
}
|
||||
}
|
||||
return { ...firstRequest };
|
||||
}, [firstRequest, followupRequest]);
|
||||
|
||||
const customRepositories = useAppSelector(selectCustomRepositories);
|
||||
// customRepositories existing === we came here from Recreate
|
||||
if (isSuccess && customRepositories) {
|
||||
// Transform the fresh repos array into a Set to access its elements in O(1)
|
||||
// complexity later in the for loop.
|
||||
const freshReposUrls = new Set(
|
||||
freshRepos.data?.map((freshRepo) => freshRepo.url)
|
||||
);
|
||||
for (const customRepo of customRepositories) {
|
||||
if (customRepo.baseurl && !freshReposUrls.has(customRepo.baseurl[0])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { useAppSelector } from '../../../store/hooks';
|
||||
import { ImageTypes } from '../../../store/imageBuilderApi';
|
||||
import { selectImageTypes } from '../../../store/wizardSlice';
|
||||
|
||||
export const useHasSpecificTargetOnly = (target: ImageTypes) => {
|
||||
const environments = useAppSelector(selectImageTypes);
|
||||
return environments.length === 1 && environments.includes(target);
|
||||
};
|
||||
20
src/Components/CreateImageWizard/utilities/parseSizeUnit.ts
Normal file
20
src/Components/CreateImageWizard/utilities/parseSizeUnit.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { UNIT_GIB, UNIT_KIB, UNIT_MIB } from '../../../constants';
|
||||
import { Units } from '../steps/FileSystem/FileSystemConfiguration';
|
||||
|
||||
export const parseSizeUnit = (bytesize: string) => {
|
||||
let size;
|
||||
let unit: Units = 'GiB';
|
||||
|
||||
if (parseInt(bytesize) % UNIT_GIB === 0) {
|
||||
size = parseInt(bytesize) / UNIT_GIB;
|
||||
unit = 'GiB';
|
||||
} else if (parseInt(bytesize) % UNIT_MIB === 0) {
|
||||
size = parseInt(bytesize) / UNIT_MIB;
|
||||
unit = 'MiB';
|
||||
} else if (parseInt(bytesize) % UNIT_KIB === 0) {
|
||||
size = parseInt(bytesize) / UNIT_KIB;
|
||||
unit = 'KiB';
|
||||
}
|
||||
|
||||
return [String(size), unit];
|
||||
};
|
||||
558
src/Components/CreateImageWizard/utilities/requestMapper.ts
Normal file
558
src/Components/CreateImageWizard/utilities/requestMapper.ts
Normal file
|
|
@ -0,0 +1,558 @@
|
|||
import { Store } from 'redux';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { parseSizeUnit } from './parseSizeUnit';
|
||||
|
||||
import {
|
||||
FIRST_BOOT_SERVICE,
|
||||
FIRST_BOOT_SERVICE_DATA,
|
||||
RHEL_8,
|
||||
RHEL_9,
|
||||
} from '../../../constants';
|
||||
import { RootState } from '../../../store';
|
||||
import {
|
||||
AwsUploadRequestOptions,
|
||||
AzureUploadRequestOptions,
|
||||
BlueprintResponse,
|
||||
CreateBlueprintRequest,
|
||||
Customizations,
|
||||
DistributionProfileItem,
|
||||
Distributions,
|
||||
File,
|
||||
Filesystem,
|
||||
GcpUploadRequestOptions,
|
||||
ImageRequest,
|
||||
ImageTypes,
|
||||
OpenScap,
|
||||
Services,
|
||||
Subscription,
|
||||
UploadTypes,
|
||||
} from '../../../store/imageBuilderApi';
|
||||
import {
|
||||
selectActivationKey,
|
||||
selectArchitecture,
|
||||
selectAwsAccountId,
|
||||
selectAwsShareMethod,
|
||||
selectAwsSourceId,
|
||||
selectAzureResourceGroup,
|
||||
selectAzureShareMethod,
|
||||
selectAzureSource,
|
||||
selectAzureSubscriptionId,
|
||||
selectAzureTenantId,
|
||||
selectBaseUrl,
|
||||
selectBlueprintDescription,
|
||||
selectBlueprintName,
|
||||
selectCustomRepositories,
|
||||
selectDistribution,
|
||||
selectGcpAccountType,
|
||||
selectGcpEmail,
|
||||
selectGcpShareMethod,
|
||||
selectGroups,
|
||||
selectImageTypes,
|
||||
selectPackages,
|
||||
selectPayloadRepositories,
|
||||
selectRecommendedRepositories,
|
||||
selectProfile,
|
||||
selectRegistrationType,
|
||||
selectServerUrl,
|
||||
wizardState,
|
||||
selectFileSystemPartitionMode,
|
||||
selectPartitions,
|
||||
selectSnapshotDate,
|
||||
selectUseLatest,
|
||||
selectFirstBootScript,
|
||||
} from '../../../store/wizardSlice';
|
||||
import {
|
||||
convertMMDDYYYYToYYYYMMDD,
|
||||
convertYYYYMMDDTOMMDDYYYY,
|
||||
} from '../../../Utilities/time';
|
||||
import { FileSystemPartitionMode } from '../steps/FileSystem';
|
||||
import {
|
||||
getConversionFactor,
|
||||
Partition,
|
||||
Units,
|
||||
} from '../steps/FileSystem/FileSystemConfiguration';
|
||||
import {
|
||||
convertSchemaToIBCustomRepo,
|
||||
convertSchemaToIBPayloadRepo,
|
||||
} from '../steps/Repositories/components/Utilities';
|
||||
import { GcpAccountType } from '../steps/TargetEnvironment/Gcp';
|
||||
|
||||
type ServerStore = {
|
||||
kernel?: { append?: string }; // TODO use API types
|
||||
services?: { enabled?: string[]; disabled?: string[]; masked?: string[] }; // TODO use API types
|
||||
};
|
||||
|
||||
/**
|
||||
* This function maps the wizard state to a valid CreateBlueprint request object
|
||||
* @param {Store} store redux store
|
||||
* @param {string} orgID organization ID
|
||||
*
|
||||
* @returns {CreateBlueprintRequest} blueprint creation request payload
|
||||
*/
|
||||
export const mapRequestFromState = (
|
||||
store: Store,
|
||||
orgID: string,
|
||||
serverStore: ServerStore
|
||||
): CreateBlueprintRequest => {
|
||||
const state = store.getState();
|
||||
const imageRequests = getImageRequests(state);
|
||||
const customizations = getCustomizations(state, orgID, serverStore);
|
||||
|
||||
return {
|
||||
name: selectBlueprintName(state),
|
||||
description: selectBlueprintDescription(state),
|
||||
distribution: selectDistribution(state),
|
||||
image_requests: imageRequests,
|
||||
customizations,
|
||||
};
|
||||
};
|
||||
|
||||
const convertFilesystemToPartition = (filesystem: Filesystem): Partition => {
|
||||
const id = uuidv4();
|
||||
const [size, unit] = parseSizeUnit(filesystem.min_size);
|
||||
const partition = {
|
||||
mountpoint: filesystem.mountpoint,
|
||||
min_size: size,
|
||||
id: id,
|
||||
unit: unit as Units,
|
||||
};
|
||||
return partition;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function overwrites distribution of the blueprints with the major release
|
||||
* Minor releases were previously used and are still present in older blueprints
|
||||
* @param distribution blueprint distribution
|
||||
*/
|
||||
const getLatestMinorRelease = (distribution: Distributions) => {
|
||||
return distribution.startsWith('rhel-9')
|
||||
? RHEL_9
|
||||
: distribution.startsWith('rhel-8')
|
||||
? RHEL_8
|
||||
: distribution;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function maps the blueprint response to the wizard state, used to populate the wizard with the blueprint details
|
||||
* @param request BlueprintResponse
|
||||
* @param source V1ListSourceResponseItem
|
||||
* @returns wizardState
|
||||
*/
|
||||
export const mapRequestToState = (request: BlueprintResponse): wizardState => {
|
||||
const wizardMode = 'edit';
|
||||
const gcp = request.image_requests.find(
|
||||
(image) => image.image_type === 'gcp'
|
||||
);
|
||||
const aws = request.image_requests.find(
|
||||
(image) => image.image_type === 'aws'
|
||||
);
|
||||
|
||||
const azure = request.image_requests.find(
|
||||
(image) => image.image_type === 'azure'
|
||||
);
|
||||
|
||||
const snapshot_date = convertYYYYMMDDTOMMDDYYYY(
|
||||
request.image_requests.find((image) => !!image.snapshot_date)
|
||||
?.snapshot_date || ''
|
||||
);
|
||||
|
||||
const awsUploadOptions = aws?.upload_request
|
||||
.options as AwsUploadRequestOptions;
|
||||
const gcpUploadOptions = gcp?.upload_request
|
||||
.options as GcpUploadRequestOptions;
|
||||
const azureUploadOptions = azure?.upload_request
|
||||
.options as AzureUploadRequestOptions;
|
||||
|
||||
const fileSystem = request.customizations.filesystem
|
||||
? {
|
||||
mode: 'manual' as FileSystemPartitionMode,
|
||||
partitions: request.customizations.filesystem.map((fs) =>
|
||||
convertFilesystemToPartition(fs)
|
||||
),
|
||||
}
|
||||
: {
|
||||
mode: 'automatic' as FileSystemPartitionMode,
|
||||
partitions: [],
|
||||
};
|
||||
|
||||
const arch = request.image_requests[0].architecture;
|
||||
if (arch !== 'x86_64' && arch !== 'aarch64') {
|
||||
throw new Error(`image type: ${arch} has no implementation yet`);
|
||||
}
|
||||
return {
|
||||
wizardMode,
|
||||
blueprintId: request.id,
|
||||
details: {
|
||||
blueprintName: request.name,
|
||||
blueprintDescription: request.description,
|
||||
},
|
||||
env: {
|
||||
serverUrl: request.customizations.subscription?.['server-url'] || '',
|
||||
baseUrl: request.customizations.subscription?.['base-url'] || '',
|
||||
},
|
||||
openScap: {
|
||||
profile: request.customizations.openscap
|
||||
?.profile_id as DistributionProfileItem,
|
||||
},
|
||||
fileSystem: fileSystem,
|
||||
firstBoot: {
|
||||
script: getFirstBootScript(request.customizations.files),
|
||||
},
|
||||
architecture: arch,
|
||||
distribution: getLatestMinorRelease(request.distribution),
|
||||
imageTypes: request.image_requests.map((image) => image.image_type),
|
||||
azure: {
|
||||
shareMethod: azureUploadOptions?.source_id ? 'sources' : 'manual',
|
||||
source: azureUploadOptions?.source_id || '',
|
||||
tenantId: azureUploadOptions?.tenant_id || '',
|
||||
subscriptionId: azureUploadOptions?.subscription_id || '',
|
||||
resourceGroup: azureUploadOptions?.resource_group,
|
||||
},
|
||||
gcp: {
|
||||
shareMethod: gcpUploadOptions?.share_with_accounts
|
||||
? 'withGoogle'
|
||||
: 'withInsights',
|
||||
accountType: gcpUploadOptions?.share_with_accounts?.[0].split(
|
||||
':'
|
||||
)[0] as GcpAccountType,
|
||||
email: gcpUploadOptions?.share_with_accounts?.[0].split(':')[1] || '',
|
||||
},
|
||||
aws: {
|
||||
accountId: awsUploadOptions?.share_with_accounts?.[0] || '',
|
||||
shareMethod: awsUploadOptions?.share_with_sources ? 'sources' : 'manual',
|
||||
source: { id: awsUploadOptions?.share_with_sources?.[0] },
|
||||
sourceId: awsUploadOptions?.share_with_sources?.[0],
|
||||
},
|
||||
snapshotting: {
|
||||
useLatest: !snapshot_date,
|
||||
snapshotDate: snapshot_date,
|
||||
},
|
||||
repositories: {
|
||||
customRepositories: request.customizations.custom_repositories || [],
|
||||
payloadRepositories: request.customizations.payload_repositories || [],
|
||||
recommendedRepositories: [],
|
||||
},
|
||||
registration: {
|
||||
registrationType: request.customizations?.subscription
|
||||
? request.customizations.subscription.rhc
|
||||
? 'register-now-rhc'
|
||||
: 'register-now-insights'
|
||||
: 'register-later',
|
||||
activationKey: request.customizations.subscription?.['activation-key'],
|
||||
},
|
||||
packages:
|
||||
request.customizations.packages
|
||||
?.filter((pkg) => !pkg.startsWith('@'))
|
||||
.map((pkg) => ({
|
||||
name: pkg,
|
||||
summary: '',
|
||||
repository: '',
|
||||
})) || [],
|
||||
groups:
|
||||
request.customizations.packages
|
||||
?.filter((grp) => grp.startsWith('@'))
|
||||
.map((grp) => ({
|
||||
name: grp.substr(1),
|
||||
description: '',
|
||||
repository: '',
|
||||
package_list: [],
|
||||
})) || [],
|
||||
};
|
||||
};
|
||||
|
||||
const getFirstBootScript = (files?: File[]): string => {
|
||||
const firstBootFile = files?.find(
|
||||
(file) => file.path === '/usr/local/sbin/custom-first-boot'
|
||||
);
|
||||
return firstBootFile?.data ? atob(firstBootFile.data) : '';
|
||||
};
|
||||
|
||||
const getImageRequests = (state: RootState): ImageRequest[] => {
|
||||
const imageTypes = selectImageTypes(state);
|
||||
const snapshotDate = convertMMDDYYYYToYYYYMMDD(selectSnapshotDate(state));
|
||||
const useLatest = selectUseLatest(state);
|
||||
return imageTypes.map((type) => ({
|
||||
architecture: selectArchitecture(state),
|
||||
image_type: type,
|
||||
upload_request: {
|
||||
type: uploadTypeByTargetEnv(type),
|
||||
options: getImageOptions(type, state),
|
||||
},
|
||||
snapshot_date: useLatest ? undefined : snapshotDate,
|
||||
}));
|
||||
};
|
||||
|
||||
const uploadTypeByTargetEnv = (imageType: ImageTypes): UploadTypes => {
|
||||
switch (imageType) {
|
||||
case 'aws':
|
||||
return 'aws';
|
||||
case 'gcp':
|
||||
return 'gcp';
|
||||
case 'azure':
|
||||
return 'azure';
|
||||
case 'oci':
|
||||
return 'oci.objectstorage';
|
||||
case 'wsl':
|
||||
return 'aws.s3';
|
||||
case 'guest-image':
|
||||
return 'aws.s3';
|
||||
case 'image-installer':
|
||||
return 'aws.s3';
|
||||
case 'vsphere':
|
||||
return 'aws.s3';
|
||||
case 'vsphere-ova':
|
||||
return 'aws.s3';
|
||||
case 'ami':
|
||||
return 'aws';
|
||||
default: {
|
||||
// TODO: add edge type
|
||||
throw new Error(`image type: ${imageType} has no implementation yet`);
|
||||
}
|
||||
}
|
||||
};
|
||||
const getImageOptions = (
|
||||
imageType: ImageTypes,
|
||||
state: RootState
|
||||
):
|
||||
| AwsUploadRequestOptions
|
||||
| AzureUploadRequestOptions
|
||||
| GcpUploadRequestOptions => {
|
||||
switch (imageType) {
|
||||
case 'aws':
|
||||
if (selectAwsShareMethod(state) === 'sources')
|
||||
return { share_with_sources: [selectAwsSourceId(state) || ''] };
|
||||
else return { share_with_accounts: [selectAwsAccountId(state)] };
|
||||
case 'azure':
|
||||
if (selectAzureShareMethod(state) === 'sources')
|
||||
return {
|
||||
source_id: selectAzureSource(state),
|
||||
resource_group: selectAzureResourceGroup(state),
|
||||
};
|
||||
else
|
||||
return {
|
||||
tenant_id: selectAzureTenantId(state),
|
||||
subscription_id: selectAzureSubscriptionId(state),
|
||||
resource_group: selectAzureResourceGroup(state),
|
||||
};
|
||||
case 'gcp': {
|
||||
let googleAccount: string = '';
|
||||
if (selectGcpShareMethod(state) === 'withGoogle') {
|
||||
const gcpEmail = selectGcpEmail(state);
|
||||
switch (selectGcpAccountType(state)) {
|
||||
case 'user':
|
||||
googleAccount = `user:${gcpEmail}`;
|
||||
break;
|
||||
case 'serviceAccount':
|
||||
googleAccount = `serviceAccount:${gcpEmail}`;
|
||||
break;
|
||||
case 'group':
|
||||
googleAccount = `group:${gcpEmail}`;
|
||||
break;
|
||||
case 'domain':
|
||||
googleAccount = `domain:${gcpEmail}`;
|
||||
}
|
||||
return { share_with_accounts: [googleAccount] };
|
||||
} else {
|
||||
// TODO: GCP withInsights is not implemented yet
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const getCustomizations = (
|
||||
state: RootState,
|
||||
orgID: string,
|
||||
serverStore: ServerStore
|
||||
): Customizations => {
|
||||
return {
|
||||
containers: undefined,
|
||||
directories: undefined,
|
||||
files: selectFirstBootScript(state)
|
||||
? [
|
||||
{
|
||||
path: '/etc/systemd/system/custom-first-boot.service',
|
||||
data: FIRST_BOOT_SERVICE_DATA,
|
||||
data_encoding: 'base64',
|
||||
ensure_parents: true,
|
||||
},
|
||||
{
|
||||
path: '/usr/local/sbin/custom-first-boot',
|
||||
data: btoa(selectFirstBootScript(state)),
|
||||
data_encoding: 'base64',
|
||||
mode: '0774',
|
||||
ensure_parents: true,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
subscription: getSubscription(state, orgID),
|
||||
packages: getPackages(state),
|
||||
payload_repositories: getPayloadRepositories(state),
|
||||
custom_repositories: getCustomRepositories(state),
|
||||
openscap: getOpenscapProfile(state),
|
||||
filesystem: getFileSystem(state),
|
||||
users: undefined,
|
||||
services: getServices(serverStore, state),
|
||||
hostname: undefined,
|
||||
kernel: serverStore.kernel?.append
|
||||
? { append: serverStore.kernel?.append }
|
||||
: undefined,
|
||||
groups: undefined,
|
||||
timezone: undefined,
|
||||
locale: undefined,
|
||||
firewall: undefined,
|
||||
installation_device: undefined,
|
||||
fdo: undefined,
|
||||
ignition: undefined,
|
||||
partitioning_mode: undefined,
|
||||
fips: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const getServices = (
|
||||
serverStore: ServerStore,
|
||||
state: RootState
|
||||
): Services | undefined => {
|
||||
const serverEnabledServices: string[] | undefined =
|
||||
serverStore.services?.enabled;
|
||||
const serverDisabledServicesFromServer: string[] | undefined =
|
||||
serverStore.services?.disabled;
|
||||
const serverMaskedServices = serverStore.services?.masked;
|
||||
const firstbootFlag: boolean =
|
||||
!!selectFirstBootScript(state) &&
|
||||
!serverEnabledServices?.includes(FIRST_BOOT_SERVICE);
|
||||
|
||||
const enabledServices = [
|
||||
...(serverEnabledServices ? serverEnabledServices : []),
|
||||
...(firstbootFlag ? [FIRST_BOOT_SERVICE] : []),
|
||||
];
|
||||
|
||||
if (
|
||||
enabledServices.length ||
|
||||
serverDisabledServicesFromServer ||
|
||||
serverMaskedServices
|
||||
) {
|
||||
return {
|
||||
enabled: enabledServices,
|
||||
disabled: serverDisabledServicesFromServer,
|
||||
masked: serverMaskedServices,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getOpenscapProfile = (state: RootState): OpenScap | undefined => {
|
||||
const profile = selectProfile(state);
|
||||
if (profile) {
|
||||
return { profile_id: profile };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getFileSystem = (state: RootState): Filesystem[] | undefined => {
|
||||
const mode = selectFileSystemPartitionMode(state);
|
||||
|
||||
const convertToBytes = (minSize: string, conversionFactor: number) => {
|
||||
return minSize.length > 0 ? parseInt(minSize) * conversionFactor : 0;
|
||||
};
|
||||
|
||||
if (mode === 'manual') {
|
||||
const partitions = selectPartitions(state);
|
||||
const fileSystem = partitions.map((partition) => {
|
||||
return {
|
||||
min_size: convertToBytes(
|
||||
partition.min_size,
|
||||
getConversionFactor(partition.unit)
|
||||
),
|
||||
mountpoint: partition.mountpoint,
|
||||
};
|
||||
});
|
||||
return fileSystem;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getPackages = (state: RootState) => {
|
||||
const packages = selectPackages(state);
|
||||
const groups = selectGroups(state);
|
||||
|
||||
if (packages.length > 0 || groups.length > 0) {
|
||||
return packages
|
||||
.map((pkg) => pkg.name)
|
||||
.concat(groups.map((grp) => '@' + grp.name));
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const getSubscription = (
|
||||
state: RootState,
|
||||
orgID: string
|
||||
): Subscription | undefined => {
|
||||
const registrationType = selectRegistrationType(state);
|
||||
const activationKey = selectActivationKey(state);
|
||||
|
||||
if (registrationType === 'register-later') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (activationKey === undefined) {
|
||||
throw new Error(
|
||||
'Activation key unexpectedly undefined while generating subscription customization'
|
||||
);
|
||||
}
|
||||
|
||||
const initialSubscription = {
|
||||
'activation-key': activationKey,
|
||||
organization: Number(orgID),
|
||||
'server-url': selectServerUrl(state),
|
||||
'base-url': selectBaseUrl(state),
|
||||
};
|
||||
|
||||
switch (registrationType) {
|
||||
case 'register-now-rhc':
|
||||
return { ...initialSubscription, insights: true, rhc: true };
|
||||
case 'register-now-insights':
|
||||
return { ...initialSubscription, insights: true, rhc: false };
|
||||
case 'register-now':
|
||||
return { ...initialSubscription, insights: false, rhc: false };
|
||||
}
|
||||
};
|
||||
|
||||
const getCustomRepositories = (state: RootState) => {
|
||||
const customRepositories = selectCustomRepositories(state);
|
||||
const recommendedRepositories = selectRecommendedRepositories(state);
|
||||
|
||||
const customAndRecommendedRepositories = [...customRepositories];
|
||||
|
||||
for (const repo in recommendedRepositories) {
|
||||
customAndRecommendedRepositories.push(
|
||||
convertSchemaToIBCustomRepo(recommendedRepositories[repo])
|
||||
);
|
||||
}
|
||||
|
||||
if (customAndRecommendedRepositories.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return customAndRecommendedRepositories;
|
||||
};
|
||||
|
||||
const getPayloadRepositories = (state: RootState) => {
|
||||
const payloadRepositories = selectPayloadRepositories(state);
|
||||
const recommendedRepositories = selectRecommendedRepositories(state);
|
||||
|
||||
const payloadAndRecommendedRepositories = [...payloadRepositories];
|
||||
|
||||
for (const repo in recommendedRepositories) {
|
||||
payloadAndRecommendedRepositories.push(
|
||||
convertSchemaToIBPayloadRepo(recommendedRepositories[repo])
|
||||
);
|
||||
}
|
||||
|
||||
if (payloadAndRecommendedRepositories.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return payloadAndRecommendedRepositories;
|
||||
};
|
||||
108
src/Components/CreateImageWizard/utilities/useValidation.tsx
Normal file
108
src/Components/CreateImageWizard/utilities/useValidation.tsx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useAppSelector } from '../../../store/hooks';
|
||||
import {
|
||||
BlueprintsResponse,
|
||||
useLazyGetBlueprintsQuery,
|
||||
} from '../../../store/imageBuilderApi';
|
||||
import {
|
||||
selectBlueprintId,
|
||||
selectBlueprintName,
|
||||
selectBlueprintDescription,
|
||||
selectFileSystemPartitionMode,
|
||||
selectPartitions,
|
||||
} from '../../../store/wizardSlice';
|
||||
import {
|
||||
getDuplicateMountPoints,
|
||||
isBlueprintNameValid,
|
||||
isBlueprintDescriptionValid,
|
||||
isMountpointMinSizeValid,
|
||||
} from '../validators';
|
||||
|
||||
export type StepValidation = {
|
||||
errors: {
|
||||
[key: string]: string;
|
||||
};
|
||||
disabledNext: boolean;
|
||||
};
|
||||
|
||||
export function useIsBlueprintValid(): boolean {
|
||||
const filesystem = useFilesystemValidation();
|
||||
const details = useDetailsValidation();
|
||||
return !filesystem.disabledNext && !details.disabledNext;
|
||||
}
|
||||
|
||||
export function useFilesystemValidation(): StepValidation {
|
||||
const mode = useAppSelector(selectFileSystemPartitionMode);
|
||||
const partitions = useAppSelector(selectPartitions);
|
||||
let disabledNext = false;
|
||||
|
||||
const errors: { [key: string]: string } = {};
|
||||
if (mode === 'automatic') {
|
||||
return { errors, disabledNext: false };
|
||||
}
|
||||
|
||||
const duplicates = getDuplicateMountPoints(partitions);
|
||||
for (const partition of partitions) {
|
||||
if (!isMountpointMinSizeValid(partition.min_size)) {
|
||||
errors[`min-size-${partition.id}`] = 'Must be larger than 0';
|
||||
disabledNext = true;
|
||||
}
|
||||
if (duplicates.includes(partition.mountpoint)) {
|
||||
errors[`mountpoint-${partition.id}`] = 'Duplicate mount points';
|
||||
disabledNext = true;
|
||||
}
|
||||
}
|
||||
return { errors, disabledNext };
|
||||
}
|
||||
|
||||
export function useDetailsValidation(): StepValidation {
|
||||
const name = useAppSelector(selectBlueprintName);
|
||||
const description = useAppSelector(selectBlueprintDescription);
|
||||
const blueprintId = useAppSelector(selectBlueprintId);
|
||||
|
||||
const nameValid = isBlueprintNameValid(name);
|
||||
const descriptionValid = isBlueprintDescriptionValid(description);
|
||||
const [uniqueName, setUniqueName] = useState<boolean | null>(null);
|
||||
|
||||
const [trigger] = useLazyGetBlueprintsQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (name !== '' && nameValid) {
|
||||
trigger({ name })
|
||||
.unwrap()
|
||||
.then((response: BlueprintsResponse) => {
|
||||
if (
|
||||
response?.meta?.count > 0 &&
|
||||
response.data[0].id !== blueprintId
|
||||
) {
|
||||
setUniqueName(false);
|
||||
} else {
|
||||
setUniqueName(true);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// If the request fails, we assume the name is unique
|
||||
setUniqueName(true);
|
||||
});
|
||||
}
|
||||
}, [blueprintId, name, setUniqueName, trigger]);
|
||||
|
||||
let nameError = '';
|
||||
if (!nameValid) {
|
||||
nameError = 'Invalid blueprint name';
|
||||
} else if (uniqueName === false) {
|
||||
nameError = 'Blueprint with this name already exists';
|
||||
} else if (!blueprintId && uniqueName === null) {
|
||||
// Hack to keep the error message from flickering in create mode
|
||||
nameError = 'default';
|
||||
}
|
||||
|
||||
return {
|
||||
errors: {
|
||||
name: nameError,
|
||||
description: descriptionValid ? '' : 'Invalid description',
|
||||
},
|
||||
disabledNext: !!nameError || !descriptionValid,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue