debian-image-builder-frontend/src/store/wizardSlice.ts
regexowl 4fb37c187e src: Rename "V2" folders to just Wizard
This replaces all occurences of "CreateImageWizardV2" with just "CreateImageWizard" as it is the only version now.
2024-07-17 11:17:06 +02:00

653 lines
18 KiB
TypeScript

import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
import { ApiRepositoryResponseRead } from './contentSourcesApi';
import {
CustomRepository,
DistributionProfileItem,
Distributions,
ImageRequest,
ImageTypes,
Repository,
} from './imageBuilderApi';
import { ActivationKeys } from './rhsmApi';
import { FileSystemPartitionMode } from '../Components/CreateImageWizard/steps/FileSystem';
import {
Partition,
Units,
} from '../Components/CreateImageWizard/steps/FileSystem/FileSystemConfiguration';
import {
GroupWithRepositoryInfo,
IBPackageWithRepositoryInfo,
} from '../Components/CreateImageWizard/steps/Packages/Packages';
import { AwsShareMethod } from '../Components/CreateImageWizard/steps/TargetEnvironment/Aws';
import { AzureShareMethod } from '../Components/CreateImageWizard/steps/TargetEnvironment/Azure';
import {
GcpAccountType,
GcpShareMethod,
} from '../Components/CreateImageWizard/steps/TargetEnvironment/Gcp';
import { V1ListSourceResponseItem } from '../Components/CreateImageWizard/types';
import { RHEL_9, X86_64 } from '../constants';
import { RootState } from '.';
type WizardModeOptions = 'create' | 'edit';
export type RegistrationType =
| 'register-later'
| 'register-now'
| 'register-now-insights'
| 'register-now-rhc';
export type wizardState = {
env: {
serverUrl: string;
baseUrl: string;
};
blueprintId?: string;
wizardMode: WizardModeOptions;
architecture: ImageRequest['architecture'];
distribution: Distributions;
imageTypes: ImageTypes[];
aws: {
accountId: string;
shareMethod: AwsShareMethod;
source: V1ListSourceResponseItem | undefined;
sourceId?: string;
};
azure: {
shareMethod: AzureShareMethod;
tenantId: string;
subscriptionId: string;
source: string;
resourceGroup: string;
};
gcp: {
shareMethod: GcpShareMethod;
accountType: GcpAccountType;
email: string;
};
registration: {
registrationType: RegistrationType;
activationKey: ActivationKeys['name'];
};
openScap: {
profile: DistributionProfileItem | undefined;
};
fileSystem: {
mode: FileSystemPartitionMode;
partitions: Partition[];
};
snapshotting: {
useLatest: boolean;
snapshotDate: string;
};
firstBoot: {
script: string;
};
repositories: {
customRepositories: CustomRepository[];
payloadRepositories: Repository[];
recommendedRepositories: ApiRepositoryResponseRead[];
};
packages: IBPackageWithRepositoryInfo[];
groups: GroupWithRepositoryInfo[];
details: {
blueprintName: string;
blueprintDescription: string;
};
};
const initialState: wizardState = {
env: {
serverUrl: '',
baseUrl: '',
},
wizardMode: 'create',
architecture: X86_64,
distribution: RHEL_9,
imageTypes: [],
aws: {
accountId: '',
shareMethod: 'sources',
source: undefined,
},
azure: {
shareMethod: 'sources',
tenantId: '',
subscriptionId: '',
source: '',
resourceGroup: '',
},
gcp: {
shareMethod: 'withGoogle',
accountType: 'user',
email: '',
},
registration: {
registrationType: 'register-now-rhc',
activationKey: undefined,
},
openScap: {
profile: undefined,
},
fileSystem: {
mode: 'automatic',
partitions: [],
},
snapshotting: {
useLatest: true,
snapshotDate: '',
},
repositories: {
customRepositories: [],
payloadRepositories: [],
recommendedRepositories: [],
},
packages: [],
groups: [],
details: {
blueprintName: '',
blueprintDescription: '',
},
firstBoot: { script: '' },
};
export const selectServerUrl = (state: RootState) => {
return state.wizard.env.serverUrl;
};
export const selectWizardMode = (state: RootState) => {
return state.wizard.wizardMode;
};
export const selectBlueprintId = (state: RootState) => {
return state.wizard.blueprintId;
};
export const selectBaseUrl = (state: RootState) => {
return state.wizard.env.baseUrl;
};
export const selectArchitecture = (state: RootState) => {
return state.wizard.architecture;
};
export const selectDistribution = (state: RootState) => {
return state.wizard.distribution;
};
export const selectImageTypes = (state: RootState) => {
return state.wizard.imageTypes;
};
export const selectAwsAccountId = (state: RootState): string => {
return state.wizard.aws.accountId;
};
export const selectAwsSourceId = (state: RootState): string | undefined => {
return state.wizard.aws.sourceId;
};
export const selectAwsShareMethod = (state: RootState) => {
return state.wizard.aws.shareMethod;
};
export const selectAzureTenantId = (state: RootState) => {
return state.wizard.azure.tenantId;
};
export const selectAzureShareMethod = (state: RootState) => {
return state.wizard.azure.shareMethod;
};
export const selectAzureSubscriptionId = (state: RootState) => {
return state.wizard.azure.subscriptionId;
};
export const selectAzureSource = (state: RootState) => {
return state.wizard.azure.source;
};
export const selectAzureResourceGroup = (state: RootState) => {
return state.wizard.azure.resourceGroup;
};
export const selectGcpShareMethod = (state: RootState) => {
return state.wizard.gcp.shareMethod;
};
export const selectGcpAccountType = (state: RootState) => {
return state.wizard.gcp.accountType;
};
export const selectGcpEmail = (state: RootState) => {
return state.wizard.gcp.email;
};
export const selectRegistrationType = (state: RootState) => {
return state.wizard.registration.registrationType;
};
export const selectActivationKey = (state: RootState) => {
return state.wizard.registration.activationKey;
};
export const selectProfile = (state: RootState) => {
return state.wizard.openScap.profile;
};
export const selectFileSystemPartitionMode = (state: RootState) => {
return state.wizard.fileSystem.mode;
};
export const selectPartitions = (state: RootState) => {
return state.wizard.fileSystem.partitions;
};
export const selectUseLatest = (state: RootState) => {
return state.wizard.snapshotting.useLatest;
};
export const selectSnapshotDate = (state: RootState) => {
return state.wizard.snapshotting.snapshotDate;
};
export const selectCustomRepositories = (state: RootState) => {
return state.wizard.repositories.customRepositories;
};
export const selectPayloadRepositories = (state: RootState) => {
return state.wizard.repositories.payloadRepositories;
};
export const selectRecommendedRepositories = (state: RootState) => {
return state.wizard.repositories.recommendedRepositories;
};
export const selectPackages = (state: RootState) => {
return state.wizard.packages;
};
export const selectGroups = (state: RootState) => {
return state.wizard.groups;
};
export const selectBlueprintName = (state: RootState) => {
return state.wizard.details.blueprintName;
};
export const selectBlueprintDescription = (state: RootState) => {
return state.wizard.details.blueprintDescription;
};
export const selectFirstBootScript = (state: RootState) => {
return state.wizard.firstBoot?.script;
};
export const wizardSlice = createSlice({
name: 'wizard',
initialState,
reducers: {
initializeWizard: () => initialState,
loadWizardState: (state, action: PayloadAction<wizardState>) =>
action.payload,
changeServerUrl: (state, action: PayloadAction<string>) => {
state.env.serverUrl = action.payload;
},
changeBaseUrl: (state, action: PayloadAction<string>) => {
state.env.baseUrl = action.payload;
},
changeArchitecture: (
state,
action: PayloadAction<ImageRequest['architecture']>
) => {
state.architecture = action.payload;
},
changeDistribution: (state, action: PayloadAction<Distributions>) => {
state.distribution = action.payload;
},
addImageType: (state, action: PayloadAction<ImageTypes>) => {
// Remove (if present) before adding to avoid duplicates
state.imageTypes = state.imageTypes.filter(
(imageType) => imageType !== action.payload
);
state.imageTypes.push(action.payload);
},
removeImageType: (state, action: PayloadAction<ImageTypes>) => {
state.imageTypes = state.imageTypes.filter(
(imageType) => imageType !== action.payload
);
},
changeImageTypes: (state, action: PayloadAction<ImageTypes[]>) => {
state.imageTypes = action.payload;
},
changeAwsAccountId: (state, action: PayloadAction<string>) => {
state.aws.accountId = action.payload;
},
changeAwsShareMethod: (state, action: PayloadAction<AwsShareMethod>) => {
state.aws.shareMethod = action.payload;
},
changeAwsSourceId: (state, action: PayloadAction<string | undefined>) => {
state.aws.sourceId = action.payload;
},
reinitializeAws: (state) => {
state.aws.accountId = '';
state.aws.shareMethod = 'sources';
state.aws.source = undefined;
},
changeAzureTenantId: (state, action: PayloadAction<string>) => {
state.azure.tenantId = action.payload;
},
changeAzureShareMethod: (
state,
action: PayloadAction<AzureShareMethod>
) => {
state.azure.shareMethod = action.payload;
},
changeAzureSubscriptionId: (state, action: PayloadAction<string>) => {
state.azure.subscriptionId = action.payload;
},
changeAzureSource: (state, action: PayloadAction<string>) => {
state.azure.source = action.payload;
},
changeAzureResourceGroup: (state, action: PayloadAction<string>) => {
state.azure.resourceGroup = action.payload;
},
reinitializeAzure: (state) => {
state.azure.shareMethod = 'sources';
state.azure.tenantId = '';
state.azure.subscriptionId = '';
state.azure.source = '';
state.azure.resourceGroup = '';
},
changeGcpShareMethod: (state, action: PayloadAction<GcpShareMethod>) => {
switch (action.payload) {
case 'withInsights':
state.gcp.accountType = undefined;
state.gcp.email = '';
break;
case 'withGoogle':
state.gcp.accountType = 'user';
}
state.gcp.shareMethod = action.payload;
},
changeGcpAccountType: (state, action: PayloadAction<GcpAccountType>) => {
state.gcp.accountType = action.payload;
},
changeGcpEmail: (state, action: PayloadAction<string>) => {
state.gcp.email = action.payload;
},
reinitializeGcp: (state) => {
state.gcp.shareMethod = 'withGoogle';
state.gcp.accountType = 'user';
state.gcp.email = '';
},
changeRegistrationType: (
state,
action: PayloadAction<RegistrationType>
) => {
state.registration.registrationType = action.payload;
},
changeActivationKey: (
state,
action: PayloadAction<ActivationKeys['name']>
) => {
state.registration.activationKey = action.payload;
},
changeOscapProfile: (
state,
action: PayloadAction<DistributionProfileItem | undefined>
) => {
state.openScap.profile = action.payload;
},
changeFileSystemConfiguration: (
state,
action: PayloadAction<Partition[]>
) => {
state.fileSystem.partitions = action.payload;
},
changeFileSystemPartitionMode: (
state,
action: PayloadAction<FileSystemPartitionMode>
) => {
const currentMode = state.fileSystem.mode;
// Only trigger if mode is being *changed*
if (currentMode !== action.payload) {
state.fileSystem.mode = action.payload;
switch (action.payload) {
case 'automatic':
state.fileSystem.partitions = [];
break;
case 'manual':
state.fileSystem.partitions = [
{
id: uuidv4(),
mountpoint: '/',
min_size: '10',
unit: 'GiB',
},
];
}
}
},
clearPartitions: (state) => {
const currentMode = state.fileSystem.mode;
if (currentMode === 'manual') {
state.fileSystem.partitions = [
{
id: uuidv4(),
mountpoint: '/',
min_size: '10',
unit: 'GiB',
},
];
}
},
addPartition: (state, action: PayloadAction<Partition>) => {
// Duplicate partitions are allowed temporarily, the wizard is responsible for final validation
state.fileSystem.partitions.push(action.payload);
},
removePartition: (state, action: PayloadAction<Partition['id']>) => {
state.fileSystem.partitions.splice(
state.fileSystem.partitions.findIndex(
(partition) => partition.id === action.payload
),
1
);
},
removePartitionByMountpoint: (
state,
action: PayloadAction<Partition['mountpoint']>
) => {
state.fileSystem.partitions.splice(
state.fileSystem.partitions.findIndex(
(partition) => partition.mountpoint === action.payload
),
1
);
},
changePartitionOrder: (state, action: PayloadAction<string[]>) => {
state.fileSystem.partitions = state.fileSystem.partitions.sort(
(a, b) => action.payload.indexOf(a.id) - action.payload.indexOf(b.id)
);
},
changePartitionMountpoint: (
state,
action: PayloadAction<{ id: string; mountpoint: string }>
) => {
const { id, mountpoint } = action.payload;
const partitionIndex = state.fileSystem.partitions.findIndex(
(partition) => partition.id === id
);
if (partitionIndex !== -1) {
state.fileSystem.partitions[partitionIndex].mountpoint = mountpoint;
}
},
changePartitionUnit: (
state,
action: PayloadAction<{ id: string; unit: Units }>
) => {
const { id, unit } = action.payload;
const partitionIndex = state.fileSystem.partitions.findIndex(
(partition) => partition.id === id
);
if (partitionIndex !== -1) {
state.fileSystem.partitions[partitionIndex].unit = unit;
}
},
changePartitionMinSize: (
state,
action: PayloadAction<{ id: string; min_size: string }>
) => {
const { id, min_size } = action.payload;
const partitionIndex = state.fileSystem.partitions.findIndex(
(partition) => partition.id === id
);
if (partitionIndex !== -1) {
state.fileSystem.partitions[partitionIndex].min_size = min_size;
}
},
changeUseLatest: (state, action: PayloadAction<boolean>) => {
state.snapshotting.useLatest = action.payload;
},
changeSnapshotDate: (state, action: PayloadAction<string>) => {
state.snapshotting.snapshotDate = action.payload;
},
changeCustomRepositories: (
state,
action: PayloadAction<CustomRepository[]>
) => {
state.repositories.customRepositories = action.payload;
},
changePayloadRepositories: (state, action: PayloadAction<Repository[]>) => {
state.repositories.payloadRepositories = action.payload;
},
addRecommendedRepository: (
state,
action: PayloadAction<ApiRepositoryResponseRead>
) => {
if (
!state.repositories.recommendedRepositories.some(
(repo) => repo.url === action.payload.url
)
) {
state.repositories.recommendedRepositories.push(action.payload);
}
},
removeRecommendedRepository: (
state,
action: PayloadAction<ApiRepositoryResponseRead>
) => {
state.repositories.recommendedRepositories =
state.repositories.recommendedRepositories.filter(
(repo) => repo.url !== action.payload.url
);
},
addPackage: (state, action: PayloadAction<IBPackageWithRepositoryInfo>) => {
const existingPackageIndex = state.packages.findIndex(
(pkg) => pkg.name === action.payload.name
);
if (existingPackageIndex !== -1) {
state.packages[existingPackageIndex] = action.payload;
} else {
state.packages.push(action.payload);
}
},
removePackage: (
state,
action: PayloadAction<IBPackageWithRepositoryInfo['name']>
) => {
state.packages.splice(
state.packages.findIndex((pkg) => pkg.name === action.payload),
1
);
},
addGroup: (state, action: PayloadAction<GroupWithRepositoryInfo>) => {
const existingGrpIndex = state.groups.findIndex(
(grp) => grp.name === action.payload.name
);
if (existingGrpIndex !== -1) {
state.groups[existingGrpIndex] = action.payload;
} else {
state.groups.push(action.payload);
}
},
removeGroup: (
state,
action: PayloadAction<GroupWithRepositoryInfo['name']>
) => {
state.groups.splice(
state.groups.findIndex((grp) => grp.name === action.payload),
1
);
},
changeBlueprintName: (state, action: PayloadAction<string>) => {
state.details.blueprintName = action.payload;
},
changeBlueprintDescription: (state, action: PayloadAction<string>) => {
state.details.blueprintDescription = action.payload;
},
setFirstBootScript: (state, action: PayloadAction<string>) => {
state.firstBoot.script = action.payload;
},
},
});
export const {
initializeWizard,
changeServerUrl,
changeBaseUrl,
changeArchitecture,
changeDistribution,
addImageType,
removeImageType,
changeImageTypes,
changeAwsAccountId,
changeAwsShareMethod,
changeAwsSourceId,
reinitializeAws,
changeAzureTenantId,
changeAzureShareMethod,
changeAzureSubscriptionId,
changeAzureSource,
changeAzureResourceGroup,
reinitializeAzure,
changeGcpShareMethod,
changeGcpAccountType,
changeGcpEmail,
reinitializeGcp,
changeRegistrationType,
changeActivationKey,
changeOscapProfile,
changeFileSystemConfiguration,
changeFileSystemPartitionMode,
clearPartitions,
addPartition,
removePartition,
removePartitionByMountpoint,
changePartitionMountpoint,
changePartitionUnit,
changePartitionMinSize,
changePartitionOrder,
changeUseLatest,
changeSnapshotDate,
changeCustomRepositories,
changePayloadRepositories,
addRecommendedRepository,
removeRecommendedRepository,
addPackage,
removePackage,
addGroup,
removeGroup,
changeBlueprintName,
changeBlueprintDescription,
loadWizardState,
setFirstBootScript,
} = wizardSlice.actions;
export default wizardSlice.reducer;