feat(xo-web/host): format logs (#5943)

See xoa-support#4100
This commit is contained in:
Mathieu 2021-10-27 15:41:29 +02:00 committed by GitHub
parent d2c5b52bf1
commit 9fe1069df0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 76 additions and 26 deletions

View File

@ -19,6 +19,7 @@
- [Jobs] Fix `job.runSequence` method (PR [#5944](https://github.com/vatesfr/xen-orchestra/pull/5944)) - [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)) - [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)) - [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 ### Packages to release

View File

@ -1,7 +1,9 @@
import fromCallback from 'promise-toolbox/fromCallback'
import getStream from 'get-stream' import getStream from 'get-stream'
import humanFormat from 'human-format' import humanFormat from 'human-format'
import React from 'react' import React from 'react'
import ReadableStream from 'readable-stream' import ReadableStream from 'readable-stream'
import xml2js from 'xml2js'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { createPredicate } from 'value-matcher' import { createPredicate } from 'value-matcher'
import { FormattedDate } from 'react-intl' import { FormattedDate } from 'react-intl'
@ -9,6 +11,7 @@ import {
clone, clone,
every, every,
forEach, forEach,
get,
isEmpty, isEmpty,
isFunction, isFunction,
isPlainObject, 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 formatSize = bytes => (bytes != null ? safeHumanFormat(bytes, { scale: 'binary', unit: 'B' }) : 'N/D')
export const formatSizeShort = bytes => safeHumanFormat(bytes, { scale: 'binary', unit: 'B', decimals: 0 }) export const formatSizeShort = bytes => safeHumanFormat(bytes, { scale: 'binary', unit: 'B', decimals: 0 })

View File

@ -1,7 +1,6 @@
import _ from 'intl' import _ from 'intl'
import Component from 'base-component' import Component from 'base-component'
import decorate from 'apply-decorators' import decorate from 'apply-decorators'
import fromCallback from 'promise-toolbox/fromCallback'
import { get as getDefined } from '@xen-orchestra/defined' import { get as getDefined } from '@xen-orchestra/defined'
import Icon from 'icon' import Icon from 'icon'
import Link from 'link' import Link from 'link'
@ -9,14 +8,13 @@ import NoObjects from 'no-objects'
import React from 'react' import React from 'react'
import SortedTable from 'sorted-table' import SortedTable from 'sorted-table'
import Tooltip from 'tooltip' import Tooltip from 'tooltip'
import xml2js from 'xml2js'
import { Network, Sr, Vm } from 'render-xo-item' import { Network, Sr, Vm } from 'render-xo-item'
import { SelectPool } from 'select-objects' import { SelectPool } from 'select-objects'
import { Container, Row, Col } from 'grid' import { Container, Row, Col } from 'grid'
import { Card, CardHeader, CardBlock } from 'card' import { Card, CardHeader, CardBlock } from 'card'
import { FormattedRelative, FormattedTime } from 'react-intl' import { FormattedRelative, FormattedTime } from 'react-intl'
import { flatten, forEach, get, includes, isEmpty, map, mapValues } from 'lodash' import { flatten, forEach, includes, isEmpty, map } from 'lodash'
import { connectStore, formatSize, noop, resolveIds } from 'utils' import { connectStore, formatLogs, formatSize, noop, resolveIds } from 'utils'
import { import {
deleteMessage, deleteMessage,
deleteMessages, deleteMessages,
@ -555,26 +553,7 @@ export default class Health extends Component {
} }
_updateAlarms = props => { _updateAlarms = props => {
Promise.all( formatLogs(props.alertMessages).then(formattedMessages => {
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 => {
this.setState({ this.setState({
messages: map(formattedMessages, ({ id, ...formattedMessage }) => ({ messages: map(formattedMessages, ({ id, ...formattedMessage }) => ({
formatted: formattedMessage, formatted: formattedMessage,

View File

@ -6,7 +6,13 @@ import SortedTable from 'sorted-table'
import { createPager } from 'selectors' import { createPager } from 'selectors'
import { Row, Col } from 'grid' import { Row, Col } from 'grid'
import { deleteMessage, deleteMessages } from 'xo' import { deleteMessage, deleteMessages } from 'xo'
import { formatLogs } from 'utils'
import { FormattedRelative, FormattedTime } from 'react-intl' import { FormattedRelative, FormattedTime } from 'react-intl'
import { map } from 'lodash'
const LOG_BODY_STYLE = {
whiteSpace: 'pre-wrap',
}
const LOG_COLUMNS = [ const LOG_COLUMNS = [
{ {
@ -34,7 +40,26 @@ const LOG_COLUMNS = [
}, },
{ {
name: _('logContent'), name: _('logContent'),
itemRenderer: log => log.body, itemRenderer: ({ formatted, body }) =>
formatted !== undefined ? (
<div>
<Row>
<Col mediumSize={6}>
<strong>{formatted.name}</strong>
</Col>
<Col mediumSize={6}>{formatted.value}</Col>
</Row>
<br />
{map(formatted.alarmAttributes, (value, label) => (
<Row key={label}>
<Col mediumSize={6}>{label}</Col>
<Col mediumSize={6}>{value}</Col>
</Row>
))}
</div>
) : (
<pre style={LOG_BODY_STYLE}>{body}</pre>
),
sortCriteria: log => log.body, sortCriteria: log => log.body,
}, },
] ]
@ -55,7 +80,7 @@ export default class TabLogs extends Component {
super() super()
this.getLogs = createPager( this.getLogs = createPager(
() => this.props.logs, () => this.state.logs,
() => this.state.page, () => this.state.page,
10 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 }) _nextPage = () => this.setState({ page: this.state.page + 1 })
_previousPage = () => this.setState({ page: this.state.page - 1 }) _previousPage = () => this.setState({ page: this.state.page - 1 })