From 114501ebc7b60fba93663603227a1ae553466a5e Mon Sep 17 00:00:00 2001 From: Pierre Donias Date: Tue, 15 May 2018 16:11:04 +0200 Subject: [PATCH] feat(XOSAN): allow user to update packs (#2782) --- packages/xo-web/src/common/intl/locales/fr.js | 3 +- packages/xo-web/src/common/intl/messages.js | 15 +- packages/xo-web/src/common/utils.js | 36 ++ packages/xo-web/src/common/xo/index.js | 39 +- .../xo/install-xosan-pack-modal/index.js | 130 ----- .../xo/update-xosan-packs-modal/index.js | 89 +++ .../xo-web/src/xo-app/host/tab-advanced.js | 441 ++++++++------- packages/xo-web/src/xo-app/xosan/index.js | 33 +- packages/xo-web/src/xo-app/xosan/new-xosan.js | 529 ++++++++++-------- 9 files changed, 693 insertions(+), 622 deletions(-) delete mode 100644 packages/xo-web/src/common/xo/install-xosan-pack-modal/index.js create mode 100644 packages/xo-web/src/common/xo/update-xosan-packs-modal/index.js diff --git a/packages/xo-web/src/common/intl/locales/fr.js b/packages/xo-web/src/common/intl/locales/fr.js index 1dc61fcfd..71c33e40a 100644 --- a/packages/xo-web/src/common/intl/locales/fr.js +++ b/packages/xo-web/src/common/intl/locales/fr.js @@ -3857,7 +3857,8 @@ export default { xosanUsedSpace: 'Espace utilisé', // Original text: "XOSAN pack needs to be installed on each host of the pool." - xosanNeedPack: 'La pack XOSAN doit être installé sur tous les hôtes du pool.', + xosanNeedPack: + 'Le pack XOSAN doit être installé et à jour sur tous les hôtes du pool.', // Original text: "Install it now!" xosanInstallIt: 'Installer maintenant !', diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index 5f74be8c6..41af58e33 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -1766,7 +1766,8 @@ const messages = { xosanUsedSpace: 'Used space', xosanLicense: 'License', xosanMultipleLicenses: 'This XOSAN has more than 1 license!', - xosanNeedPack: 'XOSAN pack needs to be installed on each host of the pool.', + xosanNeedPack: + 'XOSAN pack needs to be installed and up to date on each host of the pool.', xosanInstallIt: 'Install it now!', xosanNeedRestart: 'Some hosts need their toolstack to be restarted before you can create an XOSAN', @@ -1794,6 +1795,14 @@ const messages = { xosanPbdsDetached: 'Some SRs are detached from the XOSAN', xosanBadStatus: 'Something is wrong with: {badStatuses}', xosanRunning: 'Running', + xosanUpdatePacks: 'Update packs', + xosanPackUpdateChecking: 'Checking for updates', + xosanPackUpdateError: + 'Error while checking XOSAN packs. Please make sure that the Cloud plugin is installed and loaded and that the updater is reachable.', + xosanPackUpdateUnavailable: 'XOSAN resources are unavailable', + xosanPackUpdateUnregistered: 'Not registered for XOSAN resources', + xosanPackUpdateUpToDate: "✓ This pool's XOSAN packs are up to date!", + xosanPackUpdateVersion: 'Update pool with latest pack v{version}', xosanDelete: 'Delete XOSAN', xosanFixIssue: 'Fix', xosanCreatingOn: 'Creating XOSAN on {pool}', @@ -1810,12 +1819,8 @@ const messages = { xosanRegister: 'Register your appliance first', xosanLoading: 'Loading…', xosanNotAvailable: 'XOSAN is not available at the moment', - xosanInstallPackOnHosts: 'Install XOSAN pack on these hosts:', - xosanInstallPack: 'Install {pack} v{version}?', xosanNoPackFound: 'No compatible XOSAN pack found for your XenServer versions.', - xosanPackRequirements: - 'At least one of these version requirements must be satisfied by all the hosts in this pool:', // SR tab XOSAN xosanVmsNotRunning: 'Some XOSAN Virtual Machines are not running', xosanVmsNotFound: 'Some XOSAN Virtual Machines could not be found', diff --git a/packages/xo-web/src/common/utils.js b/packages/xo-web/src/common/utils.js index 9a100fa41..9968c9da8 100644 --- a/packages/xo-web/src/common/utils.js +++ b/packages/xo-web/src/common/utils.js @@ -20,6 +20,7 @@ import { mapValues, replace, sample, + some, startsWith, } from 'lodash' @@ -28,6 +29,7 @@ import * as actions from './store/actions' import invoke from './invoke' import store from './store' import { getObject } from './selectors' +import { satisfies as versionSatisfies } from 'semver' export const EMPTY_ARRAY = Object.freeze([]) export const EMPTY_OBJECT = Object.freeze({}) @@ -523,6 +525,40 @@ export const ShortDate = ({ timestamp }) => ( ) +export const findLatestPack = (packs, hostsVersions) => { + const checkVersion = version => + !version || + every(hostsVersions, hostVersion => versionSatisfies(hostVersion, version)) + + let latestPack = { version: '0' } + forEach(packs, pack => { + if ( + pack.type === 'iso' && + compareVersions(pack.version, '>', latestPack.version) && + checkVersion(pack.requirements && pack.requirements.xenserver) + ) { + latestPack = pack + } + }) + + if (latestPack.version === '0') { + // No compatible pack was found + return + } + + return latestPack +} + +export const isLatestXosanPackInstalled = (latestXosanPack, hosts) => + latestXosanPack !== undefined && + every(hosts, host => + some( + host.supplementalPacks, + ({ name, version }) => + name === 'XOSAN' && version === latestXosanPack.version + ) + ) + // =================================================================== export const getMemoryUsedMetric = ({ memory, memoryFree = memory }) => diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 41d3c4c67..39dc323bd 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -2412,20 +2412,6 @@ export const removeXosanBricks = (xosansr, bricks) => export const computeXosanPossibleOptions = (lvmSrs, brickSize) => _call('xosan.computeXosanPossibleOptions', { lvmSrs, brickSize }) -import InstallXosanPackModal from './install-xosan-pack-modal' // eslint-disable-line import/first -export const downloadAndInstallXosanPack = pool => - confirm({ - title: _('xosanInstallPackTitle', { pool: pool.name_label }), - icon: 'export', - body: , - }).then(pack => - _call('xosan.downloadAndInstallXosanPack', { - id: pack.id, - version: pack.version, - pool: resolveId(pool), - }) - ) - export const registerXosan = () => _call('cloud.registerResource', { namespace: 'xosan' })::tap( subscribeResourceCatalog.forceRefresh @@ -2434,6 +2420,31 @@ export const registerXosan = () => export const fixHostNotInXosanNetwork = (xosanSr, host) => _call('xosan.fixHostNotInNetwork', { xosanSr, host }) +// XOSAN packs ----------------------------------------------------------------- + +export const getResourceCatalog = () => _call('cloud.getResourceCatalog') + +const downloadAndInstallXosanPack = (pack, pool, { version }) => + _call('xosan.downloadAndInstallXosanPack', { + id: resolveId(pack), + version, + pool: resolveId(pool), + }) + +import UpdateXosanPacksModal from './update-xosan-packs-modal' // eslint-disable-line import/first +export const updateXosanPacks = pool => + confirm({ + title: _('xosanUpdatePacks'), + icon: 'host-patch-update', + body: , + }).then(pack => { + if (pack === undefined) { + return + } + + return downloadAndInstallXosanPack(pack, pool, { version: pack.version }) + }) + // Licenses -------------------------------------------------------------------- export const getLicenses = productId => _call('xoa.getLicenses', { productId }) diff --git a/packages/xo-web/src/common/xo/install-xosan-pack-modal/index.js b/packages/xo-web/src/common/xo/install-xosan-pack-modal/index.js deleted file mode 100644 index c5c1c7202..000000000 --- a/packages/xo-web/src/common/xo/install-xosan-pack-modal/index.js +++ /dev/null @@ -1,130 +0,0 @@ -import _ from 'intl' -import Component from 'base-component' -import React from 'react' -import { connectStore, compareVersions, isXosanPack } from 'utils' -import { subscribeResourceCatalog, subscribePlugins } from 'xo' -import { - createGetObjectsOfType, - createSelector, - createCollectionWrapper, -} from 'selectors' -import { satisfies as versionSatisfies } from 'semver' -import { every, filter, forEach, map, some } from 'lodash' - -const findLatestPack = (packs, hostsVersions) => { - const checkVersion = version => - every(hostsVersions, hostVersion => versionSatisfies(hostVersion, version)) - - let latestPack = { version: '0' } - forEach(packs, pack => { - const xsVersionRequirement = - pack.requirements && pack.requirements.xenserver - - if ( - pack.type === 'iso' && - compareVersions(pack.version, latestPack.version) > 0 && - (!xsVersionRequirement || checkVersion(xsVersionRequirement)) - ) { - latestPack = pack - } - }) - - if (latestPack.version === '0') { - // No compatible pack was found - return - } - - return latestPack -} - -@connectStore( - () => ({ - hosts: createGetObjectsOfType('host').filter( - createSelector( - (_, { pool }) => pool != null && pool.id, - poolId => - poolId - ? host => - host.$pool === poolId && - !some(host.supplementalPacks, isXosanPack) - : false - ) - ), - }), - { withRef: true } -) -export default class InstallXosanPackModal extends Component { - componentDidMount () { - this._unsubscribePlugins = subscribePlugins(plugins => - this.setState({ plugins }) - ) - this._unsubscribeResourceCatalog = subscribeResourceCatalog(catalog => - this.setState({ catalog }) - ) - } - - componentWillUnmount () { - this._unsubscribePlugins() - this._unsubscribeResourceCatalog() - } - - _getXosanLatestPack = createSelector( - () => this.state.catalog && this.state.catalog.xosan, - createSelector( - () => this.props.hosts, - createCollectionWrapper(hosts => map(hosts, 'version')) - ), - findLatestPack - ) - - _getXosanPacks = createSelector( - () => this.state.catalog && this.state.catalog.xosan, - packs => filter(packs, ({ type }) => type === 'iso') - ) - - get value () { - return this._getXosanLatestPack() - } - - render () { - const { hosts } = this.props - const latestPack = this._getXosanLatestPack() - - return ( -
- {latestPack ? ( -
- {_('xosanInstallPackOnHosts')} -
    - {map(hosts, host =>
  • {host.name_label}
  • )} -
-
- {_('xosanInstallPack', { - pack: latestPack.name, - version: latestPack.version, - })} -
-
- ) : ( -
- {_('xosanNoPackFound')} -
- {_('xosanPackRequirements')} -
    - {map(this._getXosanPacks(), ({ name, requirements }, key) => ( -
  • - {_.keyValue( - name, - requirements && requirements.xenserver - ? requirements.xenserver - : '/' - )} -
  • - ))} -
-
- )} -
- ) - } -} diff --git a/packages/xo-web/src/common/xo/update-xosan-packs-modal/index.js b/packages/xo-web/src/common/xo/update-xosan-packs-modal/index.js new file mode 100644 index 000000000..92f099d50 --- /dev/null +++ b/packages/xo-web/src/common/xo/update-xosan-packs-modal/index.js @@ -0,0 +1,89 @@ +import _ from 'intl' +import React from 'react' +import Component from 'base-component' +import { createGetObjectsOfType, createSelector } from 'selectors' +import { map } from 'lodash' +import { subscribeResourceCatalog } from 'xo' +import { + addSubscriptions, + isLatestXosanPackInstalled, + connectStore, + findLatestPack, +} from 'utils' + +@connectStore( + { + hosts: createGetObjectsOfType('host').filter((_, { pool }) => host => + host.$pool === pool.id + ), + }, + { withRef: true } +) +@addSubscriptions(() => ({ + catalog: subscribeResourceCatalog, +})) +export default class UpdateXosanPacksModal extends Component { + state = { + status: 'checking', + } + + get value () { + return this.state.pack + } + + _getStatus = createSelector( + () => this.props.catalog, + () => this.props.hosts, + (catalog, hosts) => { + if (catalog === undefined) { + return { status: 'error' } + } + + if (catalog._namespaces.xosan === undefined) { + return { status: 'unavailable' } + } + + if (!catalog._namespaces.xosan.registered) { + return { status: 'unregistered' } + } + + const pack = findLatestPack(catalog.xosan, map(hosts, 'version')) + + if (pack === undefined) { + return { status: 'noPack' } + } + + if (isLatestXosanPackInstalled(pack, hosts)) { + return { status: 'upToDate' } + } + + return { status: 'packFound', pack } + } + ) + + render () { + const { status, pack } = this._getStatus() + switch (status) { + case 'checking': + return {_('xosanPackUpdateChecking')} + case 'error': + return {_('xosanPackUpdateError')} + case 'unavailable': + return {_('xosanPackUpdateUnavailable')} + case 'unregistered': + return {_('xosanPackUpdateUnregistered')} + case 'noPack': + return {_('xosanNoPackFound')} + case 'upToDate': + return {_('xosanPackUpdateUpToDate')} + case 'packFound': + return ( +
+ {_('xosanPackUpdateVersion', { + version: pack.version, + })} +
+ ) + } + } +} diff --git a/packages/xo-web/src/xo-app/host/tab-advanced.js b/packages/xo-web/src/xo-app/host/tab-advanced.js index cf015f268..8709aeaa4 100644 --- a/packages/xo-web/src/xo-app/host/tab-advanced.js +++ b/packages/xo-web/src/xo-app/host/tab-advanced.js @@ -1,10 +1,11 @@ import _ from 'intl' +import Component from 'base-component' import Copiable from 'copiable' import React from 'react' import TabButton from 'tab-button' import SelectFiles from 'select-files' import Upgrade from 'xoa-upgrade' -import { connectStore } from 'utils' +import { compareVersions, connectStore } from 'utils' import { Toggle } from 'form' import { enableHost, @@ -17,7 +18,7 @@ import { import { FormattedRelative, FormattedTime } from 'react-intl' import { Container, Row, Col } from 'grid' import { createGetObjectsOfType, createSelector } from 'selectors' -import { map, noop } from 'lodash' +import { forEach, map, noop } from 'lodash' const ALLOW_INSTALL_SUPP_PACK = process.env.XOA_PLAN > 1 @@ -31,7 +32,9 @@ const formatPack = ({ name, author, description, version }, key) => ( ) -export default connectStore(() => { +const getPackId = ({ author, name }) => `${author}\0${name}` + +@connectStore(() => { const getPgpus = createGetObjectsOfType('PGPU') .pick((_, { host }) => host.$PGPUs) .sort() @@ -44,207 +47,233 @@ export default connectStore(() => { pcis: getPcis, pgpus: getPgpus, } -})(({ host, pcis, pgpus }) => ( - - - - {host.power_state === 'Running' && ( - - )} - {host.enabled ? ( - - ) : ( - - )} - - {host.power_state !== 'Running' && ( - - )} - - - - -

{_('xenSettingsLabel')}

- - - - - {host.uuid} - - - - {host.address} - - - - - - - - - - - - - - - - - - - - - {host.license_params.sku_marketing_name} {host.version} ({ - host.license_params.sku_type - }) - - - - - {host.build} - - - - {host.iSCSI_name} - - -
{_('uuid')}
{_('hostAddress')}
{_('hostStatus')} - {host.enabled - ? _('hostStatusEnabled') - : _('hostStatusDisabled')} -
{_('hostPowerOnMode')} - -
{_('hostStartedSince')} - {_('started', { - ago: , - })} -
{_('hostStackStartedSince')} - {_('started', { - ago: , - })} -
{_('hostXenServerVersion')}
{_('hostBuildNumber')}
{_('hostIscsiName')}
-
-

{_('hardwareHostSettingsLabel')}

- - - - - {host.CPUs.modelname} - - - - - - - - - - - - - {host.bios_strings['system-manufacturer']} ({ - host.bios_strings['system-product-name'] - }) - - - - - - - -
{_('hostCpusModel')}
{_('hostGpus')} - {map(pgpus, pgpu => pcis[pgpu.pci].device_name).join(', ')} -
{_('hostCpusNumber')} - {host.cpus.cores} ({host.cpus.sockets}) -
{_('hostManufacturerinfo')}
{_('hostBiosinfo')} - {host.bios_strings['bios-vendor']} ({ - host.bios_strings['bios-version'] - }) -
-
-

{_('licenseHostSettingsLabel')}

- - - - - - - - - - - - - - - -
{_('hostLicenseType')}{host.license_params.sku_type}
{_('hostLicenseSocket')}{host.license_params.sockets}
{_('hostLicenseExpiry')} - -
-
-

{_('supplementalPacks')}

- - - {map(host.supplementalPacks, formatPack)} - {ALLOW_INSTALL_SUPP_PACK && ( - - - - +}) +export default class extends Component { + _getPacks = createSelector( + () => this.props.host.supplementalPacks, + packs => { + const uniqPacks = {} + let packId, previousPack + forEach(packs, pack => { + packId = getPackId(pack) + if ( + (previousPack = uniqPacks[packId]) === undefined || + compareVersions(pack.version, previousPack.version) > 0 + ) { + uniqPacks[packId] = pack + } + }) + return uniqPacks + } + ) + + render () { + const { host, pcis, pgpus } = this.props + return ( + + + + {host.power_state === 'Running' && ( + )} - -
{_('supplementalPackNew')} - installSupplementalPack(host, file)} - /> -
- {!ALLOW_INSTALL_SUPP_PACK && [ -

{_('supplementalPackNew')}

, - - - , - ]} - -
-
-)) + {host.enabled ? ( + + ) : ( + + )} + + {host.power_state !== 'Running' && ( + + )} + + + + +

{_('xenSettingsLabel')}

+ + + + + {host.uuid} + + + + {host.address} + + + + + + + + + + + + + + + + + + + + + {host.license_params.sku_marketing_name} {host.version} ({ + host.license_params.sku_type + }) + + + + + {host.build} + + + + {host.iSCSI_name} + + +
{_('uuid')}
{_('hostAddress')}
{_('hostStatus')} + {host.enabled + ? _('hostStatusEnabled') + : _('hostStatusDisabled')} +
{_('hostPowerOnMode')} + +
{_('hostStartedSince')} + {_('started', { + ago: , + })} +
{_('hostStackStartedSince')} + {_('started', { + ago: ( + + ), + })} +
{_('hostXenServerVersion')}
{_('hostBuildNumber')}
{_('hostIscsiName')}
+
+

{_('hardwareHostSettingsLabel')}

+ + + + + {host.CPUs.modelname} + + + + + + + + + + + + + {host.bios_strings['system-manufacturer']} ({ + host.bios_strings['system-product-name'] + }) + + + + + + + +
{_('hostCpusModel')}
{_('hostGpus')} + {map(pgpus, pgpu => pcis[pgpu.pci].device_name).join(', ')} +
{_('hostCpusNumber')} + {host.cpus.cores} ({host.cpus.sockets}) +
{_('hostManufacturerinfo')}
{_('hostBiosinfo')} + {host.bios_strings['bios-vendor']} ({ + host.bios_strings['bios-version'] + }) +
+
+

{_('licenseHostSettingsLabel')}

+ + + + + + + + + + + + + + + +
{_('hostLicenseType')}{host.license_params.sku_type}
{_('hostLicenseSocket')}{host.license_params.sockets}
{_('hostLicenseExpiry')} + +
+
+

{_('supplementalPacks')}

+ + + {map(this._getPacks(), formatPack)} + {ALLOW_INSTALL_SUPP_PACK && ( + + + + + )} + +
{_('supplementalPackNew')} + installSupplementalPack(host, file)} + /> +
+ {!ALLOW_INSTALL_SUPP_PACK && [ +

{_('supplementalPackNew')}

, + + + , + ]} + +
+ + ) + } +} diff --git a/packages/xo-web/src/xo-app/xosan/index.js b/packages/xo-web/src/xo-app/xosan/index.js index c623c6899..0bd854e06 100644 --- a/packages/xo-web/src/xo-app/xosan/index.js +++ b/packages/xo-web/src/xo-app/xosan/index.js @@ -10,24 +10,13 @@ import Tooltip from 'tooltip' import { Container, Col, Row } from 'grid' import { get } from 'xo-defined' import { ignoreErrors } from 'promise-toolbox' -import { - every, - filter, - find, - flatten, - forEach, - isEmpty, - map, - mapValues, - some, -} from 'lodash' +import { every, filter, find, flatten, forEach, isEmpty, map } from 'lodash' import { createGetObjectsOfType, createSelector, isAdmin } from 'selectors' import { addSubscriptions, connectStore, cowSet, formatSize, - isXosanPack, ShortDate, } from 'utils' import { @@ -37,6 +26,7 @@ import { subscribePlugins, subscribeResourceCatalog, subscribeVolumeInfo, + updateXosanPacks, } from 'xo' import NewXosan from './new-xosan' @@ -208,6 +198,12 @@ const XOSAN_COLUMNS = [ ] const XOSAN_INDIVIDUAL_ACTIONS = [ + { + handler: (xosan, { pools }) => updateXosanPacks(pools[xosan.$pool]), + icon: 'host-patch-update', + label: _('xosanUpdatePacks'), + level: 'primary', + }, { handler: deleteSr, icon: 'delete', @@ -221,14 +217,6 @@ const XOSAN_INDIVIDUAL_ACTIONS = [ const getHostsByPool = getHosts.groupBy('$pool') const getPools = createGetObjectsOfType('pool') - const noPacksByPool = createSelector(getHostsByPool, hostsByPool => - mapValues( - hostsByPool, - (poolHosts, poolId) => - !every(poolHosts, host => some(host.supplementalPacks, isXosanPack)) - ) - ) - const getPbdsBySr = createGetObjectsOfType('PBD').groupBy('SR') const getXosanSrs = createSelector( createGetObjectsOfType('SR').filter([ @@ -291,7 +279,6 @@ const XOSAN_INDIVIDUAL_ACTIONS = [ isAdmin, isMasterOfflineByPool: getIsMasterOfflineByPool, hostsNeedRestartByPool: getHostsNeedRestartByPool, - noPacksByPool, poolPredicate: getPoolPredicate, pools: getPools, xoaRegistration: state => state.xoaRegisterState, @@ -419,8 +406,8 @@ export default class Xosan extends Component { const { hostsNeedRestartByPool, isAdmin, - noPacksByPool, poolPredicate, + pools, xoaRegistration, xosanSrs, } = this.props @@ -456,7 +443,6 @@ export default class Xosan extends Component { (this._isXosanRegistered() ? ( diff --git a/packages/xo-web/src/xo-app/xosan/new-xosan.js b/packages/xo-web/src/xo-app/xosan/new-xosan.js index 0308bfceb..b543a1ab1 100644 --- a/packages/xo-web/src/xo-app/xosan/new-xosan.js +++ b/packages/xo-web/src/xo-app/xosan/new-xosan.js @@ -29,15 +29,18 @@ import { } from 'selectors' import { addSubscriptions, + isLatestXosanPackInstalled, compareVersions, connectStore, + findLatestPack, formatSize, mapPlus, } from 'utils' import { computeXosanPossibleOptions, createXosanSR, - downloadAndInstallXosanPack, + updateXosanPacks, + getResourceCatalog, restartHostsAgents, subscribeResourceCatalog, } from 'xo' @@ -76,14 +79,47 @@ export default class NewXosan extends Component { suggestion: 0, } + _checkPacks = pool => + getResourceCatalog().then( + catalog => { + if (catalog === undefined || catalog.xosan === undefined) { + this.setState({ + checkPackError: true, + }) + return + } + + const hosts = filter(this.props.hosts, { $pool: pool.id }) + const pack = findLatestPack(catalog.xosan, map(hosts, 'version')) + + if (isLatestXosanPackInstalled(pack, hosts)) { + this.setState({ + needsUpdate: true, + }) + } + }, + () => { + this.setState({ + checkPackError: true, + }) + } + ) + + _updateXosanPacks = pool => + updateXosanPacks(pool).then(() => this._checkPacks(pool)) + _selectPool = pool => { this.setState({ - selectedSrs: {}, brickSize: DEFAULT_BRICKSIZE, + checkPackError: false, memorySize: DEFAULT_MEMORY, + needsUpdate: false, pif: undefined, pool, + selectedSrs: {}, }) + + return this._checkPacks(pool) } componentDidUpdate () { @@ -243,10 +279,12 @@ export default class NewXosan extends Component { const { brickSize, + checkPackError, customBrickSize, customIpRange, ipRange, memorySize, + needsUpdate, pif, pool, selectedSrs, @@ -256,12 +294,7 @@ export default class NewXosan extends Component { vlan, } = this.state - const { - hostsNeedRestartByPool, - noPacksByPool, - poolPredicate, - notRegistered, - } = this.props + const { hostsNeedRestartByPool, poolPredicate, notRegistered } = this.props if (notRegistered) { return ( @@ -296,9 +329,7 @@ export default class NewXosan extends Component { {pool != null && - noPacksByPool[pool.id] && ( + (checkPackError ? ( + {_('xosanPackUpdateError')} + ) : needsUpdate ? ( - {_('xosanNeedPack')} -
- - {_('xosanInstallIt')} - + + {_('xosanNeedPack')} +
+ + {_('xosanInstallIt')} + +
- )} - {!isEmpty(hostsNeedRestart) && ( - - {_('xosanNeedRestart')} -
- - {_('xosanRestartAgents')} - -
- )} - {pool != null && - !noPacksByPool[pool.id] && - isEmpty(hostsNeedRestart) && [ + ) : !isEmpty(hostsNeedRestart) ? ( - {_('xosanSelect2Srs')} - - - - - - - - - - - {map(lvmsrs, sr => { - const host = find(hosts, ['id', sr.$container]) - - return ( - - - - - - - - ) - })} - -
- {_('xosanName')}{_('xosanHost')}{_('xosanSize')}{_('xosanUsedSpace')}
- this._selectSr(event, sr)} - type='checkbox' - /> - - - {sr.name_label} - - - - {host.name_label} - - {formatSize(sr.size)} - {sr.size > 0 && ( - - - - )} -
-
, - - {!isEmpty(suggestions) && ( -
-

{_('xosanSuggestions')}

+ + {_('xosanNeedRestart')} +
+ + {_('xosanRestartAgents')} + + + + ) : ( + [ + + + {_('xosanSelect2Srs')} - - - + + + + - {map( - suggestions, - ( - { layout, redundancy, capacity, availableSpace }, - index - ) => ( - + {map(lvmsrs, sr => { + const host = find(hosts, ['id', sr.$container]) + + return ( + - - - + + + ) - )} + })}
- {_('xosanLayout')}{_('xosanRedundancy')}{_('xosanCapacity')}{_('xosanAvailableSpace')}{_('xosanName')}{_('xosanHost')}{_('xosanSize')}{_('xosanUsedSpace')}
this._selectSr(event, sr)} + type='checkbox' /> {layout}{redundancy}{capacity} - {availableSpace === 0 ? ( - 0 - ) : ( - formatSize(availableSpace) + + {sr.name_label} + + + + {host.name_label} + + {formatSize(sr.size)} + {sr.size > 0 && ( + + + )}
- {architecture.layout === 'disperse' && ( -
- {_('xosanDisperseWarning', { - link: ( - - xen-orchestra.com/docs/xosan_types.html - - ), - })} + + , + + + {!isEmpty(suggestions) && ( +
+

{_('xosanSuggestions')}

+ + + + + + + + + + + {map( + suggestions, + ( + { layout, redundancy, capacity, availableSpace }, + index + ) => ( + + + + + + + + ) + )} + +
+ {_('xosanLayout')}{_('xosanRedundancy')}{_('xosanCapacity')}{_('xosanAvailableSpace')}
+ + {layout}{redundancy}{capacity} + {availableSpace === 0 ? ( + 0 + ) : ( + formatSize(availableSpace) + )} +
+ {architecture.layout === 'disperse' && ( +
+ {_('xosanDisperseWarning', { + link: ( + + xen-orchestra.com/docs/xosan_types.html + + ), + })} +
+ )} + +
+ {' '} + {_('xosanAdvanced')}{' '} + {this.state.showAdvanced && ( + + + {_('xosanVlan')} + + + + + + + + + + + {_('xosanCustomIpNetwork')} + + + + + + + + + + + {_('xosanBrickSize')} + + + + + + + + + + + + + + + + + )} +
)} - -
- {' '} - {_('xosanAdvanced')}{' '} - {this.state.showAdvanced && ( - - - {_('xosanVlan')} - - - - - - - - - - - {_('xosanCustomIpNetwork')} - - - - - - - - - - - {_('xosanBrickSize')} - - - - - - - - - - - - - - - - - )} -
-
- )} -
, - - - - {_('xosanCreate')} - - - , - ]} + + , + + + + {_('xosanCreate')} + + + , + ] + ))}
)