foreman_openscap 7.1.1 → 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 (80) 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 +907 -814
  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 +911 -818
  7. data/app/assets/javascripts/foreman_openscap/locale/fr/foreman_openscap.js +911 -818
  8. data/app/assets/javascripts/foreman_openscap/locale/gl/foreman_openscap.js +556 -460
  9. data/app/assets/javascripts/foreman_openscap/locale/it/foreman_openscap.js +601 -505
  10. data/app/assets/javascripts/foreman_openscap/locale/ja/foreman_openscap.js +909 -816
  11. data/app/assets/javascripts/foreman_openscap/locale/ka/foreman_openscap.js +96 -0
  12. data/app/assets/javascripts/foreman_openscap/locale/ko/foreman_openscap.js +704 -611
  13. data/app/assets/javascripts/foreman_openscap/locale/pt_BR/foreman_openscap.js +911 -818
  14. data/app/assets/javascripts/foreman_openscap/locale/ru/foreman_openscap.js +706 -613
  15. data/app/assets/javascripts/foreman_openscap/locale/sv_SE/foreman_openscap.js +556 -460
  16. data/app/assets/javascripts/foreman_openscap/locale/zh_CN/foreman_openscap.js +909 -816
  17. data/app/assets/javascripts/foreman_openscap/locale/zh_TW/foreman_openscap.js +704 -611
  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/foreman_openscap/arf_report.rb +11 -2
  22. data/app/views/arf_reports/_output.html.erb +5 -1
  23. data/app/views/job_templates/run_openscap_remediation_-_ansible_default.erb +27 -0
  24. data/app/views/job_templates/run_openscap_remediation_-_script_default.erb +24 -0
  25. data/config/routes.rb +1 -0
  26. data/db/migrate/20230912122310_add_fixes_to_message.rb +5 -0
  27. data/lib/foreman_openscap/engine.rb +18 -2
  28. data/lib/foreman_openscap/version.rb +1 -1
  29. data/lib/tasks/foreman_openscap_tasks.rake +5 -16
  30. data/locale/cs_CZ/foreman_openscap.edit.po +145 -17
  31. data/locale/cs_CZ/foreman_openscap.po +96 -0
  32. data/locale/de/foreman_openscap.edit.po +248 -282
  33. data/locale/de/foreman_openscap.po +96 -0
  34. data/locale/en/foreman_openscap.edit.po +145 -17
  35. data/locale/en/foreman_openscap.po +96 -0
  36. data/locale/en_GB/foreman_openscap.edit.po +145 -17
  37. data/locale/en_GB/foreman_openscap.po +96 -0
  38. data/locale/es/foreman_openscap.edit.po +241 -279
  39. data/locale/es/foreman_openscap.po +96 -0
  40. data/locale/foreman_openscap.pot +164 -12
  41. data/locale/fr/foreman_openscap.edit.po +330 -363
  42. data/locale/fr/foreman_openscap.po +96 -0
  43. data/locale/gl/foreman_openscap.edit.po +145 -17
  44. data/locale/gl/foreman_openscap.po +96 -0
  45. data/locale/it/foreman_openscap.edit.po +145 -17
  46. data/locale/it/foreman_openscap.po +96 -0
  47. data/locale/ja/foreman_openscap.edit.po +314 -347
  48. data/locale/ja/foreman_openscap.po +96 -0
  49. data/locale/ka/foreman_openscap.edit.po +289 -328
  50. data/locale/ka/foreman_openscap.po +96 -0
  51. data/locale/ka/foreman_openscap.po.time_stamp +0 -0
  52. data/locale/ko/foreman_openscap.edit.po +146 -18
  53. data/locale/ko/foreman_openscap.po +96 -0
  54. data/locale/pt_BR/foreman_openscap.edit.po +247 -279
  55. data/locale/pt_BR/foreman_openscap.po +96 -0
  56. data/locale/ru/foreman_openscap.edit.po +146 -18
  57. data/locale/ru/foreman_openscap.po +96 -0
  58. data/locale/sv_SE/foreman_openscap.edit.po +145 -17
  59. data/locale/sv_SE/foreman_openscap.po +96 -0
  60. data/locale/zh_CN/foreman_openscap.edit.po +314 -347
  61. data/locale/zh_CN/foreman_openscap.po +96 -0
  62. data/locale/zh_TW/foreman_openscap.edit.po +146 -18
  63. data/locale/zh_TW/foreman_openscap.po +96 -0
  64. data/webpack/components/EmptyState.js +6 -2
  65. data/webpack/components/OpenscapRemediationWizard/OpenscapRemediationSelectors.js +16 -0
  66. data/webpack/components/OpenscapRemediationWizard/OpenscapRemediationWizardContext.js +4 -0
  67. data/webpack/components/OpenscapRemediationWizard/ViewSelectedHostsLink.js +38 -0
  68. data/webpack/components/OpenscapRemediationWizard/WizardHeader.js +43 -0
  69. data/webpack/components/OpenscapRemediationWizard/constants.js +14 -0
  70. data/webpack/components/OpenscapRemediationWizard/helpers.js +33 -0
  71. data/webpack/components/OpenscapRemediationWizard/index.js +160 -0
  72. data/webpack/components/OpenscapRemediationWizard/steps/Finish.js +131 -0
  73. data/webpack/components/OpenscapRemediationWizard/steps/ReviewHosts.js +217 -0
  74. data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.js +176 -0
  75. data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.scss +4 -0
  76. data/webpack/components/OpenscapRemediationWizard/steps/SnippetSelect.js +160 -0
  77. data/webpack/components/OpenscapRemediationWizard/steps/index.js +4 -0
  78. data/webpack/index.js +8 -3
  79. data/webpack/testHelper.js +6 -5
  80. metadata +20 -3
@@ -62,6 +62,12 @@ msgstr ""
62
62
  msgid "<b>Foreman</b> OpenSCAP summary"
63
63
  msgstr ""
64
64
 
65
+ msgid "A reboot is required after applying remediation."
66
+ msgstr ""
67
+
68
+ msgid "A reboot might be required after applying remediation."
69
+ msgstr ""
70
+
65
71
  msgid "A summary of reports for OpenSCAP policies"
66
72
  msgstr ""
67
73
 
@@ -113,6 +119,9 @@ msgstr ""
113
119
  msgid "Back"
114
120
  msgstr "上一頁"
115
121
 
122
+ msgid "By default, remediation is applied to the current host. Optionally, remediate any additional hosts that fail the rule."
123
+ msgstr ""
124
+
116
125
  msgid "CVEs"
117
126
  msgstr ""
118
127
 
@@ -158,6 +167,9 @@ msgstr "選擇期間"
158
167
  msgid "Choose weekday"
159
168
  msgstr "選擇星期幾"
160
169
 
170
+ msgid "Close"
171
+ msgstr ""
172
+
161
173
  msgid "Compliance"
162
174
  msgstr "合規"
163
175
 
@@ -191,6 +203,9 @@ msgstr ""
191
203
  msgid "Content"
192
204
  msgstr ""
193
205
 
206
+ msgid "Copy to clipboard"
207
+ msgstr ""
208
+
194
209
  msgid "Could not find host identified by: %s"
195
210
  msgstr ""
196
211
 
@@ -302,9 +317,15 @@ msgstr ""
302
317
  msgid "Directory to upload when using \"directory\" upload type"
303
318
  msgstr ""
304
319
 
320
+ msgid "Do not implement any of the recommended remedial actions or scripts without first testing them in a non-production environment."
321
+ msgstr ""
322
+
305
323
  msgid "Documentation"
306
324
  msgstr "文件"
307
325
 
326
+ msgid "Done"
327
+ msgstr ""
328
+
308
329
  msgid "Download"
309
330
  msgstr "下載"
310
331
 
@@ -505,6 +526,9 @@ msgstr ""
505
526
  msgid "It may sometimes be required to adjust the security policy to your specific needs. "
506
527
  msgstr ""
507
528
 
529
+ msgid "Job details"
530
+ msgstr ""
531
+
508
532
  msgid "Latest Compliance Reports"
509
533
  msgstr ""
510
534
 
@@ -538,9 +562,15 @@ msgstr "載入中…"
538
562
  msgid "Locations"
539
563
  msgstr "位置"
540
564
 
565
+ msgid "Manual"
566
+ msgstr ""
567
+
541
568
  msgid "Message"
542
569
  msgstr "訊息"
543
570
 
571
+ msgid "Method"
572
+ msgstr ""
573
+
544
574
  msgid "Monthly, day of month: %s"
545
575
  msgstr ""
546
576
 
@@ -676,6 +706,9 @@ msgstr "事件數量"
676
706
  msgid "Number of a day in month, note that not all months have same count of days"
677
707
  msgstr "月份中的日子,請注意並不是所有月份都有同樣的天數"
678
708
 
709
+ msgid "OS"
710
+ msgstr ""
711
+
679
712
  msgid "OVAL Content"
680
713
  msgstr ""
681
714
 
@@ -775,6 +808,9 @@ msgstr ""
775
808
  msgid "Other"
776
809
  msgstr "其他"
777
810
 
811
+ msgid "Other hosts failing this rule"
812
+ msgstr ""
813
+
778
814
  msgid "Othered"
779
815
  msgstr "其它"
780
816
 
@@ -867,6 +903,9 @@ msgstr "Puppet 類別"
867
903
  msgid "Rationale"
868
904
  msgstr "理由"
869
905
 
906
+ msgid "Reboot the system(s)"
907
+ msgstr ""
908
+
870
909
  msgid "Red Hat %s default content"
871
910
  msgstr ""
872
911
 
@@ -876,9 +915,21 @@ msgstr ""
876
915
  msgid "References"
877
916
  msgstr "參照"
878
917
 
918
+ msgid "Remediate %s rule"
919
+ msgstr ""
920
+
921
+ msgid "Remediation"
922
+ msgstr ""
923
+
924
+ msgid "Remediation might render the system non-functional."
925
+ msgstr ""
926
+
879
927
  msgid "Remote action:"
880
928
  msgstr "遠端動作:"
881
929
 
930
+ msgid "Remote job"
931
+ msgstr ""
932
+
882
933
  msgid "Report Metrics"
883
934
  msgstr "報告計量"
884
935
 
@@ -915,12 +966,33 @@ msgstr "資源"
915
966
  msgid "Result"
916
967
  msgstr "結果"
917
968
 
969
+ msgid "Review hosts"
970
+ msgstr ""
971
+
972
+ msgid "Review remediation"
973
+ msgstr ""
974
+
975
+ msgid "Review the remediation snippet and apply it to the host manually."
976
+ msgstr ""
977
+
978
+ msgid "Review the remediation snippet that will be applied to selected host(s)."
979
+ msgstr ""
980
+
918
981
  msgid "Rule Results"
919
982
  msgstr ""
920
983
 
984
+ msgid "Run"
985
+ msgstr ""
986
+
921
987
  msgid "Run OVAL scan"
922
988
  msgstr ""
923
989
 
990
+ msgid "Run OpenSCAP remediation with Ansible"
991
+ msgstr ""
992
+
993
+ msgid "Run OpenSCAP remediation with Shell"
994
+ msgstr ""
995
+
924
996
  msgid "Run OpenSCAP scan"
925
997
  msgstr ""
926
998
 
@@ -963,6 +1035,12 @@ msgstr ""
963
1035
  msgid "Select all items in this page"
964
1036
  msgstr "選擇這頁中的所有項目"
965
1037
 
1038
+ msgid "Select remediation method"
1039
+ msgstr ""
1040
+
1041
+ msgid "Select snippet"
1042
+ msgstr ""
1043
+
966
1044
  msgid "Severity"
967
1045
  msgstr "嚴重性"
968
1046
 
@@ -1005,6 +1083,9 @@ msgstr "顯示日誌訊息:"
1005
1083
  msgid "Smart Class Parameters"
1006
1084
  msgstr ""
1007
1085
 
1086
+ msgid "Snippet"
1087
+ msgstr ""
1088
+
1008
1089
  msgid "Something went wrong while selecting compliance reports - %s"
1009
1090
  msgstr "選擇合規報告時發生了錯誤 - %s"
1010
1091
 
@@ -1023,6 +1104,9 @@ msgstr "狀態表"
1023
1104
  msgid "Submit"
1024
1105
  msgstr "提交"
1025
1106
 
1107
+ msgid "Successfully copied to clipboard!"
1108
+ msgstr ""
1109
+
1026
1110
  msgid "Successfully deleted %s compliance reports"
1027
1111
  msgstr "刪除 %s 合規報告成功"
1028
1112
 
@@ -1085,9 +1169,15 @@ msgstr ""
1085
1169
  msgid "The identifier of the host"
1086
1170
  msgstr ""
1087
1171
 
1172
+ msgid "The job has started on selected host(s), you can check the status on the job details page."
1173
+ msgstr ""
1174
+
1088
1175
  msgid "There are significant differences in deployment options."
1089
1176
  msgstr ""
1090
1177
 
1178
+ msgid "There is no job to remediate with. Please remediate manually."
1179
+ msgstr ""
1180
+
1091
1181
  msgid "There was a following error when deleting %(name)s: %(error)s"
1092
1182
  msgstr ""
1093
1183
 
@@ -1210,6 +1300,9 @@ msgstr "檢視報告"
1210
1300
  msgid "View full report"
1211
1301
  msgstr "檢視完整報告"
1212
1302
 
1303
+ msgid "View selected hosts"
1304
+ msgstr ""
1305
+
1213
1306
  msgid "Was %s configured successfully?"
1214
1307
  msgstr ""
1215
1308
 
@@ -1240,6 +1333,9 @@ msgstr "是"
1240
1333
  msgid "You are not authorized to view the page. "
1241
1334
  msgstr ""
1242
1335
 
1336
+ msgid "You can remediate by running a remote job or you can display a snippet for manual remediation."
1337
+ msgstr ""
1338
+
1243
1339
  msgid "You can specify custom cron line, e.g. \"0 3 * * *\", separate each of 5 values by space"
1244
1340
  msgstr "您可指定自訂的 cron 行,例如\"0 3 * * *\",五個數值均由空格隔開"
1245
1341
 
@@ -56,8 +56,12 @@ EmptyStateIcon.defaultProps = {
56
56
 
57
57
  EmptyState.propTypes = {
58
58
  title: PropTypes.string,
59
- body: PropTypes.string,
60
- error: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.string]),
59
+ body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
60
+ error: PropTypes.oneOfType([
61
+ PropTypes.shape({}),
62
+ PropTypes.string,
63
+ PropTypes.bool,
64
+ ]),
61
65
  search: PropTypes.bool,
62
66
  lock: PropTypes.bool,
63
67
  primaryButton: PropTypes.node,
@@ -0,0 +1,16 @@
1
+ import {
2
+ selectAPIError,
3
+ selectAPIResponse,
4
+ selectAPIStatus,
5
+ } from 'foremanReact/redux/API/APISelectors';
6
+ import { STATUS } from 'foremanReact/constants';
7
+ import { REPORT_LOG_REQUEST_KEY } from './constants';
8
+
9
+ export const selectLogResponse = state =>
10
+ selectAPIResponse(state, REPORT_LOG_REQUEST_KEY);
11
+
12
+ export const selectLogStatus = state =>
13
+ selectAPIStatus(state, REPORT_LOG_REQUEST_KEY) || STATUS.PENDING;
14
+
15
+ export const selectLogError = state =>
16
+ selectAPIError(state, REPORT_LOG_REQUEST_KEY);
@@ -0,0 +1,4 @@
1
+ import { createContext } from 'react';
2
+
3
+ const OpenscapRemediationWizardContext = createContext({});
4
+ export default OpenscapRemediationWizardContext;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
5
+ import { Button } from '@patternfly/react-core';
6
+
7
+ import { translate as __ } from 'foremanReact/common/I18n';
8
+ import { foremanUrl } from 'foremanReact/common/helpers';
9
+ import { getHostsPageUrl } from 'foremanReact/Root/Context/ForemanContext';
10
+
11
+ const ViewSelectedHostsLink = ({
12
+ hostIdsParam,
13
+ isAllHostsSelected,
14
+ defaultFailedHostsSearch,
15
+ }) => {
16
+ const search = isAllHostsSelected ? defaultFailedHostsSearch : hostIdsParam;
17
+ const url = foremanUrl(`${getHostsPageUrl(false)}?search=${search}`);
18
+ return (
19
+ <Button
20
+ component="a"
21
+ variant="link"
22
+ icon={<ExternalLinkSquareAltIcon />}
23
+ iconPosition="right"
24
+ target="_blank"
25
+ href={url}
26
+ >
27
+ {__('View selected hosts')}
28
+ </Button>
29
+ );
30
+ };
31
+
32
+ ViewSelectedHostsLink.propTypes = {
33
+ isAllHostsSelected: PropTypes.bool.isRequired,
34
+ defaultFailedHostsSearch: PropTypes.string.isRequired,
35
+ hostIdsParam: PropTypes.string.isRequired,
36
+ };
37
+
38
+ export default ViewSelectedHostsLink;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ Grid,
5
+ TextContent,
6
+ Text,
7
+ TextVariants,
8
+ Flex,
9
+ FlexItem,
10
+ } from '@patternfly/react-core';
11
+
12
+ const WizardHeader = ({ title, description }) => (
13
+ <Grid style={{ gridGap: '24px' }}>
14
+ {title && (
15
+ <TextContent>
16
+ <Text ouiaId="wizard-header-text" component={TextVariants.h2}>
17
+ {title}
18
+ </Text>
19
+ </TextContent>
20
+ )}
21
+ {description && (
22
+ <TextContent>
23
+ <Flex flex={{ default: 'inlineFlex' }}>
24
+ <FlexItem>
25
+ <TextContent>{description}</TextContent>
26
+ </FlexItem>
27
+ </Flex>
28
+ </TextContent>
29
+ )}
30
+ </Grid>
31
+ );
32
+
33
+ WizardHeader.propTypes = {
34
+ title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
35
+ description: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
36
+ };
37
+
38
+ WizardHeader.defaultProps = {
39
+ title: undefined,
40
+ description: undefined,
41
+ };
42
+
43
+ export default WizardHeader;
@@ -0,0 +1,14 @@
1
+ export const OPENSCAP_REMEDIATION_MODAL_ID = 'openscapRemediationModal';
2
+ export const HOSTS_PATH = '/hosts';
3
+ export const FAIL_RULE_SEARCH = 'fails_xccdf_rule';
4
+
5
+ export const HOSTS_API_PATH = '/api/hosts';
6
+ export const HOSTS_API_REQUEST_KEY = 'HOSTS';
7
+ export const REPORT_LOG_REQUEST_KEY = 'ARF_REPORT_LOG';
8
+
9
+ export const JOB_INVOCATION_PATH = '/job_invocations';
10
+ export const JOB_INVOCATION_API_PATH = '/api/job_invocations';
11
+ export const JOB_INVOCATION_API_REQUEST_KEY = 'OPENSCAP_REX_JOB_INVOCATIONS';
12
+
13
+ export const SNIPPET_SH = 'urn:xccdf:fix:script:sh';
14
+ export const SNIPPET_ANSIBLE = 'urn:xccdf:fix:script:ansible';
@@ -0,0 +1,33 @@
1
+ import { join, find, map, compact, includes, filter, isString } from 'lodash';
2
+
3
+ const getResponseErrorMsgs = ({ data } = {}) => {
4
+ if (data) {
5
+ const messages =
6
+ data.displayMessage || data.message || data.errors || data.error?.message;
7
+ return Array.isArray(messages) ? messages : [messages];
8
+ }
9
+ return [];
10
+ };
11
+
12
+ export const errorMsg = data => {
13
+ if (isString(data)) return data;
14
+
15
+ return join(getResponseErrorMsgs({ data }), '\n');
16
+ };
17
+
18
+ export const findFixBySnippet = (fixes, snippet) =>
19
+ find(fixes, fix => fix.system === snippet);
20
+
21
+ export const supportedRemediationSnippets = (
22
+ fixes,
23
+ meth,
24
+ supportedJobSnippets
25
+ ) => {
26
+ if (meth === 'manual') return map(fixes, f => f.system);
27
+ return compact(
28
+ map(
29
+ filter(fixes, fix => includes(supportedJobSnippets, fix.system)),
30
+ f => f.system
31
+ )
32
+ );
33
+ };
@@ -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;