feat(xo-server/api/sr, xo-web/dashboard/health): list coalescing VDIs (#6120)
See zammad#5224
This commit is contained in:
parent
6e6886a6ba
commit
0975863d98
@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||||
|
|
||||||
|
- [Dashboad/Health] List all VDIs that need coalescing (PR [#6120](https://github.com/vatesfr/xen-orchestra/pull/6120))
|
||||||
|
- [Menu] Show a warning icon when some SRs have more than 10 VDIs to coalesce (PR [#6120](https://github.com/vatesfr/xen-orchestra/pull/6120))
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy.js'
|
import asyncMapSettled from '@xen-orchestra/async-map/legacy.js'
|
||||||
|
import filter from 'lodash/filter.js'
|
||||||
import some from 'lodash/some.js'
|
import some from 'lodash/some.js'
|
||||||
|
|
||||||
import ensureArray from '../_ensureArray.mjs'
|
import ensureArray from '../_ensureArray.mjs'
|
||||||
@ -864,6 +865,16 @@ probeNfsExists.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function getAllUnhealthyVdiChainsLength() {
|
||||||
|
const unhealthyVdiChainsLengthBySr = {}
|
||||||
|
filter(this.objects.all, obj => obj.type === 'SR' && obj.content_type !== 'iso' && obj.size > 0).forEach(sr => {
|
||||||
|
unhealthyVdiChainsLengthBySr[sr.uuid] = this.getXapi(sr).getUnhealthyVdiChainsLength(sr)
|
||||||
|
})
|
||||||
|
return unhealthyVdiChainsLengthBySr
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function getUnhealthyVdiChainsLength({ sr }) {
|
export function getUnhealthyVdiChainsLength({ sr }) {
|
||||||
return this.getXapi(sr).getUnhealthyVdiChainsLength(sr)
|
return this.getXapi(sr).getUnhealthyVdiChainsLength(sr)
|
||||||
}
|
}
|
||||||
|
@ -275,6 +275,7 @@ const messages = {
|
|||||||
homeMissingPatches: 'Missing patches',
|
homeMissingPatches: 'Missing patches',
|
||||||
homePoolMaster: 'Master:',
|
homePoolMaster: 'Master:',
|
||||||
homeResourceSet: 'Resource set: {resourceSet}',
|
homeResourceSet: 'Resource set: {resourceSet}',
|
||||||
|
homeSrVdisToCoalesce: 'Some VDIs need to be coalesced',
|
||||||
highAvailability: 'High Availability',
|
highAvailability: 'High Availability',
|
||||||
powerState: 'Power state',
|
powerState: 'Power state',
|
||||||
srSharedType: 'Shared {type}',
|
srSharedType: 'Shared {type}',
|
||||||
@ -1385,6 +1386,7 @@ const messages = {
|
|||||||
metricsLoading: 'Loading…',
|
metricsLoading: 'Loading…',
|
||||||
|
|
||||||
// ----- Health -----
|
// ----- Health -----
|
||||||
|
length: 'Length: {length}',
|
||||||
deleteBackups: 'Delete backup{nBackups, plural, one {} other {s}}',
|
deleteBackups: 'Delete backup{nBackups, plural, one {} other {s}}',
|
||||||
deleteBackupsMessage:
|
deleteBackupsMessage:
|
||||||
'Are you sure you want to delete {nBackups, number} backup{nBackups, plural, one {} other {s}}?',
|
'Are you sure you want to delete {nBackups, number} backup{nBackups, plural, one {} other {s}}?',
|
||||||
@ -1432,6 +1434,8 @@ const messages = {
|
|||||||
alarmObject: 'Issue on',
|
alarmObject: 'Issue on',
|
||||||
alarmPool: 'Pool',
|
alarmPool: 'Pool',
|
||||||
spaceLeftTooltip: '{used}% used ({free} left)',
|
spaceLeftTooltip: '{used}% used ({free} left)',
|
||||||
|
vdisToCoalesce: 'VDIs to coalesce',
|
||||||
|
srVdisToCoalesceWarning: 'This SR has more than {limitVdis, number} VDIs to coalesce',
|
||||||
|
|
||||||
// ----- New VM -----
|
// ----- New VM -----
|
||||||
createVmModalTitle: 'Create VM',
|
createVmModalTitle: 'Create VM',
|
||||||
|
@ -40,6 +40,7 @@ import parseNdJson from './_parseNdJson'
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export const ITEMS_PER_PAGE_OPTIONS = [10, 20, 50, 100]
|
export const ITEMS_PER_PAGE_OPTIONS = [10, 20, 50, 100]
|
||||||
|
export const VDIS_TO_COALESCE_LIMIT = 10
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -524,6 +525,8 @@ subscribeVolumeInfo.forceRefresh = (() => {
|
|||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
export const subscribeSrsUnhealthyVdiChainsLength = createSubscription(() => _call('sr.getAllUnhealthyVdiChainsLength'))
|
||||||
|
|
||||||
const unhealthyVdiChainsLengthSubscriptionsBySr = {}
|
const unhealthyVdiChainsLengthSubscriptionsBySr = {}
|
||||||
export const createSrUnhealthyVdiChainsLengthSubscription = sr => {
|
export const createSrUnhealthyVdiChainsLengthSubscription = sr => {
|
||||||
sr = resolveId(sr)
|
sr = resolveId(sr)
|
||||||
|
@ -36,6 +36,8 @@ import {
|
|||||||
createSort,
|
createSort,
|
||||||
} from 'selectors'
|
} from 'selectors'
|
||||||
|
|
||||||
|
import UnhealthyVdis from './unhealthyVdis'
|
||||||
|
|
||||||
const SrColContainer = connectStore(() => ({
|
const SrColContainer = connectStore(() => ({
|
||||||
container: createGetObject(),
|
container: createGetObject(),
|
||||||
}))(
|
}))(
|
||||||
@ -772,6 +774,7 @@ export default class Health extends Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
{props.areObjectsFetched && <UnhealthyVdis />}
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<Card>
|
<Card>
|
||||||
|
87
packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js
Normal file
87
packages/xo-web/src/xo-app/dashboard/health/unhealthyVdis.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import _ from 'intl'
|
||||||
|
import addSubscriptions from 'add-subscriptions'
|
||||||
|
import decorate from 'apply-decorators'
|
||||||
|
import Icon from 'icon'
|
||||||
|
import React from 'react'
|
||||||
|
import SingleLineRow from 'single-line-row'
|
||||||
|
import SortedTable from 'sorted-table'
|
||||||
|
import Tooltip from 'tooltip'
|
||||||
|
import { Card, CardHeader, CardBlock } from 'card'
|
||||||
|
import { Col, Row } from 'grid'
|
||||||
|
import { injectState, provideState } from 'reaclette'
|
||||||
|
import { map, size } from 'lodash'
|
||||||
|
import { Sr, Vdi } from 'render-xo-item'
|
||||||
|
import { subscribeSrsUnhealthyVdiChainsLength, VDIS_TO_COALESCE_LIMIT } from 'xo'
|
||||||
|
|
||||||
|
const COLUMNS = [
|
||||||
|
{
|
||||||
|
itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => (
|
||||||
|
<div>
|
||||||
|
<Sr id={srId} link />{' '}
|
||||||
|
{size(unhealthyVdiChainsLengthBySr[srId]) >= VDIS_TO_COALESCE_LIMIT && (
|
||||||
|
<Tooltip content={_('srVdisToCoalesceWarning', { limitVdis: VDIS_TO_COALESCE_LIMIT })}>
|
||||||
|
<span className='text-warning'>
|
||||||
|
<Icon icon='alarm' />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
name: _('sr'),
|
||||||
|
sortCriteria: 'name_label',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemRenderer: (srId, { unhealthyVdiChainsLengthBySr }) => (
|
||||||
|
<div>
|
||||||
|
{map(unhealthyVdiChainsLengthBySr[srId], (chainLength, vdiId) => (
|
||||||
|
<SingleLineRow key={vdiId}>
|
||||||
|
<Col>
|
||||||
|
<Vdi id={vdiId} />
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<span>{_('length', { length: chainLength })}</span>
|
||||||
|
</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
name: _('vdisToCoalesce'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const UnhealthyVdis = decorate([
|
||||||
|
addSubscriptions({
|
||||||
|
unhealthyVdiChainsLengthBySr: subscribeSrsUnhealthyVdiChainsLength,
|
||||||
|
}),
|
||||||
|
provideState({
|
||||||
|
computed: {
|
||||||
|
srIds: (state, { unhealthyVdiChainsLengthBySr = {} }) => Object.keys(unhealthyVdiChainsLengthBySr),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectState,
|
||||||
|
({ state: { srIds }, unhealthyVdiChainsLengthBySr }) => (
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<Icon icon='disk' /> {_('vdisToCoalesce')}
|
||||||
|
</CardHeader>
|
||||||
|
<CardBlock>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<SortedTable
|
||||||
|
data-unhealthyVdiChainsLengthBySr={unhealthyVdiChainsLengthBySr}
|
||||||
|
collection={srIds}
|
||||||
|
columns={COLUMNS}
|
||||||
|
stateUrlParam='s_vdis_to_coalesce'
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</CardBlock>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
export default UnhealthyVdis
|
@ -18,6 +18,8 @@ import {
|
|||||||
subscribeProxies,
|
subscribeProxies,
|
||||||
subscribeProxiesApplianceUpdaterState,
|
subscribeProxiesApplianceUpdaterState,
|
||||||
subscribeResourceSets,
|
subscribeResourceSets,
|
||||||
|
subscribeSrsUnhealthyVdiChainsLength,
|
||||||
|
VDIS_TO_COALESCE_LIMIT,
|
||||||
} from 'xo'
|
} from 'xo'
|
||||||
import {
|
import {
|
||||||
createFilter,
|
createFilter,
|
||||||
@ -29,7 +31,7 @@ import {
|
|||||||
getXoaState,
|
getXoaState,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
} from 'selectors'
|
} from 'selectors'
|
||||||
import { every, forEach, identity, isEmpty, isEqual, map, pick, some } from 'lodash'
|
import { every, forEach, identity, isEmpty, isEqual, map, pick, size, some } from 'lodash'
|
||||||
|
|
||||||
import styles from './index.css'
|
import styles from './index.css'
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ const returnTrue = () => true
|
|||||||
cb(map(proxies, 'id').sort())
|
cb(map(proxies, 'id').sort())
|
||||||
}),
|
}),
|
||||||
resourceSets: subscribeResourceSets,
|
resourceSets: subscribeResourceSets,
|
||||||
|
unhealthyVdiChainsLength: subscribeSrsUnhealthyVdiChainsLength,
|
||||||
})
|
})
|
||||||
@injectState
|
@injectState
|
||||||
export default class Menu extends Component {
|
export default class Menu extends Component {
|
||||||
@ -135,6 +138,12 @@ export default class Menu extends Component {
|
|||||||
missingPatches => some(missingPatches, _ => _)
|
missingPatches => some(missingPatches, _ => _)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_hasUnhealthyVdis = createSelector(
|
||||||
|
() => this.state.unhealthyVdiChainsLength,
|
||||||
|
unhealthyVdiChainsLength =>
|
||||||
|
some(unhealthyVdiChainsLength, vdiChainsLength => size(vdiChainsLength) >= VDIS_TO_COALESCE_LIMIT)
|
||||||
|
)
|
||||||
|
|
||||||
_toggleCollapsed = event => {
|
_toggleCollapsed = event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this._removeListener()
|
this._removeListener()
|
||||||
@ -211,6 +220,14 @@ export default class Menu extends Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
|
const unhealthyVdisWarning = this._hasUnhealthyVdis() ? (
|
||||||
|
<Tooltip content={_('homeUnhealthyVdis')}>
|
||||||
|
<span className='text-warning'>
|
||||||
|
<Icon icon='alarm' />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
) : null
|
||||||
|
|
||||||
/* eslint-disable object-property-newline */
|
/* eslint-disable object-property-newline */
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
@ -247,6 +264,7 @@ export default class Menu extends Component {
|
|||||||
to: '/dashboard/overview',
|
to: '/dashboard/overview',
|
||||||
icon: 'menu-dashboard',
|
icon: 'menu-dashboard',
|
||||||
label: 'dashboardPage',
|
label: 'dashboardPage',
|
||||||
|
extra: [unhealthyVdisWarning],
|
||||||
subMenu: [
|
subMenu: [
|
||||||
{
|
{
|
||||||
to: '/dashboard/overview',
|
to: '/dashboard/overview',
|
||||||
@ -267,6 +285,7 @@ export default class Menu extends Component {
|
|||||||
to: '/dashboard/health',
|
to: '/dashboard/health',
|
||||||
icon: 'menu-dashboard-health',
|
icon: 'menu-dashboard-health',
|
||||||
label: 'overviewHealthDashboardPage',
|
label: 'overviewHealthDashboardPage',
|
||||||
|
extra: [unhealthyVdisWarning],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user