Compare commits
22 Commits
xo-web/v5.
...
xo-web/v5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
683d510aa6 | ||
|
|
ebd7e58f61 | ||
|
|
9a498b54ac | ||
|
|
2687f45e6e | ||
|
|
f79a17fcec | ||
|
|
8fd377d1e2 | ||
|
|
fda06fbd29 | ||
|
|
cee4378e6d | ||
|
|
ab6d342886 | ||
|
|
9954c08993 | ||
|
|
3ae80aeab3 | ||
|
|
2a3534f659 | ||
|
|
fc39de0d5a | ||
|
|
64e4b79d41 | ||
|
|
53887da3da | ||
|
|
7c60d68f56 | ||
|
|
2ac1b991b1 | ||
|
|
8257714cdb | ||
|
|
1b8bacbf5a | ||
|
|
1d5b84389d | ||
|
|
f7dcf52977 | ||
|
|
e26dd5147a |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-web",
|
||||
"version": "5.0.2",
|
||||
"version": "5.0.3",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Button } from 'react-bootstrap-4/lib'
|
||||
|
||||
import Component from './base-component'
|
||||
import logError from './log-error'
|
||||
import { autobind, propTypes } from './utils'
|
||||
import propTypes from './prop-types'
|
||||
|
||||
@propTypes({
|
||||
btnStyle: propTypes.string,
|
||||
@@ -28,7 +28,6 @@ export default class ActionButton extends Component {
|
||||
router: React.PropTypes.object
|
||||
}
|
||||
|
||||
@autobind
|
||||
async _execute () {
|
||||
if (this.state.working) {
|
||||
return
|
||||
@@ -66,6 +65,7 @@ export default class ActionButton extends Component {
|
||||
logError(error)
|
||||
}
|
||||
}
|
||||
_execute = ::this._execute
|
||||
|
||||
_eventListener = event => {
|
||||
event.preventDefault()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ActionButton from 'action-button'
|
||||
import React from 'react'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import ActionButton from './action-button'
|
||||
import propTypes from './prop-types'
|
||||
|
||||
const ActionToggle = ({ className, value, ...props }) =>
|
||||
<ActionButton
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import forEach from 'lodash/forEach'
|
||||
import { Component } from 'react'
|
||||
|
||||
import getEventValue from './get-event-value'
|
||||
import invoke from './invoke'
|
||||
import shallowEqual from './shallow-equal'
|
||||
|
||||
@@ -11,6 +12,8 @@ export default class BaseComponent extends Component {
|
||||
// It really should have been done in React.Component!
|
||||
this.state = {}
|
||||
|
||||
this._linkedState = null
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
this.render = invoke(this.render, render => () => {
|
||||
console.log('render', this.constructor.name)
|
||||
@@ -20,6 +23,23 @@ export default class BaseComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// See https://preactjs.com/guide/linked-state
|
||||
linkState (name) {
|
||||
let linkedState = this._linkedState
|
||||
let cb
|
||||
if (!linkedState) {
|
||||
linkedState = this._linkedState = {}
|
||||
} else if ((cb = linkedState[name])) {
|
||||
return cb
|
||||
}
|
||||
|
||||
return (linkedState[name] = event => {
|
||||
this.setState({
|
||||
[name]: getEventValue(event)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
shouldComponentUpdate (newProps, newState) {
|
||||
return !(
|
||||
shallowEqual(this.props, newProps) &&
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
|
||||
const CARD_STYLE = {
|
||||
minHeight: '100%'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import Component from './base-component'
|
||||
import Icon from './icon'
|
||||
import propTypes from './prop-types'
|
||||
|
||||
@propTypes({
|
||||
children: propTypes.any.isRequired,
|
||||
|
||||
@@ -3,7 +3,7 @@ import classNames from 'classnames'
|
||||
import React, { createElement } from 'react'
|
||||
|
||||
import Icon from '../icon'
|
||||
import { propTypes } from '../utils'
|
||||
import propTypes from '../prop-types'
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
|
||||
9
src/common/d3-utils.js
vendored
Normal file
9
src/common/d3-utils.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import forEach from 'lodash/forEach'
|
||||
|
||||
export function setStyles (style) {
|
||||
forEach(style, (value, key) => {
|
||||
this.style(key, value)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
@@ -7,10 +7,11 @@ import React from 'react'
|
||||
|
||||
import _ from './intl'
|
||||
import Component from './base-component'
|
||||
import logError from './log-error'
|
||||
import Icon from './icon'
|
||||
import logError from './log-error'
|
||||
import propTypes from './prop-types'
|
||||
import Tooltip from './tooltip'
|
||||
import { formatSize, propTypes } from './utils'
|
||||
import { formatSize } from './utils'
|
||||
import { SizeInput } from './form'
|
||||
import {
|
||||
SelectHost,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import * as Grid from 'grid'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import * as Grid from './grid'
|
||||
import propTypes from './prop-types'
|
||||
|
||||
export const LabelCol = propTypes({
|
||||
children: propTypes.any.isRequired
|
||||
|
||||
@@ -9,11 +9,10 @@ import {
|
||||
} from 'react-bootstrap-4/lib'
|
||||
|
||||
import Component from '../base-component'
|
||||
import propTypes from '../prop-types'
|
||||
import {
|
||||
autobind,
|
||||
formatSizeRaw,
|
||||
parseSize,
|
||||
propTypes
|
||||
parseSize
|
||||
} from '../utils'
|
||||
|
||||
export Select from './select'
|
||||
@@ -33,16 +32,14 @@ export class Password extends Component {
|
||||
this.refs.field.value = value
|
||||
}
|
||||
|
||||
@autobind
|
||||
_generate () {
|
||||
_generate = () => {
|
||||
this.refs.field.value = randomPassword(8)
|
||||
this.setState({
|
||||
visible: true
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
_toggleVisibility () {
|
||||
_toggleVisibility = () => {
|
||||
this.setState({
|
||||
visible: !this.state.visible
|
||||
})
|
||||
@@ -107,8 +104,7 @@ export class Range extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
_handleChange (event) {
|
||||
_handleChange = event => {
|
||||
const { onChange } = this.props
|
||||
const { value } = event.target
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import find from 'lodash/find'
|
||||
import map from 'lodash/map'
|
||||
import React, { Component } from 'react'
|
||||
import { Select } from 'form'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import propTypes from '../prop-types'
|
||||
|
||||
import Select from './select'
|
||||
|
||||
@propTypes({
|
||||
autoFocus: propTypes.bool,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { Component } from 'react'
|
||||
import ReactSelect from 'react-select'
|
||||
import { propTypes } from 'utils'
|
||||
import {
|
||||
AutoSizer,
|
||||
VirtualScroll
|
||||
} from 'react-virtualized'
|
||||
|
||||
import propTypes from '../prop-types'
|
||||
|
||||
const SELECT_MENU_STYLE = {
|
||||
overflow: 'hidden'
|
||||
}
|
||||
|
||||
18
src/common/get-event-value.js
Normal file
18
src/common/get-event-value.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// If the param is an event, returns the value of it's target,
|
||||
// otherwise returns the param.
|
||||
const getEventValue = event => {
|
||||
let target
|
||||
if (!event || !(target = event.target)) {
|
||||
return event
|
||||
}
|
||||
|
||||
let type
|
||||
return target.nodeName.toLowerCase() === 'input' && (
|
||||
(type = target.type.toLowerCase()) === 'checkbox' ||
|
||||
type === 'radio'
|
||||
)
|
||||
? target.checked
|
||||
: target.value
|
||||
}
|
||||
|
||||
export { getEventValue as default }
|
||||
@@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
|
||||
export const Col = propTypes({
|
||||
className: propTypes.string,
|
||||
|
||||
@@ -57,10 +57,7 @@ var messages = {
|
||||
customJob: 'Custom Job',
|
||||
userPage: 'User',
|
||||
|
||||
// ----- Sign in/out -----
|
||||
usernameLabel: 'Username:',
|
||||
passwordLabel: 'Password:',
|
||||
signInButton: 'Sign in',
|
||||
// ----- Sign out -----
|
||||
signOut: 'Sign out',
|
||||
|
||||
// ----- Home view ------
|
||||
@@ -453,6 +450,7 @@ var messages = {
|
||||
vifStatusConnected: 'Connected',
|
||||
vifStatusDisconnected: 'Disconnected',
|
||||
vifIpAddresses: 'IP addresses',
|
||||
vifMacAutoGenerate: 'Auto-generated if empty',
|
||||
|
||||
// ----- VM snapshot tab -----
|
||||
noSnapshots: 'No snapshots',
|
||||
@@ -678,14 +676,16 @@ var messages = {
|
||||
deleteVmModalMessage: 'Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED',
|
||||
deleteVmsModalMessage: 'Are you sure you want to delete {vms} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED',
|
||||
migrateVmModalTitle: 'Migrate VM',
|
||||
migrateVmAdvancedModalSelectHost: 'Select a destination host:',
|
||||
migrateVmAdvancedModalSelectNetwork: 'Select a migration network:',
|
||||
migrateVmAdvancedModalSelectSrs: 'For each VDI, select an SR:',
|
||||
migrateVmAdvancedModalSelectNetworks: 'For each VIF, select a network:',
|
||||
migrateVmAdvancedModalName: 'Name',
|
||||
migrateVmAdvancedModalSr: 'SR',
|
||||
migrateVmAdvancedModalVif: 'VIF',
|
||||
migrateVmAdvancedModalNetwork: 'Network',
|
||||
migrateVmSelectHost: 'Select a destination host:',
|
||||
migrateVmSelectMigrationNetwork: 'Select a migration network:',
|
||||
migrateVmSelectSrs: 'For each VDI, select an SR:',
|
||||
migrateVmSelectNetworks: 'For each VIF, select a network:',
|
||||
migrateVmsSelectSr: 'Select a destination SR:',
|
||||
migrateVmsSelectNetwork: 'Select a network on which to connect each VIF:',
|
||||
migrateVmName: 'Name',
|
||||
migrateVmSr: 'SR',
|
||||
migrateVmVif: 'VIF',
|
||||
migrateVmNetwork: 'Network',
|
||||
importBackupModalTitle: 'Import a {name} Backup',
|
||||
importBackupModalStart: 'Start VM after restore',
|
||||
importBackupModalSelectBackup: 'Select your backup…',
|
||||
@@ -780,6 +780,8 @@ var messages = {
|
||||
mustUpgrade: 'You need to update your XOA (new version is available)',
|
||||
registerNeeded: 'Your XOA is not registered for updates',
|
||||
updaterError: 'Can\'t fetch update information',
|
||||
promptUpgradeReloadTitle: 'Upgrade successful',
|
||||
promptUpgradeReloadMessage: 'Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?',
|
||||
|
||||
// ----- OS Disclaimer -----
|
||||
disclaimerTitle: 'Xen Orchestra from the sources',
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from 'react'
|
||||
|
||||
import ActionButton from './action-button'
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types'
|
||||
import { connectStore } from './utils'
|
||||
import { SelectVdi } from './select-objects'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
@@ -9,10 +11,6 @@ import {
|
||||
createGetObject,
|
||||
createSelector
|
||||
} from './selectors'
|
||||
import {
|
||||
connectStore,
|
||||
propTypes
|
||||
} from './utils'
|
||||
import {
|
||||
ejectCd,
|
||||
insertCd
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Component } from 'react'
|
||||
import {
|
||||
propTypes
|
||||
} from 'utils'
|
||||
|
||||
import propTypes from '../prop-types'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import _ from 'intl'
|
||||
import React, { Component, cloneElement } from 'react'
|
||||
import map from 'lodash/map'
|
||||
import filter from 'lodash/filter'
|
||||
|
||||
import {
|
||||
autobind,
|
||||
propsEqual,
|
||||
propTypes
|
||||
} from 'utils'
|
||||
import _ from '../intl'
|
||||
import propTypes from '../prop-types'
|
||||
import { propsEqual } from '../utils'
|
||||
|
||||
import GenericInput from './generic-input'
|
||||
|
||||
import {
|
||||
descriptionRender,
|
||||
forceDisplayOptionalAttr
|
||||
@@ -76,15 +72,13 @@ export default class ArrayInput extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
_handleOptionalChange (event) {
|
||||
_handleOptionalChange = event => {
|
||||
this.setState({
|
||||
use: event.target.checked
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
_handleAdd () {
|
||||
_handleAdd = () => {
|
||||
const { children } = this.state
|
||||
this.setState({
|
||||
children: children.concat(this._makeChild(this.props))
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import includes from 'lodash/includes'
|
||||
|
||||
import {
|
||||
EMPTY_OBJECT,
|
||||
propTypes
|
||||
} from 'utils'
|
||||
import propTypes from '../prop-types'
|
||||
import { EMPTY_OBJECT } from '../utils'
|
||||
|
||||
import ArrayInput from './array-input'
|
||||
import BooleanInput from './boolean-input'
|
||||
@@ -13,55 +10,18 @@ import IntegerInput from './integer-input'
|
||||
import NumberInput from './number-input'
|
||||
import ObjectInput from './object-input'
|
||||
import StringInput from './string-input'
|
||||
import XoHighLevelObjectInput from './xo-highlevel-object-input'
|
||||
import XoHostInput from './xo-host-input'
|
||||
import XoPoolInput from './xo-pool-input'
|
||||
import XoRemoteInput from './xo-remote-input'
|
||||
import XoRoleInput from './xo-role-input'
|
||||
import XoSrInput from './xo-sr-input'
|
||||
import XoSubjectInput from './xo-subject-input'
|
||||
import XoVmInput from './xo-vm-input'
|
||||
|
||||
import { getType } from './helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const getType = (schema, attr = 'type') => {
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
|
||||
const type = schema[attr]
|
||||
|
||||
if (Array.isArray(type)) {
|
||||
if (includes(type, 'integer')) {
|
||||
return 'integer'
|
||||
}
|
||||
if (includes(type, 'number')) {
|
||||
return 'number'
|
||||
}
|
||||
|
||||
return 'string'
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
const getXoType = schema => getType(schema, 'xo:type')
|
||||
|
||||
const InputByType = {
|
||||
array: ArrayInput,
|
||||
boolean: BooleanInput,
|
||||
host: XoHostInput,
|
||||
integer: IntegerInput,
|
||||
number: NumberInput,
|
||||
object: ObjectInput,
|
||||
pool: XoPoolInput,
|
||||
remote: XoRemoteInput,
|
||||
sr: XoSrInput,
|
||||
string: StringInput,
|
||||
vm: XoVmInput,
|
||||
xoobject: XoHighLevelObjectInput,
|
||||
role: XoRoleInput,
|
||||
subject: XoSubjectInput
|
||||
string: StringInput
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
@@ -104,14 +64,13 @@ export default class GenericInput extends Component {
|
||||
return <EnumInput {...props} />
|
||||
}
|
||||
|
||||
// $type = Job Creation Schemas && Old XO plugins.
|
||||
const type = getXoType(schema) || getType(schema, '$type') || getType(schema)
|
||||
const type = getType(schema)
|
||||
const Input = uiSchema.widget || InputByType[type.toLowerCase()]
|
||||
|
||||
if (!Input) {
|
||||
throw new Error(`Unsupported type: ${type}.`)
|
||||
}
|
||||
|
||||
return <Input {...props} />
|
||||
return <Input {...props} {...uiSchema.config} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,43 @@
|
||||
import React from 'react'
|
||||
import includes from 'lodash/includes'
|
||||
import isArray from 'lodash/isArray'
|
||||
import marked from 'marked'
|
||||
|
||||
import { Col, Row } from 'grid'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const getType = schema => {
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
|
||||
const type = schema.type
|
||||
|
||||
if (isArray(type)) {
|
||||
if (includes(type, 'integer')) {
|
||||
return 'integer'
|
||||
}
|
||||
if (includes(type, 'number')) {
|
||||
return 'number'
|
||||
}
|
||||
|
||||
return 'string'
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
export const getXoType = schema => {
|
||||
const type = schema && (schema['xo:type'] || schema.$type)
|
||||
|
||||
if (type) {
|
||||
return type.toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const descriptionRender = description =>
|
||||
<span className='text-muted' dangerouslySetInnerHTML={{__html: marked(description || '')}} />
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
import GenericInput from './generic-input'
|
||||
export default GenericInput
|
||||
export default from './generic-input'
|
||||
|
||||
@@ -4,11 +4,8 @@ import forEach from 'lodash/forEach'
|
||||
import includes from 'lodash/includes'
|
||||
import map from 'lodash/map'
|
||||
|
||||
import {
|
||||
autobind,
|
||||
propsEqual,
|
||||
propTypes
|
||||
} from 'utils'
|
||||
import propTypes from '../prop-types'
|
||||
import { propsEqual } from '../utils'
|
||||
|
||||
import GenericInput from './generic-input'
|
||||
|
||||
@@ -81,8 +78,7 @@ export default class ObjectInput extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
_handleOptionalChange (event) {
|
||||
_handleOptionalChange = event => {
|
||||
const { checked } = event.target
|
||||
|
||||
this.setState({
|
||||
@@ -98,6 +94,7 @@ export default class ObjectInput extends Component {
|
||||
defaultValue = {}
|
||||
} = props
|
||||
const obj = {}
|
||||
const { properties } = uiSchema
|
||||
|
||||
forEach(schema.properties, (childSchema, key) => {
|
||||
obj[key] = (
|
||||
@@ -108,7 +105,7 @@ export default class ObjectInput extends Component {
|
||||
label={childSchema.title || key}
|
||||
required={includes(schema.required, key)}
|
||||
schema={childSchema}
|
||||
uiSchema={uiSchema.properties}
|
||||
uiSchema={properties && properties[key]}
|
||||
defaultValue={defaultValue[key]}
|
||||
/>
|
||||
</ObjectItem>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import React from 'react'
|
||||
|
||||
import AbstractInput from './abstract-input'
|
||||
import propTypes from '../prop-types'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@propTypes({
|
||||
password: propTypes.bool
|
||||
})
|
||||
export default class StringInput extends AbstractInput {
|
||||
render () {
|
||||
const { props } = this
|
||||
@@ -20,7 +24,7 @@ export default class StringInput extends AbstractInput {
|
||||
placeholder={props.placeholder}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
type={props.schema['xo:type'] === 'password' ? 'password' : 'text'}
|
||||
type={props.password ? 'password' : 'text'}
|
||||
/>
|
||||
</PrimitiveInputWrapper>
|
||||
)
|
||||
|
||||
59
src/common/link.js
Normal file
59
src/common/link.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import Link from 'react-router/lib/Link'
|
||||
import React from 'react'
|
||||
import { routerShape } from 'react-router/lib/PropTypes'
|
||||
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export { Link as default }
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const _IGNORED_TAGNAMES = {
|
||||
A: true,
|
||||
BUTTON: true,
|
||||
INPUT: true,
|
||||
SELECT: true
|
||||
}
|
||||
|
||||
@propTypes({
|
||||
tagName: propTypes.string
|
||||
})
|
||||
export class BlockLink extends Component {
|
||||
static contextTypes = {
|
||||
router: routerShape
|
||||
}
|
||||
|
||||
_style = { cursor: 'pointer' }
|
||||
_onClickCapture = event => {
|
||||
const { currentTarget } = event
|
||||
let element = event.target
|
||||
while (element !== currentTarget) {
|
||||
if (_IGNORED_TAGNAMES[element.tagName]) {
|
||||
return
|
||||
}
|
||||
element = element.parentNode
|
||||
}
|
||||
event.stopPropagation()
|
||||
if (event.ctrlKey || event.button === 1) {
|
||||
window.open(this.context.router.createHref(this.props.to))
|
||||
} else {
|
||||
this.context.router.push(this.props.to)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, tagName = 'div' } = this.props
|
||||
const Component = tagName
|
||||
return (
|
||||
<Component
|
||||
style={this._style}
|
||||
onClickCapture={this._onClickCapture}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import isArray from 'lodash/isArray'
|
||||
import isString from 'lodash/isString'
|
||||
import React, { Component, cloneElement } from 'react'
|
||||
import { Button, Modal as ReactModal } from 'react-bootstrap-4/lib'
|
||||
import { propTypes } from './utils'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
|
||||
let instance
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import classNames from 'classnames'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import React from 'react'
|
||||
|
||||
import Link from './link'
|
||||
|
||||
export const NavLink = ({ children, to }) => (
|
||||
<li className='nav-item' role='tab'>
|
||||
<Link className='nav-link' activeClassName='active' to={to}>
|
||||
|
||||
3
src/common/react-novnc.js
vendored
3
src/common/react-novnc.js
vendored
@@ -1,6 +1,5 @@
|
||||
import React, { Component } from 'react'
|
||||
import { createBackoff } from 'jsonrpc-websocket-client'
|
||||
import { propTypes } from 'utils'
|
||||
import { RFB } from 'novnc-node'
|
||||
import {
|
||||
format as formatUrl,
|
||||
@@ -8,6 +7,8 @@ import {
|
||||
resolve as resolveUrl
|
||||
} from 'url'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
|
||||
const parseRelativeUrl = url => parseUrl(resolveUrl(String(window.location), url))
|
||||
|
||||
const PROTOCOL_ALIASES = {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Icon from 'icon'
|
||||
import React, { Component } from 'react'
|
||||
import { createGetObject } from 'selectors'
|
||||
import { isSrWritable } from 'xo'
|
||||
|
||||
import Icon from './icon'
|
||||
import propTypes from './prop-types'
|
||||
import { createGetObject } from './selectors'
|
||||
import { isSrWritable } from './xo'
|
||||
import {
|
||||
connectStore,
|
||||
formatSize,
|
||||
propTypes
|
||||
} from 'utils'
|
||||
formatSize
|
||||
} from './utils'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import Component from 'base-component'
|
||||
import React from 'react'
|
||||
import _ from 'intl'
|
||||
import forEach from 'lodash/forEach'
|
||||
import includes from 'lodash/includes'
|
||||
import join from 'lodash/join'
|
||||
import later from 'later'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import sortedIndex from 'lodash/sortedIndex'
|
||||
import { Range } from 'form'
|
||||
import { FormattedTime } from 'react-intl'
|
||||
|
||||
import { Col, Row } from 'grid'
|
||||
import {
|
||||
Panel,
|
||||
Tab,
|
||||
Tabs
|
||||
} from 'react-bootstrap-4/lib'
|
||||
import {
|
||||
propTypes
|
||||
} from 'utils'
|
||||
|
||||
import _ from './intl'
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types'
|
||||
import { Col, Row } from './grid'
|
||||
import { Range } from './form'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import Component from 'base-component'
|
||||
import React from 'react'
|
||||
import _ from 'intl'
|
||||
import assign from 'lodash/assign'
|
||||
import classNames from 'classnames'
|
||||
import filter from 'lodash/filter'
|
||||
@@ -9,31 +7,31 @@ import groupBy from 'lodash/groupBy'
|
||||
import keyBy from 'lodash/keyBy'
|
||||
import keys from 'lodash/keys'
|
||||
import map from 'lodash/map'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
import { parse as parseRemote } from 'xo-remote-parser'
|
||||
import { Select } from 'form'
|
||||
|
||||
import _ from './intl'
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types'
|
||||
import renderXoItem from './render-xo-item'
|
||||
import { Select } from './form'
|
||||
import {
|
||||
createFilter,
|
||||
createGetObjectsOfType,
|
||||
createGetTags,
|
||||
createSelector
|
||||
} from 'selectors'
|
||||
|
||||
} from './selectors'
|
||||
import {
|
||||
connectStore,
|
||||
mapPlus,
|
||||
propTypes
|
||||
} from 'utils'
|
||||
|
||||
mapPlus
|
||||
} from './utils'
|
||||
import {
|
||||
isSrWritable,
|
||||
subscribeGroups,
|
||||
subscribeRemotes,
|
||||
subscribeRoles,
|
||||
subscribeUsers
|
||||
} from 'xo'
|
||||
} from './xo'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ import size from 'lodash/size'
|
||||
import slice from 'lodash/slice'
|
||||
import { createSelector as create } from 'reselect'
|
||||
|
||||
import invoke from './invoke'
|
||||
import shallowEqual from './shallow-equal'
|
||||
import { EMPTY_ARRAY, EMPTY_OBJECT, invoke } from './utils'
|
||||
import { EMPTY_ARRAY, EMPTY_OBJECT } from './utils'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { cloneElement } from 'react'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
|
||||
const SINGLE_LINE_STYLE = { display: 'flex' }
|
||||
const COL_STYLE = { margin: 'auto' }
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
import ceil from 'lodash/ceil'
|
||||
import debounce from 'lodash/debounce'
|
||||
import map from 'lodash/map'
|
||||
import { Pagination } from 'react-bootstrap-4/lib'
|
||||
import { Portal } from 'react-overlays'
|
||||
import { Container, Col } from 'grid'
|
||||
import { create as createMatcher } from 'complex-matcher'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import Component from '../base-component'
|
||||
import Icon from '../icon'
|
||||
import propTypes from '../prop-types'
|
||||
import SingleLineRow from '../single-line-row'
|
||||
import { Container, Col } from '../grid'
|
||||
import { create as createMatcher } from '../complex-matcher'
|
||||
import {
|
||||
createFilter,
|
||||
createPager,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import _ from './intl'
|
||||
import ActionButton from './action-button'
|
||||
import Icon from './icon'
|
||||
import Link from './link'
|
||||
|
||||
const STYLE = {
|
||||
marginBottom: '1em',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
import Icon from 'icon'
|
||||
import map from 'lodash/map'
|
||||
|
||||
import Component from './base-component'
|
||||
import { propTypes } from './utils'
|
||||
import Icon from './icon'
|
||||
import propTypes from './prop-types'
|
||||
|
||||
@propTypes({
|
||||
labels: propTypes.arrayOf(React.PropTypes.string).isRequired,
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as actions from 'store/actions'
|
||||
import every from 'lodash/every'
|
||||
import forEach from 'lodash/forEach'
|
||||
import humanFormat from 'human-format'
|
||||
import includes from 'lodash/includes'
|
||||
import isArray from 'lodash/isArray'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import isFunction from 'lodash/isFunction'
|
||||
@@ -10,8 +9,7 @@ import isPlainObject from 'lodash/isPlainObject'
|
||||
import isString from 'lodash/isString'
|
||||
import map from 'lodash/map'
|
||||
import mapValues from 'lodash/mapValues'
|
||||
import propTypes from 'prop-types'
|
||||
import React, { cloneElement } from 'react'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import BaseComponent from './base-component'
|
||||
@@ -20,8 +18,6 @@ import invoke from './invoke'
|
||||
export const EMPTY_ARRAY = Object.freeze([ ])
|
||||
export const EMPTY_OBJECT = Object.freeze({ })
|
||||
|
||||
export { propTypes }
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const ensureArray = (value) => {
|
||||
@@ -78,92 +74,6 @@ export const addSubscriptions = subscriptions => Component => {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const _bind = (fn, thisArg) => function bound () {
|
||||
return fn.apply(thisArg, arguments)
|
||||
}
|
||||
const _defineProperty = Object.defineProperty
|
||||
|
||||
export const autobind = (target, key, {
|
||||
configurable,
|
||||
enumerable,
|
||||
value: fn,
|
||||
writable
|
||||
}) => ({
|
||||
configurable,
|
||||
enumerable,
|
||||
|
||||
get () {
|
||||
if (this === target) {
|
||||
return fn
|
||||
}
|
||||
|
||||
const bound = _bind(fn, this)
|
||||
|
||||
_defineProperty(this, key, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: bound,
|
||||
writable: true
|
||||
})
|
||||
|
||||
return bound
|
||||
},
|
||||
set (newValue) {
|
||||
// Cannot use assignment because it will call the setter on
|
||||
// the prototype.
|
||||
_defineProperty(this, key, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: newValue,
|
||||
writable: true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@propTypes({
|
||||
tagName: propTypes.string
|
||||
})
|
||||
export class BlockLink extends React.Component {
|
||||
static contextTypes = {
|
||||
router: React.PropTypes.object
|
||||
}
|
||||
|
||||
_style = { cursor: 'pointer' }
|
||||
_onClickCapture = event => {
|
||||
const { currentTarget } = event
|
||||
let element = event.target
|
||||
while (element !== currentTarget) {
|
||||
if (includes(['A', 'INPUT', 'BUTTON', 'SELECT'], element.tagName)) {
|
||||
return
|
||||
}
|
||||
element = element.parentNode
|
||||
}
|
||||
event.stopPropagation()
|
||||
if (event.ctrlKey || event.button === 1) {
|
||||
window.open(this.context.router.createHref(this.props.to))
|
||||
} else {
|
||||
this.context.router.push(this.props.to)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, tagName = 'div' } = this.props
|
||||
const Component = tagName
|
||||
return (
|
||||
<Component
|
||||
style={this._style}
|
||||
onClickCapture={this._onClickCapture}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const checkPropsState = (propsNames, stateNames) => Component => {
|
||||
const nProps = propsNames && propsNames.length
|
||||
const nState = stateNames && stateNames.length
|
||||
@@ -255,18 +165,6 @@ export const connectStore = (mapStateToProps, opts = {}) => {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Simple matcher to use in object filtering.
|
||||
export const createSimpleMatcher = (pattern, valueGetter) => {
|
||||
if (!pattern) {
|
||||
return
|
||||
}
|
||||
|
||||
pattern = pattern.toLowerCase()
|
||||
return item => valueGetter(item).toLowerCase().indexOf(pattern) !== -1
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export { default as Debug } from './debug'
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -344,24 +242,6 @@ export const osFamily = invoke({
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Experimental!
|
||||
//
|
||||
// ```js
|
||||
// <If cond={user}>
|
||||
// <p>user.name</p>
|
||||
// <p>user.email</p>
|
||||
// </If>
|
||||
// ```
|
||||
export const If = ({ cond, children }) => cond && children
|
||||
? map(children, (child, key) => cloneElement(child, { key }))
|
||||
: null
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export { invoke }
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const formatSize = bytes => humanFormat(bytes, { scale: 'binary', unit: 'B' })
|
||||
|
||||
export const formatSizeRaw = bytes => humanFormat.raw(bytes, { scale: 'binary', unit: 'B' })
|
||||
@@ -470,11 +350,3 @@ export function rethrow (cb) {
|
||||
Promise.resolve(cb(error)).then(() => { throw error })
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// If param is an event: returns the value associated to it
|
||||
// Otherwise: returns param
|
||||
export function getEventValue (param) {
|
||||
return param && param.target ? param.target.value : param
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import _ from 'intl'
|
||||
import classNames from 'classnames'
|
||||
import every from 'lodash/every'
|
||||
import Icon from 'icon'
|
||||
import map from 'lodash/map'
|
||||
import { propTypes } from 'utils'
|
||||
import React, { Component, cloneElement } from 'react'
|
||||
|
||||
import _ from '../intl'
|
||||
import Icon from '../icon'
|
||||
import propTypes from '../prop-types'
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
const Wizard = ({ children }) => {
|
||||
|
||||
67
src/common/xo-json-schema-input/index.js
Normal file
67
src/common/xo-json-schema-input/index.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import forEach from 'lodash/forEach'
|
||||
|
||||
import XoHighLevelObjectInput from './xo-highlevel-object-input'
|
||||
import XoHostInput from './xo-host-input'
|
||||
import XoPoolInput from './xo-pool-input'
|
||||
import XoRemoteInput from './xo-remote-input'
|
||||
import XoRoleInput from './xo-role-input'
|
||||
import XoSrInput from './xo-sr-input'
|
||||
import XoSubjectInput from './xo-subject-input'
|
||||
import XoVmInput from './xo-vm-input'
|
||||
import { getType, getXoType } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const XO_TYPE_TO_COMPONENT = {
|
||||
host: XoHostInput,
|
||||
xoobject: XoHighLevelObjectInput,
|
||||
pool: XoPoolInput,
|
||||
remote: XoRemoteInput,
|
||||
role: XoRoleInput,
|
||||
sr: XoSrInput,
|
||||
subject: XoSubjectInput,
|
||||
vm: XoVmInput
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const buildStringInput = (uiSchema, key, xoType) => {
|
||||
if (key === 'password') {
|
||||
uiSchema.config = { password: true }
|
||||
}
|
||||
|
||||
uiSchema.widget = XO_TYPE_TO_COMPONENT[xoType]
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const _generateUiSchema = (schema, uiSchema, key) => {
|
||||
const type = getType(schema)
|
||||
|
||||
if (type === 'object') {
|
||||
const properties = uiSchema.properties = {}
|
||||
|
||||
forEach(schema.properties, (schema, key) => {
|
||||
const subUiSchema = properties[key] = {}
|
||||
_generateUiSchema(schema, subUiSchema, key)
|
||||
})
|
||||
} else if (type === 'array') {
|
||||
const widget = XO_TYPE_TO_COMPONENT[getXoType(schema.items)]
|
||||
|
||||
if (widget) {
|
||||
uiSchema.widget = widget
|
||||
uiSchema.config = { multi: true }
|
||||
} else {
|
||||
const subUiSchema = uiSchema.items = {}
|
||||
_generateUiSchema(schema.items, subUiSchema, key)
|
||||
}
|
||||
} else if (type === 'string') {
|
||||
buildStringInput(uiSchema, key, getXoType(schema))
|
||||
}
|
||||
}
|
||||
|
||||
export const generateUiSchema = schema => {
|
||||
const uiSchema = {}
|
||||
_generateUiSchema(schema, uiSchema, '')
|
||||
return uiSchema
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import map from 'lodash/map'
|
||||
import AbstractInput from './abstract-input'
|
||||
import AbstractInput from '../json-schema-input/abstract-input'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectHighLevelObject } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class HighLevelObjectInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectHighLevelObject
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectHost } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class HostInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectHost
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectPool } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class PoolInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectPool
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectRemote } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class RemoteInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectRemote
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectRole } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class RoleInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectRole
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectSr } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class SrInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectSr
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectSubject } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class SubjectInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectSubject
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { SelectVm } from 'select-objects'
|
||||
|
||||
import XoAbstractInput from './xo-abstract-input'
|
||||
import { PrimitiveInputWrapper } from './helpers'
|
||||
import { PrimitiveInputWrapper } from '../json-schema-input/helpers'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class VmInput extends XoAbstractInput {
|
||||
<PrimitiveInputWrapper {...props}>
|
||||
<SelectVm
|
||||
disabled={props.disabled}
|
||||
multi={props.schema.type === 'array'}
|
||||
multi={props.multi}
|
||||
onChange={props.onChange}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
@@ -3,10 +3,9 @@ import ChartistLegend from 'chartist-plugin-legend'
|
||||
import ChartistTooltip from 'chartist-plugin-tooltip'
|
||||
import React from 'react'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import {
|
||||
formatSize,
|
||||
propTypes
|
||||
} from 'utils'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
import { formatSize } from './utils'
|
||||
|
||||
// Number of labels on axis X.
|
||||
const N_LABELS_X = 5
|
||||
@@ -282,7 +281,7 @@ export const LoadLineChart = injectIntl(propTypes({
|
||||
nValues: length,
|
||||
endTimestamp: data.endTimestamp,
|
||||
interval: data.interval,
|
||||
valueTransform: value => `${value}`
|
||||
valueTransform: value => `${value.toPrecision(3)}`
|
||||
}),
|
||||
...options
|
||||
}}
|
||||
|
||||
294
src/common/xo-parallel-chart.js
Normal file
294
src/common/xo-parallel-chart.js
Normal file
@@ -0,0 +1,294 @@
|
||||
import * as d3 from 'd3'
|
||||
import React from 'react'
|
||||
import forEach from 'lodash/forEach'
|
||||
import keys from 'lodash/keys'
|
||||
import map from 'lodash/map'
|
||||
import times from 'lodash/times'
|
||||
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types'
|
||||
import { setStyles } from './d3-utils'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const CHART_WIDTH = 2000
|
||||
const CHART_HEIGHT = 800
|
||||
|
||||
const TICK_SIZE = CHART_WIDTH / 100
|
||||
|
||||
const N_TICKS = 4
|
||||
|
||||
const TOOLTIP_PADDING = 10
|
||||
|
||||
const DEFAULT_STROKE_WIDTH_FACTOR = 500
|
||||
const HIGHLIGHT_STROKE_WIDTH_FACTOR = 200
|
||||
|
||||
const BRUSH_SELECTION_WIDTH = 2 * CHART_WIDTH / 100
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const SVG_STYLE = {
|
||||
display: 'block',
|
||||
height: '100%',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
width: '100%'
|
||||
}
|
||||
|
||||
const SVG_CONTAINER_STYLE = {
|
||||
'padding-bottom': '50%',
|
||||
'vertical-align': 'middle',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
width: '100%'
|
||||
}
|
||||
|
||||
const SVG_CONTENT = {
|
||||
'font-size': `${CHART_WIDTH / 100}px`
|
||||
}
|
||||
|
||||
const COLUMN_TITLE_STYLE = {
|
||||
'font-size': '100%',
|
||||
'font-weight': 'bold',
|
||||
'text-anchor': 'middle'
|
||||
}
|
||||
|
||||
const COLUMN_VALUES_STYLE = {
|
||||
'font-size': '100%'
|
||||
}
|
||||
|
||||
const LINES_CONTAINER_STYLE = {
|
||||
'stroke-opacity': 0.5,
|
||||
'stroke-width': CHART_WIDTH / DEFAULT_STROKE_WIDTH_FACTOR,
|
||||
fill: 'none',
|
||||
stroke: 'red'
|
||||
}
|
||||
|
||||
const TOOLTIP_STYLE = {
|
||||
'fill': 'white',
|
||||
'font-size': '125%',
|
||||
'font-weight': 'bold'
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@propTypes({
|
||||
dataSet: propTypes.arrayOf(
|
||||
propTypes.shape({
|
||||
data: propTypes.object.isRequired,
|
||||
label: propTypes.string.isRequired,
|
||||
objectId: propTypes.string.isRequired
|
||||
})
|
||||
).isRequired,
|
||||
labels: propTypes.object.isRequired,
|
||||
renderers: propTypes.object
|
||||
})
|
||||
export default class XoParallelChart extends Component {
|
||||
_line = d3.line()
|
||||
|
||||
_color = d3.scaleOrdinal(d3.schemeCategory10)
|
||||
|
||||
_handleBrush = () => {
|
||||
// 1. Get selected brushes.
|
||||
const brushes = []
|
||||
this._svg.selectAll('.chartColumn')
|
||||
.selectAll('.brush')
|
||||
.each((_1, _2, [ brush ]) => {
|
||||
if (d3.brushSelection(brush) != null) {
|
||||
brushes.push(brush)
|
||||
}
|
||||
})
|
||||
|
||||
// 2. Change stroke of selected lines.
|
||||
const lines = this._svg.select('.linesContainer')
|
||||
.selectAll('path')
|
||||
|
||||
lines.each((elem, lineId, lines) => {
|
||||
const { data } = elem
|
||||
|
||||
const res = brushes.every(brush => {
|
||||
const selection = d3.brushSelection(brush)
|
||||
const columnId = brush.__data__
|
||||
const { invert } = this._y[columnId] // Range to domain.
|
||||
|
||||
return invert(selection[1]) <= data[columnId] && data[columnId] <= invert(selection[0])
|
||||
})
|
||||
|
||||
const line = d3.select(lines[lineId])
|
||||
|
||||
if (!res) {
|
||||
line.attr('stroke-opacity', 1.0).attr('stroke', '#e6e6e6')
|
||||
} else {
|
||||
line.attr('stroke-opacity', 0.5).attr('stroke', this._color(elem.label))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_brush = d3.brushY()
|
||||
// Brush area: (x0, y0), (x1, y1)
|
||||
.extent([[-BRUSH_SELECTION_WIDTH / 2, 0], [BRUSH_SELECTION_WIDTH / 2, CHART_HEIGHT]])
|
||||
.on('brush', this._handleBrush)
|
||||
.on('end', this._handleBrush)
|
||||
|
||||
_highlight (elem, position) {
|
||||
const svg = this._svg
|
||||
|
||||
// Reset tooltip.
|
||||
svg
|
||||
.selectAll('.objectTooltip')
|
||||
.remove()
|
||||
|
||||
// Reset all lines.
|
||||
svg
|
||||
.selectAll('.chartLine')
|
||||
.attr('stroke-width', CHART_WIDTH / DEFAULT_STROKE_WIDTH_FACTOR)
|
||||
|
||||
if (!position) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set stroke on selected line.
|
||||
svg
|
||||
.select('#chartLine-' + elem.objectId)
|
||||
.attr('stroke-width', CHART_WIDTH / HIGHLIGHT_STROKE_WIDTH_FACTOR)
|
||||
|
||||
const { label } = elem
|
||||
|
||||
const tooltip = svg.append('g')
|
||||
.attr('class', 'objectTooltip')
|
||||
|
||||
const bbox = tooltip.append('text')
|
||||
.text(label)
|
||||
.attr('x', position[0])
|
||||
.attr('y', position[1] - 30)
|
||||
::setStyles(TOOLTIP_STYLE)
|
||||
.node().getBBox()
|
||||
|
||||
tooltip.insert('rect', '*')
|
||||
.attr('x', bbox.x - TOOLTIP_PADDING)
|
||||
.attr('y', bbox.y - TOOLTIP_PADDING)
|
||||
.attr('width', bbox.width + TOOLTIP_PADDING * 2)
|
||||
.attr('height', bbox.height + TOOLTIP_PADDING * 2)
|
||||
.style('fill', this._color(label))
|
||||
}
|
||||
|
||||
_handleMouseOver = (elem, pathId, paths) => {
|
||||
this._highlight(elem, d3.mouse(paths[pathId]))
|
||||
}
|
||||
|
||||
_handleMouseOut = (elem) => {
|
||||
this._highlight()
|
||||
}
|
||||
|
||||
_draw (props = this.props) {
|
||||
const svg = this._svg
|
||||
const { labels, dataSet } = props
|
||||
|
||||
const columnsIds = keys(labels)
|
||||
const spacing = (CHART_WIDTH - 200) / (columnsIds.length - 1)
|
||||
const x = d3.scaleOrdinal()
|
||||
.domain(columnsIds).range(
|
||||
times(columnsIds.length, n => n * spacing)
|
||||
)
|
||||
|
||||
// 1. Remove old nodes.
|
||||
svg
|
||||
.selectAll('.chartColumn')
|
||||
.remove()
|
||||
|
||||
svg
|
||||
.selectAll('.linesContainer')
|
||||
.remove()
|
||||
|
||||
// 2. Build Ys.
|
||||
const y = this._y = {}
|
||||
forEach(columnsIds, (columnId, index) => {
|
||||
const max = d3.max(dataSet, elem => elem.data[columnId])
|
||||
|
||||
y[columnId] = d3.scaleLinear()
|
||||
.domain([0, max])
|
||||
.range([CHART_HEIGHT, 0])
|
||||
})
|
||||
|
||||
// 3. Build columns.
|
||||
const columns = svg.selectAll('.chartColumn')
|
||||
.data(columnsIds)
|
||||
.enter().append('g')
|
||||
.attr('class', 'chartColumn')
|
||||
.attr('transform', d => `translate(${x(d)})`)
|
||||
|
||||
// 4. Draw titles.
|
||||
columns.append('text')
|
||||
.text(columnId => labels[columnId])
|
||||
.attr('y', -50)
|
||||
::setStyles(COLUMN_TITLE_STYLE)
|
||||
|
||||
// 5. Draw axis.
|
||||
columns.append('g')
|
||||
.each((columnId, axisId, axes) => {
|
||||
const axis = d3.axisLeft()
|
||||
.ticks(N_TICKS, ',f')
|
||||
.tickSize(TICK_SIZE)
|
||||
.scale(y[columnId])
|
||||
|
||||
const renderer = props.renderers[columnId]
|
||||
|
||||
// Add optional renderer like formatSize.
|
||||
if (renderer) {
|
||||
axis.tickFormat(renderer)
|
||||
}
|
||||
|
||||
d3.select(axes[axisId]).call(axis)
|
||||
})
|
||||
::setStyles(COLUMN_VALUES_STYLE)
|
||||
|
||||
// 6. Draw lines.
|
||||
const path = elem => this._line(map(columnsIds.map(
|
||||
columnId => [x(columnId), y[columnId](elem.data[columnId])]
|
||||
)))
|
||||
svg.append('g')
|
||||
.attr('class', 'linesContainer')
|
||||
::setStyles(LINES_CONTAINER_STYLE)
|
||||
.selectAll('path')
|
||||
.data(dataSet)
|
||||
.enter().append('path')
|
||||
.attr('d', path)
|
||||
.attr('class', 'chartLine')
|
||||
.attr('id', elem => 'chartLine-' + elem.objectId)
|
||||
.attr('stroke', elem => this._color(elem.label))
|
||||
.attr('shape-rendering', 'optimizeQuality')
|
||||
.attr('stroke-linecap', 'round')
|
||||
.attr('stroke-linejoin', 'round')
|
||||
.on('mouseover', this._handleMouseOver)
|
||||
.on('mouseout', this._handleMouseOut)
|
||||
|
||||
// 7. Brushes.
|
||||
columns.append('g')
|
||||
.attr('class', 'brush')
|
||||
.each((_, brushId, brushes) => { d3.select(brushes[brushId]).call(this._brush) })
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._svg = d3.select(this.refs.chart)
|
||||
.append('div')
|
||||
::setStyles(SVG_CONTAINER_STYLE)
|
||||
.append('svg')
|
||||
::setStyles(SVG_STYLE)
|
||||
.attr('preserveAspectRatio', 'xMinYMin meet')
|
||||
.attr('viewBox', `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${100}, ${100})`)
|
||||
::setStyles(SVG_CONTENT)
|
||||
|
||||
this._draw()
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this._draw(nextProps)
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div ref='chart' />
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
SparklinesLine,
|
||||
SparklinesSpots
|
||||
} from 'react-sparklines'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
|
||||
const STYLE = {}
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import React from 'react'
|
||||
import _ from 'intl'
|
||||
import * as d3 from 'd3'
|
||||
import forEach from 'lodash/forEach'
|
||||
import map from 'lodash/map'
|
||||
import { Toggle } from 'form'
|
||||
|
||||
import Component from '../base-component'
|
||||
import _ from '../intl'
|
||||
import propTypes from '../prop-types'
|
||||
import { Toggle } from '../form'
|
||||
import { setStyles } from '../d3-utils'
|
||||
import {
|
||||
createGetObject,
|
||||
createSelector
|
||||
} from '../selectors'
|
||||
import {
|
||||
connectStore,
|
||||
propsEqual,
|
||||
propTypes
|
||||
propsEqual
|
||||
} from '../utils'
|
||||
|
||||
import styles from './index.css'
|
||||
@@ -63,16 +64,6 @@ const HORIZON_AREA_PATH_STYLE = {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
function setStyles (style) {
|
||||
forEach(style, (value, key) => {
|
||||
this.style(key, value)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@propTypes({
|
||||
chartHeight: propTypes.number,
|
||||
chartWidth: propTypes.number,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react'
|
||||
import _ from 'intl'
|
||||
import forEach from 'lodash/forEach'
|
||||
import map from 'lodash/map'
|
||||
import moment from 'moment'
|
||||
@@ -12,9 +11,10 @@ import {
|
||||
} from 'd3'
|
||||
import { FormattedTime } from 'react-intl'
|
||||
|
||||
import _ from '../intl'
|
||||
import Component from '../base-component'
|
||||
import propTypes from '../prop-types'
|
||||
import Tooltip from '../tooltip'
|
||||
import { propTypes } from '../utils'
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@ import { createBackoff } from 'jsonrpc-websocket-client'
|
||||
import { resolve } from 'url'
|
||||
|
||||
import _ from '../intl'
|
||||
import invoke from '../invoke'
|
||||
import logError from '../log-error'
|
||||
import { confirm } from '../modal'
|
||||
import { error, info, success } from '../notification'
|
||||
import { invoke, noop, rethrow, tap } from '../utils'
|
||||
import { noop, rethrow, tap } from '../utils'
|
||||
import {
|
||||
connected,
|
||||
disconnected,
|
||||
@@ -453,22 +454,45 @@ export const migrateVm = (vm, host) => (
|
||||
body: <MigrateVmModalBody vm={vm} host={host} />
|
||||
}).then(
|
||||
params => {
|
||||
if (!params && !host) {
|
||||
throw new Error('A target host is required to migrate a VM')
|
||||
}
|
||||
if (params) {
|
||||
_call('vm.migrate', { vm: vm.id, ...params })
|
||||
} else {
|
||||
_call('vm.migrate', { vm: vm.id, targetHost: host.id })
|
||||
if (!params) {
|
||||
throw new Error('a target host is required to migrate a VM')
|
||||
}
|
||||
_call('vm.migrate', { vm: vm.id, ...params })
|
||||
},
|
||||
noop
|
||||
)
|
||||
)
|
||||
|
||||
export const migrateVms = vms => {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
import MigrateVmsModalBody from './migrate-vms-modal'
|
||||
export const migrateVms = vms => (
|
||||
confirm({
|
||||
title: _('migrateVmModalTitle'),
|
||||
body: <MigrateVmsModalBody vms={vms} />
|
||||
}).then(
|
||||
params => {
|
||||
if (!params) {
|
||||
throw new Error('a target host is required to migrate a VM')
|
||||
}
|
||||
const vmsIds = resolveIds(vms)
|
||||
const {
|
||||
mapVmsMapVdisSrs,
|
||||
mapVmsMapVifsNetworks,
|
||||
migrationNetwork,
|
||||
targetHost
|
||||
} = params
|
||||
Promise.all(map(vmsIds, vm =>
|
||||
_call('vm.migrate', {
|
||||
mapVdisSrs: mapVmsMapVdisSrs[vm],
|
||||
mapVifsNetworks: mapVmsMapVifsNetworks[vm],
|
||||
migrationNetwork,
|
||||
targetHost,
|
||||
vm
|
||||
})
|
||||
))
|
||||
},
|
||||
noop
|
||||
)
|
||||
)
|
||||
|
||||
export const createVm = args => (
|
||||
_call('vm.create', args)
|
||||
@@ -636,6 +660,20 @@ export const setBootableVbd = ({ id }, bootable) => (
|
||||
_call('vbd.setBootable', { vbd: id, bootable })
|
||||
)
|
||||
|
||||
// VIF ---------------------------------------------------------------
|
||||
|
||||
export const connectVif = vif => (
|
||||
_call('vif.connect', { id: resolveId(vif) })
|
||||
)
|
||||
|
||||
export const disconnectVif = vif => (
|
||||
_call('vif.disconnect', { id: resolveId(vif) })
|
||||
)
|
||||
|
||||
export const deleteVif = vif => (
|
||||
_call('vif.delete', { id: resolveId(vif) })
|
||||
)
|
||||
|
||||
// Network -----------------------------------------------------------
|
||||
|
||||
export const editNetwork = ({ id }, props) => (
|
||||
@@ -664,6 +702,12 @@ export const deleteNetwork = network => (
|
||||
)
|
||||
)
|
||||
|
||||
// VIF ---------------------------------------------------------------
|
||||
|
||||
export const createVmInterface = (vm, network, mac, mtu) => (
|
||||
_call('vm.createInterface', resolveIds({vm, network, mtu, mac}))
|
||||
)
|
||||
|
||||
// PIF ---------------------------------------------------------------
|
||||
|
||||
export const connectPif = pif => (
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.firstBlock {
|
||||
.block {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.block {
|
||||
.groupBlock {
|
||||
padding-bottom: 1em;
|
||||
padding-top: 1em;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import BaseComponent from 'base-component'
|
||||
import forEach from 'lodash/forEach'
|
||||
import find from 'lodash/find'
|
||||
import map from 'lodash/map'
|
||||
import React, { Component } from 'react'
|
||||
import mapValues from 'lodash/mapValues'
|
||||
import React from 'react'
|
||||
|
||||
import _ from '../../intl'
|
||||
import invoke from '../../invoke'
|
||||
import SingleLineRow from '../../single-line-row'
|
||||
import { Col } from '../../grid'
|
||||
import { getDefaultNetworkForVif } from '../utils'
|
||||
import {
|
||||
SelectHost,
|
||||
SelectNetwork,
|
||||
@@ -49,19 +53,17 @@ import styles from './index.css'
|
||||
|
||||
const getPifs = createGetObjectsOfType('PIF')
|
||||
const getNetworks = createGetObjectsOfType('network')
|
||||
const getSrs = createGetObjectsOfType('SR')
|
||||
const getPools = createGetObjectsOfType('pool')
|
||||
|
||||
return {
|
||||
networks: getNetworks,
|
||||
pifs: getPifs,
|
||||
pools: getPools,
|
||||
srs: getSrs,
|
||||
vdis: getVdis,
|
||||
vifs: getVifs
|
||||
}
|
||||
}, { withRef: true })
|
||||
export default class MigrateVmModalBody extends Component {
|
||||
export default class MigrateVmModalBody extends BaseComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
@@ -79,7 +81,7 @@ export default class MigrateVmModalBody extends Component {
|
||||
() => this.state.host,
|
||||
host => (host
|
||||
? sr => isSrWritable(sr) && (sr.$container === host.id || sr.$container === host.$pool)
|
||||
: () => false
|
||||
: false
|
||||
)
|
||||
)
|
||||
|
||||
@@ -90,7 +92,7 @@ export default class MigrateVmModalBody extends Component {
|
||||
),
|
||||
pifs => {
|
||||
if (!pifs) {
|
||||
return () => false
|
||||
return false
|
||||
}
|
||||
|
||||
const networks = {}
|
||||
@@ -112,125 +114,122 @@ export default class MigrateVmModalBody extends Component {
|
||||
targetHost: this.state.host && this.state.host.id,
|
||||
mapVdisSrs: this.state.mapVdisSrs,
|
||||
mapVifsNetworks: this.state.mapVifsNetworks,
|
||||
migrationNetwork: this.state.network && this.state.network.id
|
||||
migrationNetwork: this.state.migrationNetworkId
|
||||
}
|
||||
}
|
||||
|
||||
_selectHost = host => {
|
||||
if (!host) {
|
||||
this.setState({ intraPool: undefined, targetHost: undefined })
|
||||
this.setState({ intraPool: undefined, host: undefined })
|
||||
return
|
||||
}
|
||||
const { networks, pools, pifs, srs, vdis, vifs } = this.props
|
||||
const defaultMigrationNetwork = networks[find(pifs, pif => pif.$host === host.id && pif.management).$network]
|
||||
const defaultSr = srs[pools[host.$pool].default_SR]
|
||||
const defaultNetworks = {}
|
||||
// Default network...
|
||||
forEach(vifs, vif => {
|
||||
// ...is the one which has the same name_label as the VIF's previous network (if it has an IP)...
|
||||
const defaultPif = find(host.$PIFs, pifId => {
|
||||
const pif = pifs[pifId]
|
||||
return pif.ip && networks[vif.$network].name_label === networks[pif.$network].name_label
|
||||
})
|
||||
defaultNetworks[vif.id] = defaultPif && networks[defaultPif.$network]
|
||||
// ...or the first network in the target host networks list that has an IP.
|
||||
if (!defaultNetworks[vif.id]) {
|
||||
defaultNetworks[vif.id] = networks[pifs[find(host.$PIFs, pif => pifs[pif].ip)].$network]
|
||||
}
|
||||
const { networks, pools, pifs, vdis, vifs } = this.props
|
||||
const defaultMigrationNetworkId = find(pifs, pif => pif.$host === host.id && pif.management).$network
|
||||
const defaultSr = pools[host.$pool].default_SR
|
||||
|
||||
const defaultNetwork = invoke(() => {
|
||||
// First PIF with an IP.
|
||||
const pifId = find(host.$PIFs, pif => pifs[pif].ip)
|
||||
const pif = pifId && pifs[pifId]
|
||||
|
||||
return pif && pif.$network
|
||||
})
|
||||
|
||||
const defaultNetworksForVif = {}
|
||||
forEach(vifs, vif => {
|
||||
defaultNetworksForVif[vif.id] = (
|
||||
getDefaultNetworkForVif(vif, host, pifs, networks) ||
|
||||
defaultNetwork
|
||||
)
|
||||
})
|
||||
|
||||
this.setState({
|
||||
network: defaultMigrationNetwork,
|
||||
defaultNetworks,
|
||||
defaultSr,
|
||||
host,
|
||||
intraPool: this.props.vm.$pool === host.$pool
|
||||
}, () => {
|
||||
if (!this.state.intraPool) {
|
||||
this.refs.network.value = defaultMigrationNetwork
|
||||
forEach(vdis, vdi => {
|
||||
this.refs['sr_' + vdi.id].value = defaultSr
|
||||
})
|
||||
forEach(vifs, vif => {
|
||||
this.refs['network_' + vif.id].value = defaultNetworks[vif.id]
|
||||
})
|
||||
}
|
||||
intraPool: this.props.vm.$pool === host.$pool,
|
||||
mapVdisSrs: mapValues(vdis, vdi => defaultSr),
|
||||
mapVifsNetworks: defaultNetworksForVif,
|
||||
migrationNetworkId: defaultMigrationNetworkId
|
||||
})
|
||||
}
|
||||
|
||||
_selectMigrationNetwork = network => this.setState({ network })
|
||||
_selectMigrationNetwork = migrationNetwork => this.setState({ migrationNetworkId: migrationNetwork.id })
|
||||
|
||||
render () {
|
||||
const { host, vdis, vifs, networks } = this.props
|
||||
const { vdis, vifs, networks } = this.props
|
||||
const {
|
||||
host,
|
||||
intraPool,
|
||||
mapVdisSrs,
|
||||
mapVifsNetworks,
|
||||
migrationNetworkId
|
||||
} = this.state
|
||||
return <div>
|
||||
<div className={styles.firstBlock}>
|
||||
<div className={styles.block}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('migrateVmAdvancedModalSelectHost')}</Col>
|
||||
<Col size={6}>{_('migrateVmSelectHost')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectHost
|
||||
defaultValue={host}
|
||||
onChange={this._selectHost}
|
||||
predicate={this._getHostPredicate()}
|
||||
value={host}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
{this.state.intraPool !== undefined &&
|
||||
(!this.state.intraPool &&
|
||||
{intraPool !== undefined &&
|
||||
(!intraPool &&
|
||||
<div>
|
||||
<div className={styles.block}>
|
||||
<div className={styles.groupBlock}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('migrateVmAdvancedModalSelectNetwork')}</Col>
|
||||
<Col size={6}>{_('migrateVmSelectMigrationNetwork')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectNetwork
|
||||
ref='network'
|
||||
defaultValue={this.state.network}
|
||||
onChange={this._selectMigrationNetwork}
|
||||
predicate={this._getNetworkPredicate()}
|
||||
value={migrationNetworkId}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
<div className={styles.block}>
|
||||
<div className={styles.groupBlock}>
|
||||
<SingleLineRow>
|
||||
<Col>{_('migrateVmAdvancedModalSelectSrs')}</Col>
|
||||
<Col>{_('migrateVmSelectSrs')}</Col>
|
||||
</SingleLineRow>
|
||||
<br />
|
||||
<SingleLineRow>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmAdvancedModalName')}</span></Col>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmAdvancedModalSr')}</span></Col>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmName')}</span></Col>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmSr')}</span></Col>
|
||||
</SingleLineRow>
|
||||
{map(vdis, vdi => <div className={styles.listItem} key={vdi.id}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{vdi.name_label}</Col>
|
||||
<Col size={6}>
|
||||
<SelectSr
|
||||
ref={'sr_' + vdi.id}
|
||||
defaultValue={this.state.defaultSr}
|
||||
onChange={sr => this.setState({ mapVdisSrs: { ...this.state.mapVdisSrs, [vdi.id]: sr.id } })}
|
||||
onChange={sr => this.setState({ mapVdisSrs: { ...mapVdisSrs, [vdi.id]: sr.id } })}
|
||||
predicate={this._getSrPredicate()}
|
||||
value={mapVdisSrs[vdi.id]}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>)}
|
||||
</div>
|
||||
<div className={styles.block}>
|
||||
<div className={styles.groupBlock}>
|
||||
<SingleLineRow>
|
||||
<Col>{_('migrateVmAdvancedModalSelectNetworks')}</Col>
|
||||
<Col>{_('migrateVmSelectNetworks')}</Col>
|
||||
</SingleLineRow>
|
||||
<br />
|
||||
<SingleLineRow>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmAdvancedModalVif')}</span></Col>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmAdvancedModalNetwork')}</span></Col>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmVif')}</span></Col>
|
||||
<Col size={6}><span className={styles.listTitle}>{_('migrateVmNetwork')}</span></Col>
|
||||
</SingleLineRow>
|
||||
{map(vifs, vif => <div className={styles.listItem} key={vif.id}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{vif.MAC} ({networks[vif.$network].name_label})</Col>
|
||||
<Col size={6}>
|
||||
<SelectNetwork
|
||||
ref={'network_' + vif.id}
|
||||
defaultValue={this.state.defaultNetworks[vif.id]}
|
||||
onChange={network => this.setState({ mapVifsNetworks: { ...this.state.mapVifsNetworks, [vif.id]: network.id } })}
|
||||
onChange={network => this.setState({ mapVifsNetworks: { ...mapVifsNetworks, [vif.id]: network.id } })}
|
||||
predicate={this._getNetworkPredicate()}
|
||||
value={mapVifsNetworks[vif.id]}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
200
src/common/xo/migrate-vms-modal/index.js
Normal file
200
src/common/xo/migrate-vms-modal/index.js
Normal file
@@ -0,0 +1,200 @@
|
||||
import BaseComponent from 'base-component'
|
||||
import concat from 'lodash/concat'
|
||||
import every from 'lodash/every'
|
||||
import forEach from 'lodash/forEach'
|
||||
import find from 'lodash/find'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import some from 'lodash/some'
|
||||
|
||||
import _ from '../../intl'
|
||||
import SingleLineRow from '../../single-line-row'
|
||||
import { Col } from '../../grid'
|
||||
import {
|
||||
SelectHost,
|
||||
SelectNetwork,
|
||||
SelectSr
|
||||
} from '../../select-objects'
|
||||
import {
|
||||
connectStore
|
||||
} from '../../utils'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
createPicker,
|
||||
createSelector
|
||||
} from '../../selectors'
|
||||
|
||||
import { isSrWritable } from '../'
|
||||
|
||||
const LINE_STYLE = { paddingBottom: '1em' }
|
||||
|
||||
@connectStore(() => {
|
||||
const getPifs = createGetObjectsOfType('PIF')
|
||||
const getPools = createGetObjectsOfType('pool')
|
||||
|
||||
const getVms = createGetObjectsOfType('VM').pick(
|
||||
(_, props) => props.vms
|
||||
)
|
||||
|
||||
const getVbdsByVm = createGetObjectsOfType('VBD').pick(
|
||||
createSelector(
|
||||
getVms,
|
||||
vms => concat(...map(vms, vm => vm.$VBDs))
|
||||
)
|
||||
).groupBy('VM')
|
||||
|
||||
return {
|
||||
pifs: getPifs,
|
||||
pools: getPools,
|
||||
vbdsByVm: getVbdsByVm,
|
||||
vms: getVms
|
||||
}
|
||||
}, { withRef: true })
|
||||
export default class MigrateVmsModalBody extends BaseComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this._getHostPredicate = createSelector(
|
||||
() => this.props.vms,
|
||||
vms => host => some(vms, vm => host.id !== vm.$container)
|
||||
)
|
||||
|
||||
this._getSrPredicate = createSelector(
|
||||
() => this.state.host,
|
||||
host => (host
|
||||
? sr => isSrWritable(sr) && (sr.$container === host.id || sr.$container === host.$pool)
|
||||
: false
|
||||
)
|
||||
)
|
||||
|
||||
this._getNetworkPredicate = createSelector(
|
||||
createPicker(
|
||||
() => this.props.pifs,
|
||||
() => this.state.host.$PIFs
|
||||
),
|
||||
pifs => {
|
||||
if (!pifs) {
|
||||
return false
|
||||
}
|
||||
|
||||
const networks = {}
|
||||
forEach(pifs, pif => {
|
||||
pif.ip && (networks[pif.$network] = true)
|
||||
})
|
||||
|
||||
return network => networks[network.id]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._selectHost(this.props.host)
|
||||
}
|
||||
|
||||
get value () {
|
||||
// Map VM --> ( Map VDI --> SR )
|
||||
const mapVmsMapVdisSrs = {}
|
||||
forEach(this.props.vbdsByVm, (vbds, vm) => {
|
||||
const mapVdisSrs = {}
|
||||
forEach(vbds, vbd => {
|
||||
if (!vbd.is_cd_drive && vbd.VDI) {
|
||||
mapVdisSrs[vbd.VDI] = this.state.srId
|
||||
}
|
||||
})
|
||||
mapVmsMapVdisSrs[vm] = mapVdisSrs
|
||||
})
|
||||
|
||||
// Map VM --> ( Map VIF --> network )
|
||||
const mapVmsMapVifsNetworks = {}
|
||||
forEach(this.props.vms, vm => {
|
||||
const mapVifsNetworks = {}
|
||||
forEach(vm.VIFs, vif => {
|
||||
mapVifsNetworks[vif] = this.state.networkId
|
||||
})
|
||||
mapVmsMapVifsNetworks[vm.id] = mapVifsNetworks
|
||||
})
|
||||
return {
|
||||
mapVmsMapVdisSrs,
|
||||
mapVmsMapVifsNetworks,
|
||||
migrationNetwork: this.state.migrationNetworkId,
|
||||
targetHost: this.state.host && this.state.host.id
|
||||
}
|
||||
}
|
||||
|
||||
_selectHost = host => {
|
||||
if (!host) {
|
||||
this.setState({ targetHost: undefined })
|
||||
return
|
||||
}
|
||||
const { pools, pifs } = this.props
|
||||
const defaultMigrationNetworkId = find(pifs, pif => pif.$host === host.id && pif.management).$network
|
||||
const defaultSrId = pools[host.$pool].default_SR
|
||||
this.setState({
|
||||
host,
|
||||
intraPool: every(this.props.vms, vm => vm.$pool === host.$pool),
|
||||
migrationNetworkId: defaultMigrationNetworkId,
|
||||
networkId: defaultMigrationNetworkId,
|
||||
srId: defaultSrId
|
||||
})
|
||||
}
|
||||
_selectMigrationNetwork = migrationNetwork => this.setState({ migrationNetworkId: migrationNetwork.id })
|
||||
_selectNetwork = network => this.setState({ networkId: network.id })
|
||||
_selectSr = sr => this.setState({ srId: sr.id })
|
||||
|
||||
render () {
|
||||
return <div>
|
||||
<div style={LINE_STYLE}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('migrateVmSelectHost')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectHost
|
||||
onChange={this._selectHost}
|
||||
predicate={this._getHostPredicate()}
|
||||
value={this.state.host}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
{this.state.intraPool === false &&
|
||||
<div style={LINE_STYLE}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('migrateVmSelectMigrationNetwork')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectNetwork
|
||||
onChange={this._selectMigrationNetwork}
|
||||
predicate={this._getNetworkPredicate()}
|
||||
value={this.state.migrationNetworkId}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
}
|
||||
{this.state.host && [
|
||||
<div key='sr' style={LINE_STYLE}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('migrateVmsSelectSr')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectSr
|
||||
onChange={this._selectSr}
|
||||
predicate={this._getSrPredicate()}
|
||||
value={this.state.srId}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>,
|
||||
<div key='network' style={LINE_STYLE}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('migrateVmsSelectNetwork')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectNetwork
|
||||
onChange={this._selectNetwork}
|
||||
predicate={this._getNetworkPredicate()}
|
||||
value={this.state.networkId}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
]}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
14
src/common/xo/utils.js
Normal file
14
src/common/xo/utils.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import forEach from 'lodash/forEach'
|
||||
|
||||
export const getDefaultNetworkForVif = (vif, host, pifs, networks) => {
|
||||
const nameLabel = networks[vif.$network].name_label
|
||||
let defaultNetwork
|
||||
forEach(host.$PIFs, pifId => {
|
||||
const pif = pifs[pifId]
|
||||
if (pif.ip && networks[pif.$network].name_label === nameLabel) {
|
||||
defaultNetwork = pif.$network
|
||||
return false
|
||||
}
|
||||
})
|
||||
return defaultNetwork
|
||||
}
|
||||
@@ -98,8 +98,8 @@ class XoaUpdater extends EventEmitter {
|
||||
await this._update(true)
|
||||
}
|
||||
|
||||
_promptForReload () {
|
||||
this.emit('promptForReload')
|
||||
_upgradeSuccessful () {
|
||||
this.emit('upgradeSuccessful')
|
||||
}
|
||||
|
||||
async _open () {
|
||||
@@ -158,7 +158,7 @@ class XoaUpdater extends EventEmitter {
|
||||
if (this._lowState.state === 'updater-upgraded' || this._lowState.state === 'installer-upgraded') {
|
||||
this.update()
|
||||
} else if (this._lowState.state === 'xoa-upgraded') {
|
||||
this._promptForReload()
|
||||
this._upgradeSuccessful()
|
||||
}
|
||||
this.xoaState()
|
||||
})
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import _ from 'intl'
|
||||
import Icon from 'icon'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import React from 'react'
|
||||
import { Card, CardHeader, CardBlock } from 'card'
|
||||
import { getXoaPlan, propTypes } from 'utils'
|
||||
|
||||
import _ from './intl'
|
||||
import Icon from './icon'
|
||||
import Link from './link'
|
||||
import propTypes from './prop-types'
|
||||
import { Card, CardHeader, CardBlock } from './card'
|
||||
import { getXoaPlan } from './utils'
|
||||
|
||||
const Upgrade = propTypes({
|
||||
available: propTypes.number.isRequired,
|
||||
|
||||
@@ -839,6 +839,10 @@
|
||||
@extend .fa;
|
||||
@extend .fa-server;
|
||||
}
|
||||
&-connect {
|
||||
@extend .fa;
|
||||
@extend .fa-link;
|
||||
}
|
||||
&-disconnect {
|
||||
@extend .fa;
|
||||
@extend .fa-unlink;
|
||||
|
||||
@@ -2,7 +2,7 @@ import _ from 'intl'
|
||||
import Component from 'base-component'
|
||||
import Copiable from 'copiable'
|
||||
import Icon from 'icon'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import Link from 'link'
|
||||
import Page from '../page'
|
||||
import React from 'react'
|
||||
import { getUser } from 'selectors'
|
||||
|
||||
@@ -11,6 +11,7 @@ import Upgrade from 'xoa-upgrade'
|
||||
import Wizard, { Section } from 'wizard'
|
||||
import { Container } from 'grid'
|
||||
import { error } from 'notification'
|
||||
import { generateUiSchema } from 'xo-json-schema-input'
|
||||
import { injectIntl } from 'react-intl'
|
||||
|
||||
import {
|
||||
@@ -34,7 +35,10 @@ const COMMON_SCHEMA = {
|
||||
},
|
||||
vms: {
|
||||
type: 'array',
|
||||
'xo:type': 'vm',
|
||||
items: {
|
||||
type: 'string',
|
||||
'xo:type': 'vm'
|
||||
},
|
||||
title: 'VMs',
|
||||
description: 'Choose VMs to backup.'
|
||||
},
|
||||
@@ -83,7 +87,7 @@ const BACKUP_SCHEMA = {
|
||||
required: COMMON_SCHEMA.required.concat([ 'depth', 'remoteId' ])
|
||||
}
|
||||
|
||||
const ROLLING_SNAPHOT_SCHEMA = {
|
||||
const ROLLING_SNAPSHOT_SCHEMA = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...COMMON_SCHEMA.properties,
|
||||
@@ -143,13 +147,15 @@ if (process.env.XOA_PLAN < 4) {
|
||||
const BACKUP_METHOD_TO_INFO = {
|
||||
'vm.rollingBackup': {
|
||||
schema: BACKUP_SCHEMA,
|
||||
uiSchema: generateUiSchema(BACKUP_SCHEMA),
|
||||
label: 'backup',
|
||||
icon: 'backup',
|
||||
jobKey: 'rollingBackup',
|
||||
method: 'vm.rollingBackup'
|
||||
},
|
||||
'vm.rollingSnapshot': {
|
||||
schema: ROLLING_SNAPHOT_SCHEMA,
|
||||
schema: ROLLING_SNAPSHOT_SCHEMA,
|
||||
uiSchema: generateUiSchema(ROLLING_SNAPSHOT_SCHEMA),
|
||||
label: 'rollingSnapshot',
|
||||
icon: 'rolling-snapshot',
|
||||
jobKey: 'rollingSnapshot',
|
||||
@@ -157,6 +163,7 @@ const BACKUP_METHOD_TO_INFO = {
|
||||
},
|
||||
'vm.rollingDeltaBackup': {
|
||||
schema: DELTA_BACKUP_SCHEMA,
|
||||
uiSchema: generateUiSchema(DELTA_BACKUP_SCHEMA),
|
||||
label: 'deltaBackup',
|
||||
icon: 'delta-backup',
|
||||
jobKey: 'deltaBackup',
|
||||
@@ -164,6 +171,7 @@ const BACKUP_METHOD_TO_INFO = {
|
||||
},
|
||||
'vm.rollingDrCopy': {
|
||||
schema: DISASTER_RECOVERY_SCHEMA,
|
||||
uiSchema: generateUiSchema(DISASTER_RECOVERY_SCHEMA),
|
||||
label: 'disasterRecovery',
|
||||
icon: 'disaster-recovery',
|
||||
jobKey: 'disasterRecovery',
|
||||
@@ -171,6 +179,7 @@ const BACKUP_METHOD_TO_INFO = {
|
||||
},
|
||||
'vm.deltaCopy': {
|
||||
schema: CONTINUOUS_REPLICATION_SCHEMA,
|
||||
uiSchema: generateUiSchema(CONTINUOUS_REPLICATION_SCHEMA),
|
||||
label: 'continuousReplication',
|
||||
icon: 'continuous-replication',
|
||||
jobKey: 'continuousReplication',
|
||||
@@ -318,6 +327,7 @@ export default class New extends Component {
|
||||
label={<span><Icon icon={backupInfo.icon} /> {formatMessage(messages[backupInfo.label])}</span>}
|
||||
required
|
||||
schema={backupInfo.schema}
|
||||
uiSchema={backupInfo.uiSchema}
|
||||
ref='backupInput'
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -4,19 +4,17 @@ import ActionToggle from 'action-toggle'
|
||||
import filter from 'lodash/filter'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Icon from 'icon'
|
||||
import Link from 'link'
|
||||
import LogList from '../../logs'
|
||||
import map from 'lodash/map'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import React, { Component } from 'react'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBlock
|
||||
} from 'card'
|
||||
|
||||
import {
|
||||
deleteBackupSchedule,
|
||||
disableSchedule,
|
||||
|
||||
@@ -3,7 +3,7 @@ import ActionButton from 'action-button'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import find from 'lodash/find'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import Link from 'link'
|
||||
import map from 'lodash/map'
|
||||
import moment from 'moment'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import ActionButton from 'action-button'
|
||||
import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import XoWeekCharts from 'xo-week-charts'
|
||||
import XoWeekHeatmap from 'xo-week-heatmap'
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
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 React from 'react'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
import XoWeekCharts from 'xo-week-charts'
|
||||
import XoWeekHeatmap from 'xo-week-heatmap'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { error } from 'notification'
|
||||
import { SelectHostVm } from 'select-objects'
|
||||
@@ -17,8 +18,7 @@ import { createGetObjectsOfType } from 'selectors'
|
||||
import {
|
||||
connectStore,
|
||||
formatSize,
|
||||
mapPlus,
|
||||
propTypes
|
||||
mapPlus
|
||||
} from 'utils'
|
||||
import {
|
||||
fetchHostStats,
|
||||
|
||||
@@ -1,11 +1,129 @@
|
||||
import _ from 'intl'
|
||||
import Component from 'base-component'
|
||||
import React from 'react'
|
||||
import XoParallelChart from 'xo-parallel-chart'
|
||||
import forEach from 'lodash/forEach'
|
||||
import invoke from 'invoke'
|
||||
import map from 'lodash/map'
|
||||
import mapValues from 'lodash/mapValues'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
createFilter,
|
||||
createGetObjectsOfType,
|
||||
createPicker,
|
||||
createSelector
|
||||
} from 'selectors'
|
||||
import {
|
||||
connectStore,
|
||||
formatSize
|
||||
} from 'utils'
|
||||
|
||||
export default () => <Container>
|
||||
<Row>
|
||||
<Col>
|
||||
<h3>{_('comingSoon')}</h3>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
// ===================================================================
|
||||
|
||||
// Columns order is defined by the attributes declaration order.
|
||||
const DATA_LABELS = {
|
||||
nVCpus: 'vCPUs number',
|
||||
ram: 'RAM quantity',
|
||||
nVifs: 'VIF number',
|
||||
nVdis: 'VDI number',
|
||||
vdisSize: 'Total space'
|
||||
}
|
||||
|
||||
const DATA_RENDERERS = {
|
||||
ram: formatSize,
|
||||
vdisSize: formatSize
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@connectStore(() => {
|
||||
const getVms = createGetObjectsOfType('VM')
|
||||
const getVdisByVm = invoke(() => {
|
||||
let current = {}
|
||||
const getVdisByVmSelectors = createSelector(
|
||||
vms => vms,
|
||||
vms => {
|
||||
let previous = current
|
||||
current = {}
|
||||
|
||||
forEach(vms, vm => {
|
||||
const { id } = vm
|
||||
current[id] = previous[id] || createPicker(
|
||||
(vm, vbds, vdis) => vdis,
|
||||
createSelector(
|
||||
createFilter(
|
||||
createPicker(
|
||||
(vm, vbds) => vbds,
|
||||
vm => vm.$VBDs
|
||||
),
|
||||
[ vbd => !vbd.is_cd_drive && vbd.attached ]
|
||||
),
|
||||
vbds => map(vbds, vbd => vbd.VDI)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
return current
|
||||
}
|
||||
)
|
||||
|
||||
return createSelector(
|
||||
getVms,
|
||||
createGetObjectsOfType('VBD'),
|
||||
createGetObjectsOfType('VDI'),
|
||||
(vms, vbds, vdis) => mapValues(
|
||||
getVdisByVmSelectors(vms),
|
||||
(getVdis, vmId) => getVdis(vms[vmId], vbds, vdis)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
vms: getVms,
|
||||
vdisByVm: getVdisByVm
|
||||
}
|
||||
})
|
||||
export default class Visualizations extends Component {
|
||||
_getData = createSelector(
|
||||
() => this.props.vms,
|
||||
() => this.props.vdisByVm,
|
||||
(vms, vdisByVm) => (
|
||||
map(vms, (vm, vmId) => {
|
||||
let vdisSize = 0
|
||||
let nVdis = 0
|
||||
|
||||
forEach(vdisByVm[vmId], vdi => {
|
||||
vdisSize += vdi.size
|
||||
nVdis++
|
||||
})
|
||||
|
||||
return {
|
||||
objectId: vmId,
|
||||
label: vm.name_label,
|
||||
data: {
|
||||
nVCpus: vm.CPUs.number,
|
||||
nVdis,
|
||||
nVifs: vm.VIFs.length,
|
||||
ram: vm.memory.size,
|
||||
vdisSize
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Container>
|
||||
<Row>
|
||||
<Col>
|
||||
<XoParallelChart
|
||||
dataSet={this._getData()}
|
||||
labels={DATA_LABELS}
|
||||
renderers={DATA_RENDERERS}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import Component from 'base-component'
|
||||
import Ellipsis, { EllipsisContainer } from 'ellipsis'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Link, { BlockLink } from 'link'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
import Tags from 'tags'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Link } from 'react-router'
|
||||
import { Row, Col } from 'grid'
|
||||
import { Text } from 'editable'
|
||||
import {
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
stopHost
|
||||
} from 'xo'
|
||||
import {
|
||||
BlockLink,
|
||||
connectStore,
|
||||
formatSize,
|
||||
osFamily
|
||||
|
||||
@@ -7,9 +7,11 @@ import Component from 'base-component'
|
||||
import debounce from 'lodash/debounce'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Icon from 'icon'
|
||||
import invoke from 'invoke'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import isString from 'lodash/isString'
|
||||
import keys from 'lodash/keys'
|
||||
import Link from 'link'
|
||||
import map from 'lodash/map'
|
||||
import Page from '../page'
|
||||
import React from 'react'
|
||||
@@ -24,7 +26,6 @@ import {
|
||||
startVms,
|
||||
stopVms
|
||||
} from 'xo'
|
||||
import { Link } from 'react-router'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
SelectHost,
|
||||
@@ -32,8 +33,7 @@ import {
|
||||
SelectTag
|
||||
} from 'select-objects'
|
||||
import {
|
||||
connectStore,
|
||||
invoke
|
||||
connectStore
|
||||
} from 'utils'
|
||||
import {
|
||||
areObjectsFetched,
|
||||
|
||||
@@ -3,12 +3,12 @@ import Component from 'base-component'
|
||||
import Ellipsis, { EllipsisContainer } from 'ellipsis'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Link, { BlockLink } from 'link'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
import Tags from 'tags'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Link } from 'react-router'
|
||||
import { Row, Col } from 'grid'
|
||||
import { Text, XoSelect } from 'editable'
|
||||
import {
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
stopVm
|
||||
} from 'xo'
|
||||
import {
|
||||
BlockLink,
|
||||
connectStore,
|
||||
formatSize,
|
||||
osFamily
|
||||
|
||||
@@ -4,7 +4,7 @@ import HostActionBar from './action-bar'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
import { Link } from 'react-router'
|
||||
import Link from 'link'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
import Page from '../page'
|
||||
import pick from 'lodash/pick'
|
||||
@@ -14,7 +14,6 @@ import { Text } from 'editable'
|
||||
import { editHost, fetchHostStats, getHostMissingPatches, installAllHostPatches, installHostPatch } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
autobind,
|
||||
connectStore,
|
||||
routes
|
||||
} from 'utils'
|
||||
@@ -129,7 +128,6 @@ const isRunning = host => host && host.power_state === 'Running'
|
||||
}
|
||||
})
|
||||
export default class Host extends Component {
|
||||
@autobind
|
||||
loop (host = this.props.host) {
|
||||
if (this.cancel) {
|
||||
this.cancel()
|
||||
@@ -156,6 +154,7 @@ export default class Host extends Component {
|
||||
})
|
||||
})
|
||||
}
|
||||
loop = ::this.loop
|
||||
|
||||
_getMissingPatches (host) {
|
||||
getHostMissingPatches(host).then(missingPatches => {
|
||||
|
||||
@@ -6,12 +6,10 @@ import React from 'react'
|
||||
import Tags from 'tags'
|
||||
import Tooltip from 'tooltip'
|
||||
import { addTag, removeTag } from 'xo'
|
||||
import { FormattedRelative } from 'react-intl'
|
||||
import { BlockLink } from 'link'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
BlockLink,
|
||||
formatSize
|
||||
} from 'utils'
|
||||
import { FormattedRelative } from 'react-intl'
|
||||
import { formatSize } from 'utils'
|
||||
import {
|
||||
CpuSparkLines,
|
||||
MemorySparkLines,
|
||||
|
||||
@@ -3,7 +3,6 @@ import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { autobind } from 'utils'
|
||||
import { fetchHostStats } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
@@ -14,7 +13,6 @@ import {
|
||||
} from 'xo-line-chart'
|
||||
|
||||
export default class HostStats extends Component {
|
||||
@autobind
|
||||
loop (host = this.props.host) {
|
||||
if (this.cancel) {
|
||||
this.cancel()
|
||||
@@ -42,6 +40,7 @@ export default class HostStats extends Component {
|
||||
})
|
||||
})
|
||||
}
|
||||
loop = ::this.loop
|
||||
|
||||
componentWillMount () {
|
||||
this.loop()
|
||||
@@ -64,7 +63,6 @@ export default class HostStats extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleSelectStats (event) {
|
||||
const granularity = event.target.value
|
||||
clearTimeout(this.timeout)
|
||||
@@ -74,6 +72,7 @@ export default class HostStats extends Component {
|
||||
selectStatsLoading: true
|
||||
}, this.loop)
|
||||
}
|
||||
handleSelectStats = ::this.handleSelectStats
|
||||
|
||||
render () {
|
||||
const {
|
||||
|
||||
@@ -3,8 +3,9 @@ import React from 'react'
|
||||
import _ from 'intl'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
import { BlockLink } from 'link'
|
||||
import { TabButtonLink } from 'tab-button'
|
||||
import { BlockLink, formatSize } from 'utils'
|
||||
import { formatSize } from 'utils'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { Text } from 'editable'
|
||||
|
||||
@@ -4,7 +4,7 @@ import ActionRowButton from 'action-row-button'
|
||||
import delay from 'lodash/delay'
|
||||
import find from 'lodash/find'
|
||||
import forEach from 'lodash/forEach'
|
||||
import GenericInput from 'json-schema-input/generic-input'
|
||||
import GenericInput from 'json-schema-input'
|
||||
import includes from 'lodash/includes'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
@@ -13,6 +13,7 @@ import size from 'lodash/size'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import React, { Component } from 'react'
|
||||
import { error } from 'notification'
|
||||
import { generateUiSchema } from 'xo-json-schema-input'
|
||||
import { SelectPlainObject } from 'form'
|
||||
|
||||
import {
|
||||
@@ -53,7 +54,7 @@ const reduceObject = (value, propertyName = 'id') => value && value[propertyName
|
||||
const dataToParamVectorItems = function (params, data) {
|
||||
const items = []
|
||||
forEach(params, (param, name) => {
|
||||
if (Array.isArray(data[name]) && param.$type) { // We have an array for building cross product, the "real" type was $type
|
||||
if (Array.isArray(data[name]) && param.items) { // We have an array for building cross product, the "real" type was $type
|
||||
const values = []
|
||||
if (data[name].length === 1) { // One value, no need to engage cross-product
|
||||
data[name] = data[name].pop()
|
||||
@@ -173,8 +174,11 @@ export default class Jobs extends Component {
|
||||
Vm: 'VM(s)',
|
||||
XoObject: 'Object(s)'
|
||||
}
|
||||
prop.$type = type
|
||||
prop.type = 'array'
|
||||
prop.items = {
|
||||
type: 'string',
|
||||
$type: type
|
||||
}
|
||||
prop.title = titles[type]
|
||||
}
|
||||
|
||||
@@ -215,7 +219,8 @@ export default class Jobs extends Component {
|
||||
method,
|
||||
group,
|
||||
command,
|
||||
info
|
||||
info,
|
||||
uiSchema: generateUiSchema(info)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -314,7 +319,7 @@ export default class Jobs extends Component {
|
||||
</div>
|
||||
<SelectPlainObject ref='method' options={actions} optionKey='method' onChange={this._handleSelectMethod} placeholder={_('jobActionPlaceHolder')} />
|
||||
{action && <fieldset>
|
||||
<GenericInput ref='params' schema={action.info} label={action.method} required />
|
||||
<GenericInput ref='params' schema={action.info} uiSchema={action.uiSchema} label={action.method} required />
|
||||
{job && <p className='text-warning'>{_('jobEditMessage', { name: job.name, id: job.id })}</p>}
|
||||
{process.env.XOA_PLAN > 3
|
||||
? <span><ActionButton form='newJobForm' handler={this._handleSubmit} icon='save' btnStyle='primary'>{_('saveResourceSet')}</ActionButton>
|
||||
|
||||
@@ -4,21 +4,19 @@ import ActionToggle from 'action-toggle'
|
||||
import filter from 'lodash/filter'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Icon from 'icon'
|
||||
import Link from 'link'
|
||||
import LogList from '../../logs'
|
||||
import map from 'lodash/map'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import React, { Component } from 'react'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Container } from 'grid'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBlock
|
||||
} from 'card'
|
||||
|
||||
import {
|
||||
deleteSchedule,
|
||||
disableSchedule,
|
||||
|
||||
@@ -7,11 +7,12 @@ 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 React, { Component } from 'react'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import SortedTable from 'sorted-table'
|
||||
import { alert, confirm } from 'modal'
|
||||
import { connectStore, propTypes } from 'utils'
|
||||
import { connectStore } from 'utils'
|
||||
import { createGetObject } from 'selectors'
|
||||
import { FormattedDate } from 'react-intl'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import _ from 'intl'
|
||||
import Component from 'base-component'
|
||||
import classNames from 'classnames'
|
||||
import Icon from 'icon'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import Link from 'link'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import Tooltip from 'tooltip'
|
||||
@@ -182,7 +182,7 @@ const MenuLinkItem = props => {
|
||||
|
||||
return <li className='nav-item xo-menu-item'>
|
||||
<Link activeClassName='active' className={classNames('nav-link', styles.centerCollapsed)} to={to}>
|
||||
<Icon className={classNames(pill && styles.hiddenCollapsed)} icon={`${icon}`} size='lg' fixedWidth />
|
||||
<Icon className={classNames((pill || extra) && styles.hiddenCollapsed)} icon={`${icon}`} size='lg' fixedWidth />
|
||||
<span className={styles.hiddenCollapsed}>{' '}{_(label)} </span>
|
||||
{pill > 0 && <span className='tag tag-pill tag-primary'>{pill}</span>}
|
||||
{extra}
|
||||
|
||||
@@ -8,6 +8,7 @@ import every from 'lodash/every'
|
||||
import filter from 'lodash/filter'
|
||||
import find from 'lodash/find'
|
||||
import forEach from 'lodash/forEach'
|
||||
import getEventValue from 'get-event-value'
|
||||
import Icon from 'icon'
|
||||
import isArray from 'lodash/isArray'
|
||||
import map from 'lodash/map'
|
||||
@@ -38,7 +39,6 @@ import {
|
||||
import {
|
||||
connectStore,
|
||||
formatSize,
|
||||
getEventValue,
|
||||
noop
|
||||
} from 'utils'
|
||||
import {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import _, { messages } from 'intl'
|
||||
import differenceBy from 'lodash/differenceBy'
|
||||
import filter from 'lodash/filter'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Icon from 'icon'
|
||||
import intersection from 'lodash/intersection'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import keyBy from 'lodash/keyBy'
|
||||
import map from 'lodash/map'
|
||||
import propTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import reduce from 'lodash/reduce'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
@@ -17,13 +18,11 @@ import { Container, Col, Row } from 'grid'
|
||||
import { createGetObjectsOfType } from 'selectors'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { SizeInput } from 'form'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardBlock,
|
||||
CardHeader
|
||||
} from 'card'
|
||||
|
||||
import {
|
||||
SelectSubject,
|
||||
SelectNetwork,
|
||||
@@ -31,13 +30,10 @@ import {
|
||||
SelectSr,
|
||||
SelectVmTemplate
|
||||
} from 'select-objects'
|
||||
|
||||
import {
|
||||
connectStore,
|
||||
formatSize,
|
||||
propTypes
|
||||
formatSize
|
||||
} from 'utils'
|
||||
|
||||
import {
|
||||
createResourceSet,
|
||||
deleteResourceSet,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { Component } from 'react'
|
||||
import _ from 'intl'
|
||||
import forEach from 'lodash/forEach'
|
||||
import keyBy from 'lodash/keyBy'
|
||||
import map from 'lodash/map'
|
||||
import propTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import store from 'store'
|
||||
import { getObject } from 'selectors'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
import {
|
||||
subscribeGroups,
|
||||
|
||||
@@ -5,10 +5,11 @@ 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 React from 'react'
|
||||
import size from 'lodash/size'
|
||||
import SortedTable from 'sorted-table'
|
||||
import { addSubscriptions, propTypes } from 'utils'
|
||||
import { addSubscriptions } from 'utils'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { SelectSubject } from 'select-objects'
|
||||
import { Text } from 'editable'
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, { Component } from 'react'
|
||||
import _ from 'intl'
|
||||
import map from 'lodash/map'
|
||||
import { addSubscriptions } from 'utils'
|
||||
import { generateUiSchema } from 'xo-json-schema-input'
|
||||
import { lastly } from 'promise-toolbox'
|
||||
import {
|
||||
configurePlugin,
|
||||
@@ -20,11 +21,13 @@ import {
|
||||
class Plugin extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
const { configurationSchema } = props
|
||||
|
||||
// Don't update input with schema in edit mode!
|
||||
// It's always the same!
|
||||
this.state = {
|
||||
configurationSchema: props.configurationSchema
|
||||
configurationSchema,
|
||||
uiSchema: generateUiSchema(configurationSchema)
|
||||
}
|
||||
this.formId = `form-${props.id}`
|
||||
}
|
||||
@@ -32,13 +35,17 @@ class Plugin extends Component {
|
||||
componentWillReceiveProps (nextProps) {
|
||||
// Don't update input with schema in edit mode!
|
||||
if (!this.state.edit) {
|
||||
const { configurationSchema } = nextProps
|
||||
|
||||
this.setState({
|
||||
configurationSchema: nextProps.configurationSchema
|
||||
configurationSchema,
|
||||
uiSchema: generateUiSchema(configurationSchema)
|
||||
})
|
||||
|
||||
if (this.refs.pluginInput) {
|
||||
// TODO: Compare values!!!
|
||||
this.refs.pluginInput.value = nextProps.configuration
|
||||
// `|| undefined` because old configs can be null.
|
||||
this.refs.pluginInput.value = nextProps.configuration || undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,9 +146,10 @@ class Plugin extends Component {
|
||||
disabled={!edit}
|
||||
label='Configuration'
|
||||
schema={state.configurationSchema}
|
||||
uiSchema={state.uiSchema}
|
||||
required
|
||||
ref='pluginInput'
|
||||
defaultValue={props.configuration}
|
||||
defaultValue={props.configuration || undefined}
|
||||
/>
|
||||
<div className='form-group pull-xs-right'>
|
||||
<div className='btn-toolbar'>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import _ from 'intl'
|
||||
import React, {
|
||||
Component
|
||||
} from 'react'
|
||||
import { propTypes } from 'utils'
|
||||
|
||||
@propTypes({
|
||||
onSubmit: propTypes.func.isRequired
|
||||
})
|
||||
export default class SignIn extends Component {
|
||||
render () {
|
||||
return <form onSubmit={event => {
|
||||
event.preventDefault()
|
||||
|
||||
const { refs } = this
|
||||
this.props.onSubmit({
|
||||
password: refs.password.value,
|
||||
username: refs.username.value
|
||||
})
|
||||
}}>
|
||||
<p>
|
||||
<label>
|
||||
{_('usernameLabel')}
|
||||
</label>
|
||||
<input type='text' ref='username' />
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
{_('passwordLabel')}
|
||||
</label>
|
||||
<input type='password' ref='password' />
|
||||
</p>
|
||||
<p>
|
||||
<button type='submit'>
|
||||
{_('signInButton')}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import CenterPanel from 'center-panel'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import keys from 'lodash/keys'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import Link from 'link'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
|
||||
@@ -4,7 +4,7 @@ import BaseComponent from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import { alert } from 'modal'
|
||||
import { autobind, connectStore } from 'utils'
|
||||
import { connectStore } from 'utils'
|
||||
import { changePassword } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { getLang, getUser } from 'selectors'
|
||||
@@ -26,10 +26,10 @@ const HEADER = <Container>
|
||||
})
|
||||
@injectIntl
|
||||
export default class User extends BaseComponent {
|
||||
@autobind
|
||||
handleSelectLang (event) {
|
||||
handleSelectLang = event => {
|
||||
this.props.selectLang(event.target.value)
|
||||
}
|
||||
|
||||
_handleSavePassword = () => {
|
||||
const { oldPassword, newPassword, confirmPassword } = this.state
|
||||
if (newPassword !== confirmPassword) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import assign from 'lodash/assign'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Link from 'link'
|
||||
import map from 'lodash/map'
|
||||
import { Link } from 'react-router'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
import Page from '../page'
|
||||
import pick from 'lodash/pick'
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
autobind,
|
||||
connectStore,
|
||||
mapPlus,
|
||||
routes
|
||||
@@ -119,7 +118,6 @@ export default class Vm extends Component {
|
||||
router: React.PropTypes.object
|
||||
}
|
||||
|
||||
@autobind
|
||||
loop (vm = this.props.vm) {
|
||||
if (this.cancel) {
|
||||
this.cancel()
|
||||
@@ -146,6 +144,7 @@ export default class Vm extends Component {
|
||||
})
|
||||
})
|
||||
}
|
||||
loop = ::this.loop
|
||||
|
||||
componentWillMount () {
|
||||
this.loop()
|
||||
|
||||
@@ -7,20 +7,20 @@ import HTML5Backend from 'react-dnd-html5-backend'
|
||||
import Icon from 'icon'
|
||||
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 React from 'react'
|
||||
import some from 'lodash/some'
|
||||
import TabButton from 'tab-button'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { createSelector } from 'selectors'
|
||||
import { DragDropContext, DragSource, DropTarget } from 'react-dnd'
|
||||
import { Link } from 'react-router'
|
||||
import { noop, propTypes } from 'utils'
|
||||
import { noop } from 'utils'
|
||||
import { SelectSr, SelectVdi } from 'select-objects'
|
||||
import { SizeInput, Toggle } from 'form'
|
||||
import { XoSelect, Size, Text } from 'editable'
|
||||
import some from 'lodash/some'
|
||||
import { createSelector } from 'selectors'
|
||||
|
||||
import {
|
||||
attachDiskToVm,
|
||||
createDisk,
|
||||
|
||||
@@ -6,11 +6,11 @@ import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import Tags from 'tags'
|
||||
import { addTag, editVm, removeTag } from 'xo'
|
||||
import { BlockLink } from 'link'
|
||||
import { FormattedRelative } from 'react-intl'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { Number, Size } from 'editable'
|
||||
import {
|
||||
BlockLink,
|
||||
formatSize,
|
||||
osFamily
|
||||
} from 'utils'
|
||||
|
||||
@@ -1,14 +1,79 @@
|
||||
import _ from 'intl'
|
||||
import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
import propTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import TabButton from 'tab-button'
|
||||
import { connectStore } from 'utils'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { connectStore, noop } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
createSelector
|
||||
} from 'selectors'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { SelectNetwork } from 'select-objects'
|
||||
|
||||
import {
|
||||
connectVif,
|
||||
createVmInterface,
|
||||
deleteVif,
|
||||
disconnectVif
|
||||
} from 'xo'
|
||||
|
||||
@propTypes({
|
||||
onClose: propTypes.func,
|
||||
vm: propTypes.object.isRequired
|
||||
})
|
||||
@injectIntl
|
||||
class NewVif extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
network: undefined
|
||||
}
|
||||
}
|
||||
_getNetworkPredicate = createSelector(
|
||||
() => {
|
||||
const { vm } = this.props
|
||||
return vm && vm.$pool
|
||||
},
|
||||
poolId => network => network.$pool === poolId
|
||||
)
|
||||
|
||||
_selectNetwork = network => this.setState({network})
|
||||
|
||||
_createVif = () => {
|
||||
const { vm, onClose = noop } = this.props
|
||||
const { mac, mtu } = this.refs
|
||||
const { network } = this.state
|
||||
return createVmInterface(vm, network, mac.value || undefined, mtu.value || String(network.MTU))
|
||||
.then(onClose)
|
||||
}
|
||||
|
||||
render () {
|
||||
const formatMessage = this.props.intl.formatMessage
|
||||
return <form id='newVifForm'>
|
||||
<div className='form-group'>
|
||||
<SelectNetwork predicate={this._getNetworkPredicate()} onChange={this._selectNetwork} required />
|
||||
</div>
|
||||
<fieldset className='form-inline'>
|
||||
<div className='form-group'>
|
||||
<input type='text' ref='mac' placeholder={formatMessage(messages.vifMacLabel)} className='form-control' /> ({_('vifMacAutoGenerate')})
|
||||
</div>
|
||||
{' '}
|
||||
<div className='form-group'>
|
||||
<input type='text' ref='mtu' placeholder={formatMessage(messages.vifMtuLabel)} className='form-control' />
|
||||
</div>
|
||||
<span className='pull-right'>
|
||||
<ActionButton form='newVifForm' icon='add' btnStyle='primary' handler={this._createVif}>Create</ActionButton>
|
||||
</span>
|
||||
</fieldset>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
@connectStore(() => {
|
||||
const vifs = createGetObjectsOfType('VIF').pick(
|
||||
@@ -27,7 +92,19 @@ import {
|
||||
})
|
||||
})
|
||||
export default class TabNetwork extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
newVif: false
|
||||
}
|
||||
}
|
||||
|
||||
_toggleNewVif = () => this.setState({
|
||||
newVif: !this.state.newVif
|
||||
})
|
||||
|
||||
render () {
|
||||
const { newVif } = this.state
|
||||
const {
|
||||
networks,
|
||||
vifs,
|
||||
@@ -39,12 +116,17 @@ export default class TabNetwork extends Component {
|
||||
<Col className='text-xs-right'>
|
||||
<TabButton
|
||||
btnStyle='primary'
|
||||
handler={() => null()} // TODO: add vif
|
||||
handler={this._toggleNewVif}
|
||||
icon='add'
|
||||
labelId='vifCreateDeviceButton'
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
{newVif && <div><NewVif vm={vm} onClose={this._toggleNewVif} /><hr /></div>}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
{!isEmpty(vifs)
|
||||
@@ -68,11 +150,34 @@ export default class TabNetwork extends Component {
|
||||
<td>{networks[vif.$network] && networks[vif.$network].name_label}</td>
|
||||
<td>
|
||||
{vif.attached
|
||||
? <span className='tag tag-success'>
|
||||
? <span>
|
||||
<span className='tag tag-success'>
|
||||
{_('vifStatusConnected')}
|
||||
</span>
|
||||
<ButtonGroup className='pull-xs-right'>
|
||||
<ActionRowButton
|
||||
icon='disconnect'
|
||||
handler={disconnectVif}
|
||||
handlerParam={vif}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</span>
|
||||
: <span className='tag tag-default'>
|
||||
: <span>
|
||||
<span className='tag tag-default'>
|
||||
{_('vifStatusDisconnected')}
|
||||
</span>
|
||||
<ButtonGroup className='pull-xs-right'>
|
||||
<ActionRowButton
|
||||
icon='connect'
|
||||
handler={connectVif}
|
||||
handlerParam={vif}
|
||||
/>
|
||||
<ActionRowButton
|
||||
icon='remove'
|
||||
handler={deleteVif}
|
||||
handlerParam={vif}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
@@ -3,7 +3,6 @@ import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { autobind } from 'utils'
|
||||
import { fetchVmStats } from 'xo'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
|
||||
export default injectIntl(
|
||||
class VmStats extends Component {
|
||||
@autobind
|
||||
loop (vm = this.props.vm) {
|
||||
if (this.cancel) {
|
||||
this.cancel()
|
||||
@@ -44,6 +42,7 @@ export default injectIntl(
|
||||
})
|
||||
})
|
||||
}
|
||||
loop = ::this.loop
|
||||
|
||||
componentWillMount () {
|
||||
this.loop()
|
||||
@@ -66,7 +65,6 @@ export default injectIntl(
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleSelectStats (event) {
|
||||
const granularity = event.target.value
|
||||
clearTimeout(this.timeout)
|
||||
@@ -76,6 +74,7 @@ export default injectIntl(
|
||||
selectStatsLoading: true
|
||||
}, this.loop)
|
||||
}
|
||||
handleSelectStats = ::this.handleSelectStats
|
||||
|
||||
render () {
|
||||
const {
|
||||
|
||||
@@ -19,8 +19,14 @@ import { serverVersion } from 'xo'
|
||||
|
||||
import pkg from '../../../package'
|
||||
|
||||
const promptForReload = () => confirm({
|
||||
title: _('promptUpgradeReloadTitle'),
|
||||
body: <p>{_('promptUpgradeReloadMessage')}</p>
|
||||
}).then(() => window.location.reload())
|
||||
|
||||
if (+process.env.XOA_PLAN < 5) {
|
||||
xoaUpdater.start()
|
||||
xoaUpdater.on('upgradeSuccessful', promptForReload)
|
||||
}
|
||||
|
||||
const HEADER = <Container>
|
||||
@@ -78,9 +84,9 @@ export default class XoaUpdates extends Component {
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
return xoaUpdater.register(email.value, password.value, alreadyRegistered)
|
||||
.then(() => { email.value = password.value = '' })
|
||||
}
|
||||
return xoaUpdater.register(email.value, password.value, alreadyRegistered)
|
||||
.then(() => { email.value = password.value = '' })
|
||||
}
|
||||
|
||||
_configure = async () => {
|
||||
@@ -129,7 +135,9 @@ export default class XoaUpdates extends Component {
|
||||
serverVersion.then(serverVersion => {
|
||||
this.setState({ serverVersion })
|
||||
})
|
||||
update()
|
||||
}
|
||||
|
||||
render () {
|
||||
const textClasses = {
|
||||
info: 'text-info',
|
||||
@@ -264,7 +272,7 @@ export default class XoaUpdates extends Component {
|
||||
{' '}
|
||||
<ActionButton form='registrationForm' icon='success' btnStyle='primary' handler={this._register}>{_('register')}</ActionButton>
|
||||
</form>
|
||||
{process.env.XOA_PLAN === 1 &&
|
||||
{+process.env.XOA_PLAN === 1 &&
|
||||
<div>
|
||||
<h2>{_('trial')}</h2>
|
||||
{this._trialAllowed(trial) &&
|
||||
@@ -320,17 +328,17 @@ export const UpdateTag = connectStore((state) => {
|
||||
const icons = {
|
||||
'disconnected': 'update-unknown',
|
||||
'connected': 'update-unknown',
|
||||
'upToDate': 'check',
|
||||
'upToDate': 'success',
|
||||
'upgradeNeeded': 'update-ready',
|
||||
'registerNeeded': 'not-registered',
|
||||
'error': 'alarm'
|
||||
}
|
||||
const classNames = {
|
||||
'disconnected': 'text-warning',
|
||||
'connected': 'text-info',
|
||||
'disconnected': 'text-danger',
|
||||
'connected': 'text-success',
|
||||
'upToDate': 'text-success',
|
||||
'upgradeNeeded': 'text-primary',
|
||||
'registerNeeded': 'text-warning',
|
||||
'upgradeNeeded': 'text-warning',
|
||||
'registerNeeded': 'text-danger',
|
||||
'error': 'text-danger'
|
||||
}
|
||||
const tooltips = {
|
||||
@@ -341,5 +349,5 @@ export const UpdateTag = connectStore((state) => {
|
||||
'registerNeeded': _('registerNeeded'),
|
||||
'error': _('updaterError')
|
||||
}
|
||||
return <Tooltip content={tooltips[state]}><Icon className={classNames[state]} icon={icons[state]} /></Tooltip>
|
||||
return <Tooltip content={tooltips[state]}><Icon className={classNames[state]} icon={icons[state]} size='lg' /></Tooltip>
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user