feat(dashboard/overview): display missing patches (#1191)

Fixes #1148
This commit is contained in:
ABHAMON Ronan
2016-07-01 15:28:07 +02:00
committed by Julien Fontanet
parent 34006bcbf6
commit e23b8a6891
3 changed files with 253 additions and 74 deletions

View File

@@ -142,7 +142,14 @@ export default class SortedTable extends Component {
createMatcher
)
),
() => this._getSelectedColumn().sortCriteria,
createSelector(
() => this._getSelectedColumn().sortCriteria,
() => this.props.userData,
(sortCriteria, userData) =>
(typeof sortCriteria === 'function')
? object => sortCriteria(object, userData)
: sortCriteria
),
() => this.state.sortOrder
)

View File

@@ -0,0 +1,4 @@
.bigCardContent {
font-size: 4em;
text-align: center;
}

View File

@@ -1,14 +1,21 @@
import _ from 'intl'
import ActionButton from 'action-button'
import ChartistGraph from 'react-chartist'
import Component from 'base-component'
import forEach from 'lodash/forEach'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import Upgrade from 'xoa-upgrade'
import React, { Component } from 'react'
import React from 'react'
import SortedTable from 'sorted-table'
import { ButtonGroup } from 'react-bootstrap-4/lib'
import { Card, CardBlock, CardHeader } from 'card'
import { Container, Row, Col } from 'grid'
import {
createCollectionWrapper,
createCounter,
createFilter,
createGetObjectsOfType,
createSelector,
createTop
@@ -18,10 +25,164 @@ import {
formatSize
} from 'utils'
import {
getHostMissingPatches,
installAllHostPatches,
isSrWritable,
subscribeUsers
} from 'xo'
import styles from './index.css'
// ===================================================================
const MISSING_PATCHES_COLUMNS = [
{
name: _('srPool'),
itemRenderer: (host, { pools }) => pools[host.$pool].name_label,
sortCriteria: (host, { pools }) => pools[host.$pool].name_label
},
{
name: _('srHost'),
itemRenderer: host => host.name_label,
sortCriteria: host => host.name_label
},
{
name: _('hostDescription'),
itemRenderer: host => host.name_description,
sortCriteria: host => host.name_description
},
{
name: _('hostMissingPatches'),
itemRenderer: (host, { missingPatches }) => missingPatches[host.id],
sortCriteria: (host, { missingPatches }) => missingPatches[host.id]
},
{
name: _('patchUpdateButton'),
itemRenderer: host => (
<ActionButton
btnStyle='primary'
handler={installAllHostPatches}
handlerParam={host}
icon='host-patch-update'
/>
)
}
]
// ===================================================================
@connectStore(() => {
const getPools = createGetObjectsOfType('pool')
const getHosts = createGetObjectsOfType('host').sort()
return {
pools: getPools,
hosts: getHosts
}
})
class MissingPatchesPanel extends Component {
constructor (props) {
super(props)
this.state.missingPatches = {}
}
_getHosts = createFilter(
() => this.props.hosts,
() => this.state.missingPatches,
[ (host, missingPatches) => missingPatches[host.id] ]
)
_refreshMissingPatches = async () => {
const missingPatches = {}
await Promise.all(
map(this.props.hosts, host => (
getHostMissingPatches(host).then(patches => {
missingPatches[host.id] = patches.length
})
))
)
this.setState({ missingPatches })
}
_installAllMissingPatches = () => (
Promise.all(
map(this._getHosts(), installAllHostPatches)
)
)
componentWillMount () {
this._refreshMissingPatches()
}
componentWillReceiveProps (nextProps) {
forEach(nextProps.hosts, (host, push) => {
const { id } = host
if (this.state.missingPatches[id] !== undefined) {
return
}
this.setState({
missingPatches: {
...this.state.missingPatches,
[id]: 0
}
})
getHostMissingPatches(host).then(patches => {
this.setState({
missingPatches: {
...this.state.missingPatches,
[id]: patches.length
}
})
})
})
}
render () {
const hosts = this._getHosts()
const noPatches = isEmpty(hosts)
return (
<Card>
<CardHeader>
<Icon icon='host-patch-update' /> {_('update')}
<ButtonGroup className='pull-right'>
<ActionButton
btnStyle='secondary'
handler={this._refreshMissingPatches}
icon='refresh'
/>
<ActionButton
btnStyle='primary'
disabled={noPatches}
handler={this._installAllMissingPatches}
icon='host-patch-update'
/>
</ButtonGroup>
</CardHeader>
<CardBlock>
{!noPatches
? (
<SortedTable
collection={hosts}
columns={MISSING_PATCHES_COLUMNS}
userData={{
missingPatches: this.state.missingPatches,
pools: this.props.pools
}}
/>
) : <p>{_('patchNothing')}</p>
}
</CardBlock>
</Card>
)
}
}
// ===================================================================
@connectStore(() => {
const getHosts = createGetObjectsOfType('host')
const getVms = createGetObjectsOfType('VM')
@@ -137,43 +298,43 @@ export default class Overview extends Component {
? <Container>
<Row>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='pool' /> {_('poolPanel', { pools: this.props.nPools })}
</div>
<div className='card-block-dashboard'>
<p>{this.props.nPools}</p>
</div>
</div>
</CardHeader>
<CardBlock>
<p className={styles.bigCardContent}>{this.props.nPools}</p>
</CardBlock>
</Card>
</Col>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='host' /> {_('hostPanel', { hosts: this.props.nHosts })}
</div>
<div className='card-block-dashboard'>
<p>{this.props.nHosts}</p>
</div>
</div>
</CardHeader>
<CardBlock>
<p className={styles.bigCardContent}>{this.props.nHosts}</p>
</CardBlock>
</Card>
</Col>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='vm' /> {_('vmPanel', { vms: this.props.nVms })}
</div>
<div className='card-block-dashboard'>
<p>{this.props.nVms}</p>
</div>
</div>
</CardHeader>
<CardBlock>
<p className={styles.bigCardContent}>{this.props.nVms}</p>
</CardBlock>
</Card>
</Col>
</Row>
<Row>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='memory' /> {_('memoryStatePanel')}
</div>
<div className='card-block'>
</CardHeader>
<CardBlock>
<ChartistGraph
data={{
labels: ['Used Memory', 'Total Memory'],
@@ -182,15 +343,15 @@ export default class Overview extends Component {
options={{ donut: true, donutWidth: 40, showLabel: false }}
type='Pie' />
<p className='text-xs-center'>{formatSize(this.props.hostMetrics.memoryUsage)} ({_('ofUsage')} {formatSize(this.props.hostMetrics.memoryTotal)})</p>
</div>
</div>
</CardBlock>
</Card>
</Col>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='cpu' /> {_('cpuStatePanel')}
</div>
<div className='card-block'>
</CardHeader>
<CardBlock>
<div className='ct-chart'>
<ChartistGraph
data={{
@@ -201,15 +362,15 @@ export default class Overview extends Component {
type='Bar' />
<p className='text-xs-center'>{this.props.vmMetrics.vcpus} vCPUS ({_('ofUsage')} {this.props.hostMetrics.cpus} CPUs)</p>
</div>
</div>
</div>
</CardBlock>
</Card>
</Col>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='disk' /> {_('srUsageStatePanel')}
</div>
<div className='card-block'>
</CardHeader>
<CardBlock>
<div className='ct-chart'>
<ChartistGraph
data={{
@@ -220,49 +381,51 @@ export default class Overview extends Component {
type='Pie' />
<p className='text-xs-center'>{formatSize(this.props.srMetrics.srUsage)} ({_('ofUsage')} {formatSize(this.props.srMetrics.srTotal)})</p>
</div>
</div>
</div>
</CardBlock>
</Card>
</Col>
</Row>
<Row>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='alarm' /> {_('alarmMessage')}
</div>
<div className='card-block-dashboard'>
<p className={this.props.nAlarmMessages > 0 ? 'text-warning' : ''}>{this.props.nAlarmMessages}</p>
</div>
</div>
</CardHeader>
<CardBlock>
<p className={`${styles.bigCardContent} ${this.props.nAlarmMessages > 0 ? 'text-warning' : ''}`}>
{this.props.nAlarmMessages}
</p>
</CardBlock>
</Card>
</Col>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='task' /> {_('taskStatePanel')}
</div>
<div className='card-block-dashboard'>
<p>{this.props.nTasks}</p>
</div>
</div>
</CardHeader>
<CardBlock>
<p className={styles.bigCardContent}>{this.props.nTasks}</p>
</CardBlock>
</Card>
</Col>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='user' /> {_('usersStatePanel')}
</div>
<div className='card-block-dashboard'>
<p>{nUsers}</p>
</div>
</div>
</CardHeader>
<CardBlock>
<p className={styles.bigCardContent}>{nUsers}</p>
</CardBlock>
</Card>
</Col>
</Row>
<Row>
<Col mediumSize={4}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='vm-force-shutdown' /> {_('vmStatePanel')}
</div>
<div className='card-block'>
</CardHeader>
<CardBlock>
<ChartistGraph
data={{
labels: ['Running', 'Halted', 'Other'],
@@ -271,15 +434,15 @@ export default class Overview extends Component {
options={{ showLabel: false }}
type='Pie' />
<p className='text-xs-center'>{this.props.vmMetrics.running} running ({this.props.vmMetrics.halted} halted)</p>
</div>
</div>
</CardBlock>
</Card>
</Col>
<Col mediumSize={8}>
<div className='card-dashboard'>
<div className='card-header-dashboard'>
<Card>
<CardHeader>
<Icon icon='disk' /> {_('srTopUsageStatePanel')}
</div>
<div className='card-block'>
</CardHeader>
<CardBlock>
<ChartistGraph
style={{strokeWidth: '30px'}}
data={{
@@ -288,8 +451,13 @@ export default class Overview extends Component {
}}
options={{ showLabel: false, showGrid: false, distributeSeries: true, high: 100 }}
type='Bar' />
</div>
</div>
</CardBlock>
</Card>
</Col>
</Row>
<Row>
<Col>
<MissingPatchesPanel />
</Col>
</Row>
</Container>