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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/foreman_openscap/locale/cs_CZ/foreman_openscap.js +517 -421
- data/app/assets/javascripts/foreman_openscap/locale/de/foreman_openscap.js +907 -814
- data/app/assets/javascripts/foreman_openscap/locale/en/foreman_openscap.js +517 -421
- data/app/assets/javascripts/foreman_openscap/locale/en_GB/foreman_openscap.js +549 -453
- data/app/assets/javascripts/foreman_openscap/locale/es/foreman_openscap.js +911 -818
- data/app/assets/javascripts/foreman_openscap/locale/fr/foreman_openscap.js +911 -818
- data/app/assets/javascripts/foreman_openscap/locale/gl/foreman_openscap.js +556 -460
- data/app/assets/javascripts/foreman_openscap/locale/it/foreman_openscap.js +601 -505
- data/app/assets/javascripts/foreman_openscap/locale/ja/foreman_openscap.js +909 -816
- data/app/assets/javascripts/foreman_openscap/locale/ka/foreman_openscap.js +96 -0
- data/app/assets/javascripts/foreman_openscap/locale/ko/foreman_openscap.js +704 -611
- data/app/assets/javascripts/foreman_openscap/locale/pt_BR/foreman_openscap.js +911 -818
- data/app/assets/javascripts/foreman_openscap/locale/ru/foreman_openscap.js +706 -613
- data/app/assets/javascripts/foreman_openscap/locale/sv_SE/foreman_openscap.js +556 -460
- data/app/assets/javascripts/foreman_openscap/locale/zh_CN/foreman_openscap.js +909 -816
- data/app/assets/javascripts/foreman_openscap/locale/zh_TW/foreman_openscap.js +704 -611
- data/app/assets/javascripts/foreman_openscap/reports.js +5 -0
- data/app/controllers/arf_reports_controller.rb +23 -2
- data/app/helpers/arf_reports_helper.rb +17 -4
- data/app/models/foreman_openscap/arf_report.rb +11 -2
- data/app/views/arf_reports/_output.html.erb +5 -1
- data/app/views/job_templates/run_openscap_remediation_-_ansible_default.erb +27 -0
- data/app/views/job_templates/run_openscap_remediation_-_script_default.erb +24 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20230912122310_add_fixes_to_message.rb +5 -0
- data/lib/foreman_openscap/engine.rb +18 -2
- data/lib/foreman_openscap/version.rb +1 -1
- data/lib/tasks/foreman_openscap_tasks.rake +5 -16
- data/locale/cs_CZ/foreman_openscap.edit.po +145 -17
- data/locale/cs_CZ/foreman_openscap.po +96 -0
- data/locale/de/foreman_openscap.edit.po +248 -282
- data/locale/de/foreman_openscap.po +96 -0
- data/locale/en/foreman_openscap.edit.po +145 -17
- data/locale/en/foreman_openscap.po +96 -0
- data/locale/en_GB/foreman_openscap.edit.po +145 -17
- data/locale/en_GB/foreman_openscap.po +96 -0
- data/locale/es/foreman_openscap.edit.po +241 -279
- data/locale/es/foreman_openscap.po +96 -0
- data/locale/foreman_openscap.pot +164 -12
- data/locale/fr/foreman_openscap.edit.po +330 -363
- data/locale/fr/foreman_openscap.po +96 -0
- data/locale/gl/foreman_openscap.edit.po +145 -17
- data/locale/gl/foreman_openscap.po +96 -0
- data/locale/it/foreman_openscap.edit.po +145 -17
- data/locale/it/foreman_openscap.po +96 -0
- data/locale/ja/foreman_openscap.edit.po +314 -347
- data/locale/ja/foreman_openscap.po +96 -0
- data/locale/ka/foreman_openscap.edit.po +289 -328
- data/locale/ka/foreman_openscap.po +96 -0
- data/locale/ka/foreman_openscap.po.time_stamp +0 -0
- data/locale/ko/foreman_openscap.edit.po +146 -18
- data/locale/ko/foreman_openscap.po +96 -0
- data/locale/pt_BR/foreman_openscap.edit.po +247 -279
- data/locale/pt_BR/foreman_openscap.po +96 -0
- data/locale/ru/foreman_openscap.edit.po +146 -18
- data/locale/ru/foreman_openscap.po +96 -0
- data/locale/sv_SE/foreman_openscap.edit.po +145 -17
- data/locale/sv_SE/foreman_openscap.po +96 -0
- data/locale/zh_CN/foreman_openscap.edit.po +314 -347
- data/locale/zh_CN/foreman_openscap.po +96 -0
- data/locale/zh_TW/foreman_openscap.edit.po +146 -18
- data/locale/zh_TW/foreman_openscap.po +96 -0
- data/webpack/components/EmptyState.js +6 -2
- data/webpack/components/OpenscapRemediationWizard/OpenscapRemediationSelectors.js +16 -0
- data/webpack/components/OpenscapRemediationWizard/OpenscapRemediationWizardContext.js +4 -0
- data/webpack/components/OpenscapRemediationWizard/ViewSelectedHostsLink.js +38 -0
- data/webpack/components/OpenscapRemediationWizard/WizardHeader.js +43 -0
- data/webpack/components/OpenscapRemediationWizard/constants.js +14 -0
- data/webpack/components/OpenscapRemediationWizard/helpers.js +33 -0
- data/webpack/components/OpenscapRemediationWizard/index.js +160 -0
- data/webpack/components/OpenscapRemediationWizard/steps/Finish.js +131 -0
- data/webpack/components/OpenscapRemediationWizard/steps/ReviewHosts.js +217 -0
- data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.js +176 -0
- data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.scss +4 -0
- data/webpack/components/OpenscapRemediationWizard/steps/SnippetSelect.js +160 -0
- data/webpack/components/OpenscapRemediationWizard/steps/index.js +4 -0
- data/webpack/index.js +8 -3
- data/webpack/testHelper.js +6 -5
- 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([
|
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,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;
|