foreman_openscap 7.1.0 → 8.0.0

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_openscap/locale/cs_CZ/foreman_openscap.js +517 -421
  3. data/app/assets/javascripts/foreman_openscap/locale/de/foreman_openscap.js +909 -813
  4. data/app/assets/javascripts/foreman_openscap/locale/en/foreman_openscap.js +517 -421
  5. data/app/assets/javascripts/foreman_openscap/locale/en_GB/foreman_openscap.js +549 -453
  6. data/app/assets/javascripts/foreman_openscap/locale/es/foreman_openscap.js +917 -821
  7. data/app/assets/javascripts/foreman_openscap/locale/fr/foreman_openscap.js +917 -821
  8. data/app/assets/javascripts/foreman_openscap/locale/gl/foreman_openscap.js +557 -461
  9. data/app/assets/javascripts/foreman_openscap/locale/it/foreman_openscap.js +603 -507
  10. data/app/assets/javascripts/foreman_openscap/locale/ja/foreman_openscap.js +908 -812
  11. data/app/assets/javascripts/foreman_openscap/locale/ka/foreman_openscap.js +1398 -0
  12. data/app/assets/javascripts/foreman_openscap/locale/ko/foreman_openscap.js +705 -609
  13. data/app/assets/javascripts/foreman_openscap/locale/pt_BR/foreman_openscap.js +917 -821
  14. data/app/assets/javascripts/foreman_openscap/locale/ru/foreman_openscap.js +707 -611
  15. data/app/assets/javascripts/foreman_openscap/locale/sv_SE/foreman_openscap.js +557 -461
  16. data/app/assets/javascripts/foreman_openscap/locale/zh_CN/foreman_openscap.js +908 -812
  17. data/app/assets/javascripts/foreman_openscap/locale/zh_TW/foreman_openscap.js +705 -609
  18. data/app/assets/javascripts/foreman_openscap/reports.js +5 -0
  19. data/app/controllers/arf_reports_controller.rb +23 -2
  20. data/app/helpers/arf_reports_helper.rb +17 -4
  21. data/app/models/concerns/foreman_openscap/host_extensions.rb +2 -0
  22. data/app/models/foreman_openscap/arf_report.rb +11 -2
  23. data/app/views/arf_reports/_output.html.erb +5 -1
  24. data/app/views/job_templates/run_openscap_remediation_-_ansible_default.erb +27 -0
  25. data/app/views/job_templates/run_openscap_remediation_-_script_default.erb +24 -0
  26. data/config/routes.rb +1 -0
  27. data/db/migrate/20230912122310_add_fixes_to_message.rb +5 -0
  28. data/lib/foreman_openscap/engine.rb +18 -2
  29. data/lib/foreman_openscap/version.rb +1 -1
  30. data/lib/tasks/foreman_openscap_tasks.rake +5 -16
  31. data/locale/Makefile +3 -3
  32. data/locale/cs_CZ/foreman_openscap.edit.po +144 -16
  33. data/locale/cs_CZ/foreman_openscap.po +97 -1
  34. data/locale/de/LC_MESSAGES/foreman_openscap.mo +0 -0
  35. data/locale/de/foreman_openscap.edit.po +145 -17
  36. data/locale/de/foreman_openscap.po +98 -2
  37. data/locale/en/foreman_openscap.edit.po +144 -16
  38. data/locale/en/foreman_openscap.po +97 -1
  39. data/locale/en_GB/foreman_openscap.edit.po +144 -16
  40. data/locale/en_GB/foreman_openscap.po +97 -1
  41. data/locale/es/LC_MESSAGES/foreman_openscap.mo +0 -0
  42. data/locale/es/foreman_openscap.edit.po +145 -17
  43. data/locale/es/foreman_openscap.po +98 -2
  44. data/locale/foreman_openscap.pot +168 -16
  45. data/locale/fr/LC_MESSAGES/foreman_openscap.mo +0 -0
  46. data/locale/fr/foreman_openscap.edit.po +145 -17
  47. data/locale/fr/foreman_openscap.po +98 -2
  48. data/locale/gl/foreman_openscap.edit.po +144 -16
  49. data/locale/gl/foreman_openscap.po +97 -1
  50. data/locale/it/foreman_openscap.edit.po +144 -16
  51. data/locale/it/foreman_openscap.po +97 -1
  52. data/locale/ja/LC_MESSAGES/foreman_openscap.mo +0 -0
  53. data/locale/ja/foreman_openscap.edit.po +145 -17
  54. data/locale/ja/foreman_openscap.po +98 -2
  55. data/locale/ka/LC_MESSAGES/foreman_openscap.mo +0 -0
  56. data/locale/ka/foreman_openscap.edit.po +1863 -0
  57. data/locale/ka/foreman_openscap.po +1405 -0
  58. data/locale/ka/foreman_openscap.po.time_stamp +0 -0
  59. data/locale/ko/LC_MESSAGES/foreman_openscap.mo +0 -0
  60. data/locale/ko/foreman_openscap.edit.po +145 -17
  61. data/locale/ko/foreman_openscap.po +98 -2
  62. data/locale/pt_BR/LC_MESSAGES/foreman_openscap.mo +0 -0
  63. data/locale/pt_BR/foreman_openscap.edit.po +145 -17
  64. data/locale/pt_BR/foreman_openscap.po +98 -2
  65. data/locale/ru/LC_MESSAGES/foreman_openscap.mo +0 -0
  66. data/locale/ru/foreman_openscap.edit.po +145 -17
  67. data/locale/ru/foreman_openscap.po +98 -2
  68. data/locale/sv_SE/foreman_openscap.edit.po +144 -16
  69. data/locale/sv_SE/foreman_openscap.po +97 -1
  70. data/locale/zh_CN/LC_MESSAGES/foreman_openscap.mo +0 -0
  71. data/locale/zh_CN/foreman_openscap.edit.po +145 -17
  72. data/locale/zh_CN/foreman_openscap.po +98 -2
  73. data/locale/zh_TW/LC_MESSAGES/foreman_openscap.mo +0 -0
  74. data/locale/zh_TW/foreman_openscap.edit.po +145 -17
  75. data/locale/zh_TW/foreman_openscap.po +98 -2
  76. data/webpack/components/EmptyState.js +6 -2
  77. data/webpack/components/OpenscapRemediationWizard/OpenscapRemediationSelectors.js +16 -0
  78. data/webpack/components/OpenscapRemediationWizard/OpenscapRemediationWizardContext.js +4 -0
  79. data/webpack/components/OpenscapRemediationWizard/ViewSelectedHostsLink.js +38 -0
  80. data/webpack/components/OpenscapRemediationWizard/WizardHeader.js +43 -0
  81. data/webpack/components/OpenscapRemediationWizard/constants.js +14 -0
  82. data/webpack/components/OpenscapRemediationWizard/helpers.js +33 -0
  83. data/webpack/components/OpenscapRemediationWizard/index.js +160 -0
  84. data/webpack/components/OpenscapRemediationWizard/steps/Finish.js +131 -0
  85. data/webpack/components/OpenscapRemediationWizard/steps/ReviewHosts.js +217 -0
  86. data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.js +176 -0
  87. data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.scss +4 -0
  88. data/webpack/components/OpenscapRemediationWizard/steps/SnippetSelect.js +160 -0
  89. data/webpack/components/OpenscapRemediationWizard/steps/index.js +4 -0
  90. data/webpack/index.js +8 -3
  91. data/webpack/testHelper.js +6 -5
  92. metadata +24 -3
@@ -0,0 +1,160 @@
1
+ import React, { useState, useRef } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+ import { Button, Wizard } from '@patternfly/react-core';
5
+
6
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
7
+ import { API_OPERATIONS, get } from 'foremanReact/redux/API';
8
+
9
+ import OpenscapRemediationWizardContext from './OpenscapRemediationWizardContext';
10
+ import {
11
+ selectLogResponse,
12
+ selectLogError,
13
+ selectLogStatus,
14
+ } from './OpenscapRemediationSelectors';
15
+ import { REPORT_LOG_REQUEST_KEY, FAIL_RULE_SEARCH } from './constants';
16
+ import { SnippetSelect, ReviewHosts, ReviewRemediation, Finish } from './steps';
17
+
18
+ const OpenscapRemediationWizard = ({
19
+ report_id: reportId,
20
+ host: { id: hostId, name: hostName },
21
+ supported_remediation_snippets: supportedJobSnippets,
22
+ }) => {
23
+ const dispatch = useDispatch();
24
+ const log = useSelector(state => selectLogResponse(state))?.log;
25
+ const logStatus = useSelector(state => selectLogStatus(state));
26
+ const logError = useSelector(state => selectLogError(state));
27
+
28
+ const fixes = JSON.parse(log?.message?.fixes || null) || [];
29
+ const source = log?.source?.value || '';
30
+ const title = log?.message?.value || '';
31
+ const defaultHostIdsParam = `id ^ (${hostId})`;
32
+ const defaultFailedHostsSearch = `${FAIL_RULE_SEARCH} = ${source}`;
33
+
34
+ const [isRemediationWizardOpen, setIsRemediationWizardOpen] = useState(false);
35
+ const [snippet, setSnippet] = useState('');
36
+ const [method, setMethod] = useState('job');
37
+ const [hostIdsParam, setHostIdsParam] = useState(defaultHostIdsParam);
38
+ const [isRebootSelected, setIsRebootSelected] = useState(false);
39
+ const [isAllHostsSelected, setIsAllHostsSelected] = useState(false);
40
+
41
+ const savedHostSelectionsRef = useRef({});
42
+
43
+ const onModalButtonClick = e => {
44
+ e.preventDefault();
45
+ const logId = e.target.getAttribute('data-log-id');
46
+ dispatch(
47
+ get({
48
+ type: API_OPERATIONS.GET,
49
+ key: REPORT_LOG_REQUEST_KEY,
50
+ url: `/compliance/arf_reports/${reportId}/show_log`,
51
+ params: { log_id: logId },
52
+ })
53
+ );
54
+ setIsRemediationWizardOpen(true);
55
+ };
56
+
57
+ const onWizardClose = () => {
58
+ // Reset to defaults
59
+ setHostIdsParam(defaultHostIdsParam);
60
+ setSnippet('');
61
+ setMethod('job');
62
+ setIsRebootSelected(false);
63
+ setIsRemediationWizardOpen(false);
64
+ savedHostSelectionsRef.current = {};
65
+ };
66
+
67
+ const reviewHostsStep = {
68
+ id: 2,
69
+ name: __('Review hosts'),
70
+ component: <ReviewHosts />,
71
+ canJumpTo: Boolean(snippet && method === 'job'),
72
+ enableNext: Boolean(snippet && method),
73
+ };
74
+ const steps = [
75
+ {
76
+ id: 1,
77
+ name: __('Select snippet'),
78
+ component: <SnippetSelect />,
79
+ canJumpTo: true,
80
+ enableNext: Boolean(snippet && method),
81
+ },
82
+ ...(snippet && method === 'job' ? [reviewHostsStep] : []),
83
+ {
84
+ id: 3,
85
+ name: __('Review remediation'),
86
+ component: <ReviewRemediation />,
87
+ canJumpTo: Boolean(snippet && method),
88
+ enableNext: method === 'job',
89
+ nextButtonText: __('Run'),
90
+ },
91
+ {
92
+ id: 4,
93
+ name: __('Done'),
94
+ component: <Finish onClose={onWizardClose} />,
95
+ isFinishedStep: true,
96
+ },
97
+ ];
98
+
99
+ return (
100
+ <>
101
+ {isRemediationWizardOpen && (
102
+ <OpenscapRemediationWizardContext.Provider
103
+ value={{
104
+ fixes,
105
+ snippet,
106
+ setSnippet,
107
+ method,
108
+ setMethod,
109
+ hostName,
110
+ source,
111
+ logStatus,
112
+ logError,
113
+ supportedJobSnippets,
114
+ isRebootSelected,
115
+ setIsRebootSelected,
116
+ hostId,
117
+ hostIdsParam,
118
+ setHostIdsParam,
119
+ isAllHostsSelected,
120
+ setIsAllHostsSelected,
121
+ defaultFailedHostsSearch,
122
+ savedHostSelectionsRef,
123
+ }}
124
+ >
125
+ <Wizard
126
+ title={title}
127
+ description={sprintf(__('Remediate %s rule'), source)}
128
+ isOpen={isRemediationWizardOpen}
129
+ steps={steps}
130
+ onClose={onWizardClose}
131
+ />
132
+ </OpenscapRemediationWizardContext.Provider>
133
+ )}
134
+ <Button
135
+ id="openscapRemediationWizardButton"
136
+ variant="link"
137
+ isInline
138
+ component="span"
139
+ onClick={e => onModalButtonClick(e)}
140
+ />
141
+ </>
142
+ );
143
+ };
144
+
145
+ OpenscapRemediationWizard.propTypes = {
146
+ report_id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
147
+ host: PropTypes.shape({
148
+ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
149
+ name: PropTypes.string,
150
+ }),
151
+ supported_remediation_snippets: PropTypes.array,
152
+ };
153
+
154
+ OpenscapRemediationWizard.defaultProps = {
155
+ report_id: '',
156
+ host: {},
157
+ supported_remediation_snippets: [],
158
+ };
159
+
160
+ export default OpenscapRemediationWizard;
@@ -0,0 +1,131 @@
1
+ /* eslint-disable camelcase */
2
+ import React, { useContext } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import { Button, Bullseye } from '@patternfly/react-core';
5
+ import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
6
+
7
+ import { translate as __ } from 'foremanReact/common/I18n';
8
+ import { foremanUrl } from 'foremanReact/common/helpers';
9
+ import { STATUS } from 'foremanReact/constants';
10
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
11
+ import Loading from 'foremanReact/components/Loading';
12
+ import PermissionDenied from 'foremanReact/components/PermissionDenied';
13
+
14
+ import OpenscapRemediationWizardContext from '../OpenscapRemediationWizardContext';
15
+ import EmptyState from '../../EmptyState';
16
+ import ViewSelectedHostsLink from '../ViewSelectedHostsLink';
17
+ import { errorMsg, findFixBySnippet } from '../helpers';
18
+
19
+ import {
20
+ JOB_INVOCATION_PATH,
21
+ JOB_INVOCATION_API_PATH,
22
+ JOB_INVOCATION_API_REQUEST_KEY,
23
+ SNIPPET_SH,
24
+ SNIPPET_ANSIBLE,
25
+ } from '../constants';
26
+
27
+ const Finish = ({ onClose }) => {
28
+ const {
29
+ fixes,
30
+ snippet,
31
+ isRebootSelected,
32
+ hostIdsParam,
33
+ isAllHostsSelected,
34
+ defaultFailedHostsSearch,
35
+ } = useContext(OpenscapRemediationWizardContext);
36
+
37
+ const snippetText = findFixBySnippet(fixes, snippet)?.full_text;
38
+
39
+ const remediationJobParams = () => {
40
+ let feature = 'script_run_openscap_remediation';
41
+ const inputs = {};
42
+ switch (snippet) {
43
+ case SNIPPET_ANSIBLE:
44
+ feature = 'ansible_run_openscap_remediation';
45
+ inputs.tasks = snippetText;
46
+ inputs.reboot = isRebootSelected;
47
+ break;
48
+ case SNIPPET_SH:
49
+ default:
50
+ feature = 'script_run_openscap_remediation';
51
+ inputs.command = snippetText;
52
+ inputs.reboot = isRebootSelected;
53
+ }
54
+
55
+ return {
56
+ job_invocation: {
57
+ feature,
58
+ inputs,
59
+ search_query: isAllHostsSelected
60
+ ? defaultFailedHostsSearch
61
+ : hostIdsParam,
62
+ },
63
+ };
64
+ };
65
+
66
+ const response = useAPI('post', JOB_INVOCATION_API_PATH, {
67
+ key: JOB_INVOCATION_API_REQUEST_KEY,
68
+ params: remediationJobParams(),
69
+ });
70
+ const {
71
+ response: { response: { status: statusCode, data } = {} },
72
+ status = STATUS.PENDING,
73
+ } = response;
74
+
75
+ const linkToJob = (
76
+ <Button
77
+ variant="link"
78
+ icon={<ExternalLinkSquareAltIcon />}
79
+ iconPosition="right"
80
+ target="_blank"
81
+ component="a"
82
+ href={foremanUrl(`${JOB_INVOCATION_PATH}/${response?.response?.id}`)}
83
+ >
84
+ {__('Job details')}
85
+ </Button>
86
+ );
87
+ const closeBtn = <Button onClick={onClose}>{__('Close')}</Button>;
88
+ const errorComponent =
89
+ statusCode === 403 ? (
90
+ <PermissionDenied
91
+ missingPermissions={data?.error?.missing_permissions}
92
+ primaryButton={closeBtn}
93
+ />
94
+ ) : (
95
+ <EmptyState
96
+ error
97
+ title={__('Error!')}
98
+ body={errorMsg(data)}
99
+ primaryButton={closeBtn}
100
+ />
101
+ );
102
+ const body =
103
+ status === STATUS.RESOLVED ? (
104
+ <EmptyState
105
+ title={__(
106
+ 'The job has started on selected host(s), you can check the status on the job details page.'
107
+ )}
108
+ body={
109
+ <>
110
+ {linkToJob}
111
+ <ViewSelectedHostsLink
112
+ isAllHostsSelected={isAllHostsSelected}
113
+ hostIdsParam={hostIdsParam}
114
+ defaultFailedHostsSearch={defaultFailedHostsSearch}
115
+ />
116
+ </>
117
+ }
118
+ primaryButton={closeBtn}
119
+ />
120
+ ) : (
121
+ errorComponent
122
+ );
123
+
124
+ return <Bullseye>{status === STATUS.PENDING ? <Loading /> : body}</Bullseye>;
125
+ };
126
+
127
+ Finish.propTypes = {
128
+ onClose: PropTypes.func.isRequired,
129
+ };
130
+
131
+ export default Finish;
@@ -0,0 +1,217 @@
1
+ import React, { useContext, useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ Spinner,
5
+ Toolbar,
6
+ ToolbarContent,
7
+ ToolbarGroup,
8
+ ToolbarItem,
9
+ } from '@patternfly/react-core';
10
+ import { Td } from '@patternfly/react-table';
11
+ import { toArray } from 'lodash';
12
+
13
+ import { foremanUrl } from 'foremanReact/common/helpers';
14
+ import { translate as __ } from 'foremanReact/common/I18n';
15
+ import SelectAllCheckbox from 'foremanReact/components/PF4/TableIndexPage/Table/SelectAllCheckbox';
16
+ import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table';
17
+ import { useBulkSelect } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
18
+ import { getPageStats } from 'foremanReact/components/PF4/TableIndexPage/Table/helpers';
19
+ import { STATUS } from 'foremanReact/constants';
20
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
21
+
22
+ import OpenscapRemediationWizardContext from '../OpenscapRemediationWizardContext';
23
+ import WizardHeader from '../WizardHeader';
24
+ import { HOSTS_API_PATH, HOSTS_API_REQUEST_KEY } from '../constants';
25
+
26
+ const ReviewHosts = () => {
27
+ const {
28
+ hostId,
29
+ setHostIdsParam,
30
+ defaultFailedHostsSearch,
31
+ setIsAllHostsSelected,
32
+ savedHostSelectionsRef,
33
+ } = useContext(OpenscapRemediationWizardContext);
34
+
35
+ const defaultParams = {
36
+ search: defaultFailedHostsSearch,
37
+ };
38
+ const defaultHostsArry = [hostId];
39
+
40
+ const [params, setParams] = useState(defaultParams);
41
+
42
+ const response = useAPI('get', `${HOSTS_API_PATH}?include_permissions=true`, {
43
+ key: HOSTS_API_REQUEST_KEY,
44
+ params: defaultParams,
45
+ });
46
+ const {
47
+ response: {
48
+ search: apiSearchQuery,
49
+ results,
50
+ per_page: perPage,
51
+ page,
52
+ subtotal,
53
+ message: errorMessage,
54
+ },
55
+ status = STATUS.PENDING,
56
+ setAPIOptions,
57
+ } = response;
58
+
59
+ const subtotalCount = Number(subtotal ?? 0);
60
+
61
+ const setParamsAndAPI = newParams => {
62
+ setParams(newParams);
63
+ setAPIOptions({ key: HOSTS_API_REQUEST_KEY, params: newParams });
64
+ };
65
+
66
+ const { pageRowCount } = getPageStats({
67
+ total: subtotalCount,
68
+ page,
69
+ perPage,
70
+ });
71
+ const { fetchBulkParams, ...selectAllOptions } = useBulkSelect({
72
+ results,
73
+ metadata: { total: subtotalCount, page },
74
+ initialSearchQuery: apiSearchQuery || defaultFailedHostsSearch,
75
+ isSelectable: () => true,
76
+ defaultArry: defaultHostsArry,
77
+ initialArry: toArray(
78
+ savedHostSelectionsRef.current.inclusionSet || defaultHostsArry
79
+ ),
80
+ initialExclusionArry: toArray(
81
+ savedHostSelectionsRef.current.exclusionSet || []
82
+ ),
83
+ initialSelectAllMode: savedHostSelectionsRef.current.selectAllMode || false,
84
+ });
85
+ const {
86
+ selectPage,
87
+ selectedCount,
88
+ selectOne,
89
+ selectNone,
90
+ selectDefault,
91
+ selectAll,
92
+ areAllRowsOnPageSelected,
93
+ areAllRowsSelected,
94
+ isSelected,
95
+ inclusionSet,
96
+ exclusionSet,
97
+ selectAllMode,
98
+ } = selectAllOptions;
99
+
100
+ useEffect(() => {
101
+ if (selectedCount) {
102
+ setHostIdsParam(fetchBulkParams());
103
+ savedHostSelectionsRef.current = {
104
+ inclusionSet,
105
+ exclusionSet,
106
+ selectAllMode,
107
+ };
108
+ }
109
+ }, [selectedCount, fetchBulkParams, setHostIdsParam]);
110
+
111
+ const selectionToolbar = (
112
+ <ToolbarItem key="selectAll">
113
+ <SelectAllCheckbox
114
+ {...{
115
+ selectAll: () => {
116
+ selectAll(true);
117
+ setIsAllHostsSelected(true);
118
+ },
119
+ selectPage: () => {
120
+ selectPage();
121
+ setIsAllHostsSelected(false);
122
+ },
123
+ selectDefault: () => {
124
+ selectDefault();
125
+ setIsAllHostsSelected(false);
126
+ },
127
+ selectNone: () => {
128
+ selectNone();
129
+ setIsAllHostsSelected(false);
130
+ },
131
+ selectedCount,
132
+ pageRowCount,
133
+ }}
134
+ totalCount={subtotalCount}
135
+ selectedDefaultCount={1} // The default host (hostId) is always selected
136
+ areAllRowsOnPageSelected={areAllRowsOnPageSelected()}
137
+ areAllRowsSelected={areAllRowsSelected()}
138
+ />
139
+ </ToolbarItem>
140
+ );
141
+
142
+ const RowSelectTd = ({ rowData }) => (
143
+ <Td
144
+ select={{
145
+ rowIndex: rowData.id,
146
+ onSelect: (_event, isSelecting) => {
147
+ selectOne(isSelecting, rowData.id, rowData);
148
+ // If at least one was unselected, then it's not all selected
149
+ if (!isSelecting) setIsAllHostsSelected(false);
150
+ },
151
+ isSelected: rowData.id === hostId || isSelected(rowData.id),
152
+ disable: rowData.id === hostId || false,
153
+ }}
154
+ />
155
+ );
156
+ RowSelectTd.propTypes = {
157
+ rowData: PropTypes.object.isRequired,
158
+ };
159
+
160
+ const columns = {
161
+ name: {
162
+ title: __('Name'),
163
+ wrapper: ({ id, name }) => <a href={foremanUrl(`hosts/${id}`)}>{name}</a>,
164
+ isSorted: true,
165
+ },
166
+ operatingsystem_name: {
167
+ title: __('OS'),
168
+ },
169
+ };
170
+
171
+ return (
172
+ <>
173
+ <WizardHeader
174
+ title={__('Review hosts')}
175
+ description={__(
176
+ 'By default, remediation is applied to the current host. Optionally, remediate any additional hosts that fail the rule.'
177
+ )}
178
+ />
179
+ <Toolbar ouiaId="table-toolbar" className="table-toolbar">
180
+ <ToolbarContent>
181
+ <ToolbarGroup>
182
+ {selectionToolbar}
183
+ {status === STATUS.PENDING && (
184
+ <ToolbarItem>
185
+ <Spinner size="sm" />
186
+ </ToolbarItem>
187
+ )}
188
+ </ToolbarGroup>
189
+ </ToolbarContent>
190
+ </Toolbar>
191
+ <Table
192
+ ouiaId="hosts-review-table"
193
+ isEmbedded
194
+ params={params}
195
+ setParams={setParamsAndAPI}
196
+ itemCount={subtotalCount}
197
+ results={results}
198
+ url={HOSTS_API_PATH}
199
+ refreshData={() =>
200
+ setAPIOptions({
201
+ key: HOSTS_API_REQUEST_KEY,
202
+ params: { defaultFailedHostsSearch },
203
+ })
204
+ }
205
+ columns={columns}
206
+ errorMessage={
207
+ status === STATUS.ERROR && errorMessage ? errorMessage : null
208
+ }
209
+ isPending={status === STATUS.PENDING}
210
+ showCheckboxes
211
+ rowSelectTd={RowSelectTd}
212
+ />
213
+ </>
214
+ );
215
+ };
216
+
217
+ export default ReviewHosts;
@@ -0,0 +1,176 @@
1
+ /* eslint-disable camelcase */
2
+ import React, { useContext, useState } from 'react';
3
+ import { some } from 'lodash';
4
+ import {
5
+ CodeBlock,
6
+ CodeBlockAction,
7
+ CodeBlockCode,
8
+ ClipboardCopyButton,
9
+ Button,
10
+ Grid,
11
+ GridItem,
12
+ Alert,
13
+ Checkbox,
14
+ } from '@patternfly/react-core';
15
+ import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
16
+
17
+ import { translate as __ } from 'foremanReact/common/I18n';
18
+ import { foremanUrl } from 'foremanReact/common/helpers';
19
+ import { getHostsPageUrl } from 'foremanReact/Root/Context/ForemanContext';
20
+
21
+ import OpenscapRemediationWizardContext from '../OpenscapRemediationWizardContext';
22
+ import WizardHeader from '../WizardHeader';
23
+ import ViewSelectedHostsLink from '../ViewSelectedHostsLink';
24
+ import { HOSTS_PATH, FAIL_RULE_SEARCH } from '../constants';
25
+ import { findFixBySnippet } from '../helpers';
26
+
27
+ import './ReviewRemediation.scss';
28
+
29
+ const ReviewRemediation = () => {
30
+ const {
31
+ fixes,
32
+ snippet,
33
+ method,
34
+ hostName,
35
+ source,
36
+ isRebootSelected,
37
+ setIsRebootSelected,
38
+ isAllHostsSelected,
39
+ hostIdsParam,
40
+ defaultFailedHostsSearch,
41
+ } = useContext(OpenscapRemediationWizardContext);
42
+ const [copied, setCopied] = useState(false);
43
+ const selectedFix = findFixBySnippet(fixes, snippet);
44
+ const snippetText = selectedFix?.full_text;
45
+ // can be one of null, "true", "false"
46
+ // if null, it may indicate that reboot might be needed
47
+ const checkForReboot = () => !some(fixes, { reboot: 'false' });
48
+ const isRebootRequired = () => some(fixes, { reboot: 'true' });
49
+
50
+ const copyToClipboard = (e, text) => {
51
+ navigator.clipboard.writeText(text.toString());
52
+ };
53
+
54
+ const onCopyClick = (e, text) => {
55
+ copyToClipboard(e, text);
56
+ setCopied(true);
57
+ };
58
+
59
+ const description =
60
+ method === 'manual'
61
+ ? __('Review the remediation snippet and apply it to the host manually.')
62
+ : __(
63
+ 'Review the remediation snippet that will be applied to selected host(s).'
64
+ );
65
+
66
+ const rebootAlertTitle = isRebootRequired()
67
+ ? __('A reboot is required after applying remediation.')
68
+ : __('A reboot might be required after applying remediation.');
69
+
70
+ const actions = (
71
+ <React.Fragment>
72
+ <CodeBlockAction>
73
+ <ClipboardCopyButton
74
+ id="basic-copy-button"
75
+ textId="code-content"
76
+ aria-label="Copy to clipboard"
77
+ onClick={e => onCopyClick(e, snippetText)}
78
+ exitDelay={copied ? 1500 : 600}
79
+ maxWidth="110px"
80
+ variant="plain"
81
+ onTooltipHidden={() => setCopied(false)}
82
+ >
83
+ {copied
84
+ ? __('Successfully copied to clipboard!')
85
+ : __('Copy to clipboard')}
86
+ </ClipboardCopyButton>
87
+ </CodeBlockAction>
88
+ </React.Fragment>
89
+ );
90
+
91
+ return (
92
+ <>
93
+ <WizardHeader
94
+ title={__('Review remediation')}
95
+ description={description}
96
+ />
97
+ <Grid hasGutter>
98
+ <br />
99
+ <GridItem>
100
+ <Alert
101
+ ouiaId="review-alert"
102
+ variant="danger"
103
+ title={`${__(
104
+ 'Do not implement any of the recommended remedial actions or scripts without first testing them in a non-production environment.'
105
+ )}
106
+ ${__('Remediation might render the system non-functional.')}`}
107
+ />
108
+ </GridItem>
109
+ <GridItem md={12} span={4} rowSpan={1}>
110
+ <ViewSelectedHostsLink
111
+ isAllHostsSelected={isAllHostsSelected}
112
+ hostIdsParam={hostIdsParam}
113
+ defaultFailedHostsSearch={defaultFailedHostsSearch}
114
+ />
115
+ </GridItem>
116
+ <GridItem md={12} span={4} rowSpan={1}>
117
+ <Button
118
+ variant="link"
119
+ icon={<ExternalLinkSquareAltIcon />}
120
+ iconPosition="right"
121
+ target="_blank"
122
+ component="a"
123
+ href={foremanUrl(`${getHostsPageUrl(true)}/${hostName}`)}
124
+ >
125
+ {hostName}
126
+ </Button>{' '}
127
+ </GridItem>
128
+ <GridItem md={12} span={8} rowSpan={1}>
129
+ <Button
130
+ variant="link"
131
+ icon={<ExternalLinkSquareAltIcon />}
132
+ iconPosition="right"
133
+ target="_blank"
134
+ component="a"
135
+ href={foremanUrl(
136
+ `${HOSTS_PATH}/?search=${FAIL_RULE_SEARCH} = ${source}`
137
+ )}
138
+ >
139
+ {__('Other hosts failing this rule')}
140
+ </Button>
141
+ </GridItem>
142
+ {checkForReboot() ? (
143
+ <>
144
+ <GridItem span={12} rowSpan={1}>
145
+ <Alert
146
+ ouiaId="reboot-alert"
147
+ variant={isRebootRequired() ? 'warning' : 'info'}
148
+ title={rebootAlertTitle}
149
+ />
150
+ </GridItem>
151
+ {method === 'manual' ? null : (
152
+ <GridItem span={4} rowSpan={1}>
153
+ <Checkbox
154
+ id="reboot-checkbox"
155
+ label={__('Reboot the system(s)')}
156
+ name="reboot-checkbox"
157
+ isChecked={isRebootSelected}
158
+ onChange={selected => setIsRebootSelected(selected)}
159
+ />
160
+ </GridItem>
161
+ )}
162
+ </>
163
+ ) : null}
164
+ <GridItem>
165
+ <CodeBlock actions={actions}>
166
+ <CodeBlockCode id="code-content" className="remediation-code">
167
+ {snippetText}
168
+ </CodeBlockCode>
169
+ </CodeBlock>
170
+ </GridItem>
171
+ </Grid>
172
+ </>
173
+ );
174
+ };
175
+
176
+ export default ReviewRemediation;
@@ -0,0 +1,4 @@
1
+ pre.remediation-code {
2
+ border: none;
3
+ border-radius: none;
4
+ }