foreman_host_reports 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +545 -0
- data/app/controllers/api/v2/host_reports_controller.rb +98 -0
- data/app/controllers/concerns/foreman_host_reports/controller/parameters/host_report.rb +25 -0
- data/app/controllers/host_reports_controller.rb +46 -0
- data/app/models/concerns/foreman_host_reports/host_extensions.rb +9 -0
- data/app/models/concerns/foreman_host_reports/smart_proxy_extensions.rb +9 -0
- data/app/models/host_report.rb +85 -0
- data/app/models/report_keyword.rb +11 -0
- data/app/views/api/v2/host_reports/base.json.rabl +5 -0
- data/app/views/api/v2/host_reports/create.json.rabl +5 -0
- data/app/views/api/v2/host_reports/index.json.rabl +5 -0
- data/app/views/api/v2/host_reports/main.json.rabl +15 -0
- data/app/views/api/v2/host_reports/show.json.rabl +11 -0
- data/config/routes.rb +33 -0
- data/db/migrate/20210112183526_add_host_reports.rb +36 -0
- data/db/migrate/20210616133601_create_report_keywords.rb +12 -0
- data/lib/foreman_host_reports/engine.rb +68 -0
- data/lib/foreman_host_reports/version.rb +3 -0
- data/lib/foreman_host_reports.rb +4 -0
- data/lib/tasks/foreman_host_reports_tasks.rake +50 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_host_reports.po +19 -0
- data/locale/foreman_host_reports.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/package.json +45 -0
- data/test/controllers/api/v2/host_reports_controller_test.rb +278 -0
- data/test/factories/foreman_host_reports_factories.rb +21 -0
- data/test/snapshots/foreman-web.json +918 -0
- data/test/test_plugin_helper.rb +11 -0
- data/test/unit/foreman_host_reports_test.rb +9 -0
- data/webpack/global_index.js +4 -0
- data/webpack/global_test_setup.js +11 -0
- data/webpack/index.js +0 -0
- data/webpack/src/Router/HostReports/Components/HostReportDeleteModal.js +50 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/FormatCell.js +53 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/FormatCell.scss +4 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/formatCellFormatter.js +6 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/hostReportsToShowFormatter.js +13 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/index.js +4 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/reportToShowFormatter.js +13 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/statusFormatter.js +9 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/HostReportsToShowCell.js +32 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/ReportToShowCell.js +27 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.js +77 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.scss +9 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/formatImages.js +7 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/images/ansible.png +0 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/images/puppet.png +0 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/HostReportsTable.js +85 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/HostReportsTableSchema.js +65 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/index.js +28 -0
- data/webpack/src/Router/HostReports/IndexPage/IndexPage.js +88 -0
- data/webpack/src/Router/HostReports/IndexPage/IndexPageActions.js +55 -0
- data/webpack/src/Router/HostReports/IndexPage/IndexPageHelpers.js +50 -0
- data/webpack/src/Router/HostReports/IndexPage/IndexPageSelectors.js +86 -0
- data/webpack/src/Router/HostReports/IndexPage/constants.js +14 -0
- data/webpack/src/Router/HostReports/IndexPage/index.js +98 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/HostReportMetrics/HostReportMetrics.scss +3 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/HostReportMetrics/index.js +100 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Ansible.js +77 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Puppet.js +79 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/helpers.js +17 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/index.js +29 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogsFilter/index.js +109 -0
- data/webpack/src/Router/HostReports/ShowPage/ShowPage.js +160 -0
- data/webpack/src/Router/HostReports/ShowPage/ShowPageSelectors.js +51 -0
- data/webpack/src/Router/HostReports/ShowPage/index.js +75 -0
- data/webpack/src/Router/HostReports/constants.js +15 -0
- data/webpack/src/Router/routes.js +23 -0
- data/webpack/test_setup.js +17 -0
- 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,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;
|