feat(XOSAN): allow user to update packs (#2782)
This commit is contained in:
parent
ebab7c0867
commit
114501ebc7
@ -3857,7 +3857,8 @@ export default {
|
|||||||
xosanUsedSpace: 'Espace utilisé',
|
xosanUsedSpace: 'Espace utilisé',
|
||||||
|
|
||||||
// Original text: "XOSAN pack needs to be installed on each host of the pool."
|
// 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!"
|
// Original text: "Install it now!"
|
||||||
xosanInstallIt: 'Installer maintenant !',
|
xosanInstallIt: 'Installer maintenant !',
|
||||||
|
@ -1766,7 +1766,8 @@ const messages = {
|
|||||||
xosanUsedSpace: 'Used space',
|
xosanUsedSpace: 'Used space',
|
||||||
xosanLicense: 'License',
|
xosanLicense: 'License',
|
||||||
xosanMultipleLicenses: 'This XOSAN has more than 1 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!',
|
xosanInstallIt: 'Install it now!',
|
||||||
xosanNeedRestart:
|
xosanNeedRestart:
|
||||||
'Some hosts need their toolstack to be restarted before you can create an XOSAN',
|
'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',
|
xosanPbdsDetached: 'Some SRs are detached from the XOSAN',
|
||||||
xosanBadStatus: 'Something is wrong with: {badStatuses}',
|
xosanBadStatus: 'Something is wrong with: {badStatuses}',
|
||||||
xosanRunning: 'Running',
|
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',
|
xosanDelete: 'Delete XOSAN',
|
||||||
xosanFixIssue: 'Fix',
|
xosanFixIssue: 'Fix',
|
||||||
xosanCreatingOn: 'Creating XOSAN on {pool}',
|
xosanCreatingOn: 'Creating XOSAN on {pool}',
|
||||||
@ -1810,12 +1819,8 @@ const messages = {
|
|||||||
xosanRegister: 'Register your appliance first',
|
xosanRegister: 'Register your appliance first',
|
||||||
xosanLoading: 'Loading…',
|
xosanLoading: 'Loading…',
|
||||||
xosanNotAvailable: 'XOSAN is not available at the moment',
|
xosanNotAvailable: 'XOSAN is not available at the moment',
|
||||||
xosanInstallPackOnHosts: 'Install XOSAN pack on these hosts:',
|
|
||||||
xosanInstallPack: 'Install {pack} v{version}?',
|
|
||||||
xosanNoPackFound:
|
xosanNoPackFound:
|
||||||
'No compatible XOSAN pack found for your XenServer versions.',
|
'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
|
// SR tab XOSAN
|
||||||
xosanVmsNotRunning: 'Some XOSAN Virtual Machines are not running',
|
xosanVmsNotRunning: 'Some XOSAN Virtual Machines are not running',
|
||||||
xosanVmsNotFound: 'Some XOSAN Virtual Machines could not be found',
|
xosanVmsNotFound: 'Some XOSAN Virtual Machines could not be found',
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
mapValues,
|
mapValues,
|
||||||
replace,
|
replace,
|
||||||
sample,
|
sample,
|
||||||
|
some,
|
||||||
startsWith,
|
startsWith,
|
||||||
} from 'lodash'
|
} from 'lodash'
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ import * as actions from './store/actions'
|
|||||||
import invoke from './invoke'
|
import invoke from './invoke'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import { getObject } from './selectors'
|
import { getObject } from './selectors'
|
||||||
|
import { satisfies as versionSatisfies } from 'semver'
|
||||||
|
|
||||||
export const EMPTY_ARRAY = Object.freeze([])
|
export const EMPTY_ARRAY = Object.freeze([])
|
||||||
export const EMPTY_OBJECT = Object.freeze({})
|
export const EMPTY_OBJECT = Object.freeze({})
|
||||||
@ -523,6 +525,40 @@ export const ShortDate = ({ timestamp }) => (
|
|||||||
<FormattedDate value={timestamp} month='short' day='numeric' year='numeric' />
|
<FormattedDate value={timestamp} month='short' day='numeric' year='numeric' />
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 }) =>
|
export const getMemoryUsedMetric = ({ memory, memoryFree = memory }) =>
|
||||||
|
@ -2412,20 +2412,6 @@ export const removeXosanBricks = (xosansr, bricks) =>
|
|||||||
export const computeXosanPossibleOptions = (lvmSrs, brickSize) =>
|
export const computeXosanPossibleOptions = (lvmSrs, brickSize) =>
|
||||||
_call('xosan.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: <InstallXosanPackModal pool={pool} />,
|
|
||||||
}).then(pack =>
|
|
||||||
_call('xosan.downloadAndInstallXosanPack', {
|
|
||||||
id: pack.id,
|
|
||||||
version: pack.version,
|
|
||||||
pool: resolveId(pool),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const registerXosan = () =>
|
export const registerXosan = () =>
|
||||||
_call('cloud.registerResource', { namespace: 'xosan' })::tap(
|
_call('cloud.registerResource', { namespace: 'xosan' })::tap(
|
||||||
subscribeResourceCatalog.forceRefresh
|
subscribeResourceCatalog.forceRefresh
|
||||||
@ -2434,6 +2420,31 @@ export const registerXosan = () =>
|
|||||||
export const fixHostNotInXosanNetwork = (xosanSr, host) =>
|
export const fixHostNotInXosanNetwork = (xosanSr, host) =>
|
||||||
_call('xosan.fixHostNotInNetwork', { 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: <UpdateXosanPacksModal pool={pool} />,
|
||||||
|
}).then(pack => {
|
||||||
|
if (pack === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return downloadAndInstallXosanPack(pack, pool, { version: pack.version })
|
||||||
|
})
|
||||||
|
|
||||||
// Licenses --------------------------------------------------------------------
|
// Licenses --------------------------------------------------------------------
|
||||||
|
|
||||||
export const getLicenses = productId => _call('xoa.getLicenses', { productId })
|
export const getLicenses = productId => _call('xoa.getLicenses', { productId })
|
||||||
|
@ -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 (
|
|
||||||
<div>
|
|
||||||
{latestPack ? (
|
|
||||||
<div>
|
|
||||||
{_('xosanInstallPackOnHosts')}
|
|
||||||
<ul>
|
|
||||||
{map(hosts, host => <li key={host.id}>{host.name_label}</li>)}
|
|
||||||
</ul>
|
|
||||||
<div className='mt-1'>
|
|
||||||
{_('xosanInstallPack', {
|
|
||||||
pack: latestPack.name,
|
|
||||||
version: latestPack.version,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
{_('xosanNoPackFound')}
|
|
||||||
<br />
|
|
||||||
{_('xosanPackRequirements')}
|
|
||||||
<ul>
|
|
||||||
{map(this._getXosanPacks(), ({ name, requirements }, key) => (
|
|
||||||
<li key={key}>
|
|
||||||
{_.keyValue(
|
|
||||||
name,
|
|
||||||
requirements && requirements.xenserver
|
|
||||||
? requirements.xenserver
|
|
||||||
: '/'
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <em>{_('xosanPackUpdateChecking')}</em>
|
||||||
|
case 'error':
|
||||||
|
return <em>{_('xosanPackUpdateError')}</em>
|
||||||
|
case 'unavailable':
|
||||||
|
return <em>{_('xosanPackUpdateUnavailable')}</em>
|
||||||
|
case 'unregistered':
|
||||||
|
return <em>{_('xosanPackUpdateUnregistered')}</em>
|
||||||
|
case 'noPack':
|
||||||
|
return <em>{_('xosanNoPackFound')}</em>
|
||||||
|
case 'upToDate':
|
||||||
|
return <em>{_('xosanPackUpdateUpToDate')}</em>
|
||||||
|
case 'packFound':
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{_('xosanPackUpdateVersion', {
|
||||||
|
version: pack.version,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
import _ from 'intl'
|
import _ from 'intl'
|
||||||
|
import Component from 'base-component'
|
||||||
import Copiable from 'copiable'
|
import Copiable from 'copiable'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import TabButton from 'tab-button'
|
import TabButton from 'tab-button'
|
||||||
import SelectFiles from 'select-files'
|
import SelectFiles from 'select-files'
|
||||||
import Upgrade from 'xoa-upgrade'
|
import Upgrade from 'xoa-upgrade'
|
||||||
import { connectStore } from 'utils'
|
import { compareVersions, connectStore } from 'utils'
|
||||||
import { Toggle } from 'form'
|
import { Toggle } from 'form'
|
||||||
import {
|
import {
|
||||||
enableHost,
|
enableHost,
|
||||||
@ -17,7 +18,7 @@ import {
|
|||||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||||
import { Container, Row, Col } from 'grid'
|
import { Container, Row, Col } from 'grid'
|
||||||
import { createGetObjectsOfType, createSelector } from 'selectors'
|
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
|
const ALLOW_INSTALL_SUPP_PACK = process.env.XOA_PLAN > 1
|
||||||
|
|
||||||
@ -31,7 +32,9 @@ const formatPack = ({ name, author, description, version }, key) => (
|
|||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default connectStore(() => {
|
const getPackId = ({ author, name }) => `${author}\0${name}`
|
||||||
|
|
||||||
|
@connectStore(() => {
|
||||||
const getPgpus = createGetObjectsOfType('PGPU')
|
const getPgpus = createGetObjectsOfType('PGPU')
|
||||||
.pick((_, { host }) => host.$PGPUs)
|
.pick((_, { host }) => host.$PGPUs)
|
||||||
.sort()
|
.sort()
|
||||||
@ -44,207 +47,233 @@ export default connectStore(() => {
|
|||||||
pcis: getPcis,
|
pcis: getPcis,
|
||||||
pgpus: getPgpus,
|
pgpus: getPgpus,
|
||||||
}
|
}
|
||||||
})(({ host, pcis, pgpus }) => (
|
})
|
||||||
<Container>
|
export default class extends Component {
|
||||||
<Row>
|
_getPacks = createSelector(
|
||||||
<Col className='text-xs-right'>
|
() => this.props.host.supplementalPacks,
|
||||||
{host.power_state === 'Running' && (
|
packs => {
|
||||||
<TabButton
|
const uniqPacks = {}
|
||||||
btnStyle='warning'
|
let packId, previousPack
|
||||||
handler={forceReboot}
|
forEach(packs, pack => {
|
||||||
handlerParam={host}
|
packId = getPackId(pack)
|
||||||
icon='host-force-reboot'
|
if (
|
||||||
labelId='forceRebootHostLabel'
|
(previousPack = uniqPacks[packId]) === undefined ||
|
||||||
/>
|
compareVersions(pack.version, previousPack.version) > 0
|
||||||
)}
|
) {
|
||||||
{host.enabled ? (
|
uniqPacks[packId] = pack
|
||||||
<TabButton
|
}
|
||||||
btnStyle='warning'
|
})
|
||||||
handler={disableHost}
|
return uniqPacks
|
||||||
handlerParam={host}
|
}
|
||||||
icon='host-disable'
|
)
|
||||||
labelId='disableHostLabel'
|
|
||||||
/>
|
render () {
|
||||||
) : (
|
const { host, pcis, pgpus } = this.props
|
||||||
<TabButton
|
return (
|
||||||
btnStyle='success'
|
<Container>
|
||||||
handler={enableHost}
|
<Row>
|
||||||
handlerParam={host}
|
<Col className='text-xs-right'>
|
||||||
icon='host-enable'
|
{host.power_state === 'Running' && (
|
||||||
labelId='enableHostLabel'
|
<TabButton
|
||||||
/>
|
btnStyle='warning'
|
||||||
)}
|
handler={forceReboot}
|
||||||
<TabButton
|
handlerParam={host}
|
||||||
btnStyle='danger'
|
icon='host-force-reboot'
|
||||||
handler={detachHost}
|
labelId='forceRebootHostLabel'
|
||||||
handlerParam={host}
|
/>
|
||||||
icon='host-eject'
|
|
||||||
labelId='detachHost'
|
|
||||||
/>
|
|
||||||
{host.power_state !== 'Running' && (
|
|
||||||
<TabButton
|
|
||||||
btnStyle='danger'
|
|
||||||
handler={forgetHost}
|
|
||||||
handlerParam={host}
|
|
||||||
icon='host-forget'
|
|
||||||
labelId='forgetHost'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<h3>{_('xenSettingsLabel')}</h3>
|
|
||||||
<table className='table'>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>{_('uuid')}</th>
|
|
||||||
<Copiable tagName='td'>{host.uuid}</Copiable>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostAddress')}</th>
|
|
||||||
<Copiable tagName='td'>{host.address}</Copiable>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostStatus')}</th>
|
|
||||||
<td>
|
|
||||||
{host.enabled
|
|
||||||
? _('hostStatusEnabled')
|
|
||||||
: _('hostStatusDisabled')}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostPowerOnMode')}</th>
|
|
||||||
<td>
|
|
||||||
<Toggle
|
|
||||||
disabled
|
|
||||||
onChange={noop}
|
|
||||||
value={Boolean(host.powerOnMode)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostStartedSince')}</th>
|
|
||||||
<td>
|
|
||||||
{_('started', {
|
|
||||||
ago: <FormattedRelative value={host.startTime * 1000} />,
|
|
||||||
})}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostStackStartedSince')}</th>
|
|
||||||
<td>
|
|
||||||
{_('started', {
|
|
||||||
ago: <FormattedRelative value={host.agentStartTime * 1000} />,
|
|
||||||
})}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostXenServerVersion')}</th>
|
|
||||||
<Copiable tagName='td' data={host.version}>
|
|
||||||
{host.license_params.sku_marketing_name} {host.version} ({
|
|
||||||
host.license_params.sku_type
|
|
||||||
})
|
|
||||||
</Copiable>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostBuildNumber')}</th>
|
|
||||||
<Copiable tagName='td'>{host.build}</Copiable>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostIscsiName')}</th>
|
|
||||||
<Copiable tagName='td'>{host.iSCSI_name}</Copiable>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<br />
|
|
||||||
<h3>{_('hardwareHostSettingsLabel')}</h3>
|
|
||||||
<table className='table'>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostCpusModel')}</th>
|
|
||||||
<Copiable tagName='td'>{host.CPUs.modelname}</Copiable>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostGpus')}</th>
|
|
||||||
<td>
|
|
||||||
{map(pgpus, pgpu => pcis[pgpu.pci].device_name).join(', ')}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostCpusNumber')}</th>
|
|
||||||
<td>
|
|
||||||
{host.cpus.cores} ({host.cpus.sockets})
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostManufacturerinfo')}</th>
|
|
||||||
<Copiable tagName='td'>
|
|
||||||
{host.bios_strings['system-manufacturer']} ({
|
|
||||||
host.bios_strings['system-product-name']
|
|
||||||
})
|
|
||||||
</Copiable>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostBiosinfo')}</th>
|
|
||||||
<td>
|
|
||||||
{host.bios_strings['bios-vendor']} ({
|
|
||||||
host.bios_strings['bios-version']
|
|
||||||
})
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<br />
|
|
||||||
<h3>{_('licenseHostSettingsLabel')}</h3>
|
|
||||||
<table className='table'>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostLicenseType')}</th>
|
|
||||||
<td>{host.license_params.sku_type}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostLicenseSocket')}</th>
|
|
||||||
<td>{host.license_params.sockets}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{_('hostLicenseExpiry')}</th>
|
|
||||||
<td>
|
|
||||||
<FormattedTime
|
|
||||||
value={host.license_expiry * 1000}
|
|
||||||
day='numeric'
|
|
||||||
month='long'
|
|
||||||
year='numeric'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h3>{_('supplementalPacks')}</h3>
|
|
||||||
<table className='table'>
|
|
||||||
<tbody>
|
|
||||||
{map(host.supplementalPacks, formatPack)}
|
|
||||||
{ALLOW_INSTALL_SUPP_PACK && (
|
|
||||||
<tr>
|
|
||||||
<th>{_('supplementalPackNew')}</th>
|
|
||||||
<td>
|
|
||||||
<SelectFiles
|
|
||||||
type='file'
|
|
||||||
onChange={file => installSupplementalPack(host, file)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
)}
|
||||||
</tbody>
|
{host.enabled ? (
|
||||||
</table>
|
<TabButton
|
||||||
{!ALLOW_INSTALL_SUPP_PACK && [
|
btnStyle='warning'
|
||||||
<h3>{_('supplementalPackNew')}</h3>,
|
handler={disableHost}
|
||||||
<Container>
|
handlerParam={host}
|
||||||
<Upgrade place='supplementalPacks' available={2} />
|
icon='host-disable'
|
||||||
</Container>,
|
labelId='disableHostLabel'
|
||||||
]}
|
/>
|
||||||
</Col>
|
) : (
|
||||||
</Row>
|
<TabButton
|
||||||
</Container>
|
btnStyle='success'
|
||||||
))
|
handler={enableHost}
|
||||||
|
handlerParam={host}
|
||||||
|
icon='host-enable'
|
||||||
|
labelId='enableHostLabel'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TabButton
|
||||||
|
btnStyle='danger'
|
||||||
|
handler={detachHost}
|
||||||
|
handlerParam={host}
|
||||||
|
icon='host-eject'
|
||||||
|
labelId='detachHost'
|
||||||
|
/>
|
||||||
|
{host.power_state !== 'Running' && (
|
||||||
|
<TabButton
|
||||||
|
btnStyle='danger'
|
||||||
|
handler={forgetHost}
|
||||||
|
handlerParam={host}
|
||||||
|
icon='host-forget'
|
||||||
|
labelId='forgetHost'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<h3>{_('xenSettingsLabel')}</h3>
|
||||||
|
<table className='table'>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{_('uuid')}</th>
|
||||||
|
<Copiable tagName='td'>{host.uuid}</Copiable>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostAddress')}</th>
|
||||||
|
<Copiable tagName='td'>{host.address}</Copiable>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostStatus')}</th>
|
||||||
|
<td>
|
||||||
|
{host.enabled
|
||||||
|
? _('hostStatusEnabled')
|
||||||
|
: _('hostStatusDisabled')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostPowerOnMode')}</th>
|
||||||
|
<td>
|
||||||
|
<Toggle
|
||||||
|
disabled
|
||||||
|
onChange={noop}
|
||||||
|
value={Boolean(host.powerOnMode)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostStartedSince')}</th>
|
||||||
|
<td>
|
||||||
|
{_('started', {
|
||||||
|
ago: <FormattedRelative value={host.startTime * 1000} />,
|
||||||
|
})}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostStackStartedSince')}</th>
|
||||||
|
<td>
|
||||||
|
{_('started', {
|
||||||
|
ago: (
|
||||||
|
<FormattedRelative value={host.agentStartTime * 1000} />
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostXenServerVersion')}</th>
|
||||||
|
<Copiable tagName='td' data={host.version}>
|
||||||
|
{host.license_params.sku_marketing_name} {host.version} ({
|
||||||
|
host.license_params.sku_type
|
||||||
|
})
|
||||||
|
</Copiable>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostBuildNumber')}</th>
|
||||||
|
<Copiable tagName='td'>{host.build}</Copiable>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostIscsiName')}</th>
|
||||||
|
<Copiable tagName='td'>{host.iSCSI_name}</Copiable>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<h3>{_('hardwareHostSettingsLabel')}</h3>
|
||||||
|
<table className='table'>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostCpusModel')}</th>
|
||||||
|
<Copiable tagName='td'>{host.CPUs.modelname}</Copiable>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostGpus')}</th>
|
||||||
|
<td>
|
||||||
|
{map(pgpus, pgpu => pcis[pgpu.pci].device_name).join(', ')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostCpusNumber')}</th>
|
||||||
|
<td>
|
||||||
|
{host.cpus.cores} ({host.cpus.sockets})
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostManufacturerinfo')}</th>
|
||||||
|
<Copiable tagName='td'>
|
||||||
|
{host.bios_strings['system-manufacturer']} ({
|
||||||
|
host.bios_strings['system-product-name']
|
||||||
|
})
|
||||||
|
</Copiable>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostBiosinfo')}</th>
|
||||||
|
<td>
|
||||||
|
{host.bios_strings['bios-vendor']} ({
|
||||||
|
host.bios_strings['bios-version']
|
||||||
|
})
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<h3>{_('licenseHostSettingsLabel')}</h3>
|
||||||
|
<table className='table'>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostLicenseType')}</th>
|
||||||
|
<td>{host.license_params.sku_type}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostLicenseSocket')}</th>
|
||||||
|
<td>{host.license_params.sockets}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{_('hostLicenseExpiry')}</th>
|
||||||
|
<td>
|
||||||
|
<FormattedTime
|
||||||
|
value={host.license_expiry * 1000}
|
||||||
|
day='numeric'
|
||||||
|
month='long'
|
||||||
|
year='numeric'
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>{_('supplementalPacks')}</h3>
|
||||||
|
<table className='table'>
|
||||||
|
<tbody>
|
||||||
|
{map(this._getPacks(), formatPack)}
|
||||||
|
{ALLOW_INSTALL_SUPP_PACK && (
|
||||||
|
<tr>
|
||||||
|
<th>{_('supplementalPackNew')}</th>
|
||||||
|
<td>
|
||||||
|
<SelectFiles
|
||||||
|
type='file'
|
||||||
|
onChange={file => installSupplementalPack(host, file)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{!ALLOW_INSTALL_SUPP_PACK && [
|
||||||
|
<h3>{_('supplementalPackNew')}</h3>,
|
||||||
|
<Container>
|
||||||
|
<Upgrade place='supplementalPacks' available={2} />
|
||||||
|
</Container>,
|
||||||
|
]}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,24 +10,13 @@ import Tooltip from 'tooltip'
|
|||||||
import { Container, Col, Row } from 'grid'
|
import { Container, Col, Row } from 'grid'
|
||||||
import { get } from 'xo-defined'
|
import { get } from 'xo-defined'
|
||||||
import { ignoreErrors } from 'promise-toolbox'
|
import { ignoreErrors } from 'promise-toolbox'
|
||||||
import {
|
import { every, filter, find, flatten, forEach, isEmpty, map } from 'lodash'
|
||||||
every,
|
|
||||||
filter,
|
|
||||||
find,
|
|
||||||
flatten,
|
|
||||||
forEach,
|
|
||||||
isEmpty,
|
|
||||||
map,
|
|
||||||
mapValues,
|
|
||||||
some,
|
|
||||||
} from 'lodash'
|
|
||||||
import { createGetObjectsOfType, createSelector, isAdmin } from 'selectors'
|
import { createGetObjectsOfType, createSelector, isAdmin } from 'selectors'
|
||||||
import {
|
import {
|
||||||
addSubscriptions,
|
addSubscriptions,
|
||||||
connectStore,
|
connectStore,
|
||||||
cowSet,
|
cowSet,
|
||||||
formatSize,
|
formatSize,
|
||||||
isXosanPack,
|
|
||||||
ShortDate,
|
ShortDate,
|
||||||
} from 'utils'
|
} from 'utils'
|
||||||
import {
|
import {
|
||||||
@ -37,6 +26,7 @@ import {
|
|||||||
subscribePlugins,
|
subscribePlugins,
|
||||||
subscribeResourceCatalog,
|
subscribeResourceCatalog,
|
||||||
subscribeVolumeInfo,
|
subscribeVolumeInfo,
|
||||||
|
updateXosanPacks,
|
||||||
} from 'xo'
|
} from 'xo'
|
||||||
|
|
||||||
import NewXosan from './new-xosan'
|
import NewXosan from './new-xosan'
|
||||||
@ -208,6 +198,12 @@ const XOSAN_COLUMNS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const XOSAN_INDIVIDUAL_ACTIONS = [
|
const XOSAN_INDIVIDUAL_ACTIONS = [
|
||||||
|
{
|
||||||
|
handler: (xosan, { pools }) => updateXosanPacks(pools[xosan.$pool]),
|
||||||
|
icon: 'host-patch-update',
|
||||||
|
label: _('xosanUpdatePacks'),
|
||||||
|
level: 'primary',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
handler: deleteSr,
|
handler: deleteSr,
|
||||||
icon: 'delete',
|
icon: 'delete',
|
||||||
@ -221,14 +217,6 @@ const XOSAN_INDIVIDUAL_ACTIONS = [
|
|||||||
const getHostsByPool = getHosts.groupBy('$pool')
|
const getHostsByPool = getHosts.groupBy('$pool')
|
||||||
const getPools = createGetObjectsOfType('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 getPbdsBySr = createGetObjectsOfType('PBD').groupBy('SR')
|
||||||
const getXosanSrs = createSelector(
|
const getXosanSrs = createSelector(
|
||||||
createGetObjectsOfType('SR').filter([
|
createGetObjectsOfType('SR').filter([
|
||||||
@ -291,7 +279,6 @@ const XOSAN_INDIVIDUAL_ACTIONS = [
|
|||||||
isAdmin,
|
isAdmin,
|
||||||
isMasterOfflineByPool: getIsMasterOfflineByPool,
|
isMasterOfflineByPool: getIsMasterOfflineByPool,
|
||||||
hostsNeedRestartByPool: getHostsNeedRestartByPool,
|
hostsNeedRestartByPool: getHostsNeedRestartByPool,
|
||||||
noPacksByPool,
|
|
||||||
poolPredicate: getPoolPredicate,
|
poolPredicate: getPoolPredicate,
|
||||||
pools: getPools,
|
pools: getPools,
|
||||||
xoaRegistration: state => state.xoaRegisterState,
|
xoaRegistration: state => state.xoaRegisterState,
|
||||||
@ -419,8 +406,8 @@ export default class Xosan extends Component {
|
|||||||
const {
|
const {
|
||||||
hostsNeedRestartByPool,
|
hostsNeedRestartByPool,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
noPacksByPool,
|
|
||||||
poolPredicate,
|
poolPredicate,
|
||||||
|
pools,
|
||||||
xoaRegistration,
|
xoaRegistration,
|
||||||
xosanSrs,
|
xosanSrs,
|
||||||
} = this.props
|
} = this.props
|
||||||
@ -456,7 +443,6 @@ export default class Xosan extends Component {
|
|||||||
(this._isXosanRegistered() ? (
|
(this._isXosanRegistered() ? (
|
||||||
<NewXosan
|
<NewXosan
|
||||||
hostsNeedRestartByPool={hostsNeedRestartByPool}
|
hostsNeedRestartByPool={hostsNeedRestartByPool}
|
||||||
noPacksByPool={noPacksByPool}
|
|
||||||
poolPredicate={poolPredicate}
|
poolPredicate={poolPredicate}
|
||||||
onSrCreationFinished={this._updateLicenses}
|
onSrCreationFinished={this._updateLicenses}
|
||||||
onSrCreationStarted={this._onSrCreationStarted}
|
onSrCreationStarted={this._onSrCreationStarted}
|
||||||
@ -498,6 +484,7 @@ export default class Xosan extends Component {
|
|||||||
isAdmin,
|
isAdmin,
|
||||||
licensesByXosan: this._getLicensesByXosan(),
|
licensesByXosan: this._getLicensesByXosan(),
|
||||||
licenseError,
|
licenseError,
|
||||||
|
pools,
|
||||||
status: this.state.status,
|
status: this.state.status,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -29,15 +29,18 @@ import {
|
|||||||
} from 'selectors'
|
} from 'selectors'
|
||||||
import {
|
import {
|
||||||
addSubscriptions,
|
addSubscriptions,
|
||||||
|
isLatestXosanPackInstalled,
|
||||||
compareVersions,
|
compareVersions,
|
||||||
connectStore,
|
connectStore,
|
||||||
|
findLatestPack,
|
||||||
formatSize,
|
formatSize,
|
||||||
mapPlus,
|
mapPlus,
|
||||||
} from 'utils'
|
} from 'utils'
|
||||||
import {
|
import {
|
||||||
computeXosanPossibleOptions,
|
computeXosanPossibleOptions,
|
||||||
createXosanSR,
|
createXosanSR,
|
||||||
downloadAndInstallXosanPack,
|
updateXosanPacks,
|
||||||
|
getResourceCatalog,
|
||||||
restartHostsAgents,
|
restartHostsAgents,
|
||||||
subscribeResourceCatalog,
|
subscribeResourceCatalog,
|
||||||
} from 'xo'
|
} from 'xo'
|
||||||
@ -76,14 +79,47 @@ export default class NewXosan extends Component {
|
|||||||
suggestion: 0,
|
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 => {
|
_selectPool = pool => {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedSrs: {},
|
|
||||||
brickSize: DEFAULT_BRICKSIZE,
|
brickSize: DEFAULT_BRICKSIZE,
|
||||||
|
checkPackError: false,
|
||||||
memorySize: DEFAULT_MEMORY,
|
memorySize: DEFAULT_MEMORY,
|
||||||
|
needsUpdate: false,
|
||||||
pif: undefined,
|
pif: undefined,
|
||||||
pool,
|
pool,
|
||||||
|
selectedSrs: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return this._checkPacks(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
@ -243,10 +279,12 @@ export default class NewXosan extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
brickSize,
|
brickSize,
|
||||||
|
checkPackError,
|
||||||
customBrickSize,
|
customBrickSize,
|
||||||
customIpRange,
|
customIpRange,
|
||||||
ipRange,
|
ipRange,
|
||||||
memorySize,
|
memorySize,
|
||||||
|
needsUpdate,
|
||||||
pif,
|
pif,
|
||||||
pool,
|
pool,
|
||||||
selectedSrs,
|
selectedSrs,
|
||||||
@ -256,12 +294,7 @@ export default class NewXosan extends Component {
|
|||||||
vlan,
|
vlan,
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
const {
|
const { hostsNeedRestartByPool, poolPredicate, notRegistered } = this.props
|
||||||
hostsNeedRestartByPool,
|
|
||||||
noPacksByPool,
|
|
||||||
poolPredicate,
|
|
||||||
notRegistered,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (notRegistered) {
|
if (notRegistered) {
|
||||||
return (
|
return (
|
||||||
@ -296,9 +329,7 @@ export default class NewXosan extends Component {
|
|||||||
<Col size={4}>
|
<Col size={4}>
|
||||||
<SelectPif
|
<SelectPif
|
||||||
disabled={
|
disabled={
|
||||||
pool == null ||
|
pool == null || needsUpdate || !isEmpty(hostsNeedRestart)
|
||||||
noPacksByPool[pool.id] ||
|
|
||||||
!isEmpty(hostsNeedRestart)
|
|
||||||
}
|
}
|
||||||
onChange={this.linkState('pif')}
|
onChange={this.linkState('pif')}
|
||||||
predicate={this._getPifPredicate()}
|
predicate={this._getPifPredicate()}
|
||||||
@ -307,261 +338,273 @@ export default class NewXosan extends Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{pool != null &&
|
{pool != null &&
|
||||||
noPacksByPool[pool.id] && (
|
(checkPackError ? (
|
||||||
|
<em>{_('xosanPackUpdateError')}</em>
|
||||||
|
) : needsUpdate ? (
|
||||||
<Row>
|
<Row>
|
||||||
<Icon icon='error' /> {_('xosanNeedPack')}
|
<Col>
|
||||||
<br />
|
<Icon icon='error' /> {_('xosanNeedPack')}
|
||||||
<ActionButton
|
<br />
|
||||||
btnStyle='success'
|
<ActionButton
|
||||||
handler={downloadAndInstallXosanPack}
|
btnStyle='success'
|
||||||
handlerParam={pool}
|
handler={this._updateXosanPacks}
|
||||||
icon='export'
|
handlerParam={pool}
|
||||||
>
|
icon='export'
|
||||||
{_('xosanInstallIt')}
|
>
|
||||||
</ActionButton>
|
{_('xosanInstallIt')}
|
||||||
|
</ActionButton>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
) : !isEmpty(hostsNeedRestart) ? (
|
||||||
{!isEmpty(hostsNeedRestart) && (
|
|
||||||
<Row>
|
|
||||||
<Icon icon='error' /> {_('xosanNeedRestart')}
|
|
||||||
<br />
|
|
||||||
<ActionButton
|
|
||||||
btnStyle='success'
|
|
||||||
handler={restartHostsAgents}
|
|
||||||
handlerParam={hostsNeedRestart}
|
|
||||||
icon='host-restart-agent'
|
|
||||||
>
|
|
||||||
{_('xosanRestartAgents')}
|
|
||||||
</ActionButton>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
{pool != null &&
|
|
||||||
!noPacksByPool[pool.id] &&
|
|
||||||
isEmpty(hostsNeedRestart) && [
|
|
||||||
<Row>
|
<Row>
|
||||||
<em>{_('xosanSelect2Srs')}</em>
|
<Col>
|
||||||
<table className='table table-striped'>
|
<Icon icon='error' /> {_('xosanNeedRestart')}
|
||||||
<thead>
|
<br />
|
||||||
<tr>
|
<ActionButton
|
||||||
<th />
|
btnStyle='success'
|
||||||
<th>{_('xosanName')}</th>
|
handler={restartHostsAgents}
|
||||||
<th>{_('xosanHost')}</th>
|
handlerParam={hostsNeedRestart}
|
||||||
<th>{_('xosanSize')}</th>
|
icon='host-restart-agent'
|
||||||
<th>{_('xosanUsedSpace')}</th>
|
>
|
||||||
</tr>
|
{_('xosanRestartAgents')}
|
||||||
</thead>
|
</ActionButton>
|
||||||
<tbody>
|
</Col>
|
||||||
{map(lvmsrs, sr => {
|
</Row>
|
||||||
const host = find(hosts, ['id', sr.$container])
|
) : (
|
||||||
|
[
|
||||||
return (
|
<Row>
|
||||||
<tr key={sr.id}>
|
<Col>
|
||||||
<td>
|
<em>{_('xosanSelect2Srs')}</em>
|
||||||
<input
|
|
||||||
checked={selectedSrs[sr.id] || false}
|
|
||||||
disabled={disableSrCheckbox(sr)}
|
|
||||||
onChange={event => this._selectSr(event, sr)}
|
|
||||||
type='checkbox'
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Link to={`/srs/${sr.id}/general`}>
|
|
||||||
{sr.name_label}
|
|
||||||
</Link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Link to={`/hosts/${host.id}/general`}>
|
|
||||||
{host.name_label}
|
|
||||||
</Link>
|
|
||||||
</td>
|
|
||||||
<td>{formatSize(sr.size)}</td>
|
|
||||||
<td>
|
|
||||||
{sr.size > 0 && (
|
|
||||||
<Tooltip
|
|
||||||
content={_('spaceLeftTooltip', {
|
|
||||||
used: String(
|
|
||||||
Math.round(sr.physical_usage / sr.size * 100)
|
|
||||||
),
|
|
||||||
free: formatSize(sr.size - sr.physical_usage),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<progress
|
|
||||||
className='progress'
|
|
||||||
max='100'
|
|
||||||
value={sr.physical_usage / sr.size * 100}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Row>,
|
|
||||||
<Row>
|
|
||||||
{!isEmpty(suggestions) && (
|
|
||||||
<div>
|
|
||||||
<h3>{_('xosanSuggestions')}</h3>
|
|
||||||
<table className='table table-striped'>
|
<table className='table table-striped'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th />
|
||||||
<th>{_('xosanLayout')}</th>
|
<th>{_('xosanName')}</th>
|
||||||
<th>{_('xosanRedundancy')}</th>
|
<th>{_('xosanHost')}</th>
|
||||||
<th>{_('xosanCapacity')}</th>
|
<th>{_('xosanSize')}</th>
|
||||||
<th>{_('xosanAvailableSpace')}</th>
|
<th>{_('xosanUsedSpace')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{map(
|
{map(lvmsrs, sr => {
|
||||||
suggestions,
|
const host = find(hosts, ['id', sr.$container])
|
||||||
(
|
|
||||||
{ layout, redundancy, capacity, availableSpace },
|
return (
|
||||||
index
|
<tr key={sr.id}>
|
||||||
) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
checked={+suggestion === index}
|
checked={selectedSrs[sr.id] || false}
|
||||||
name={`suggestion_${pool.id}`}
|
disabled={disableSrCheckbox(sr)}
|
||||||
onChange={this.linkState('suggestion')}
|
onChange={event => this._selectSr(event, sr)}
|
||||||
type='radio'
|
type='checkbox'
|
||||||
value={index}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{layout}</td>
|
|
||||||
<td>{redundancy}</td>
|
|
||||||
<td>{capacity}</td>
|
|
||||||
<td>
|
<td>
|
||||||
{availableSpace === 0 ? (
|
<Link to={`/srs/${sr.id}/general`}>
|
||||||
<strong className='text-danger'>0</strong>
|
{sr.name_label}
|
||||||
) : (
|
</Link>
|
||||||
formatSize(availableSpace)
|
</td>
|
||||||
|
<td>
|
||||||
|
<Link to={`/hosts/${host.id}/general`}>
|
||||||
|
{host.name_label}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td>{formatSize(sr.size)}</td>
|
||||||
|
<td>
|
||||||
|
{sr.size > 0 && (
|
||||||
|
<Tooltip
|
||||||
|
content={_('spaceLeftTooltip', {
|
||||||
|
used: String(
|
||||||
|
Math.round(
|
||||||
|
sr.physical_usage / sr.size * 100
|
||||||
|
)
|
||||||
|
),
|
||||||
|
free: formatSize(
|
||||||
|
sr.size - sr.physical_usage
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<progress
|
||||||
|
className='progress'
|
||||||
|
max='100'
|
||||||
|
value={sr.physical_usage / sr.size * 100}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
)}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{architecture.layout === 'disperse' && (
|
</Col>
|
||||||
<div className='alert alert-danger'>
|
</Row>,
|
||||||
{_('xosanDisperseWarning', {
|
<Row>
|
||||||
link: (
|
<Col>
|
||||||
<a href='https://xen-orchestra.com/docs/xosan_types.html'>
|
{!isEmpty(suggestions) && (
|
||||||
xen-orchestra.com/docs/xosan_types.html
|
<div>
|
||||||
</a>
|
<h3>{_('xosanSuggestions')}</h3>
|
||||||
),
|
<table className='table table-striped'>
|
||||||
})}
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th />
|
||||||
|
<th>{_('xosanLayout')}</th>
|
||||||
|
<th>{_('xosanRedundancy')}</th>
|
||||||
|
<th>{_('xosanCapacity')}</th>
|
||||||
|
<th>{_('xosanAvailableSpace')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{map(
|
||||||
|
suggestions,
|
||||||
|
(
|
||||||
|
{ layout, redundancy, capacity, availableSpace },
|
||||||
|
index
|
||||||
|
) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
checked={+suggestion === index}
|
||||||
|
name={`suggestion_${pool.id}`}
|
||||||
|
onChange={this.linkState('suggestion')}
|
||||||
|
type='radio'
|
||||||
|
value={index}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{layout}</td>
|
||||||
|
<td>{redundancy}</td>
|
||||||
|
<td>{capacity}</td>
|
||||||
|
<td>
|
||||||
|
{availableSpace === 0 ? (
|
||||||
|
<strong className='text-danger'>0</strong>
|
||||||
|
) : (
|
||||||
|
formatSize(availableSpace)
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{architecture.layout === 'disperse' && (
|
||||||
|
<div className='alert alert-danger'>
|
||||||
|
{_('xosanDisperseWarning', {
|
||||||
|
link: (
|
||||||
|
<a href='https://xen-orchestra.com/docs/xosan_types.html'>
|
||||||
|
xen-orchestra.com/docs/xosan_types.html
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Graph
|
||||||
|
height={160}
|
||||||
|
layout={architecture.layout}
|
||||||
|
nSrs={this._getNSelectedSrs()}
|
||||||
|
redundancy={architecture.redundancy}
|
||||||
|
width={600}
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
<Toggle
|
||||||
|
onChange={this.toggleState('showAdvanced')}
|
||||||
|
value={this.state.showAdvanced}
|
||||||
|
/>{' '}
|
||||||
|
{_('xosanAdvanced')}{' '}
|
||||||
|
{this.state.showAdvanced && (
|
||||||
|
<Container className='mb-1'>
|
||||||
|
<SingleLineRow>
|
||||||
|
<Col>{_('xosanVlan')}</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
<SingleLineRow>
|
||||||
|
<Col size={1}>
|
||||||
|
<Toggle
|
||||||
|
onChange={this.linkState('useVlan')}
|
||||||
|
value={useVlan}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col size={3}>
|
||||||
|
<input
|
||||||
|
className='form-control'
|
||||||
|
disabled={!useVlan}
|
||||||
|
onChange={this.linkState('vlan')}
|
||||||
|
placeholder='VLAN'
|
||||||
|
type='text'
|
||||||
|
value={vlan}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
<SingleLineRow>
|
||||||
|
<Col>{_('xosanCustomIpNetwork')}</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
<SingleLineRow>
|
||||||
|
<Col size={1}>
|
||||||
|
<Toggle
|
||||||
|
onChange={this.linkState('customIpRange')}
|
||||||
|
value={customIpRange}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col size={3}>
|
||||||
|
<input
|
||||||
|
className='form-control'
|
||||||
|
disabled={!customIpRange}
|
||||||
|
onChange={this.linkState('ipRange')}
|
||||||
|
placeholder='ipRange'
|
||||||
|
type='text'
|
||||||
|
value={ipRange}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
<SingleLineRow>
|
||||||
|
<Col>{_('xosanBrickSize')}</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
<SingleLineRow>
|
||||||
|
<Col size={1}>
|
||||||
|
<Toggle
|
||||||
|
className='mr-1'
|
||||||
|
onChange={this._onCustomBrickSizeChange}
|
||||||
|
value={customBrickSize}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col size={3}>
|
||||||
|
<SizeInput
|
||||||
|
readOnly={!customBrickSize}
|
||||||
|
value={brickSize}
|
||||||
|
onChange={this._onBrickSizeChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
<SingleLineRow>
|
||||||
|
<Col size={4}>
|
||||||
|
<label>{_('xosanMemorySize')}</label>
|
||||||
|
<SizeInput
|
||||||
|
value={memorySize}
|
||||||
|
onChange={this.linkState('memorySize')}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</SingleLineRow>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Graph
|
</Col>
|
||||||
height={160}
|
</Row>,
|
||||||
layout={architecture.layout}
|
<Row>
|
||||||
nSrs={this._getNSelectedSrs()}
|
<Col>
|
||||||
redundancy={architecture.redundancy}
|
<ActionButton
|
||||||
width={600}
|
btnStyle='success'
|
||||||
/>
|
disabled={this._getDisableCreation()}
|
||||||
<hr />
|
handler={this._createXosanVm}
|
||||||
<Toggle
|
icon='add'
|
||||||
onChange={this.toggleState('showAdvanced')}
|
>
|
||||||
value={this.state.showAdvanced}
|
{_('xosanCreate')}
|
||||||
/>{' '}
|
</ActionButton>
|
||||||
{_('xosanAdvanced')}{' '}
|
</Col>
|
||||||
{this.state.showAdvanced && (
|
</Row>,
|
||||||
<Container className='mb-1'>
|
]
|
||||||
<SingleLineRow>
|
))}
|
||||||
<Col>{_('xosanVlan')}</Col>
|
|
||||||
</SingleLineRow>
|
|
||||||
<SingleLineRow>
|
|
||||||
<Col size={1}>
|
|
||||||
<Toggle
|
|
||||||
onChange={this.linkState('useVlan')}
|
|
||||||
value={useVlan}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col size={3}>
|
|
||||||
<input
|
|
||||||
className='form-control'
|
|
||||||
disabled={!useVlan}
|
|
||||||
onChange={this.linkState('vlan')}
|
|
||||||
placeholder='VLAN'
|
|
||||||
type='text'
|
|
||||||
value={vlan}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</SingleLineRow>
|
|
||||||
<SingleLineRow>
|
|
||||||
<Col>{_('xosanCustomIpNetwork')}</Col>
|
|
||||||
</SingleLineRow>
|
|
||||||
<SingleLineRow>
|
|
||||||
<Col size={1}>
|
|
||||||
<Toggle
|
|
||||||
onChange={this.linkState('customIpRange')}
|
|
||||||
value={customIpRange}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col size={3}>
|
|
||||||
<input
|
|
||||||
className='form-control'
|
|
||||||
disabled={!customIpRange}
|
|
||||||
onChange={this.linkState('ipRange')}
|
|
||||||
placeholder='ipRange'
|
|
||||||
type='text'
|
|
||||||
value={ipRange}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</SingleLineRow>
|
|
||||||
<SingleLineRow>
|
|
||||||
<Col>{_('xosanBrickSize')}</Col>
|
|
||||||
</SingleLineRow>
|
|
||||||
<SingleLineRow>
|
|
||||||
<Col size={1}>
|
|
||||||
<Toggle
|
|
||||||
className='mr-1'
|
|
||||||
onChange={this._onCustomBrickSizeChange}
|
|
||||||
value={customBrickSize}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col size={3}>
|
|
||||||
<SizeInput
|
|
||||||
readOnly={!customBrickSize}
|
|
||||||
value={brickSize}
|
|
||||||
onChange={this._onBrickSizeChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</SingleLineRow>
|
|
||||||
<SingleLineRow>
|
|
||||||
<Col size={4}>
|
|
||||||
<label>{_('xosanMemorySize')}</label>
|
|
||||||
<SizeInput
|
|
||||||
value={memorySize}
|
|
||||||
onChange={this.linkState('memorySize')}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</SingleLineRow>
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Row>,
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<ActionButton
|
|
||||||
btnStyle='success'
|
|
||||||
disabled={this._getDisableCreation()}
|
|
||||||
handler={this._createXosanVm}
|
|
||||||
icon='add'
|
|
||||||
>
|
|
||||||
{_('xosanCreate')}
|
|
||||||
</ActionButton>
|
|
||||||
</Col>
|
|
||||||
</Row>,
|
|
||||||
]}
|
|
||||||
<hr />
|
<hr />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user