foreman_host_reports 0.0.4 → 1.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/README.md +67 -447
- data/app/controllers/api/v2/host_reports_controller.rb +39 -20
- data/app/controllers/concerns/foreman_host_reports/controller/hosts_controller_extensions.rb +18 -0
- data/app/controllers/concerns/foreman_host_reports/controller/parameters/host_report.rb +1 -1
- data/app/controllers/host_reports_controller.rb +32 -2
- data/app/helpers/concerns/foreman_host_reports/hosts_helper_extensions.rb +25 -0
- data/app/models/concerns/foreman_host_reports/host_extensions.rb +6 -0
- data/app/models/host_report.rb +15 -0
- data/app/models/host_status/host_report_status.rb +185 -0
- data/app/views/api/v2/host_reports/main.json.rabl +1 -2
- data/config/routes.rb +2 -4
- data/db/migrate/20220113064436_rename_status_summaries.rb +12 -0
- data/lib/foreman_host_reports/engine.rb +11 -5
- data/lib/foreman_host_reports/version.rb +1 -1
- data/test/controllers/api/v2/host_reports_controller_test.rb +30 -67
- data/test/factories/foreman_host_reports_factories.rb +13 -5
- data/test/model/host_report_status_test.rb +204 -0
- data/test/test_plugin_helper.rb +4 -2
- data/webpack/__mocks__/foremanReact/components/Pagination/index.js +2 -0
- data/webpack/fills.js +23 -0
- data/webpack/global_index.js +2 -0
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/statusFormatter.js +3 -4
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.js +1 -1
- data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/HostReportsTable.js +2 -10
- data/webpack/src/Router/HostReports/IndexPage/IndexPage.js +0 -1
- data/webpack/src/Router/HostReports/IndexPage/IndexPageActions.js +5 -4
- data/webpack/src/Router/HostReports/IndexPage/IndexPageHelpers.js +1 -1
- data/webpack/src/Router/HostReports/IndexPage/__tests__/HostReportsIndexPage.test.js +3 -4
- data/webpack/src/Router/HostReports/IndexPage/__tests__/__snapshots__/HostReportsIndexPage.test.js.snap +4 -6
- data/webpack/src/Router/HostReports/IndexPage/constants.js +4 -3
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Ansible.js +62 -27
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/EmptyLogsRow.js +27 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/RawMsgModal.js +41 -0
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Puppet.js +38 -37
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/index.js +10 -3
- data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogsFilter/index.js +56 -65
- data/webpack/src/Router/HostReports/ShowPage/ShowPage.js +34 -8
- data/webpack/src/Router/HostReports/constants.js +2 -0
- data/webpack/src/components/ReportsTab/ReportsTable.js +117 -0
- data/webpack/src/components/ReportsTab/helpers.js +155 -0
- data/webpack/src/components/ReportsTab/index.js +132 -0
- metadata +18 -19
- data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +0 -4
@@ -1,8 +1,9 @@
|
|
1
1
|
import { getControllerSearchProps } from 'foremanReact/constants';
|
2
2
|
|
3
|
-
export const HOST_REPORTS_SEARCH_PROPS =
|
4
|
-
'host_reports'
|
5
|
-
|
3
|
+
export const HOST_REPORTS_SEARCH_PROPS = {
|
4
|
+
...getControllerSearchProps('/host_reports'),
|
5
|
+
controller: 'host_reports',
|
6
|
+
};
|
6
7
|
|
7
8
|
export const HOST_REPORTS_PATH = '/host_reports';
|
8
9
|
export const HOST_REPORTS_API_PATH =
|
@@ -1,16 +1,49 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
|
4
|
-
import { Alert, AlertActionCloseButton } from '@patternfly/react-core';
|
4
|
+
import { Alert, AlertActionCloseButton, Button } from '@patternfly/react-core';
|
5
|
+
|
6
|
+
import {
|
7
|
+
TableComposable,
|
8
|
+
Thead,
|
9
|
+
Tbody,
|
10
|
+
Tr,
|
11
|
+
Th,
|
12
|
+
Td,
|
13
|
+
} from '@patternfly/react-table';
|
5
14
|
|
6
15
|
import { translate as __ } from 'foremanReact/common/I18n';
|
16
|
+
import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks';
|
17
|
+
|
18
|
+
import RawMsgModal from './Components/RawMsgModal';
|
19
|
+
import EmptyLogsRow from './Components/EmptyLogsRow';
|
7
20
|
|
8
21
|
import { msgLevelClasses } from './helpers';
|
9
22
|
|
10
|
-
|
23
|
+
import { RAW_MSG_MODAL_ID } from '../../../constants';
|
24
|
+
|
25
|
+
const AnsibleLogs = ({ logs, checkMode, onClear }) => {
|
11
26
|
const [alertVisibility, setAlertVisibility] = useState(true);
|
27
|
+
const [selectedMsg, setSelectedMsg] = useState(0);
|
28
|
+
const { setModalOpen: setRawModalOpen } = useForemanModal({
|
29
|
+
id: RAW_MSG_MODAL_ID,
|
30
|
+
});
|
31
|
+
|
32
|
+
const rawMsg = idx => {
|
33
|
+
const onClick = () => {
|
34
|
+
setSelectedMsg(idx);
|
35
|
+
setRawModalOpen();
|
36
|
+
};
|
37
|
+
return (
|
38
|
+
<Button isSmall onClick={onClick} variant="secondary">
|
39
|
+
{__('Show')}
|
40
|
+
</Button>
|
41
|
+
);
|
42
|
+
};
|
43
|
+
|
12
44
|
return (
|
13
45
|
<>
|
46
|
+
<RawMsgModal body={logs[selectedMsg]} />
|
14
47
|
{checkMode && alertVisibility ? (
|
15
48
|
<Alert
|
16
49
|
variant="info"
|
@@ -23,50 +56,52 @@ const AnsibleLogs = ({ logs, checkMode }) => {
|
|
23
56
|
{__('Notice that ansible roles run in check mode.')}
|
24
57
|
</Alert>
|
25
58
|
) : null}
|
26
|
-
<
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
<
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
</thead>
|
37
|
-
<tbody>
|
59
|
+
<TableComposable id="report_log" variant="compact">
|
60
|
+
<Thead noWrap>
|
61
|
+
<Tr>
|
62
|
+
<Th> {__('Level')} </Th>
|
63
|
+
<Th> {__('Task')} </Th>
|
64
|
+
<Th> {__('Message')} </Th>
|
65
|
+
<Th> {__('Raw data')} </Th>
|
66
|
+
</Tr>
|
67
|
+
</Thead>
|
68
|
+
<Tbody>
|
38
69
|
{logs.map((log, idx) => (
|
39
|
-
<
|
40
|
-
<
|
70
|
+
<Tr key={`tr-${idx + 1}`}>
|
71
|
+
<Td>
|
41
72
|
<span className={msgLevelClasses(log.level)}>{log.level}</span>
|
42
|
-
</
|
43
|
-
<
|
73
|
+
</Td>
|
74
|
+
<Td>{log.task.name}</Td>
|
44
75
|
{Array.isArray(log.friendlyMessage) ? (
|
45
|
-
<
|
76
|
+
<Td>
|
46
77
|
<ul>
|
47
78
|
{log.friendlyMessage.map((msg, i) => (
|
48
79
|
<li key={`li-${i + 1}`}>{msg}</li>
|
49
80
|
))}
|
50
81
|
</ul>
|
51
|
-
</
|
82
|
+
</Td>
|
52
83
|
) : (
|
53
|
-
<
|
84
|
+
<Td>{log.friendlyMessage}</Td>
|
54
85
|
)}
|
55
|
-
|
86
|
+
<Td>{rawMsg(idx)}</Td>
|
87
|
+
</Tr>
|
56
88
|
))}
|
57
89
|
{logs.length === 0 ? (
|
58
|
-
<
|
59
|
-
<
|
60
|
-
|
90
|
+
<Tr key="tr-0">
|
91
|
+
<Td colSpan={4}>
|
92
|
+
<EmptyLogsRow onClear={onClear} />
|
93
|
+
</Td>
|
94
|
+
</Tr>
|
61
95
|
) : null}
|
62
|
-
</
|
63
|
-
</
|
96
|
+
</Tbody>
|
97
|
+
</TableComposable>
|
64
98
|
</>
|
65
99
|
);
|
66
100
|
};
|
67
101
|
|
68
102
|
AnsibleLogs.propTypes = {
|
69
103
|
logs: PropTypes.array.isRequired,
|
104
|
+
onClear: PropTypes.func.isRequired,
|
70
105
|
checkMode: PropTypes.bool,
|
71
106
|
};
|
72
107
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
|
4
|
+
|
5
|
+
import DefaultEmptyState from 'foremanReact/components/common/EmptyState';
|
6
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
7
|
+
|
8
|
+
const EmptyLogsRow = ({ onClear }) => (
|
9
|
+
<DefaultEmptyState
|
10
|
+
header={__('No results found')}
|
11
|
+
icon={<SearchIcon />}
|
12
|
+
description={__(
|
13
|
+
'No results match this filter criteria. Clear all filters and try again.'
|
14
|
+
)}
|
15
|
+
action={{
|
16
|
+
title: __('Clear all filters'),
|
17
|
+
url: '#',
|
18
|
+
onClick: onClear,
|
19
|
+
}}
|
20
|
+
/>
|
21
|
+
);
|
22
|
+
|
23
|
+
EmptyLogsRow.propTypes = {
|
24
|
+
onClear: PropTypes.func.isRequired,
|
25
|
+
};
|
26
|
+
|
27
|
+
export default EmptyLogsRow;
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import JSONTree from 'react-json-tree';
|
4
|
+
import Immutable from 'seamless-immutable';
|
5
|
+
|
6
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
7
|
+
import ForemanModal from 'foremanReact/components/ForemanModal';
|
8
|
+
|
9
|
+
import { RAW_MSG_MODAL_ID } from '../../../../constants';
|
10
|
+
|
11
|
+
const RawMsgModal = ({ body }) => {
|
12
|
+
const theme = {
|
13
|
+
scheme: 'foreman',
|
14
|
+
backgroundColor: 'rgba(0, 0, 0, 255)',
|
15
|
+
base00: 'rgba(0, 0, 0, 0)',
|
16
|
+
};
|
17
|
+
return (
|
18
|
+
<ForemanModal
|
19
|
+
id={RAW_MSG_MODAL_ID}
|
20
|
+
title={__('Raw data')}
|
21
|
+
backdrop="static"
|
22
|
+
enforceFocus
|
23
|
+
>
|
24
|
+
<JSONTree
|
25
|
+
data={Immutable.asMutable(body, { deep: true })}
|
26
|
+
hideRoot
|
27
|
+
theme={theme}
|
28
|
+
/>
|
29
|
+
</ForemanModal>
|
30
|
+
);
|
31
|
+
};
|
32
|
+
|
33
|
+
RawMsgModal.propTypes = {
|
34
|
+
body: PropTypes.object,
|
35
|
+
};
|
36
|
+
|
37
|
+
RawMsgModal.defaultProps = {
|
38
|
+
body: {},
|
39
|
+
};
|
40
|
+
|
41
|
+
export default RawMsgModal;
|
@@ -2,13 +2,24 @@ import React from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
3
3
|
import PropTypes from 'prop-types';
|
4
4
|
|
5
|
-
import {
|
5
|
+
import {
|
6
|
+
TableComposable,
|
7
|
+
Thead,
|
8
|
+
Tbody,
|
9
|
+
Tr,
|
10
|
+
Th,
|
11
|
+
Td,
|
12
|
+
} from '@patternfly/react-table';
|
13
|
+
|
14
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
6
15
|
import * as diffModalActions from 'foremanReact/components/ConfigReports/DiffModal/DiffModalActions';
|
7
16
|
import DiffModal from 'foremanReact/components/ConfigReports/DiffModal';
|
8
17
|
|
18
|
+
import EmptyLogsRow from './Components/EmptyLogsRow';
|
19
|
+
|
9
20
|
import { msgLevelClasses } from './helpers';
|
10
21
|
|
11
|
-
const PuppetLogs = ({ logs,
|
22
|
+
const PuppetLogs = ({ logs, onClear }) => {
|
12
23
|
const dispatch = useDispatch();
|
13
24
|
const showDiff = (e, diff, title) => {
|
14
25
|
e.preventDefault();
|
@@ -18,31 +29,23 @@ const PuppetLogs = ({ logs, environment }) => {
|
|
18
29
|
return (
|
19
30
|
<>
|
20
31
|
<DiffModal />
|
21
|
-
|
22
|
-
<
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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>
|
32
|
+
<TableComposable id="report_log" variant="compact">
|
33
|
+
<Thead noWrap>
|
34
|
+
<Tr>
|
35
|
+
<Th className="col col-md"> {__('Level')} </Th>
|
36
|
+
<Th className="col col-md-3"> {__('Resource')} </Th>
|
37
|
+
<Th className="col col-md-9"> {__('Message')} </Th>
|
38
|
+
</Tr>
|
39
|
+
</Thead>
|
40
|
+
<Tbody>
|
38
41
|
{logs.map((log, i) => (
|
39
|
-
<
|
40
|
-
<
|
42
|
+
<Tr key={`tr-${i + 1}`}>
|
43
|
+
<Td>
|
41
44
|
<span className={msgLevelClasses(log[0])}>{log[0]}</span>
|
42
|
-
</
|
43
|
-
<
|
45
|
+
</Td>
|
46
|
+
<Td className="break-me">{log[1]}</Td>
|
44
47
|
{log[2].startsWith('\n---') ? (
|
45
|
-
<
|
48
|
+
<Td className="break-me">
|
46
49
|
<a
|
47
50
|
onClick={e =>
|
48
51
|
showDiff(e, log[2], /File\[(.*?)\]/.exec(log[1])[1])
|
@@ -50,30 +53,28 @@ const PuppetLogs = ({ logs, environment }) => {
|
|
50
53
|
>
|
51
54
|
{__('Show Diff')}
|
52
55
|
</a>
|
53
|
-
</
|
56
|
+
</Td>
|
54
57
|
) : (
|
55
|
-
<
|
58
|
+
<Td className="break-me">{log[2]}</Td>
|
56
59
|
)}
|
57
|
-
</
|
60
|
+
</Tr>
|
58
61
|
))}
|
59
62
|
{logs.length === 0 ? (
|
60
|
-
<
|
61
|
-
<
|
62
|
-
|
63
|
+
<Tr key="tr-0">
|
64
|
+
<Td colSpan={3}>
|
65
|
+
<EmptyLogsRow onClear={onClear} />
|
66
|
+
</Td>
|
67
|
+
</Tr>
|
63
68
|
) : null}
|
64
|
-
</
|
65
|
-
</
|
69
|
+
</Tbody>
|
70
|
+
</TableComposable>
|
66
71
|
</>
|
67
72
|
);
|
68
73
|
};
|
69
74
|
|
70
75
|
PuppetLogs.propTypes = {
|
71
76
|
logs: PropTypes.array.isRequired,
|
72
|
-
|
73
|
-
};
|
74
|
-
|
75
|
-
PuppetLogs.defaultProps = {
|
76
|
-
environment: null,
|
77
|
+
onClear: PropTypes.func.isRequired,
|
77
78
|
};
|
78
79
|
|
79
80
|
export default PuppetLogs;
|
@@ -4,12 +4,18 @@ import PropTypes from 'prop-types';
|
|
4
4
|
import PuppetLogs from './Puppet';
|
5
5
|
import AnsibleLogs from './Ansible';
|
6
6
|
|
7
|
-
const ReportLogs = ({ format, logs, meta }) => {
|
7
|
+
const ReportLogs = ({ format, logs, meta, onFilterClear }) => {
|
8
8
|
switch (format) {
|
9
9
|
case 'puppet':
|
10
|
-
return <PuppetLogs logs={logs}
|
10
|
+
return <PuppetLogs logs={logs} onClear={onFilterClear} />;
|
11
11
|
case 'ansible':
|
12
|
-
return
|
12
|
+
return (
|
13
|
+
<AnsibleLogs
|
14
|
+
logs={logs}
|
15
|
+
checkMode={meta.checkMode}
|
16
|
+
onClear={onFilterClear}
|
17
|
+
/>
|
18
|
+
);
|
13
19
|
default:
|
14
20
|
return <></>;
|
15
21
|
}
|
@@ -17,6 +23,7 @@ const ReportLogs = ({ format, logs, meta }) => {
|
|
17
23
|
|
18
24
|
ReportLogs.propTypes = {
|
19
25
|
format: PropTypes.string.isRequired,
|
26
|
+
onFilterClear: PropTypes.func.isRequired,
|
20
27
|
logs: PropTypes.array,
|
21
28
|
meta: PropTypes.object,
|
22
29
|
};
|
@@ -1,14 +1,23 @@
|
|
1
|
-
import React, { useState, useEffect
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
|
6
|
-
import {
|
6
|
+
import {
|
7
|
+
Select,
|
8
|
+
SelectOption,
|
9
|
+
SelectVariant,
|
10
|
+
Toolbar,
|
11
|
+
ToolbarContent,
|
12
|
+
ToolbarToggleGroup,
|
13
|
+
ToolbarGroup,
|
14
|
+
ToolbarItem,
|
15
|
+
} from '@patternfly/react-core';
|
16
|
+
import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';
|
7
17
|
|
8
18
|
import ReportLogs from '../ReportLogs';
|
9
19
|
|
10
|
-
const ReportLogsFilter = ({ format,
|
11
|
-
const reportedAtLocal = new Date(reportedAt);
|
20
|
+
const ReportLogsFilter = ({ format, meta }) => {
|
12
21
|
const { logs } = meta;
|
13
22
|
const filterItems = [
|
14
23
|
{ text: __('All messages'), accepts: [] },
|
@@ -19,36 +28,17 @@ const ReportLogsFilter = ({ format, reportedAt, meta }) => {
|
|
19
28
|
{ text: __('Warnings and errors'), accepts: ['warning', 'err'] },
|
20
29
|
{ text: __('Errors only'), accepts: ['err'] },
|
21
30
|
];
|
22
|
-
const [currentItem, setCurrentItem] = useState(filterItems[0]);
|
23
|
-
const [currentFilterItems, setCurrentFilterItems] = useState(filterItems);
|
24
|
-
const [searchValue, setSearchValue] = useState('');
|
25
|
-
const [isOpen, setIsOpen] = useState(false);
|
26
31
|
const [filteredLogs, setFilteredLogs] = useState(logs);
|
32
|
+
const [isOpen, setIsOpen] = useState(false);
|
33
|
+
const [selected, setSelected] = useState(filterItems[0]);
|
27
34
|
|
28
|
-
|
29
|
-
|
30
|
-
const filtered =
|
31
|
-
searchValue === ''
|
32
|
-
? filterItems
|
33
|
-
: filterItems.filter(item =>
|
34
|
-
item.text.toLowerCase().includes(searchValue.toLowerCase())
|
35
|
-
);
|
36
|
-
setCurrentFilterItems(filtered || []);
|
37
|
-
}, [searchValue]);
|
38
|
-
/* eslint-enable react-hooks/exhaustive-deps */
|
39
|
-
|
40
|
-
useEffect(() => {
|
41
|
-
onSearchButtonClick();
|
42
|
-
}, [searchValue, onSearchButtonClick]);
|
43
|
-
|
44
|
-
const onToggle = (event, newIsOpen) => {
|
45
|
-
setIsOpen(newIsOpen);
|
46
|
-
};
|
47
|
-
const onSelect = () => {
|
48
|
-
setIsOpen(!isOpen);
|
35
|
+
const onToggle = isExpanded => {
|
36
|
+
setIsOpen(isExpanded);
|
49
37
|
};
|
50
|
-
const
|
51
|
-
|
38
|
+
const onSelect = (event, selection) => {
|
39
|
+
const item = filterItems.find(i => i.text === selection);
|
40
|
+
setSelected(item);
|
41
|
+
setIsOpen(false);
|
52
42
|
};
|
53
43
|
const filterLogs = (toFilter, accepts) => {
|
54
44
|
if (!accepts.length) return toFilter;
|
@@ -57,48 +47,49 @@ const ReportLogsFilter = ({ format, reportedAt, meta }) => {
|
|
57
47
|
};
|
58
48
|
|
59
49
|
useEffect(() => {
|
60
|
-
setFilteredLogs(filterLogs(logs,
|
61
|
-
}, [logs,
|
50
|
+
setFilteredLogs(filterLogs(logs, selected.accepts));
|
51
|
+
}, [logs, selected]);
|
52
|
+
|
53
|
+
const onFilterClear = () => {
|
54
|
+
setSelected(filterItems[0]);
|
55
|
+
};
|
62
56
|
|
63
57
|
return (
|
64
58
|
<>
|
65
|
-
<
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
</p>
|
94
|
-
<ReportLogs format={format} logs={filteredLogs} meta={meta} />
|
59
|
+
<Toolbar id="logs-toolbar">
|
60
|
+
<ToolbarContent>
|
61
|
+
<ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint="lg">
|
62
|
+
<ToolbarGroup variant="filter-group">
|
63
|
+
<ToolbarItem variant="label">{__('Message')}</ToolbarItem>
|
64
|
+
<ToolbarItem>
|
65
|
+
<Select
|
66
|
+
variant={SelectVariant.single}
|
67
|
+
onToggle={onToggle}
|
68
|
+
onSelect={onSelect}
|
69
|
+
selections={selected.text}
|
70
|
+
isOpen={isOpen}
|
71
|
+
>
|
72
|
+
{filterItems.map((option, index) => (
|
73
|
+
<SelectOption key={index} value={option.text} />
|
74
|
+
))}
|
75
|
+
</Select>
|
76
|
+
</ToolbarItem>
|
77
|
+
</ToolbarGroup>
|
78
|
+
</ToolbarToggleGroup>
|
79
|
+
</ToolbarContent>
|
80
|
+
</Toolbar>
|
81
|
+
<ReportLogs
|
82
|
+
format={format}
|
83
|
+
logs={filteredLogs}
|
84
|
+
meta={meta}
|
85
|
+
onFilterClear={onFilterClear}
|
86
|
+
/>
|
95
87
|
</>
|
96
88
|
);
|
97
89
|
};
|
98
90
|
|
99
91
|
ReportLogsFilter.propTypes = {
|
100
92
|
format: PropTypes.string.isRequired,
|
101
|
-
reportedAt: PropTypes.string.isRequired,
|
102
93
|
meta: PropTypes.object,
|
103
94
|
};
|
104
95
|
|
@@ -1,10 +1,17 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import JSONTree from 'react-json-tree';
|
3
3
|
import PropTypes from 'prop-types';
|
4
|
-
import {
|
4
|
+
import {
|
5
|
+
Button,
|
6
|
+
Grid,
|
7
|
+
GridItem,
|
8
|
+
Toolbar,
|
9
|
+
ToolbarItem,
|
10
|
+
ToolbarContent,
|
11
|
+
} from '@patternfly/react-core';
|
5
12
|
|
6
13
|
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
7
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
14
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
8
15
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
9
16
|
import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks';
|
10
17
|
|
@@ -28,6 +35,7 @@ const HostReportsShowPage = ({
|
|
28
35
|
isLoading,
|
29
36
|
fetchAndPush,
|
30
37
|
}) => {
|
38
|
+
const reportedAtLocal = new Date(reportedAt);
|
31
39
|
const {
|
32
40
|
setModalOpen: setDeleteModalOpen,
|
33
41
|
setModalClosed: setDeleteModalClosed,
|
@@ -77,7 +85,6 @@ const HostReportsShowPage = ({
|
|
77
85
|
const meta = {};
|
78
86
|
switch (format) {
|
79
87
|
case 'puppet':
|
80
|
-
meta.environment = body.environment;
|
81
88
|
meta.logs = body.logs;
|
82
89
|
break;
|
83
90
|
case 'ansible':
|
@@ -124,11 +131,30 @@ const HostReportsShowPage = ({
|
|
124
131
|
) : (
|
125
132
|
<Grid hasGutter>
|
126
133
|
<GridItem>
|
127
|
-
<
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
134
|
+
<Toolbar id="meta-toolbar">
|
135
|
+
<ToolbarContent>
|
136
|
+
<ToolbarItem>
|
137
|
+
{sprintf(
|
138
|
+
__('Reported at %s'),
|
139
|
+
reportedAtLocal.toLocaleString()
|
140
|
+
)}
|
141
|
+
</ToolbarItem>
|
142
|
+
{format === 'puppet' ? (
|
143
|
+
<>
|
144
|
+
<ToolbarItem variant="separator" />
|
145
|
+
<ToolbarItem>
|
146
|
+
{sprintf(
|
147
|
+
__('Puppet Environment: %s'),
|
148
|
+
body.environment
|
149
|
+
)}
|
150
|
+
</ToolbarItem>
|
151
|
+
</>
|
152
|
+
) : null}
|
153
|
+
</ToolbarContent>
|
154
|
+
</Toolbar>
|
155
|
+
</GridItem>
|
156
|
+
<GridItem>
|
157
|
+
<ReportLogsFilter format={format} meta={meta} />
|
132
158
|
</GridItem>
|
133
159
|
{format === 'puppet' ? (
|
134
160
|
<GridItem>
|