feat(xo-server, xo-web): display proxy available upgrades (#5167)
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
- [Self Service] Ability to globally ignore snapshots in resource set quotas (PR [#5164](https://github.com/vatesfr/xen-orchestra/pull/5164))
|
||||
- [Home/VM, host] Ability to filter by power state (PR [#5118](https://github.com/vatesfr/xen-orchestra/pull/5118))
|
||||
- [Import/OVA] Allow for VMDK disks inside .ova files to be gzipped (PR [#5085](https://github.com/vatesfr/xen-orchestra/pull/5085))
|
||||
- [Proxy] Show pending upgrades (PR [#5167](https://github.com/vatesfr/xen-orchestra/pull/5167))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
||||
@@ -159,6 +159,17 @@ upgradeAppliance.params = {
|
||||
},
|
||||
}
|
||||
|
||||
export function getApplianceUpdaterState({ id }) {
|
||||
return this.getProxyApplianceUpdaterState(id)
|
||||
}
|
||||
|
||||
getApplianceUpdaterState.permission = 'admin'
|
||||
getApplianceUpdaterState.params = {
|
||||
id: {
|
||||
type: 'string',
|
||||
},
|
||||
}
|
||||
|
||||
export function checkHealth({ id }) {
|
||||
return this.checkProxyHealth(id)
|
||||
}
|
||||
|
||||
@@ -160,7 +160,24 @@ export default class Proxy {
|
||||
'vm-data/xoa-updater-channel': JSON.stringify(this._xoProxyConf.channel),
|
||||
})
|
||||
|
||||
return xapi.rebootVm(vmUuid)
|
||||
try {
|
||||
await xapi.rebootVm(vmUuid)
|
||||
} catch (error) {
|
||||
if (error.code !== 'VM_BAD_POWER_STATE') {
|
||||
throw error
|
||||
}
|
||||
|
||||
await xapi.startVm(vmUuid)
|
||||
}
|
||||
|
||||
await xapi._waitObjectState(
|
||||
vmUuid,
|
||||
vm => extractIpFromVmNetworks(vm.$guest_metrics?.networks) !== undefined
|
||||
)
|
||||
}
|
||||
|
||||
getProxyApplianceUpdaterState(id) {
|
||||
return this.callProxyMethod(id, 'appliance.updater.getState')
|
||||
}
|
||||
|
||||
@defer
|
||||
@@ -326,6 +343,8 @@ export default class Proxy {
|
||||
)
|
||||
|
||||
await this.checkProxyHealth(proxyId)
|
||||
|
||||
return proxyId
|
||||
}
|
||||
|
||||
async checkProxyHealth(id) {
|
||||
|
||||
@@ -79,6 +79,7 @@ const messages = {
|
||||
noLicenseAvailable: 'No license available',
|
||||
emailPlaceholderExample: 'Email address, e.g.: it@company.net',
|
||||
unknown: 'Unknown',
|
||||
upgradesAvailable: 'Upgrades available',
|
||||
|
||||
// ----- Modals -----
|
||||
alertOk: 'OK',
|
||||
@@ -2371,7 +2372,6 @@ const messages = {
|
||||
redeployProxyWarning: 'This action will destroy the old proxy VM',
|
||||
noProxiesAvailable: 'No proxies available',
|
||||
checkProxyHealth: 'Test your proxy',
|
||||
upgradeProxyAppliance: 'upgrade the appliance',
|
||||
proxyTestSuccess: 'Test passed for {name}',
|
||||
proxyTestSuccessMessage: 'The proxy appears to work correctly',
|
||||
proxyLinkedRemotes: 'Click to see linked remotes',
|
||||
@@ -2382,6 +2382,7 @@ const messages = {
|
||||
'The select only contains SRs connected to at least one HVM-capable host',
|
||||
httpProxy: 'HTTP proxy',
|
||||
httpProxyPlaceholder: 'protocol://username:password@address:port',
|
||||
proxyUpgradesError: 'Unable to check upgrades availability',
|
||||
|
||||
// ----- Utils -----
|
||||
secondsFormat: '{seconds, plural, one {# second} other {# seconds}}',
|
||||
|
||||
@@ -3185,6 +3185,9 @@ export const destroyProxyAppliances = proxies =>
|
||||
export const upgradeProxyAppliance = proxy =>
|
||||
_call('proxy.upgradeAppliance', { id: resolveId(proxy) })
|
||||
|
||||
export const getProxyApplianceUpdaterState = id =>
|
||||
_call('proxy.getApplianceUpdaterState', { id })
|
||||
|
||||
export const checkProxyHealth = proxy =>
|
||||
_call('proxy.checkHealth', { id: resolveId(proxy) }).then(() =>
|
||||
success(
|
||||
|
||||
@@ -7,6 +7,7 @@ import NoObjects from 'no-objects'
|
||||
import React from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import { adminOnly } from 'utils'
|
||||
import { provideState, injectState } from 'reaclette'
|
||||
import { Text } from 'editable'
|
||||
import { Vm } from 'render-xo-item'
|
||||
import { withRouter } from 'react-router'
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
destroyProxyAppliances,
|
||||
editProxyAppliance,
|
||||
forgetProxyAppliances,
|
||||
getProxyApplianceUpdaterState,
|
||||
subscribeProxies,
|
||||
upgradeProxyAppliance,
|
||||
} from 'xo'
|
||||
@@ -49,7 +51,7 @@ const ACTIONS = [
|
||||
|
||||
const INDIVIDUAL_ACTIONS = [
|
||||
{
|
||||
handler: proxy =>
|
||||
handler: (proxy, { deployProxy }) =>
|
||||
deployProxy({
|
||||
proxy,
|
||||
}),
|
||||
@@ -63,13 +65,6 @@ const INDIVIDUAL_ACTIONS = [
|
||||
label: _('checkProxyHealth'),
|
||||
level: 'primary',
|
||||
},
|
||||
{
|
||||
disabled: ({ vmUuid }) => vmUuid === undefined,
|
||||
handler: upgradeProxyAppliance,
|
||||
icon: 'vm',
|
||||
label: _('upgradeProxyAppliance'),
|
||||
level: 'primary',
|
||||
},
|
||||
{
|
||||
handler: ({ id }, { router }) =>
|
||||
router.push({
|
||||
@@ -114,21 +109,116 @@ const COLUMNS = [
|
||||
itemRenderer: proxy => <Vm id={proxy.vmUuid} link />,
|
||||
name: _('vm'),
|
||||
},
|
||||
{
|
||||
itemRenderer: (proxy, { upgradesByProxy, upgradeAppliance }) => {
|
||||
const globalState = upgradesByProxy[proxy.id]
|
||||
if (globalState === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const { state } = globalState
|
||||
if (state.endsWith('-upgrade-needed')) {
|
||||
return (
|
||||
<div>
|
||||
<ActionButton
|
||||
btnStyle='success'
|
||||
disabled={proxy.vmUuid === undefined}
|
||||
handler={upgradeAppliance}
|
||||
handlerParam={proxy.id}
|
||||
icon='upgrade'
|
||||
>
|
||||
{_('upgrade')}
|
||||
</ActionButton>
|
||||
<p className='text-warning'>
|
||||
<Icon icon='alarm' />
|
||||
{_('upgradesAvailable')}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
state === 'xoa-up-to-date' ||
|
||||
state === 'xoa-upgraded' ||
|
||||
state === 'updater-upgraded' ||
|
||||
state === 'installer-upgraded'
|
||||
) {
|
||||
return (
|
||||
<ActionButton
|
||||
btnStyle='primary'
|
||||
disabled={proxy.vmUuid === undefined}
|
||||
handler={upgradeAppliance}
|
||||
handlerParam={proxy.id}
|
||||
icon='upgrade'
|
||||
>
|
||||
{_('upgrade')}
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ActionButton
|
||||
btnStyle='success'
|
||||
disabled={proxy.vmUuid === undefined}
|
||||
handler={upgradeAppliance}
|
||||
handlerParam={proxy.id}
|
||||
icon='upgrade'
|
||||
>
|
||||
{_('upgrade')}
|
||||
</ActionButton>
|
||||
<p className='text-danger'>
|
||||
<Icon icon='alarm' />
|
||||
{globalState.message}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
name: _('upgrade'),
|
||||
},
|
||||
]
|
||||
|
||||
export default decorate([
|
||||
adminOnly,
|
||||
withRouter,
|
||||
addSubscriptions({
|
||||
proxies: subscribeProxies,
|
||||
const Proxies = decorate([
|
||||
provideState({
|
||||
initialState: () => ({
|
||||
upgradesByProxy: {},
|
||||
}),
|
||||
effects: {
|
||||
initialize({ fetchProxyUpgrades }) {
|
||||
return fetchProxyUpgrades(this.props.proxies.map(({ id }) => id))
|
||||
},
|
||||
async fetchProxyUpgrades(effects, proxies) {
|
||||
const upgradesByProxy = { ...this.state.upgradesByProxy }
|
||||
await Promise.all(
|
||||
proxies.map(async id => {
|
||||
upgradesByProxy[id] = await getProxyApplianceUpdaterState(id).catch(
|
||||
e => ({
|
||||
state: 'error',
|
||||
message: _('proxyUpgradesError'),
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
this.state.upgradesByProxy = upgradesByProxy
|
||||
},
|
||||
async deployProxy({ fetchProxyUpgrades }, proxy) {
|
||||
return fetchProxyUpgrades([await deployProxy(proxy)])
|
||||
},
|
||||
async upgradeAppliance({ fetchProxyUpgrades }, id) {
|
||||
await upgradeProxyAppliance(id)
|
||||
return fetchProxyUpgrades([id])
|
||||
},
|
||||
},
|
||||
}),
|
||||
({ proxies, router }) => (
|
||||
withRouter,
|
||||
injectState,
|
||||
({ effects, proxies, router, state }) => (
|
||||
<Page header={HEADER} title='proxies' formatTitle>
|
||||
<div>
|
||||
<div className='mt-1 mb-1'>
|
||||
<ActionButton
|
||||
btnStyle='success'
|
||||
handler={deployProxy}
|
||||
handler={effects.deployProxy}
|
||||
icon='proxy'
|
||||
size='large'
|
||||
>
|
||||
@@ -140,7 +230,10 @@ export default decorate([
|
||||
collection={proxies}
|
||||
columns={COLUMNS}
|
||||
component={SortedTable}
|
||||
data-deployProxy={effects.deployProxy}
|
||||
data-router={router}
|
||||
data-upgradesByProxy={state.upgradesByProxy}
|
||||
data-upgradeAppliance={effects.upgradeAppliance}
|
||||
emptyMessage={
|
||||
<span className='text-muted'>
|
||||
<Icon icon='alarm' />
|
||||
@@ -155,3 +248,12 @@ export default decorate([
|
||||
</Page>
|
||||
),
|
||||
])
|
||||
|
||||
export default decorate([
|
||||
adminOnly,
|
||||
addSubscriptions({
|
||||
proxies: subscribeProxies,
|
||||
}),
|
||||
({ proxies }) =>
|
||||
proxies === undefined ? _('statusLoading') : <Proxies proxies={proxies} />,
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user