Compare commits
32 Commits
v5.8.0
...
xo-web/v5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b2650282d | ||
|
|
475be2ee30 | ||
|
|
12e1da4ef2 | ||
|
|
780d072bb7 | ||
|
|
f7e5a5cf92 | ||
|
|
3574c8de5c | ||
|
|
b09ab4d403 | ||
|
|
1997f4af51 | ||
|
|
347cd063a3 | ||
|
|
74a4519a33 | ||
|
|
20acf7cfb2 | ||
|
|
99bc34b2da | ||
|
|
f65b5e3ddd | ||
|
|
dc10492b84 | ||
|
|
6f7c10537b | ||
|
|
7f503cfc21 | ||
|
|
9dbef0c20a | ||
|
|
923166b4e3 | ||
|
|
b420128e40 | ||
|
|
7776a6ce23 | ||
|
|
8db949734a | ||
|
|
bb5bdfb9b2 | ||
|
|
9fac3ecd81 | ||
|
|
8a84cc2627 | ||
|
|
61179ec67d | ||
|
|
59fc5955ba | ||
|
|
e853ba6244 | ||
|
|
fb40ae7264 | ||
|
|
f629047be2 | ||
|
|
278d8adf1b | ||
|
|
87087d55aa | ||
|
|
46e95fe7eb |
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
import propTypes from './prop-types-decorator'
|
||||
|
||||
const CARD_STYLE = {
|
||||
minHeight: '100%'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.checkbox {
|
||||
display: none;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
33
src/common/prop-types-decorator.js
Normal file
33
src/common/prop-types-decorator.js
Normal 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 }
|
||||
@@ -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 }
|
||||
2
src/common/react-novnc.js
vendored
2
src/common/react-novnc.js
vendored
@@ -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))
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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`]}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
SparklinesLine
|
||||
} from 'react-sparklines'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
import propTypes from './prop-types-decorator'
|
||||
import {
|
||||
computeArraysAvg,
|
||||
computeObjectsAvg
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
.usage-element-highlight {
|
||||
background-color: $brand-primary;
|
||||
background-color: $brand-warning;
|
||||
}
|
||||
|
||||
.usage-element-others {
|
||||
|
||||
3
src/xo-app/backup/file-restore/index.css
Normal file
3
src/xo-app/backup/file-restore/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.listRestoreBackupInfos {
|
||||
list-style-type: none;
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -282,6 +282,7 @@ class TimeoutInput extends Component {
|
||||
return <input
|
||||
{...props}
|
||||
onChange={this._onChange}
|
||||
min='1'
|
||||
type='number'
|
||||
value={value == null ? '' : String(value / 1e3)}
|
||||
/>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
/>
|
||||
|
||||
@@ -1317,7 +1298,7 @@ export default class NewVm extends BaseComponent {
|
||||
<Item>
|
||||
<input
|
||||
checked={autoPoweron}
|
||||
onChange={this._getOnChangeCheckbox('autoPoweron')}
|
||||
onChange={this._linkState('autoPoweron')}
|
||||
type='checkbox'
|
||||
/>
|
||||
|
||||
@@ -1331,7 +1312,7 @@ export default class NewVm extends BaseComponent {
|
||||
<Item>
|
||||
<input
|
||||
checked={share}
|
||||
onChange={this._getOnChangeCheckbox('share')}
|
||||
onChange={this._linkState('share')}
|
||||
type='checkbox'
|
||||
/>
|
||||
|
||||
@@ -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'
|
||||
/>
|
||||
{' '}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
]))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
60
yarn.lock
60
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user