foreman_openscap 7.1.1 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
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;