Compare commits

...

32 Commits

Author SHA1 Message Date
Julien Fontanet
6b2650282d 5.8.3 2017-05-23 18:53:10 +02:00
Julien Fontanet
475be2ee30 fix(vm/advanced): behave with missing container 2017-05-23 18:52:39 +02:00
Julien Fontanet
12e1da4ef2 5.8.2 2017-05-23 18:24:21 +02:00
Julien Fontanet
780d072bb7 fix(new/vm): check pool is defined (#2169)
Fixes #2168
2017-05-23 17:38:07 +02:00
Julien Fontanet
f7e5a5cf92 fix(Icon): prop-types → prop-types-decorator 2017-05-17 15:49:34 +02:00
Nicolas Raynaud
3574c8de5c fix(package): update react-select to 1.0.0-rc.4 (#2150)
Fixes #2142
2017-05-17 15:46:04 +02:00
Julien Fontanet
b09ab4d403 fix(Button): fix @propTypes() use 2017-05-17 15:30:36 +02:00
Olivier Lambert
1997f4af51 feat(host): add RAM usage for memory bar in tooltip. Fixes #2149 2017-05-16 21:33:40 +02:00
Danp2
347cd063a3 Fix scanFilesError (#2153) 2017-05-16 21:08:46 +02:00
Olivier Lambert
74a4519a33 fix(i18n): English typo 2017-05-16 15:20:52 +02:00
BCedric
20acf7cfb2 feat(vm/general): display when the VM was last running (#2147)
Fixes #1613
2017-05-16 15:16:44 +02:00
Julien Fontanet
99bc34b2da fix(form/toggle): remove debug trace 2017-05-15 16:48:50 +02:00
Julien Fontanet
f65b5e3ddd feat(settings/server): add click for info on error icon 2017-05-15 16:48:09 +02:00
Julien Fontanet
dc10492b84 fix(xo/connectServer): refresh subscription also in case of error 2017-05-15 16:48:08 +02:00
Julien Fontanet
6f7c10537b fix(vm/advanced): add missing key prop 2017-05-15 16:48:08 +02:00
Julien Fontanet
7f503cfc21 chore(form/toggle): simplify implementation 2017-05-15 16:48:08 +02:00
Julien Fontanet
9dbef0c20a chore(Icon): use propTypes decorator 2017-05-15 16:48:08 +02:00
Julien Fontanet
923166b4e3 feat(Icon): pass extraneous props down 2017-05-15 16:48:08 +02:00
Julien Fontanet
b420128e40 chore(settings/servers): remove useless styles 2017-05-15 16:48:08 +02:00
Julien Fontanet
7776a6ce23 5.8.1 2017-05-12 16:13:44 +02:00
Julien Fontanet
8db949734a feat(settings/servers): improve self-signed error 2017-05-12 16:07:49 +02:00
badrAZ
bb5bdfb9b2 feat(settings/servers): allow unauthorized certificates (#2148)
Fixes #2138
2017-05-12 12:01:08 +02:00
BCedric
9fac3ecd81 feat(backup/file-restore): explicit compatible backups (#2146) 2017-05-11 14:59:31 +02:00
BCedric
8a84cc2627 fix: display when host is disabled (#2121)
Fixes #2098
2017-05-09 17:16:38 +02:00
Julien Fontanet
61179ec67d feat(prop-types): can also be used to set context types 2017-05-09 14:33:15 +02:00
badrAZ
59fc5955ba fix(vm/advanced): affinity host selector (#2143)
Do not remove the current affinity host from the options.

Fixes #2141.
2017-05-09 10:57:31 +02:00
Julien Fontanet
e853ba6244 chore(BaseComponent): use explicit tests 2017-05-07 22:17:20 +02:00
badrAZ
fb40ae7264 feat(vm): ability to choose the cores per socket when creating or editing a VM (#2127)
Fixes #130
2017-05-04 16:12:17 +02:00
badrAZ
f629047be2 chore(vm/new-vm): use _linkState instead of _getOnChange (#2134) 2017-05-04 16:06:50 +02:00
Olivier Lambert
278d8adf1b fix(dashboard): compute correctly the total SR size and used space (#2132)
Fixes #2123
2017-05-03 17:12:58 +02:00
Olivier Lambert
87087d55aa feat(sr): also display unmanaged VDIs (#2131) 2017-05-03 17:07:03 +02:00
Julien Fontanet
46e95fe7eb feat(backup/new): min timeout is 1s 2017-04-28 22:30:32 +02:00
83 changed files with 529 additions and 274 deletions

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "xo-web",
"version": "5.8.0",
"version": "5.8.3",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [
@@ -114,7 +114,7 @@
"react-overlays": "^0.6.0",
"react-redux": "^5.0.0",
"react-router": "^3.0.0",
"react-select": "^1.0.0-rc.3",
"react-select": "^1.0.0-rc.4",
"react-shortcuts": "^1.3.1",
"react-sparklines": "^1.5.0",
"react-virtualized": "^8.0.8",

View File

@@ -5,7 +5,7 @@ import Button from './button'
import Component from './base-component'
import Icon from './icon'
import logError from './log-error'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import Tooltip from './tooltip'
import { error as _error } from './notification'

View File

@@ -1,7 +1,7 @@
import React from 'react'
import ActionButton from './action-button'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
const ActionToggle = ({ className, value, ...props }) =>
<ActionButton

View File

@@ -54,20 +54,20 @@ export default class BaseComponent extends PureComponent {
// See https://preactjs.com/guide/linked-state
linkState (name, targetPath) {
const key = targetPath
const key = targetPath !== undefined
? `${name}##${targetPath}`
: name
let linkedState = this._linkedState
let cb
if (!linkedState) {
if (linkedState === null) {
linkedState = this._linkedState = {}
} else if ((cb = linkedState[key])) {
} else if ((cb = linkedState[key]) !== undefined) {
return cb
}
let getValue
if (targetPath) {
if (targetPath !== undefined) {
const path = targetPath.split('.')
getValue = event => get(getEventValue(event), path, 0)
} else {
@@ -91,9 +91,9 @@ export default class BaseComponent extends PureComponent {
toggleState (name) {
let linkedState = this._linkedState
let cb
if (!linkedState) {
if (linkedState === null) {
linkedState = this._linkedState = {}
} else if ((cb = linkedState[name])) {
} else if ((cb = linkedState[name]) !== undefined) {
return cb
}

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames'
import React from 'react'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
const Button = ({
active,
@@ -27,7 +27,7 @@ const Button = ({
return <button {...props}>{children}</button>
}
propTypes(Button)({
propTypes({
active: propTypes.bool,
block: propTypes.bool,
@@ -51,6 +51,6 @@ propTypes(Button)({
'large',
'small'
])
})
})(Button)
export { Button as default }

View File

@@ -1,6 +1,6 @@
import React from 'react'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
const CARD_STYLE = {
minHeight: '100%'

View File

@@ -3,7 +3,7 @@ import React from 'react'
import Button from './button'
import Component from './base-component'
import Icon from './icon'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
@propTypes({
children: propTypes.any.isRequired,

View File

@@ -7,7 +7,7 @@ import {
} from 'react-bootstrap-4/lib'
import Component from './base-component'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
@uncontrollableInput({
defaultValue: ''

View File

@@ -5,7 +5,7 @@ import React, { createElement } from 'react'
import _ from '../intl'
import Button from '../button'
import Icon from '../icon'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import Tooltip from '../tooltip'
import styles from './index.css'

View File

@@ -1,5 +1,5 @@
import Component from 'base-component'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import ReactDropzone from 'react-dropzone'

View File

@@ -11,7 +11,7 @@ import Component from '../base-component'
import getEventValue from '../get-event-value'
import Icon from '../icon'
import logError from '../log-error'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import Tooltip from '../tooltip'
import { formatSize } from '../utils'
import { SizeInput } from '../form'

View File

@@ -1,7 +1,7 @@
import React from 'react'
import * as Grid from './grid'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
export const LabelCol = propTypes({
children: propTypes.any.isRequired

View File

@@ -15,7 +15,7 @@ import {
import Button from '../button'
import Component from '../base-component'
import getEventValue from '../get-event-value'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import {
firstDefined,
formatSizeRaw,

View File

@@ -4,7 +4,7 @@ import find from 'lodash/find'
import map from 'lodash/map'
import React from 'react'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import Select from './select'

View File

@@ -8,7 +8,7 @@ import {
List
} from 'react-virtualized'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
const SELECT_MENU_STYLE = {
overflow: 'hidden'

View File

@@ -2,11 +2,9 @@ import React from 'react'
import classNames from 'classnames'
import uncontrollableInput from 'uncontrollable-input'
import Component from '../../base-component'
import Icon from '../../icon'
import propTypes from '../../prop-types'
import styles from './index.css'
import Component from '../base-component'
import Icon from '../icon'
import propTypes from '../prop-types-decorator'
@uncontrollableInput()
@propTypes({
@@ -25,30 +23,24 @@ export default class Toggle extends Component {
iconSize: 2
}
_onChange = event => this.props.onChange(event.target.checked)
_toggle = () => {
const { props } = this
props.onChange(!props.value)
}
render () {
const { props } = this
return (
<label
<Icon
className={classNames(
props.disabled ? 'text-muted' : props.value ? 'text-success' : null,
props.className
)}
>
<Icon
icon={props.icon || (props.value ? props.iconOn : props.iconOff)}
size={props.iconSize}
/>
<input
checked={props.value || false}
className={styles.checkbox}
disabled={props.disabled}
onChange={this._onChange}
type='checkbox'
/>
</label>
icon={props.icon || (props.value ? props.iconOn : props.iconOff)}
onClick={this._toggle}
size={props.iconSize}
/>
)
}
}

View File

@@ -1,3 +0,0 @@
.checkbox {
display: none;
}

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames'
import React from 'react'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
export const Col = propTypes({
className: propTypes.string,

View File

@@ -1,7 +1,7 @@
import React from 'react'
import Component from './base-component'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import Tags from './tags'
import { createString, createProperty, toString } from './complex-matcher'

View File

@@ -9,7 +9,7 @@ import ActionButton from './action-button'
import Component from './base-component'
import forEach from 'lodash/forEach'
import Link from './link'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import SortedTable from './sorted-table'
import TabButton from './tab-button'
import { connectStore } from './utils'

View File

@@ -1,21 +1,25 @@
import classNames from 'classnames'
import isInteger from 'lodash/isInteger'
import React, { PropTypes } from 'react'
import React from 'react'
const Icon = ({ className, icon, size = 1, fixedWidth }) => (
<i className={classNames(
className,
icon ? `xo-icon-${icon}` : 'fa', // Without icon prop, is a placeholder.
isInteger(size) ? `fa-${size}x` : `fa-${size}`,
fixedWidth && 'fa-fw'
)} />
)
Icon.propTypes = {
fixedWidth: PropTypes.bool,
icon: PropTypes.string,
size: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
import propTypes from './prop-types-decorator'
const Icon = ({ icon, size = 1, fixedWidth, ...props }) => {
props.className = classNames(
props.className,
icon !== undefined ? `xo-icon-${icon}` : 'fa', // Without icon prop, is a placeholder.
isInteger(size) ? `fa-${size}x` : `fa-${size}`,
fixedWidth && 'fa-fw'
)
return <i {...props} />
}
propTypes(Icon)({
fixedWidth: propTypes.bool,
icon: propTypes.string,
size: propTypes.oneOfType([
propTypes.string,
propTypes.number
])
})
export default Icon

View File

@@ -24,6 +24,8 @@ var messages = {
onError: 'On error',
successful: 'Successful',
filterNoSnapshots: 'Full disks only',
filterOnlyBaseCopy: 'Base copy only',
filterOnlyRegularDisks: 'Regular disks only',
filterOnlySnapshots: 'Snapshots only',
// ----- Copiable component -----
@@ -625,6 +627,7 @@ var messages = {
vmSettings: 'Started {ago}',
vmCurrentStatus: 'Current status:',
vmNotRunning: 'Not running',
vmHaltedSince: 'Halted {ago}',
// ----- VM general tab -----
noToolsDetected: 'No Xen tools detected',
@@ -779,6 +782,11 @@ var messages = {
unknownOriginalTemplate: 'Unknown',
vmLimitsLabel: 'VM limits',
vmCpuLimitsLabel: 'CPU limits',
vmCpuTopology: 'Topology',
vmChooseCoresPerSocket: 'Default behavior',
vmCoresPerSocket: '{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket',
vmCoresPerSocketIncorrectValue: 'Incorrect cores per socket value',
vmCoresPerSocketIncorrectValueSolution: 'Please change the selected value to fix it.',
vmMemoryLimitsLabel: 'Memory limits (min/max)',
vmMaxVcpus: 'vCPUs max:',
vmMaxRam: 'Memory max:',
@@ -980,6 +988,7 @@ var messages = {
delta: 'delta',
restoreBackups: 'Restore Backups',
restoreBackupsInfo: 'Click on a VM to display restore options',
restoreDeltaBackupsInfo: 'Only the files of Delta Backup which are not on a SMB remote can be restored',
remoteEnabled: 'Enabled',
remoteError: 'Error',
noBackup: 'No backup available',
@@ -1087,6 +1096,9 @@ var messages = {
serverPassword: 'Password',
serverAction: 'Action',
serverReadOnly: 'Read Only',
serverUnauthorizedCertificates: 'Unauthorized Certificates',
serverAllowUnauthorizedCertificates: 'Allow Unauthorized Certificates',
serverUnauthorizedCertificatesInfo: 'Enable it if your certificate is rejected, but it\'s not recommended because your connection will not be secured.',
serverDisconnect: 'Disconnect server',
serverPlaceHolderUser: 'username',
serverPlaceHolderPassword: 'password',
@@ -1096,12 +1108,14 @@ var messages = {
serverError: 'Error',
serverAddFailed: 'Adding server failed',
serverStatus: 'Status',
serverConnectionFailed: 'Connection failed',
serverConnectionFailed: 'Connection failed. Click for more information.',
serverConnecting: 'Connecting...',
serverConnected: 'Connected',
serverDisconnected: 'Disconnected',
serverAuthFailed: 'Authentication error',
serverUnknownError: 'Unknown error',
serverSelfSignedCertError: 'Invalid self-signed certificate',
serverSelfSignedCertQuestion: 'Do you want to accept self-signed certificate for this server even though it would decrease security?',
// ----- Copy VM -----
copyVm: 'Copy VM',

View File

@@ -4,7 +4,7 @@ import _ from 'intl'
import ActionButton from './action-button'
import Component from './base-component'
import Icon from 'icon'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import Tooltip from 'tooltip'
import { alert } from 'modal'
import { connectStore } from './utils'

View File

@@ -5,7 +5,7 @@ import { filter, map } from 'lodash'
import _ from '../intl'
import Button from '../button'
import Component from '../base-component'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import { EMPTY_ARRAY } from '../utils'
import GenericInput from './generic-input'

View File

@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import getEventValue from '../get-event-value'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import uncontrollableInput from 'uncontrollable-input'
import { EMPTY_OBJECT } from '../utils'

View File

@@ -5,7 +5,7 @@ import { keyBy, map } from 'lodash'
import _ from '../intl'
import Component from '../base-component'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import { EMPTY_OBJECT } from '../utils'
import GenericInput from './generic-input'

View File

@@ -3,7 +3,7 @@ import React from 'react'
import uncontrollableInput from 'uncontrollable-input'
import Combobox from '../combobox'
import Component from '../base-component'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import { PrimitiveInputWrapper } from './helpers'

View File

@@ -3,7 +3,7 @@ import React from 'react'
import { routerShape } from 'react-router/lib/PropTypes'
import Component from './base-component'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
// ===================================================================

View File

@@ -6,7 +6,7 @@ import { Modal as ReactModal } from 'react-bootstrap-4/lib'
import _ from './intl'
import Button from './button'
import Icon from './icon'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import {
disable as disableShortcuts,
enable as enableShortcuts

View File

@@ -0,0 +1,33 @@
import assign from 'lodash/assign'
import { PropTypes } from 'react'
// Decorators to help declaring properties and context types on React
// components without using the tedious static properties syntax.
//
// ```js
// @propTypes({
// children: propTypes.node.isRequired
// }, {
// store: propTypes.object.isRequired
// })
// class MyComponent extends React.Component {}
// ```
const propTypes = (propTypes, contextTypes) => target => {
if (propTypes !== undefined) {
target.propTypes = {
...target.propTypes,
...propTypes
}
}
if (contextTypes !== undefined) {
target.contextTypes = {
...target.contextTypes,
...contextTypes
}
}
return target
}
assign(propTypes, PropTypes)
export { propTypes as default }

View File

@@ -1,22 +0,0 @@
import assign from 'lodash/assign'
import { PropTypes } from 'react'
// Decorators to help declaring on React components without using the
// tedious static properties syntax.
//
// ```js
// @propTypes({
// children: propTypes.node.isRequired
// })
// class MyComponent extends React.Component {}
// ```
const propTypes = types => target => {
target.propTypes = {
...target.propTypes,
...types
}
return target
}
assign(propTypes, PropTypes)
export { propTypes as default }

View File

@@ -8,7 +8,7 @@ import {
} from 'url'
import { enable as enableShortcuts, disable as disableShortcuts } from 'shortcuts'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
const parseRelativeUrl = url => parseUrl(resolveUrl(String(window.location), url))

View File

@@ -2,7 +2,7 @@ import _ from 'intl'
import React from 'react'
import Icon from './icon'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import { createGetObject } from './selectors'
import { isSrWritable } from './xo'
import {

View File

@@ -13,7 +13,7 @@ import {
import _ from './intl'
import Button from './button'
import Component from './base-component'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import TimezonePicker from './timezone-picker'
import Icon from './icon'
import Tooltip from './tooltip'

View File

@@ -1,7 +1,7 @@
import _ from 'intl'
import Component from 'base-component'
import Icon from 'icon'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import { omit } from 'lodash'

View File

@@ -25,7 +25,7 @@ import _ from './intl'
import Button from './button'
import Component from './base-component'
import Icon from './icon'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import renderXoItem from './render-xo-item'
import store from './store'
import Tooltip from './tooltip'

View File

@@ -448,6 +448,24 @@ export const createGetTags = collectionSelectors => {
return _extendCollectionSelector(getTags, 'tag')
}
export const createGetVmLastShutdownTime = (getVmId = (_, {vm}) => vm != null ? vm.id : undefined) => create(
getVmId,
createGetObjectsOfType('message'),
(vmId, messages) => {
let max = null
forEach(messages, message => {
if (
message.$object === vmId &&
message.name === 'VM_SHUTDOWN' &&
(max === null || message.time > max)
) {
max = message.time
}
})
return max
}
)
export const createGetObjectMessages = objectSelector =>
createGetObjectsOfType('message').filter(
create(

View File

@@ -1,6 +1,6 @@
import React, { cloneElement } from 'react'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
const SINGLE_LINE_STYLE = { display: 'flex' }
const COL_STYLE = { marginTop: 'auto', marginBottom: 'auto' }

View File

@@ -15,7 +15,7 @@ import { Portal } from 'react-overlays'
import Button from '../button'
import Component from '../base-component'
import Icon from '../icon'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import SingleLineRow from '../single-line-row'
import { BlockLink } from '../link'
import { Container, Col } from '../grid'

View File

@@ -2,7 +2,7 @@ import React from 'react'
import styled from 'styled-components'
import ActionButton from './action-button'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
const Button = styled(ActionButton)`
background-color: ${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateBg`]}

View File

@@ -5,7 +5,7 @@ import React from 'react'
import Component from './base-component'
import Icon from './icon'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
const INPUT_STYLE = {
margin: '2px',

View File

@@ -5,7 +5,7 @@ import React from 'react'
import _ from './intl'
import Component from './base-component'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import { getXoServerTimezone } from './xo'
import { Select } from './form'

View File

@@ -5,7 +5,7 @@ import ReactDOM from 'react-dom'
import Component from '../base-component'
import getPosition from './get-position'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import styles from './index.css'

View File

@@ -537,3 +537,21 @@ export const compareVersions = makeNiceCompare((v1, v2) => {
export const isXosanPack = ({ name }) =>
startsWith(name, 'XOSAN')
// ===================================================================
export const getCoresPerSocketPossibilities = (maxCoresPerSocket, vCPUs) => {
// According to : https://www.citrix.com/blogs/2014/03/11/citrix-xenserver-setting-more-than-one-vcpu-per-vm-to-improve-application-performance-and-server-consolidation-e-g-for-cad3-d-graphical-applications/
const maxVCPUs = 16
const options = []
if (maxCoresPerSocket !== undefined && vCPUs !== '') {
const ratio = vCPUs / maxVCPUs
for (let coresPerSocket = maxCoresPerSocket; coresPerSocket >= ratio; coresPerSocket--) {
if (vCPUs % coresPerSocket === 0) options.push(coresPerSocket)
}
}
return options
}

View File

@@ -4,7 +4,7 @@ import React, { Component, cloneElement } from 'react'
import _ from '../intl'
import Icon from '../icon'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import styles from './index.css'

View File

@@ -15,7 +15,7 @@ import {
values
} from 'lodash'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import { computeArraysSum } from '../xo-stats'
import { formatSize } from '../utils'

View File

@@ -6,7 +6,7 @@ import map from 'lodash/map'
import times from 'lodash/times'
import Component from './base-component'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import { setStyles } from './d3-utils'
// ===================================================================

View File

@@ -4,7 +4,7 @@ import {
SparklinesLine
} from 'react-sparklines'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import {
computeArraysAvg,
computeObjectsAvg

View File

@@ -5,7 +5,7 @@ import map from 'lodash/map'
import Component from '../base-component'
import _ from '../intl'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import { Toggle } from '../form'
import { setStyles } from '../d3-utils'
import {

View File

@@ -13,7 +13,7 @@ import { FormattedTime } from 'react-intl'
import _ from '../intl'
import Component from '../base-component'
import propTypes from '../prop-types'
import propTypes from '../prop-types-decorator'
import Tooltip from '../tooltip'
import styles from './index.css'

View File

@@ -5,7 +5,7 @@ import * as FormGrid from '../../form-grid'
import _ from '../../intl'
import Combobox from '../../combobox'
import Component from '../../base-component'
import propTypes from '../../prop-types'
import propTypes from '../../prop-types-decorator'
import { createSelector } from '../../selectors'
@propTypes({

View File

@@ -15,8 +15,8 @@ import sortBy from 'lodash/sortBy'
import throttle from 'lodash/throttle'
import Xo from 'xo-lib'
import { createBackoff } from 'jsonrpc-websocket-client'
import { lastly, reflect } from 'promise-toolbox'
import { noHostsAvailable } from 'xo-common/api-errors'
import { reflect } from 'promise-toolbox'
import { resolve } from 'url'
import _ from '../intl'
@@ -323,7 +323,7 @@ export const editServer = (server, props) => (
)
export const connectServer = server => (
_call('server.connect', { id: resolveId(server) })::tap(
_call('server.connect', { id: resolveId(server) })::lastly(
subscribeServers.forceRefresh
)
)

View File

@@ -3,7 +3,7 @@ import React from 'react'
import _ from './intl'
import Icon from './icon'
import Link from './link'
import propTypes from './prop-types'
import propTypes from './prop-types-decorator'
import { Card, CardHeader, CardBlock } from './card'
import { connectStore, getXoaPlan } from './utils'
import { isAdmin } from 'selectors'

View File

@@ -375,6 +375,12 @@
@extend .xo-status-busy;
}
&-disabled {
@extend .fa;
@extend .fa-circle;
@extend .xo-status-busy;
}
&-all-connected {
@extend .fa;
@extend .fa-circle;
@@ -441,6 +447,11 @@
@extend .fa-server;
@extend .text-danger;
}
&-disabled {
@extend .fa;
@extend .fa-server;
@extend .text-warning;
}
&-working {
@extend .fa;
@extend .fa-circle;

View File

@@ -18,7 +18,7 @@
}
.usage-element-highlight {
background-color: $brand-primary;
background-color: $brand-warning;
}
.usage-element-others {

View File

@@ -0,0 +1,3 @@
.listRestoreBackupInfos {
list-style-type: none;
}

View File

@@ -27,6 +27,7 @@ import {
} from 'xo'
import RestoreFileModalBody from './restore-file-modal'
import styles from './index.css'
const VM_COLUMNS = [
{
@@ -118,9 +119,18 @@ export default class FileRestore extends Component {
? <Container>
<h2>{_('restoreFiles')}</h2>
{isEmpty(backupInfoByVm)
? _('noBackup')
? <div>
<em><Icon icon='info' /> {_('restoreDeltaBackupsInfo')}</em>
<div>
<a>{_('noBackup')}</a>
</div>
</div>
: <div>
<em><Icon icon='info' /> {_('restoreBackupsInfo')}</em>
<ul className={styles.listRestoreBackupInfos}>
<li><em><Icon icon='info' /> {_('restoreBackupsInfo')}</em></li>
<li><em><Icon icon='info' /> {_('restoreDeltaBackupsInfo')}</em></li>
</ul>
<SortedTable collection={backupInfoByVm} columns={VM_COLUMNS} rowAction={openImportModal} defaultColumn={2} />
</div>
}

View File

@@ -85,7 +85,8 @@ export default class RestoreFileModalBody extends Component {
return scanFiles(backup.remoteId, disk, path, partition).then(
rawFiles => this.setState({
files: formatFilesOptions(rawFiles, path),
scanningFiles: false
scanningFiles: false,
scanFilesError: false
}),
error => {
this.setState({
@@ -104,7 +105,8 @@ export default class RestoreFileModalBody extends Component {
partition: undefined,
file: undefined,
selectedFiles: undefined,
scanDiskError: false
scanDiskError: false,
scanFilesError: false
})
}
@@ -113,7 +115,8 @@ export default class RestoreFileModalBody extends Component {
partition: undefined,
file: undefined,
selectedFiles: undefined,
scanDiskError: false
scanDiskError: false,
scanFilesError: false
})
if (!disk) {

View File

@@ -282,6 +282,7 @@ class TimeoutInput extends Component {
return <input
{...props}
onChange={this._onChange}
min='1'
type='number'
value={value == null ? '' : String(value / 1e3)}
/>

View File

@@ -4,7 +4,7 @@ import ChartistGraph from 'react-chartist'
import Component from 'base-component'
import forEach from 'lodash/forEach'
import Icon from 'icon'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import Link, { BlockLink } from 'link'
import map from 'lodash/map'
import HostsPatchesTable from 'hosts-patches-table'
@@ -69,23 +69,19 @@ class PatchesCard extends Component {
const getHostMetrics = createGetHostMetrics(getHosts)
const userSrs = createTop(
createGetObjectsOfType('SR').filter(
[ isSrWritable ]
),
[ sr => sr.physical_usage / sr.size ],
5
const writableSrs = createGetObjectsOfType('SR').filter(
[ isSrWritable ]
)
const getSrMetrics = createCollectionWrapper(
createSelector(
userSrs,
userSrs => {
writableSrs,
writableSrs => {
const metrics = {
srTotal: 0,
srUsage: 0
}
forEach(userSrs, sr => {
forEach(writableSrs, sr => {
metrics.srUsage += sr.physical_usage
metrics.srTotal += sr.size
})
@@ -144,7 +140,11 @@ class PatchesCard extends Component {
nTasks: getNumberOfTasks,
nVms: getNumberOfVms,
srMetrics: getSrMetrics,
userSrs: userSrs,
topWritableSrs: createTop(
writableSrs,
[ sr => sr.physical_usage / sr.size ],
5
),
vmMetrics: getVmMetrics
}
})
@@ -350,8 +350,8 @@ export default class Overview extends Component {
<ChartistGraph
style={{strokeWidth: '30px'}}
data={{
labels: map(props.userSrs, 'name_label'),
series: map(props.userSrs, sr => (sr.physical_usage / sr.size) * 100)
labels: map(props.topWritableSrs, 'name_label'),
series: map(props.topWritableSrs, sr => (sr.physical_usage / sr.size) * 100)
}}
options={{ showLabel: false, showGrid: false, distributeSeries: true, high: 100 }}
type='Bar'

View File

@@ -5,7 +5,7 @@ import Component from 'base-component'
import forEach from 'lodash/forEach'
import Icon from 'icon'
import map from 'lodash/map'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import renderXoItem from 'render-xo-item'
import sortBy from 'lodash/sortBy'

View File

@@ -79,7 +79,7 @@ class MiniStats extends Component {
}
}
@connectStore(({
@connectStore(() => ({
container: createGetObject((_, props) => props.item.$pool),
needsRestart: createDoesHostNeedRestart((_, props) => props.item),
nVms: createGetObjectsOfType('VM').count(
@@ -106,6 +106,7 @@ export default class HostItem extends Component {
render () {
const { item: host, container, expandAll, selected, nVms } = this.props
const toolTipContent = host.power_state === `Running` && !host.enabled ? `disabled` : _(`powerState${host.power_state}`)
return <div className={styles.item}>
<BlockLink to={`/hosts/${host.id}`}>
<SingleLineRow>
@@ -115,13 +116,15 @@ export default class HostItem extends Component {
&nbsp;&nbsp;
<Tooltip
content={isEmpty(host.current_operations)
? _(`powerState${host.power_state}`)
: <div>{_(`powerState${host.power_state}`)}{' ('}{map(host.current_operations)[0]}{')'}</div>
? toolTipContent
: <div>{toolTipContent}{' ('}{map(host.current_operations)[0]}{')'}</div>
}
>
{isEmpty(host.current_operations)
? <Icon icon={`${host.power_state.toLowerCase()}`} />
: <Icon icon='busy' />
{!isEmpty(host.current_operations)
? <Icon icon='busy' />
: (host.power_state === 'Running' && !host.enabled)
? <Icon icon='disabled' />
: <Icon icon={`${host.power_state.toLowerCase()}`} />
}
</Tooltip>
&nbsp;&nbsp;

View File

@@ -235,7 +235,7 @@ export default class Host extends Component {
<Row>
<Col mediumSize={6} className='header-title'>
<h2>
<Icon icon={`host-${host.power_state.toLowerCase()}`} />
<Icon icon={host.power_state === 'Running' && !host.enabled ? 'host-disabled' : `host-${host.power_state.toLowerCase()}`} />
{' '}
<Text
value={host.name_label}

View File

@@ -85,11 +85,11 @@ export default ({
<Usage total={host.memory.size}>
<UsageElement
highlight
tooltip='XenServer'
tooltip={`XenServer (${formatSize(vmController.memory.size)})`}
value={vmController.memory.size}
/>
{map(vms, vm => <UsageElement
tooltip={vm.name_label}
tooltip={`${vm.name_label} (${formatSize(vm.memory.size)})`}
key={vm.id}
value={vm.memory.size}
href={`#/vms/${vm.id}`}

View File

@@ -9,7 +9,7 @@ import Icon from 'icon'
import includes from 'lodash/includes'
import map from 'lodash/map'
import orderBy from 'lodash/orderBy'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React, { Component } from 'react'
import renderXoItem from 'render-xo-item'
import SortedTable from 'sorted-table'

View File

@@ -4,7 +4,6 @@ import BaseComponent from 'base-component'
import Button from 'button'
import classNames from 'classnames'
import DebounceInput from 'react-debounce-input'
import getEventValue from 'get-event-value'
import Icon from 'icon'
import isIp from 'is-ip'
import Page from '../page'
@@ -24,7 +23,6 @@ import {
forEach,
get,
includes,
isArray,
isEmpty,
join,
map,
@@ -70,6 +68,7 @@ import {
connectStore,
firstDefined,
formatSize,
getCoresPerSocketPossibilities,
noop,
resolveResourceSet
} from 'utils'
@@ -363,6 +362,7 @@ export default class NewVm extends BaseComponent {
VIFs: _VIFs,
resourceSet: resourceSet && resourceSet.id,
// vm.set parameters
coresPerSocket: state.coresPerSocket,
CPUs: state.CPUs,
cpuWeight: state.cpuWeight === '' ? null : state.cpuWeight,
cpuCap: state.cpuCap === '' ? null : state.cpuCap,
@@ -593,58 +593,19 @@ export default class NewVm extends BaseComponent {
})
)
_getCoresPerSocketPossibilities = createSelector(
() => {
const { pool } = this.props
if (pool !== undefined) {
return pool.cpus.cores
}
},
() => this.state.state.CPUs,
getCoresPerSocketPossibilities
)
// 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) { // The element should be an array or an object
value = this.state.state[prop]
value = isArray(value) ? [ ...value ] : { ...value } // Clone the element
let eventValue = getEventValue(event)
eventValue = targetObjectProp ? eventValue[targetObjectProp] : eventValue // Get the new value
if (value[index] && stateObjectProp) {
value[index][stateObjectProp] = eventValue
} else {
value[index] = eventValue
}
} else {
value = getEventValue(event)
}
this._setState({ [prop]: value })
}
}
_getOnChangeCheckbox (prop, index, stateObjectProp) {
return event => {
let value
if (index !== undefined) {
value = this.state.state[prop]
value = [ ...value ]
let eventValue = event.target.checked
stateObjectProp ? value[index][stateObjectProp] = eventValue : value[index] = eventValue
} else {
value = event.target.checked
}
this._setState({ [prop]: value })
}
}
_onChangeSshKeys = keys => this._setState({ sshKeys: map(keys, key => key.id) })
_updateNbVms = () => {
@@ -859,7 +820,7 @@ export default class NewVm extends BaseComponent {
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('name_label')}
onChange={this._linkState('name_label')}
value={name_label}
/>
</Item>
@@ -867,7 +828,7 @@ export default class NewVm extends BaseComponent {
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('name_description')}
onChange={this._linkState('name_description')}
value={name_description}
/>
</Item>
@@ -880,7 +841,8 @@ export default class NewVm extends BaseComponent {
}
_renderPerformances = () => {
const { CPUs, memoryDynamicMax } = this.state.state
const { CPUs, memoryDynamicMax, coresPerSocket } = this.state.state
return <Section icon='new-vm-perf' title='newVmPerfPanel' done={this._isPerformancesDone()}>
<SectionContent>
<Item label={_('newVmVcpusLabel')}>
@@ -888,7 +850,7 @@ export default class NewVm extends BaseComponent {
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
min={0}
onChange={this._getOnChange('CPUs')}
onChange={this._linkState('CPUs')}
type='number'
value={CPUs}
/>
@@ -900,6 +862,25 @@ export default class NewVm extends BaseComponent {
value={firstDefined(memoryDynamicMax, null)}
/>
</Item>
<Item label={_('vmCpuTopology')}>
<select
className='form-control'
onChange={this._linkState('coresPerSocket')}
value={coresPerSocket}
>
{_('vmChooseCoresPerSocket', message => <option value=''>{message}</option>)}
{map(
this._getCoresPerSocketPossibilities(),
coresPerSocket => _(
'vmCoresPerSocket', {
nSockets: CPUs / coresPerSocket,
nCores: coresPerSocket
},
message => <option key={coresPerSocket} value={coresPerSocket}>{message}</option>
)
)}
</select>
</Item>
</SectionContent>
</Section>
}
@@ -938,7 +919,7 @@ export default class NewVm extends BaseComponent {
<span className={styles.configDriveToggle}>
<Toggle
value={configDrive}
onChange={this._getOnChange('configDrive')}
onChange={this._linkState('configDrive')}
/>
</span>
</div>
@@ -949,7 +930,7 @@ export default class NewVm extends BaseComponent {
checked={installMethod === 'SSH'}
disabled={!configDrive}
name='installMethod'
onChange={this._getOnChange('installMethod')}
onChange={this._linkState('installMethod')}
type='radio'
value='SSH'
/>
@@ -962,7 +943,7 @@ export default class NewVm extends BaseComponent {
className='form-control'
disabled={!configDrive || installMethod !== 'SSH'}
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('newSshKey')}
onChange={this._linkState('newSshKey')}
value={newSshKey}
/>
<span className='input-group-btn'>
@@ -985,7 +966,7 @@ export default class NewVm extends BaseComponent {
checked={installMethod === 'customConfig'}
disabled={!configDrive}
name='installMethod'
onChange={this._getOnChange('installMethod')}
onChange={this._linkState('installMethod')}
type='radio'
value='customConfig'
/>
@@ -997,7 +978,7 @@ export default class NewVm extends BaseComponent {
debounceTimeout={DEBOUNCE_TIMEOUT}
disabled={!configDrive || installMethod !== 'customConfig'}
element='textarea'
onChange={this._getOnChange('customConfig')}
onChange={this._linkState('customConfig')}
value={customConfig}
/>
</LineItem>
@@ -1008,7 +989,7 @@ export default class NewVm extends BaseComponent {
<input
checked={installMethod === 'ISO'}
name='installMethod'
onChange={this._getOnChange('installMethod')}
onChange={this._linkState('installMethod')}
type='radio'
value='ISO'
/>
@@ -1018,13 +999,13 @@ export default class NewVm extends BaseComponent {
<span className={styles.inlineSelect}>
{this.props.pool ? <SelectVdi
disabled={installMethod !== 'ISO'}
onChange={this._getOnChange('installIso')}
onChange={this._linkState('installIso')}
srPredicate={this._getIsoPredicate()}
value={installIso}
/>
: <SelectResourceSetsVdi
disabled={installMethod !== 'ISO'}
onChange={this._getOnChange('installIso')}
onChange={this._linkState('installIso')}
resourceSet={this._getResolvedResourceSet()}
srPredicate={this._getIsoPredicate()}
value={installIso}
@@ -1038,7 +1019,7 @@ export default class NewVm extends BaseComponent {
<input
checked={installMethod === 'network'}
name='installMethod'
onChange={this._getOnChange('installMethod')}
onChange={this._linkState('installMethod')}
type='radio'
value='network'
/>
@@ -1050,7 +1031,7 @@ export default class NewVm extends BaseComponent {
debounceTimeout={DEBOUNCE_TIMEOUT}
disabled={installMethod !== 'network'}
key='networkInput'
onChange={this._getOnChange('installNetwork')}
onChange={this._linkState('installNetwork')}
placeholder={formatMessage(messages.newVmInstallNetworkPlaceHolder)}
value={installNetwork}
/>
@@ -1059,7 +1040,7 @@ export default class NewVm extends BaseComponent {
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('pv_args')}
onChange={this._linkState('pv_args')}
value={pv_args}
/>
</Item>
@@ -1068,7 +1049,7 @@ export default class NewVm extends BaseComponent {
<input
checked={installMethod === 'PXE'}
name='installMethod'
onChange={this._getOnChange('installMethod')}
onChange={this._linkState('installMethod')}
type='radio'
value='PXE'
/>
@@ -1083,7 +1064,7 @@ export default class NewVm extends BaseComponent {
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
element='textarea'
onChange={this._getOnChange('cloudConfig')}
onChange={this._linkState('cloudConfig')}
rows={7}
value={cloudConfig}
/>
@@ -1167,12 +1148,12 @@ export default class NewVm extends BaseComponent {
<Item label={_('newVmSrLabel')}>
<span className={styles.inlineSelect}>
{pool ? <SelectSr
onChange={this._getOnChange('existingDisks', index, '$SR', 'id')}
onChange={this._linkState(`existingDisks.${index}.$SR`, 'id')}
predicate={this._getSrPredicate()}
value={disk.$SR}
/>
: <SelectResourceSetsSr
onChange={this._getOnChange('existingDisks', index, '$SR', 'id')}
onChange={this._linkState(`existingDisks.${index}.$SR`, 'id')}
predicate={this._getSrPredicate()}
resourceSet={resourceSet}
value={disk.$SR}
@@ -1184,7 +1165,7 @@ export default class NewVm extends BaseComponent {
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('existingDisks', index, 'name_label')}
onChange={this._linkState(`existingDisks.${index}.name_label`)}
value={disk.name_label}
/>
</Item>
@@ -1192,14 +1173,14 @@ export default class NewVm extends BaseComponent {
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('existingDisks', index, 'name_description')}
onChange={this._linkState(`existingDisks.${index}.name_description`)}
value={disk.name_description}
/>
</Item>
<Item label={_('newVmSizeLabel')}>
<SizeInput
className={styles.sizeInput}
onChange={this._getOnChange('existingDisks', index, 'size')}
onChange={this._linkState(`existingDisks.${index}.size`)}
readOnly={!configDrive}
value={firstDefined(disk.size, null)}
/>
@@ -1214,12 +1195,12 @@ export default class NewVm extends BaseComponent {
<Item label={_('newVmSrLabel')}>
<span className={styles.inlineSelect}>
{pool ? <SelectSr
onChange={this._getOnChange('VDIs', index, 'SR', 'id')}
onChange={this._linkState(`VDIs.${index}.SR`, 'id')}
predicate={this._getSrPredicate()}
value={vdi.SR}
/>
: <SelectResourceSetsSr
onChange={this._getOnChange('VDIs', index, 'SR', 'id')}
onChange={this._linkState(`VDIs.${index}.SR`, 'id')}
predicate={this._getSrPredicate()}
resourceSet={resourceSet}
value={vdi.SR}
@@ -1230,7 +1211,7 @@ export default class NewVm extends BaseComponent {
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('VDIs', index, 'name_label')}
onChange={this._linkState(`VDIs.${index}.name_label`)}
value={vdi.name_label}
/>
</Item>
@@ -1238,14 +1219,14 @@ export default class NewVm extends BaseComponent {
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
onChange={this._getOnChange('VDIs', index, 'name_description')}
onChange={this._linkState(`VDIs.${index}.name_description`)}
value={vdi.name_description}
/>
</Item>
<Item label={_('newVmSizeLabel')}>
<SizeInput
className={styles.sizeInput}
onChange={this._getOnChange('VDIs', index, 'size')}
onChange={this._linkState(`VDIs.${index}.size`)}
value={firstDefined(vdi.size, null)}
/>
</Item>
@@ -1308,7 +1289,7 @@ export default class NewVm extends BaseComponent {
<Item>
<input
checked={bootAfterCreate}
onChange={this._getOnChangeCheckbox('bootAfterCreate')}
onChange={this._linkState('bootAfterCreate')}
type='checkbox'
/>
&nbsp;
@@ -1317,7 +1298,7 @@ export default class NewVm extends BaseComponent {
<Item>
<input
checked={autoPoweron}
onChange={this._getOnChangeCheckbox('autoPoweron')}
onChange={this._linkState('autoPoweron')}
type='checkbox'
/>
&nbsp;
@@ -1331,7 +1312,7 @@ export default class NewVm extends BaseComponent {
<Item>
<input
checked={share}
onChange={this._getOnChangeCheckbox('share')}
onChange={this._linkState('share')}
type='checkbox'
/>
&nbsp;
@@ -1345,7 +1326,7 @@ export default class NewVm extends BaseComponent {
debounceTimeout={DEBOUNCE_TIMEOUT}
min={0}
max={65535}
onChange={this._getOnChange('cpuWeight')}
onChange={this._linkState('cpuWeight')}
placeholder={formatMessage(messages.newVmDefaultCpuWeight, { value: XEN_DEFAULT_CPU_WEIGHT })}
type='number'
value={cpuWeight}
@@ -1356,7 +1337,7 @@ export default class NewVm extends BaseComponent {
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
min={0}
onChange={this._getOnChange('cpuCap')}
onChange={this._linkState('cpuCap')}
placeholder={formatMessage(messages.newVmDefaultCpuCap, { value: XEN_DEFAULT_CPU_CAP })}
type='number'
value={cpuCap}
@@ -1376,14 +1357,14 @@ export default class NewVm extends BaseComponent {
</SectionContent>,
<SectionContent>
<Item label={_('newVmMultipleVms')}>
<Toggle value={multipleVms} onChange={this._getOnChange('multipleVms')} />
<Toggle value={multipleVms} onChange={this._linkState('multipleVms')} />
</Item>
<Item label={_('newVmMultipleVmsPattern')}>
<DebounceInput
className='form-control'
debounceTimeout={DEBOUNCE_TIMEOUT}
disabled={!multipleVms}
onChange={this._getOnChange('namePattern')}
onChange={this._linkState('namePattern')}
placeholder={formatMessage(messages.newVmMultipleVmsPatternPlaceholder)}
value={namePattern}
/>
@@ -1393,7 +1374,7 @@ export default class NewVm extends BaseComponent {
className={'form-control'}
debounceTimeout={DEBOUNCE_TIMEOUT}
disabled={!multipleVms}
onChange={this._getOnChange('seqStart')}
onChange={this._linkState('seqStart')}
type='number'
value={seqStart}
/>
@@ -1405,7 +1386,7 @@ export default class NewVm extends BaseComponent {
disabled={!multipleVms}
max={NB_VMS_MAX}
min={NB_VMS_MIN}
onChange={this._getOnChange('nbVms')}
onChange={this._linkState('nbVms')}
type='number'
value={nbVms}
/>
@@ -1425,7 +1406,7 @@ export default class NewVm extends BaseComponent {
{multipleVms && <LineItem>
{map(nameLabels, (nameLabel, index) =>
<Item key={`nameLabel_${index}`}>
<input type='text' className='form-control' value={nameLabel} onChange={this._getOnChange('nameLabels', index)} />
<input type='text' className='form-control' value={nameLabel} onChange={this._linkState(`nameLabels.${index}`)} />
</Item>
)}
</LineItem>}
@@ -1527,7 +1508,7 @@ export default class NewVm extends BaseComponent {
<span style={{margin: 'auto'}}>
<input
checked={fastClone}
onChange={this._getOnChangeCheckbox('fastClone')}
onChange={this._linkState('fastClone')}
type='checkbox'
/>
{' '}

View File

@@ -8,7 +8,7 @@ import info, { error } from 'notification'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import Page from '../../page'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import store from 'store'
import trim from 'lodash/trim'

View File

@@ -40,7 +40,7 @@ export default ({
<Col smallOffset={1} mediumSize={10}>
<Usage total={sumBy(hosts, 'memory.size')}>
{map(hosts, host => <UsageElement
tooltip={host.name_label}
tooltip={`${host.name_label} (${formatSize(host.memory.usage)})`}
key={host.id}
value={host.memory.usage}
href={`#/hosts/${host.id}`}

View File

@@ -5,7 +5,7 @@ import includes from 'lodash/includes'
import intersection from 'lodash/intersection'
import keyBy from 'lodash/keyBy'
import map from 'lodash/map'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React, { Component } from 'react'
import reduce from 'lodash/reduce'
import renderXoItem from 'render-xo-item'

View File

@@ -14,7 +14,7 @@ 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'
import propTypes from 'prop-types-decorator'
import React from 'react'
import remove from 'lodash/remove'
import renderXoItem from 'render-xo-item'

View File

@@ -5,7 +5,7 @@ import includes from 'lodash/includes'
import isEmpty from 'lodash/isEmpty'
import keyBy from 'lodash/keyBy'
import map from 'lodash/map'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import size from 'lodash/size'
import SortedTable from 'sorted-table'

View File

@@ -3,16 +3,16 @@ import ActionButton from 'action-button'
import ActionRowButton from 'action-row-button'
import Component from 'base-component'
import Icon from 'icon'
import map from 'lodash/map'
import React from 'react'
import StateButton from 'state-button'
import Tooltip from 'tooltip'
import { addSubscriptions } from 'utils'
import { alert } from 'modal'
import { alert, confirm } from 'modal'
import { Container } from 'grid'
import { Password as EditablePassword, Text } from 'editable'
import { Password, Toggle } from 'form'
import { injectIntl } from 'react-intl'
import { map, noop } from 'lodash'
import {
addServer,
editServer,
@@ -36,9 +36,31 @@ export default class Servers extends Component {
this.setState({ label: '', host: '', password: '', username: '' })
}
_showError = error => alert(
error.code === 'SESSION_AUTHENTICATION_FAILED' ? _('serverAuthFailed') : error.code || _('serverUnknownError'),
error.message
_showError = server => {
const { code, message } = server.error
if (code === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
return confirm({
title: _('serverSelfSignedCertError'),
body: _('serverSelfSignedCertQuestion')
}).then(
() => editServer(server, { allowUnauthorized: true }).then(
() => connectServer(server)
),
noop
)
}
if (code === 'SESSION_AUTHENTICATION_FAILED') {
return alert(_('serverAuthFailed'), message)
}
return alert(code || _('serverUnknownError'), message)
}
_showInfo = () => alert(
_('serverAllowUnauthorizedCertificates'),
_('serverUnauthorizedCertificatesInfo')
)
render () {
@@ -60,6 +82,21 @@ export default class Servers extends Component {
<td>{_('serverPassword')}</td>
<td>{_('serverStatus')}</td>
<td>{_('serverReadOnly')}</td>
<td>
{_('serverUnauthorizedCertificates')}
{' '}
<Tooltip content={_('serverAllowUnauthorizedCertificates')}>
<a
className='text-info'
onClick={this._showInfo}
>
<Icon
icon='info'
size='lg'
/>
</a>
</Tooltip>
</td>
<td className='text-xs-right'>{_('serverAction')}</td>
</tr>
</thead>
@@ -112,9 +149,8 @@ export default class Servers extends Component {
{server.error &&
<Tooltip content={_('serverConnectionFailed')}>
<a
className='text-danger btn btn-link'
style={{ padding: '0px' }}
onClick={() => this._showError(server.error)}
className='text-danger btn btn-link btn-sm'
onClick={() => this._showError(server)}
>
<Icon
icon='alarm'
@@ -125,15 +161,18 @@ export default class Servers extends Component {
}
</td>
<td><Toggle value={!!server.readOnly} onChange={readOnly => editServer(server, { readOnly })} /></td>
<td>
<Toggle
value={server.allowUnauthorized}
onChange={allowUnauthorized => editServer(server, { allowUnauthorized })}
/>
</td>
<td className='text-xs-right'>
<ActionRowButton
btnStyle='danger'
handler={removeServer}
handlerParam={server}
icon='delete'
style={{
marginRight: '0.5em'
}}
/>
</td>
</tr>

View File

@@ -77,6 +77,12 @@ import TabXosan from './tab-xosan'
)
).groupBy('VDI')
// -----------------------------------------------------------------------------
const getVdisUnmanaged = createGetObjectsOfType('VDI-unmanaged').pick(
createSelector(getSr, sr => sr.VDIs)
).sort()
// -----------------------------------------------------------------------------
const getVdiSnapshots = createGetObjectsOfType('VDI-snapshot').pick(
@@ -136,6 +142,7 @@ import TabXosan from './tab-xosan'
pbds: getPbds(state, props),
logs: getLogs(state, props),
vdis: getVdis(state, props),
vdisUnmanaged: getVdisUnmanaged(state, props),
vdiSnapshots: getVdiSnapshots(state, props),
vdisToVmIds: getVdisToVmIds(state, props),
sr
@@ -216,6 +223,7 @@ export default class Sr extends Component {
'pbds',
'sr',
'vdis',
'vdisUnmanaged',
'vdiSnapshots',
'vdisToVmIds'
]))

View File

@@ -76,17 +76,19 @@ const COLUMNS = [
const FILTERS = {
filterNoSnapshots: 'type:!VDI-snapshot',
filterOnlyBaseCopy: 'type:VDI-unmanaged',
filterOnlyRegularDisks: 'type:!VDI-unmanaged type:!VDI-snapshot',
filterOnlySnapshots: 'type:VDI-snapshot'
}
// ===================================================================
export default ({ vdis, vdiSnapshots, vdisToVmIds }) => (
export default ({ vdis, vdisUnmanaged, vdiSnapshots, vdisToVmIds }) => (
<Container>
<Row>
<Col>
{!isEmpty(vdis)
? <SortedTable collection={vdis.concat(vdiSnapshots)} userData={vdisToVmIds} columns={COLUMNS} filters={FILTERS} />
? <SortedTable collection={vdis.concat(vdiSnapshots, vdisUnmanaged)} userData={vdisToVmIds} columns={COLUMNS} filters={FILTERS} />
: <h4 className='text-xs-center'>{_('srNoVdis')}</h4>
}
</Col>

View File

@@ -12,6 +12,7 @@ import Usage, { UsageElement } from 'usage'
export default ({
sr,
vdis,
vdisUnmanaged,
vdisToVmIds
}) => <Container>
<Row className='text-xs-center'>
@@ -34,6 +35,16 @@ export default ({
<Row>
<Col smallOffset={1} mediumSize={10}>
<Usage total={sr.size}>
{map(vdisUnmanaged, vdi => <UsageElement
highlight
key={vdi.id}
tooltip={<span>
{vdi.name_label}
<br />
{vdisToVmIds[vdi.id] && renderXoItemFromId(vdisToVmIds[vdi.id])}
</span>}
value={vdi.usage}
/>)}
{map(vdis, vdi => <UsageElement
key={vdi.id}
tooltip={<span>

View File

@@ -6,7 +6,7 @@ import Component from 'base-component'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import { Text } from 'editable'
import { alert } from 'modal'

View File

@@ -8,7 +8,7 @@ import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import orderBy from 'lodash/orderBy'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import Upgrade from 'xoa-upgrade'
import { Container, Col, Row } from 'grid'

View File

@@ -7,11 +7,13 @@ import isEmpty from 'lodash/isEmpty'
import React from 'react'
import renderXoItem from 'render-xo-item'
import TabButton from 'tab-button'
import Tooltip from 'tooltip'
import { Toggle } from 'form'
import { Number, Size, Text, XoSelect } from 'editable'
import { Container, Row, Col } from 'grid'
import {
every,
includes,
map,
uniq
} from 'lodash'
@@ -19,6 +21,7 @@ import {
connectStore,
firstDefined,
formatSize,
getCoresPerSocketPossibilities,
normalizeXenToolsStatus,
osFamily
} from 'utils'
@@ -71,11 +74,9 @@ const fullCopy = vm => cloneVm(vm, true)
)
const getAffinityHostPredicate = createSelector(
getAffinityHost,
getSrsContainers,
(affinityHost, containers) =>
host => (!affinityHost || host.id !== affinityHost.id) &&
every(containers, container => container === host.$pool || container === host.id)
containers =>
host => every(containers, container => container === host.$pool || container === host.id)
)
return {
@@ -113,7 +114,67 @@ class AffinityHost extends Component {
}
}
class CoresPerSocket extends Component {
_getCoresPerSocketPossibilities = createSelector(
() => {
const { container } = this.props
if (container !== undefined) {
return container.cpus.cores
}
},
() => this.props.vm.CPUs.number,
getCoresPerSocketPossibilities
)
_selectedValueIsNotInOptions = createSelector(
() => this.props.vm.coresPerSocket,
this._getCoresPerSocketPossibilities,
(selectedCoresPerSocket, options) => selectedCoresPerSocket !== undefined && !includes(options, selectedCoresPerSocket)
)
_onChange = event => editVm(this.props.vm, { coresPerSocket: getEventValue(event) || null })
render () {
const vm = this.props.vm
const selectedCoresPerSocket = vm.coresPerSocket
const options = this._getCoresPerSocketPossibilities()
return <form className='form-inline'>
<select
className='form-control'
onChange={this._onChange}
value={selectedCoresPerSocket || ''}
>
{_('vmChooseCoresPerSocket', message => <option value=''>{message}</option>)}
{this._selectedValueIsNotInOptions() &&
_('vmCoresPerSocketIncorrectValue', message => <option value={selectedCoresPerSocket}> {message}</option>)
}
{map(
options,
coresPerSocket => _(
'vmCoresPerSocket', {
nSockets: vm.CPUs.number / coresPerSocket,
nCores: coresPerSocket
},
message => <option key={coresPerSocket} value={coresPerSocket}>{message}</option>
)
)}
</select>
{' '}
{this._selectedValueIsNotInOptions() &&
<Tooltip content={_('vmCoresPerSocketIncorrectValueSolution')}>
<Icon
icon='error'
size='lg'
/>
</Tooltip>
}
</form>
}
}
export default ({
container,
vm
}) => <Container>
<Row>
@@ -271,7 +332,11 @@ export default ({
onChange={event => editVm(vm, { videoram: +getEventValue(event) })}
value={vm.videoram}
>
{map(XEN_VIDEORAM_VALUES, val => <option value={val}>{formatSize(val * 1048576)}</option>)}
{map(XEN_VIDEORAM_VALUES, val =>
<option key={val} value={val}>
{formatSize(val * 1048576)}
</option>)
}
</select>
</td>
</tr>
@@ -293,6 +358,12 @@ export default ({
}
</td>
</tr>
<tr>
<th>{_('vmCpuTopology')}</th>
<td>
<CoresPerSocket container={container} vm={vm} />
</td>
</tr>
<tr>
<th>{_('vmMemoryLimitsLabel')}</th>
<td>

View File

@@ -9,7 +9,7 @@ import isEmpty from 'lodash/isEmpty'
import IsoDevice from 'iso-device'
import Link from 'link'
import map from 'lodash/map'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import SingleLineRow from 'single-line-row'
import some from 'lodash/some'

View File

@@ -7,11 +7,13 @@ import React from 'react'
import HomeTags from 'home-tags'
import Tooltip from 'tooltip'
import { addTag, editVm, removeTag } from 'xo'
import { createGetVmLastShutdownTime } from 'selectors'
import { BlockLink } from 'link'
import { FormattedRelative } from 'react-intl'
import { Container, Row, Col } from 'grid'
import { Number, Size } from 'editable'
import {
connectStore,
firstDefined,
formatSize,
osFamily
@@ -23,7 +25,11 @@ import {
XvdSparkLines
} from 'xo-sparklines'
export default ({
export default connectStore(() => {
return { lastShutdownTime: createGetVmLastShutdownTime() }
})(
({
lastShutdownTime,
statsOverview,
vm,
vmTotalDiskSpace
@@ -59,7 +65,12 @@ export default ({
? <div>
<p className='text-xs-center'>{_('started', { ago: <FormattedRelative value={vm.startTime * 1000} /> })}</p>
</div>
: <p className='text-xs-center'>{_('vmNotRunning')}</p>
: <p className='text-xs-center'>
{ lastShutdownTime
? _('vmHaltedSince', {ago: <FormattedRelative value={lastShutdownTime * 1000} />})
: _('vmNotRunning')
}
</p>
}
</Col>
<Col mediumSize={3}>
@@ -107,3 +118,4 @@ export default ({
</Row>
}
</Container>
)

View File

@@ -10,7 +10,7 @@ import includes from 'lodash/includes'
import isEmpty from 'lodash/isEmpty'
import keys from 'lodash/keys'
import map from 'lodash/map'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import remove from 'lodash/remove'
import StateButton from 'state-button'

View File

@@ -1,7 +1,7 @@
import _ from 'intl'
import Component from 'base-component'
import Icon from 'icon'
import propTypes from 'prop-types'
import propTypes from 'prop-types-decorator'
import React from 'react'
import {
map

View File

@@ -1825,6 +1825,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2:
create-hash "^1.1.0"
inherits "^2.0.1"
create-react-class@^15.5.2:
version "15.5.3"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.5.3.tgz#fb0f7cae79339e9a179e194ef466efa3923820fe"
dependencies:
fbjs "^0.8.9"
loose-envify "^1.3.1"
object-assign "^4.1.1"
cross-spawn@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
@@ -2881,6 +2889,18 @@ fbjs@^0.8.1, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.7, fbjs@^0.8.8:
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
fbjs@^0.8.9:
version "0.8.12"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -4334,6 +4354,10 @@ js-tokens@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5"
js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
js-yaml@^3.5.1, js-yaml@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
@@ -4862,6 +4886,12 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
dependencies:
js-tokens "^2.0.0"
loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
dependencies:
js-tokens "^3.0.0"
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -5340,6 +5370,10 @@ object-assign@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
object-is@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
@@ -5756,6 +5790,13 @@ promise@^7.0.1, promise@^7.1.1:
dependencies:
asap "~2.0.3"
prop-types@^15.5.8:
version "15.5.10"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
dependencies:
fbjs "^0.8.9"
loose-envify "^1.3.1"
prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@@ -6046,9 +6087,12 @@ react-dropzone@^3.5.0:
dependencies:
attr-accept "^1.0.3"
react-input-autosize@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.1.0.tgz#3fe1ac832387d8abab85f6051ceab1c9e5570853"
react-input-autosize@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.1.4.tgz#cbc45072d4084ddc57806db8e3b34e644b8366ac"
dependencies:
create-react-class "^15.5.2"
prop-types "^15.5.8"
react-intl@^2.0.1:
version "2.2.2"
@@ -6134,12 +6178,14 @@ react-router@^3.0.0:
loose-envify "^1.2.0"
warning "^3.0.0"
react-select@^1.0.0-rc.3:
version "1.0.0-rc.3"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.0.0-rc.3.tgz#94ade090522153067eff09282d0525249ea1d9e3"
react-select@^1.0.0-rc.4:
version "1.0.0-rc.4"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.0.0-rc.4.tgz#f28f3bab18196ff8f32337bb52ed015773c90663"
dependencies:
classnames "^2.2.4"
react-input-autosize "^1.1.0"
create-react-class "^15.5.2"
prop-types "^15.5.8"
react-input-autosize "^1.1.3"
react-shortcuts@^1.3.1:
version "1.3.1"