diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 9863df346..106ce4d0f 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -19,6 +19,7 @@ - [Jobs] Fix `job.runSequence` method (PR [#5944](https://github.com/vatesfr/xen-orchestra/pull/5944)) - [Netbox] Fix error when testing plugin on versions older than 2.10 (PR [#5963](https://github.com/vatesfr/xen-orchestra/pull/5963)) - [Snapshot] Fix "Create VM from snapshot" creating a template instead of a VM (PR [#5955](https://github.com/vatesfr/xen-orchestra/pull/5955)) +- [Host/Logs] Improve the display of log content (PR [#5943](https://github.com/vatesfr/xen-orchestra/pull/5943)) ### Packages to release diff --git a/packages/xo-web/src/common/utils.js b/packages/xo-web/src/common/utils.js index d580f79cb..12b1e8849 100644 --- a/packages/xo-web/src/common/utils.js +++ b/packages/xo-web/src/common/utils.js @@ -1,7 +1,9 @@ +import fromCallback from 'promise-toolbox/fromCallback' import getStream from 'get-stream' import humanFormat from 'human-format' import React from 'react' import ReadableStream from 'readable-stream' +import xml2js from 'xml2js' import { connect } from 'react-redux' import { createPredicate } from 'value-matcher' import { FormattedDate } from 'react-intl' @@ -9,6 +11,7 @@ import { clone, every, forEach, + get, isEmpty, isFunction, isPlainObject, @@ -215,6 +218,28 @@ function safeHumanFormat(value, opts) { } } +export const formatLogs = logs => + Promise.all( + map(logs, ({ body }, id) => { + const matches = /^value:\s*([0-9.]+)\s+config:\s*([^]*)$/.exec(body) + if (matches === null) { + return + } + + const [, value, xml] = matches + return fromCallback(xml2js.parseString, xml).then((result = {}) => { + const object = mapValues(result.variable, value => get(value, '[0].$.value')) + if (object.name === undefined) { + return + } + + const { name, ...alarmAttributes } = object + + return { name, value, alarmAttributes, id } + }, noop) + }) + ) + export const formatSize = bytes => (bytes != null ? safeHumanFormat(bytes, { scale: 'binary', unit: 'B' }) : 'N/D') export const formatSizeShort = bytes => safeHumanFormat(bytes, { scale: 'binary', unit: 'B', decimals: 0 }) diff --git a/packages/xo-web/src/xo-app/dashboard/health/index.js b/packages/xo-web/src/xo-app/dashboard/health/index.js index 169549117..495c49e44 100644 --- a/packages/xo-web/src/xo-app/dashboard/health/index.js +++ b/packages/xo-web/src/xo-app/dashboard/health/index.js @@ -1,7 +1,6 @@ import _ from 'intl' import Component from 'base-component' import decorate from 'apply-decorators' -import fromCallback from 'promise-toolbox/fromCallback' import { get as getDefined } from '@xen-orchestra/defined' import Icon from 'icon' import Link from 'link' @@ -9,14 +8,13 @@ import NoObjects from 'no-objects' import React from 'react' import SortedTable from 'sorted-table' import Tooltip from 'tooltip' -import xml2js from 'xml2js' import { Network, Sr, Vm } from 'render-xo-item' import { SelectPool } from 'select-objects' import { Container, Row, Col } from 'grid' import { Card, CardHeader, CardBlock } from 'card' import { FormattedRelative, FormattedTime } from 'react-intl' -import { flatten, forEach, get, includes, isEmpty, map, mapValues } from 'lodash' -import { connectStore, formatSize, noop, resolveIds } from 'utils' +import { flatten, forEach, includes, isEmpty, map } from 'lodash' +import { connectStore, formatLogs, formatSize, noop, resolveIds } from 'utils' import { deleteMessage, deleteMessages, @@ -555,26 +553,7 @@ export default class Health extends Component { } _updateAlarms = props => { - Promise.all( - map(props.alertMessages, ({ body }, id) => { - const matches = /^value:\s*([0-9.]+)\s+config:\s*([^]*)$/.exec(body) - if (!matches) { - return - } - - const [, value, xml] = matches - return fromCallback(xml2js.parseString, xml).then(result => { - const object = mapValues(result && result.variable, value => get(value, '[0].$.value')) - if (!object || !object.name) { - return - } - - const { name, ...alarmAttributes } = object - - return { name, value, alarmAttributes, id } - }, noop) - }) - ).then(formattedMessages => { + formatLogs(props.alertMessages).then(formattedMessages => { this.setState({ messages: map(formattedMessages, ({ id, ...formattedMessage }) => ({ formatted: formattedMessage, diff --git a/packages/xo-web/src/xo-app/host/tab-logs.js b/packages/xo-web/src/xo-app/host/tab-logs.js index ae7aa9c7b..10f031def 100644 --- a/packages/xo-web/src/xo-app/host/tab-logs.js +++ b/packages/xo-web/src/xo-app/host/tab-logs.js @@ -6,7 +6,13 @@ import SortedTable from 'sorted-table' import { createPager } from 'selectors' import { Row, Col } from 'grid' import { deleteMessage, deleteMessages } from 'xo' +import { formatLogs } from 'utils' import { FormattedRelative, FormattedTime } from 'react-intl' +import { map } from 'lodash' + +const LOG_BODY_STYLE = { + whiteSpace: 'pre-wrap', +} const LOG_COLUMNS = [ { @@ -34,7 +40,26 @@ const LOG_COLUMNS = [ }, { name: _('logContent'), - itemRenderer: log => log.body, + itemRenderer: ({ formatted, body }) => + formatted !== undefined ? ( +
+ + + {formatted.name} + + {formatted.value} + +
+ {map(formatted.alarmAttributes, (value, label) => ( + + {label} + {value} + + ))} +
+ ) : ( +
{body}
+ ), sortCriteria: log => log.body, }, ] @@ -55,7 +80,7 @@ export default class TabLogs extends Component { super() this.getLogs = createPager( - () => this.props.logs, + () => this.state.logs, () => this.state.page, 10 ) @@ -65,6 +90,26 @@ export default class TabLogs extends Component { } } + componentDidMount() { + this._formatLogs(this.props.logs) + } + + componentDidUpdate(props) { + if (props.logs !== this.props.logs) { + this._formatLogs(this.props.logs) + } + } + + _formatLogs = logs => + formatLogs(logs).then(formattedLogs => { + this.setState({ + logs: map(formattedLogs, ({ id, ...formattedLogs }) => ({ + formatted: formattedLogs, + ...logs[id], + })), + }) + }) + _nextPage = () => this.setState({ page: this.state.page + 1 }) _previousPage = () => this.setState({ page: this.state.page - 1 })