parent
539d136936
commit
0d47332526
|
@ -310,7 +310,8 @@ export default {
|
|||
alarmPool: 'Pool',
|
||||
alarmRemoveAll: 'Supprimer toutes les alarmes',
|
||||
// ----- New VM -----
|
||||
newVmCreateNewVmOn: 'Créer une nouvelle VM sur {pool}',
|
||||
newVmCreateNewVmOn: 'Créer une nouvelle VM sur {select}',
|
||||
newVmCreateNewVmOn2: 'Créer une nouvelle VM sur {select1} ou {select2}',
|
||||
newVmInfoPanel: 'Informations',
|
||||
newVmNameLabel: 'Nom',
|
||||
newVmTemplateLabel: 'Modèle',
|
||||
|
|
|
@ -126,6 +126,11 @@ var messages = {
|
|||
selectPifs: 'Select PIF(s)…',
|
||||
selectPools: 'Select Pool(s)…',
|
||||
selectRemotes: 'Select Remote(s)…',
|
||||
selectResourceSets: 'Select resource set(s)…',
|
||||
selectResourceSetsVmTemplate: 'Select template(s)…',
|
||||
selectResourceSetsSr: 'Select SR(s)…',
|
||||
selectResourceSetsNetwork: 'Select network(s)…',
|
||||
selectResourceSetsVdi: 'Select disk(s)…',
|
||||
selectSrs: 'Select SR(s)…',
|
||||
selectVms: 'Select VM(s)…',
|
||||
selectVmTemplates: 'Select VM template(s)…',
|
||||
|
@ -583,7 +588,9 @@ var messages = {
|
|||
alarmRemoveAll: 'Remove all alarms',
|
||||
|
||||
// ----- New VM -----
|
||||
newVmCreateNewVmOn: 'Create a new VM on {pool}',
|
||||
newVmCreateNewVmOn: 'Create a new VM on {select}',
|
||||
newVmCreateNewVmOn2: 'Create a new VM on {select1} or {select2}',
|
||||
newVmCreateNewVmNoPermission: 'You have no permission to create a VM',
|
||||
newVmInfoPanel: 'Infos',
|
||||
newVmNameLabel: 'Name',
|
||||
newVmTemplateLabel: 'Template',
|
||||
|
@ -622,6 +629,7 @@ var messages = {
|
|||
newVmCreateVms: 'Create VMs',
|
||||
newVmCreateVmsConfirm: 'Are you sure you want to create {nbVms} VMs?',
|
||||
newVmMultipleVms: 'Multiple VMs:',
|
||||
newVmSelectResourceSet: 'Select a resource set:',
|
||||
|
||||
// ----- Self -----
|
||||
resourceSets: 'Resource sets',
|
||||
|
|
|
@ -108,6 +108,11 @@ const xoItemToRender = {
|
|||
<Icon icon='user' /> {user.email}
|
||||
</span>
|
||||
),
|
||||
resourceSet: resourceSet => (
|
||||
<span>
|
||||
<Icon icon='resource-set' /> {resourceSet.name}
|
||||
</span>
|
||||
),
|
||||
|
||||
// XO objects.
|
||||
pool: pool => (
|
||||
|
|
|
@ -2,12 +2,14 @@ import React from 'react'
|
|||
import assign from 'lodash/assign'
|
||||
import classNames from 'classnames'
|
||||
import filter from 'lodash/filter'
|
||||
import flatten from 'lodash/flatten'
|
||||
import forEach from 'lodash/forEach'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import keyBy from 'lodash/keyBy'
|
||||
import keys from 'lodash/keys'
|
||||
import map from 'lodash/map'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
import store from 'store'
|
||||
import { parse as parseRemote } from 'xo-remote-parser'
|
||||
|
||||
import _ from './intl'
|
||||
|
@ -19,16 +21,19 @@ import {
|
|||
createFilter,
|
||||
createGetObjectsOfType,
|
||||
createGetTags,
|
||||
createSelector
|
||||
createSelector,
|
||||
getObject
|
||||
} from './selectors'
|
||||
import {
|
||||
connectStore,
|
||||
mapPlus
|
||||
mapPlus,
|
||||
resolveResourceSets
|
||||
} from './utils'
|
||||
import {
|
||||
isSrWritable,
|
||||
subscribeGroups,
|
||||
subscribeRemotes,
|
||||
subscribeResourceSets,
|
||||
subscribeRoles,
|
||||
subscribeUsers
|
||||
} from './xo'
|
||||
|
@ -580,3 +585,184 @@ export const SelectRemote = makeSubscriptionSelect(subscriber => {
|
|||
|
||||
return unsubscribeRemotes
|
||||
}, { placeholder: _('selectRemotes') })
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const SelectResourceSet = makeSubscriptionSelect(subscriber => {
|
||||
const unsubscribeResourceSets = subscribeResourceSets(resourceSets => {
|
||||
const xoObjects = map(sortBy(resolveResourceSets(resourceSets), 'name'), resourceSet => ({...resourceSet, type: 'resourceSet'}))
|
||||
|
||||
subscriber({xoObjects})
|
||||
})
|
||||
|
||||
return unsubscribeResourceSets
|
||||
}, { placeholder: _('selectResourceSets') })
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export class SelectResourceSetsVmTemplate extends Component {
|
||||
get value () {
|
||||
return this.refs.select.value
|
||||
}
|
||||
|
||||
set value (value) {
|
||||
this.refs.select.value = value
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
|
||||
this.setState({
|
||||
resourceSets: resolveResourceSets(resourceSets)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_getTemplates = createSelector(
|
||||
() => this.props.resourceSet,
|
||||
({ objectsByType }) => {
|
||||
const { predicate } = this.props
|
||||
const templates = objectsByType['VM-template']
|
||||
return sortBy(predicate ? filter(templates, predicate) : templates, 'name_label')
|
||||
}
|
||||
)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<GenericSelect
|
||||
ref='select'
|
||||
placeholder={_('selectResourceSetsVmTemplate')}
|
||||
{...this.props}
|
||||
xoObjects={this._getTemplates()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export class SelectResourceSetsSr extends Component {
|
||||
get value () {
|
||||
return this.refs.select.value
|
||||
}
|
||||
|
||||
set value (value) {
|
||||
this.refs.select.value = value
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
|
||||
this.setState({
|
||||
resourceSets: resolveResourceSets(resourceSets)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_getSrs = createSelector(
|
||||
() => this.props.resourceSet,
|
||||
({ objectsByType }) => {
|
||||
const { predicate } = this.props
|
||||
const srs = objectsByType['SR']
|
||||
return sortBy(predicate ? filter(srs, predicate) : srs, 'name_label')
|
||||
}
|
||||
)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<GenericSelect
|
||||
ref='select'
|
||||
placeholder={_('selectResourceSetsSr')}
|
||||
{...this.props}
|
||||
xoObjects={this._getSrs()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export class SelectResourceSetsVdi extends Component {
|
||||
get value () {
|
||||
return this.refs.select.value
|
||||
}
|
||||
|
||||
set value (value) {
|
||||
this.refs.select.value = value
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
|
||||
this.setState({
|
||||
resourceSets: resolveResourceSets(resourceSets)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_getObject (id) {
|
||||
return getObject(store.getState(), id, true)
|
||||
}
|
||||
|
||||
_getSrs = createSelector(
|
||||
() => this.props.resourceSet,
|
||||
({ objectsByType }) => {
|
||||
const { srPredicate } = this.props
|
||||
const srs = objectsByType['SR']
|
||||
return srPredicate ? filter(srs, srPredicate) : srs
|
||||
}
|
||||
)
|
||||
|
||||
_getVdis = createSelector(
|
||||
this._getSrs,
|
||||
srs => sortBy(map(flatten(map(srs, sr => sr.VDIs)), this._getObject), 'name_label')
|
||||
)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<GenericSelect
|
||||
ref='select'
|
||||
placeholder={_('selectResourceSetsVdi')}
|
||||
{...this.props}
|
||||
xoObjects={this._getVdis()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export class SelectResourceSetsNetwork extends Component {
|
||||
get value () {
|
||||
return this.refs.select.value
|
||||
}
|
||||
|
||||
set value (value) {
|
||||
this.refs.select.value = value
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
|
||||
this.setState({
|
||||
resourceSets: resolveResourceSets(resourceSets)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_getNetworks = createSelector(
|
||||
() => this.props.resourceSet,
|
||||
({ objectsByType }) => {
|
||||
const { predicate } = this.props
|
||||
const networks = objectsByType['network']
|
||||
return sortBy(predicate ? filter(networks, predicate) : networks, 'name_label')
|
||||
}
|
||||
)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<GenericSelect
|
||||
ref='select'
|
||||
placeholder={_('selectResourceSetsNetwork')}
|
||||
{...this.props}
|
||||
xoObjects={this._getNetworks()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,13 +245,18 @@ const _getPermissionsPredicate = invoke(() => {
|
|||
|
||||
// Creates an object selector from an id selector.
|
||||
export const createGetObject = (idSelector = _getId) =>
|
||||
(state, props) => {
|
||||
(state, props, useResourceSet) => {
|
||||
const object = state.objects.all[idSelector(state, props)]
|
||||
if (!object) {
|
||||
return
|
||||
}
|
||||
|
||||
if (useResourceSet) {
|
||||
return object
|
||||
}
|
||||
|
||||
const predicate = _getPermissionsPredicate(state)
|
||||
|
||||
if (!predicate) {
|
||||
if (predicate == null) {
|
||||
return object // no filtering
|
||||
|
|
|
@ -14,7 +14,9 @@ import map from 'lodash/map'
|
|||
import mapValues from 'lodash/mapValues'
|
||||
import React from 'react'
|
||||
import replace from 'lodash/replace'
|
||||
import store from 'store'
|
||||
import { connect } from 'react-redux'
|
||||
import { getObject } from 'selectors'
|
||||
|
||||
import BaseComponent from './base-component'
|
||||
import invoke from './invoke'
|
||||
|
@ -355,6 +357,41 @@ export function rethrow (cb) {
|
|||
)
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const resolveResourceSets = resourceSets => (
|
||||
map(resourceSets, resourceSet => {
|
||||
const { objects, ...attrs } = resourceSet
|
||||
const resolvedObjects = {}
|
||||
const resolvedSet = {
|
||||
...attrs,
|
||||
missingObjects: [],
|
||||
objectsByType: resolvedObjects
|
||||
}
|
||||
const state = store.getState()
|
||||
|
||||
forEach(objects, id => {
|
||||
const object = getObject(state, id, true) // true: useResourceSet to bypass permissions
|
||||
|
||||
// Error, missing resource.
|
||||
if (!object) {
|
||||
resolvedSet.missingObjects.push(id)
|
||||
return
|
||||
}
|
||||
|
||||
const { type } = object
|
||||
|
||||
if (!resolvedObjects[type]) {
|
||||
resolvedObjects[type] = [ object ]
|
||||
} else {
|
||||
resolvedObjects[type].push(object)
|
||||
}
|
||||
})
|
||||
|
||||
return resolvedSet
|
||||
})
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Creates a string replacer based on a pattern and a list of rules
|
||||
|
|
|
@ -2,15 +2,22 @@ import _ from 'intl'
|
|||
import Component from 'base-component'
|
||||
import classNames from 'classnames'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Link from 'link'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
import { connectStore, noop, getXoaPlan } from 'utils'
|
||||
import { createGetObjectsOfType, getLang, getUser } from 'selectors'
|
||||
import { signOut } from 'xo'
|
||||
import { signOut, subscribePermissions, subscribeResourceSets } from 'xo'
|
||||
import { UpdateTag } from '../xoa-updates'
|
||||
import {
|
||||
createFilter,
|
||||
createGetObjectsOfType,
|
||||
createSelector,
|
||||
getLang,
|
||||
getUser
|
||||
} from 'selectors'
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
|
@ -20,10 +27,10 @@ import styles from './index.css'
|
|||
// There are currently issues between context updates (used by
|
||||
// react-intl) and pure components.
|
||||
lang: getLang,
|
||||
|
||||
nTasks: createGetObjectsOfType('task').count(
|
||||
[ task => task.status === 'pending' ]
|
||||
),
|
||||
pools: createGetObjectsOfType('pool'),
|
||||
user: getUser
|
||||
}), {
|
||||
withRef: true
|
||||
|
@ -40,12 +47,37 @@ export default class Menu extends Component {
|
|||
window.removeEventListener('resize', updateCollapsed)
|
||||
this._removeListener = noop
|
||||
}
|
||||
|
||||
this._unsubscribeResourceSets = subscribeResourceSets(resourceSets => {
|
||||
this.setState({
|
||||
resourceSets
|
||||
})
|
||||
})
|
||||
this._unsubscribePermissions = subscribePermissions(permissions => {
|
||||
this.setState({
|
||||
permissions
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this._removeListener()
|
||||
this._unsubscribeResourceSets()
|
||||
this._unsubscribePermissions()
|
||||
}
|
||||
|
||||
_getNoOperatablePools = createSelector(
|
||||
createFilter(
|
||||
() => this.props.pools,
|
||||
() => this.permissions,
|
||||
[ ({ id }, permissions) => {
|
||||
const { user } = this.props
|
||||
return user && user.permission === 'admin' || permissions && permissions[id] && permissions[id].operate
|
||||
} ]
|
||||
),
|
||||
isEmpty
|
||||
)
|
||||
|
||||
get height () {
|
||||
return this.refs.content.offsetHeight
|
||||
}
|
||||
|
@ -58,6 +90,8 @@ export default class Menu extends Component {
|
|||
render () {
|
||||
const { nTasks, user } = this.props
|
||||
const isAdmin = user && user.permission === 'admin'
|
||||
const noOperatablePools = this._getNoOperatablePools()
|
||||
const noResourceSets = isEmpty(this.state.resourceSets)
|
||||
|
||||
const items = [
|
||||
{ to: '/home', icon: 'menu-home', label: 'homePage' },
|
||||
|
@ -92,11 +126,11 @@ export default class Menu extends Component {
|
|||
]},
|
||||
{ to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
||||
{ to: '/tasks', icon: 'task', label: 'taskMenu', pill: nTasks },
|
||||
{ to: '/vms/new', icon: 'menu-new', label: 'newMenu', subMenu: [
|
||||
!(noOperatablePools && noResourceSets) && { to: '/vms/new', icon: 'menu-new', label: 'newMenu', subMenu: [
|
||||
{ to: '/vms/new', icon: 'menu-new-vm', label: 'newVmPage' },
|
||||
isAdmin && { to: '/new/sr', icon: 'menu-new-sr', label: 'newSrPage' },
|
||||
isAdmin && { to: '/settings/servers', icon: 'menu-settings-servers', label: 'newServerPage' },
|
||||
{ to: '/vms/import', icon: 'menu-new-import', label: 'newImport' }
|
||||
!noOperatablePools && { to: '/vms/import', icon: 'menu-new-import', label: 'newImport' }
|
||||
]}
|
||||
]
|
||||
|
||||
|
|
|
@ -10,26 +10,37 @@ import find from 'lodash/find'
|
|||
import forEach from 'lodash/forEach'
|
||||
import getEventValue from 'get-event-value'
|
||||
import Icon from 'icon'
|
||||
import includes from 'lodash/includes'
|
||||
import isArray from 'lodash/isArray'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import isObject from 'lodash/isObject'
|
||||
import map from 'lodash/map'
|
||||
import Page from '../page'
|
||||
import React from 'react'
|
||||
import size from 'lodash/size'
|
||||
import slice from 'lodash/slice'
|
||||
import store from 'store'
|
||||
import toArray from 'lodash/toArray'
|
||||
import Wizard, { Section } from 'wizard'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import {
|
||||
createVm,
|
||||
createVms,
|
||||
getCloudInitConfig
|
||||
getCloudInitConfig,
|
||||
subscribePermissions,
|
||||
subscribeResourceSets
|
||||
} from 'xo'
|
||||
import {
|
||||
SelectVdi,
|
||||
SelectNetwork,
|
||||
SelectPool,
|
||||
SelectResourceSet,
|
||||
SelectResourceSetsNetwork,
|
||||
SelectResourceSetsSr,
|
||||
SelectResourceSetsVdi,
|
||||
SelectResourceSetsVmTemplate,
|
||||
SelectSr,
|
||||
SelectVdi,
|
||||
SelectVmTemplate
|
||||
} from 'select-objects'
|
||||
import {
|
||||
|
@ -39,12 +50,15 @@ import {
|
|||
import {
|
||||
connectStore,
|
||||
formatSize,
|
||||
noop
|
||||
noop,
|
||||
resolveResourceSets
|
||||
} from 'utils'
|
||||
import {
|
||||
createFilter,
|
||||
createSelector,
|
||||
createGetObject,
|
||||
createGetObjectsOfType
|
||||
createGetObjectsOfType,
|
||||
getUser
|
||||
} from 'selectors'
|
||||
|
||||
import styles from './index.css'
|
||||
|
@ -54,6 +68,9 @@ const NB_VMS_MIN = 2
|
|||
const NB_VMS_MAX = 100
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
// Sub-components --------------------------------------------------------------
|
||||
|
||||
const SectionContent = ({ summary, column, children }) => (
|
||||
<div className={classNames(
|
||||
'form-inline',
|
||||
|
@ -63,13 +80,11 @@ const SectionContent = ({ summary, column, children }) => (
|
|||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const LineItem = ({ children }) => (
|
||||
<div className={styles.lineItem}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const Item = ({ label, children }) => (
|
||||
<span className={styles.item}>
|
||||
{label && <span>{_(label)} </span>}
|
||||
|
@ -77,10 +92,17 @@ const Item = ({ label, children }) => (
|
|||
</span>
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const getObject = createGetObject((_, id) => id)
|
||||
|
||||
@connectStore(() => ({
|
||||
isAdmin: createSelector(
|
||||
getUser,
|
||||
user => user && user.permission === 'admin'
|
||||
),
|
||||
networks: createGetObjectsOfType('network').sort(),
|
||||
pools: createGetObjectsOfType('pool'),
|
||||
templates: createGetObjectsOfType('VM-template').sort()
|
||||
}))
|
||||
@injectIntl
|
||||
|
@ -89,8 +111,8 @@ export default class NewVm extends BaseComponent {
|
|||
super()
|
||||
|
||||
this._uniqueId = 0
|
||||
// NewVm's state is stored in this.state.state instead of this.state
|
||||
// so it can be emptied easily with this.setState(state: {})
|
||||
// NewVm's form's state is stored in this.state.state instead of this.state
|
||||
// so it can be emptied easily with this.setState({ state: {} })
|
||||
this.state = { state: {} }
|
||||
}
|
||||
|
||||
|
@ -98,49 +120,34 @@ export default class NewVm extends BaseComponent {
|
|||
this._reset()
|
||||
}
|
||||
|
||||
getPoolNetworks = createSelector(
|
||||
() => this.props.networks,
|
||||
() => {
|
||||
const { pool } = this.state.state
|
||||
return pool && pool.id
|
||||
},
|
||||
(networks, poolId) => filter(networks, network => network.$pool === poolId)
|
||||
)
|
||||
componentWillMount () {
|
||||
this._unsubscribeResourceSets = subscribeResourceSets(resourceSets => {
|
||||
this.setState({
|
||||
resourceSets: resolveResourceSets(resourceSets)
|
||||
})
|
||||
})
|
||||
this._unsubscribePermissions = subscribePermissions(permissions => {
|
||||
this.setState({
|
||||
permissions
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this._unsubscribeResourceSets()
|
||||
this._unsubscribePermissions()
|
||||
}
|
||||
|
||||
// Utils -----------------------------------------------------------------------
|
||||
|
||||
getUniqueId () {
|
||||
return this._uniqueId++
|
||||
}
|
||||
|
||||
get _isDiskTemplate () {
|
||||
const { template } = this.state.state
|
||||
return template &&
|
||||
template.template_info.disks.length === 0 && template.name_label !== 'Other install media'
|
||||
}
|
||||
|
||||
_updateNbVms = () => {
|
||||
const { nbVms, name_label, nameLabels } = this.state.state
|
||||
const nbVmsClamped = clamp(nbVms, NB_VMS_MIN, NB_VMS_MAX)
|
||||
const newNameLabels = [ ...nameLabels ]
|
||||
if (nbVmsClamped < nameLabels.length) {
|
||||
this._setState({ nameLabels: slice(newNameLabels, 0, nbVmsClamped) })
|
||||
} else {
|
||||
for (let i = nameLabels.length + 1; i <= nbVmsClamped; i++) {
|
||||
newNameLabels.push(`${name_label || 'VM'}_${i}`)
|
||||
}
|
||||
this._setState({ nameLabels: newNameLabels })
|
||||
}
|
||||
}
|
||||
|
||||
_updateNameLabels = () => {
|
||||
const { name_label, nameLabels } = this.state.state
|
||||
const nbVms = nameLabels.length
|
||||
const newNameLabels = []
|
||||
for (let i = 1; i <= nbVms; i++) {
|
||||
newNameLabels.push(`${name_label || 'VM'}_${i}`)
|
||||
}
|
||||
this._setState({ nameLabels: newNameLabels })
|
||||
}
|
||||
|
||||
_setState = (newValues, callback) => {
|
||||
this.setState({ state: {
|
||||
...this.state.state,
|
||||
|
@ -150,23 +157,30 @@ export default class NewVm extends BaseComponent {
|
|||
_replaceState = (state, callback) =>
|
||||
this.setState({ state }, callback)
|
||||
|
||||
_reset = pool =>
|
||||
// Actions ---------------------------------------------------------------------
|
||||
|
||||
_reset = ({ pool, resourceSet } = { pool: this.state.pool, resourceSet: this.state.resourceSet }) => {
|
||||
this.setState({ pool, resourceSet })
|
||||
this._replaceState({
|
||||
bootAfterCreate: true,
|
||||
configDrive: false,
|
||||
CPUs: '',
|
||||
cpuWeight: 1,
|
||||
existingDisks: {},
|
||||
fastClone: true,
|
||||
multipleVms: false,
|
||||
name_label: '',
|
||||
name_description: '',
|
||||
nameLabels: map(Array(NB_VMS_MIN), (_, index) => `VM_${index + 1}`),
|
||||
|
||||
nbVms: NB_VMS_MIN,
|
||||
pool: pool || this.state.state.pool,
|
||||
VDIs: [],
|
||||
VIFs: []
|
||||
})
|
||||
}
|
||||
|
||||
_create = () => {
|
||||
const { state } = this.state
|
||||
const { resourceSet, state } = this.state
|
||||
let installation
|
||||
switch (state.installMethod) {
|
||||
case 'ISO':
|
||||
|
@ -205,13 +219,14 @@ export default class NewVm extends BaseComponent {
|
|||
}
|
||||
|
||||
const data = {
|
||||
clone: this._isDiskTemplate && state.fastClone,
|
||||
clone: !this.isDiskTemplate && state.fastClone,
|
||||
existingDisks: state.existingDisks,
|
||||
installation,
|
||||
name_label: state.name_label,
|
||||
template: state.template.id,
|
||||
VDIs: state.VDIs,
|
||||
VIFs: state.VIFs,
|
||||
resourceSet: resourceSet && resourceSet.id,
|
||||
// TODO: To be added in xo-server
|
||||
// vm.set parameters
|
||||
CPUs: state.CPUs,
|
||||
|
@ -227,28 +242,6 @@ export default class NewVm extends BaseComponent {
|
|||
|
||||
return state.multipleVms ? createVms(data, state.nameLabels) : createVm(data)
|
||||
}
|
||||
|
||||
_selectPool = pool =>
|
||||
this._reset(pool)
|
||||
|
||||
_getIsInPool = createSelector(
|
||||
() => this.state.state.pool.id,
|
||||
poolId => object => object.$pool === poolId
|
||||
)
|
||||
|
||||
_getSrPredicate = createSelector(
|
||||
() => this.state.state.pool.id,
|
||||
poolId => disk => disk.$pool === poolId && disk.content_type !== 'iso' && disk.size > 0
|
||||
)
|
||||
|
||||
_getDefaultNetworkId = () => {
|
||||
const network = find(this.getPoolNetworks(), network => {
|
||||
const pif = getObject(store.getState(), network.PIFs[0])
|
||||
return pif && pif.management
|
||||
})
|
||||
return network && network.id
|
||||
}
|
||||
|
||||
_initTemplate = template => {
|
||||
if (!template) {
|
||||
return this._reset()
|
||||
|
@ -257,6 +250,8 @@ export default class NewVm extends BaseComponent {
|
|||
this._setState({ template })
|
||||
|
||||
const storeState = store.getState()
|
||||
const _isInResourceSet = this._getIsInResourceSet()
|
||||
const { pool, resourceSet, state } = this.state
|
||||
|
||||
const existingDisks = {}
|
||||
forEach(template.$VBDs, vbdId => {
|
||||
|
@ -270,7 +265,9 @@ export default class NewVm extends BaseComponent {
|
|||
name_label: vdi.name_label,
|
||||
name_description: vdi.name_description,
|
||||
size: vdi.size,
|
||||
$SR: vdi.$SR
|
||||
$SR: pool || _isInResourceSet(vdi.$SR, 'SR')
|
||||
? vdi.$SR
|
||||
: resourceSet.objectsByType['SR'][0].id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -280,7 +277,9 @@ export default class NewVm extends BaseComponent {
|
|||
const vif = getObject(storeState, vifId)
|
||||
VIFs.push({
|
||||
id: this.getUniqueId(),
|
||||
network: vif.$network
|
||||
network: pool || _isInResourceSet(vif.$network, 'network')
|
||||
? vif.$network
|
||||
: resourceSet.objectsByType['network'][0].id
|
||||
})
|
||||
})
|
||||
if (VIFs.length === 0) {
|
||||
|
@ -290,7 +289,6 @@ export default class NewVm extends BaseComponent {
|
|||
network: networkId
|
||||
})
|
||||
}
|
||||
const { state } = this.state
|
||||
const name_label = state.name_label === '' || !state.name_labelHasChanged ? template.name_label : state.name_label
|
||||
this._setState({
|
||||
// infos
|
||||
|
@ -316,55 +314,118 @@ export default class NewVm extends BaseComponent {
|
|||
device,
|
||||
name_description: disk.name_description || 'Created by XO',
|
||||
name_label: (name_label || 'disk') + '_' + device,
|
||||
SR: state.pool.default_SR
|
||||
SR: pool
|
||||
? pool.default_SR
|
||||
: resourceSet.objectsByType['SR'][0].id
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
getCloudInitConfig(template.id).then(
|
||||
cloudConfig => this._setState({ cloudConfig }),
|
||||
noop
|
||||
)
|
||||
if (template.name_label === 'CoreOS') {
|
||||
getCloudInitConfig(template.id).then(
|
||||
cloudConfig => this._setState({ cloudConfig }),
|
||||
noop
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_addVdi = () => {
|
||||
const { state } = this.state
|
||||
const device = String(this.getUniqueId())
|
||||
this._setState({ VDIs: [ ...state.VDIs, {
|
||||
device,
|
||||
name_description: 'Created by XO',
|
||||
name_label: (state.name_label || 'disk') + '_' + device,
|
||||
SR: state.pool.default_SR,
|
||||
type: 'system'
|
||||
}] })
|
||||
}
|
||||
_removeVdi = index => {
|
||||
const { VDIs } = this.state.state
|
||||
this._setState({ VDIs: [ ...VDIs.slice(0, index), ...VDIs.slice(index + 1) ] })
|
||||
}
|
||||
_addInterface = () => {
|
||||
const networkId = this._getDefaultNetworkId()
|
||||
this._setState({ VIFs: [ ...this.state.state.VIFs, {
|
||||
id: this.getUniqueId(),
|
||||
network: networkId
|
||||
}] })
|
||||
}
|
||||
_removeInterface = index => {
|
||||
const { VIFs } = this.state.state
|
||||
this._setState({ VIFs: [ ...VIFs.slice(0, index), ...VIFs.slice(index + 1) ] })
|
||||
// Selectors -------------------------------------------------------------------
|
||||
|
||||
_getIsInPool = createSelector(
|
||||
() => {
|
||||
const { pool } = this.state
|
||||
return pool && pool.id
|
||||
},
|
||||
poolId => ({ $pool }) =>
|
||||
$pool === poolId
|
||||
)
|
||||
_getIsInResourceSet = createSelector(
|
||||
() => {
|
||||
const { resourceSet } = this.state
|
||||
return resourceSet && resourceSet.objectsByType
|
||||
},
|
||||
objectsByType => (obj, objType) => {
|
||||
const [id, type] = isObject(obj) ? [obj.id, obj.type] : [obj, objType]
|
||||
return objectsByType && includes(map(objectsByType[type], object => object.id), id)
|
||||
}
|
||||
)
|
||||
_getCanOperate = createSelector(
|
||||
() => this.state.permissions,
|
||||
permissions => ({ id }) =>
|
||||
this.props.isAdmin || permissions && permissions[id] && permissions[id].operate
|
||||
)
|
||||
_getVmPredicate = createSelector(
|
||||
this._getIsInPool,
|
||||
this._getIsInResourceSet,
|
||||
(isInPool, isInResourceSet) => vm =>
|
||||
isInResourceSet(vm) || isInPool(vm)
|
||||
)
|
||||
_getSrPredicate = createSelector(
|
||||
this._getIsInPool,
|
||||
this._getIsInResourceSet,
|
||||
(isInPool, isInResourceSet) => disk =>
|
||||
(isInResourceSet(disk) || isInPool(disk)) && disk.content_type !== 'iso' && disk.size > 0
|
||||
)
|
||||
_getIsoPredicate = () => disk =>
|
||||
disk.content_type === 'iso'
|
||||
_getNetworkPredicate = createSelector(
|
||||
this._getIsInPool,
|
||||
this._getIsInResourceSet,
|
||||
(isInPool, isInResourceSet) => network =>
|
||||
isInResourceSet(network) || isInPool(network)
|
||||
)
|
||||
_getPoolNetworks = createSelector(
|
||||
() => this.props.networks,
|
||||
() => {
|
||||
const { pool } = this.state
|
||||
return pool && pool.id
|
||||
},
|
||||
(networks, poolId) => filter(networks, network => network.$pool === poolId)
|
||||
)
|
||||
_getOperatablePools = createFilter(
|
||||
() => this.props.pools,
|
||||
this._getCanOperate,
|
||||
[ (pool, canOperate) => canOperate(pool) ]
|
||||
)
|
||||
_getDefaultNetworkId = () => {
|
||||
const { resourceSet } = this.state
|
||||
if (resourceSet) {
|
||||
return resourceSet.objectsByType['network'][0].id
|
||||
}
|
||||
const network = find(this._getPoolNetworks(), network => {
|
||||
const pif = getObject(store.getState(), network.PIFs[0])
|
||||
return pif && pif.management
|
||||
})
|
||||
return network && network.id
|
||||
}
|
||||
|
||||
// if index: the element to be modified is an array/object
|
||||
// if stateObjectProp: the array/object contains objects and stateObjectProp needs to be modified
|
||||
// if targetObjectProp: the event target value is an object and the new value is the targetObjectProp of this object
|
||||
// On change -------------------------------------------------------------------
|
||||
/*
|
||||
* if index: the element to be modified should be an array/object
|
||||
* if stateObjectProp: the array/object contains objects and stateObjectProp needs to be modified
|
||||
* if targetObjectProp: the event target value is an object and the new value is the targetObjectProp of this object
|
||||
*
|
||||
* SCHEMA: EXAMPLE:
|
||||
*
|
||||
* state: { this.state.state: {
|
||||
* [prop]: { existingDisks: {
|
||||
* [index]: { 0: {
|
||||
* [stateObjectProp]: TO BE MODIFIED name_label: TO BE MODIFIED
|
||||
* ... name_description
|
||||
* } ...
|
||||
* ... }
|
||||
* } 1: {...}
|
||||
* ... }
|
||||
* } }
|
||||
*/
|
||||
_getOnChange (prop, index, stateObjectProp, targetObjectProp) {
|
||||
return event => {
|
||||
let value
|
||||
if (index !== undefined) {
|
||||
if (index !== undefined) { // The element should be an array or an object
|
||||
value = this.state.state[prop]
|
||||
value = isArray(value) ? [ ...value ] : { ...value }
|
||||
value = isArray(value) ? [ ...value ] : { ...value } // Clone the element
|
||||
let eventValue = getEventValue(event)
|
||||
eventValue = targetObjectProp ? eventValue[targetObjectProp] : eventValue
|
||||
eventValue = targetObjectProp ? eventValue[targetObjectProp] : eventValue // Get the new value
|
||||
if (value[index] && stateObjectProp) {
|
||||
value[index][stateObjectProp] = eventValue
|
||||
} else {
|
||||
|
@ -390,20 +451,106 @@ export default class NewVm extends BaseComponent {
|
|||
this._setState({ [prop]: value })
|
||||
}
|
||||
}
|
||||
_updateNbVms = () => {
|
||||
const { nbVms, name_label, nameLabels } = this.state.state
|
||||
const nbVmsClamped = clamp(nbVms, NB_VMS_MIN, NB_VMS_MAX)
|
||||
const newNameLabels = [ ...nameLabels ]
|
||||
if (nbVmsClamped < nameLabels.length) {
|
||||
this._setState({ nameLabels: slice(newNameLabels, 0, nbVmsClamped) })
|
||||
} else {
|
||||
for (let i = nameLabels.length + 1; i <= nbVmsClamped; i++) {
|
||||
newNameLabels.push(`${name_label || 'VM'}_${i}`)
|
||||
}
|
||||
this._setState({ nameLabels: newNameLabels })
|
||||
}
|
||||
}
|
||||
_updateNameLabels = () => {
|
||||
const { name_label, nameLabels } = this.state.state
|
||||
const nbVms = nameLabels.length
|
||||
const newNameLabels = []
|
||||
for (let i = 1; i <= nbVms; i++) {
|
||||
newNameLabels.push(`${name_label || 'VM'}_${i}`)
|
||||
}
|
||||
this._setState({ nameLabels: newNameLabels })
|
||||
}
|
||||
_selectResourceSet = resourceSet =>
|
||||
this._reset({ pool: undefined, resourceSet })
|
||||
_selectPool = pool =>
|
||||
this._reset({ pool, resourceSet: undefined })
|
||||
_addVdi = () => {
|
||||
const { pool, state } = this.state
|
||||
const device = String(this.getUniqueId())
|
||||
this._setState({ VDIs: [ ...state.VDIs, {
|
||||
device,
|
||||
name_description: 'Created by XO',
|
||||
name_label: (state.name_label || 'disk') + '_' + device,
|
||||
SR: pool && pool.default_SR,
|
||||
type: 'system'
|
||||
}] })
|
||||
}
|
||||
_removeVdi = index => {
|
||||
const { VDIs } = this.state.state
|
||||
this._setState({ VDIs: [ ...VDIs.slice(0, index), ...VDIs.slice(index + 1) ] })
|
||||
}
|
||||
_addInterface = () => {
|
||||
const networkId = this._getDefaultNetworkId()
|
||||
this._setState({ VIFs: [ ...this.state.state.VIFs, {
|
||||
id: this.getUniqueId(),
|
||||
network: networkId
|
||||
}] })
|
||||
}
|
||||
_removeInterface = index => {
|
||||
const { VIFs } = this.state.state
|
||||
this._setState({ VIFs: [ ...VIFs.slice(0, index), ...VIFs.slice(index + 1) ] })
|
||||
}
|
||||
|
||||
_getRedirectionUrl = id =>
|
||||
this.state.state.multipleVms ? '/home' : `/vms/${id}`
|
||||
|
||||
// MAIN ------------------------------------------------------------------------
|
||||
|
||||
_renderHeader = () => {
|
||||
const { pool, resourceSet, resourceSets } = this.state
|
||||
const showSelectPool = !isEmpty(this._getOperatablePools())
|
||||
const showSelectResourceSet = !this.props.isAdmin && !isEmpty(resourceSets)
|
||||
const selectPool = <span className={styles.inlineSelect}>
|
||||
<SelectPool
|
||||
onChange={this._selectPool}
|
||||
predicate={this._getCanOperate()}
|
||||
value={pool}
|
||||
/>
|
||||
</span>
|
||||
const selectResourceSet = <span className={styles.inlineSelect}>
|
||||
<SelectResourceSet
|
||||
onChange={this._selectResourceSet}
|
||||
value={resourceSet}
|
||||
/>
|
||||
</span>
|
||||
return <Container>
|
||||
<Row>
|
||||
<Col mediumSize={12}>
|
||||
<h2>
|
||||
{showSelectPool && showSelectResourceSet
|
||||
? _('newVmCreateNewVmOn2', {
|
||||
select1: selectPool,
|
||||
select2: selectResourceSet
|
||||
})
|
||||
: showSelectPool || showSelectResourceSet
|
||||
? _('newVmCreateNewVmOn', {
|
||||
select: isEmpty(this._getOperatablePools()) ? selectResourceSet : selectPool
|
||||
})
|
||||
: _('newVmCreateNewVmNoPermission')
|
||||
}
|
||||
</h2>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div>
|
||||
<h1>
|
||||
{_('newVmCreateNewVmOn', {
|
||||
pool: <span className={styles.inlineSelect}>
|
||||
<SelectPool value={this.state.state.pool} onChange={this._selectPool} />
|
||||
</span>
|
||||
})}
|
||||
</h1>
|
||||
{this.state.state.pool && <form id='vmCreation'>
|
||||
const { resourceSet, pool } = this.state
|
||||
return <Page header={this._renderHeader()}>
|
||||
{(pool || resourceSet) && <form id='vmCreation'>
|
||||
<Wizard>
|
||||
{this._renderInfo()}
|
||||
{this._renderPerformances()}
|
||||
|
@ -440,9 +587,11 @@ export default class NewVm extends BaseComponent {
|
|||
</ActionButton>
|
||||
</div>
|
||||
</form>}
|
||||
</div>
|
||||
</Page>
|
||||
}
|
||||
|
||||
// INFO ------------------------------------------------------------------------
|
||||
|
||||
_renderInfo = () => {
|
||||
const {
|
||||
multipleVms,
|
||||
|
@ -456,12 +605,18 @@ export default class NewVm extends BaseComponent {
|
|||
<SectionContent>
|
||||
<Item label='newVmTemplateLabel'>
|
||||
<span className={styles.inlineSelect}>
|
||||
<SelectVmTemplate
|
||||
{this.state.pool ? <SelectVmTemplate
|
||||
onChange={this._initTemplate}
|
||||
placeholder={_('newVmSelectTemplate')}
|
||||
predicate={this._getIsInPool()}
|
||||
predicate={this._getVmPredicate()}
|
||||
value={template}
|
||||
/>
|
||||
: <SelectResourceSetsVmTemplate
|
||||
onChange={this._initTemplate}
|
||||
placeholder={_('newVmSelectTemplate')}
|
||||
resourceSet={this.state.resourceSet}
|
||||
value={template}
|
||||
/>}
|
||||
</span>
|
||||
</Item>
|
||||
<Item label='newVmNameLabel'>
|
||||
|
@ -558,6 +713,8 @@ export default class NewVm extends BaseComponent {
|
|||
return CPUs && memory !== undefined
|
||||
}
|
||||
|
||||
// INSTALL SETTINGS ------------------------------------------------------------
|
||||
|
||||
_renderInstallSettings = () => {
|
||||
const { template } = this.state.state
|
||||
if (!template) {
|
||||
|
@ -570,7 +727,6 @@ export default class NewVm extends BaseComponent {
|
|||
installIso,
|
||||
installMethod,
|
||||
installNetwork,
|
||||
pool,
|
||||
pv_args,
|
||||
sshKey
|
||||
} = this.state.state
|
||||
|
@ -644,12 +800,19 @@ export default class NewVm extends BaseComponent {
|
|||
<span>{_('newVmIsoDvdLabel')}</span>
|
||||
|
||||
<span className={styles.inlineSelect}>
|
||||
<SelectVdi
|
||||
{this.state.pool ? <SelectVdi
|
||||
disabled={installMethod !== 'ISO'}
|
||||
onChange={this._getOnChange('installIso')}
|
||||
srPredicate={sr => sr.$pool === pool.id && sr.SR_type === 'iso'}
|
||||
srPredicate={this._getIsoPredicate()}
|
||||
value={installIso}
|
||||
/>
|
||||
: <SelectResourceSetsVdi
|
||||
disabled={installMethod !== 'ISO'}
|
||||
onChange={this._getOnChange('installIso')}
|
||||
resourceSet={this.state.resourceSet}
|
||||
srPredicate={this._getIsoPredicate()}
|
||||
value={installIso}
|
||||
/>}
|
||||
</span>
|
||||
</span>
|
||||
</Item>
|
||||
|
@ -729,11 +892,14 @@ export default class NewVm extends BaseComponent {
|
|||
}
|
||||
}
|
||||
|
||||
// INTERFACES ------------------------------------------------------------------
|
||||
|
||||
_renderInterfaces = () => {
|
||||
const { formatMessage } = this.props.intl
|
||||
const {
|
||||
VIFs
|
||||
} = this.state.state
|
||||
state: { VIFs },
|
||||
pool
|
||||
} = this.state
|
||||
return <Section icon='new-vm-interfaces' title='newVmInterfacesPanel' done={this._isInterfacesDone()}>
|
||||
<SectionContent column>
|
||||
{map(VIFs, (vif, index) => <div key={index}>
|
||||
|
@ -750,11 +916,16 @@ export default class NewVm extends BaseComponent {
|
|||
</Item>
|
||||
<Item label='newVmNetworkLabel'>
|
||||
<span className={styles.inlineSelect}>
|
||||
<SelectNetwork
|
||||
{pool ? <SelectNetwork
|
||||
onChange={this._getOnChange('VIFs', index, 'network', 'id')}
|
||||
predicate={this._getIsInPool()}
|
||||
predicate={this._getNetworkPredicate()}
|
||||
value={vif.network}
|
||||
/>
|
||||
: <SelectResourceSetsNetwork
|
||||
onChange={this._getOnChange('VIFs', index, 'network', 'id')}
|
||||
resourceSet={this.state.resourceSet}
|
||||
value={vif.network}
|
||||
/>}
|
||||
</span>
|
||||
</Item>
|
||||
<Item>
|
||||
|
@ -780,25 +951,35 @@ export default class NewVm extends BaseComponent {
|
|||
vif.network
|
||||
)
|
||||
|
||||
// DISKS -----------------------------------------------------------------------
|
||||
|
||||
_renderDisks = () => {
|
||||
const {
|
||||
configDrive,
|
||||
state: { configDrive,
|
||||
existingDisks,
|
||||
VDIs
|
||||
} = this.state.state
|
||||
VDIs },
|
||||
pool
|
||||
} = this.state
|
||||
let i = 0
|
||||
return <Section icon='new-vm-disks' title='newVmDisksPanel' done={this._isDisksDone()}>
|
||||
<SectionContent column>
|
||||
|
||||
{/* Existing disks */}
|
||||
{map(toArray(existingDisks), (disk, index) => <div key={index}>
|
||||
{map(existingDisks, (disk, index) => <div key={i}>
|
||||
<LineItem>
|
||||
<Item label='newVmSrLabel'>
|
||||
<span className={styles.inlineSelect}>
|
||||
<SelectSr
|
||||
{pool ? <SelectSr
|
||||
onChange={this._getOnChange('existingDisks', index, '$SR', 'id')}
|
||||
predicate={this._getSrPredicate()}
|
||||
value={disk.$SR}
|
||||
/>
|
||||
: <SelectResourceSetsSr
|
||||
onChange={this._getOnChange('existingDisks', index, '$SR', 'id')}
|
||||
predicate={this._getSrPredicate()}
|
||||
resourceSet={this.state.resourceSet}
|
||||
value={disk.$SR}
|
||||
/>}
|
||||
</span>
|
||||
</Item>
|
||||
{' '}
|
||||
|
@ -827,7 +1008,7 @@ export default class NewVm extends BaseComponent {
|
|||
/>
|
||||
</Item>
|
||||
</LineItem>
|
||||
{index < size(existingDisks) + VDIs.length - 1 && <hr />}
|
||||
{i++ < size(existingDisks) + VDIs.length - 1 && <hr />}
|
||||
</div>)}
|
||||
|
||||
{/* VDIs */}
|
||||
|
@ -835,11 +1016,17 @@ export default class NewVm extends BaseComponent {
|
|||
<LineItem>
|
||||
<Item label='newVmSrLabel'>
|
||||
<span className={styles.inlineSelect}>
|
||||
<SelectSr
|
||||
{pool ? <SelectSr
|
||||
onChange={this._getOnChange('VDIs', index, 'SR', 'id')}
|
||||
predicate={this._getSrPredicate()}
|
||||
value={vdi.SR}
|
||||
/>
|
||||
: <SelectResourceSetsSr
|
||||
onChange={this._getOnChange('VDIs', index, 'SR', 'id')}
|
||||
predicate={this._getSrPredicate()}
|
||||
resourceSet={this.state.resourceSet}
|
||||
value={vdi.SR}
|
||||
/>}
|
||||
</span>
|
||||
</Item>
|
||||
{' '}
|
||||
|
@ -902,6 +1089,8 @@ export default class NewVm extends BaseComponent {
|
|||
vdi.$SR && vdi.name_label && vdi.size !== undefined
|
||||
)
|
||||
|
||||
// SUMMARY ---------------------------------------------------------------------
|
||||
|
||||
_renderSummary = () => {
|
||||
const {
|
||||
bootAfterCreate,
|
||||
|
|
|
@ -32,7 +32,8 @@ import {
|
|||
} from 'select-objects'
|
||||
import {
|
||||
connectStore,
|
||||
formatSize
|
||||
formatSize,
|
||||
resolveResourceSets
|
||||
} from 'utils'
|
||||
import {
|
||||
createResourceSet,
|
||||
|
@ -42,8 +43,7 @@ import {
|
|||
} from 'xo'
|
||||
|
||||
import {
|
||||
Subjects,
|
||||
resolveResourceSets
|
||||
Subjects
|
||||
} from '../helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
|
|
@ -8,9 +8,13 @@ import _ from 'intl'
|
|||
import map from 'lodash/map'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { formatSize } from 'utils'
|
||||
import { subscribeResourceSets } from 'xo'
|
||||
|
||||
import {
|
||||
formatSize,
|
||||
resolveResourceSets
|
||||
} from 'utils'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardBlock,
|
||||
|
@ -18,8 +22,7 @@ import {
|
|||
} from 'card'
|
||||
|
||||
import {
|
||||
Subjects,
|
||||
resolveResourceSets
|
||||
Subjects
|
||||
} from '../helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import _ from 'intl'
|
||||
import forEach from 'lodash/forEach'
|
||||
import keyBy from 'lodash/keyBy'
|
||||
import map from 'lodash/map'
|
||||
import propTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import store from 'store'
|
||||
import { getObject } from 'selectors'
|
||||
|
||||
import {
|
||||
subscribeGroups,
|
||||
|
@ -15,41 +12,6 @@ import {
|
|||
|
||||
// ===================================================================
|
||||
|
||||
export const resolveResourceSets = resourceSets => (
|
||||
map(resourceSets, resourceSet => {
|
||||
const { objects, ...attrs } = resourceSet
|
||||
const resolvedObjects = {}
|
||||
const resolvedSet = {
|
||||
...attrs,
|
||||
missingObjects: [],
|
||||
objectsByType: resolvedObjects
|
||||
}
|
||||
const state = store.getState()
|
||||
|
||||
forEach(objects, id => {
|
||||
const object = getObject(state, id)
|
||||
|
||||
// Error, missing resource.
|
||||
if (!object) {
|
||||
resolvedSet.missingObjects.push(id)
|
||||
return
|
||||
}
|
||||
|
||||
const { type } = object
|
||||
|
||||
if (!resolvedObjects[type]) {
|
||||
resolvedObjects[type] = [ object ]
|
||||
} else {
|
||||
resolvedObjects[type].push(object)
|
||||
}
|
||||
})
|
||||
|
||||
return resolvedSet
|
||||
})
|
||||
)
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@propTypes({
|
||||
subjects: propTypes.array.isRequired
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue
Block a user