foreman_rh_cloud 3.0.19 → 3.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +17 -2
- data/app/controllers/insights_cloud/hits_controller.rb +37 -0
- data/app/controllers/insights_cloud/settings_controller.rb +1 -1
- data/app/controllers/insights_cloud/tasks_controller.rb +1 -2
- data/app/models/inventory_sync/inventory_status.rb +6 -0
- data/app/models/setting/rh_cloud.rb +5 -5
- data/app/services/foreman_rh_cloud/remediations_retriever.rb +78 -0
- data/app/services/foreman_rh_cloud/template_renderer_helper.rb +22 -0
- data/app/views/job_templates/rh_cloud_remediations.erb +14 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20210404000001_change_resolutions.foreman_rh_cloud.rb +10 -0
- data/db/seeds.d/50_job_templates.rb +14 -0
- data/lib/foreman_rh_cloud/engine.rb +13 -1
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/insights_cloud.rb +12 -0
- data/lib/insights_cloud/async/insights_full_sync.rb +16 -6
- data/lib/insights_cloud/async/insights_resolutions_sync.rb +69 -0
- data/lib/insights_cloud/async/insights_rules_sync.rb +13 -17
- data/lib/insights_cloud/async/insights_scheduled_sync.rb +1 -1
- data/lib/tasks/rh_cloud_inventory.rake +1 -1
- data/package.json +1 -1
- data/test/factories/insights_factories.rb +22 -0
- data/test/jobs/insights_full_sync_test.rb +12 -8
- data/test/jobs/insights_resolutions_sync_test.rb +74 -0
- data/test/jobs/insights_rules_sync_test.rb +5 -3
- data/test/jobs/inventory_full_sync_test.rb +1 -1
- data/test/unit/services/foreman_rh_cloud/remediations_retriever_test.rb +49 -0
- data/test/unit/services/foreman_rh_cloud/template_renderer_helper_test.rb +28 -0
- data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/AdvancedSettingsConstants.js +5 -3
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/PageDescription/PageDescription.js +15 -2
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/PageDescription/__tests__/__snapshots__/PageDescription.test.js.snap +13 -2
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +1 -1
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableSelectors.js +3 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediateButton.js +59 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationActions.js +12 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +43 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +101 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.scss +9 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModalFooter.js +43 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationTableConstants.js +38 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/Resolutions.js +55 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/index.js +34 -0
- data/webpack/InsightsCloudSync/InsightsCloudSync.js +8 -3
- data/webpack/InsightsCloudSync/InsightsCloudSync.scss +5 -0
- data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +9 -6
- data/webpack/{InsightsCloudSync/Components/InsightsTable/components → common/table}/EmptyState.js +0 -0
- data/webpack/common/table/helpers.js +7 -0
- metadata +33 -9
|
@@ -6,7 +6,7 @@ import { Table, TableHeader, TableBody } from '@patternfly/react-table';
|
|
|
6
6
|
import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
|
|
7
7
|
import SelectAllAlert from './SelectAllAlert';
|
|
8
8
|
import { columns } from './InsightsTableConstants';
|
|
9
|
-
import TableEmptyState from '
|
|
9
|
+
import TableEmptyState from '../../../common/table/EmptyState';
|
|
10
10
|
import {
|
|
11
11
|
modifySelectedRows,
|
|
12
12
|
getSortColumnIndex,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { isEmpty } from 'lodash';
|
|
4
|
+
import { Button, Popover } from '@patternfly/react-core';
|
|
5
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
6
|
+
import { foremanUrl } from '../../../ForemanRhCloudHelpers';
|
|
7
|
+
|
|
8
|
+
const RemediateButton = ({ isExperimentalMode, selectedIds, toggleModal }) => {
|
|
9
|
+
const [isVisible, setVisible] = React.useState(true);
|
|
10
|
+
|
|
11
|
+
const popoverContent = __(
|
|
12
|
+
`To use this feature, please enable <a href=${foremanUrl(
|
|
13
|
+
'/settings?search=name+%3D+lab_features'
|
|
14
|
+
)}>Show Experimental Labs</a> in settings.`
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
let button = (
|
|
18
|
+
<Button
|
|
19
|
+
variant="primary"
|
|
20
|
+
isDisabled={isEmpty(selectedIds)}
|
|
21
|
+
onClick={() => {
|
|
22
|
+
if (!isExperimentalMode) {
|
|
23
|
+
setVisible(value => !value);
|
|
24
|
+
} else {
|
|
25
|
+
toggleModal();
|
|
26
|
+
}
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
{__('Remediate')}
|
|
30
|
+
</Button>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (!isExperimentalMode) {
|
|
34
|
+
button = (
|
|
35
|
+
<Popover
|
|
36
|
+
isVisible={isVisible}
|
|
37
|
+
bodyContent={
|
|
38
|
+
<div dangerouslySetInnerHTML={{ __html: popoverContent }} />
|
|
39
|
+
}
|
|
40
|
+
>
|
|
41
|
+
{button}
|
|
42
|
+
</Popover>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return button;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
RemediateButton.propTypes = {
|
|
49
|
+
selectedIds: PropTypes.object,
|
|
50
|
+
isExperimentalMode: PropTypes.bool,
|
|
51
|
+
toggleModal: PropTypes.func.isRequired,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
RemediateButton.defaultProps = {
|
|
55
|
+
selectedIds: {},
|
|
56
|
+
isExperimentalMode: false,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default RemediateButton;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { get } from 'foremanReact/redux/API';
|
|
2
|
+
import {
|
|
3
|
+
REMEDIATIONS_API_KEY,
|
|
4
|
+
REMEDIATIONS_PATH,
|
|
5
|
+
} from './RemediationTableConstants';
|
|
6
|
+
|
|
7
|
+
export const fetchRemediations = ({ selectedIds, isAllSelected, query }) =>
|
|
8
|
+
get({
|
|
9
|
+
key: REMEDIATIONS_API_KEY,
|
|
10
|
+
url: REMEDIATIONS_PATH,
|
|
11
|
+
params: { ids: Object.keys(selectedIds), isAllSelected, query },
|
|
12
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { orderBy } from 'lodash';
|
|
4
|
+
import Resolutions from './Resolutions';
|
|
5
|
+
|
|
6
|
+
export const modifyRows = (remediations, setResolutions, setHostsIds) => {
|
|
7
|
+
if (remediations.length === 0) return [];
|
|
8
|
+
|
|
9
|
+
const resolutionToSubmit = [];
|
|
10
|
+
const hostsIdsToSubmit = new Set();
|
|
11
|
+
const modifiedRemediations = orderBy(
|
|
12
|
+
remediations.asMutable(),
|
|
13
|
+
[r => r.resolutions?.length || 0],
|
|
14
|
+
['desc']
|
|
15
|
+
).map(({ id, host_id, hostname, title, resolutions, reboot }) => {
|
|
16
|
+
hostsIdsToSubmit.add(host_id);
|
|
17
|
+
const selectedResolution = resolutions[0]?.id;
|
|
18
|
+
resolutionToSubmit.push({
|
|
19
|
+
hit_id: id,
|
|
20
|
+
resolution_id: selectedResolution /** defaults to the first resolution if many */,
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
cells: [
|
|
24
|
+
hostname,
|
|
25
|
+
title,
|
|
26
|
+
<div>
|
|
27
|
+
<Resolutions
|
|
28
|
+
hit_id={id}
|
|
29
|
+
resolutions={resolutions}
|
|
30
|
+
setResolutions={setResolutions}
|
|
31
|
+
selectedResolution={selectedResolution}
|
|
32
|
+
/>
|
|
33
|
+
</div>,
|
|
34
|
+
reboot,
|
|
35
|
+
],
|
|
36
|
+
id,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
setResolutions(resolutionToSubmit);
|
|
41
|
+
setHostsIds(Array.from(hostsIdsToSubmit));
|
|
42
|
+
return modifiedRemediations;
|
|
43
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { Table, TableHeader, TableBody } from '@patternfly/react-table';
|
|
5
|
+
import { Modal, ModalVariant } from '@patternfly/react-core';
|
|
6
|
+
import { STATUS } from 'foremanReact/constants';
|
|
7
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
8
|
+
import { columns } from './RemediationTableConstants';
|
|
9
|
+
import { modifyRows } from './RemediationHelpers';
|
|
10
|
+
import ModalFooter from './RemediationModalFooter';
|
|
11
|
+
import TableEmptyState from '../../../common/table/EmptyState';
|
|
12
|
+
import './RemediationModal.scss';
|
|
13
|
+
import RemediateButton from './RemediateButton';
|
|
14
|
+
|
|
15
|
+
const RemediationModal = ({
|
|
16
|
+
selectedIds,
|
|
17
|
+
fetchRemediations,
|
|
18
|
+
remediations,
|
|
19
|
+
status,
|
|
20
|
+
error,
|
|
21
|
+
isAllSelected,
|
|
22
|
+
query,
|
|
23
|
+
isExperimentalMode,
|
|
24
|
+
}) => {
|
|
25
|
+
const [rows, setRows] = React.useState([]);
|
|
26
|
+
const [open, setOpen] = React.useState(false);
|
|
27
|
+
const [resolutions, setResolutions] = React.useState([]);
|
|
28
|
+
const [hostsIds, setHostsIds] = React.useState([]);
|
|
29
|
+
const toggleModal = () => setOpen(prevValue => !prevValue);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (open) fetchRemediations({ selectedIds, isAllSelected, query });
|
|
33
|
+
}, [open]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const modifiedRows =
|
|
37
|
+
status === STATUS.PENDING
|
|
38
|
+
? []
|
|
39
|
+
: modifyRows(remediations, setResolutions, setHostsIds);
|
|
40
|
+
setRows(modifiedRows);
|
|
41
|
+
}, [remediations, status]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<React.Fragment>
|
|
45
|
+
<RemediateButton
|
|
46
|
+
isExperimentalMode={isExperimentalMode}
|
|
47
|
+
selectedIds={selectedIds}
|
|
48
|
+
toggleModal={toggleModal}
|
|
49
|
+
/>{' '}
|
|
50
|
+
<Modal
|
|
51
|
+
id="remediation-modal"
|
|
52
|
+
appendTo={document.body}
|
|
53
|
+
variant={ModalVariant.large}
|
|
54
|
+
title={__('Remediation summary')}
|
|
55
|
+
isOpen={open}
|
|
56
|
+
onClose={toggleModal}
|
|
57
|
+
footer={
|
|
58
|
+
<ModalFooter
|
|
59
|
+
toggleModal={toggleModal}
|
|
60
|
+
resolutions={resolutions}
|
|
61
|
+
hostsIds={hostsIds}
|
|
62
|
+
/>
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
<Table
|
|
66
|
+
className="remediations-table"
|
|
67
|
+
aria-label="remediations Table"
|
|
68
|
+
cells={columns}
|
|
69
|
+
rows={rows}
|
|
70
|
+
>
|
|
71
|
+
<TableHeader />
|
|
72
|
+
<TableBody />
|
|
73
|
+
</Table>
|
|
74
|
+
<TableEmptyState status={status} error={error} />
|
|
75
|
+
</Modal>
|
|
76
|
+
</React.Fragment>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
RemediationModal.propTypes = {
|
|
81
|
+
selectedIds: PropTypes.object,
|
|
82
|
+
fetchRemediations: PropTypes.func.isRequired,
|
|
83
|
+
remediations: PropTypes.array,
|
|
84
|
+
status: PropTypes.string,
|
|
85
|
+
error: PropTypes.string,
|
|
86
|
+
isAllSelected: PropTypes.bool,
|
|
87
|
+
query: PropTypes.string,
|
|
88
|
+
isExperimentalMode: PropTypes.bool,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
RemediationModal.defaultProps = {
|
|
92
|
+
selectedIds: {},
|
|
93
|
+
remediations: [],
|
|
94
|
+
status: null,
|
|
95
|
+
error: null,
|
|
96
|
+
isAllSelected: false,
|
|
97
|
+
query: null,
|
|
98
|
+
isExperimentalMode: false,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default RemediationModal;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Button } from '@patternfly/react-core';
|
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
5
|
+
import { JOB_INVOCATION_PATH } from './RemediationTableConstants';
|
|
6
|
+
|
|
7
|
+
const ModalFooter = ({ toggleModal, resolutions, hostsIds }) => {
|
|
8
|
+
let token = document.querySelector('meta[name="csrf-token"]');
|
|
9
|
+
token = token?.content || '';
|
|
10
|
+
return (
|
|
11
|
+
<form action={JOB_INVOCATION_PATH} method="post">
|
|
12
|
+
<Button type="submit" key="confirm" variant="primary">
|
|
13
|
+
{__('Remediate')}
|
|
14
|
+
</Button>
|
|
15
|
+
<Button key="cancel" variant="link" onClick={toggleModal}>
|
|
16
|
+
{__('Cancel')}
|
|
17
|
+
</Button>
|
|
18
|
+
<input type="hidden" name="feature" value="rh_cloud_remediate_hosts" />
|
|
19
|
+
<input type="hidden" name="authenticity_token" value={token} />
|
|
20
|
+
<input
|
|
21
|
+
type="hidden"
|
|
22
|
+
name="inputs[hit_remediation_pairs]"
|
|
23
|
+
value={JSON.stringify(resolutions)}
|
|
24
|
+
/>
|
|
25
|
+
{hostsIds.map(id => (
|
|
26
|
+
<input type="hidden" name="host_ids[]" key={id} value={id} />
|
|
27
|
+
))}
|
|
28
|
+
</form>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
ModalFooter.propTypes = {
|
|
33
|
+
toggleModal: PropTypes.func.isRequired,
|
|
34
|
+
resolutions: PropTypes.array,
|
|
35
|
+
hostsIds: PropTypes.array,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
ModalFooter.defaultProps = {
|
|
39
|
+
resolutions: [],
|
|
40
|
+
hostsIds: [],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default ModalFooter;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cellWidth } from '@patternfly/react-table';
|
|
3
|
+
import { CheckCircleIcon } from '@patternfly/react-icons';
|
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
5
|
+
import { foremanUrl } from '../../../ForemanRhCloudHelpers';
|
|
6
|
+
|
|
7
|
+
export const rebootFormatter = ({ title: reboot }) => ({
|
|
8
|
+
children: reboot ? <CheckCircleIcon color="green" /> : __('No'),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const columns = [
|
|
12
|
+
{
|
|
13
|
+
sortKey: 'hostname',
|
|
14
|
+
title: __('Hostname'),
|
|
15
|
+
transforms: [cellWidth(20)],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: __('Recommendation'),
|
|
19
|
+
transforms: [cellWidth(35)],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
title: __('Resolution'),
|
|
23
|
+
transforms: [cellWidth(30)],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: __('Reboot Required'),
|
|
27
|
+
transforms: [cellWidth(15)],
|
|
28
|
+
cellTransforms: [rebootFormatter],
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const REMEDIATIONS_PATH = foremanUrl('/insights_cloud/hits/resolutions');
|
|
33
|
+
|
|
34
|
+
export const JOB_INVOCATION_PATH = foremanUrl('/job_invocations/new');
|
|
35
|
+
|
|
36
|
+
export const REMEDIATIONS_API_KEY = 'INSIGHTS_REMEDIATIONS';
|
|
37
|
+
|
|
38
|
+
export const SUBMIT_RESOLUTIONS = 'SUBMIT_INSIGHTS_RESOLUTIONS';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { Radio } from '@patternfly/react-core';
|
|
5
|
+
|
|
6
|
+
const Resolutions = ({
|
|
7
|
+
resolutions,
|
|
8
|
+
setResolutions,
|
|
9
|
+
selectedResolution,
|
|
10
|
+
hit_id,
|
|
11
|
+
}) => {
|
|
12
|
+
const [checkedID, setCheckedID] = React.useState(selectedResolution);
|
|
13
|
+
|
|
14
|
+
if (resolutions.length === 1) return <>{resolutions[0].description}</>;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
{resolutions.map(({ id: resolution_id, description }) => (
|
|
19
|
+
<Radio
|
|
20
|
+
key={resolution_id}
|
|
21
|
+
className="resolution-radio"
|
|
22
|
+
id={resolution_id}
|
|
23
|
+
isChecked={resolution_id === checkedID}
|
|
24
|
+
onChange={() =>
|
|
25
|
+
setResolutions(stateRes =>
|
|
26
|
+
stateRes.map(res => {
|
|
27
|
+
if (hit_id === res.hit_id) {
|
|
28
|
+
setCheckedID(resolution_id);
|
|
29
|
+
return { ...res, resolution_id };
|
|
30
|
+
}
|
|
31
|
+
return res;
|
|
32
|
+
})
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
label={description}
|
|
36
|
+
/>
|
|
37
|
+
))}
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
Resolutions.propTypes = {
|
|
43
|
+
setResolutions: PropTypes.func.isRequired,
|
|
44
|
+
resolutions: PropTypes.array,
|
|
45
|
+
hit_id: PropTypes.number,
|
|
46
|
+
selectedResolution: PropTypes.number,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
Resolutions.defaultProps = {
|
|
50
|
+
resolutions: [],
|
|
51
|
+
hit_id: null,
|
|
52
|
+
selectedResolution: null,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default Resolutions;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { bindActionCreators } from 'redux';
|
|
2
|
+
import { connect } from 'react-redux';
|
|
3
|
+
import {
|
|
4
|
+
selectAPIResponse,
|
|
5
|
+
selectAPIStatus,
|
|
6
|
+
selectAPIErrorMessage,
|
|
7
|
+
} from 'foremanReact/redux/API/APISelectors';
|
|
8
|
+
import * as actions from './RemediationActions';
|
|
9
|
+
import RemediationModal from './RemediationModal';
|
|
10
|
+
import { REMEDIATIONS_API_KEY } from './RemediationTableConstants';
|
|
11
|
+
import {
|
|
12
|
+
selectExperimental,
|
|
13
|
+
selectIsAllSelected,
|
|
14
|
+
selectSearch,
|
|
15
|
+
selectSelectedIds,
|
|
16
|
+
} from '../InsightsTable/InsightsTableSelectors';
|
|
17
|
+
|
|
18
|
+
// map state to props
|
|
19
|
+
const mapStateToProps = state => ({
|
|
20
|
+
selectedIds: selectSelectedIds(state),
|
|
21
|
+
remediations: selectAPIResponse(state, REMEDIATIONS_API_KEY).hits || [],
|
|
22
|
+
status: selectAPIStatus(state, REMEDIATIONS_API_KEY),
|
|
23
|
+
error: selectAPIErrorMessage(state, REMEDIATIONS_API_KEY),
|
|
24
|
+
itemCount: selectAPIResponse(state, REMEDIATIONS_API_KEY).itemCount || 0,
|
|
25
|
+
isAllSelected: selectIsAllSelected(state),
|
|
26
|
+
query: selectSearch(state),
|
|
27
|
+
isExperimentalMode: selectExperimental(state),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// map action dispatchers to props
|
|
31
|
+
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
|
|
32
|
+
|
|
33
|
+
// export connected component
|
|
34
|
+
export default connect(mapStateToProps, mapDispatchToProps)(RemediationModal);
|
|
@@ -6,10 +6,12 @@ import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
|
|
6
6
|
import InsightsHeader from './Components/InsightsHeader';
|
|
7
7
|
import { NoTokenEmptyState } from './Components/NoTokenEmptyState';
|
|
8
8
|
import InsightsTable from './Components/InsightsTable';
|
|
9
|
+
import RemediationModal from './Components/RemediationModal';
|
|
9
10
|
import {
|
|
10
11
|
INSIGHTS_SYNC_PAGE_TITLE,
|
|
11
12
|
INSIGHTS_SEARCH_PROPS,
|
|
12
13
|
} from './InsightsCloudSyncConstants';
|
|
14
|
+
import './InsightsCloudSync.scss';
|
|
13
15
|
|
|
14
16
|
const InsightsCloudSync = ({
|
|
15
17
|
syncInsights,
|
|
@@ -32,9 +34,12 @@ const InsightsCloudSync = ({
|
|
|
32
34
|
onSearch={nextQuery => fetchInsights({ query: nextQuery, page: 1 })}
|
|
33
35
|
header={INSIGHTS_SYNC_PAGE_TITLE}
|
|
34
36
|
toolbarButtons={
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
<>
|
|
38
|
+
<RemediationModal />
|
|
39
|
+
<Button variant="secondary" onClick={syncInsights}>
|
|
40
|
+
{__('Start recommendations sync')}
|
|
41
|
+
</Button>
|
|
42
|
+
</>
|
|
38
43
|
}
|
|
39
44
|
searchQuery={query}
|
|
40
45
|
beforeToolbarComponent={<InsightsHeader />}
|