Skip to content

Commit

Permalink
feat(xo-web/home): icon grouping (#6655)
Browse files Browse the repository at this point in the history
  • Loading branch information
MathieuRA committed Mar 15, 2023
1 parent 2823af9 commit aadc1bb
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Plugin/auth-oidc] Support `email` for _username field_ setting [Forum#59587](https://xcp-ng.org/forum/post/59587)
- [Plugin/auth-oidc] Well-known suffix is now optional in _auto-discovery URL_
- [PIF selector] Display the VLAN number when displaying a VLAN PIF [#4697](https://github.com/vatesfr/xen-orchestra/issues/4697) (PR [#6714](https://github.com/vatesfr/xen-orchestra/pull/6714))
- [Home/pool, host] Grouping of alert icons (PR [#6655](https://github.com/vatesfr/xen-orchestra/pull/6655))

### Bug fixes

Expand Down
63 changes: 63 additions & 0 deletions packages/xo-web/src/common/bulk-icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import _ from 'intl'
import PropTypes from 'prop-types'
import React from 'react'

import BaseComponent from './base-component'
import Icon from './icon'
import { alert } from './modal'
import { createSelector } from './selectors'

const ICON_WARNING_MODAL_LEVEL = {
danger: 1,
warning: 2,
success: 3,
}

class BulkIcons extends BaseComponent {
getSortedAlerts = createSelector(
() => this.props.alerts,
alerts =>
alerts?.sort((curr, next) => ICON_WARNING_MODAL_LEVEL[curr.level] - ICON_WARNING_MODAL_LEVEL[next.level]) ?? []
)

onClick = () =>
alert(
_('alerts'),
this.getSortedAlerts().map(({ level, render }, index) => (
<div className={`text-${level}`} key={index}>
{render}
</div>
))
)

render() {
const alerts = this.getSortedAlerts()
const length = alerts.length
const level = alerts[0]?.level

return (
length !== 0 && (
// <a> in order to bypass the BlockLink component
<a className='fa-stack' onClick={this.onClick} style={{ transform: 'scale(0.8)' }}>
<Icon icon='alarm' color={`text-${level}`} className='fa-stack-2x' />
{/* `fa-triangle` does not exist on FontAwesome4.`l` is used to fill the `!` of the `alarm` icon */}
<span className={`fa-stack-2x font-weight-bold text-${level}`} style={{ fontSize: '2.3em' }}>
l
</span>
<span className='fa-stack-1x text-white font-weight-bold'>{length}</span>
</a>
)
)
}
}

BulkIcons.propTypes = {
alerts: PropTypes.arrayOf(
PropTypes.exact({
level: PropTypes.string.isRequired,
render: PropTypes.element.isRequired,
})
),
}

export default BulkIcons
1 change: 1 addition & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const forEach = require('lodash/forEach')

const messages = {
alpha: 'Alpha',
alerts: 'Alerts',
creation: 'Creation',
description: 'Description',
deleteSourceVm: 'Delete source VM',
Expand Down
88 changes: 68 additions & 20 deletions packages/xo-web/src/xo-app/home/host-item.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import _ from 'intl'
import Component from 'base-component'
import InconsistentHostTimeWarning from 'inconsistent-host-time-warning'
import Ellipsis, { EllipsisContainer } from 'ellipsis'
import Icon from 'icon'
import Link, { BlockLink } from 'link'
Expand All @@ -12,7 +11,16 @@ import HomeTags from 'home-tags'
import Tooltip from 'tooltip'
import { Row, Col } from 'grid'
import { Text } from 'editable'
import { addTag, editHost, fetchHostStats, removeTag, startHost, stopHost, subscribeHvSupportedVersions } from 'xo'
import {
addTag,
editHost,
fetchHostStats,
isHostTimeConsistentWithXoaTime,
removeTag,
startHost,
stopHost,
subscribeHvSupportedVersions,
} from 'xo'
import { addSubscriptions, connectStore, formatSizeShort, hasLicenseRestrictions, osFamily } from 'utils'
import {
createDoesHostNeedRestart,
Expand All @@ -23,9 +31,11 @@ import {
} from 'selectors'

import MiniStats from './mini-stats'
import LicenseWarning from '../host/license-warning'
import styles from './index.css'

import BulkIcons from '../../common/bulk-icons'
import { LICENSE_WARNING_BODY } from '../host/license-warning'

@addSubscriptions({
hvSupportedVersions: subscribeHvSupportedVersions,
})
Expand Down Expand Up @@ -66,6 +76,60 @@ export default class HostItem extends Component {
_toggleExpanded = () => this.setState({ expanded: !this.state.expanded })
_onSelect = () => this.props.onSelect(this.props.item.id)

_getAlerts = createSelector(
() => this.props.needsRestart,
() => this.props.item,
this._isMaintained,
(needsRestart, host, isMaintained) => {
const alerts = []

if (needsRestart) {
alerts.push({
level: 'warning',
render: (
<Link className='text-warning' to={`/hosts/${host.id}/patches`}>
<Icon icon='alarm' /> {_('rebootUpdateHostLabel')}
</Link>
),
})
}

if (!isMaintained) {
alerts.push({
level: 'warning',
render: (
<p>
<Icon icon='alarm' /> {_('noMoreMaintained')}
</p>
),
})
}

if (!isHostTimeConsistentWithXoaTime(host)) {
alerts.push({
level: 'danger',
render: (
<p>
<Icon icon='alarm' /> {_('warningHostTimeTooltip')}
</p>
),
})
}

if (hasLicenseRestrictions(host)) {
alerts.push({
level: 'danger',
render: (
<span>
<Icon icon='alarm' /> {_('licenseRestrictionsModalTitle')} {LICENSE_WARNING_BODY}
</span>
),
})
}
return alerts
}
)

render() {
const { container, expandAll, item: host, nVms, selected, state } = this.props

Expand Down Expand Up @@ -102,23 +166,7 @@ export default class HostItem extends Component {
<span className='tag tag-pill tag-info'>{_('pillMaster')}</span>
)}
&nbsp;
{this.props.needsRestart && (
<Tooltip content={_('rebootUpdateHostLabel')}>
<Link to={`/hosts/${host.id}/patches`}>
<Icon icon='alarm' />
</Link>
</Tooltip>
)}
&nbsp;
{!this._isMaintained() && (
<Tooltip content={_('noMoreMaintained')}>
<Icon className='text-warning' icon='alarm' />
</Tooltip>
)}
&nbsp;
<InconsistentHostTimeWarning host={host} />
&nbsp;
{hasLicenseRestrictions(host) && <LicenseWarning />}
<BulkIcons alerts={this._getAlerts()} />
</EllipsisContainer>
</Col>
<Col mediumSize={3} className='hidden-lg-down'>
Expand Down
51 changes: 43 additions & 8 deletions packages/xo-web/src/xo-app/home/pool-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import { injectState } from 'reaclette'

import styles from './index.css'

import BulkIcons from '../../common/bulk-icons'
import { isAdmin } from '../../common/selectors'
import { ShortDate } from '../../common/utils'
import { getXoaPlan, SOURCES } from '../../common/xoa-plans'

@connectStore(() => {
const getPoolHosts = createGetObjectsOfType('host').filter(
Expand Down Expand Up @@ -72,28 +74,58 @@ export default class PoolItem extends Component {
this.props.missingPatches.then(patches => this.setState({ missingPatchCount: size(patches) }))
}

_getPoolLicenseIcon() {
_getPoolLicenseIconTooltip() {
const { state: reacletteState, item: pool } = this.props
let tooltip
const { icon, earliestExpirationDate, nHostsUnderLicense, nHosts, supportLevel } =
const { earliestExpirationDate, nHostsUnderLicense, nHosts, supportLevel } =
reacletteState.poolLicenseInfoByPoolId[pool.id]
let tooltip = _('poolNoSupport')

if (getXoaPlan() === SOURCES) {
tooltip = _('poolSupportSourceUsers')
}
if (supportLevel === 'total') {
tooltip = _('earliestExpirationDate', { dateString: <ShortDate timestamp={earliestExpirationDate} /> })
}
if (supportLevel === 'partial') {
tooltip = _('poolPartialSupport', { nHostsLicense: nHostsUnderLicense, nHosts })
}
return icon(tooltip)
return tooltip
}

_isXcpngPool() {
return Object.values(this.props.poolHosts)[0].productBrand === 'XCP-ng'
}

_getPoolLicenseInfo = () => this.props.state.poolLicenseInfoByPoolId[this.props.item.id]

_getAlerts = createSelector(
() => this.props.isAdmin,
this._getPoolLicenseInfo,
(isAdmin, poolLicenseInfo) => {
const alerts = []

if (isAdmin && this._isXcpngPool()) {
const { icon, supportLevel } = poolLicenseInfo
if (supportLevel !== 'total') {
const level = supportLevel === 'partial' ? 'warning' : 'danger'
alerts.push({
level,
render: (
<p>
{icon()} {this._getPoolLicenseIconTooltip()}
</p>
),
})
}
}
return alerts
}
)

render() {
const { item: pool, expandAll, isAdmin, selected, hostMetrics, poolHosts, nSrs, nVms } = this.props
const { item: pool, expandAll, selected, hostMetrics, poolHosts, nSrs, nVms } = this.props
const { missingPatchCount } = this.state
const { icon, supportLevel } = this._getPoolLicenseInfo()

return (
<div className={styles.item}>
Expand All @@ -106,16 +138,19 @@ export default class PoolItem extends Component {
<Ellipsis>
<Text value={pool.name_label} onChange={this._setNameLabel} useLongClick />
</Ellipsis>
{isAdmin && this._isXcpngPool() && <span className='ml-1'>{this._getPoolLicenseIcon()}</span>}
&nbsp;&nbsp;
&nbsp;
<BulkIcons alerts={this._getAlerts()} />
&nbsp;
{missingPatchCount > 0 && (
<span>
&nbsp;&nbsp;
<Tooltip content={_('homeMissingPatches')}>
<span className='tag tag-pill tag-danger'>{missingPatchCount}</span>
</Tooltip>
</span>
)}
&nbsp;
{isAdmin && this._isXcpngPool() && supportLevel === 'total' && icon(this._getPoolLicenseIconTooltip())}
&nbsp;
{pool.HA_enabled && (
<span>
&nbsp;&nbsp;
Expand Down
36 changes: 18 additions & 18 deletions packages/xo-web/src/xo-app/host/license-warning.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ import Icon from 'icon'
import Tooltip from 'tooltip'
import { alert } from 'modal'

const showInfo = () =>
alert(
_('licenseRestrictionsModalTitle'),
<span>
<a href='https://xcp-ng.com/pricing.html#xcpngvsxenserver' rel='noopener noreferrer' target='_blank'>
{_('actionsRestricted')}
</a>{' '}
{_('counterRestrictionsOptions')}
<ul>
<li>
<a href='https://github.com/xcp-ng/xcp/wiki/Upgrade-from-XenServer' rel='noopener noreferrer' target='_blank'>
{_('counterRestrictionsOptionsXcp')}
</a>
</li>
<li>{_('counterRestrictionsOptionsXsLicense')}</li>
</ul>
</span>
)
export const LICENSE_WARNING_BODY = (
<span>
<a href='https://xcp-ng.com/pricing.html#xcpngvsxenserver' rel='noopener noreferrer' target='_blank'>
{_('actionsRestricted')}
</a>{' '}
{_('counterRestrictionsOptions')}
<ul>
<li>
<a href='https://github.com/xcp-ng/xcp/wiki/Upgrade-from-XenServer' rel='noopener noreferrer' target='_blank'>
{_('counterRestrictionsOptionsXcp')}
</a>
</li>
<li>{_('counterRestrictionsOptionsXsLicense')}</li>
</ul>
</span>
)

const showInfo = () => alert(_('licenseRestrictionsModalTitle'), LICENSE_WARNING_BODY)

const LicenseWarning = ({ iconSize = 'sm' }) => (
<Tooltip content={_('licenseRestrictions')}>
Expand Down
30 changes: 13 additions & 17 deletions packages/xo-web/src/xo-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,27 +82,27 @@ const BODY_STYLE = {
width: '100%',
}

const WrapperIconPoolLicense = ({ children, tooltip }) => (
<Tooltip content={tooltip}>
<a href='https://xcp-ng.com' rel='noreferrer noopener' target='_blank'>
{children}
</a>
</Tooltip>
const WrapperIconPoolLicense = ({ children }) => (
<a href='https://xcp-ng.com' rel='noreferrer noopener' target='_blank'>
{children}
</a>
)

export const ICON_POOL_LICENSE = {
total: tooltip => (
<WrapperIconPoolLicense tooltip={tooltip}>
<Icon icon='pro-support' className='text-success' />
</WrapperIconPoolLicense>
<Tooltip content={tooltip}>
<WrapperIconPoolLicense>
<Icon icon='pro-support' className='text-success' />
</WrapperIconPoolLicense>
</Tooltip>
),
partial: tooltip => (
<WrapperIconPoolLicense tooltip={tooltip}>
partial: () => (
<WrapperIconPoolLicense>
<Icon icon='alarm' className='text-warning' />
</WrapperIconPoolLicense>
),
any: () => (
<WrapperIconPoolLicense tooltip={_('poolNoSupport')}>
<WrapperIconPoolLicense>
<Icon icon='alarm' className='text-danger' />
</WrapperIconPoolLicense>
),
Expand Down Expand Up @@ -187,11 +187,7 @@ export const ICON_POOL_LICENSE = {
if (getXoaPlan() === SOURCES.name) {
poolLicenseInfoByPoolId[poolId] = {
nHostsUnderLicense,
icon: () => (
<Tooltip content={_('poolSupportSourceUsers')}>
<Icon icon='unknown-status' className='text-danger' />
</Tooltip>
),
icon: () => <Icon icon='unknown-status' className='text-danger' />,
nHosts,
}
return
Expand Down

0 comments on commit aadc1bb

Please sign in to comment.