WizardV2: map oscap to wizard request
This commit is contained in:
parent
a7f46e938d
commit
ba3a2dc333
6 changed files with 138 additions and 186 deletions
|
|
@ -1,9 +1,8 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
FormGroup,
|
||||
Spinner,
|
||||
Popover,
|
||||
TextContent,
|
||||
Text,
|
||||
|
|
@ -26,14 +25,8 @@ import {
|
|||
} from '../../../../store/imageBuilderApi';
|
||||
import {
|
||||
changeOscapProfile,
|
||||
changeKernel,
|
||||
selectDistribution,
|
||||
selectProfile,
|
||||
selectKernel,
|
||||
selectDisabledServices,
|
||||
selectEnabledServices,
|
||||
changeDisabledServices,
|
||||
changeEnabledServices,
|
||||
clearOscapPackages,
|
||||
addPackage,
|
||||
selectPackages,
|
||||
|
|
@ -42,13 +35,10 @@ import {
|
|||
|
||||
const ProfileSelector = () => {
|
||||
const oscapProfile = useAppSelector(selectProfile);
|
||||
let kernel = useAppSelector(selectKernel);
|
||||
let disabledServices = useAppSelector(selectDisabledServices);
|
||||
let enabledServices = useAppSelector(selectEnabledServices);
|
||||
|
||||
const release = useAppSelector(selectDistribution);
|
||||
const packages = useAppSelector(selectPackages);
|
||||
const dispatch = useAppDispatch();
|
||||
const [profileName, setProfileName] = useState<string | undefined>('None');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const {
|
||||
data: profiles,
|
||||
|
|
@ -60,7 +50,7 @@ const ProfileSelector = () => {
|
|||
distribution: release,
|
||||
});
|
||||
|
||||
const { data } = useGetOscapCustomizationsQuery(
|
||||
const { data: oscapData } = useGetOscapCustomizationsQuery(
|
||||
{
|
||||
distribution: release,
|
||||
// @ts-ignore if oscapProfile is undefined the query is going to get skipped, so it's safe here to ignore the linter here
|
||||
|
|
@ -70,55 +60,28 @@ const ProfileSelector = () => {
|
|||
skip: !oscapProfile,
|
||||
}
|
||||
);
|
||||
kernel = data?.kernel?.append;
|
||||
disabledServices = data?.services?.disabled;
|
||||
enabledServices = data?.services?.enabled;
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetching || !isSuccess) return;
|
||||
dispatch(changeKernel(kernel));
|
||||
dispatch(changeDisabledServices(disabledServices));
|
||||
dispatch(changeEnabledServices(enabledServices));
|
||||
}, [
|
||||
isFetching,
|
||||
isSuccess,
|
||||
dispatch,
|
||||
data?.kernel?.append,
|
||||
data?.services?.disabled,
|
||||
data?.services?.enabled,
|
||||
disabledServices,
|
||||
enabledServices,
|
||||
kernel,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
data &&
|
||||
data.openscap &&
|
||||
typeof data.openscap.profile_name === 'string'
|
||||
) {
|
||||
setProfileName(data.openscap.profile_name);
|
||||
}
|
||||
}, [data]);
|
||||
const profileName = oscapProfile ? oscapData?.openscap?.profile_name : 'None';
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(clearOscapPackages());
|
||||
for (const pkg in data?.packages) {
|
||||
for (const pkg in oscapData?.packages) {
|
||||
if (
|
||||
packages.map((pkg) => pkg.name).includes(data?.packages[Number(pkg)])
|
||||
packages
|
||||
.map((pkg) => pkg.name)
|
||||
.includes(oscapData?.packages[Number(pkg)])
|
||||
) {
|
||||
dispatch(removePackage(data?.packages[Number(pkg)]));
|
||||
dispatch(removePackage(oscapData?.packages[Number(pkg)]));
|
||||
}
|
||||
dispatch(
|
||||
addPackage({
|
||||
name: data?.packages[Number(pkg)],
|
||||
name: oscapData?.packages[Number(pkg)],
|
||||
summary: 'Required by chosen OpenSCAP profile',
|
||||
repository: 'distro',
|
||||
isRequiredByOpenScap: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [data?.packages, dispatch]);
|
||||
}, [oscapData?.packages, dispatch]);
|
||||
|
||||
const handleToggle = () => {
|
||||
if (!isOpen) {
|
||||
|
|
@ -129,52 +92,26 @@ const ProfileSelector = () => {
|
|||
|
||||
const handleClear = () => {
|
||||
dispatch(changeOscapProfile(undefined));
|
||||
dispatch(changeKernel(undefined));
|
||||
dispatch(changeDisabledServices(undefined));
|
||||
dispatch(changeEnabledServices(undefined));
|
||||
dispatch(clearOscapPackages());
|
||||
setProfileName(undefined);
|
||||
};
|
||||
|
||||
const handleSelect = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
selection: DistributionProfileItem
|
||||
selection: OScapSelectOptionValueType
|
||||
) => {
|
||||
dispatch(changeOscapProfile(selection));
|
||||
dispatch(changeKernel(kernel));
|
||||
dispatch(changeDisabledServices(disabledServices));
|
||||
dispatch(changeEnabledServices(enabledServices));
|
||||
dispatch(changeOscapProfile(selection.id));
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const options = [
|
||||
<OScapNoneOption setProfileName={setProfileName} key="oscap-none-option" />,
|
||||
];
|
||||
if (isSuccess) {
|
||||
options.concat(
|
||||
profiles.map((profile_id) => {
|
||||
return (
|
||||
<OScapSelectOption
|
||||
key={profile_id}
|
||||
profile_id={profile_id}
|
||||
setProfileName={setProfileName}
|
||||
/>
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (isFetching) {
|
||||
options.push(
|
||||
<SelectOption
|
||||
isNoResultsOption={true}
|
||||
data-testid="policies-loading"
|
||||
key={'None'}
|
||||
>
|
||||
<Spinner size="md" />
|
||||
</SelectOption>
|
||||
);
|
||||
}
|
||||
const options = () => {
|
||||
if (profiles) {
|
||||
return [<OScapNoneOption key="oscap-none-option" />].concat(
|
||||
profiles.map((profile_id, index) => {
|
||||
return <OScapSelectOption key={index} profile_id={profile_id} />;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
|
|
@ -203,11 +140,13 @@ const ProfileSelector = () => {
|
|||
}
|
||||
>
|
||||
<Select
|
||||
loadingVariant={isFetching ? 'spinner' : undefined}
|
||||
ouiaId="profileSelect"
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
maxHeight="300px"
|
||||
selections={profileName}
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select a profile"
|
||||
|
|
@ -215,19 +154,13 @@ const ProfileSelector = () => {
|
|||
isDisabled={!isSuccess}
|
||||
onFilter={(_event, value) => {
|
||||
if (profiles) {
|
||||
return [
|
||||
<OScapNoneOption
|
||||
setProfileName={setProfileName}
|
||||
key="oscap-none-option"
|
||||
/>,
|
||||
].concat(
|
||||
return [<OScapNoneOption key="oscap-none-option" />].concat(
|
||||
profiles.map((profile_id, index) => {
|
||||
return (
|
||||
<OScapSelectOption
|
||||
key={index}
|
||||
profile_id={profile_id}
|
||||
setProfileName={setProfileName}
|
||||
input={value}
|
||||
filter={value}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
|
@ -235,7 +168,7 @@ const ProfileSelector = () => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
{options}
|
||||
{options()}
|
||||
</Select>
|
||||
{isError && (
|
||||
<Alert
|
||||
|
|
@ -251,59 +184,51 @@ const ProfileSelector = () => {
|
|||
);
|
||||
};
|
||||
|
||||
type OScapNoneOptionPropType = {
|
||||
setProfileName: (name: string) => void;
|
||||
};
|
||||
|
||||
const OScapNoneOption = ({ setProfileName }: OScapNoneOptionPropType) => {
|
||||
const OScapNoneOption = () => {
|
||||
return (
|
||||
<SelectOption
|
||||
value={undefined}
|
||||
onClick={() => {
|
||||
setProfileName('None');
|
||||
}}
|
||||
>
|
||||
<p>{'None'}</p>
|
||||
</SelectOption>
|
||||
<SelectOption value={{ toString: () => 'None', compareTo: () => false }} />
|
||||
);
|
||||
};
|
||||
|
||||
type OScapSelectOptionPropType = {
|
||||
profile_id: DistributionProfileItem;
|
||||
setProfileName: (name: string) => void;
|
||||
input?: string;
|
||||
filter?: string;
|
||||
};
|
||||
|
||||
type OScapSelectOptionValueType = {
|
||||
id: DistributionProfileItem;
|
||||
toString: () => string;
|
||||
};
|
||||
|
||||
const OScapSelectOption = ({
|
||||
profile_id,
|
||||
setProfileName,
|
||||
input,
|
||||
filter,
|
||||
}: OScapSelectOptionPropType) => {
|
||||
const release = useAppSelector(selectDistribution);
|
||||
const { data } = useGetOscapCustomizationsQuery({
|
||||
distribution: release,
|
||||
profile: profile_id,
|
||||
});
|
||||
|
||||
if (
|
||||
input &&
|
||||
!data?.openscap?.profile_name?.toLowerCase().includes(input.toLowerCase())
|
||||
filter &&
|
||||
!data?.openscap?.profile_name?.toLowerCase().includes(filter.toLowerCase())
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const selectObject = (
|
||||
id: DistributionProfileItem,
|
||||
name?: string
|
||||
): OScapSelectOptionValueType => ({
|
||||
id,
|
||||
toString: () => name || '',
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectOption
|
||||
key={profile_id}
|
||||
value={profile_id}
|
||||
onClick={() => {
|
||||
if (data?.openscap?.profile_name) {
|
||||
setProfileName(data?.openscap?.profile_name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<p>{data?.openscap?.profile_name}</p>
|
||||
</SelectOption>
|
||||
value={selectObject(profile_id, data?.openscap?.profile_name)}
|
||||
description={data?.openscap?.profile_description}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { Button, Form, Text, Title } from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
|
|
@ -10,9 +10,14 @@ import { useAppSelector } from '../../../../store/hooks';
|
|||
import { selectDistribution } from '../../../../store/wizardSlice';
|
||||
|
||||
const OscapStep = () => {
|
||||
const prefetchOscapProfile = imageBuilderApi.usePrefetch('getOscapProfiles');
|
||||
const prefetchOscapProfile = imageBuilderApi.usePrefetch(
|
||||
'getOscapProfiles',
|
||||
{}
|
||||
);
|
||||
const release = useAppSelector(selectDistribution);
|
||||
prefetchOscapProfile({ distribution: release });
|
||||
useEffect(() => {
|
||||
prefetchOscapProfile({ distribution: release });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||
import CreateDropdown from './CreateDropdown';
|
||||
import EditDropdown from './EditDropdown';
|
||||
|
||||
import { useServerStore } from '../../../../../store/hooks';
|
||||
import {
|
||||
useCreateBlueprintMutation,
|
||||
useUpdateBlueprintMutation,
|
||||
|
|
@ -33,6 +34,9 @@ const ReviewWizardFooter = () => {
|
|||
reset: resetCreate,
|
||||
},
|
||||
] = useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey' });
|
||||
|
||||
// initialize the server store with the data from RTK query
|
||||
const serverStore = useServerStore();
|
||||
const [
|
||||
,
|
||||
{
|
||||
|
|
@ -61,7 +65,7 @@ const ReviewWizardFooter = () => {
|
|||
const getBlueprintPayload = async () => {
|
||||
const userData = await auth?.getUser();
|
||||
const orgId = userData?.identity?.internal?.org_id;
|
||||
const requestBody = orgId && mapRequestFromState(store, orgId);
|
||||
const requestBody = orgId && mapRequestFromState(store, orgId, serverStore);
|
||||
return requestBody;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ import {
|
|||
BlueprintResponse,
|
||||
CreateBlueprintRequest,
|
||||
Customizations,
|
||||
DistributionProfileItem,
|
||||
GcpUploadRequestOptions,
|
||||
ImageRequest,
|
||||
ImageTypes,
|
||||
OpenScap,
|
||||
Services,
|
||||
Subscription,
|
||||
UploadTypes,
|
||||
} from '../../../store/imageBuilderApi';
|
||||
|
|
@ -36,6 +39,7 @@ import {
|
|||
selectPackages,
|
||||
selectPayloadRepositories,
|
||||
selectRecommendedRepositories,
|
||||
selectProfile,
|
||||
selectRegistrationType,
|
||||
selectServerUrl,
|
||||
wizardState,
|
||||
|
|
@ -46,18 +50,26 @@ import {
|
|||
} from '../steps/Repositories/Repositories';
|
||||
import { GcpAccountType } from '../steps/TargetEnvironment/Gcp';
|
||||
|
||||
type ServerStore = {
|
||||
kernel?: { append?: string };
|
||||
services?: { enabled?: string[]; disabled?: string[] };
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
orgID: string,
|
||||
serverStore: ServerStore
|
||||
): CreateBlueprintRequest => {
|
||||
const state = store.getState();
|
||||
const imageRequests = getImageRequests(state);
|
||||
const customizations = getCustomizations(state, orgID);
|
||||
const customizations = getCustomizations(state, orgID, serverStore);
|
||||
|
||||
return {
|
||||
name: selectBlueprintName(state),
|
||||
|
|
@ -102,16 +114,9 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => {
|
|||
serverUrl: request.customizations.subscription?.['server-url'] || '',
|
||||
baseUrl: request.customizations.subscription?.['base-url'] || '',
|
||||
},
|
||||
// TODO: add openscap support
|
||||
openScap: {
|
||||
profile: undefined,
|
||||
kernel: {
|
||||
kernelAppend: '',
|
||||
},
|
||||
services: {
|
||||
disabled: [],
|
||||
enabled: [],
|
||||
},
|
||||
profile: request.customizations.openscap
|
||||
?.profile_id as DistributionProfileItem,
|
||||
},
|
||||
fileSystem: {
|
||||
mode: 'automatic',
|
||||
|
|
@ -258,7 +263,11 @@ const getImageOptions = (
|
|||
return {};
|
||||
};
|
||||
|
||||
const getCustomizations = (state: RootState, orgID: string): Customizations => {
|
||||
const getCustomizations = (
|
||||
state: RootState,
|
||||
orgID: string,
|
||||
serverStore: ServerStore
|
||||
): Customizations => {
|
||||
return {
|
||||
containers: undefined,
|
||||
directories: undefined,
|
||||
|
|
@ -267,12 +276,14 @@ const getCustomizations = (state: RootState, orgID: string): Customizations => {
|
|||
packages: getPackages(state),
|
||||
payload_repositories: getPayloadRepositories(state),
|
||||
custom_repositories: getCustomRepositories(state),
|
||||
openscap: undefined,
|
||||
openscap: getOpenscapProfile(state),
|
||||
filesystem: undefined,
|
||||
users: undefined,
|
||||
services: undefined,
|
||||
services: getServices(serverStore),
|
||||
hostname: undefined,
|
||||
kernel: undefined,
|
||||
kernel: serverStore.kernel?.append
|
||||
? { append: serverStore.kernel?.append }
|
||||
: undefined,
|
||||
groups: undefined,
|
||||
timezone: undefined,
|
||||
locale: undefined,
|
||||
|
|
@ -285,6 +296,27 @@ const getCustomizations = (state: RootState, orgID: string): Customizations => {
|
|||
};
|
||||
};
|
||||
|
||||
const getServices = (serverStore: ServerStore): Services | undefined => {
|
||||
const enabledServices = serverStore.services?.enabled;
|
||||
const disabledServices = serverStore.services?.disabled;
|
||||
|
||||
if (enabledServices || disabledServices) {
|
||||
return {
|
||||
enabled: enabledServices,
|
||||
disabled: disabledServices,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getOpenscapProfile = (state: RootState): OpenScap | undefined => {
|
||||
const profile = selectProfile(state);
|
||||
if (profile) {
|
||||
return { profile_id: profile };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getPackages = (state: RootState) => {
|
||||
const packages = selectPackages(state);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,37 @@
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { useGetOscapCustomizationsQuery } from './imageBuilderApi';
|
||||
import { selectDistribution, selectProfile } from './wizardSlice';
|
||||
|
||||
import type { RootState, AppDispatch } from './index';
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
||||
export const useAppSelector = useSelector.withTypes<RootState>();
|
||||
|
||||
// common hooks
|
||||
export const useOscapData = () => {
|
||||
const release = useAppSelector(selectDistribution);
|
||||
const openScapProfile = useAppSelector(selectProfile);
|
||||
const { data } = useGetOscapCustomizationsQuery(
|
||||
{
|
||||
distribution: release,
|
||||
// @ts-ignore if openScapProfile is undefined the query is going to get skipped
|
||||
profile: openScapProfile,
|
||||
},
|
||||
{ skip: !openScapProfile }
|
||||
);
|
||||
if (!openScapProfile) return undefined;
|
||||
return {
|
||||
kernel: { append: data?.kernel?.append },
|
||||
services: {
|
||||
enabled: data?.services?.enabled,
|
||||
disabled: data?.services?.disabled,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useServerStore = () => {
|
||||
const oscap = useOscapData();
|
||||
return { ...oscap };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -66,13 +66,6 @@ export type wizardState = {
|
|||
};
|
||||
openScap: {
|
||||
profile: DistributionProfileItem | undefined;
|
||||
kernel: {
|
||||
kernelAppend: string | undefined;
|
||||
};
|
||||
services: {
|
||||
disabled: string[] | undefined;
|
||||
enabled: string[] | undefined;
|
||||
};
|
||||
};
|
||||
fileSystem: {
|
||||
mode: FileSystemPartitionMode;
|
||||
|
|
@ -122,13 +115,6 @@ const initialState: wizardState = {
|
|||
},
|
||||
openScap: {
|
||||
profile: undefined,
|
||||
kernel: {
|
||||
kernelAppend: '',
|
||||
},
|
||||
services: {
|
||||
disabled: [],
|
||||
enabled: [],
|
||||
},
|
||||
},
|
||||
fileSystem: {
|
||||
mode: 'automatic',
|
||||
|
|
@ -223,18 +209,6 @@ export const selectProfile = (state: RootState) => {
|
|||
return state.wizard.openScap.profile;
|
||||
};
|
||||
|
||||
export const selectKernel = (state: RootState) => {
|
||||
return state.wizard.openScap.kernel.kernelAppend;
|
||||
};
|
||||
|
||||
export const selectDisabledServices = (state: RootState) => {
|
||||
return state.wizard.openScap.services.disabled;
|
||||
};
|
||||
|
||||
export const selectEnabledServices = (state: RootState) => {
|
||||
return state.wizard.openScap.services.enabled;
|
||||
};
|
||||
|
||||
export const selectFileSystemPartitionMode = (state: RootState) => {
|
||||
return state.wizard.fileSystem.mode;
|
||||
};
|
||||
|
|
@ -370,21 +344,6 @@ export const wizardSlice = createSlice({
|
|||
state.openScap.profile = action.payload;
|
||||
},
|
||||
|
||||
changeKernel: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.openScap.kernel.kernelAppend = action.payload;
|
||||
},
|
||||
changeDisabledServices: (
|
||||
state,
|
||||
action: PayloadAction<string[] | undefined>
|
||||
) => {
|
||||
state.openScap.services.disabled = action.payload;
|
||||
},
|
||||
changeEnabledServices: (
|
||||
state,
|
||||
action: PayloadAction<string[] | undefined>
|
||||
) => {
|
||||
state.openScap.services.enabled = action.payload;
|
||||
},
|
||||
changeFileSystemConfiguration: (
|
||||
state,
|
||||
action: PayloadAction<Partition[]>
|
||||
|
|
@ -527,9 +486,6 @@ export const {
|
|||
changeRegistrationType,
|
||||
changeActivationKey,
|
||||
changeOscapProfile,
|
||||
changeKernel,
|
||||
changeDisabledServices,
|
||||
changeEnabledServices,
|
||||
changeFileSystemConfiguration,
|
||||
setIsNextButtonTouched,
|
||||
changeFileSystemPartitionMode,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue