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.
- 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;
|