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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -447
  3. data/app/controllers/api/v2/host_reports_controller.rb +39 -20
  4. data/app/controllers/concerns/foreman_host_reports/controller/hosts_controller_extensions.rb +18 -0
  5. data/app/controllers/concerns/foreman_host_reports/controller/parameters/host_report.rb +1 -1
  6. data/app/controllers/host_reports_controller.rb +32 -2
  7. data/app/helpers/concerns/foreman_host_reports/hosts_helper_extensions.rb +25 -0
  8. data/app/models/concerns/foreman_host_reports/host_extensions.rb +6 -0
  9. data/app/models/host_report.rb +15 -0
  10. data/app/models/host_status/host_report_status.rb +185 -0
  11. data/app/views/api/v2/host_reports/main.json.rabl +1 -2
  12. data/config/routes.rb +2 -4
  13. data/db/migrate/20220113064436_rename_status_summaries.rb +12 -0
  14. data/lib/foreman_host_reports/engine.rb +11 -5
  15. data/lib/foreman_host_reports/version.rb +1 -1
  16. data/test/controllers/api/v2/host_reports_controller_test.rb +30 -67
  17. data/test/factories/foreman_host_reports_factories.rb +13 -5
  18. data/test/model/host_report_status_test.rb +204 -0
  19. data/test/test_plugin_helper.rb +4 -2
  20. data/webpack/__mocks__/foremanReact/components/Pagination/index.js +2 -0
  21. data/webpack/fills.js +23 -0
  22. data/webpack/global_index.js +2 -0
  23. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/statusFormatter.js +3 -4
  24. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.js +1 -1
  25. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/HostReportsTable.js +2 -10
  26. data/webpack/src/Router/HostReports/IndexPage/IndexPage.js +0 -1
  27. data/webpack/src/Router/HostReports/IndexPage/IndexPageActions.js +5 -4
  28. data/webpack/src/Router/HostReports/IndexPage/IndexPageHelpers.js +1 -1
  29. data/webpack/src/Router/HostReports/IndexPage/__tests__/HostReportsIndexPage.test.js +3 -4
  30. data/webpack/src/Router/HostReports/IndexPage/__tests__/__snapshots__/HostReportsIndexPage.test.js.snap +4 -6
  31. data/webpack/src/Router/HostReports/IndexPage/constants.js +4 -3
  32. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Ansible.js +62 -27
  33. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/EmptyLogsRow.js +27 -0
  34. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/RawMsgModal.js +41 -0
  35. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Puppet.js +38 -37
  36. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/index.js +10 -3
  37. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogsFilter/index.js +56 -65
  38. data/webpack/src/Router/HostReports/ShowPage/ShowPage.js +34 -8
  39. data/webpack/src/Router/HostReports/constants.js +2 -0
  40. data/webpack/src/components/ReportsTab/ReportsTable.js +117 -0
  41. data/webpack/src/components/ReportsTab/helpers.js +155 -0
  42. data/webpack/src/components/ReportsTab/index.js +132 -0
  43. metadata +18 -19
  44. 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 = getControllerSearchProps(
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
- const AnsibleLogs = ({ logs, checkMode }) => {
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
- <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>
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
- <tr key={`tr-${idx + 1}`}>
40
- <td>
70
+ <Tr key={`tr-${idx + 1}`}>
71
+ <Td>
41
72
  <span className={msgLevelClasses(log.level)}>{log.level}</span>
42
- </td>
43
- <td className="break-me">{log.task.name}</td>
73
+ </Td>
74
+ <Td>{log.task.name}</Td>
44
75
  {Array.isArray(log.friendlyMessage) ? (
45
- <td>
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
- </td>
82
+ </Td>
52
83
  ) : (
53
- <td className="break-me">{log.friendlyMessage}</td>
84
+ <Td>{log.friendlyMessage}</Td>
54
85
  )}
55
- </tr>
86
+ <Td>{rawMsg(idx)}</Td>
87
+ </Tr>
56
88
  ))}
57
89
  {logs.length === 0 ? (
58
- <tr key="tr-0">
59
- <td colSpan="3">{__('Nothing to show')}</td>
60
- </tr>
90
+ <Tr key="tr-0">
91
+ <Td colSpan={4}>
92
+ <EmptyLogsRow onClear={onClear} />
93
+ </Td>
94
+ </Tr>
61
95
  ) : null}
62
- </tbody>
63
- </table>
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 { sprintf, translate as __ } from 'foremanReact/common/I18n';
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, environment }) => {
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
- {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>
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
- <tr key={`tr-${i + 1}`}>
40
- <td>
42
+ <Tr key={`tr-${i + 1}`}>
43
+ <Td>
41
44
  <span className={msgLevelClasses(log[0])}>{log[0]}</span>
42
- </td>
43
- <td className="break-me">{log[1]}</td>
45
+ </Td>
46
+ <Td className="break-me">{log[1]}</Td>
44
47
  {log[2].startsWith('\n---') ? (
45
- <td className="break-me">
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
- </td>
56
+ </Td>
54
57
  ) : (
55
- <td className="break-me">{log[2]}</td>
58
+ <Td className="break-me">{log[2]}</Td>
56
59
  )}
57
- </tr>
60
+ </Tr>
58
61
  ))}
59
62
  {logs.length === 0 ? (
60
- <tr key="tr-0">
61
- <td colSpan="3">{__('Nothing to show')}</td>
62
- </tr>
63
+ <Tr key="tr-0">
64
+ <Td colSpan={3}>
65
+ <EmptyLogsRow onClear={onClear} />
66
+ </Td>
67
+ </Tr>
63
68
  ) : null}
64
- </tbody>
65
- </table>
69
+ </Tbody>
70
+ </TableComposable>
66
71
  </>
67
72
  );
68
73
  };
69
74
 
70
75
  PuppetLogs.propTypes = {
71
76
  logs: PropTypes.array.isRequired,
72
- environment: PropTypes.string,
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} environment={meta.environment} />;
10
+ return <PuppetLogs logs={logs} onClear={onFilterClear} />;
11
11
  case 'ansible':
12
- return <AnsibleLogs logs={logs} checkMode={meta.checkMode} />;
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, useCallback } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
- import { sprintf, translate as __ } from 'foremanReact/common/I18n';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
5
 
6
- import { ContextSelector, ContextSelectorItem } from '@patternfly/react-core';
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, reportedAt, meta }) => {
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
- /* eslint-disable react-hooks/exhaustive-deps */
29
- const onSearchButtonClick = useCallback(() => {
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 onSearchInputChange = (value, event) => {
51
- setSearchValue(event.target.value);
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, currentItem.accepts));
61
- }, [logs, currentItem]);
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
- <span>{__('Show log messages:')}</span>
66
- <br />
67
- <ContextSelector
68
- id="report-logs-filter"
69
- toggleText={currentItem.text}
70
- onSearchInputChange={onSearchInputChange}
71
- isOpen={isOpen}
72
- searchInputValue={searchValue}
73
- onToggle={onToggle}
74
- onSelect={onSelect}
75
- onSearchButtonClick={onSearchButtonClick}
76
- screenReaderLabel="Selected Messages:"
77
- >
78
- {currentFilterItems.map((item, i) => (
79
- <ContextSelectorItem
80
- key={i + 1}
81
- id={`select_messages_${i}`}
82
- onClick={() => {
83
- setCurrentItem(item);
84
- }}
85
- isDisabled={item.text === currentItem.text}
86
- >
87
- {item.text}
88
- </ContextSelectorItem>
89
- ))}
90
- </ContextSelector>
91
- <p className="ra">
92
- {sprintf(__('Reported at %s'), reportedAtLocal.toLocaleString())}
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 { Button, Grid, GridItem } from '@patternfly/react-core';
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
- <ReportLogsFilter
128
- format={format}
129
- reportedAt={reportedAt}
130
- meta={meta}
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>
@@ -13,3 +13,5 @@ export const HOST_REPORT_REQUEST_KEY = 'HOST_REPORT';
13
13
  export const HOST_REPORTS_API_REQUEST_KEY = 'HOST_REPORTS_API';
14
14
 
15
15
  export const HOST_REPORT_DELETE_MODAL_ID = 'hostReportDeleteModal';
16
+
17
+ export const RAW_MSG_MODAL_ID = 'rawMsgModal';