feat(xo-server,xo-web/menu): proxy upgrade notification (#5930)
See xoa-support#4105
This commit is contained in:
parent
eb238bf107
commit
fc73971d63
@ -13,6 +13,7 @@
|
|||||||
- [VM/export] Ability to copy the export URL (PR [#5948](https://github.com/vatesfr/xen-orchestra/pull/5948))
|
- [VM/export] Ability to copy the export URL (PR [#5948](https://github.com/vatesfr/xen-orchestra/pull/5948))
|
||||||
- [Servers] Ability to use an HTTP proxy between XO and a server
|
- [Servers] Ability to use an HTTP proxy between XO and a server
|
||||||
- [Pool/advanced] Ability to define network for importing/exporting VMs/VDIs (PR [#5957](https://github.com/vatesfr/xen-orchestra/pull/5957))
|
- [Pool/advanced] Ability to define network for importing/exporting VMs/VDIs (PR [#5957](https://github.com/vatesfr/xen-orchestra/pull/5957))
|
||||||
|
- [Menu] Notify user when proxies need to be upgraded (PR [#5930](https://github.com/vatesfr/xen-orchestra/pull/5930))
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
|
@ -23,9 +23,12 @@ import { timeout } from 'promise-toolbox'
|
|||||||
|
|
||||||
import Collection from '../collection/redis.mjs'
|
import Collection from '../collection/redis.mjs'
|
||||||
import patch from '../patch.mjs'
|
import patch from '../patch.mjs'
|
||||||
|
import { debounceWithKey } from '../_pDebounceWithKey.mjs'
|
||||||
import { extractIpFromVmNetworks } from '../_extractIpFromVmNetworks.mjs'
|
import { extractIpFromVmNetworks } from '../_extractIpFromVmNetworks.mjs'
|
||||||
import { generateToken } from '../utils.mjs'
|
import { generateToken } from '../utils.mjs'
|
||||||
|
|
||||||
|
const DEBOUNCE_TIME_PROXY_STATE = 60000
|
||||||
|
|
||||||
const extractProperties = _ => _.properties
|
const extractProperties = _ => _.properties
|
||||||
const omitToken = proxy => omit(proxy, 'authenticationToken')
|
const omitToken = proxy => omit(proxy, 'authenticationToken')
|
||||||
const synchronizedWrite = synchronized()
|
const synchronizedWrite = synchronized()
|
||||||
@ -191,6 +194,7 @@ export default class Proxy {
|
|||||||
await xapi._waitObjectState(vmUuid, vm => extractIpFromVmNetworks(vm.$guest_metrics?.networks) !== undefined)
|
await xapi._waitObjectState(vmUuid, vm => extractIpFromVmNetworks(vm.$guest_metrics?.networks) !== undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@decorateWith(debounceWithKey, DEBOUNCE_TIME_PROXY_STATE, id => id)
|
||||||
getProxyApplianceUpdaterState(id) {
|
getProxyApplianceUpdaterState(id) {
|
||||||
return this.callProxyMethod(id, 'appliance.updater.getState')
|
return this.callProxyMethod(id, 'appliance.updater.getState')
|
||||||
}
|
}
|
||||||
|
@ -2380,6 +2380,7 @@ const messages = {
|
|||||||
proxyUpToDate: 'Your proxy is up-to-date',
|
proxyUpToDate: 'Your proxy is up-to-date',
|
||||||
proxyRunningBackupsMessage:
|
proxyRunningBackupsMessage:
|
||||||
'The upgrade will interrupt {nJobs, number} running backup job{nJobs, plural, one {} other {s}}. Do you want to continue?',
|
'The upgrade will interrupt {nJobs, number} running backup job{nJobs, plural, one {} other {s}}. Do you want to continue?',
|
||||||
|
proxiesNeedUpgrade: 'Some proxies need to be upgraded.',
|
||||||
upgradeNeededForProxies: 'Some proxies need to be upgraded. Click here to get more information.',
|
upgradeNeededForProxies: 'Some proxies need to be upgraded. Click here to get more information.',
|
||||||
|
|
||||||
// ----- Utils -----
|
// ----- Utils -----
|
||||||
|
@ -477,6 +477,25 @@ subscribeHostMissingPatches.forceRefresh = host => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proxiesApplianceUpdaterState = {}
|
||||||
|
export const subscribeProxiesApplianceUpdaterState = (proxyId, cb) => {
|
||||||
|
if (proxiesApplianceUpdaterState[proxyId] === undefined) {
|
||||||
|
proxiesApplianceUpdaterState[proxyId] = createSubscription(() => getProxyApplianceUpdaterState(proxyId))
|
||||||
|
}
|
||||||
|
return proxiesApplianceUpdaterState[proxyId](cb)
|
||||||
|
}
|
||||||
|
subscribeProxiesApplianceUpdaterState.forceRefresh = proxyId => {
|
||||||
|
if (proxyId === undefined) {
|
||||||
|
forEach(proxiesApplianceUpdaterState, subscription => subscription.forceRefresh())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = proxiesApplianceUpdaterState[proxyId]
|
||||||
|
if (subscription !== undefined) {
|
||||||
|
subscription.forceRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const volumeInfoBySr = {}
|
const volumeInfoBySr = {}
|
||||||
export const subscribeVolumeInfo = ({ sr, infoType }, cb) => {
|
export const subscribeVolumeInfo = ({ sr, infoType }, cb) => {
|
||||||
sr = resolveId(sr)
|
sr = resolveId(sr)
|
||||||
|
@ -327,6 +327,10 @@
|
|||||||
@extend .fa-ticket;
|
@extend .fa-ticket;
|
||||||
}
|
}
|
||||||
&-circle {
|
&-circle {
|
||||||
|
@extend .fa;
|
||||||
|
@extend .fa-circle;
|
||||||
|
}
|
||||||
|
&-circle-thin {
|
||||||
@extend .fa;
|
@extend .fa;
|
||||||
@extend .fa-circle-thin;
|
@extend .fa-circle-thin;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import _ from 'intl'
|
import _ from 'intl'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import Component from 'base-component'
|
import Component from 'base-component'
|
||||||
import Icon from 'icon'
|
import Icon, { StackedIcons } from 'icon'
|
||||||
import Link from 'link'
|
import Link from 'link'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Tooltip from 'tooltip'
|
import Tooltip from 'tooltip'
|
||||||
@ -15,6 +15,8 @@ import {
|
|||||||
subscribeHostMissingPatches,
|
subscribeHostMissingPatches,
|
||||||
subscribeNotifications,
|
subscribeNotifications,
|
||||||
subscribePermissions,
|
subscribePermissions,
|
||||||
|
subscribeProxies,
|
||||||
|
subscribeProxiesApplianceUpdaterState,
|
||||||
subscribeResourceSets,
|
subscribeResourceSets,
|
||||||
} from 'xo'
|
} from 'xo'
|
||||||
import {
|
import {
|
||||||
@ -60,6 +62,10 @@ const returnTrue = () => true
|
|||||||
@addSubscriptions({
|
@addSubscriptions({
|
||||||
notifications: subscribeNotifications,
|
notifications: subscribeNotifications,
|
||||||
permissions: subscribePermissions,
|
permissions: subscribePermissions,
|
||||||
|
proxyIds: cb =>
|
||||||
|
subscribeProxies(proxies => {
|
||||||
|
cb(map(proxies, 'id').sort())
|
||||||
|
}),
|
||||||
resourceSets: subscribeResourceSets,
|
resourceSets: subscribeResourceSets,
|
||||||
})
|
})
|
||||||
@injectState
|
@injectState
|
||||||
@ -77,19 +83,30 @@ export default class Menu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._updateMissingPatchesSubscriptions()
|
this._updateMissingPatchesSubscriptions()
|
||||||
|
this._updateProxiesSubscriptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._removeListener()
|
this._removeListener()
|
||||||
this._unsubscribeMissingPatches()
|
this._unsubscribeMissingPatches()
|
||||||
|
this._unsubscribeProxiesApplianceUpdaterState()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!isEqual(Object.keys(prevProps.hosts).sort(), Object.keys(this.props.hosts).sort())) {
|
if (!isEqual(Object.keys(prevProps.hosts).sort(), Object.keys(this.props.hosts).sort())) {
|
||||||
this._updateMissingPatchesSubscriptions()
|
this._updateMissingPatchesSubscriptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isEqual(prevProps.proxyIds, this.props.proxyIds)) {
|
||||||
|
this._updateProxiesSubscriptions()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_areProxiesOutOfDate = createSelector(
|
||||||
|
() => this.state.proxyStates,
|
||||||
|
proxyStates => some(proxyStates, state => state.endsWith('-upgrade-needed'))
|
||||||
|
)
|
||||||
|
|
||||||
_checkPermissions = createSelector(
|
_checkPermissions = createSelector(
|
||||||
() => this.props.isAdmin,
|
() => this.props.isAdmin,
|
||||||
() => this.props.permissions,
|
() => this.props.permissions,
|
||||||
@ -157,6 +174,29 @@ export default class Menu extends Component {
|
|||||||
this._unsubscribeMissingPatches = () => forEach(unsubs, unsub => unsub())
|
this._unsubscribeMissingPatches = () => forEach(unsubs, unsub => unsub())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateProxiesSubscriptions = () => {
|
||||||
|
this.setState(({ proxyStates }) => ({
|
||||||
|
proxyStates: pick(proxyStates, this.props.proxyIds),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const unsubs = map(this.props.proxyIds, proxyId =>
|
||||||
|
subscribeProxiesApplianceUpdaterState(proxyId, ({ state: proxyState = '' }) => {
|
||||||
|
this.setState(state => ({
|
||||||
|
proxyStates: {
|
||||||
|
...state.proxyStates,
|
||||||
|
[proxyId]: proxyState,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if (this._unsubscribeProxiesApplianceUpdaterState !== undefined) {
|
||||||
|
this._unsubscribeProxiesApplianceUpdaterState()
|
||||||
|
}
|
||||||
|
|
||||||
|
this._unsubscribeProxiesApplianceUpdaterState = () => forEach(unsubs, unsub => unsub())
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isAdmin, isPoolAdmin, nTasks, state, status, user, pools, nHosts, srs, xoaState } = this.props
|
const { isAdmin, isPoolAdmin, nTasks, state, status, user, pools, nHosts, srs, xoaState } = this.props
|
||||||
const noOperatablePools = this._getNoOperatablePools()
|
const noOperatablePools = this._getNoOperatablePools()
|
||||||
@ -397,6 +437,18 @@ export default class Menu extends Component {
|
|||||||
to: '/proxies',
|
to: '/proxies',
|
||||||
icon: 'proxy',
|
icon: 'proxy',
|
||||||
label: 'proxies',
|
label: 'proxies',
|
||||||
|
extra: [
|
||||||
|
this._areProxiesOutOfDate() ? (
|
||||||
|
<Tooltip content={_('proxiesNeedUpgrade')}>
|
||||||
|
<StackedIcons
|
||||||
|
icons={[
|
||||||
|
{ color: 'text-success', icon: 'circle', size: 2 },
|
||||||
|
{ icon: 'menu-update', size: 1 },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : null,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
||||||
!noOperatablePools && {
|
!noOperatablePools && {
|
||||||
|
@ -355,7 +355,7 @@ class VifStatus extends BaseComponent {
|
|||||||
<StackedIcons
|
<StackedIcons
|
||||||
icons={[
|
icons={[
|
||||||
{ icon: 'vif-disable', size: 1 },
|
{ icon: 'vif-disable', size: 1 },
|
||||||
{ icon: 'circle', size: 2 },
|
{ icon: 'circle-thin', size: 2 },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -366,7 +366,7 @@ class VifStatus extends BaseComponent {
|
|||||||
<StackedIcons
|
<StackedIcons
|
||||||
icons={[
|
icons={[
|
||||||
{ icon: 'unlock', size: 1 },
|
{ icon: 'unlock', size: 1 },
|
||||||
{ icon: 'circle', size: 2 },
|
{ icon: 'circle-thin', size: 2 },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
Loading…
Reference in New Issue
Block a user