foreman_host_reports 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +545 -0
  4. data/app/controllers/api/v2/host_reports_controller.rb +98 -0
  5. data/app/controllers/concerns/foreman_host_reports/controller/parameters/host_report.rb +25 -0
  6. data/app/controllers/host_reports_controller.rb +46 -0
  7. data/app/models/concerns/foreman_host_reports/host_extensions.rb +9 -0
  8. data/app/models/concerns/foreman_host_reports/smart_proxy_extensions.rb +9 -0
  9. data/app/models/host_report.rb +85 -0
  10. data/app/models/report_keyword.rb +11 -0
  11. data/app/views/api/v2/host_reports/base.json.rabl +5 -0
  12. data/app/views/api/v2/host_reports/create.json.rabl +5 -0
  13. data/app/views/api/v2/host_reports/index.json.rabl +5 -0
  14. data/app/views/api/v2/host_reports/main.json.rabl +15 -0
  15. data/app/views/api/v2/host_reports/show.json.rabl +11 -0
  16. data/config/routes.rb +33 -0
  17. data/db/migrate/20210112183526_add_host_reports.rb +36 -0
  18. data/db/migrate/20210616133601_create_report_keywords.rb +12 -0
  19. data/lib/foreman_host_reports/engine.rb +68 -0
  20. data/lib/foreman_host_reports/version.rb +3 -0
  21. data/lib/foreman_host_reports.rb +4 -0
  22. data/lib/tasks/foreman_host_reports_tasks.rake +50 -0
  23. data/locale/Makefile +60 -0
  24. data/locale/en/foreman_host_reports.po +19 -0
  25. data/locale/foreman_host_reports.pot +19 -0
  26. data/locale/gemspec.rb +2 -0
  27. data/package.json +45 -0
  28. data/test/controllers/api/v2/host_reports_controller_test.rb +278 -0
  29. data/test/factories/foreman_host_reports_factories.rb +21 -0
  30. data/test/snapshots/foreman-web.json +918 -0
  31. data/test/test_plugin_helper.rb +11 -0
  32. data/test/unit/foreman_host_reports_test.rb +9 -0
  33. data/webpack/global_index.js +4 -0
  34. data/webpack/global_test_setup.js +11 -0
  35. data/webpack/index.js +0 -0
  36. data/webpack/src/Router/HostReports/Components/HostReportDeleteModal.js +50 -0
  37. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/FormatCell.js +53 -0
  38. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/FormatCell.scss +4 -0
  39. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/formatCellFormatter.js +6 -0
  40. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/hostReportsToShowFormatter.js +13 -0
  41. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/index.js +4 -0
  42. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/reportToShowFormatter.js +13 -0
  43. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/statusFormatter.js +9 -0
  44. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/HostReportsToShowCell.js +32 -0
  45. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/ReportToShowCell.js +27 -0
  46. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.js +77 -0
  47. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.scss +9 -0
  48. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/formatImages.js +7 -0
  49. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/images/ansible.png +0 -0
  50. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/images/puppet.png +0 -0
  51. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/HostReportsTable.js +85 -0
  52. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/HostReportsTableSchema.js +65 -0
  53. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/index.js +28 -0
  54. data/webpack/src/Router/HostReports/IndexPage/IndexPage.js +88 -0
  55. data/webpack/src/Router/HostReports/IndexPage/IndexPageActions.js +55 -0
  56. data/webpack/src/Router/HostReports/IndexPage/IndexPageHelpers.js +50 -0
  57. data/webpack/src/Router/HostReports/IndexPage/IndexPageSelectors.js +86 -0
  58. data/webpack/src/Router/HostReports/IndexPage/constants.js +14 -0
  59. data/webpack/src/Router/HostReports/IndexPage/index.js +98 -0
  60. data/webpack/src/Router/HostReports/ShowPage/Components/HostReportMetrics/HostReportMetrics.scss +3 -0
  61. data/webpack/src/Router/HostReports/ShowPage/Components/HostReportMetrics/index.js +100 -0
  62. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Ansible.js +77 -0
  63. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Puppet.js +79 -0
  64. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/helpers.js +17 -0
  65. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/index.js +29 -0
  66. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogsFilter/index.js +109 -0
  67. data/webpack/src/Router/HostReports/ShowPage/ShowPage.js +160 -0
  68. data/webpack/src/Router/HostReports/ShowPage/ShowPageSelectors.js +51 -0
  69. data/webpack/src/Router/HostReports/ShowPage/index.js +75 -0
  70. data/webpack/src/Router/HostReports/constants.js +15 -0
  71. data/webpack/src/Router/routes.js +23 -0
  72. data/webpack/test_setup.js +17 -0
  73. metadata +133 -0
@@ -0,0 +1,55 @@
1
+ import history from 'foremanReact/history';
2
+ import { get } from 'foremanReact/redux/API';
3
+ import { stringifyParams, getParams } from 'foremanReact/common/urlHelpers';
4
+
5
+ import { buildQuery } from './IndexPageHelpers';
6
+ import {
7
+ HOST_REPORTS_API_PATH,
8
+ HOST_REPORTS_PATH,
9
+ HOST_REPORTS_API_REQUEST_KEY,
10
+ } from './constants';
11
+
12
+ export const initializeHostReports = () => dispatch => {
13
+ const params = getParams();
14
+ dispatch(fetchHostReports(params));
15
+ if (!history.action === 'POP') {
16
+ history.replace({
17
+ pathname: HOST_REPORTS_PATH,
18
+ search: stringifyParams(params),
19
+ });
20
+ }
21
+ };
22
+
23
+ export const fetchHostReports = (
24
+ { page, perPage, searchQuery, sort },
25
+ url = HOST_REPORTS_API_PATH
26
+ ) => async dispatch => {
27
+ const sortString =
28
+ sort && Object.keys(sort).length > 0 ? `${sort.by} ${sort.order}` : '';
29
+
30
+ return dispatch(
31
+ get({
32
+ key: HOST_REPORTS_API_REQUEST_KEY,
33
+ url,
34
+ params: {
35
+ page,
36
+ per_page: perPage,
37
+ search: searchQuery,
38
+ order: sortString,
39
+ },
40
+ })
41
+ );
42
+ };
43
+
44
+ export const fetchAndPush = (params = {}) => (dispatch, getState) => {
45
+ const query = buildQuery(params, getState());
46
+ dispatch(fetchHostReports(query));
47
+ history.push({
48
+ pathname: HOST_REPORTS_PATH,
49
+ search: stringifyParams(query),
50
+ });
51
+ };
52
+
53
+ export const reloadWithSearch = query => dispatch => {
54
+ dispatch(fetchAndPush({ searchQuery: query, page: 1 }));
55
+ };
@@ -0,0 +1,50 @@
1
+ import URI from 'urijs';
2
+ import { snakeCase } from 'lodash';
3
+ import { compose } from 'redux';
4
+
5
+ import { foremanUrl } from 'foremanReact/common/helpers';
6
+
7
+ import {
8
+ selectSort,
9
+ selectPage,
10
+ selectPerPage,
11
+ selectSearch,
12
+ } from './IndexPageSelectors';
13
+
14
+ import { HOST_REPORTS_API_PATH } from './constants';
15
+
16
+ export const buildQuery = (query, state) => {
17
+ const querySort = pickSort(query, state);
18
+
19
+ return {
20
+ page: query.page || selectPage(state),
21
+ perPage: query.perPage || selectPerPage(state),
22
+ searchQuery:
23
+ query.searchQuery === undefined ? selectSearch(state) : query.searchQuery,
24
+ ...(querySort && { sort: querySort }),
25
+ };
26
+ };
27
+
28
+ export const pickSort = (query, state) =>
29
+ checkSort(query.sort)
30
+ ? transformSort(query.sort)
31
+ : checkSort(compose(transformSort, selectSort)(state));
32
+
33
+ const checkSort = sort => (sort && sort.by && sort.order ? sort : undefined);
34
+
35
+ const transformSort = sort => ({ ...sort, by: snakeCase(sort.by) });
36
+
37
+ export const getExportUrl = (path, query) => {
38
+ let url = new URI(path);
39
+ url = url.pathname(`${url.pathname()}/export`);
40
+ url.addSearch(query);
41
+ return url.toString();
42
+ };
43
+
44
+ export const hostReportsIndexUrl = hostId => {
45
+ if (!hostId) return foremanUrl(HOST_REPORTS_API_PATH);
46
+
47
+ return foremanUrl(
48
+ `/api/v2/hosts/${hostId}/host_reports?include_permissions=true`
49
+ );
50
+ };
@@ -0,0 +1,86 @@
1
+ import { camelCase, isEmpty } from 'lodash';
2
+ import Immutable from 'seamless-immutable';
3
+ import { STATUS } from 'foremanReact/constants';
4
+ import { deepPropsToCamelCase } from 'foremanReact/common/helpers';
5
+ import {
6
+ selectAPIStatus,
7
+ selectAPIResponse,
8
+ selectAPIErrorMessage,
9
+ } from 'foremanReact/redux/API/APISelectors';
10
+
11
+ import { HOST_REPORTS_API_REQUEST_KEY } from './constants';
12
+
13
+ export const emptyResponse = {
14
+ results: [],
15
+ page: 0,
16
+ perPage: 0,
17
+ search: '',
18
+ sort: {},
19
+ canCreate: false,
20
+ subtotal: 0,
21
+ };
22
+
23
+ const selectHostReportsPageResponse = state => {
24
+ const response = deepPropsToCamelCase(
25
+ selectAPIResponse(state, HOST_REPORTS_API_REQUEST_KEY)
26
+ );
27
+ if (isEmpty(response)) {
28
+ return Immutable(emptyResponse);
29
+ }
30
+ return response;
31
+ };
32
+
33
+ export const selectIsLoading = state => {
34
+ const status = selectHostReportsPageStatus(state);
35
+ return !status || status === STATUS.PENDING;
36
+ };
37
+
38
+ const selectHostReportsPageStatus = state =>
39
+ selectAPIStatus(state, HOST_REPORTS_API_REQUEST_KEY);
40
+
41
+ export const selectHasError = state =>
42
+ selectHostReportsPageStatus(state) === STATUS.ERROR;
43
+
44
+ export const selectHostReports = state => {
45
+ if (selectHasError(state)) {
46
+ return [];
47
+ }
48
+ return selectHostReportsPageResponse(state).results;
49
+ };
50
+
51
+ export const selectHasData = state => {
52
+ const status = selectHostReportsPageStatus(state);
53
+ const results = selectHostReports(state);
54
+
55
+ return status === STATUS.RESOLVED && results && results.length > 0;
56
+ };
57
+
58
+ export const selectPage = state =>
59
+ selectHostReportsPageResponse(state).page || 1;
60
+ export const selectPerPage = state =>
61
+ selectHostReportsPageResponse(state).perPage || 20;
62
+ export const selectSearch = state =>
63
+ selectHostReportsPageResponse(state).search;
64
+
65
+ export const selectSort = state => {
66
+ const sort = selectHostReportsPageResponse(state).sort || Immutable({});
67
+ if (sort.by && sort.order) {
68
+ return { ...sort, by: camelCase(sort.by) };
69
+ }
70
+ return sort;
71
+ };
72
+
73
+ export const selectSubtotal = state =>
74
+ selectHostReportsPageResponse(state).subtotal || 0;
75
+
76
+ export const selectErrorMessage = state => {
77
+ if (!selectHasError(state)) return { message: '', details: '' };
78
+ const error = selectHostReportsPageResponse(state).response?.data?.error;
79
+
80
+ if (error) return error;
81
+
82
+ return {
83
+ message: selectAPIErrorMessage(state, HOST_REPORTS_API_REQUEST_KEY),
84
+ details: '',
85
+ };
86
+ };
@@ -0,0 +1,14 @@
1
+ import { getControllerSearchProps } from 'foremanReact/constants';
2
+
3
+ export const HOST_REPORTS_SEARCH_PROPS = getControllerSearchProps(
4
+ 'host_reports'
5
+ );
6
+
7
+ export const HOST_REPORTS_PATH = '/host_reports';
8
+ export const HOST_REPORTS_API_PATH =
9
+ '/api/v2/host_reports?include_permissions=true';
10
+ export const HOST_REPORTS_API_PLAIN_PATH = '/api/v2/host_reports';
11
+
12
+ export const HOST_REPORTS_API_REQUEST_KEY = 'HOST_REPORTS_API';
13
+
14
+ export const HOST_REPORT_DELETE_MODAL_ID = 'hostReportDeleteModal';
@@ -0,0 +1,98 @@
1
+ import React, { useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useSelector, useDispatch } from 'react-redux';
4
+ import { isEmpty } from 'lodash';
5
+
6
+ import { get } from 'foremanReact/redux/API';
7
+
8
+ import Loading from 'foremanReact/components/Loading';
9
+ import DefaultEmptyState from 'foremanReact/components/common/EmptyState';
10
+ import { WelcomeConfigReports } from 'foremanReact/components/ConfigReports/Welcome';
11
+
12
+ import { HOST_REPORTS_API_REQUEST_KEY } from './constants';
13
+
14
+ import HostReportsIndexPage from './IndexPage';
15
+
16
+ import {
17
+ selectHostReports,
18
+ selectPage,
19
+ selectPerPage,
20
+ selectSearch,
21
+ selectSort,
22
+ selectHasData,
23
+ selectHasError,
24
+ selectIsLoading,
25
+ selectSubtotal,
26
+ selectErrorMessage,
27
+ } from './IndexPageSelectors';
28
+
29
+ import { reloadWithSearch, fetchAndPush } from './IndexPageActions';
30
+
31
+ import { hostReportsIndexUrl } from './IndexPageHelpers';
32
+
33
+ const ConnectedHostReportsIndexPage = ({ history, match }) => {
34
+ const dispatch = useDispatch();
35
+
36
+ const reports = useSelector(selectHostReports);
37
+ const page = useSelector(selectPage);
38
+ const perPage = useSelector(selectPerPage);
39
+ const search = useSelector(selectSearch);
40
+ const sort = useSelector(selectSort);
41
+ const isLoading = useSelector(selectIsLoading);
42
+ const hasData = useSelector(selectHasData);
43
+ const hasError = useSelector(selectHasError);
44
+ const itemCount = useSelector(selectSubtotal);
45
+ const error = useSelector(selectErrorMessage);
46
+
47
+ const { hostId } = match.params;
48
+
49
+ useEffect(() => {
50
+ dispatch(
51
+ get({
52
+ key: HOST_REPORTS_API_REQUEST_KEY,
53
+ url: hostReportsIndexUrl(hostId),
54
+ })
55
+ );
56
+ }, [dispatch, hostId]);
57
+
58
+ if (isLoading && !hasError) return <Loading />;
59
+
60
+ if (!isLoading && hasError) {
61
+ return (
62
+ <DefaultEmptyState
63
+ icon="error-circle-o"
64
+ header={error.message}
65
+ description={error.details || ''}
66
+ documentation={null}
67
+ />
68
+ );
69
+ }
70
+
71
+ if (!isLoading && !hasError && !hasData && isEmpty(search)) {
72
+ return <WelcomeConfigReports />;
73
+ }
74
+
75
+ return (
76
+ <HostReportsIndexPage
77
+ fetchAndPush={params => dispatch(fetchAndPush(params))}
78
+ search={search}
79
+ isLoading={isLoading}
80
+ hasData={hasData}
81
+ reports={reports}
82
+ page={page}
83
+ perPage={perPage}
84
+ sort={sort}
85
+ itemCount={itemCount}
86
+ reloadWithSearch={query => dispatch(reloadWithSearch(query))}
87
+ history={history}
88
+ hostId={hostId}
89
+ />
90
+ );
91
+ };
92
+
93
+ ConnectedHostReportsIndexPage.propTypes = {
94
+ history: PropTypes.object.isRequired,
95
+ match: PropTypes.object.isRequired,
96
+ };
97
+
98
+ export default ConnectedHostReportsIndexPage;
@@ -0,0 +1,3 @@
1
+ .bar-chart-medium-width {
2
+ min-width: 450px;
3
+ }
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { Grid, GridItem } from '@patternfly/react-core';
5
+
6
+ import { cloneDeep, remove } from 'lodash';
7
+
8
+ import ChartBox from 'foremanReact/components/ChartBox/ChartBox';
9
+
10
+ import { translate as __ } from 'foremanReact/common/I18n';
11
+ import { STATUS } from 'foremanReact/constants';
12
+
13
+ import './HostReportMetrics.scss';
14
+
15
+ const HostReportMetrics = ({
16
+ metrics: {
17
+ time: { values: timeValues },
18
+ resources: { values: resValues },
19
+ },
20
+ }) => {
21
+ const clonedTimeValues = cloneDeep(timeValues).filter(v => v[2] >= 0.001);
22
+ const totalTime = remove(clonedTimeValues, val => val[0] === 'total');
23
+ const metricsChartData = clonedTimeValues
24
+ .map(v => [v[1], parseFloat(v[2].toFixed(4))])
25
+ .sort((a, b) => b[1] - a[1]);
26
+
27
+ const clonedStatuses = cloneDeep(resValues);
28
+ const totalStatuses = remove(clonedStatuses, val => val[0] === 'total');
29
+ const statuses = clonedStatuses
30
+ .map(v => [v[1], v[2]])
31
+ .sort((a, b) => b[1] - a[1]);
32
+
33
+ const createRow = ([name, value], i) => (
34
+ <tr key={i}>
35
+ <td className="break-me">{name}</td>
36
+ <td>{value}</td>
37
+ </tr>
38
+ );
39
+
40
+ const chartBoxProps = {
41
+ className: 'report-chart',
42
+ noDataMsg: __('No data available'),
43
+ status: STATUS.RESOLVED,
44
+ config: 'medium',
45
+ };
46
+
47
+ return (
48
+ <Grid hasGutter>
49
+ <GridItem span={4}>
50
+ <table className="table table-bordered table-striped table-hover report-chart">
51
+ <tbody>{statuses.map((label, v) => createRow(label, v))}</tbody>
52
+ {totalStatuses.length ? (
53
+ <tfoot>
54
+ <tr>
55
+ <th>{__('Total')}</th>
56
+ <th>{totalStatuses[0][2]}</th>
57
+ </tr>
58
+ </tfoot>
59
+ ) : null}
60
+ </table>
61
+ </GridItem>
62
+ <GridItem span={4}>
63
+ <ChartBox
64
+ {...chartBoxProps}
65
+ type="donut"
66
+ chart={{ data: metricsChartData }}
67
+ title={__('Report Metrics')}
68
+ />
69
+ </GridItem>
70
+ <GridItem span={4}>
71
+ <table className="table table-bordered table-striped table-hover report-chart">
72
+ <tbody>
73
+ {metricsChartData.map((label, t) => createRow(label, t))}
74
+ </tbody>
75
+ {totalTime.length ? (
76
+ <tfoot>
77
+ <tr>
78
+ <th>{__('Total')}</th>
79
+ <th>{parseFloat(totalTime[0][2].toFixed(4))}</th>
80
+ </tr>
81
+ </tfoot>
82
+ ) : null}
83
+ </table>
84
+ </GridItem>
85
+ </Grid>
86
+ );
87
+ };
88
+
89
+ HostReportMetrics.propTypes = {
90
+ metrics: PropTypes.shape({
91
+ time: PropTypes.shape({
92
+ values: PropTypes.array,
93
+ }),
94
+ resources: PropTypes.shape({
95
+ values: PropTypes.array,
96
+ }),
97
+ }).isRequired,
98
+ };
99
+
100
+ export default HostReportMetrics;
@@ -0,0 +1,77 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { Alert, AlertActionCloseButton } from '@patternfly/react-core';
5
+
6
+ import { translate as __ } from 'foremanReact/common/I18n';
7
+
8
+ import { msgLevelClasses } from './helpers';
9
+
10
+ const AnsibleLogs = ({ logs, checkMode }) => {
11
+ const [alertVisibility, setAlertVisibility] = useState(true);
12
+ return (
13
+ <>
14
+ {checkMode && alertVisibility ? (
15
+ <Alert
16
+ variant="info"
17
+ isInline
18
+ title={__('Ansible check mode')}
19
+ actionClose={
20
+ <AlertActionCloseButton onClose={() => setAlertVisibility(false)} />
21
+ }
22
+ >
23
+ {__('Notice that ansible roles run in check mode.')}
24
+ </Alert>
25
+ ) : null}
26
+ <table
27
+ id="report_log"
28
+ className="table table-bordered table-striped table-hover"
29
+ >
30
+ <thead>
31
+ <tr>
32
+ <th className="col col-md"> {__('Level')} </th>
33
+ <th className="col col-md-3"> {__('Task')} </th>
34
+ <th className="col col-md-9"> {__('Message')} </th>
35
+ </tr>
36
+ </thead>
37
+ <tbody>
38
+ {logs.map((log, idx) => (
39
+ <tr key={`tr-${idx + 1}`}>
40
+ <td>
41
+ <span className={msgLevelClasses(log.level)}>{log.level}</span>
42
+ </td>
43
+ <td className="break-me">{log.task.name}</td>
44
+ {Array.isArray(log.friendlyMessage) ? (
45
+ <td>
46
+ <ul>
47
+ {log.friendlyMessage.map((msg, i) => (
48
+ <li key={`li-${i + 1}`}>{msg}</li>
49
+ ))}
50
+ </ul>
51
+ </td>
52
+ ) : (
53
+ <td className="break-me">{log.friendlyMessage}</td>
54
+ )}
55
+ </tr>
56
+ ))}
57
+ {logs.length === 0 ? (
58
+ <tr key="tr-0">
59
+ <td colSpan="3">{__('Nothing to show')}</td>
60
+ </tr>
61
+ ) : null}
62
+ </tbody>
63
+ </table>
64
+ </>
65
+ );
66
+ };
67
+
68
+ AnsibleLogs.propTypes = {
69
+ logs: PropTypes.array.isRequired,
70
+ checkMode: PropTypes.bool,
71
+ };
72
+
73
+ AnsibleLogs.defaultProps = {
74
+ checkMode: false,
75
+ };
76
+
77
+ export default AnsibleLogs;
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
6
+ import * as diffModalActions from 'foremanReact/components/ConfigReports/DiffModal/DiffModalActions';
7
+ import DiffModal from 'foremanReact/components/ConfigReports/DiffModal';
8
+
9
+ import { msgLevelClasses } from './helpers';
10
+
11
+ const PuppetLogs = ({ logs, environment }) => {
12
+ const dispatch = useDispatch();
13
+ const showDiff = (e, diff, title) => {
14
+ e.preventDefault();
15
+ dispatch(diffModalActions.createDiff(diff, title));
16
+ };
17
+
18
+ return (
19
+ <>
20
+ <DiffModal />
21
+ {environment ? (
22
+ <p className="ra">
23
+ {sprintf(__('Puppet Environment: %s'), environment)}
24
+ </p>
25
+ ) : null}
26
+ <table
27
+ id="report_log"
28
+ className="table table-bordered table-striped table-hover"
29
+ >
30
+ <thead>
31
+ <tr>
32
+ <th className="col col-md"> {__('Level')} </th>
33
+ <th className="col col-md-3"> {__('Resource')} </th>
34
+ <th className="col col-md-9"> {__('Message')} </th>
35
+ </tr>
36
+ </thead>
37
+ <tbody>
38
+ {logs.map((log, i) => (
39
+ <tr key={`tr-${i + 1}`}>
40
+ <td>
41
+ <span className={msgLevelClasses(log[0])}>{log[0]}</span>
42
+ </td>
43
+ <td className="break-me">{log[1]}</td>
44
+ {log[2].startsWith('\n---') ? (
45
+ <td className="break-me">
46
+ <a
47
+ onClick={e =>
48
+ showDiff(e, log[2], /File\[(.*?)\]/.exec(log[1])[1])
49
+ }
50
+ >
51
+ {__('Show Diff')}
52
+ </a>
53
+ </td>
54
+ ) : (
55
+ <td className="break-me">{log[2]}</td>
56
+ )}
57
+ </tr>
58
+ ))}
59
+ {logs.length === 0 ? (
60
+ <tr key="tr-0">
61
+ <td colSpan="3">{__('Nothing to show')}</td>
62
+ </tr>
63
+ ) : null}
64
+ </tbody>
65
+ </table>
66
+ </>
67
+ );
68
+ };
69
+
70
+ PuppetLogs.propTypes = {
71
+ logs: PropTypes.array.isRequired,
72
+ environment: PropTypes.string,
73
+ };
74
+
75
+ PuppetLogs.defaultProps = {
76
+ environment: null,
77
+ };
78
+
79
+ export default PuppetLogs;
@@ -0,0 +1,17 @@
1
+ export const msgLevelClasses = level => {
2
+ let tag;
3
+ switch (level) {
4
+ case 'notice':
5
+ tag = 'info';
6
+ break;
7
+ case 'warning':
8
+ tag = 'warning';
9
+ break;
10
+ case 'err':
11
+ tag = 'danger';
12
+ break;
13
+ default:
14
+ tag = 'default';
15
+ }
16
+ return `label label-${tag} result-filter-tag`;
17
+ };
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import PuppetLogs from './Puppet';
5
+ import AnsibleLogs from './Ansible';
6
+
7
+ const ReportLogs = ({ format, logs, meta }) => {
8
+ switch (format) {
9
+ case 'puppet':
10
+ return <PuppetLogs logs={logs} environment={meta.environment} />;
11
+ case 'ansible':
12
+ return <AnsibleLogs logs={logs} checkMode={meta.checkMode} />;
13
+ default:
14
+ return <></>;
15
+ }
16
+ };
17
+
18
+ ReportLogs.propTypes = {
19
+ format: PropTypes.string.isRequired,
20
+ logs: PropTypes.array,
21
+ meta: PropTypes.object,
22
+ };
23
+
24
+ ReportLogs.defaultProps = {
25
+ logs: [],
26
+ meta: {},
27
+ };
28
+
29
+ export default ReportLogs;