diff --git a/src/web/components/powerfilter/__tests__/__snapshots__/minqodgroup.jsx.snap b/src/web/components/powerfilter/__tests__/__snapshots__/minqodgroup.jsx.snap
index 4639acb333..f76e5c38f7 100644
--- a/src/web/components/powerfilter/__tests__/__snapshots__/minqodgroup.jsx.snap
+++ b/src/web/components/powerfilter/__tests__/__snapshots__/minqodgroup.jsx.snap
@@ -19,28 +19,6 @@ exports[`MinQodGroup tests > should render 1`] = `
align-items: center;
}
-.c5 {
- margin-left: -5px;
-}
-
-.c5>* {
- display: -webkit-inline-box;
- display: -webkit-inline-flex;
- display: -ms-inline-flexbox;
- display: inline-flex;
-}
-
-.c5>* {
- margin-left: 5px;
-}
-
-.c4 {
- display: -webkit-inline-box;
- display: -webkit-inline-flex;
- display: -ms-inline-flexbox;
- display: inline-flex;
-}
-
.c0 {
display: -webkit-box;
display: -webkit-flex;
@@ -148,6 +126,28 @@ exports[`MinQodGroup tests > should render 1`] = `
bottom: 0;
}
+.c5 {
+ margin-left: -5px;
+}
+
+.c5>* {
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+}
+
+.c5>* {
+ margin-left: 5px;
+}
+
+.c4 {
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+}
+
diff --git a/src/web/components/powerfilter/__tests__/compliancelevelsgroup.jsx b/src/web/components/powerfilter/__tests__/compliancelevelsgroup.jsx
new file mode 100644
index 0000000000..3454b78e68
--- /dev/null
+++ b/src/web/components/powerfilter/__tests__/compliancelevelsgroup.jsx
@@ -0,0 +1,239 @@
+/* SPDX-FileCopyrightText: 2024 Greenbone AG
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import {describe, test, expect, testing} from '@gsa/testing';
+import {render, fireEvent} from 'web/utils/testing';
+
+import ComplianceLevelsFilterGroup from 'web/components/powerfilter/compliancelevelsgroup';
+
+import Filter from 'gmp/models/filter';
+
+describe('ComplianceLevelsFilterGroup audit reports tests', () => {
+ test('should call change handler', () => {
+ const filter = Filter.fromString('report_compliance_levels=');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+ fireEvent.click(checkbox[0]);
+
+ expect(handleChange).toHaveBeenCalled();
+ expect(handleChange).toHaveBeenCalledWith('y', 'report_compliance_levels');
+ });
+
+ test('should check checkbox', () => {
+ const filter = Filter.fromString('report_compliance_levels=yn');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+
+ expect(checkbox[0].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+ });
+
+ test('should uncheck checkbox', () => {
+ const filter1 = Filter.fromString('report_compliance_levels=yni');
+ const filter2 = Filter.fromString('report_compliance_levels=yn');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element, rerender} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+
+ expect(checkbox[0].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+
+ rerender(
+
,
+ );
+
+ expect(checkbox[0].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+ expect(checkbox[2].checked).toEqual(false);
+ });
+
+ test('should be unchecked by default', () => {
+ const filter = Filter.fromString();
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+
+ expect(checkbox[0].checked).toEqual(false);
+ expect(checkbox[1].checked).toEqual(false);
+ expect(checkbox[2].checked).toEqual(false);
+ expect(checkbox[3].checked).toEqual(false);
+ });
+
+ test('should call remove handler', () => {
+ const filter = Filter.fromString('report_compliance_levels=y');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+ expect(checkbox[0].checked).toEqual(true);
+
+ fireEvent.click(checkbox[0]);
+
+ expect(handleRemove).toHaveBeenCalled();
+ });
+});
+
+describe('ComplianceLevelsFilterGroup audit results tests', () => {
+ test('should call change handler', () => {
+ const filter = Filter.fromString('compliance_levels=');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+ fireEvent.click(checkbox[0]);
+
+ expect(handleChange).toHaveBeenCalled();
+ expect(handleChange).toHaveBeenCalledWith('y', 'compliance_levels');
+ });
+
+ test('should check checkbox', () => {
+ const filter = Filter.fromString('compliance_levels=yn');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+
+ expect(checkbox[0].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+ });
+
+ test('should uncheck checkbox', () => {
+ const filter1 = Filter.fromString('compliance_levels=yni');
+ const filter2 = Filter.fromString('compliance_levels=yn');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element, rerender} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+
+ expect(checkbox[0].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+
+ rerender(
+
,
+ );
+
+ expect(checkbox[0].checked).toEqual(true);
+ expect(checkbox[1].checked).toEqual(true);
+ expect(checkbox[2].checked).toEqual(false);
+ });
+
+ test('should be unchecked by default', () => {
+ const filter = Filter.fromString();
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+
+ expect(checkbox[0].checked).toEqual(false);
+ expect(checkbox[1].checked).toEqual(false);
+ expect(checkbox[2].checked).toEqual(false);
+ expect(checkbox[3].checked).toEqual(false);
+ });
+
+ test('should call remove handler', () => {
+ const filter = Filter.fromString('compliance_levels=y');
+ const handleChange = testing.fn();
+ const handleRemove = testing.fn();
+ const {element} = render(
+
,
+ );
+
+ const checkbox = element.querySelectorAll('input');
+ expect(checkbox[0].checked).toEqual(true);
+
+ fireEvent.click(checkbox[0]);
+
+ expect(handleRemove).toHaveBeenCalled();
+ });
+});
diff --git a/src/web/components/powerfilter/compliancelevelsgroup.jsx b/src/web/components/powerfilter/compliancelevelsgroup.jsx
new file mode 100644
index 0000000000..6dde65d727
--- /dev/null
+++ b/src/web/components/powerfilter/compliancelevelsgroup.jsx
@@ -0,0 +1,102 @@
+/* SPDX-FileCopyrightText: 2024 Greenbone AG
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import React from 'react';
+
+import useTranslation from 'web/hooks/useTranslation';
+
+import {isDefined} from 'gmp/utils/identity';
+
+import PropTypes from 'web/utils/proptypes';
+
+import Checkbox from 'web/components/form/checkbox';
+import FormGroup from 'web/components/form/formgroup';
+
+import IconDivider from 'web/components/layout/icondivider';
+
+import ComplianceStateLabels from 'web/components/label/compliancestate';
+
+const ComplianceLevelsFilterGroup = ({
+ filter,
+ onChange,
+ onRemove,
+ isResult = false,
+}) => {
+ const [_] = useTranslation();
+ const handleComplianceChange = (value, level) => {
+ const filterName = isResult
+ ? 'compliance_levels'
+ : 'report_compliance_levels';
+
+ let compliance = filter.get(filterName);
+
+ if (!compliance) {
+ compliance = '';
+ }
+
+ if (value && !compliance.includes(level)) {
+ compliance += level;
+ onChange(compliance, filterName);
+ } else if (!value && compliance.includes(level)) {
+ compliance = compliance.replace(level, '');
+
+ if (compliance.trim().length === 0) {
+ onRemove();
+ } else {
+ onChange(compliance, filterName);
+ }
+ }
+ };
+
+ let complianceLevels = filter.get(
+ isResult ? 'compliance_levels' : 'report_compliance_levels',
+ );
+
+ if (!isDefined(complianceLevels)) {
+ complianceLevels = '';
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+ComplianceLevelsFilterGroup.propTypes = {
+ filter: PropTypes.filter.isRequired,
+ isResult: PropTypes.bool,
+ onChange: PropTypes.func.isRequired,
+ onRemove: PropTypes.func.isRequired,
+};
+
+export default ComplianceLevelsFilterGroup;
diff --git a/src/web/components/powerfilter/severitylevelsgroup.jsx b/src/web/components/powerfilter/severitylevelsgroup.jsx
index aae6ebeead..1d2a43d254 100644
--- a/src/web/components/powerfilter/severitylevelsgroup.jsx
+++ b/src/web/components/powerfilter/severitylevelsgroup.jsx
@@ -51,7 +51,10 @@ const SeverityLevelsFilterGroup = ({filter, onChange, onRemove}) => {
levels = '';
}
return (
-
+
{
const gmp = useGmp();
const username = useUserName();
const loggedIn = useUserIsLoggedIn();
- const history = useHistory();
+ const navigate = useNavigate();
const handleSettingsClick = useCallback(
event => {
event.preventDefault();
- history.push('/usersettings');
+ navigate('/usersettings');
},
- [history],
+ [navigate],
);
const handleLogout = useCallback(
@@ -39,10 +39,10 @@ const Header = () => {
event.preventDefault();
gmp.doLogout().then(() => {
- history.push('/login?type=logout');
+ navigate('/login?type=logout');
});
},
- [gmp, history],
+ [gmp, navigate],
);
const menuPoints = [
diff --git a/src/web/components/table/__tests__/__snapshots__/detailstable.jsx.snap b/src/web/components/table/__tests__/__snapshots__/detailstable.jsx.snap
index 47a12aef28..ee9f45906c 100644
--- a/src/web/components/table/__tests__/__snapshots__/detailstable.jsx.snap
+++ b/src/web/components/table/__tests__/__snapshots__/detailstable.jsx.snap
@@ -19,6 +19,14 @@ exports[`DetailsTable tests > should render 1`] = `
align-items: stretch;
}
+.c2 {
+ width: 10%;
+}
+
+.c3 {
+ width: 90%;
+}
+
.c0 {
border: 0;
border-spacing: 0px;
@@ -36,14 +44,6 @@ exports[`DetailsTable tests > should render 1`] = `
padding-right: 5px;
}
-.c2 {
- width: 10%;
-}
-
-.c3 {
- width: 90%;
-}
-
@media print {
.c0 {
border-collapse: collapse;
diff --git a/src/web/entities/container.jsx b/src/web/entities/container.jsx
index 0386e7d4d9..27c651fd92 100644
--- a/src/web/entities/container.jsx
+++ b/src/web/entities/container.jsx
@@ -3,9 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
import React from 'react';
-import {withRouter} from 'react-router-dom';
+import {withRouter} from 'web/utils/withRouter';
import {connect} from 'react-redux';
@@ -320,17 +321,18 @@ class EntitiesContainer extends React.Component {
this.changeFilter(RESET_FILTER);
}
- handleFilterReset() {
- const {history, location} = this.props;
- const query = {...location.query};
+ handleFilterReset = () => {
+ const {navigate, location, searchParams} = this.props;
- // remove filter param from url
- delete query.filter;
+ searchParams.delete('filter');
- history.push({pathname: location.pathname, query});
+ navigate({
+ pathname: location.pathname,
+ search: searchParams.toString(),
+ });
this.changeFilter();
- }
+ };
openTagDialog() {
this.setState({tagDialogVisible: true});
@@ -584,9 +586,11 @@ EntitiesContainer.propTypes = {
entitiesCounts: PropTypes.counts,
entitiesError: PropTypes.error,
filter: PropTypes.filter,
+ searchParams: PropTypes.object,
+ location: PropTypes.object,
gmp: PropTypes.gmp.isRequired,
gmpname: PropTypes.string.isRequired,
- history: PropTypes.object.isRequired,
+ navigate: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,
isGenericBulkTrashcanDeleteDialog: PropTypes.bool,
listExportFileName: PropTypes.string,
diff --git a/src/web/entities/filterprovider.jsx b/src/web/entities/filterprovider.jsx
index 786e38cce2..13a80ccacb 100644
--- a/src/web/entities/filterprovider.jsx
+++ b/src/web/entities/filterprovider.jsx
@@ -17,7 +17,7 @@ const FilterProvider = ({
pageName = gmpname,
locationQuery = {},
}) => {
- const [returnedFilter, isLoadingFilter] = usePageFilter(pageName, {
+ const [returnedFilter, isLoadingFilter] = usePageFilter(pageName, gmpname, {
fallbackFilter,
locationQueryFilterString: locationQuery?.filter,
});
diff --git a/src/web/entities/withEntitiesContainer.jsx b/src/web/entities/withEntitiesContainer.jsx
index 0db78be131..7f3f179db9 100644
--- a/src/web/entities/withEntitiesContainer.jsx
+++ b/src/web/entities/withEntitiesContainer.jsx
@@ -5,7 +5,7 @@
import React from 'react';
-import {useLocation} from 'react-router-dom';
+import {useSearchParams} from 'react-router-dom';
import {connect} from 'react-redux';
@@ -27,99 +27,98 @@ import EntitiesContainer from './container';
const noop = () => {};
-const withEntitiesContainer = (
- gmpname,
- {
- entitiesSelector,
- loadEntities: loadEntitiesFunc,
- reloadInterval = noop,
- fallbackFilter,
- },
-) => Component => {
- let EntitiesContainerWrapper = ({
- children,
- filter,
- loadEntities,
- notify,
- ...props
- }) => (
- reloadInterval(props)}
- reload={(newFilter = filter) => loadEntities(newFilter)}
- name={gmpname}
- >
- {({reload}) => (
-
- {pageProps => }
-
- )}
-
- );
-
- EntitiesContainerWrapper.propTypes = {
- filter: PropTypes.filter,
- loadEntities: PropTypes.func.isRequired,
- notify: PropTypes.func.isRequired,
- };
-
- const mapStateToProps = (state, {filter}) => {
- const eSelector = entitiesSelector(state);
- const entities = eSelector.getEntities(filter);
- return {
- entities,
- entitiesCounts: eSelector.getEntitiesCounts(filter),
- entitiesError: eSelector.getEntitiesError(filter),
+const withEntitiesContainer =
+ (
+ gmpname,
+ {
+ entitiesSelector,
+ loadEntities: loadEntitiesFunc,
+ reloadInterval = noop,
+ fallbackFilter,
+ },
+ ) =>
+ Component => {
+ let EntitiesContainerWrapper = ({
+ children,
filter,
- isLoading: eSelector.isLoadingEntities(filter),
- loadedFilter: eSelector.getLoadedFilter(filter),
- };
- };
-
- const mapDispatchToProps = (dispatch, {gmp}) => ({
- loadEntities: filter => dispatch(loadEntitiesFunc(gmp)(filter)),
- updateFilter: filter => dispatch(pageFilter(gmpname, filter)),
- onInteraction: () => dispatch(renewSessionTimeout(gmp)()),
- });
-
- EntitiesContainerWrapper = compose(
- withDialogNotification,
- withDownload,
- withGmp,
- connect(
- mapStateToProps,
- mapDispatchToProps,
- ),
- )(EntitiesContainerWrapper);
-
- return props => {
- const location = useLocation();
- return (
-
- {({notify}) => (
- (
+ reloadInterval(props)}
+ reload={(newFilter = filter) => loadEntities(newFilter)}
+ name={gmpname}
+ >
+ {({reload}) => (
+
- {({filter}) => (
-
- )}
-
+ {pageProps => }
+
)}
-
+
);
+
+ EntitiesContainerWrapper.propTypes = {
+ filter: PropTypes.filter,
+ loadEntities: PropTypes.func.isRequired,
+ notify: PropTypes.func.isRequired,
+ };
+
+ const mapStateToProps = (state, {filter}) => {
+ const eSelector = entitiesSelector(state);
+ const entities = eSelector.getEntities(filter);
+ return {
+ entities,
+ entitiesCounts: eSelector.getEntitiesCounts(filter),
+ entitiesError: eSelector.getEntitiesError(filter),
+ filter,
+ isLoading: eSelector.isLoadingEntities(filter),
+ loadedFilter: eSelector.getLoadedFilter(filter),
+ };
+ };
+
+ const mapDispatchToProps = (dispatch, {gmp}) => ({
+ loadEntities: filter => dispatch(loadEntitiesFunc(gmp)(filter)),
+ updateFilter: filter => dispatch(pageFilter(gmpname, filter)),
+ onInteraction: () => dispatch(renewSessionTimeout(gmp)()),
+ });
+
+ EntitiesContainerWrapper = compose(
+ withDialogNotification,
+ withDownload,
+ withGmp,
+ connect(mapStateToProps, mapDispatchToProps),
+ )(EntitiesContainerWrapper);
+
+ return props => {
+ const [searchParams] = useSearchParams();
+ return (
+
+ {({notify}) => (
+
+ {({filter}) => (
+
+ )}
+
+ )}
+
+ );
+ };
};
-};
export default withEntitiesContainer;
diff --git a/src/web/entity/component.jsx b/src/web/entity/component.jsx
index 662e4ac833..0f22134ec3 100644
--- a/src/web/entity/component.jsx
+++ b/src/web/entity/component.jsx
@@ -23,14 +23,16 @@ import {generateFilename} from 'web/utils/render';
import withGmp from 'web/utils/withGmp';
-export const goto_details = (type, props) => ({data}) => {
- const {history} = props;
- return history.push('/' + type + '/' + data.id);
-};
+export const goto_details =
+ (type, props) =>
+ ({data}) => {
+ const {navigate} = props;
+ return navigate('/' + type + '/' + data.id);
+ };
export const goto_list = (type, props) => () => {
- const {history} = props;
- return history.push('/' + type);
+ const {navigate} = props;
+ return navigate('/' + type);
};
class EntityComponent extends React.Component {
@@ -173,10 +175,7 @@ const mapDispatchToProps = (dispatch, {name, gmp}) => {
export default compose(
withGmp,
- connect(
- mapStateToProps,
- mapDispatchToProps,
- ),
+ connect(mapStateToProps, mapDispatchToProps),
)(EntityComponent);
// vim: set ts=2 sw=2 tw=80:
diff --git a/src/web/entity/withEntityContainer.jsx b/src/web/entity/withEntityContainer.jsx
index e0b1ebbf1e..c0a48bd7dd 100644
--- a/src/web/entity/withEntityContainer.jsx
+++ b/src/web/entity/withEntityContainer.jsx
@@ -7,7 +7,7 @@ import React from 'react';
import {connect} from 'react-redux';
-import {withRouter} from 'react-router-dom';
+import {withRouter} from 'web/utils/withRouter';
import Filter from 'gmp/models/filter';
@@ -45,71 +45,70 @@ export const permissionsSubjectFilter = id =>
id,
).all();
-const withEntityContainer = (
- entityType,
- {
- load,
- entitySelector,
- mapStateToProps: componentMapStateToProps,
- reloadInterval = defaultEntityReloadIntervalFunc,
- },
-) => Component => {
- const EntityContainerWrapper = props => (
- reloadInterval(props)}
- reload={(id = props.id) => props.load(id)}
- name={entityType}
- >
- {({reload}) => (
-
- {cprops => }
-
- )}
-
- );
-
- EntityContainerWrapper.propTypes = {
- id: PropTypes.id.isRequired,
- load: PropTypes.func.isRequired,
- };
+const withEntityContainer =
+ (
+ entityType,
+ {
+ load,
+ entitySelector,
+ mapStateToProps: componentMapStateToProps,
+ reloadInterval = defaultEntityReloadIntervalFunc,
+ },
+ ) =>
+ Component => {
+ const EntityContainerWrapper = props => (
+ reloadInterval(props)}
+ reload={(id = props.id) => props.load(id)}
+ name={entityType}
+ >
+ {({reload}) => (
+
+ {cprops => }
+
+ )}
+
+ );
+
+ EntityContainerWrapper.propTypes = {
+ id: PropTypes.id.isRequired,
+ load: PropTypes.func.isRequired,
+ };
- const mapDispatchToProps = (dispatch, {gmp}) => ({
- onInteraction: () => dispatch(renewSessionTimeout(gmp)()),
- load: id => dispatch(load(gmp)(id)),
- });
-
- const mapStateToProps = (rootState, {gmp, id, match, ...props}) => {
- if (!isDefined(id)) {
- id = decodeURIComponent(match.params.id); // decodeURIComponent needs to be done for CPE IDs
- }
- const entitySel = entitySelector(rootState);
- const otherProps = isDefined(componentMapStateToProps)
- ? componentMapStateToProps(rootState, {
- gmp,
- id,
- ...props,
- })
- : undefined;
- return {
- isLoading: entitySel.isLoadingEntity(id),
- ...otherProps,
- id,
- entity: entitySel.getEntity(id),
- entityError: entitySel.getEntityError(id),
+ const mapDispatchToProps = (dispatch, {gmp}) => ({
+ onInteraction: () => dispatch(renewSessionTimeout(gmp)()),
+ load: id => dispatch(load(gmp)(id)),
+ });
+
+ const mapStateToProps = (rootState, {gmp, id, params, ...props}) => {
+ if (!isDefined(id)) {
+ id = decodeURIComponent(params.id); // decodeURIComponent needs to be done for CPE IDs
+ }
+ const entitySel = entitySelector(rootState);
+ const otherProps = isDefined(componentMapStateToProps)
+ ? componentMapStateToProps(rootState, {
+ gmp,
+ id,
+ ...props,
+ })
+ : undefined;
+ return {
+ isLoading: entitySel.isLoadingEntity(id),
+ ...otherProps,
+ id,
+ entity: entitySel.getEntity(id),
+ entityError: entitySel.getEntityError(id),
+ };
};
- };
- return compose(
- withGmp,
- withRouter,
- withDialogNotification,
- withDownload,
- connect(
- mapStateToProps,
- mapDispatchToProps,
- ),
- )(EntityContainerWrapper);
-};
+ return compose(
+ withGmp,
+ withRouter,
+ withDialogNotification,
+ withDownload,
+ connect(mapStateToProps, mapDispatchToProps),
+ )(EntityContainerWrapper);
+ };
export default withEntityContainer;
diff --git a/src/web/hooks/__tests__/usePageFilter.jsx b/src/web/hooks/__tests__/usePageFilter.jsx
index 6f12833aa5..3f817ec015 100644
--- a/src/web/hooks/__tests__/usePageFilter.jsx
+++ b/src/web/hooks/__tests__/usePageFilter.jsx
@@ -9,37 +9,25 @@ import {describe, test, expect, testing} from '@gsa/testing';
import Filter, {DEFAULT_FALLBACK_FILTER} from 'gmp/models/filter';
-import {fireEvent, rendererWith, screen} from 'web/utils/testing';
+import {rendererWith, waitFor} from 'web/utils/testing';
import {loadingActions} from 'web/store/usersettings/defaults/actions';
import {defaultFilterLoadingActions} from 'web/store/usersettings/defaultfilters/actions';
import usePageFilter from '../usePageFilter';
import {pageFilter} from 'web/store/pages/actions';
+import {vi} from 'vitest';
-const TestComponent = ({fallbackFilter}) => {
- const [filter, isLoadingFilter, changeFilter, removeFilter, resetFilter] =
- usePageFilter('somePage', {fallbackFilter});
- return (
- <>
- {isLoadingFilter ? (
- Loading...
- ) : (
- <>
- {filter.toFilterString()}
-
-
-
{!isLoading && (
@@ -86,13 +91,15 @@ const ToolBarIcons = ({
>
-
-
-
+ {!audit && (
+
+
+
+ )}
{!delta && (
@@ -144,6 +151,7 @@ const ToolBarIcons = ({
);
ToolBarIcons.propTypes = {
+ audit: PropTypes.bool,
delta: PropTypes.bool,
filter: PropTypes.filter,
isLoading: PropTypes.bool,
diff --git a/src/web/pages/reports/detailsfilterdialog.jsx b/src/web/pages/reports/detailsfilterdialog.jsx
index 4adb4cc7ba..da6ad218cc 100644
--- a/src/web/pages/reports/detailsfilterdialog.jsx
+++ b/src/web/pages/reports/detailsfilterdialog.jsx
@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
import React from 'react';
import PropTypes from 'web/utils/proptypes';
@@ -12,6 +11,7 @@ import Checkbox from 'web/components/form/checkbox';
import FilterDialog from 'web/components/powerfilter/filterdialog';
import BooleanFilterGroup from 'web/components/powerfilter/booleanfiltergroup';
+import ComplianceLevelsFilterGroup from 'web/components/powerfilter/compliancelevelsgroup';
import FilterStringGroup from 'web/components/powerfilter/filterstringgroup';
import FirstResultGroup from 'web/components/powerfilter/firstresultgroup';
import MinQodGroup from 'web/components/powerfilter/minqodgroup';
@@ -31,6 +31,7 @@ import useCapabilities from 'web/hooks/useCapabilities';
import DeltaResultsFilterGroup from './deltaresultsfiltergroup';
const ReportDetailsFilterDialog = ({
+ audit = false,
delta = false,
filter: initialFilter,
onCloseClick,
@@ -64,6 +65,8 @@ const ReportDetailsFilterDialog = ({
const resultHostsOnly = filter.get('result_hosts_only');
const handleRemoveLevels = () =>
onFilterChange(filter.copy().delete('levels'));
+ const handleRemoveCompliance = () =>
+ onFilterChange(filter.delete('compliance_levels'));
return (
)}
-
+ {!audit && (
+
+ )}
-
+ {audit ? (
+
+ ) : (
+
+ )}
-
+ {!audit && (
+
+ )}
@@ -154,6 +170,7 @@ const ReportDetailsFilterDialog = ({
};
ReportDetailsFilterDialog.propTypes = {
+ audit: PropTypes.bool,
delta: PropTypes.bool,
filter: PropTypes.filter,
onClose: PropTypes.func,
diff --git a/src/web/pages/reports/detailspage.jsx b/src/web/pages/reports/detailspage.jsx
index bfe1ffc340..634ae749a4 100644
--- a/src/web/pages/reports/detailspage.jsx
+++ b/src/web/pages/reports/detailspage.jsx
@@ -7,6 +7,7 @@
import React from 'react';
import {connect} from 'react-redux';
+import {withRouter} from 'web/utils/withRouter';
import _ from 'gmp/locale';
@@ -684,7 +685,6 @@ ReportDetails.propTypes = {
loadSettings: PropTypes.func.isRequired,
loadTarget: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
- match: PropTypes.object.isRequired,
pageFilter: PropTypes.filter,
reload: PropTypes.func.isRequired,
reportComposerDefaults: PropTypes.object,
@@ -780,7 +780,7 @@ ReportDetailsWrapper.propTypes = {
const getReportPageName = id => `report-${id}`;
-const mapDispatchToProps = (dispatch, {gmp, match}) => ({
+const mapDispatchToProps = (dispatch, {gmp, params}) => ({
onInteraction: () => dispatch(renewSessionTimeout(gmp)()),
loadFilters: () => dispatch(loadFilters(gmp)(RESULTS_FILTER_FILTER)),
loadSettings: () => dispatch(loadUserSettingDefaults(gmp)()),
@@ -793,12 +793,11 @@ const mapDispatchToProps = (dispatch, {gmp, match}) => ({
loadReportComposerDefaults: () => dispatch(loadReportComposerDefaults(gmp)()),
saveReportComposerDefaults: reportComposerDefaults =>
dispatch(saveReportComposerDefaults(gmp)(reportComposerDefaults)),
- updateFilter: f =>
- dispatch(setPageFilter(getReportPageName(match.params.id), f)),
+ updateFilter: f => dispatch(setPageFilter(getReportPageName(params.id), f)),
});
-const mapStateToProps = (rootState, {match}) => {
- const {id} = match.params;
+const mapStateToProps = (rootState, {params}) => {
+ const {id} = params;
const filterSel = filterSelector(rootState);
const reportSel = reportSelector(rootState);
const reportConfigsSel = reportConfigsSelector(rootState);
@@ -846,6 +845,7 @@ export default compose(
withGmp,
withDialogNotification,
withDownload,
+ withRouter,
connect(mapStateToProps, mapDispatchToProps),
)(ReportDetailsWrapper);
diff --git a/src/web/pages/reports/listpage.jsx b/src/web/pages/reports/listpage.jsx
index 946f2a0966..0728d61f7b 100644
--- a/src/web/pages/reports/listpage.jsx
+++ b/src/web/pages/reports/listpage.jsx
@@ -159,11 +159,13 @@ class Page extends React.Component {
const {selectedDeltaReport, beforeSelectFilter} = this.state;
if (isDefined(selectedDeltaReport)) {
- const {history} = this.props;
+ const {navigate} = this.props;
onFilterChanged(beforeSelectFilter);
- history.push('/report/delta/' + selectedDeltaReport.id + '/' + report.id);
+ navigate('/report/delta/' + selectedDeltaReport.id + '/' + report.id, {
+ replace: true,
+ });
} else {
const {filter = new Filter()} = this.props;
@@ -192,11 +194,8 @@ class Page extends React.Component {
render() {
const {filter, onFilterChanged, onInteraction, tasks} = this.props;
- const {
- containerTaskDialogVisible,
- importDialogVisible,
- task_id,
- } = this.state;
+ const {containerTaskDialogVisible, importDialogVisible, task_id} =
+ this.state;
return (
@@ -252,7 +251,7 @@ class Page extends React.Component {
Page.propTypes = {
filter: PropTypes.filter,
gmp: PropTypes.gmp.isRequired,
- history: PropTypes.object.isRequired,
+ navigate: PropTypes.func.isRequired,
loadTasks: PropTypes.func.isRequired,
tasks: PropTypes.arrayOf(PropTypes.model),
onChanged: PropTypes.func.isRequired,
@@ -284,10 +283,7 @@ const FALLBACK_REPORT_LIST_FILTER = Filter.fromString(
export default compose(
withGmp,
- connect(
- mapStateToProps,
- mapDispatchToProps,
- ),
+ connect(mapStateToProps, mapDispatchToProps),
withEntitiesContainer('report', {
fallbackFilter: FALLBACK_REPORT_LIST_FILTER,
entitiesSelector,
diff --git a/src/web/pages/results/__tests__/row.jsx b/src/web/pages/results/__tests__/row.jsx
index e061adf30e..b8f6b103cf 100644
--- a/src/web/pages/results/__tests__/row.jsx
+++ b/src/web/pages/results/__tests__/row.jsx
@@ -142,3 +142,147 @@ describe('Delta reports V2 with same severity, qod and hostname', () => {
expect(icons.length).toBe(0);
});
});
+
+describe('Audit reports with compliance', () => {
+ const {render} = rendererWith({gmp, store: true});
+
+ test('should render Audit report with compliance yes', () => {
+ const entity = Result.fromElement({
+ _id: '101',
+ name: 'Result 1',
+ host: {__text: '123.456.78.910', hostname: 'foo'},
+ port: '80/tcp',
+ severity: 10.0,
+ qod: {value: 80},
+ notes: [],
+ overrides: [],
+ tickets: [],
+ compliance: 'yes',
+ });
+
+ const {getAllByTestId} = render(
+ ,
+ );
+ const bars = getAllByTestId('progressbar-box');
+
+ expect(bars[0]).toHaveAttribute('title', 'Yes');
+ expect(bars[0]).toHaveTextContent('Yes');
+ });
+
+ test('should render Audit report with compliance no', () => {
+ const entity = Result.fromElement({
+ _id: '101',
+ name: 'Result 1',
+ host: {__text: '123.456.78.910', hostname: 'foo'},
+ port: '80/tcp',
+ severity: 10.0,
+ qod: {value: 80},
+ notes: [],
+ overrides: [],
+ tickets: [],
+ compliance: 'no',
+ });
+
+ const {getAllByTestId} = render(
+ ,
+ );
+ const bars = getAllByTestId('progressbar-box');
+ expect(bars[0]).toHaveAttribute('title', 'No');
+ expect(bars[0]).toHaveTextContent('No');
+ });
+
+ test('should render Audit report with compliance incomplete', () => {
+ const entity = Result.fromElement({
+ _id: '101',
+ name: 'Result 1',
+ host: {__text: '123.456.78.910', hostname: 'foo'},
+ port: '80/tcp',
+ severity: 10.0,
+ qod: {value: 80},
+ notes: [],
+ overrides: [],
+ tickets: [],
+ compliance: 'incomplete',
+ });
+
+ const {getAllByTestId} = render(
+ ,
+ );
+ const bars = getAllByTestId('progressbar-box');
+ expect(bars[0]).toHaveAttribute('title', 'Incomplete');
+ expect(bars[0]).toHaveTextContent('Incomplete');
+ });
+
+ test('should render Audit report with compliance undefined', () => {
+ const entity = Result.fromElement({
+ _id: '101',
+ name: 'Result 1',
+ host: {__text: '123.456.78.910', hostname: 'foo'},
+ port: '80/tcp',
+ severity: 10.0,
+ qod: {value: 80},
+ notes: [],
+ overrides: [],
+ tickets: [],
+ compliance: 'undefined',
+ });
+
+ const {getAllByTestId} = render(
+ ,
+ );
+ const bars = getAllByTestId('progressbar-box');
+ expect(bars[0]).toHaveAttribute('title', 'Undefined');
+ expect(bars[0]).toHaveTextContent('Undefined');
+ });
+
+ test('Delta audit report with changed compliance', () => {
+ const entity = Result.fromElement({
+ _id: '101',
+ name: 'Result 1',
+ host: {__text: '123.456.78.910', hostname: 'foo'},
+ port: '80/tcp',
+ severity: 10.0,
+ qod: {value: 80},
+ notes: [],
+ overrides: [],
+ tickets: [],
+ compliance: 'undefined',
+ delta: {
+ delta_type: 'changed',
+ result: {
+ compliance: 'yes',
+ },
+ },
+ });
+
+ const {getAllByTestId} = render(
+ ,
+ );
+ const icons = getAllByTestId('svg-icon');
+ expect(icons.length).toEqual(1);
+ expect(icons[0]).toHaveAttribute(
+ 'title',
+ 'Compliance is changed from yes.',
+ );
+ });
+});
diff --git a/src/web/pages/results/row.jsx b/src/web/pages/results/row.jsx
index addef1f093..18ee6ad6ed 100644
--- a/src/web/pages/results/row.jsx
+++ b/src/web/pages/results/row.jsx
@@ -13,6 +13,7 @@ import {isDefined, isNumber} from 'gmp/utils/identity';
import {shorten} from 'gmp/utils/string';
import SeverityBar from 'web/components/bar/severitybar';
+import ComplianceBar from 'web/components/bar/compliancebar';
import DateTime from 'web/components/date/datetime';
@@ -42,6 +43,7 @@ import useGmp from "web/hooks/useGmp";
const Row = ({
actionsComponent: ActionsComponent = EntitiesActions,
+ audit = false,
delta = false,
entity,
links = true,
@@ -59,6 +61,7 @@ const Row = ({
entity.overrides.filter(override => override.isActive()).length > 0;
const hasTickets = entity.tickets.length > 0;
const deltaSeverity = entity.delta?.result?.severity;
+ const deltaCompliance = entity.delta?.result?.compliance;
const deltaHostname = entity.delta?.result?.host?.hostname;
const deltaQoD = entity.delta?.result?.qod?.value;
const epssScore = entity?.information?.epss?.max_severity?.score
@@ -95,16 +98,30 @@ const Row = ({
)}
-
-
- {isDefined(deltaSeverity) && entity.severity !== deltaSeverity && (
-
- )}
-
+ {audit ? (
+
+
+ {isDefined(deltaCompliance) &&
+ entity.compliance !== deltaCompliance && (
+
+ )}
+
+ ) : (
+
+ {}
+ {isDefined(deltaSeverity) && entity.severity !== deltaSeverity && (
+
+ )}
+
+ )}
@@ -165,6 +182,7 @@ const Row = ({
Row.propTypes = {
actionsComponent: PropTypes.component,
+ audit: PropTypes.bool,
delta: PropTypes.bool,
entity: PropTypes.model.isRequired,
links: PropTypes.bool,
diff --git a/src/web/pages/results/table.jsx b/src/web/pages/results/table.jsx
index 30e4344b04..ff936cba57 100644
--- a/src/web/pages/results/table.jsx
+++ b/src/web/pages/results/table.jsx
@@ -30,6 +30,7 @@ import useGmp from "web/hooks/useGmp";
const Header = ({
actionsColumn,
+ audit = false,
delta = false,
links = true,
sort = true,
@@ -72,15 +73,27 @@ const Header = ({
)}
-
+ {audit ? (
+
+ ) : (
+
+ )}
(
+}) => {
+ const caps = useCapabilities();
+ const displayIds = [
+ ...ALL_DISPLAYS,
+ ...(caps.featureEnabled('COMPLIANCE_REPORTS')
+ ? AUDIT_REPORTS_DISPLAYS
+ : [])
+ ];
+ return (
-);
+)};
StartDashboard.propTypes = {
id: PropTypes.id.isRequired,
diff --git a/src/web/pages/tags/component.jsx b/src/web/pages/tags/component.jsx
index 870f8eaf47..fa868aaa99 100644
--- a/src/web/pages/tags/component.jsx
+++ b/src/web/pages/tags/component.jsx
@@ -29,6 +29,7 @@ export const MAX_RESOURCES = 40; // concerns listing in "Assigned Resources" tab
const TYPES = [
'alert',
+ 'audit',
'host',
'operatingsystem',
'cpe',
@@ -42,6 +43,7 @@ const TYPES = [
'nvt',
'override',
'permission',
+ 'policy',
'portlist',
'report',
'reportconfig',
@@ -106,7 +108,13 @@ class TagComponent extends React.Component {
getResourceTypes() {
const {capabilities} = this.props;
- return TYPES.map(type =>
+ const types = [
+ ...TYPES,
+ ...(capabilities.featureEnabled('COMPLIANCE_REPORTS')
+ ? ['auditreport']
+ : [])
+ ].sort();
+ return types.map(type =>
capabilities.mayAccess(type) ? [type, typeName(type)] : undefined,
).filter(isDefined);
}
diff --git a/src/web/pages/tags/dialog.jsx b/src/web/pages/tags/dialog.jsx
index 9c49ae1760..38e1e661ea 100644
--- a/src/web/pages/tags/dialog.jsx
+++ b/src/web/pages/tags/dialog.jsx
@@ -40,6 +40,7 @@ const ScrollableContent = styled.div`
`;
const types = {
+ auditreport: 'audit_report',
operatingsystem: 'os',
certbund: 'cert_bund_adv',
dfncert: 'dfn_cert_adv',
diff --git a/src/web/pages/tasks/__tests__/detailspage.jsx b/src/web/pages/tasks/__tests__/detailspage.jsx
index b8db4497d3..ad5f4d31df 100644
--- a/src/web/pages/tasks/__tests__/detailspage.jsx
+++ b/src/web/pages/tasks/__tests__/detailspage.jsx
@@ -697,17 +697,18 @@ describe('Task ToolBarIcons tests', () => {
);
const icons = getAllByTestId('svg-icon');
+
const badgeIcons = getAllByTestId('badge-icon');
const links = baseElement.querySelectorAll('a');
const divs = baseElement.querySelectorAll('div');
- fireEvent.click(divs[9]);
+ fireEvent.click(divs[8]);
expect(handleTaskCreate).toHaveBeenCalled();
- expect(divs[9]).toHaveTextContent('New Task');
+ expect(divs[8]).toHaveTextContent('New Task');
- fireEvent.click(divs[10]);
+ fireEvent.click(divs[9]);
expect(handleContainerTaskCreate).toHaveBeenCalled();
- expect(divs[10]).toHaveTextContent('New Container Task');
+ expect(divs[9]).toHaveTextContent('New Container Task');
fireEvent.click(icons[3]);
expect(handleTaskClone).toHaveBeenCalledWith(task3);
@@ -797,13 +798,13 @@ describe('Task ToolBarIcons tests', () => {
const links = baseElement.querySelectorAll('a');
const divs = baseElement.querySelectorAll('div');
- fireEvent.click(divs[9]);
+ fireEvent.click(divs[8]);
expect(handleTaskCreate).toHaveBeenCalled();
- expect(divs[9]).toHaveTextContent('New Task');
+ expect(divs[8]).toHaveTextContent('New Task');
- fireEvent.click(divs[10]);
+ fireEvent.click(divs[9]);
expect(handleContainerTaskCreate).toHaveBeenCalled();
- expect(divs[10]).toHaveTextContent('New Container Task');
+ expect(divs[9]).toHaveTextContent('New Container Task');
fireEvent.click(icons[3]);
expect(handleTaskClone).toHaveBeenCalledWith(task4);
@@ -900,13 +901,13 @@ describe('Task ToolBarIcons tests', () => {
const links = baseElement.querySelectorAll('a');
const divs = baseElement.querySelectorAll('div');
- fireEvent.click(divs[9]);
+ fireEvent.click(divs[8]);
expect(handleTaskCreate).toHaveBeenCalled();
- expect(divs[9]).toHaveTextContent('New Task');
+ expect(divs[8]).toHaveTextContent('New Task');
- fireEvent.click(divs[10]);
+ fireEvent.click(divs[9]);
expect(handleContainerTaskCreate).toHaveBeenCalled();
- expect(divs[10]).toHaveTextContent('New Container Task');
+ expect(divs[9]).toHaveTextContent('New Container Task');
fireEvent.click(icons[3]);
expect(handleTaskClone).toHaveBeenCalledWith(task5);
@@ -1004,13 +1005,13 @@ describe('Task ToolBarIcons tests', () => {
const links = baseElement.querySelectorAll('a');
const divs = baseElement.querySelectorAll('div');
- fireEvent.click(divs[9]);
+ fireEvent.click(divs[8]);
expect(handleTaskCreate).toHaveBeenCalled();
- expect(divs[9]).toHaveTextContent('New Task');
+ expect(divs[8]).toHaveTextContent('New Task');
- fireEvent.click(divs[10]);
+ fireEvent.click(divs[9]);
expect(handleContainerTaskCreate).toHaveBeenCalled();
- expect(divs[10]).toHaveTextContent('New Container Task');
+ expect(divs[9]).toHaveTextContent('New Container Task');
fireEvent.click(icons[3]);
expect(handleTaskClone).toHaveBeenCalledWith(task2);
@@ -1106,13 +1107,13 @@ describe('Task ToolBarIcons tests', () => {
const links = baseElement.querySelectorAll('a');
const divs = baseElement.querySelectorAll('div');
- fireEvent.click(divs[9]);
+ fireEvent.click(divs[8]);
expect(handleTaskCreate).toHaveBeenCalled();
- expect(divs[9]).toHaveTextContent('New Task');
+ expect(divs[8]).toHaveTextContent('New Task');
- fireEvent.click(divs[10]);
+ fireEvent.click(divs[9]);
expect(handleContainerTaskCreate).toHaveBeenCalled();
- expect(divs[10]).toHaveTextContent('New Container Task');
+ expect(divs[9]).toHaveTextContent('New Container Task');
fireEvent.click(icons[3]);
expect(handleTaskClone).toHaveBeenCalledWith(task6);
@@ -1271,13 +1272,13 @@ describe('Task ToolBarIcons tests', () => {
const links = baseElement.querySelectorAll('a');
const divs = baseElement.querySelectorAll('div');
- fireEvent.click(divs[9]);
+ fireEvent.click(divs[8]);
expect(handleTaskCreate).toHaveBeenCalled();
- expect(divs[9]).toHaveTextContent('New Task');
+ expect(divs[8]).toHaveTextContent('New Task');
- fireEvent.click(divs[10]);
+ fireEvent.click(divs[9]);
expect(handleContainerTaskCreate).toHaveBeenCalled();
- expect(divs[10]).toHaveTextContent('New Container Task');
+ expect(divs[9]).toHaveTextContent('New Container Task');
fireEvent.click(icons[3]);
expect(handleTaskClone).toHaveBeenCalledWith(task8);
diff --git a/src/web/pages/tasks/dashboard/highresults.jsx b/src/web/pages/tasks/dashboard/highresults.jsx
index 34a43c8b2a..9190c8009b 100644
--- a/src/web/pages/tasks/dashboard/highresults.jsx
+++ b/src/web/pages/tasks/dashboard/highresults.jsx
@@ -5,7 +5,7 @@
import React from 'react';
-import {withRouter} from 'react-router-dom';
+import {withRouter} from 'web/utils/withRouter';
import {format as d3format} from 'd3-format';
@@ -73,9 +73,9 @@ export class TasksHighResultsDisplay extends React.Component {
}
handleDataClick(data) {
- const {history} = this.props;
+ const {navigate} = this.props;
- history.push(`/task/${data.id}`);
+ navigate(`/task/${data.id}`);
}
render() {
@@ -109,7 +109,7 @@ export class TasksHighResultsDisplay extends React.Component {
TasksHighResultsDisplay.propTypes = {
filter: PropTypes.filter,
- history: PropTypes.object.isRequired,
+ navigate: PropTypes.func.isRequired,
};
TasksHighResultsDisplay = compose(
diff --git a/src/web/pages/tasks/dashboard/mosthighresults.jsx b/src/web/pages/tasks/dashboard/mosthighresults.jsx
index 2fbe5bf242..3e827ed1bf 100644
--- a/src/web/pages/tasks/dashboard/mosthighresults.jsx
+++ b/src/web/pages/tasks/dashboard/mosthighresults.jsx
@@ -5,7 +5,7 @@
import React from 'react';
-import {withRouter} from 'react-router-dom';
+import {withRouter} from 'web/utils/withRouter';
import {format as d3format} from 'd3-format';
@@ -66,9 +66,9 @@ export class TasksMostHighResultsDisplay extends React.Component {
}
handleDataClick(data) {
- const {history} = this.props;
+ const {navigate} = this.props;
- history.push(`/task/${data.id}`);
+ navigate(`/task/${data.id}`);
}
render() {
@@ -107,7 +107,7 @@ export class TasksMostHighResultsDisplay extends React.Component {
TasksMostHighResultsDisplay.propTypes = {
filter: PropTypes.filter,
- history: PropTypes.object.isRequired,
+ navigate: PropTypes.func.isRequired,
};
TasksMostHighResultsDisplay = compose(
diff --git a/src/web/pages/tasks/detailspage.jsx b/src/web/pages/tasks/detailspage.jsx
index 5206549b06..5fe2c78b47 100644
--- a/src/web/pages/tasks/detailspage.jsx
+++ b/src/web/pages/tasks/detailspage.jsx
@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
import React from 'react';
import _ from 'gmp/locale';
@@ -312,9 +311,9 @@ Details.propTypes = {
class Page extends React.Component {
componentDidUpdate() {
// eslint-disable-next-line no-unused-vars
- const {entity, history, ...props} = this.props;
+ const {entity, navigate, ...props} = this.props;
if (isDefined(entity) && entity.usageType === 'audit') {
- return history.replace('/audit/' + entity.id);
+ return navigate('/audit/' + entity.id, {replace: true});
}
}
@@ -440,7 +439,7 @@ class Page extends React.Component {
Page.propTypes = {
entity: PropTypes.model,
- history: PropTypes.object.isRequired,
+ navigate: PropTypes.func.isRequired,
permissions: PropTypes.array,
onChanged: PropTypes.func.isRequired,
onDownloaded: PropTypes.func.isRequired,
diff --git a/src/web/pages/tasks/status.jsx b/src/web/pages/tasks/status.jsx
index 03a6778574..8fcf3cb9d8 100644
--- a/src/web/pages/tasks/status.jsx
+++ b/src/web/pages/tasks/status.jsx
@@ -23,7 +23,7 @@ const StyledDetailsLink = styled(DetailsLink)`
}
`;
-const TaskStatus = ({task, links = true}) => {
+const TaskStatus = ({task, links = true, isAudit = false}) => {
let report_id;
if (isDefined(task.current_report)) {
report_id = task.current_report.id;
@@ -35,16 +35,20 @@ const TaskStatus = ({task, links = true}) => {
}
return (
-
+
{
};
TaskStatus.propTypes = {
+ isAudit: PropTypes.bool,
links: PropTypes.bool,
task: PropTypes.model.isRequired,
};
diff --git a/src/web/pages/tlscertificates/filterdialog.jsx b/src/web/pages/tlscertificates/filterdialog.jsx
index f8ea18db00..6e102c4d1f 100644
--- a/src/web/pages/tlscertificates/filterdialog.jsx
+++ b/src/web/pages/tlscertificates/filterdialog.jsx
@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
import PropTypes from 'web/utils/proptypes';
import DefaultFilterDialog from 'web/components/powerfilter/dialog';
diff --git a/src/web/pages/usersettings/dialog.jsx b/src/web/pages/usersettings/dialog.jsx
index 0fe8335694..1673f37f13 100644
--- a/src/web/pages/usersettings/dialog.jsx
+++ b/src/web/pages/usersettings/dialog.jsx
@@ -69,6 +69,7 @@ let UserSettingsDialog = ({
defaultSchedule,
defaultTarget,
alertsFilter,
+ auditReportsFilter,
configsFilter,
credentialsFilter,
filtersFilter,
@@ -124,6 +125,7 @@ let UserSettingsDialog = ({
defaultSchedule,
defaultTarget,
alertsFilter,
+ auditReportsFilter,
configsFilter,
credentialsFilter,
filtersFilter,
@@ -247,6 +249,7 @@ let UserSettingsDialog = ({
filters.filter(filter => filter.filter_type === type);
const FilterPart = ({
alertsFilter,
+ auditReportsFilter,
configsFilter,
credentialsFilter,
filtersFilter,
@@ -50,6 +52,7 @@ const FilterPart = ({
onChange,
}) => {
const [_] = useTranslation();
+ const caps = useCapabilities();
return (
@@ -63,6 +66,19 @@ const FilterPart = ({
onChange={onChange}
/>
+ {caps.featureEnabled('COMPLIANCE_REPORTS') && (
+
+
+
+ )}
+ {capabilities.featureEnabled('COMPLIANCE_REPORTS') && (
+
+ )
+ }
{
const defaultSchedule = schedulesSel.getEntity(defaultScheduleId);
const defaultTarget = targetsSel.getEntity(defaultTargetId);
const alertsFilter = userDefaultFilterSelector.getFilter('alert');
+ const auditReportsFilter = userDefaultFilterSelector.getFilter('auditreport');
const configsFilter = userDefaultFilterSelector.getFilter('scanconfig');
const credentialsFilter = userDefaultFilterSelector.getFilter('credential');
const filtersFilter = userDefaultFilterSelector.getFilter('filter');
@@ -1010,6 +1026,7 @@ const mapStateToProps = rootState => {
defaultSchedule,
defaultTarget,
alertsFilter,
+ auditReportsFilter,
configsFilter,
credentialsFilter,
filtersFilter,
@@ -1050,6 +1067,7 @@ const mapDispatchToProps = (dispatch, {gmp}) => ({
loadFilterDefaults: () =>
Promise.all([
dispatch(loadUserSettingsDefaultFilter(gmp)('alert')),
+ dispatch(loadUserSettingsDefaultFilter(gmp)('auditreport')),
dispatch(loadUserSettingsDefaultFilter(gmp)('scanconfig')),
dispatch(loadUserSettingsDefaultFilter(gmp)('credential')),
dispatch(loadUserSettingsDefaultFilter(gmp)('filter')),
diff --git a/src/web/routes.jsx b/src/web/routes.jsx
index 63d0825356..b12ae1e86e 100644
--- a/src/web/routes.jsx
+++ b/src/web/routes.jsx
@@ -5,15 +5,18 @@
import React from 'react';
-import {Router, Route, Switch} from 'react-router-dom';
-
-import {createBrowserHistory} from 'history';
-import {stringify, parse} from 'qs';
-import qhistory from 'qhistory';
-
+import {
+ BrowserRouter as Router,
+ Routes,
+ Route,
+ Navigate,
+} from 'react-router-dom';
+import {isLoggedIn as selectIsLoggedIn} from 'web/store/usersettings/selectors';
import LocationObserver from 'web/components/observer/locationobserver';
import SessionObserver from 'web/components/observer/sessionobserver';
+import ConditionalRoute from 'web/components/conditionalRoute/ConditionalRoute';
+
import LegacyOmpPage from './pages/omp';
import Page from './pages/page';
import PageNotFound from './pages/notfoundpage';
@@ -21,8 +24,10 @@ import StartPage from './pages/start/page';
import AboutPage from './pages/help/about';
import AlertsPage from './pages/alerts/listpage';
+import AuditReportDetailsPage from './pages/reports/auditdetailspage';
import AlertDetailsPage from './pages/alerts/detailspage';
import AuditsPage from './pages/audits/listpage';
+import AuditReportsPage from './pages/reports/auditreportslistpage';
import AuditsDetailsPage from './pages/audits/detailspage';
import CertBundsPage from './pages/certbund/listpage';
import CertBundDetailsPage from './pages/certbund/detailspage';
@@ -66,6 +71,7 @@ import ReportFormatsPage from './pages/reportformats/listpage';
import ReportFormatDetailsPage from './pages/reportformats/detailspage';
import ReportsPage from './pages/reports/listpage';
import ReportDetailsPage from './pages/reports/detailspage';
+import DeltaAuditReportDetailsPage from './pages/reports/auditdeltadetailspage';
import DeltaReportDetailsPage from './pages/reports/deltadetailspage';
import ResultsPage from './pages/results/listpage';
import ResultDetailsPage from './pages/results/detailspage';
@@ -93,143 +99,167 @@ import UserSettingsPage from './pages/usersettings/usersettingspage';
import UsersPage from './pages/users/listpage';
import VulnerabilitiesPage from './pages/vulns/listpage';
-import PropTypes from 'web/utils/proptypes';
-import withGmp from 'web/utils/withGmp';
-
+import {useSelector} from 'react-redux';
import Authorized from './authorized';
-// create an own history for location.query support
-// see https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md#query-strings
-// for details
-export const createQueryHistory = (history = createBrowserHistory()) =>
- qhistory(history, stringify, parse);
+const LoggedOutRoutes = () => (
+
+ } />
+ } />
+ } />
+
+);
-const HISTORY = createQueryHistory();
+const LoggedInRoutes = () => {
+ return (
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ }
+ />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
-const Routes = () => (
-
-
-
-
-
-
-
-
-
-
+ } />
+
+ }
+ />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ }
+ />
+
+ }
+ />
+ } />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ } />
+
+
+
+
+ );
+};
-
-
-
-
-
-
-
-
-);
+const AppRoutes = () => {
+ const isLoggedIn = useSelector(selectIsLoggedIn);
-Routes.propTypes = {
- gmp: PropTypes.gmp.isRequired,
+ return (
+ {isLoggedIn ? : }
+ );
};
-export default withGmp(Routes);
-
-// vim: set ts=2 sw=2 tw=80:
+export default AppRoutes;
diff --git a/src/web/store/entities/__tests__/reducers.js b/src/web/store/entities/__tests__/reducers.js
index 71e7a11e04..e9112b0c86 100644
--- a/src/web/store/entities/__tests__/reducers.js
+++ b/src/web/store/entities/__tests__/reducers.js
@@ -24,10 +24,12 @@ describe('entities reducer tests', () => {
expect(entitiesReducer(undefined, {})).toEqual({
alert: initState,
audit: initState,
+ auditreport: initState,
certbund: initState,
cpe: initState,
credential: initState,
cve: initState,
+ deltaAuditReport: initState,
deltaReport: initState,
dfncert: initState,
filter: initState,
diff --git a/src/web/store/entities/auditreports.js b/src/web/store/entities/auditreports.js
new file mode 100644
index 0000000000..95febbe2e3
--- /dev/null
+++ b/src/web/store/entities/auditreports.js
@@ -0,0 +1,62 @@
+/* SPDX-FileCopyrightText: 2024 Greenbone AG
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import {
+ createEntitiesLoadingActions,
+ createLoadAllEntities,
+ createLoadEntities,
+ types,
+} from 'web/store/entities/utils/actions';
+
+import {createReducer, initialState} from 'web/store/entities/utils/reducers';
+import {createEntitiesSelector} from 'web/store/entities/utils/selectors';
+
+import {reportReducer} from './report/reducers';
+import {reportsReducer} from './reports/reducers';
+
+const reportsSelector = createEntitiesSelector('auditreport');
+const entitiesActions = createEntitiesLoadingActions('auditreport');
+const loadAllEntities = createLoadAllEntities({
+ selector: reportsSelector,
+ actions: entitiesActions,
+ entityType: 'auditreport',
+});
+const loadEntities = createLoadEntities({
+ selector: reportsSelector,
+ actions: entitiesActions,
+ entityType: 'auditreport',
+});
+
+const reducer = (state = initialState, action) => {
+ if (action.entityType !== 'auditreport') {
+ return state;
+ }
+
+ switch (action.type) {
+ case types.ENTITIES_LOADING_REQUEST:
+ case types.ENTITIES_LOADING_SUCCESS:
+ case types.ENTITIES_LOADING_ERROR:
+ return reportsReducer(state, action);
+
+ case types.ENTITY_LOADING_REQUEST:
+ case types.ENTITY_LOADING_SUCCESS:
+ case types.ENTITY_LOADING_ERROR:
+ return reportReducer(state, action);
+
+ default:
+ return state;
+ }
+};
+
+const deltaAuditReducer = createReducer('deltaAuditReport');
+
+export {
+ deltaAuditReducer,
+ loadAllEntities,
+ loadEntities,
+ reducer,
+ reportsSelector as selector,
+ entitiesActions,
+};
diff --git a/src/web/store/entities/reducers.js b/src/web/store/entities/reducers.js
index d12f11332f..7ec854db10 100644
--- a/src/web/store/entities/reducers.js
+++ b/src/web/store/entities/reducers.js
@@ -7,6 +7,10 @@ import {combineReducers} from 'redux';
import {reducer as alert} from './alerts';
import {reducer as audit} from './audits';
+import {
+ reducer as auditreport,
+ deltaAuditReducer as deltaAuditReport,
+} from './auditreports';
import {reducer as certbund} from './certbund';
import {reducer as cpe} from './cpes';
import {reducer as credential} from './credentials';
@@ -41,10 +45,12 @@ import {reducer as vuln} from './vulns';
const entitiesReducer = combineReducers({
alert,
audit,
+ auditreport,
certbund,
cpe,
credential,
cve,
+ deltaAuditReport,
deltaReport,
dfncert,
filter,
diff --git a/src/web/store/entities/report/__tests__/actions.js b/src/web/store/entities/report/__tests__/actions.js
index 5712358259..ca9bb8c2e5 100644
--- a/src/web/store/entities/report/__tests__/actions.js
+++ b/src/web/store/entities/report/__tests__/actions.js
@@ -14,18 +14,24 @@ import {types} from 'web/store/entities/utils/actions';
import {createState, testEntityActions} from 'web/store/entities/utils/testing';
import {
+ auditReportActions,
deltaReportActions,
loadReport,
+ loadAuditReport,
+ loadAuditReportIfNeeded,
+ loadAuditReportWithThreshold,
+ loadDeltaReport,
+ loadDeltaAuditReport,
loadReportIfNeeded,
loadReportWithThreshold,
reportActions,
- loadDeltaReport,
} from '../actions';
import {reportIdentifier} from '../selectors';
testEntityActions('report', reportActions);
testEntityActions('deltaReport', deltaReportActions);
+testEntityActions('auditreport', auditReportActions);
describe('loadReport function tests', () => {
test('should load report successfully', () => {
@@ -936,3 +942,936 @@ describe('loadDeltaReport function tests', () => {
});
});
});
+
+describe('loadAuditReport function tests', () => {
+ test('should load audit report successfully', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockResolvedValue({
+ data: {foo: 'bar'},
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ expect(loadAuditReport).toBeDefined();
+ expect(isFunction(loadAuditReport)).toBe(true);
+
+ expect.assertions(7);
+
+ return loadAuditReport(gmp)(id)(dispatch, getState).then(() => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: true, filter: undefined});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ data: {foo: 'bar'},
+ id,
+ });
+ });
+ });
+
+ test('should load audit report with results filter successfully', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockResolvedValue({
+ data: {foo: 'bar'},
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ const filter = Filter.fromString('foo=bar');
+
+ expect(loadAuditReport).toBeDefined();
+ expect(isFunction(loadAuditReport)).toBe(true);
+
+ expect.assertions(7);
+
+ return loadAuditReport(gmp)(id, {filter})(dispatch, getState).then(() => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: true, filter});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ filter,
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ filter,
+ data: {foo: 'bar'},
+ id,
+ });
+ });
+ });
+
+ test('should not load audit report if isLoading is true', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: true,
+ },
+ });
+
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockResolvedValue([{id: 'foo'}]);
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ expect.assertions(3);
+
+ return loadAuditReport(gmp)(id)(dispatch, getState).then(() => {
+ expect(getState).toBeCalled();
+ expect(dispatch).not.toBeCalled();
+ expect(get).not.toBeCalled();
+ });
+ });
+
+ test('should fail loading audit report with an error', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ [id]: {
+ isLoading: false,
+ },
+ });
+
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockRejectedValue('An Error');
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ expect.assertions(5);
+
+ return loadAuditReport(gmp)(id)(dispatch, getState).catch(() => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: true, filter: undefined});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_ERROR,
+ entityType: 'auditreport',
+ error: 'An Error',
+ id,
+ });
+ });
+ });
+});
+
+describe('report loadAuditReportIfNeeded function tests', () => {
+ test('should load audit report successfully if needed', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockResolvedValue({data: {foo: 'bar'}});
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ expect.assertions(7);
+
+ expect(loadAuditReportIfNeeded).toBeDefined();
+ expect(isFunction(loadAuditReportIfNeeded)).toBe(true);
+
+ return loadAuditReportIfNeeded(gmp)(id)(dispatch, getState).then(() => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: false, filter: undefined});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ data: {foo: 'bar'},
+ id,
+ });
+ });
+ });
+
+ test('should not load audit report if report is already in store', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: false,
+ },
+ byId: {
+ [id]: 'a1',
+ },
+ });
+
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockResolvedValue([{id: 'foo'}]);
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ expect.assertions(3);
+
+ return loadAuditReportIfNeeded(gmp)(id)(dispatch, getState).then(() => {
+ expect(getState).toBeCalled();
+ expect(dispatch).not.toBeCalled();
+ expect(get).not.toBeCalled();
+ });
+ });
+
+ test('should load audit report with results filter successfully if needed', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockResolvedValue({data: {foo: 'bar'}});
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ const filter = Filter.fromString('foo=bar');
+
+ expect.assertions(7);
+
+ expect(loadAuditReportIfNeeded).toBeDefined();
+ expect(isFunction(loadAuditReportIfNeeded)).toBe(true);
+
+ return loadAuditReportIfNeeded(gmp)(id, {filter})(dispatch, getState).then(
+ () => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: false, filter});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ filter,
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ filter,
+ data: {foo: 'bar'},
+ id,
+ });
+ },
+ );
+ });
+
+ test('should not audit load report if isLoading is true', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: true,
+ },
+ });
+
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockResolvedValue([{id: 'foo'}]);
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ expect.assertions(3);
+
+ return loadAuditReportIfNeeded(gmp)(id)(dispatch, getState).then(() => {
+ expect(getState).toBeCalled();
+ expect(dispatch).not.toBeCalled();
+ expect(get).not.toBeCalled();
+ });
+ });
+
+ test('should fail loading audit report with an error', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ [id]: {
+ isLoading: false,
+ },
+ });
+
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const get = testing.fn().mockRejectedValue('An Error');
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ };
+
+ expect.assertions(5);
+
+ return loadAuditReportIfNeeded(gmp)(id)(dispatch, getState).catch(() => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: false, filter: undefined});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch.mock.calls[0]).toEqual([
+ {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ id,
+ },
+ ]);
+ expect(dispatch.mock.calls[1]).toEqual([
+ {
+ type: types.ENTITY_LOADING_ERROR,
+ entityType: 'auditreport',
+ error: 'An Error',
+ id,
+ },
+ ]);
+ });
+ });
+});
+
+describe('loadAuditReportWithThreshold tests', () => {
+ test('should only load "simple" audit report', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const auditreport = {
+ report: {
+ results: {
+ counts: {
+ filtered: 10000,
+ },
+ },
+ },
+ };
+
+ const get = testing.fn().mockResolvedValue({
+ data: auditreport,
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ settings: {
+ reportResultsThreshold: 1000,
+ },
+ };
+
+ expect(loadAuditReportWithThreshold).toBeDefined();
+ expect(isFunction(loadAuditReportWithThreshold)).toBe(true);
+
+ expect.assertions(7);
+
+ return loadAuditReportWithThreshold(gmp)(id)(dispatch, getState).then(
+ () => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: false, filter: undefined});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ data: auditreport,
+ id,
+ });
+ },
+ );
+ });
+
+ test('should load "full" audit report', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const auditreport = {
+ report: {
+ results: {
+ counts: {
+ filtered: 10000,
+ },
+ },
+ },
+ };
+
+ const get = testing.fn().mockResolvedValue({
+ data: auditreport,
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ settings: {
+ reportResultsThreshold: 100001,
+ },
+ };
+
+ expect(loadAuditReportWithThreshold).toBeDefined();
+ expect(isFunction(loadAuditReportWithThreshold)).toBe(true);
+
+ expect.assertions(11);
+
+ return loadAuditReportWithThreshold(gmp)(id)(dispatch, getState).then(
+ () => {
+ expect(getState).toBeCalled();
+ expect(get).toHaveBeenCalledTimes(2);
+ expect(get).toHaveBeenNthCalledWith(
+ 1,
+ {id},
+ {details: false, filter: undefined},
+ );
+ expect(get).toHaveBeenNthCalledWith(
+ 2,
+ {id},
+ {details: true, filter: undefined},
+ );
+ expect(dispatch).toHaveBeenCalledTimes(4);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ data: auditreport,
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(3, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(4, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ data: auditreport,
+ id,
+ });
+ },
+ );
+ });
+
+ test('should only load "simple" audit report with filter', () => {
+ const id = 'a1';
+ const filter = Filter.fromString('foo=bar rows=10');
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [reportIdentifier(id, filter)]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const auditreport = {
+ report: {
+ results: {
+ counts: {
+ filtered: 10000,
+ },
+ },
+ },
+ };
+
+ const get = testing.fn().mockResolvedValue({
+ data: auditreport,
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ settings: {
+ reportResultsThreshold: 1000,
+ },
+ };
+
+ expect(loadAuditReportWithThreshold).toBeDefined();
+ expect(isFunction(loadAuditReportWithThreshold)).toBe(true);
+
+ expect.assertions(7);
+
+ return loadAuditReportWithThreshold(gmp)(id, {filter})(
+ dispatch,
+ getState,
+ ).then(() => {
+ expect(getState).toBeCalled();
+ expect(get).toBeCalledWith({id}, {details: false, filter});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ filter,
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ filter,
+ data: auditreport,
+ id,
+ });
+ });
+ });
+
+ test('should load "full" audit report with filter', () => {
+ const id = 'a1';
+ const filter = Filter.fromString('foo=bar rows=10');
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [reportIdentifier(id, filter)]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const auditreport = {
+ report: {
+ results: {
+ counts: {
+ filtered: 10000,
+ },
+ },
+ },
+ };
+
+ const get = testing.fn().mockResolvedValue({
+ data: auditreport,
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ settings: {
+ reportResultsThreshold: 100001,
+ },
+ };
+
+ expect(loadAuditReportWithThreshold).toBeDefined();
+ expect(isFunction(loadAuditReportWithThreshold)).toBe(true);
+
+ expect.assertions(11);
+
+ return loadAuditReportWithThreshold(gmp)(id, {filter})(
+ dispatch,
+ getState,
+ ).then(() => {
+ expect(getState).toBeCalled();
+ expect(get).toHaveBeenCalledTimes(2);
+ expect(get).toHaveBeenNthCalledWith(1, {id}, {details: false, filter});
+ expect(get).toHaveBeenNthCalledWith(2, {id}, {details: true, filter});
+ expect(dispatch).toHaveBeenCalledTimes(4);
+ expect(dispatch).toHaveBeenNthCalledWith(1, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ filter,
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(2, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ filter,
+ data: auditreport,
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(3, {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
+ filter,
+ id,
+ });
+ expect(dispatch).toHaveBeenNthCalledWith(4, {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'auditreport',
+ filter,
+ data: auditreport,
+ id,
+ });
+ });
+ });
+
+ test('should not load audit report if already loading', () => {
+ const id = 'a1';
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [id]: true,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const auditreport = {
+ report: {
+ results: {
+ counts: {
+ filtered: 10000,
+ },
+ },
+ },
+ };
+
+ const get = testing.fn().mockResolvedValue({
+ data: auditreport,
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ settings: {
+ reportResultsThreshold: 1000,
+ },
+ };
+
+ expect(loadAuditReportWithThreshold).toBeDefined();
+ expect(isFunction(loadAuditReportWithThreshold)).toBe(true);
+
+ expect.assertions(5);
+
+ return loadAuditReportWithThreshold(gmp)(id)(dispatch, getState).then(
+ () => {
+ expect(getState).toBeCalled();
+ expect(get).not.toHaveBeenCalled();
+ expect(dispatch).not.toHaveBeenCalled();
+ },
+ );
+ });
+
+ test('should not audit load report if already loading with filter', () => {
+ const id = 'a1';
+ const filter = Filter.fromString('foo=bar rows=10');
+ const rootState = createState('auditreport', {
+ isLoading: {
+ [reportIdentifier(id, filter)]: true,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const auditreport = {
+ report: {
+ results: {
+ counts: {
+ filtered: 10000,
+ },
+ },
+ },
+ };
+
+ const get = testing.fn().mockResolvedValue({
+ data: auditreport,
+ });
+
+ const gmp = {
+ auditreport: {
+ get,
+ },
+ settings: {
+ reportResultsThreshold: 1000,
+ },
+ };
+
+ expect(loadAuditReportWithThreshold).toBeDefined();
+ expect(isFunction(loadAuditReportWithThreshold)).toBe(true);
+
+ expect.assertions(5);
+
+ return loadAuditReportWithThreshold(gmp)(id, {filter})(
+ dispatch,
+ getState,
+ ).then(() => {
+ expect(getState).toBeCalled();
+ expect(get).not.toHaveBeenCalled();
+ expect(dispatch).not.toHaveBeenCalled();
+ });
+ });
+});
+
+describe('loadDeltaAuditReport function tests', () => {
+ test('should load delta audit report successfully', () => {
+ const id = 'a1';
+ const deltaId = 'a2';
+ const identifier = `${id}+${deltaId}`;
+
+ const rootState = createState('deltaAuditReport', {
+ isLoading: {
+ [identifier]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const getDelta = testing.fn().mockResolvedValue({
+ data: {foo: 'bar'},
+ });
+
+ const gmp = {
+ auditreport: {
+ getDelta,
+ },
+ };
+
+ expect(loadDeltaAuditReport).toBeDefined();
+ expect(isFunction(loadDeltaAuditReport)).toBe(true);
+
+ return loadDeltaAuditReport(gmp)(id, deltaId)(dispatch, getState).then(
+ () => {
+ expect(getState).toBeCalled();
+ expect(getDelta).toBeCalledWith(
+ {id},
+ {id: deltaId},
+ {filter: undefined},
+ );
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch.mock.calls[0]).toEqual([
+ {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'deltaAuditReport',
+ id: identifier,
+ },
+ ]);
+ expect(dispatch.mock.calls[1]).toEqual([
+ {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'deltaAuditReport',
+ data: {foo: 'bar'},
+ id: identifier,
+ },
+ ]);
+ },
+ );
+ });
+
+ test('should load delta audit report with results filter successfully', () => {
+ const id = 'a1';
+ const deltaId = 'a2';
+ const identifier = `${id}+${deltaId}`;
+
+ const rootState = createState('deltaAuditReport', {
+ isLoading: {
+ [identifier]: false,
+ },
+ });
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const getDelta = testing.fn().mockResolvedValue({
+ data: {foo: 'bar'},
+ });
+
+ const gmp = {
+ auditreport: {
+ getDelta,
+ },
+ };
+
+ const filter = Filter.fromString('foo=bar');
+
+ expect(loadDeltaAuditReport).toBeDefined();
+ expect(isFunction(loadDeltaReport)).toBe(true);
+
+ return loadDeltaAuditReport(gmp)(
+ id,
+ deltaId,
+ filter,
+ )(dispatch, getState).then(() => {
+ expect(getState).toBeCalled();
+ expect(getDelta).toBeCalledWith({id}, {id: deltaId}, {filter});
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch.mock.calls[0]).toEqual([
+ {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'deltaAuditReport',
+ id: identifier,
+ },
+ ]);
+ expect(dispatch.mock.calls[1]).toEqual([
+ {
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'deltaAuditReport',
+ data: {foo: 'bar'},
+ id: identifier,
+ },
+ ]);
+ });
+ });
+
+ test('should not load audit delta report if isLoading is true', () => {
+ const id = 'a1';
+ const deltaId = 'a2';
+ const identifier = `${id}+${deltaId}`;
+ const rootState = createState('deltaAuditReport', {
+ isLoading: {
+ [identifier]: true,
+ },
+ });
+
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const getDelta = testing.fn().mockResolvedValue([{id: 'foo'}]);
+
+ const gmp = {
+ auditreport: {
+ getDelta,
+ },
+ };
+
+ return loadDeltaAuditReport(gmp)(id, deltaId)(dispatch, getState).then(
+ () => {
+ expect(getState).toBeCalled();
+ expect(dispatch).not.toBeCalled();
+ expect(getDelta).not.toBeCalled();
+ },
+ );
+ });
+
+ test('should fail loading audit delta report with an error', () => {
+ const id = 'a1';
+ const deltaId = 'a2';
+ const identifier = `${id}+${deltaId}`;
+ const rootState = createState('deltaAuditReport', {
+ [identifier]: {
+ isLoading: false,
+ },
+ });
+
+ const getState = testing.fn().mockReturnValue(rootState);
+
+ const dispatch = testing.fn();
+
+ const getDelta = testing.fn().mockRejectedValue('An Error');
+
+ const gmp = {
+ auditreport: {
+ getDelta,
+ },
+ };
+
+ return loadDeltaAuditReport(gmp)(id, deltaId)(dispatch, getState).then(
+ () => {
+ expect(getState).toBeCalled();
+ expect(getDelta).toBeCalledWith(
+ {id},
+ {id: deltaId},
+ {filter: undefined},
+ );
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch.mock.calls[0]).toEqual([
+ {
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'deltaAuditReport',
+ id: identifier,
+ },
+ ]);
+ expect(dispatch.mock.calls[1]).toEqual([
+ {
+ type: types.ENTITY_LOADING_ERROR,
+ entityType: 'deltaAuditReport',
+ error: 'An Error',
+ id: identifier,
+ },
+ ]);
+ },
+ );
+ });
+});
diff --git a/src/web/store/entities/report/actions.js b/src/web/store/entities/report/actions.js
index 50bf796acf..a667af5cb4 100644
--- a/src/web/store/entities/report/actions.js
+++ b/src/web/store/entities/report/actions.js
@@ -11,30 +11,53 @@ import {
} from 'web/store/entities/utils/actions';
import {
+ auditReportSelector,
reportSelector,
deltaReportSelector,
+ deltaAuditReportSelector,
deltaReportIdentifier,
} from './selectors';
-const entityType = 'report';
-
export const reportActions = {
request: (id, filter) => ({
type: types.ENTITY_LOADING_REQUEST,
- entityType,
+ entityType: 'report',
+ filter,
+ id,
+ }),
+ success: (id, data, filter) => ({
+ type: types.ENTITY_LOADING_SUCCESS,
+ entityType: 'report',
+ data,
+ filter,
+ id,
+ }),
+ error: (id, error, filter) => ({
+ type: types.ENTITY_LOADING_ERROR,
+ entityType: 'report',
+ error,
+ filter,
+ id,
+ }),
+};
+
+export const auditReportActions = {
+ request: (id, filter) => ({
+ type: types.ENTITY_LOADING_REQUEST,
+ entityType: 'auditreport',
filter,
id,
}),
success: (id, data, filter) => ({
type: types.ENTITY_LOADING_SUCCESS,
- entityType,
+ entityType: 'auditreport',
data,
filter,
id,
}),
error: (id, error, filter) => ({
type: types.ENTITY_LOADING_ERROR,
- entityType,
+ entityType: 'auditreport',
error,
filter,
id,
@@ -150,3 +173,108 @@ export const loadDeltaReport =
error => dispatch(deltaReportActions.error(identifier, error)),
);
};
+
+export const loadAuditReport =
+ gmp =>
+ (id, {filter, details = true, force = false} = {}) =>
+ (dispatch, getState) => {
+ const rootState = getState();
+ const state = auditReportSelector(rootState);
+
+ if (!force && state.isLoadingEntity(id, filter)) {
+ return Promise.resolve();
+ }
+
+ dispatch(auditReportActions.request(id, filter));
+
+ return gmp.auditreport
+ .get({id}, {filter, details})
+ .then(
+ response => response.data,
+ error => {
+ dispatch(auditReportActions.error(id, error, filter));
+ return Promise.reject(error);
+ },
+ )
+ .then(data => {
+ dispatch(auditReportActions.success(id, data, filter));
+
+ return data;
+ });
+ };
+
+export const loadAuditReportWithThreshold =
+ gmp =>
+ (id, {filter} = {}) =>
+ (dispatch, getState) => {
+ const rootState = getState();
+ const state = auditReportSelector(rootState);
+
+ if (state.isLoadingEntity(id, filter)) {
+ return Promise.resolve();
+ }
+
+ dispatch(auditReportActions.request(id, filter));
+
+ const {reportResultsThreshold: threshold} = gmp.settings;
+ return gmp.auditreport
+ .get({id}, {filter, details: false})
+ .then(
+ response => response.data,
+ error => {
+ dispatch(auditReportActions.error(id, error, filter));
+ return Promise.reject(error);
+ },
+ )
+ .then(report => {
+ const fullReport =
+ isDefined(report) &&
+ isDefined(report.report) &&
+ isDefined(report.report.results) &&
+ report.report.results.counts.filtered < threshold;
+
+ dispatch(auditReportActions.success(id, report, filter));
+ if (fullReport) {
+ return loadAuditReport(gmp)(id, {filter, details: true, force: true})(
+ dispatch,
+ getState,
+ );
+ }
+ });
+ };
+
+export const loadAuditReportIfNeeded =
+ gmp =>
+ (id, {filter, details = false} = {}) =>
+ (dispatch, getState) => {
+ const rootState = getState();
+ const state = auditReportSelector(rootState);
+
+ if (isDefined(state.getEntity(id, filter))) {
+ return Promise.resolve();
+ }
+ return loadAuditReport(gmp)(id, {filter, details})(dispatch, getState);
+ };
+
+export const deltaAuditReportActions =
+ createEntityLoadingActions('deltaAuditReport');
+
+export const loadDeltaAuditReport =
+ gmp => (id, deltaId, filter) => (dispatch, getState) => {
+ const rootState = getState();
+ const state = deltaAuditReportSelector(rootState);
+
+ if (state.isLoading(id, deltaId)) {
+ return Promise.resolve();
+ }
+
+ const identifier = deltaReportIdentifier(id, deltaId);
+
+ dispatch(deltaAuditReportActions.request(identifier));
+
+ return gmp.auditreport.getDelta({id}, {id: deltaId}, {filter}).then(
+ response =>
+ dispatch(deltaAuditReportActions.success(identifier, response.data)),
+ error => dispatch(deltaAuditReportActions.error(identifier, error)),
+ );
+ };
diff --git a/src/web/store/entities/report/reducers.js b/src/web/store/entities/report/reducers.js
index 0166bac722..6a6dd2a4c8 100644
--- a/src/web/store/entities/report/reducers.js
+++ b/src/web/store/entities/report/reducers.js
@@ -70,7 +70,7 @@ const byId = (state = {}, action) => {
};
export const reportReducer = (state = {}, action) => {
- if (action.entityType !== 'report') {
+ if (action.entityType !== 'report' && action.entityType !== 'auditreport') {
return state;
}
diff --git a/src/web/store/entities/report/selectors.js b/src/web/store/entities/report/selectors.js
index 228b0d241a..26d7b3da4d 100644
--- a/src/web/store/entities/report/selectors.js
+++ b/src/web/store/entities/report/selectors.js
@@ -78,5 +78,11 @@ class DeltaReportSelector {
export const reportSelector = rootState =>
new ReportSelector(rootState.entities.report);
+export const auditReportSelector = rootState =>
+ new ReportSelector(rootState.entities.auditreport);
+
export const deltaReportSelector = rootState =>
new DeltaReportSelector(rootState.entities.deltaReport);
+
+export const deltaAuditReportSelector = rootState =>
+ new DeltaReportSelector(rootState.entities.deltaAuditReport);
diff --git a/src/web/store/entities/reports/reducers.js b/src/web/store/entities/reports/reducers.js
index cd66b54b5f..0ee6e3c88b 100644
--- a/src/web/store/entities/reports/reducers.js
+++ b/src/web/store/entities/reports/reducers.js
@@ -77,7 +77,7 @@ const byId = (state = {}, action) => {
};
export const reportsReducer = (state = {}, action) => {
- if (action.entityType !== 'report') {
+ if (action.entityType !== 'report' && action.entityType !== 'auditreport') {
return state;
}
diff --git a/src/web/store/usersettings/actions.js b/src/web/store/usersettings/actions.js
index c046627fd6..9325b77f0e 100644
--- a/src/web/store/usersettings/actions.js
+++ b/src/web/store/usersettings/actions.js
@@ -52,11 +52,14 @@ export const setIsLoggedIn = isLoggedIn => ({
isLoggedIn: isLoggedIn === true,
});
-export const renewSessionTimeout = gmp => () => dispatch =>
- gmp.user
- .renewSession()
- .then(response => dispatch(setSessionTimeout(response.data)));
-
+export const renewSessionTimeout = gmp => () => async dispatch => {
+ try {
+ const response = await gmp.user.renewSession();
+ dispatch(setSessionTimeout(response.data));
+ } catch (error) {
+ console.error('Error renewing session:', error);
+ }
+};
export const updateTimezone = gmp => timezone => dispatch => {
gmp.setTimezone(timezone);
return Promise.resolve(dispatch(setTimezone(timezone)));
diff --git a/src/web/utils/testing.jsx b/src/web/utils/testing.jsx
index 9130726598..6eea9d437f 100644
--- a/src/web/utils/testing.jsx
+++ b/src/web/utils/testing.jsx
@@ -26,12 +26,10 @@ import userEvent, {PointerEventsCheckLevel} from '@testing-library/user-event';
import {ThemeProvider, theme} from '@greenbone/opensight-ui-components';
-import {Router} from 'react-router-dom';
+import {BrowserRouter} from 'react-router-dom';
import {Provider} from 'react-redux';
-import {createMemoryHistory} from 'history';
-
import EverythingCapabilities from 'gmp/capabilities/everything';
import {hasValue, isDefined} from 'gmp/utils/identity';
@@ -40,7 +38,6 @@ import GmpContext from 'web/components/provider/gmpprovider';
import CapabilitiesContext from 'web/components/provider/capabilitiesprovider';
import LicenseProvider from 'web/components/provider/licenseprovider';
-import {createQueryHistory} from 'web/routes';
import configureStore from 'web/store';
import {StyleSheetManager} from 'styled-components';
@@ -126,7 +123,6 @@ const withProvider =
const TestingGmpProvider = withProvider('gmp', 'value')(GmpContext.Provider);
const TestingStoreProvider = withProvider('store')(Provider);
-const TestingRouter = withProvider('history')(Router);
const TestingCapabilitiesProvider = withProvider(
'capabilities',
'value',
@@ -146,11 +142,6 @@ export const rendererWith = (
store = configureStore({testing: true});
}
- let history;
- if (router === true) {
- history = createQueryHistory(createMemoryHistory({initialEntries: ['/']}));
- }
-
if (capabilities === true) {
capabilities = new EverythingCapabilities();
}
@@ -160,7 +151,11 @@ export const rendererWith = (
- {children}
+ {router ? (
+ {children}
+ ) : (
+ children
+ )}
@@ -173,7 +168,6 @@ export const rendererWith = (
render: ui => render({ui}),
gmp,
store,
- history,
renderHook: hook => rtlRenderHook(hook, {wrapper}),
};
};
diff --git a/src/web/utils/theme.jsx b/src/web/utils/theme.jsx
index d6f31fdaf9..a03c73b7a6 100644
--- a/src/web/utils/theme.jsx
+++ b/src/web/utils/theme.jsx
@@ -28,6 +28,11 @@ const Theme = {
darkRed: '#c12c30', // used by: dialog errors font
errorRed: '#c83814', // used by: progressbar
+ complianceYes: '#4cb045',
+ complianceNo: '#D80000',
+ complianceIncomplete: 'orange',
+ complianceUndefined: 'silver',
+
lightBlue: '#d6e6fd', // used by InfoPanel and dashboard hovering
mediumBlue: '#77acf7', // used by active/hovered items in Select
blue: '#0a53b8', // used by: links
diff --git a/src/web/utils/withRouter.jsx b/src/web/utils/withRouter.jsx
new file mode 100644
index 0000000000..fc5d45aa49
--- /dev/null
+++ b/src/web/utils/withRouter.jsx
@@ -0,0 +1,32 @@
+/* SPDX-FileCopyrightText: 2024 Greenbone AG
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import {
+ useLocation,
+ useNavigate,
+ useParams,
+ useSearchParams,
+} from 'react-router-dom';
+
+export const withRouter = Component => {
+ function ComponentWithRouterProp(props) {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const params = useParams();
+ const [searchParams] = useSearchParams();
+
+ return (
+
+ );
+ }
+
+ return ComponentWithRouterProp;
+};