diff --git a/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx b/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx index 911aec2d..763349f1 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Alert, @@ -18,6 +18,7 @@ import { v4 as uuidv4 } from 'uuid'; import OscapProfileInformation from './OscapProfileInformation'; +import { usePoliciesQuery, PolicyRead } from '../../../../store/complianceApi'; import { useAppDispatch, useAppSelector } from '../../../../store/hooks'; import { DistributionProfileItem, @@ -29,9 +30,11 @@ import { Services, } from '../../../../store/imageBuilderApi'; import { - changeOscapProfile, + changeCompliance, selectDistribution, - selectProfile, + selectComplianceProfileID, + selectCompliancePolicyID, + selectCompliancePolicyTitle, addPackage, addPartition, changeFileSystemConfigurationType, @@ -42,47 +45,125 @@ import { changeMaskedServices, changeDisabledServices, changeKernelAppend, + selectComplianceType, } from '../../../../store/wizardSlice'; import { useHasSpecificTargetOnly } from '../../utilities/hasSpecificTargetOnly'; import { parseSizeUnit } from '../../utilities/parseSizeUnit'; import { Partition, Units } from '../FileSystem/FileSystemConfiguration'; +const OpenSCAPFGLabel = () => { + return ( + <> + OpenSCAP profile + + + To run a manual compliance scan in OpenSCAP, download this image. + + + } + > + + + + ); +}; + const ProfileSelector = () => { - const oscapProfile = useAppSelector(selectProfile); + const policyID = useAppSelector(selectCompliancePolicyID); + const policyTitle = useAppSelector(selectCompliancePolicyTitle); + const profileID = useAppSelector(selectComplianceProfileID); const release = useAppSelector(selectDistribution); + const majorVersion = release.split('-')[1]; const hasWslTargetOnly = useHasSpecificTargetOnly('wsl'); const dispatch = useAppDispatch(); const [isOpen, setIsOpen] = useState(false); + const complianceType = useAppSelector(selectComplianceType); + const { data: profiles, isFetching, isSuccess, isError, refetch, - } = useGetOscapProfilesQuery({ - distribution: release, - }); + } = useGetOscapProfilesQuery( + { + distribution: release, + }, + { + skip: complianceType === 'compliance', + } + ); + + const { + data: policies, + isFetching: isFetchingPolicies, + isSuccess: isSuccessPolicies, + } = usePoliciesQuery( + { + filter: `os_major_version=${majorVersion}`, + }, + { + skip: complianceType === 'openscap', + } + ); const { data: currentProfileData } = useGetOscapCustomizationsQuery( { distribution: release, // @ts-ignore if openScapProfile is undefined the query is going to get skipped - profile: oscapProfile, + profile: profileID, }, - { skip: !oscapProfile } + { skip: !profileID } ); const [trigger] = useLazyGetOscapCustomizationsQuery(); + useEffect(() => { + if (!policies || policies.data === undefined) { + return; + } + + if (policyID && !policyTitle) { + for (const p of policies.data) { + const pol = p as PolicyRead; + if (pol.id === policyID) { + dispatch( + changeCompliance({ + policyID: pol.id, + profileID: pol.ref_id, + policyTitle: pol.title, + }) + ); + } + } + } + }, [isSuccessPolicies]); + const handleToggle = () => { - if (!isOpen) { + if (!isOpen && complianceType === 'openscap') { refetch(); } setIsOpen(!isOpen); }; const handleClear = () => { - dispatch(changeOscapProfile(undefined)); + dispatch( + changeCompliance({ + profileID: undefined, + policyID: undefined, + policyTitle: undefined, + }) + ); clearOscapPackages(currentProfileData?.packages || []); dispatch(changeFileSystemConfigurationType('automatic')); handleServices(undefined); @@ -142,9 +223,9 @@ const ProfileSelector = () => { const handleSelect = ( _event: React.MouseEvent, - selection: OScapSelectOptionValueType + selection: OScapSelectOptionValueType | ComplianceSelectOptionValueType ) => { - if (selection.id === undefined) { + if (selection.profileID === undefined) { // handle user has selected 'None' case handleClear(); } else { @@ -152,7 +233,7 @@ const ProfileSelector = () => { trigger( { distribution: release, - profile: selection.id, + profile: selection.profileID as DistributionProfileItem, }, true // preferCacheValue ) @@ -164,7 +245,24 @@ const ProfileSelector = () => { handlePackages(oldOscapPackages, newOscapPackages); handleServices(response.services); dispatch(changeKernelAppend(response.kernel?.append || '')); - dispatch(changeOscapProfile(selection.id)); + if (complianceType === 'openscap') { + dispatch( + changeCompliance({ + profileID: selection.profileID, + policyID: undefined, + policyTitle: undefined, + }) + ); + } else { + const compl = selection as ComplianceSelectOptionValueType; + dispatch( + changeCompliance({ + profileID: compl.profileID, + policyID: compl.policyID, + policyTitle: compl.toString(), + }) + ); + } }); } setIsOpen(false); @@ -180,67 +278,77 @@ const ProfileSelector = () => { } }; + const complianceOptions = () => { + if (!policies || policies.data === undefined) { + return []; + } + + const res = []; + for (const p of policies.data) { + if (p === undefined) { + continue; + } + const pol = p as PolicyRead; + res.push(); + } + return res; + }; + return ( - OpenSCAP profile - - - To run a manual compliance scan in OpenSCAP, download this - image. - - - } - > - - - - } + label={complianceType === 'openscap' ? : <>Policy} > - { + if (profiles) { + return [].concat( + profiles.map((profile_id, index) => { + return ( + + ); + }) + ); + } + }} + > + {options()} + + )} + {complianceType === 'compliance' && ( + + ouiaId="compliancePolicySelect" + > + {complianceOptions()} + + )} {isError && ( string; }; @@ -291,7 +399,7 @@ const OScapSelectOption = ({ id: DistributionProfileItem, name?: string ): OScapSelectOptionValueType => ({ - id, + profileID: id, toString: () => name || '', }); @@ -303,9 +411,52 @@ const OScapSelectOption = ({ /> ); }; +type ComplianceSelectOptionPropType = { + policy: PolicyRead; +}; + +type ComplianceSelectOptionValueType = { + policyID: string; + profileID: string; + toString: () => string; +}; + +const ComplianceNoneOption = () => { + return ( + 'None', compareTo: () => false }} /> + ); +}; + +const ComplianceSelectOption = ({ policy }: ComplianceSelectOptionPropType) => { + const selectObj = ( + policyID: string, + profileID: string, + title: string + ): ComplianceSelectOptionValueType => ({ + policyID, + profileID, + toString: () => title, + }); + + const descr = ( + <> + Threshold: {policy.compliance_threshold} +
+ Active systems: {policy.total_system_count} + + ); + + return ( + + ); +}; export const Oscap = () => { - const oscapProfile = useAppSelector(selectProfile); + const oscapProfile = useAppSelector(selectComplianceProfileID); const environments = useAppSelector(selectImageTypes); return ( diff --git a/src/Components/CreateImageWizard/steps/Oscap/index.tsx b/src/Components/CreateImageWizard/steps/Oscap/index.tsx index 68279558..3516b03b 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/index.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/index.tsx @@ -1,21 +1,44 @@ import React, { useEffect } from 'react'; -import { Button, Form, Text, Title } from '@patternfly/react-core'; +import { + Button, + Form, + FormGroup, + Radio, + Text, + Title, +} from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; +import { useFlag } from '@unleash/proxy-client-react'; import { Oscap } from './Oscap'; -import { COMPLIANCE_AND_VULN_SCANNING_URL } from '../../../../constants'; +import { + COMPLIANCE_AND_VULN_SCANNING_URL, + COMPLIANCE_PROD_URL, + COMPLIANCE_STAGE_URL, +} from '../../../../constants'; import { imageBuilderApi } from '../../../../store/enhancedImageBuilderApi'; -import { useAppSelector } from '../../../../store/hooks'; -import { selectDistribution } from '../../../../store/wizardSlice'; +import { useAppDispatch, useAppSelector } from '../../../../store/hooks'; +import { + ComplianceType, + changeComplianceType, + selectDistribution, + selectComplianceType, +} from '../../../../store/wizardSlice'; +import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment'; const OscapStep = () => { + const dispatch = useAppDispatch(); + const complianceEnabled = useFlag('image-builder.compliance.enabled'); + const complianceType = useAppSelector(selectComplianceType); const prefetchOscapProfile = imageBuilderApi.usePrefetch( 'getOscapProfiles', {} ); + const { isProd } = useGetEnvironment(); const release = useAppSelector(selectDistribution); + useEffect(() => { prefetchOscapProfile({ distribution: release }); // This useEffect hook should run *only* on mount and therefore has an empty @@ -26,24 +49,78 @@ const OscapStep = () => { return (
- OpenSCAP profile + {complianceEnabled ? 'Compliance' : 'OpenSCAP profile'} - - OpenSCAP enables you to automatically monitor the adherence of your - registered RHEL systems to a selected regulatory compliance profile. -
- -
+ {complianceEnabled && ( + + + dispatch(changeComplianceType('openscap' as ComplianceType)) + } + /> + + dispatch(changeComplianceType('compliance' as ComplianceType)) + } + /> + + )} + {(!complianceEnabled || complianceType === 'openscap') && ( + + OpenSCAP enables you to automatically monitor the adherence of your + registered RHEL systems to a selected regulatory compliance profile. +
+ +
+ )} + {complianceType === 'compliance' && ( + + Insights compliance enables you to monitor the adherence of your + registered RHEL systems to a selected compliance policy. +
+ +
+ +
+ )} ); diff --git a/src/constants.ts b/src/constants.ts index 910c15b1..c1883042 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,6 +24,10 @@ export const RELEASE_LIFECYCLE_URL = 'https://access.redhat.com/support/policy/updates/errata'; export const AZURE_AUTH_URL = 'https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow'; +export const COMPLIANCE_PROD_URL = + 'https://console.redhat.com/insights/compliance/scappolicies'; +export const COMPLIANCE_STAGE_URL = + 'https://console.stage.redhat.com/insights/compliance/scappolicies'; export const ACTIVATION_KEYS_PROD_URL = 'https://console.redhat.com/insights/connector/activation-keys'; export const ACTIVATION_KEYS_STAGE_URL =