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”
|
||||
|
||||
- [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
|
||||
|
||||
> 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 filter from 'lodash/filter.js'
|
||||
import some from 'lodash/some.js'
|
||||
|
||||
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 }) {
|
||||
return this.getXapi(sr).getUnhealthyVdiChainsLength(sr)
|
||||
}
|
||||
|
@ -275,6 +275,7 @@ const messages = {
|
||||
homeMissingPatches: 'Missing patches',
|
||||
homePoolMaster: 'Master:',
|
||||
homeResourceSet: 'Resource set: {resourceSet}',
|
||||
homeSrVdisToCoalesce: 'Some VDIs need to be coalesced',
|
||||
highAvailability: 'High Availability',
|
||||
powerState: 'Power state',
|
||||
srSharedType: 'Shared {type}',
|
||||
@ -1385,6 +1386,7 @@ const messages = {
|
||||
metricsLoading: 'Loading…',
|
||||
|
||||
// ----- Health -----
|
||||
length: 'Length: {length}',
|
||||
deleteBackups: 'Delete backup{nBackups, plural, one {} other {s}}',
|
||||
deleteBackupsMessage:
|
||||
'Are you sure you want to delete {nBackups, number} backup{nBackups, plural, one {} other {s}}?',
|
||||
@ -1432,6 +1434,8 @@ const messages = {
|
||||
alarmObject: 'Issue on',
|
||||
alarmPool: 'Pool',
|
||||
spaceLeftTooltip: '{used}% used ({free} left)',
|
||||
vdisToCoalesce: 'VDIs to coalesce',
|
||||
srVdisToCoalesceWarning: 'This SR has more than {limitVdis, number} VDIs to coalesce',
|
||||
|
||||
// ----- New VM -----
|
||||
createVmModalTitle: 'Create VM',
|
||||
|
@ -40,6 +40,7 @@ import parseNdJson from './_parseNdJson'
|
||||
// ===================================================================
|
||||
|
||||
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 = {}
|
||||
export const createSrUnhealthyVdiChainsLengthSubscription = sr => {
|
||||
sr = resolveId(sr)
|
||||
|
@ -36,6 +36,8 @@ import {
|
||||
createSort,
|
||||
} from 'selectors'
|
||||
|
||||
import UnhealthyVdis from './unhealthyVdis'
|
||||
|
||||
const SrColContainer = connectStore(() => ({
|
||||
container: createGetObject(),
|
||||
}))(
|
||||
@ -772,6 +774,7 @@ export default class Health extends Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{props.areObjectsFetched && <UnhealthyVdis />}
|
||||
<Row>
|
||||
<Col>
|
||||
<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,
|
||||
subscribeProxiesApplianceUpdaterState,
|
||||
subscribeResourceSets,
|
||||
subscribeSrsUnhealthyVdiChainsLength,
|
||||
VDIS_TO_COALESCE_LIMIT,
|
||||
} from 'xo'
|
||||
import {
|
||||
createFilter,
|
||||
@ -29,7 +31,7 @@ import {
|
||||
getXoaState,
|
||||
isAdmin,
|
||||
} 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'
|
||||
|
||||
@ -67,6 +69,7 @@ const returnTrue = () => true
|
||||
cb(map(proxies, 'id').sort())
|
||||
}),
|
||||
resourceSets: subscribeResourceSets,
|
||||
unhealthyVdiChainsLength: subscribeSrsUnhealthyVdiChainsLength,
|
||||
})
|
||||
@injectState
|
||||
export default class Menu extends Component {
|
||||
@ -135,6 +138,12 @@ export default class Menu extends Component {
|
||||
missingPatches => some(missingPatches, _ => _)
|
||||
)
|
||||
|
||||
_hasUnhealthyVdis = createSelector(
|
||||
() => this.state.unhealthyVdiChainsLength,
|
||||
unhealthyVdiChainsLength =>
|
||||
some(unhealthyVdiChainsLength, vdiChainsLength => size(vdiChainsLength) >= VDIS_TO_COALESCE_LIMIT)
|
||||
)
|
||||
|
||||
_toggleCollapsed = event => {
|
||||
event.preventDefault()
|
||||
this._removeListener()
|
||||
@ -211,6 +220,14 @@ export default class Menu extends Component {
|
||||
</Tooltip>
|
||||
) : null
|
||||
|
||||
const unhealthyVdisWarning = this._hasUnhealthyVdis() ? (
|
||||
<Tooltip content={_('homeUnhealthyVdis')}>
|
||||
<span className='text-warning'>
|
||||
<Icon icon='alarm' />
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null
|
||||
|
||||
/* eslint-disable object-property-newline */
|
||||
const items = [
|
||||
{
|
||||
@ -247,6 +264,7 @@ export default class Menu extends Component {
|
||||
to: '/dashboard/overview',
|
||||
icon: 'menu-dashboard',
|
||||
label: 'dashboardPage',
|
||||
extra: [unhealthyVdisWarning],
|
||||
subMenu: [
|
||||
{
|
||||
to: '/dashboard/overview',
|
||||
@ -267,6 +285,7 @@ export default class Menu extends Component {
|
||||
to: '/dashboard/health',
|
||||
icon: 'menu-dashboard-health',
|
||||
label: 'overviewHealthDashboardPage',
|
||||
extra: [unhealthyVdisWarning],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user