feat(xo-web/tasks): show tasks for Self Service users (#6217)
See zammad#5436
This commit is contained in:
parent
c7df11cc6f
commit
dae37c6a50
@ -10,6 +10,7 @@
|
|||||||
- [Backup] Merge delta backups without copying data when using VHD directories on NFS/SMB/local remote(https://github.com/vatesfr/xen-orchestra/pull/6271))
|
- [Backup] Merge delta backups without copying data when using VHD directories on NFS/SMB/local remote(https://github.com/vatesfr/xen-orchestra/pull/6271))
|
||||||
- [Proxies] Ability to copy the proxy access URL (PR [#6287](https://github.com/vatesfr/xen-orchestra/pull/6287))
|
- [Proxies] Ability to copy the proxy access URL (PR [#6287](https://github.com/vatesfr/xen-orchestra/pull/6287))
|
||||||
- [User] User tokens management through XO interface (PR [#6276](https://github.com/vatesfr/xen-orchestra/pull/6276))
|
- [User] User tokens management through XO interface (PR [#6276](https://github.com/vatesfr/xen-orchestra/pull/6276))
|
||||||
|
- [Tasks, VM/General] Self Service users: show tasks related to their pools, hosts, SRs, networks and VMs (PR [#6217](https://github.com/vatesfr/xen-orchestra/pull/6217))
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import add from 'lodash/add'
|
import add from 'lodash/add'
|
||||||
|
import defined from '@xen-orchestra/defined'
|
||||||
import { check as checkPermissions } from 'xo-acl-resolver'
|
import { check as checkPermissions } from 'xo-acl-resolver'
|
||||||
import { createSelector as create } from 'reselect'
|
import { createSelector as create } from 'reselect'
|
||||||
import {
|
import {
|
||||||
@ -6,6 +7,7 @@ import {
|
|||||||
filter,
|
filter,
|
||||||
find,
|
find,
|
||||||
forEach,
|
forEach,
|
||||||
|
forOwn,
|
||||||
groupBy,
|
groupBy,
|
||||||
identity,
|
identity,
|
||||||
isArrayLike,
|
isArrayLike,
|
||||||
@ -588,3 +590,60 @@ export const createGetHostState = getHost =>
|
|||||||
(powerState, enabled, operations) =>
|
(powerState, enabled, operations) =>
|
||||||
powerState !== 'Running' ? powerState : !isEmpty(operations) ? 'Busy' : !enabled ? 'Disabled' : 'Running'
|
powerState !== 'Running' ? powerState : !isEmpty(operations) ? 'Busy' : !enabled ? 'Disabled' : 'Running'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const taskPredicate = obj => !isEmpty(obj.current_operations)
|
||||||
|
const getLinkedObjectsByTaskRefOrId = create(
|
||||||
|
createGetObjectsOfType('pool').filter([taskPredicate]),
|
||||||
|
createGetObjectsOfType('host').filter([taskPredicate]),
|
||||||
|
createGetObjectsOfType('SR').filter([taskPredicate]),
|
||||||
|
createGetObjectsOfType('VDI').filter([taskPredicate]),
|
||||||
|
createGetObjectsOfType('VM').filter([taskPredicate]),
|
||||||
|
createGetObjectsOfType('network').filter([taskPredicate]),
|
||||||
|
getCheckPermissions,
|
||||||
|
(pools, hosts, srs, vdis, vms, networks, check) => {
|
||||||
|
const linkedObjectsByTaskRefOrId = {}
|
||||||
|
const resolveLinkedObjects = obj => {
|
||||||
|
if (!check(obj.id, 'view')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(obj.current_operations).forEach(task => {
|
||||||
|
if (linkedObjectsByTaskRefOrId[task] === undefined) {
|
||||||
|
linkedObjectsByTaskRefOrId[task] = []
|
||||||
|
}
|
||||||
|
linkedObjectsByTaskRefOrId[task].push(obj)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
forOwn(pools, resolveLinkedObjects)
|
||||||
|
forOwn(hosts, resolveLinkedObjects)
|
||||||
|
forOwn(srs, resolveLinkedObjects)
|
||||||
|
forOwn(vdis, resolveLinkedObjects)
|
||||||
|
forOwn(vms, resolveLinkedObjects)
|
||||||
|
forOwn(networks, resolveLinkedObjects)
|
||||||
|
|
||||||
|
return linkedObjectsByTaskRefOrId
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const getResolvedPendingTasks = create(
|
||||||
|
createGetObjectsOfType('task').filter([task => task.status === 'pending']),
|
||||||
|
getLinkedObjectsByTaskRefOrId,
|
||||||
|
(tasks, linkedObjectsByTaskRefOrId) => {
|
||||||
|
const resolvedTasks = []
|
||||||
|
forEach(tasks, task => {
|
||||||
|
const objects = [
|
||||||
|
...defined(linkedObjectsByTaskRefOrId[task.xapiRef], []),
|
||||||
|
// for VMs, the current_operations prop is
|
||||||
|
// { taskId → operation } map instead of { taskRef → operation } map
|
||||||
|
...defined(linkedObjectsByTaskRefOrId[task.id], []),
|
||||||
|
]
|
||||||
|
objects.length > 0 &&
|
||||||
|
resolvedTasks.push({
|
||||||
|
...task,
|
||||||
|
objects,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return resolvedTasks
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
createGetObjectsOfType,
|
createGetObjectsOfType,
|
||||||
createSelector,
|
createSelector,
|
||||||
getIsPoolAdmin,
|
getIsPoolAdmin,
|
||||||
|
getResolvedPendingTasks,
|
||||||
getStatus,
|
getStatus,
|
||||||
getUser,
|
getUser,
|
||||||
getXoaState,
|
getXoaState,
|
||||||
@ -44,18 +45,19 @@ const returnTrue = () => true
|
|||||||
@connectStore(
|
@connectStore(
|
||||||
() => {
|
() => {
|
||||||
const getHosts = createGetObjectsOfType('host')
|
const getHosts = createGetObjectsOfType('host')
|
||||||
return {
|
return (state, props) => ({
|
||||||
hosts: getHosts,
|
hosts: getHosts(state, props),
|
||||||
isAdmin,
|
isAdmin: isAdmin(state, props),
|
||||||
isPoolAdmin: getIsPoolAdmin,
|
isPoolAdmin: getIsPoolAdmin(state, props),
|
||||||
nHosts: getHosts.count(),
|
nHosts: getHosts.count()(state, props),
|
||||||
nTasks: createGetObjectsOfType('task').count([task => task.status === 'pending']),
|
// true: useResourceSet to bypass permissions
|
||||||
pools: createGetObjectsOfType('pool'),
|
nResolvedTasks: getResolvedPendingTasks(state, props, true).length,
|
||||||
srs: createGetObjectsOfType('SR'),
|
pools: createGetObjectsOfType('pool')(state, props),
|
||||||
status: getStatus,
|
srs: createGetObjectsOfType('SR')(state, props),
|
||||||
user: getUser,
|
status: getStatus(state, props),
|
||||||
xoaState: getXoaState,
|
user: getUser(state, props),
|
||||||
}
|
xoaState: getXoaState(state, props),
|
||||||
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
withRef: true,
|
withRef: true,
|
||||||
@ -207,7 +209,7 @@ export default class Menu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isAdmin, isPoolAdmin, nTasks, state, status, user, pools, nHosts, srs, xoaState } = this.props
|
const { isAdmin, isPoolAdmin, nResolvedTasks, state, status, user, pools, nHosts, srs, xoaState } = this.props
|
||||||
const noOperatablePools = this._getNoOperatablePools()
|
const noOperatablePools = this._getNoOperatablePools()
|
||||||
const noResourceSets = this._getNoResourceSets()
|
const noResourceSets = this._getNoResourceSets()
|
||||||
const noNotifications = this._getNoNotifications()
|
const noNotifications = this._getNoNotifications()
|
||||||
@ -470,11 +472,11 @@ export default class Menu extends Component {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
||||||
!noOperatablePools && {
|
{
|
||||||
to: '/tasks',
|
to: '/tasks',
|
||||||
icon: 'task',
|
icon: 'task',
|
||||||
label: 'taskMenu',
|
label: 'taskMenu',
|
||||||
pill: nTasks,
|
pill: nResolvedTasks,
|
||||||
},
|
},
|
||||||
isAdmin && { to: '/xosan', icon: 'menu-xosan', label: 'xosan' },
|
isAdmin && { to: '/xosan', icon: 'menu-xosan', label: 'xosan' },
|
||||||
!noOperatablePools && {
|
!noOperatablePools && {
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
import _, { messages } from 'intl'
|
import _, { messages } from 'intl'
|
||||||
import Collapse from 'collapse'
|
import Collapse from 'collapse'
|
||||||
import Component from 'base-component'
|
import Component from 'base-component'
|
||||||
import defined from '@xen-orchestra/defined'
|
|
||||||
import Icon from 'icon'
|
import Icon from 'icon'
|
||||||
import Link from 'link'
|
import Link from 'link'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import renderXoItem, { Pool } from 'render-xo-item'
|
import renderXoItem, { Pool } from 'render-xo-item'
|
||||||
import SortedTable from 'sorted-table'
|
import SortedTable from 'sorted-table'
|
||||||
|
import { addSubscriptions, connectStore, resolveIds } from 'utils'
|
||||||
import { FormattedDate, FormattedRelative, injectIntl } from 'react-intl'
|
import { FormattedDate, FormattedRelative, injectIntl } from 'react-intl'
|
||||||
import { SelectPool } from 'select-objects'
|
import { SelectPool } from 'select-objects'
|
||||||
import { connectStore, resolveIds } from 'utils'
|
|
||||||
import { Col, Container, Row } from 'grid'
|
import { Col, Container, Row } from 'grid'
|
||||||
import { differenceBy, flatMap, flatten, forOwn, groupBy, isEmpty, keys, map, some, toArray } from 'lodash'
|
import { differenceBy, flatMap, groupBy, isEmpty, keys, map, some } from 'lodash'
|
||||||
import { createFilter, createGetObject, createGetObjectsOfType, createSelector } from 'selectors'
|
import {
|
||||||
import { cancelTask, cancelTasks, destroyTask, destroyTasks } from 'xo'
|
createFilter,
|
||||||
|
createGetObject,
|
||||||
|
createGetObjectsOfType,
|
||||||
|
createSelector,
|
||||||
|
getResolvedPendingTasks,
|
||||||
|
isAdmin,
|
||||||
|
} from 'selectors'
|
||||||
|
import { cancelTask, cancelTasks, destroyTask, destroyTasks, subscribePermissions } from 'xo'
|
||||||
|
|
||||||
import Page from '../page'
|
import Page from '../page'
|
||||||
|
|
||||||
@ -182,66 +188,25 @@ const GROUPED_ACTIONS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@addSubscriptions({
|
||||||
|
permissions: subscribePermissions,
|
||||||
|
})
|
||||||
@connectStore(() => {
|
@connectStore(() => {
|
||||||
const getPendingTasks = createGetObjectsOfType('task').filter([task => task.status === 'pending'])
|
const getResolvedPendingTasksByPool = createSelector(getResolvedPendingTasks, resolvedPendingTasks =>
|
||||||
|
groupBy(resolvedPendingTasks, '$pool')
|
||||||
|
)
|
||||||
|
|
||||||
const getNPendingTasks = getPendingTasks.count()
|
const getPools = createGetObjectsOfType('pool').pick(createSelector(getResolvedPendingTasksByPool, keys))
|
||||||
|
|
||||||
const predicate = obj => !isEmpty(obj.current_operations)
|
return (state, props) => {
|
||||||
|
// true: useResourceSet to bypass permissions
|
||||||
const getLinkedObjectsByTaskRefOrId = createSelector(
|
const resolvedPendingTasksByPool = getResolvedPendingTasks(state, props, true)
|
||||||
createGetObjectsOfType('pool').filter([predicate]),
|
return {
|
||||||
createGetObjectsOfType('host').filter([predicate]),
|
isAdmin: isAdmin(state, props),
|
||||||
createGetObjectsOfType('SR').filter([predicate]),
|
nResolvedTasks: resolvedPendingTasksByPool.length,
|
||||||
createGetObjectsOfType('VDI').filter([predicate]),
|
pools: getPools(state, props, true),
|
||||||
createGetObjectsOfType('VM').filter([predicate]),
|
resolvedPendingTasksByPool,
|
||||||
createGetObjectsOfType('network').filter([predicate]),
|
|
||||||
(pools, hosts, srs, vdis, vms, networks) => {
|
|
||||||
const linkedObjectsByTaskRefOrId = {}
|
|
||||||
const resolveLinkedObjects = obj => {
|
|
||||||
Object.keys(obj.current_operations).forEach(task => {
|
|
||||||
if (linkedObjectsByTaskRefOrId[task] === undefined) {
|
|
||||||
linkedObjectsByTaskRefOrId[task] = []
|
|
||||||
}
|
|
||||||
linkedObjectsByTaskRefOrId[task].push(obj)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
forOwn(pools, resolveLinkedObjects)
|
|
||||||
forOwn(hosts, resolveLinkedObjects)
|
|
||||||
forOwn(srs, resolveLinkedObjects)
|
|
||||||
forOwn(vdis, resolveLinkedObjects)
|
|
||||||
forOwn(vms, resolveLinkedObjects)
|
|
||||||
forOwn(networks, resolveLinkedObjects)
|
|
||||||
|
|
||||||
return linkedObjectsByTaskRefOrId
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
const getPendingTasksByPool = createSelector(
|
|
||||||
getPendingTasks,
|
|
||||||
getLinkedObjectsByTaskRefOrId,
|
|
||||||
(tasks, linkedObjectsByTaskRefOrId) =>
|
|
||||||
groupBy(
|
|
||||||
map(tasks, task => ({
|
|
||||||
...task,
|
|
||||||
objects: [
|
|
||||||
...defined(linkedObjectsByTaskRefOrId[task.xapiRef], []),
|
|
||||||
// for VMs, the current_operations prop is
|
|
||||||
// { taskId → operation } map instead of { taskRef → operation } map
|
|
||||||
...defined(linkedObjectsByTaskRefOrId[task.id], []),
|
|
||||||
],
|
|
||||||
})),
|
|
||||||
'$pool'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const getPools = createGetObjectsOfType('pool').pick(createSelector(getPendingTasksByPool, keys))
|
|
||||||
|
|
||||||
return {
|
|
||||||
nTasks: getNPendingTasks,
|
|
||||||
pendingTasksByPool: getPendingTasksByPool,
|
|
||||||
pools: getPools,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@ -251,11 +216,7 @@ export default class Tasks extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(props) {
|
componentWillReceiveProps(props) {
|
||||||
const finishedTasks = differenceBy(
|
const finishedTasks = differenceBy(this.props.resolvedPendingTasksByPool, props.resolvedPendingTasksByPool, 'id')
|
||||||
flatten(toArray(this.props.pendingTasksByPool)),
|
|
||||||
flatten(toArray(props.pendingTasksByPool)),
|
|
||||||
'id'
|
|
||||||
)
|
|
||||||
if (!isEmpty(finishedTasks)) {
|
if (!isEmpty(finishedTasks)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
finishedTasks: finishedTasks
|
finishedTasks: finishedTasks
|
||||||
@ -267,11 +228,11 @@ export default class Tasks extends Component {
|
|||||||
|
|
||||||
_getTasks = createSelector(
|
_getTasks = createSelector(
|
||||||
createSelector(() => this.state.pools, resolveIds),
|
createSelector(() => this.state.pools, resolveIds),
|
||||||
() => this.props.pendingTasksByPool,
|
() => this.props.resolvedPendingTasksByPool,
|
||||||
(poolIds, pendingTasksByPool) =>
|
(poolIds, resolvedPendingTasksByPool) =>
|
||||||
isEmpty(poolIds)
|
isEmpty(poolIds)
|
||||||
? flatten(toArray(pendingTasksByPool))
|
? resolvedPendingTasksByPool
|
||||||
: flatMap(poolIds, poolId => pendingTasksByPool[poolId] || [])
|
: flatMap(poolIds, poolId => resolvedPendingTasksByPool[poolId] || [])
|
||||||
)
|
)
|
||||||
|
|
||||||
_getFinishedTasks = createFilter(
|
_getFinishedTasks = createFilter(
|
||||||
@ -288,11 +249,11 @@ export default class Tasks extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { props } = this
|
const { props } = this
|
||||||
const { intl, nTasks, pools } = props
|
const { intl, nResolvedTasks, pools } = props
|
||||||
const { formatMessage } = intl
|
const { formatMessage } = intl
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page header={HEADER} title={`(${nTasks}) ${formatMessage(messages.taskPage)}`}>
|
<Page header={HEADER} title={`(${nResolvedTasks}) ${formatMessage(messages.taskPage)}`}>
|
||||||
<Container>
|
<Container>
|
||||||
<Row className='mb-1'>
|
<Row className='mb-1'>
|
||||||
<Col mediumSize={7}>
|
<Col mediumSize={7}>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import _ from 'intl'
|
import _ from 'intl'
|
||||||
import Copiable from 'copiable'
|
import Copiable from 'copiable'
|
||||||
|
import decorate from 'apply-decorators'
|
||||||
import defined, { get } from '@xen-orchestra/defined'
|
import defined, { get } from '@xen-orchestra/defined'
|
||||||
import Icon from 'icon'
|
import Icon from 'icon'
|
||||||
import isEmpty from 'lodash/isEmpty'
|
import isEmpty from 'lodash/isEmpty'
|
||||||
@ -14,14 +15,15 @@ import { FormattedRelative, FormattedDate } from 'react-intl'
|
|||||||
import { Container, Row, Col } from 'grid'
|
import { Container, Row, Col } from 'grid'
|
||||||
import { Number, Size } from 'editable'
|
import { Number, Size } from 'editable'
|
||||||
import {
|
import {
|
||||||
createCollectionWrapper,
|
|
||||||
createFinder,
|
createFinder,
|
||||||
createGetObjectsOfType,
|
createGetObjectsOfType,
|
||||||
createGetVmLastShutdownTime,
|
createGetVmLastShutdownTime,
|
||||||
createSelector,
|
createSelector,
|
||||||
|
getResolvedPendingTasks,
|
||||||
} from 'selectors'
|
} from 'selectors'
|
||||||
import { connectStore, formatSizeShort, getVirtualizationModeLabel, osFamily } from 'utils'
|
import { connectStore, formatSizeShort, getVirtualizationModeLabel, osFamily } from 'utils'
|
||||||
import { CpuSparkLines, MemorySparkLines, NetworkSparkLines, XvdSparkLines } from 'xo-sparklines'
|
import { CpuSparkLines, MemorySparkLines, NetworkSparkLines, XvdSparkLines } from 'xo-sparklines'
|
||||||
|
import { injectState, provideState } from 'reaclette'
|
||||||
|
|
||||||
const GuestToolsDetection = ({ vm }) => {
|
const GuestToolsDetection = ({ vm }) => {
|
||||||
if (vm.power_state !== 'Running' || vm.pvDriversDetected === undefined) {
|
if (vm.power_state !== 'Running' || vm.pvDriversDetected === undefined) {
|
||||||
@ -78,153 +80,165 @@ const GuestToolsDetection = ({ vm }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connectStore(() => {
|
const GeneralTab = decorate([
|
||||||
const getVgpus = createGetObjectsOfType('vgpu')
|
connectStore(() => {
|
||||||
.pick((_, { vm }) => vm.$VGPUs)
|
const getVgpus = createGetObjectsOfType('vgpu')
|
||||||
.sort()
|
.pick((_, { vm }) => vm.$VGPUs)
|
||||||
|
.sort()
|
||||||
|
|
||||||
const getAttachedVgpu = createFinder(getVgpus, vgpu => vgpu.currentlyAttached)
|
const getAttachedVgpu = createFinder(getVgpus, vgpu => vgpu.currentlyAttached)
|
||||||
|
|
||||||
const getVgpuTypes = createGetObjectsOfType('vgpuType').pick(
|
const getVgpuTypes = createGetObjectsOfType('vgpuType').pick(
|
||||||
createSelector(getVgpus, vgpus => map(vgpus, 'vgpuType'))
|
createSelector(getVgpus, vgpus => map(vgpus, 'vgpuType'))
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return (state, props) => ({
|
||||||
lastShutdownTime: createGetVmLastShutdownTime(),
|
lastShutdownTime: createGetVmLastShutdownTime()(state, props),
|
||||||
tasks: createGetObjectsOfType('task')
|
// true: useResourceSet to bypass permissions
|
||||||
.pick(createSelector((_, { vm }) => vm.current_operations, createCollectionWrapper(Object.keys)))
|
resolvedPendingTasks: getResolvedPendingTasks(state, props, true),
|
||||||
.filter({ status: 'pending' })
|
vgpu: getAttachedVgpu(state, props),
|
||||||
.sort(),
|
vgpuTypes: getVgpuTypes(state, props),
|
||||||
vgpu: getAttachedVgpu,
|
})
|
||||||
vgpuTypes: getVgpuTypes,
|
}),
|
||||||
}
|
provideState({
|
||||||
})(({ lastShutdownTime, statsOverview, tasks, vgpu, vgpuTypes, vm, vmTotalDiskSpace }) => {
|
computed: {
|
||||||
const {
|
vmResolvedPendingTasks: (_, { resolvedPendingTasks, vm }) => {
|
||||||
CPUs: cpus,
|
const vmTaskIds = Object.keys(vm.current_operations)
|
||||||
id,
|
return resolvedPendingTasks.filter(task => vmTaskIds.includes(task.id))
|
||||||
installTime,
|
},
|
||||||
mainIpAddress,
|
},
|
||||||
memory,
|
}),
|
||||||
os_version: osVersion,
|
injectState,
|
||||||
power_state: powerState,
|
({ state: { vmResolvedPendingTasks }, lastShutdownTime, statsOverview, vgpu, vgpuTypes, vm, vmTotalDiskSpace }) => {
|
||||||
startTime,
|
const {
|
||||||
tags,
|
CPUs: cpus,
|
||||||
VIFs: vifs,
|
id,
|
||||||
} = vm
|
installTime,
|
||||||
return (
|
mainIpAddress,
|
||||||
<Container>
|
memory,
|
||||||
{/* TODO: use CSS style */}
|
os_version: osVersion,
|
||||||
<br />
|
power_state: powerState,
|
||||||
<Row className='text-xs-center'>
|
startTime,
|
||||||
<Col mediumSize={3}>
|
tags,
|
||||||
<h2>
|
VIFs: vifs,
|
||||||
<Number value={cpus.number} onChange={vcpus => editVm(vm, { CPUs: vcpus })} />
|
} = vm
|
||||||
x <Icon icon='cpu' size='lg' />
|
return (
|
||||||
</h2>
|
<Container>
|
||||||
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <CpuSparkLines data={statsOverview} />}</BlockLink>
|
{/* TODO: use CSS style */}
|
||||||
</Col>
|
<br />
|
||||||
<Col mediumSize={3}>
|
|
||||||
<h2 className='form-inline'>
|
|
||||||
<Size value={defined(memory.dynamic[1], null)} onChange={memory => editVm(vm, { memory })} />
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<Icon icon='memory' size='lg' />
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <MemorySparkLines data={statsOverview} />}</BlockLink>
|
|
||||||
</Col>
|
|
||||||
<Col mediumSize={3}>
|
|
||||||
<BlockLink to={`/vms/${id}/network`}>
|
|
||||||
<h2>
|
|
||||||
{vifs.length}x <Icon icon='network' size='lg' />
|
|
||||||
</h2>
|
|
||||||
</BlockLink>
|
|
||||||
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <NetworkSparkLines data={statsOverview} />}</BlockLink>
|
|
||||||
</Col>
|
|
||||||
<Col mediumSize={3}>
|
|
||||||
<BlockLink to={`/vms/${id}/disks`}>
|
|
||||||
<h2>
|
|
||||||
{formatSizeShort(vmTotalDiskSpace)} <Icon icon='disk' size='lg' />
|
|
||||||
</h2>
|
|
||||||
</BlockLink>
|
|
||||||
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <XvdSparkLines data={statsOverview} />}</BlockLink>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/* TODO: use CSS style */}
|
|
||||||
<br />
|
|
||||||
<Row className='text-xs-center'>
|
|
||||||
<Col mediumSize={3}>
|
|
||||||
{installTime !== null && (
|
|
||||||
<div className='text-xs-center'>
|
|
||||||
{_('created', {
|
|
||||||
date: <FormattedDate day='2-digit' month='long' value={installTime * 1000} year='numeric' />,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{powerState === 'Running' || powerState === 'Paused' ? (
|
|
||||||
<div>
|
|
||||||
<p className='text-xs-center'>
|
|
||||||
{_('started', {
|
|
||||||
ago: <FormattedRelative value={startTime * 1000} />,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className='text-xs-center'>
|
|
||||||
{lastShutdownTime
|
|
||||||
? _('vmHaltedSince', {
|
|
||||||
ago: <FormattedRelative value={lastShutdownTime * 1000} />,
|
|
||||||
})
|
|
||||||
: _('vmNotRunning')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
<Col mediumSize={3}>
|
|
||||||
<p>{getVirtualizationModeLabel(vm)}</p>
|
|
||||||
{vgpu !== undefined && <p>{renderXoItem(vgpuTypes[vgpu.vgpuType])}</p>}
|
|
||||||
</Col>
|
|
||||||
<Col mediumSize={3}>
|
|
||||||
<BlockLink to={`/vms/${id}/network`}>
|
|
||||||
{mainIpAddress !== undefined ? (
|
|
||||||
<Copiable tagName='p'>{mainIpAddress}</Copiable>
|
|
||||||
) : (
|
|
||||||
<p>{_('noIpv4Record')}</p>
|
|
||||||
)}
|
|
||||||
</BlockLink>
|
|
||||||
</Col>
|
|
||||||
<Col mediumSize={3}>
|
|
||||||
<BlockLink to={`/vms/${id}/advanced`}>
|
|
||||||
<Tooltip content={osVersion ? osVersion.name : _('unknownOsName')}>
|
|
||||||
<h1>
|
|
||||||
<Icon className='text-info' icon={osVersion && osVersion.distro && osFamily(osVersion.distro)} />
|
|
||||||
</h1>
|
|
||||||
</Tooltip>
|
|
||||||
</BlockLink>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<GuestToolsDetection vm={vm} />
|
|
||||||
{/* TODO: use CSS style */}
|
|
||||||
<br />
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<h2 className='text-xs-center'>
|
|
||||||
<HomeTags type='VM' labels={tags} onDelete={tag => removeTag(id, tag)} onAdd={tag => addTag(id, tag)} />
|
|
||||||
</h2>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{isEmpty(tasks) ? null : (
|
|
||||||
<Row className='text-xs-center'>
|
<Row className='text-xs-center'>
|
||||||
<Col>
|
<Col mediumSize={3}>
|
||||||
<h4>{_('vmCurrentStatus')}</h4>
|
<h2>
|
||||||
{map(tasks, task => (
|
<Number value={cpus.number} onChange={vcpus => editVm(vm, { CPUs: vcpus })} />
|
||||||
<p>
|
x <Icon icon='cpu' size='lg' />
|
||||||
<strong>{task.name_label}</strong>
|
</h2>
|
||||||
{task.progress > 0 && <span>: {Math.round(task.progress * 100)}%</span>}
|
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <CpuSparkLines data={statsOverview} />}</BlockLink>
|
||||||
</p>
|
</Col>
|
||||||
))}
|
<Col mediumSize={3}>
|
||||||
|
<h2 className='form-inline'>
|
||||||
|
<Size value={defined(memory.dynamic[1], null)} onChange={memory => editVm(vm, { memory })} />
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<Icon icon='memory' size='lg' />
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <MemorySparkLines data={statsOverview} />}</BlockLink>
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={3}>
|
||||||
|
<BlockLink to={`/vms/${id}/network`}>
|
||||||
|
<h2>
|
||||||
|
{vifs.length}x <Icon icon='network' size='lg' />
|
||||||
|
</h2>
|
||||||
|
</BlockLink>
|
||||||
|
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <NetworkSparkLines data={statsOverview} />}</BlockLink>
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={3}>
|
||||||
|
<BlockLink to={`/vms/${id}/disks`}>
|
||||||
|
<h2>
|
||||||
|
{formatSizeShort(vmTotalDiskSpace)} <Icon icon='disk' size='lg' />
|
||||||
|
</h2>
|
||||||
|
</BlockLink>
|
||||||
|
<BlockLink to={`/vms/${id}/stats`}>{statsOverview && <XvdSparkLines data={statsOverview} />}</BlockLink>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
{/* TODO: use CSS style */}
|
||||||
</Container>
|
<br />
|
||||||
)
|
<Row className='text-xs-center'>
|
||||||
})
|
<Col mediumSize={3}>
|
||||||
|
{installTime !== null && (
|
||||||
|
<div className='text-xs-center'>
|
||||||
|
{_('created', {
|
||||||
|
date: <FormattedDate day='2-digit' month='long' value={installTime * 1000} year='numeric' />,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{powerState === 'Running' || powerState === 'Paused' ? (
|
||||||
|
<div>
|
||||||
|
<p className='text-xs-center'>
|
||||||
|
{_('started', {
|
||||||
|
ago: <FormattedRelative value={startTime * 1000} />,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className='text-xs-center'>
|
||||||
|
{lastShutdownTime
|
||||||
|
? _('vmHaltedSince', {
|
||||||
|
ago: <FormattedRelative value={lastShutdownTime * 1000} />,
|
||||||
|
})
|
||||||
|
: _('vmNotRunning')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={3}>
|
||||||
|
<p>{getVirtualizationModeLabel(vm)}</p>
|
||||||
|
{vgpu !== undefined && <p>{renderXoItem(vgpuTypes[vgpu.vgpuType])}</p>}
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={3}>
|
||||||
|
<BlockLink to={`/vms/${id}/network`}>
|
||||||
|
{mainIpAddress !== undefined ? (
|
||||||
|
<Copiable tagName='p'>{mainIpAddress}</Copiable>
|
||||||
|
) : (
|
||||||
|
<p>{_('noIpv4Record')}</p>
|
||||||
|
)}
|
||||||
|
</BlockLink>
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={3}>
|
||||||
|
<BlockLink to={`/vms/${id}/advanced`}>
|
||||||
|
<Tooltip content={osVersion ? osVersion.name : _('unknownOsName')}>
|
||||||
|
<h1>
|
||||||
|
<Icon className='text-info' icon={osVersion && osVersion.distro && osFamily(osVersion.distro)} />
|
||||||
|
</h1>
|
||||||
|
</Tooltip>
|
||||||
|
</BlockLink>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<GuestToolsDetection vm={vm} />
|
||||||
|
{/* TODO: use CSS style */}
|
||||||
|
<br />
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<h2 className='text-xs-center'>
|
||||||
|
<HomeTags type='VM' labels={tags} onDelete={tag => removeTag(id, tag)} onAdd={tag => addTag(id, tag)} />
|
||||||
|
</h2>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{isEmpty(vmResolvedPendingTasks) ? null : (
|
||||||
|
<Row className='text-xs-center'>
|
||||||
|
<Col>
|
||||||
|
<h4>{_('vmCurrentStatus')}</h4>
|
||||||
|
{map(vmResolvedPendingTasks, task => (
|
||||||
|
<p>
|
||||||
|
<strong>{task.name_label}</strong>
|
||||||
|
{task.progress > 0 && <span>: {Math.round(task.progress * 100)}%</span>}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
export default GeneralTab
|
||||||
|
Loading…
Reference in New Issue
Block a user