feat(xo-web): display attached VDI snapshots in Health (#2684)

Fixes #2634
This commit is contained in:
Julien Fontanet
2018-02-23 16:30:40 +01:00
committed by GitHub
parent 75521f8757
commit 9a8f9dd1d7
2 changed files with 133 additions and 97 deletions

View File

@@ -18,6 +18,7 @@ import {
isEmpty,
isFunction,
map,
startsWith,
} from 'lodash'
import ActionRowButton from '../action-row-button'
@@ -33,6 +34,7 @@ import Tooltip from '../tooltip'
import { BlockLink } from '../link'
import { Container, Col } from '../grid'
import {
createCollectionWrapper,
createCounter,
createFilter,
createPager,
@@ -287,10 +289,13 @@ const URL_STATE_RE = /^(?:(\d+)(?:_(\d+)(_desc)?)?-)?(.*)$/
paginationContainer: propTypes.func,
rowAction: propTypes.func,
rowLink: propTypes.oneOfType([propTypes.func, propTypes.string]),
rowTransform: propTypes.func,
// DOM node selector like body or .my-class
// The shortcuts will be enabled when the node is focused
shortcutsTarget: propTypes.string,
stateUrlParam: propTypes.string,
// @deprecated, use `data-${key}` instead
userData: propTypes.any,
},
{
@@ -305,6 +310,20 @@ export default class SortedTable extends Component {
constructor (props, context) {
super(props, context)
this._getUserData =
'userData' in props
? () => this.props.userData
: createCollectionWrapper(() => {
const { props } = this
const userData = {}
Object.keys(props).forEach(key => {
if (startsWith(key, 'data-')) {
userData[key.slice(5)] = props[key]
}
})
return userData
})
let selectedColumn = props.defaultColumn
if (selectedColumn == null) {
selectedColumn = findIndex(props.columns, 'default')
@@ -350,17 +369,27 @@ export default class SortedTable extends Component {
this._getSelectedColumn = () =>
this.props.columns[this.state.selectedColumn]
this._getTotalNumberOfItems = createCounter(() => this.props.collection)
let getAllItems = () => this.props.collection
if ('rowTransform' in props) {
getAllItems = createSelector(
getAllItems,
this._getUserData,
() => this.props.rowTransform,
(items, userData, rowTransform) =>
map(items, item => rowTransform(item, userData))
)
}
this._getTotalNumberOfItems = createCounter(getAllItems)
const createMatcher = str => CM.parse(str).createPredicate()
this._getItems = createSort(
createFilter(
() => this.props.collection,
getAllItems,
createSelector(() => this.state.filter, createMatcher)
),
createSelector(
() => this._getSelectedColumn().sortCriteria,
() => this.props.userData,
this._getUserData,
(sortCriteria, userData) =>
typeof sortCriteria === 'function'
? object => sortCriteria(object, userData)
@@ -396,7 +425,7 @@ export default class SortedTable extends Component {
() => this.state.highlighted,
() => this.props.rowLink,
() => this.props.rowAction,
() => this.props.userData,
this._getUserData,
(
visibleItems,
hasGroupedActions,
@@ -643,7 +672,8 @@ export default class SortedTable extends Component {
_renderItem = (item, i) => {
const { props, state } = this
const { actions, individualActions, rowAction, rowLink, userData } = props
const { actions, individualActions, rowAction, rowLink } = props
const userData = this._getUserData()
const hasGroupedActions = this._hasGroupedActions()
const hasIndividualActions =
@@ -736,7 +766,6 @@ export default class SortedTable extends Component {
itemsPerPage,
paginationContainer,
shortcutsTarget,
userData,
} = props
const { all } = state
const groupedActions = this._getGroupedActions()
@@ -773,6 +802,8 @@ export default class SortedTable extends Component {
/>
)
const userData = this._getUserData()
return (
<div>
{shortcutsTarget !== undefined && (

View File

@@ -12,7 +12,7 @@ import Upgrade from 'xoa-upgrade'
import xml2js from 'xml2js'
import { Card, CardHeader, CardBlock } from 'card'
import { confirm } from 'modal'
import { connectStore, formatSize, mapPlus, noop, resolveIds } from 'utils'
import { connectStore, formatSize, noop, resolveIds } from 'utils'
import { Container, Row, Col } from 'grid'
import { flatten, get, includes, isEmpty, map, mapValues } from 'lodash'
import { FormattedRelative, FormattedTime } from 'react-intl'
@@ -174,51 +174,102 @@ const ORPHANED_VDI_COLUMNS = [
},
]
const CONTROL_DOMAIN_VDI_COLUMNS = [
const AttachedVdisTable = [
connectStore({
pools: createGetObjectsOfType('pool'),
srs: createGetObjectsOfType('SR'),
vbds: createGetObjectsOfType('VBD').pick(
createSelector(
createFilter(
createGetObjectsOfType('VM-controller'),
(_, props) => props.poolPredicate
),
createCollectionWrapper(vmControllers =>
flatten(map(vmControllers, '$VBDs'))
)
)
),
vdis: createGetObjectsOfType('VDI'),
vdiSnapshots: createGetObjectsOfType('VDI-snapshot'),
}),
({ columns, rowTransform }) => ({ pools, srs, vbds, vdis, vdiSnapshots }) => (
<NoObjects
collection={vbds}
columns={columns}
component={SortedTable}
data-pools={pools}
data-srs={srs}
data-vdis={vdis}
data-vdiSnapshots={vdiSnapshots}
emptyMessage={_('noControlDomainVdis')}
rowTransform={rowTransform}
/>
),
{
name: _('vdiNameLabel'),
itemRenderer: vdi => vdi && vdi.name_label,
sortCriteria: vdi => vdi && vdi.name_label,
columns: [
{
name: _('vdiNameLabel'),
itemRenderer: ({ vdi }) => (
<span>
{vdi.name_label}
{vdi.type === 'VDI-snapshot' && [
' ',
<Icon icon='vm-snapshot' key='1' />,
]}
</span>
),
sortCriteria: ({ vdi }) => vdi.name_label,
},
{
name: _('vdiNameDescription'),
itemRenderer: ({ vdi }) => vdi.name_description,
sortCriteria: ({ vdi }) => vdi.name_description,
},
{
name: _('vdiPool'),
itemRenderer: ({ pool }) =>
pool === undefined ? null : (
<Link to={`pools/${pool.id}`}>{pool.name_label}</Link>
),
sortCriteria: ({ pool }) => pool != null && pool.name_label,
},
{
name: _('vdiSize'),
itemRenderer: ({ vdi }) => formatSize(vdi.size),
sortCriteria: ({ vdi }) => vdi.size,
},
{
name: _('vdiSr'),
itemRenderer: ({ sr }) =>
sr === undefined ? null : (
<Link to={`srs/${sr.id}`}>{sr.name_label}</Link>
),
sortCriteria: ({ sr }) => sr != null && sr.name_label,
},
{
name: _('vdiAction'),
itemRenderer: ({ vbd }) => (
<ActionRowButton
btnStyle='danger'
handler={deleteVbd}
handlerParam={vbd}
icon='delete'
/>
),
},
],
rowTransform: (vbd, { pools, srs, vdis, vdiSnapshots }) => {
const vdi = vdis[vbd.VDI] || vdiSnapshots[vbd.VDI]
return {
vbd,
vdi,
sr: srs[vdi.$SR],
pool: pools[vbd.$poolId],
}
},
},
{
name: _('vdiNameDescription'),
itemRenderer: vdi => vdi && vdi.name_description,
sortCriteria: vdi => vdi && vdi.name_description,
},
{
name: _('vdiPool'),
itemRenderer: vdi =>
vdi &&
vdi.pool && (
<Link to={`pools/${vdi.pool.id}`}>{vdi.pool.name_label}</Link>
),
sortCriteria: vdi => vdi && vdi.pool && vdi.pool.name_label,
},
{
name: _('vdiSize'),
itemRenderer: vdi => vdi && formatSize(vdi.size),
sortCriteria: vdi => vdi && vdi.size,
},
{
name: _('vdiSr'),
itemRenderer: vdi =>
vdi && vdi.sr && <Link to={`srs/${vdi.sr.id}`}>{vdi.sr.name_label}</Link>,
sortCriteria: vdi => vdi && vdi.sr && vdi.sr.name_label,
},
{
name: _('vdiAction'),
itemRenderer: vdi =>
vdi &&
vdi.vbd && (
<ActionRowButton
btnStyle='danger'
handler={deleteVbd}
handlerParam={vdi.vbd}
icon='delete'
/>
),
},
]
].reduceRight((value, decorator) => decorator(value))
const VM_COLUMNS = [
{
@@ -334,37 +385,6 @@ const ALARM_COLUMNS = [
const getOrphanVdiSnapshots = createGetObjectsOfType('VDI-snapshot')
.filter([_ => !_.$snapshot_of && _.$VBDs.length === 0])
.sort()
const getControlDomainVbds = createGetObjectsOfType('VBD')
.pick(
createSelector(
createGetObjectsOfType('VM-controller'),
createCollectionWrapper(vmControllers =>
flatten(map(vmControllers, '$VBDs'))
)
)
)
.sort()
const getControlDomainVdis = createSelector(
getControlDomainVbds,
createGetObjectsOfType('VDI'),
createGetObjectsOfType('pool'),
createGetObjectsOfType('SR'),
(vbds, vdis, pools, srs) =>
mapPlus(vbds, (vbd, push) => {
const vdi = vdis[vbd.VDI]
if (vdi == null) {
return
}
push({
...vdi,
pool: pools[vbd.$pool],
sr: srs[vdi.$SR],
vbd,
})
})
)
const getOrphanVmSnapshots = createGetObjectsOfType('VM-snapshot')
.filter([snapshot => !snapshot.$snapshot_of])
.sort()
@@ -379,7 +399,6 @@ const ALARM_COLUMNS = [
return {
areObjectsFetched,
alertMessages: getAlertMessages,
controlDomainVdis: getControlDomainVdis,
userSrs: getUserSrs,
vdiOrphaned: getOrphanVdiSnapshots,
vdiSr: getVdiSrs,
@@ -464,11 +483,6 @@ export default class Health extends Component {
this._getPoolPredicate
)
_getControlDomainVdis = createFilter(
() => this.props.controlDomainVdis,
this._getPoolPredicate
)
_getVmOrphaned = createFilter(
() => this.props.vmOrphaned,
this._getPoolPredicate
@@ -569,16 +583,7 @@ export default class Health extends Component {
<Icon icon='disk' /> {_('vdisOnControlDomain')}
</CardHeader>
<CardBlock>
<NoObjects
collection={
props.areObjectsFetched
? this._getControlDomainVdis()
: null
}
columns={CONTROL_DOMAIN_VDI_COLUMNS}
component={SortedTable}
emptyMessage={_('noControlDomainVdis')}
/>
<AttachedVdisTable poolPredicate={this._getPoolPredicate()} />
</CardBlock>
</Card>
</Col>