diff --git a/src/common/intl/messages.js b/src/common/intl/messages.js
index ed5dc80ce..fcf23ba71 100644
--- a/src/common/intl/messages.js
+++ b/src/common/intl/messages.js
@@ -904,12 +904,22 @@ const messages = {
hostPanel: 'Host{hosts, plural, one {} other {s}}',
vmPanel: 'VM{vms, plural, one {} other {s}}',
memoryStatePanel: 'RAM Usage:',
+ usedMemory: 'Used Memory',
+ totalMemory: 'Total Memory',
+ totalCpus: 'CPUs Total',
+ usedVCpus: 'Used vCPUs',
+ usedSpace: 'Used Space',
+ totalSpace: 'Total Space',
cpuStatePanel: 'CPUs Usage',
vmStatePanel: 'VMs Power state',
+ vmStateHalted: 'Halted',
+ vmStateOther: 'Other',
+ vmStateRunning: 'Running',
taskStatePanel: 'Pending tasks',
usersStatePanel: 'Users',
srStatePanel: 'Storage state',
ofUsage: '{usage} (of {total})',
+ ofCpusUsage: '{nVcpus, number} vCPU{nVcpus, plural, one {} other {s}} (of {nCpus, number} CPU{nCpus, plural, one {} other {s}})',
noSrs: 'No storage',
srName: 'Name',
srPool: 'Pool',
@@ -921,6 +931,7 @@ const messages = {
srFree: 'free',
srUsageStatePanel: 'Storage Usage',
srTopUsageStatePanel: 'Top 5 SR Usage (in %)',
+ notEnoughPermissionsError: 'Not enough permissions!',
vmsStates: '{running, number} running ({halted, number} halted)',
dashboardStatsButtonRemoveAll: 'Clear selection',
dashboardStatsButtonAddAllHost: 'Add all hosts',
@@ -1048,9 +1059,10 @@ const messages = {
ipPool: 'IP pool',
quantity: 'Quantity',
noResourceSetLimits: 'No limits.',
- totalResource: 'Total:',
remainingResource: 'Remaining:',
- usedResource: 'Used:',
+ usedResourceLabel: 'Used',
+ availableResourceLabel: 'Available',
+ resourceSetQuota: 'Used: {usage} (Total: {total})',
resourceSetNew: 'New',
// ---- VM import ---
diff --git a/src/common/resource-set-quotas.js b/src/common/resource-set-quotas.js
new file mode 100644
index 000000000..b19da2c96
--- /dev/null
+++ b/src/common/resource-set-quotas.js
@@ -0,0 +1,127 @@
+import _, { messages } from 'intl'
+import ChartistGraph from 'react-chartist'
+import PropTypes from 'prop-types'
+import React from 'react'
+import { Card, CardBlock, CardHeader } from 'card'
+import { Container, Row, Col } from 'grid'
+import { forEach, map } from 'lodash'
+import { injectIntl } from 'react-intl'
+
+import Component from './base-component'
+import Icon from './icon'
+import { createSelector } from './selectors'
+import { formatSize } from './utils'
+
+// ===================================================================
+
+const RESOURCES = ['disk', 'memory', 'cpus']
+
+// ===================================================================
+
+@injectIntl
+export default class ResourceSetQuotas extends Component {
+ static propTypes = {
+ limits: PropTypes.object.isRequired,
+ }
+
+ _getQuotas = createSelector(
+ () => this.props.limits,
+ limits => {
+ const quotas = {}
+
+ forEach(RESOURCES, resource => {
+ if (limits[resource] != null) {
+ const { available, total } = limits[resource]
+ quotas[resource] = {
+ available,
+ total,
+ usage: total - available,
+ }
+ }
+ })
+
+ return quotas
+ }
+ )
+
+ render () {
+ const { intl: { formatMessage } } = this.props
+ const labels = [
+ formatMessage(messages.availableResourceLabel),
+ formatMessage(messages.usedResourceLabel),
+ ]
+ const { cpus, disk, memory } = this._getQuotas()
+ const quotas = [
+ {
+ header: (
+
+ {_('cpuStatePanel')}
+
+ ),
+ validFormat: true,
+ quota: cpus,
+ },
+ {
+ header: (
+
+ {_('memoryStatePanel')}
+
+ ),
+ validFormat: false,
+ quota: memory,
+ },
+ {
+ header: (
+
+ {_('srUsageStatePanel')}
+
+ ),
+ validFormat: false,
+ quota: disk,
+ },
+ ]
+ return (
+
+
+ {map(quotas, ({ header, validFormat, quota }, key) => (
+
+
+ {header}
+
+ {quota !== undefined ? (
+
+
+
+ {_('resourceSetQuota', {
+ total: validFormat
+ ? quota.total.toString()
+ : formatSize(quota.total),
+ usage: validFormat
+ ? quota.usage.toString()
+ : formatSize(quota.usage),
+ })}
+
+
+ ) : (
+ ∞
+ )}
+
+
+
+ ))}
+
+
+ )
+ }
+}
diff --git a/src/xo-app/dashboard/overview/index.js b/src/xo-app/dashboard/overview/index.js
index dce8216ae..391e21a73 100644
--- a/src/xo-app/dashboard/overview/index.js
+++ b/src/xo-app/dashboard/overview/index.js
@@ -1,18 +1,18 @@
-import _ from 'intl'
+import _, { messages } from 'intl'
import ButtonGroup from 'button-group'
import ChartistGraph from 'react-chartist'
import Component from 'base-component'
-import forEach from 'lodash/forEach'
-import Icon from 'icon'
-import propTypes from 'prop-types-decorator'
-import Link, { BlockLink } from 'link'
-import map from 'lodash/map'
import HostsPatchesTable from 'hosts-patches-table'
+import Icon from 'icon'
+import Link, { BlockLink } from 'link'
+import PropTypes from 'prop-types'
import React from 'react'
-import size from 'lodash/size'
+import ResourceSetQuotas from 'resource-set-quotas'
import Upgrade from 'xoa-upgrade'
import { Card, CardBlock, CardHeader } from 'card'
import { Container, Row, Col } from 'grid'
+import { forEach, isEmpty, map, size } from 'lodash'
+import { injectIntl } from 'react-intl'
import {
createCollectionWrapper,
createCounter,
@@ -22,17 +22,27 @@ import {
createTop,
isAdmin,
} from 'selectors'
-import { connectStore, formatSize } from 'utils'
-import { isSrWritable, subscribeUsers } from 'xo'
+import { addSubscriptions, connectStore, formatSize } from 'utils'
+import {
+ isSrWritable,
+ subscribePermissions,
+ subscribeResourceSets,
+ subscribeUsers,
+} from 'xo'
import styles from './index.css'
// ===================================================================
-@propTypes({
- hosts: propTypes.object.isRequired,
-})
+const PIE_GRAPH_OPTIONS = { donut: true, donutWidth: 40, showLabel: false }
+
+// ===================================================================
+
class PatchesCard extends Component {
+ static propTypes = {
+ hosts: PropTypes.object.isRequired,
+ }
+
_getContainer = () => this.refs.container
render () {
@@ -55,8 +65,6 @@ class PatchesCard extends Component {
}
}
-// ===================================================================
-
@connectStore(() => {
const getHosts = createGetObjectsOfType('host')
const getVms = createGetObjectsOfType('VM')
@@ -111,7 +119,6 @@ class PatchesCard extends Component {
return {
hostMetrics: getHostMetrics,
hosts: getHosts,
- isAdmin,
nAlarmMessages: getNumberOfAlarmMessages,
nHosts: getNumberOfHosts,
nPools: getNumberOfPools,
@@ -126,18 +133,22 @@ class PatchesCard extends Component {
vmMetrics: getVmMetrics,
}
})
-export default class Overview extends Component {
+@injectIntl
+class DefaultCard extends Component {
componentWillMount () {
this.componentWillUnmount = subscribeUsers(users => {
this.setState({ users })
})
}
+
render () {
const { props, state } = this
const users = state && state.users
const nUsers = size(users)
- return process.env.XOA_PLAN > 2 ? (
+ const { formatMessage } = props.intl
+
+ return (
@@ -186,14 +197,17 @@ export default class Overview extends Component {
@@ -214,7 +228,10 @@ export default class Overview extends Component {
- {_('ofUsage', {
- total: `${props.hostMetrics.cpus} CPUs`,
- usage: `${props.vmMetrics.vcpus} vCPUs`,
+ {_('ofCpusUsage', {
+ nCpus: props.hostMetrics.cpus,
+ nVcpus: props.vmMetrics.vcpus,
})}
@@ -244,17 +261,16 @@ export default class Overview extends Component {
@@ -326,7 +342,11 @@ export default class Overview extends Component {
- ) : (
-
-
-
+ )
+ }
+}
+
+// ===================================================================
+
+@addSubscriptions({
+ resourceSets: subscribeResourceSets,
+ permissions: subscribePermissions,
+})
+@connectStore({
+ isAdmin,
+})
+export default class Overview extends Component {
+ render () {
+ const { props } = this
+ const showResourceSets = !isEmpty(props.resourceSets) && !props.isAdmin
+ const authorized = !isEmpty(props.permissions) || props.isAdmin
+
+ if (!authorized && !showResourceSets) {
+ return {_('notEnoughPermissionsError')}
+ }
+
+ return (
+
+
+ {showResourceSets ? (
+ map(props.resourceSets, resourceSet => (
+
+
+
+ {resourceSet.name}
+
+
+
+
+
+
+ ))
+ ) : (
+
+ )}
+
+
)
}
}
diff --git a/src/xo-app/self/index.js b/src/xo-app/self/index.js
index c56ab5b44..1377d7855 100644
--- a/src/xo-app/self/index.js
+++ b/src/xo-app/self/index.js
@@ -1,6 +1,5 @@
import _ from 'intl'
import ActionButton from 'action-button'
-import ChartistGraph from 'react-chartist'
import Collapse from 'collapse'
import Component from 'base-component'
import defined from 'xo-defined'
@@ -15,13 +14,15 @@ import isEmpty from 'lodash/isEmpty'
import keys from 'lodash/keys'
import map from 'lodash/map'
import mapKeys from 'lodash/mapKeys'
-import propTypes from 'prop-types-decorator'
+import PropTypes from 'prop-types'
import React from 'react'
import remove from 'lodash/remove'
import renderXoItem from 'render-xo-item'
+import ResourceSetQuotas from 'resource-set-quotas'
import Upgrade from 'xoa-upgrade'
import { Container, Row, Col } from 'grid'
import { createGetObjectsOfType, createSelector } from 'selectors'
+import { injectIntl } from 'react-intl'
import { SizeInput } from 'form'
import {
@@ -36,13 +37,10 @@ import {
import {
addSubscriptions,
connectStore,
- formatSize,
resolveIds,
resolveResourceSets,
} from 'utils'
-import { Card, CardBlock, CardHeader } from 'card'
-
import {
SelectIpPool,
SelectNetwork,
@@ -72,10 +70,7 @@ const HEADER = (
// ===================================================================
-const Hosts = propTypes({
- eligibleHosts: propTypes.array.isRequired,
- excludedHosts: propTypes.array.isRequired,
-})(({ eligibleHosts, excludedHosts }) => (
+const Hosts = ({ eligibleHosts, excludedHosts }) => (
@@ -117,14 +112,15 @@ const Hosts = propTypes({
-))
+)
+
+Hosts.propTypes = {
+ eligibleHosts: PropTypes.array.isRequired,
+ excludedHosts: PropTypes.array.isRequired,
+}
// ===================================================================
-@propTypes({
- onSave: propTypes.func,
- resourceSet: propTypes.object,
-})
@connectStore(() => {
const getHosts = createGetObjectsOfType('host').sort()
const getHostsByPool = getHosts.groupBy('$pool')
@@ -135,6 +131,11 @@ const Hosts = propTypes({
}
})
export class Edit extends Component {
+ static propTypes = {
+ onSave: PropTypes.func,
+ resourceSet: PropTypes.object,
+ }
+
constructor (props) {
super(props)
@@ -588,16 +589,12 @@ export class Edit extends Component {
@addSubscriptions({
ipPools: subscribeIpPools,
})
+@injectIntl
class ResourceSet extends Component {
_renderDisplay = () => {
const { resourceSet } = this.props
const resolvedIpPools = mapKeys(this.props.ipPools, 'id')
- const {
- limits: { cpus, disk, memory } = {},
- ipPools,
- subjects,
- objectsByType,
- } = resourceSet
+ const { limits, ipPools, subjects, objectsByType } = resourceSet
return [
@@ -614,16 +611,16 @@ class ResourceSet extends Component {
{map(ipPools, pool => {
const resolvedIpPool = resolvedIpPools[pool]
- const limits = get(resourceSet, `limits[ipPool:${pool}]`)
- const available = limits && limits.available
- const total = limits && limits.total
+ const ipPoolLimits = limits && get(limits, `[ipPool:${pool}]`)
+ const available = ipPoolLimits && ipPoolLimits.available
+ const total = ipPoolLimits && ipPoolLimits.total
return (
{renderXoItem({
name: resolvedIpPool && resolvedIpPool.name,
type: 'ipPool',
})}
- {limits && (
+ {ipPoolLimits && (
{' '}
({available}/{total})
@@ -635,112 +632,7 @@ class ResourceSet extends Component {
),
-
-
-
-
- {_('resourceSetVcpus')}
-
-
- {cpus ? (
-
-
-
- {_('usedResource')} {cpus.total - cpus.available} ({_(
- 'totalResource'
- )}{' '}
- {cpus.total})
-
-
- ) : (
- ∞
- )}
-
-
-
-
-
-
- {_('resourceSetMemory')}
-
-
- {memory ? (
-
-
-
- {_('usedResource')}{' '}
- {formatSize(memory.total - memory.available)} ({_(
- 'totalResource'
- )}{' '}
- {formatSize(memory.total)})
-
-
- ) : (
- ∞
- )}
-
-
-
-
-
-
- {_('resourceSetStorage')}
-
-
- {disk ? (
-
-
-
- {_('usedResource')}{' '}
- {formatSize(disk.total - disk.available)} ({_(
- 'totalResource'
- )}{' '}
- {formatSize(disk.total)})
-
-
- ) : (
- ∞
- )}
-
-
-
-
+
,