Compare commits
73 Commits
xo-web/v5.
...
v5.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
090c9ea4d7 | ||
|
|
647eb7299e | ||
|
|
027652e80a | ||
|
|
185d380c36 | ||
|
|
9008b5c4e7 | ||
|
|
f5ad59803e | ||
|
|
81d1d7ba13 | ||
|
|
3328e71805 | ||
|
|
d7e3dbac26 | ||
|
|
905182bf2e | ||
|
|
a0146290ee | ||
|
|
173aa22432 | ||
|
|
9e5b871ebe | ||
|
|
8824ce55ec | ||
|
|
155edf5533 | ||
|
|
6d06e1f89d | ||
|
|
6d1e2c47d3 | ||
|
|
8b9b0346cb | ||
|
|
0d11817e3f | ||
|
|
a8cb209717 | ||
|
|
cf45ffddf1 | ||
|
|
2e0ea51c30 | ||
|
|
0f7f8c7330 | ||
|
|
808f72409f | ||
|
|
f8e2d29372 | ||
|
|
22dec27c65 | ||
|
|
89b3806a7a | ||
|
|
b6bedf9253 | ||
|
|
0d4983043b | ||
|
|
f9ff3fe168 | ||
|
|
4a25c5323f | ||
|
|
9b4e2d3bb8 | ||
|
|
3915efcf92 | ||
|
|
4591ff8522 | ||
|
|
e3491797f3 | ||
|
|
6eee167675 | ||
|
|
16b965b28a | ||
|
|
5125410efd | ||
|
|
1a4da2a8de | ||
|
|
991fbaec86 | ||
|
|
fb399278b3 | ||
|
|
b868092365 | ||
|
|
80fdc6849f | ||
|
|
25ffcb952b | ||
|
|
083ac1e2d6 | ||
|
|
5a4b553a60 | ||
|
|
b1135ef566 | ||
|
|
1928d1e00f | ||
|
|
a369f7f387 | ||
|
|
33d9801dfe | ||
|
|
8c7a031cca | ||
|
|
9484d87e76 | ||
|
|
4b6822d6e5 | ||
|
|
7241a0529b | ||
|
|
66083b4e50 | ||
|
|
f631b3cc64 | ||
|
|
bb58d9b4d6 | ||
|
|
93ebff1055 | ||
|
|
08aec1c09a | ||
|
|
8ca98a56fe | ||
|
|
705f53e3e5 | ||
|
|
adaf069d20 | ||
|
|
d7be7d8660 | ||
|
|
faddee86b6 | ||
|
|
c4fcc65d16 | ||
|
|
890631d33b | ||
|
|
8e8145bb48 | ||
|
|
d73d6719a5 | ||
|
|
3419bee198 | ||
|
|
4368fad393 | ||
|
|
ab93fdbf10 | ||
|
|
8fd7697a45 | ||
|
|
1121a60912 |
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,32 @@
|
||||
# ChangeLog
|
||||
|
||||
## **5.8.0** (2017-04-28)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Limit About view info for non-admins [\#2109](https://github.com/vatesfr/xo-web/issues/2109)
|
||||
- Enabling/disabling boot device on HVM VM [\#2105](https://github.com/vatesfr/xo-web/issues/2105)
|
||||
- Filter: Hide snapshots in SR disk view [\#2102](https://github.com/vatesfr/xo-web/issues/2102)
|
||||
- Smarter XOSAN install [\#2084](https://github.com/vatesfr/xo-web/issues/2084)
|
||||
- PL translation [\#2079](https://github.com/vatesfr/xo-web/issues/2079)
|
||||
- Remove the "share this VM" option if not in self service [\#2061](https://github.com/vatesfr/xo-web/issues/2061)
|
||||
- "connected" status graphics are not the same on the host storage and networking tabs [\#2060](https://github.com/vatesfr/xo-web/issues/2060)
|
||||
- Ability to view and edit `vga` and `videoram` fields in VM view [\#158](https://github.com/vatesfr/xo-web/issues/158)
|
||||
- Performances [\#1](https://github.com/vatesfr/xen-api/issues/1)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Dashboard display issues [\#2108](https://github.com/vatesfr/xo-web/issues/2108)
|
||||
- Dashboard CPUs Usage [\#2105](https://github.com/vatesfr/xo-web/issues/2105)
|
||||
- [Dashboard/Overview] Warning [\#2090](https://github.com/vatesfr/xo-web/issues/2090)
|
||||
- VM creation displays all networks [\#2086](https://github.com/vatesfr/xo-web/issues/2086)
|
||||
- Cannot change HA mode for a VM [\#2080](https://github.com/vatesfr/xo-web/issues/2080)
|
||||
- [Smart backup] Tags selection does not work [\#2077](https://github.com/vatesfr/xo-web/issues/2077)
|
||||
- [Backup jobs] Timeout should be in seconds, not milliseconds [\#2076](https://github.com/vatesfr/xo-web/issues/2076)
|
||||
- Missing VM templates [\#2075](https://github.com/vatesfr/xo-web/issues/2075)
|
||||
- [transport-email] From header not set [\#2074](https://github.com/vatesfr/xo-web/issues/2074)
|
||||
- Missing objects should be displayed in backup edition [\#2052](https://github.com/vatesfr/xo-web/issues/2052)
|
||||
|
||||
## **5.7.0** (2017-03-31)
|
||||
|
||||
### Enhancements
|
||||
@@ -258,7 +285,7 @@ File level restore.
|
||||
- Tooltip on OS icon in VM view [\#1416](https://github.com/vatesfr/xo-web/issues/1416)
|
||||
- Display pool master [\#1407](https://github.com/vatesfr/xo-web/issues/1407)
|
||||
- Missing tooltips in VM creation view [\#1402](https://github.com/vatesfr/xo-web/issues/1402)
|
||||
- Handle VDB disconnect and connect [\#1397](https://github.com/vatesfr/xo-web/issues/1397)
|
||||
- Handle VBD disconnect and connect [\#1397](https://github.com/vatesfr/xo-web/issues/1397)
|
||||
- Eject host from a pool [\#1395](https://github.com/vatesfr/xo-web/issues/1395)
|
||||
- Improve pool general view [\#1393](https://github.com/vatesfr/xo-web/issues/1393)
|
||||
- Improve patching system [\#1392](https://github.com/vatesfr/xo-web/issues/1392)
|
||||
|
||||
14
package.json
14
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-web",
|
||||
"version": "5.7.2",
|
||||
"version": "5.8.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -61,7 +61,7 @@
|
||||
"dependency-check": "^2.5.1",
|
||||
"enzyme": "^2.6.0",
|
||||
"enzyme-to-json": "^1.4.4",
|
||||
"event-to-promise": "^0.7.0",
|
||||
"event-to-promise": "^0.8.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"font-mfizz": "github:fizzed/font-mfizz",
|
||||
"get-stream": "^2.3.0",
|
||||
@@ -77,7 +77,7 @@
|
||||
"gulp-sourcemaps": "^2.2.3",
|
||||
"gulp-uglify": "^2.0.0",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"human-format": "^0.7.0",
|
||||
"human-format": "^0.8.0",
|
||||
"husky": "^0.13.1",
|
||||
"index-modules": "^0.3.0",
|
||||
"is-ip": "^1.0.0",
|
||||
@@ -124,18 +124,18 @@
|
||||
"redux-devtools-dock-monitor": "^1.1.0",
|
||||
"redux-devtools-log-monitor": "^1.0.5",
|
||||
"redux-thunk": "^2.0.1",
|
||||
"reselect": "^2.2.1",
|
||||
"reselect": "^2.5.4",
|
||||
"semver": "^5.3.0",
|
||||
"standard": "^8.4.0",
|
||||
"standard": "^10.0.0",
|
||||
"styled-components": "^1.4.4",
|
||||
"superagent": "^3.5.0",
|
||||
"tar-stream": "^1.5.2",
|
||||
"uncontrollable-input": "^0.0.0",
|
||||
"uncontrollable-input": "^0.0.1",
|
||||
"vinyl": "^2.0.0",
|
||||
"watchify": "^3.7.0",
|
||||
"xml2js": "^0.4.17",
|
||||
"xo-acl-resolver": "^0.2.3",
|
||||
"xo-common": "0.1.0",
|
||||
"xo-common": "^0.1.1",
|
||||
"xo-lib": "^0.8.0",
|
||||
"xo-remote-parser": "^0.3"
|
||||
},
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import {
|
||||
ButtonGroup
|
||||
} from 'react-bootstrap-4/lib'
|
||||
import {
|
||||
noop
|
||||
} from 'utils'
|
||||
import { map, noop } from 'lodash'
|
||||
|
||||
import _ from './intl'
|
||||
import ActionButton from './action-button'
|
||||
import ButtonGroup from './button-group'
|
||||
|
||||
const ActionBar = ({ actions, param }) => (
|
||||
<ButtonGroup>
|
||||
@@ -16,13 +12,20 @@ const ActionBar = ({ actions, param }) => (
|
||||
return
|
||||
}
|
||||
|
||||
const { handler, handlerParam = param, label, icon, redirectOnSuccess } = button
|
||||
const {
|
||||
handler,
|
||||
handlerParam = param,
|
||||
icon,
|
||||
label,
|
||||
pending,
|
||||
redirectOnSuccess
|
||||
} = button
|
||||
return <ActionButton
|
||||
key={index}
|
||||
btnStyle='secondary'
|
||||
handler={handler || noop}
|
||||
handlerParam={handlerParam}
|
||||
icon={icon}
|
||||
pending={pending}
|
||||
redirectOnSuccess={redirectOnSuccess}
|
||||
size='large'
|
||||
tooltip={_(label)}
|
||||
|
||||
@@ -1,38 +1,59 @@
|
||||
import Icon from 'icon'
|
||||
import isFunction from 'lodash/isFunction'
|
||||
import React from 'react'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
|
||||
import Button from './button'
|
||||
import Component from './base-component'
|
||||
import Icon from './icon'
|
||||
import logError from './log-error'
|
||||
import propTypes from './prop-types'
|
||||
import Tooltip from './tooltip'
|
||||
import { error as _error } from './notification'
|
||||
|
||||
@propTypes({
|
||||
btnStyle: propTypes.string,
|
||||
// React element to use as button content
|
||||
children: propTypes.node,
|
||||
|
||||
// whether this button is disabled (default to false)
|
||||
disabled: propTypes.bool,
|
||||
|
||||
// form identifier
|
||||
//
|
||||
// if provided, this button and its action are associated to this
|
||||
// form for the submit event
|
||||
form: propTypes.string,
|
||||
|
||||
// function to call when the action is triggered (via a clik on the
|
||||
// button or submit on the form)
|
||||
handler: propTypes.func.isRequired,
|
||||
|
||||
// optional value which will be passed as first param to the handler
|
||||
handlerParam: propTypes.any,
|
||||
|
||||
// XO icon to use for this button
|
||||
icon: propTypes.string.isRequired,
|
||||
|
||||
// whether the action of this action is already underway
|
||||
pending: propTypes.bool,
|
||||
|
||||
// path to redirect to when the triggered action finish successfully
|
||||
//
|
||||
// if a function, it will be called with the result of the action to
|
||||
// compute the path
|
||||
redirectOnSuccess: propTypes.oneOfType([
|
||||
propTypes.func,
|
||||
propTypes.string
|
||||
]),
|
||||
size: propTypes.oneOf([
|
||||
'large',
|
||||
'small'
|
||||
]),
|
||||
|
||||
// React element to use tooltip for the component
|
||||
tooltip: propTypes.node
|
||||
})
|
||||
export default class ActionButton extends Component {
|
||||
static contextTypes = {
|
||||
router: React.PropTypes.object
|
||||
router: propTypes.object
|
||||
}
|
||||
|
||||
async _execute () {
|
||||
if (this.state.working) {
|
||||
if (this.props.pending || this.state.working) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -45,18 +66,17 @@ export default class ActionButton extends Component {
|
||||
|
||||
try {
|
||||
this.setState({
|
||||
error: null,
|
||||
error: undefined,
|
||||
working: true
|
||||
})
|
||||
|
||||
const result = await handler(handlerParam)
|
||||
|
||||
let { redirectOnSuccess } = this.props
|
||||
const { redirectOnSuccess } = this.props
|
||||
if (redirectOnSuccess) {
|
||||
if (isFunction(redirectOnSuccess)) {
|
||||
redirectOnSuccess = redirectOnSuccess(result)
|
||||
}
|
||||
return this.context.router.push(redirectOnSuccess)
|
||||
return this.context.router.push(
|
||||
isFunction(redirectOnSuccess) ? redirectOnSuccess(result) : redirectOnSuccess
|
||||
)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -101,28 +121,30 @@ export default class ActionButton extends Component {
|
||||
render () {
|
||||
const {
|
||||
props: {
|
||||
btnStyle,
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
form,
|
||||
icon,
|
||||
size: bsSize,
|
||||
style,
|
||||
tooltip
|
||||
pending,
|
||||
tooltip,
|
||||
...props
|
||||
},
|
||||
state: { error, working }
|
||||
} = this
|
||||
|
||||
const button = <Button
|
||||
bsStyle={error ? 'warning' : btnStyle}
|
||||
form={form}
|
||||
onClick={!form && this._execute}
|
||||
disabled={working || disabled}
|
||||
type={form ? 'submit' : 'button'}
|
||||
{...{ bsSize, className, style }}
|
||||
>
|
||||
<Icon icon={working ? 'loading' : icon} fixedWidth />
|
||||
if (error !== undefined) {
|
||||
props.btnStyle = 'warning'
|
||||
}
|
||||
if (pending || working) {
|
||||
props.disabled = true
|
||||
}
|
||||
delete props.handler
|
||||
delete props.handlerParam
|
||||
if (props.form === undefined) {
|
||||
props.onClick = this._execute
|
||||
}
|
||||
delete props.redirectOnSuccess
|
||||
|
||||
const button = <Button {...props}>
|
||||
<Icon icon={pending || working ? 'loading' : icon} fixedWidth />
|
||||
{children && ' '}
|
||||
{children}
|
||||
</Button>
|
||||
|
||||
@@ -17,7 +17,7 @@ const cowSet = (object, path, value, depth) => {
|
||||
return value
|
||||
}
|
||||
|
||||
object = clone(object)
|
||||
object = object != null ? clone(object) : {}
|
||||
const prop = path[depth]
|
||||
object[prop] = cowSet(object[prop], path, value, depth + 1)
|
||||
return object
|
||||
|
||||
8
src/common/button-group.js
Normal file
8
src/common/button-group.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
const ButtonGroup = ({ children }) =>
|
||||
<div className='btn-group' role='group'>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
export { ButtonGroup as default }
|
||||
56
src/common/button.js
Normal file
56
src/common/button.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import propTypes from './prop-types'
|
||||
|
||||
const Button = ({
|
||||
active,
|
||||
block,
|
||||
btnStyle = 'secondary',
|
||||
children,
|
||||
outline,
|
||||
size,
|
||||
...props
|
||||
}) => {
|
||||
props.className = classNames(
|
||||
props.className,
|
||||
'btn',
|
||||
`btn${outline ? '-outline' : ''}-${btnStyle}`,
|
||||
active !== undefined && 'active',
|
||||
block && 'btn-block',
|
||||
size === 'large' ? 'btn-lg' : size === 'small' ? 'btn-sm' : null
|
||||
)
|
||||
if (props.type === undefined && props.form === undefined) {
|
||||
props.type = 'button'
|
||||
}
|
||||
|
||||
return <button {...props}>{children}</button>
|
||||
}
|
||||
|
||||
propTypes(Button)({
|
||||
active: propTypes.bool,
|
||||
block: propTypes.bool,
|
||||
|
||||
// Bootstrap button style
|
||||
//
|
||||
// See https://v4-alpha.getbootstrap.com/components/buttons/#examples
|
||||
//
|
||||
// The default value (secondary) is not listed here because it does
|
||||
// not make sense to explicit it.
|
||||
btnStyle: propTypes.oneOf([
|
||||
'danger',
|
||||
'info',
|
||||
'link',
|
||||
'primary',
|
||||
'success',
|
||||
'warning'
|
||||
]),
|
||||
|
||||
outline: propTypes.bool,
|
||||
size: propTypes.oneOf([
|
||||
'large',
|
||||
'small'
|
||||
])
|
||||
})
|
||||
|
||||
export { Button as default }
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import Button from './button'
|
||||
import Component from './base-component'
|
||||
import Icon from './icon'
|
||||
import propTypes from './prop-types'
|
||||
@@ -27,9 +28,9 @@ export default class Collapse extends Component {
|
||||
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<button className='btn btn-lg btn-primary btn-block' onClick={this._onClick}>
|
||||
<Button block btnStyle='primary' size='large' onClick={this._onClick}>
|
||||
{props.buttonText} <Icon icon={`chevron-${isOpened ? 'up' : 'down'}`} />
|
||||
</button>
|
||||
</Button>
|
||||
{isOpened && props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
64
src/common/combobox.js
Normal file
64
src/common/combobox.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react'
|
||||
import uncontrollableInput from 'uncontrollable-input'
|
||||
import { isEmpty, map } from 'lodash'
|
||||
import {
|
||||
DropdownButton,
|
||||
MenuItem
|
||||
} from 'react-bootstrap-4/lib'
|
||||
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types'
|
||||
|
||||
@uncontrollableInput({
|
||||
defaultValue: ''
|
||||
})
|
||||
@propTypes({
|
||||
disabled: propTypes.bool,
|
||||
options: propTypes.oneOfType([
|
||||
propTypes.arrayOf(propTypes.string),
|
||||
propTypes.objectOf(propTypes.string)
|
||||
]),
|
||||
onChange: propTypes.func.isRequired,
|
||||
value: propTypes.string.isRequired
|
||||
})
|
||||
export default class Combobox extends Component {
|
||||
_handleChange = event => {
|
||||
this.props.onChange(event.target.value)
|
||||
}
|
||||
|
||||
_setText (value) {
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { options, ...props } = this.props
|
||||
|
||||
props.className = 'form-control'
|
||||
props.onChange = this._handleChange
|
||||
const Input = <input {...props} />
|
||||
|
||||
if (isEmpty(options)) {
|
||||
return Input
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='input-group'>
|
||||
<div className='input-group-btn'>
|
||||
<DropdownButton
|
||||
bsStyle='secondary'
|
||||
disabled={props.disabled}
|
||||
id='selectInput'
|
||||
title=''
|
||||
>
|
||||
{map(options, option =>
|
||||
<MenuItem key={option} onClick={() => this._setText(option)}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DropdownButton>
|
||||
</div>
|
||||
{Input}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.button {
|
||||
border-radius: 0px;
|
||||
};
|
||||
@@ -1,105 +0,0 @@
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import size from 'lodash/size'
|
||||
|
||||
import Component from '../base-component'
|
||||
import propTypes from '../prop-types'
|
||||
import { ensureArray } from '../utils'
|
||||
import {
|
||||
DropdownButton,
|
||||
MenuItem
|
||||
} from 'react-bootstrap-4/lib'
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
@propTypes({
|
||||
defaultValue: propTypes.any,
|
||||
disabled: propTypes.bool,
|
||||
max: propTypes.number,
|
||||
min: propTypes.number,
|
||||
options: propTypes.oneOfType([
|
||||
propTypes.arrayOf(propTypes.string),
|
||||
propTypes.number,
|
||||
propTypes.objectOf(propTypes.string),
|
||||
propTypes.string
|
||||
]),
|
||||
onChange: propTypes.func,
|
||||
placeholder: propTypes.string,
|
||||
required: propTypes.bool,
|
||||
step: propTypes.any,
|
||||
type: propTypes.string,
|
||||
value: propTypes.any
|
||||
})
|
||||
export default class Combobox extends Component {
|
||||
static defaultProps = {
|
||||
type: 'text'
|
||||
}
|
||||
|
||||
get value () {
|
||||
return this.refs.input.value
|
||||
}
|
||||
|
||||
set value (value) {
|
||||
this.refs.input.value = value
|
||||
}
|
||||
|
||||
_handleChange = event => {
|
||||
const { onChange } = this.props
|
||||
|
||||
if (onChange) {
|
||||
onChange(event.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
_setText (value) {
|
||||
this.refs.input.value = value
|
||||
}
|
||||
|
||||
render () {
|
||||
const { props } = this
|
||||
const options = ensureArray(props.options)
|
||||
|
||||
const Input = (
|
||||
<input
|
||||
className='form-control'
|
||||
defaultValue={props.defaultValue}
|
||||
disabled={props.disabled}
|
||||
max={props.max}
|
||||
min={props.min}
|
||||
options={options}
|
||||
onChange={this._handleChange}
|
||||
placeholder={props.placeholder}
|
||||
ref='input'
|
||||
required={props.required}
|
||||
step={props.step}
|
||||
type={props.type}
|
||||
value={props.value}
|
||||
/>
|
||||
)
|
||||
|
||||
if (!size(options)) {
|
||||
return Input
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='input-group'>
|
||||
<div className='input-group-btn'>
|
||||
<DropdownButton
|
||||
bsStyle='secondary'
|
||||
className={styles.button}
|
||||
disabled={props.disabled}
|
||||
id='selectInput'
|
||||
title=''
|
||||
>
|
||||
{map(options, option => (
|
||||
<MenuItem key={option} onClick={() => this._setText(option)}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</DropdownButton>
|
||||
</div>
|
||||
{Input}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -294,10 +294,10 @@ export const getPropertyClausesStrings = function () {
|
||||
|
||||
export const removePropertyClause = function (name) {
|
||||
let type
|
||||
if (
|
||||
!this ||
|
||||
(type = this.type) === 'property' && this.name === name
|
||||
) {
|
||||
if (!this || (
|
||||
(type = this.type) === 'property' &&
|
||||
this.name === name
|
||||
)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ export const setPropertyClause = function (name, child) {
|
||||
return _addAndClause(
|
||||
this,
|
||||
property,
|
||||
node => node.type === 'property' && node.name === name,
|
||||
node => node.type === 'property' && node.name === name
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import _ from 'intl'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import classNames from 'classnames'
|
||||
import Tooltip from 'tooltip'
|
||||
import React, { createElement } from 'react'
|
||||
|
||||
import _ from '../intl'
|
||||
import Button from '../button'
|
||||
import Icon from '../icon'
|
||||
import propTypes from '../prop-types'
|
||||
import Tooltip from '../tooltip'
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
@@ -22,9 +23,9 @@ const Copiable = propTypes({
|
||||
' ',
|
||||
<Tooltip content={_('copyToClipboard')}>
|
||||
<CopyToClipboard text={props.data || props.children}>
|
||||
<button className={classNames('btn btn-sm btn-secondary', styles.button)}>
|
||||
<Button className={styles.button} size='small'>
|
||||
<Icon icon='clipboard' />
|
||||
</button>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</Tooltip>
|
||||
))
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
MenuItem
|
||||
} from 'react-bootstrap-4/lib'
|
||||
|
||||
import Button from '../button'
|
||||
import Component from '../base-component'
|
||||
import getEventValue from '../get-event-value'
|
||||
import propTypes from '../prop-types'
|
||||
@@ -70,9 +71,9 @@ export class Password extends Component {
|
||||
|
||||
return <div className='input-group'>
|
||||
{enableGenerator && <span className='input-group-btn'>
|
||||
<button type='button' className='btn btn-secondary' onClick={this._generate}>
|
||||
<Button onClick={this._generate}>
|
||||
<Icon icon='password' />
|
||||
</button>
|
||||
</Button>
|
||||
</span>}
|
||||
<input
|
||||
{...props}
|
||||
@@ -81,9 +82,9 @@ export class Password extends Component {
|
||||
type={visible ? 'text' : 'password'}
|
||||
/>
|
||||
<span className='input-group-btn'>
|
||||
<button type='button' className='btn btn-secondary' onClick={this._toggleVisibility}>
|
||||
<Button onClick={this._toggleVisibility}>
|
||||
<Icon icon={visible ? 'shown' : 'hidden'} />
|
||||
</button>
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ export default class Toggle extends Component {
|
||||
iconSize: 2
|
||||
}
|
||||
|
||||
_onChange = event => this.props.onChange(event.target.checked)
|
||||
|
||||
render () {
|
||||
const { props } = this
|
||||
|
||||
@@ -43,7 +45,7 @@ export default class Toggle extends Component {
|
||||
checked={props.value || false}
|
||||
className={styles.checkbox}
|
||||
disabled={props.disabled}
|
||||
onChange={props.onChange}
|
||||
onChange={this._onChange}
|
||||
type='checkbox'
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -64,6 +64,15 @@ const POOLS_MISSING_PATCHES_COLUMNS = [{
|
||||
sortCriteria: (host, { pools }) => pools[host.$pool].name_label
|
||||
}].concat(MISSING_PATCHES_COLUMNS)
|
||||
|
||||
// Small component to homogenize Button usage in HostsPatchesTable
|
||||
const ActionButton_ = ({ children, labelId, ...props }) =>
|
||||
<ActionButton
|
||||
{...props}
|
||||
tooltip={_(labelId)}
|
||||
>
|
||||
{children}
|
||||
</ActionButton>
|
||||
|
||||
// ===================================================================
|
||||
|
||||
class HostsPatchesTable extends Component {
|
||||
@@ -150,15 +159,17 @@ class HostsPatchesTable extends Component {
|
||||
const { props } = this
|
||||
|
||||
const Container = props.container || 'div'
|
||||
const Button = props.useTabButton ? TabButton : ActionButton
|
||||
|
||||
const Button = this.props.useTabButton
|
||||
? TabButton
|
||||
: ActionButton_
|
||||
|
||||
const Buttons = (
|
||||
<Container>
|
||||
<Button
|
||||
btnStyle='secondary'
|
||||
handler={this._refreshMissingPatches}
|
||||
icon='refresh'
|
||||
labelId='refreshPatches'
|
||||
labelId='checkForUpdates'
|
||||
/>
|
||||
<Button
|
||||
btnStyle='primary'
|
||||
|
||||
@@ -1605,10 +1605,10 @@ export default {
|
||||
vdiRemove: undefined,
|
||||
|
||||
// Original text: "Boot flag"
|
||||
vdbBootableStatus: 'Etiqueta de Inicio',
|
||||
vbdBootableStatus: 'Etiqueta de Inicio',
|
||||
|
||||
// Original text: "Status"
|
||||
vdbStatus: 'Estado',
|
||||
vbdStatus: 'Estado',
|
||||
|
||||
// Original text: "Connected"
|
||||
vbdStatusConnected: 'Conectado',
|
||||
@@ -1626,19 +1626,19 @@ export default {
|
||||
vbdDisconnect: undefined,
|
||||
|
||||
// Original text: 'Bootable'
|
||||
vdbBootable: undefined,
|
||||
vbdBootable: undefined,
|
||||
|
||||
// Original text: 'Readonly'
|
||||
vdbReadonly: undefined,
|
||||
vbdReadonly: undefined,
|
||||
|
||||
// Original text: 'Create'
|
||||
vdbCreate: undefined,
|
||||
vbdCreate: undefined,
|
||||
|
||||
// Original text: 'Disk name'
|
||||
vdbNamePlaceHolder: undefined,
|
||||
vbdNamePlaceHolder: undefined,
|
||||
|
||||
// Original text: 'Size'
|
||||
vdbSizePlaceHolder: undefined,
|
||||
vbdSizePlaceHolder: undefined,
|
||||
|
||||
// Original text: 'Save'
|
||||
saveBootOption: undefined,
|
||||
|
||||
@@ -1608,10 +1608,10 @@ export default {
|
||||
vdiRemove: 'Supprimer le VDI',
|
||||
|
||||
// Original text: "Boot flag"
|
||||
vdbBootableStatus: 'Boot flag',
|
||||
vbdBootableStatus: 'Boot flag',
|
||||
|
||||
// Original text: "Status"
|
||||
vdbStatus: 'État',
|
||||
vbdStatus: 'État',
|
||||
|
||||
// Original text: "Connected"
|
||||
vbdStatusConnected: 'Connecté',
|
||||
@@ -1629,19 +1629,19 @@ export default {
|
||||
vbdDisconnect: 'Déconnecter un VBD',
|
||||
|
||||
// Original text: "Bootable"
|
||||
vdbBootable: 'Bootable',
|
||||
vbdBootable: 'Bootable',
|
||||
|
||||
// Original text: "Readonly"
|
||||
vdbReadonly: 'Lecture seule',
|
||||
vbdReadonly: 'Lecture seule',
|
||||
|
||||
// Original text: "Create"
|
||||
vdbCreate: 'Créer',
|
||||
vbdCreate: 'Créer',
|
||||
|
||||
// Original text: "Disk name"
|
||||
vdbNamePlaceHolder: 'Nom du disque',
|
||||
vbdNamePlaceHolder: 'Nom du disque',
|
||||
|
||||
// Original text: "Size"
|
||||
vdbSizePlaceHolder: 'Taille',
|
||||
vbdSizePlaceHolder: 'Taille',
|
||||
|
||||
// Original text: "Save"
|
||||
saveBootOption: 'Enregistrer',
|
||||
|
||||
@@ -1605,10 +1605,10 @@ export default {
|
||||
vdiRemove: undefined,
|
||||
|
||||
// Original text: 'Boot flag'
|
||||
vdbBootableStatus: undefined,
|
||||
vbdBootableStatus: undefined,
|
||||
|
||||
// Original text: 'Status'
|
||||
vdbStatus: undefined,
|
||||
vbdStatus: undefined,
|
||||
|
||||
// Original text: 'Connected'
|
||||
vbdStatusConnected: undefined,
|
||||
@@ -1626,19 +1626,19 @@ export default {
|
||||
vbdDisconnect: undefined,
|
||||
|
||||
// Original text: 'Bootable'
|
||||
vdbBootable: undefined,
|
||||
vbdBootable: undefined,
|
||||
|
||||
// Original text: 'Readonly'
|
||||
vdbReadonly: undefined,
|
||||
vbdReadonly: undefined,
|
||||
|
||||
// Original text: 'Create'
|
||||
vdbCreate: undefined,
|
||||
vbdCreate: undefined,
|
||||
|
||||
// Original text: 'Disk name'
|
||||
vdbNamePlaceHolder: undefined,
|
||||
vbdNamePlaceHolder: undefined,
|
||||
|
||||
// Original text: 'Size'
|
||||
vdbSizePlaceHolder: undefined,
|
||||
vbdSizePlaceHolder: undefined,
|
||||
|
||||
// Original text: 'Save'
|
||||
saveBootOption: undefined,
|
||||
|
||||
@@ -1806,10 +1806,10 @@ export default {
|
||||
vdiRemove: 'VDI Eltávolítás',
|
||||
|
||||
// Original text: "Boot flag"
|
||||
vdbBootableStatus: 'Boot flag',
|
||||
vbdBootableStatus: 'Boot flag',
|
||||
|
||||
// Original text: "Status"
|
||||
vdbStatus: 'Állapot',
|
||||
vbdStatus: 'Állapot',
|
||||
|
||||
// Original text: "Connected"
|
||||
vbdStatusConnected: 'Kapcsolódva',
|
||||
@@ -1827,22 +1827,22 @@ export default {
|
||||
vbdDisconnect: 'VBD Lecsatlakozás',
|
||||
|
||||
// Original text: "Bootable"
|
||||
vdbBootable: 'Bootolható',
|
||||
vbdBootable: 'Bootolható',
|
||||
|
||||
// Original text: "Readonly"
|
||||
vdbReadonly: 'Csak olvasható',
|
||||
vbdReadonly: 'Csak olvasható',
|
||||
|
||||
// Original text: 'Action'
|
||||
vbdAction: undefined,
|
||||
|
||||
// Original text: "Create"
|
||||
vdbCreate: 'Létrehozás',
|
||||
vbdCreate: 'Létrehozás',
|
||||
|
||||
// Original text: "Disk name"
|
||||
vdbNamePlaceHolder: 'Diszk név',
|
||||
vbdNamePlaceHolder: 'Diszk név',
|
||||
|
||||
// Original text: "Size"
|
||||
vdbSizePlaceHolder: 'Méret',
|
||||
vbdSizePlaceHolder: 'Méret',
|
||||
|
||||
// Original text: "Save"
|
||||
saveBootOption: 'Mentés',
|
||||
|
||||
3139
src/common/intl/locales/pl.js
Normal file
3139
src/common/intl/locales/pl.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1605,10 +1605,10 @@ export default {
|
||||
vdiRemove: undefined,
|
||||
|
||||
// Original text: "Boot flag"
|
||||
vdbBootableStatus: 'Indicador de inicialização',
|
||||
vbdBootableStatus: 'Indicador de inicialização',
|
||||
|
||||
// Original text: "Status"
|
||||
vdbStatus: 'Status',
|
||||
vbdStatus: 'Status',
|
||||
|
||||
// Original text: "Connected"
|
||||
vbdStatusConnected: 'Conectado',
|
||||
@@ -1626,19 +1626,19 @@ export default {
|
||||
vbdDisconnect: undefined,
|
||||
|
||||
// Original text: 'Bootable'
|
||||
vdbBootable: undefined,
|
||||
vbdBootable: undefined,
|
||||
|
||||
// Original text: 'Readonly'
|
||||
vdbReadonly: undefined,
|
||||
vbdReadonly: undefined,
|
||||
|
||||
// Original text: 'Create'
|
||||
vdbCreate: undefined,
|
||||
vbdCreate: undefined,
|
||||
|
||||
// Original text: 'Disk name'
|
||||
vdbNamePlaceHolder: undefined,
|
||||
vbdNamePlaceHolder: undefined,
|
||||
|
||||
// Original text: 'Size'
|
||||
vdbSizePlaceHolder: undefined,
|
||||
vbdSizePlaceHolder: undefined,
|
||||
|
||||
// Original text: 'Save'
|
||||
saveBootOption: undefined,
|
||||
|
||||
@@ -1203,10 +1203,10 @@ export default {
|
||||
vdiVm: '虚拟机',
|
||||
|
||||
// Original text: "Boot flag"
|
||||
vdbBootableStatus: '启动标识',
|
||||
vbdBootableStatus: '启动标识',
|
||||
|
||||
// Original text: "Status"
|
||||
vdbStatus: '状态',
|
||||
vbdStatus: '状态',
|
||||
|
||||
// Original text: "Connected"
|
||||
vbdStatusConnected: '已连接',
|
||||
|
||||
@@ -23,6 +23,8 @@ var messages = {
|
||||
// ----- Filters -----
|
||||
onError: 'On error',
|
||||
successful: 'Successful',
|
||||
filterNoSnapshots: 'Full disks only',
|
||||
filterOnlySnapshots: 'Snapshots only',
|
||||
|
||||
// ----- Copiable component -----
|
||||
copyToClipboard: 'Copy to clipboard',
|
||||
@@ -245,7 +247,7 @@ var messages = {
|
||||
noJobs: 'No jobs found.',
|
||||
noSchedules: 'No schedules found',
|
||||
jobActionPlaceHolder: 'Select a xo-server API command',
|
||||
jobTimeoutPlaceHolder: ' Job timeout (seconds)',
|
||||
jobTimeoutPlaceHolder: ' Timeout (number of seconds after which a VM is considered failed)',
|
||||
jobSchedules: 'Schedules',
|
||||
jobScheduleNamePlaceHolder: 'Name of your schedule',
|
||||
jobScheduleJobPlaceHolder: 'Select a Job',
|
||||
@@ -382,7 +384,7 @@ var messages = {
|
||||
userLabel: 'User',
|
||||
adminLabel: 'Admin',
|
||||
noUserInGroup: 'No user in group',
|
||||
countUsers: '{users} user{users, plural, one {} other {s}}',
|
||||
countUsers: '{users, number} user{users, plural, one {} other {s}}',
|
||||
selectPermission: 'Select Permission',
|
||||
|
||||
// ----- Plugins ------
|
||||
@@ -500,7 +502,7 @@ var messages = {
|
||||
noHostsAvailableErrorTitle: 'Error while restarting host',
|
||||
noHostsAvailableErrorMessage: 'Some VMs cannot be migrated before restarting this host. Please try force reboot.',
|
||||
failHostBulkRestartTitle: 'Error while restarting hosts',
|
||||
failHostBulkRestartMessage: '{failedHosts}/{totalHosts} host{failedHosts, plural, one {} other {s}} could not be restarted.',
|
||||
failHostBulkRestartMessage: '{failedHosts, number}/{totalHosts, number} host{failedHosts, plural, one {} other {s}} could not be restarted.',
|
||||
rebootUpdateHostLabel: 'Reboot to apply updates',
|
||||
emergencyModeLabel: 'Emergency mode',
|
||||
// ----- Host tabs -----
|
||||
@@ -554,6 +556,7 @@ var messages = {
|
||||
pifStatusDisconnected: 'Disconnected',
|
||||
pifNoInterface: 'No physical interface detected',
|
||||
pifInUse: 'This interface is currently in use',
|
||||
pifAction: 'Action',
|
||||
defaultLockingMode: 'Default locking mode',
|
||||
pifConfigureIp: 'Configure IP address',
|
||||
configIpErrorTitle: 'Invalid parameters',
|
||||
@@ -595,6 +598,7 @@ var messages = {
|
||||
// ----- Pool patch tabs -----
|
||||
refreshPatches: 'Refresh patches',
|
||||
installPoolPatches: 'Install pool patches',
|
||||
checkForUpdates: 'Check for updates',
|
||||
// ----- Pool storage tabs -----
|
||||
defaultSr: 'Default SR',
|
||||
setAsDefaultSr: 'Set as default SR',
|
||||
@@ -680,19 +684,21 @@ var messages = {
|
||||
vdiMigrateNoSrMessage: 'A target SR is required to migrate a VDI',
|
||||
vdiForget: 'Forget',
|
||||
vdiRemove: 'Remove VDI',
|
||||
vdbBootableStatus: 'Boot flag',
|
||||
vdbStatus: 'Status',
|
||||
vbdBootableStatus: 'Boot flag',
|
||||
vbdStatus: 'Status',
|
||||
vbdStatusConnected: 'Connected',
|
||||
vbdStatusDisconnected: 'Disconnected',
|
||||
vbdNoVbd: 'No disks',
|
||||
vbdConnect: 'Connect VBD',
|
||||
vbdDisconnect: 'Disconnect VBD',
|
||||
vdbBootable: 'Bootable',
|
||||
vdbReadonly: 'Readonly',
|
||||
vbdBootable: 'Bootable',
|
||||
vbdReadonly: 'Readonly',
|
||||
vbdAction: 'Action',
|
||||
vdbCreate: 'Create',
|
||||
vdbNamePlaceHolder: 'Disk name',
|
||||
vdbSizePlaceHolder: 'Size',
|
||||
vbdCreate: 'Create',
|
||||
vbdNamePlaceHolder: 'Disk name',
|
||||
vbdSizePlaceHolder: 'Size',
|
||||
cdDriveNotInstalled: 'CD drive not completely installed',
|
||||
cdDriveInstallation: 'Stop and start the VM to install the CD drive',
|
||||
saveBootOption: 'Save',
|
||||
resetBootOption: 'Reset',
|
||||
|
||||
@@ -764,6 +770,8 @@ var messages = {
|
||||
autoPowerOn: 'Auto power on',
|
||||
ha: 'HA',
|
||||
vmAffinityHost: 'Affinity host',
|
||||
vmVga: 'VGA',
|
||||
vmVideoram: 'Video RAM',
|
||||
noAffinityHost: 'None',
|
||||
originalTemplate: 'Original template',
|
||||
unknownOsName: 'Unknown',
|
||||
@@ -812,7 +820,7 @@ var messages = {
|
||||
srFree: 'free',
|
||||
srUsageStatePanel: 'Storage Usage',
|
||||
srTopUsageStatePanel: 'Top 5 SR Usage (in %)',
|
||||
vmsStates: '{running} running ({halted} halted)',
|
||||
vmsStates: '{running, number} running ({halted, number} halted)',
|
||||
dashboardStatsButtonRemoveAll: 'Clear selection',
|
||||
dashboardStatsButtonAddAllHost: 'Add all hosts',
|
||||
dashboardStatsButtonAddAllVM: 'Add all VMs',
|
||||
@@ -891,7 +899,7 @@ var messages = {
|
||||
newVmDefaultCpuCap: 'Default: {value, number}',
|
||||
newVmCloudConfig: 'Cloud config',
|
||||
newVmCreateVms: 'Create VMs',
|
||||
newVmCreateVmsConfirm: 'Are you sure you want to create {nbVms} VMs?',
|
||||
newVmCreateVmsConfirm: 'Are you sure you want to create {nbVms, number} VMs?',
|
||||
newVmMultipleVms: 'Multiple VMs:',
|
||||
newVmSelectResourceSet: 'Select a resource set:',
|
||||
newVmMultipleVmsPattern: 'Name pattern:',
|
||||
@@ -1005,7 +1013,7 @@ var messages = {
|
||||
|
||||
// ----- Modals -----
|
||||
emergencyShutdownHostsModalTitle: 'Emergency shutdown Host{nHosts, plural, one {} other {s}}',
|
||||
emergencyShutdownHostsModalMessage: 'Are you sure you want to shutdown {nHosts} Host{nHosts, plural, one {} other {s}}?',
|
||||
emergencyShutdownHostsModalMessage: 'Are you sure you want to shutdown {nHosts, number} Host{nHosts, plural, one {} other {s}}?',
|
||||
stopHostModalTitle: 'Shutdown host',
|
||||
stopHostModalMessage: 'This will shutdown your host. Do you want to continue? If it\'s the pool master, your connection to the pool will be lost',
|
||||
addHostModalTitle: 'Add host',
|
||||
@@ -1013,25 +1021,25 @@ var messages = {
|
||||
restartHostModalTitle: 'Restart host',
|
||||
restartHostModalMessage: 'This will restart your host. Do you want to continue?',
|
||||
restartHostsAgentsModalTitle: 'Restart Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}',
|
||||
restartHostsAgentsModalMessage: 'Are you sure you want to restart {nHosts} Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}?',
|
||||
restartHostsAgentsModalMessage: 'Are you sure you want to restart {nHosts, number} Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}?',
|
||||
restartHostsModalTitle: 'Restart Host{nHosts, plural, one {} other {s}}',
|
||||
restartHostsModalMessage: 'Are you sure you want to restart {nHosts} Host{nHosts, plural, one {} other {s}}?',
|
||||
restartHostsModalMessage: 'Are you sure you want to restart {nHosts, number} Host{nHosts, plural, one {} other {s}}?',
|
||||
startVmsModalTitle: 'Start VM{vms, plural, one {} other {s}}',
|
||||
startVmsModalMessage: 'Are you sure you want to start {vms} VM{vms, plural, one {} other {s}}?',
|
||||
startVmsModalMessage: 'Are you sure you want to start {vms, number} VM{vms, plural, one {} other {s}}?',
|
||||
stopHostsModalTitle: 'Stop Host{nHosts, plural, one {} other {s}}',
|
||||
stopHostsModalMessage: 'Are you sure you want to stop {nHosts} Host{nHosts, plural, one {} other {s}}?',
|
||||
stopHostsModalMessage: 'Are you sure you want to stop {nHosts, number} Host{nHosts, plural, one {} other {s}}?',
|
||||
stopVmsModalTitle: 'Stop VM{vms, plural, one {} other {s}}',
|
||||
stopVmsModalMessage: 'Are you sure you want to stop {vms} VM{vms, plural, one {} other {s}}?',
|
||||
stopVmsModalMessage: 'Are you sure you want to stop {vms, number} VM{vms, plural, one {} other {s}}?',
|
||||
restartVmModalTitle: 'Restart VM',
|
||||
restartVmModalMessage: 'Are you sure you want to restart {name}?',
|
||||
stopVmModalTitle: 'Stop VM',
|
||||
stopVmModalMessage: 'Are you sure you want to stop {name}?',
|
||||
restartVmsModalTitle: 'Restart VM{vms, plural, one {} other {s}}',
|
||||
restartVmsModalMessage: 'Are you sure you want to restart {vms} VM{vms, plural, one {} other {s}}?',
|
||||
restartVmsModalMessage: 'Are you sure you want to restart {vms, number} VM{vms, plural, one {} other {s}}?',
|
||||
snapshotVmsModalTitle: 'Snapshot VM{vms, plural, one {} other {s}}',
|
||||
snapshotVmsModalMessage: 'Are you sure you want to snapshot {vms} VM{vms, plural, one {} other {s}}?',
|
||||
snapshotVmsModalMessage: 'Are you sure you want to snapshot {vms, number} VM{vms, plural, one {} other {s}}?',
|
||||
deleteVmsModalTitle: 'Delete VM{vms, plural, one {} other {s}}',
|
||||
deleteVmsModalMessage: 'Are you sure you want to delete {vms} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED',
|
||||
deleteVmsModalMessage: 'Are you sure you want to delete {vms, number} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED',
|
||||
deleteVmModalTitle: 'Delete VM',
|
||||
deleteVmModalMessage: 'Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED',
|
||||
migrateVmModalTitle: 'Migrate VM',
|
||||
@@ -1213,6 +1221,10 @@ var messages = {
|
||||
disconnectPifConfirm: 'Are you sure you want to disconnect this PIF?',
|
||||
deletePif: 'Delete PIF',
|
||||
deletePifConfirm: 'Are you sure you want to delete this PIF?',
|
||||
pifConnected: 'Connected',
|
||||
pifDisconnected: 'Disconnected',
|
||||
pifPhysicallyConnected: 'Physically connected',
|
||||
pifPhysicallyDisconnected: 'Physically disconnected',
|
||||
|
||||
// ----- User -----
|
||||
username: 'Username',
|
||||
@@ -1339,6 +1351,9 @@ var messages = {
|
||||
xosanUsedSpace: 'Used space',
|
||||
xosanNeedPack: 'XOSAN pack needs to be installed on each host of the pool.',
|
||||
xosanInstallIt: 'Install it now!',
|
||||
xosanNeedRestart: 'Some hosts need their toolstack to be restarted before you can create an XOSAN',
|
||||
xosanRestartAgents: 'Restart toolstacks',
|
||||
xosanMasterOffline: 'Pool master is not running',
|
||||
xosanInstallPackTitle: 'Install XOSAN pack on {pool}',
|
||||
xosanSelect2Srs: 'Select at least 2 SRs',
|
||||
xosanLayout: 'Layout',
|
||||
|
||||
@@ -28,7 +28,7 @@ function assertIpv4 (str, msg) {
|
||||
if (!ipv4.test(str)) { throw new Error(msg) }
|
||||
}
|
||||
|
||||
function *range (ip1, ip2) {
|
||||
function * range (ip1, ip2) {
|
||||
assertIpv4(ip1, 'argument "ip1" must be a valid IPv4 address')
|
||||
assertIpv4(ip2, 'argument "ip2" must be a valid IPv4 address')
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React from 'react'
|
||||
|
||||
import _ from 'intl'
|
||||
import ActionButton from './action-button'
|
||||
import Component from './base-component'
|
||||
import Icon from 'icon'
|
||||
import propTypes from './prop-types'
|
||||
import Tooltip from 'tooltip'
|
||||
import { alert } from 'modal'
|
||||
import { connectStore } from './utils'
|
||||
import { SelectVdi } from './select-objects'
|
||||
import {
|
||||
@@ -51,8 +55,9 @@ export default class IsoDevice extends Component {
|
||||
const samePool = vmPool === sr.$pool
|
||||
|
||||
return (
|
||||
samePool && (vmRunning ? sr.shared || sameHost : true) &&
|
||||
sr.SR_type === 'iso' || sr.SR_type === 'udev' && sr.size
|
||||
samePool &&
|
||||
(vmRunning ? sr.shared || sameHost : true) &&
|
||||
(sr.SR_type === 'iso' || (sr.SR_type === 'udev' && sr.size))
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -69,8 +74,10 @@ export default class IsoDevice extends Component {
|
||||
|
||||
_handleEject = () => ejectCd(this.props.vm)
|
||||
|
||||
_showWarning = () => alert(_('cdDriveNotInstalled'), _('cdDriveInstallation'))
|
||||
|
||||
render () {
|
||||
const { mountedIso } = this.props
|
||||
const {cdDrive, mountedIso} = this.props
|
||||
|
||||
return (
|
||||
<div className='input-group'>
|
||||
@@ -81,12 +88,24 @@ export default class IsoDevice extends Component {
|
||||
/>
|
||||
<span className='input-group-btn'>
|
||||
<ActionButton
|
||||
btnStyle='secondary'
|
||||
disabled={!mountedIso}
|
||||
handler={this._handleEject}
|
||||
icon='vm-eject'
|
||||
/>
|
||||
</span>
|
||||
{mountedIso && !cdDrive.device &&
|
||||
<Tooltip content={_('cdDriveNotInstalled')}>
|
||||
<a
|
||||
className='text-warning btn btn-link'
|
||||
onClick={this._showWarning}
|
||||
>
|
||||
<Icon
|
||||
icon='alarm'
|
||||
size='lg'
|
||||
/>
|
||||
</a>
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import uncontrollableInput from 'uncontrollable-input'
|
||||
import { filter, map } from 'lodash'
|
||||
|
||||
import _ from '../intl'
|
||||
import Button from '../button'
|
||||
import Component from '../base-component'
|
||||
import propTypes from '../prop-types'
|
||||
import { EMPTY_ARRAY } from '../utils'
|
||||
@@ -96,26 +97,26 @@ export default class ObjectInput extends Component {
|
||||
uiSchema={itemUiSchema}
|
||||
value={value}
|
||||
/>
|
||||
<button
|
||||
className='btn btn-danger pull-right'
|
||||
<Button
|
||||
btnStyle='danger'
|
||||
className='pull-right'
|
||||
disabled={disabled}
|
||||
name={key}
|
||||
onClick={() => this._onRemoveItem(key)}
|
||||
type='button'
|
||||
>
|
||||
{_('remove')}
|
||||
</button>
|
||||
</Button>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
<button
|
||||
className='btn btn-primary pull-right mt-1 mr-1'
|
||||
<Button
|
||||
btnStyle='primary'
|
||||
className='pull-right mt-1 mr-1'
|
||||
disabled={disabled}
|
||||
onClick={this._onAddItem}
|
||||
type='button'
|
||||
>
|
||||
{_('add')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ export default class ObjectInput extends Component {
|
||||
} = this
|
||||
|
||||
const childDepth = depth + 2
|
||||
const properties = uiSchema && uiSchema.properties || EMPTY_OBJECT
|
||||
const properties = (uiSchema != null && uiSchema.properties) || EMPTY_OBJECT
|
||||
const requiredProps = this._getRequiredProps()
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import _ from 'intl'
|
||||
import Icon from 'icon'
|
||||
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 { Modal as ReactModal } from 'react-bootstrap-4/lib'
|
||||
|
||||
import _ from './intl'
|
||||
import Button from './button'
|
||||
import Icon from './icon'
|
||||
import propTypes from './prop-types'
|
||||
import {
|
||||
disable as disableShortcuts,
|
||||
@@ -75,8 +76,6 @@ class Confirm extends Component {
|
||||
instance.close()
|
||||
}
|
||||
|
||||
_style = { marginRight: '0.5em' }
|
||||
|
||||
render () {
|
||||
const { Body, Footer, Header, Title } = ReactModal
|
||||
const { title, icon } = this.props
|
||||
@@ -97,14 +96,14 @@ class Confirm extends Component {
|
||||
</Body>
|
||||
<Footer>
|
||||
<Button
|
||||
bsStyle='primary'
|
||||
btnStyle='primary'
|
||||
onClick={this._resolve}
|
||||
style={this._style}
|
||||
>
|
||||
{_('confirmOk')}
|
||||
</Button>
|
||||
{' '}
|
||||
<Button
|
||||
bsStyle='secondary'
|
||||
onClick={this._reject}
|
||||
>
|
||||
{_('confirmCancel')}
|
||||
|
||||
5
src/common/react-novnc.js
vendored
5
src/common/react-novnc.js
vendored
@@ -77,6 +77,11 @@ export default class NoVnc extends Component {
|
||||
_connect = () => {
|
||||
this._clean()
|
||||
|
||||
const { canvas } = this.refs
|
||||
if (!canvas) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = parseRelativeUrl(this.props.url)
|
||||
fixProtocol(url)
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import classNames from 'classnames'
|
||||
import Icon from 'icon'
|
||||
import later from 'later'
|
||||
import React from 'react'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Toggle } from 'form'
|
||||
import { FormattedDate, FormattedTime } from 'react-intl'
|
||||
import {
|
||||
forEach,
|
||||
@@ -14,12 +11,15 @@ import {
|
||||
} from 'lodash'
|
||||
|
||||
import _ from './intl'
|
||||
import Button from './button'
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types'
|
||||
import TimezonePicker from './timezone-picker'
|
||||
import Icon from './icon'
|
||||
import Tooltip from './tooltip'
|
||||
import { Card, CardHeader, CardBlock } from './card'
|
||||
import { Col, Row } from './grid'
|
||||
import { Range } from './form'
|
||||
import { Range, Toggle } from './form'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -259,9 +259,12 @@ class TableSelect extends Component {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className='btn btn-secondary pull-right' onClick={this._reset}>
|
||||
<Button
|
||||
className='pull-right'
|
||||
onClick={this._reset}
|
||||
>
|
||||
{_(`selectTableAll${labelId}`)} {value && !value.length && <Icon icon='success' />}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -447,23 +450,27 @@ class DayPicker extends Component {
|
||||
// ===================================================================
|
||||
|
||||
@propTypes({
|
||||
cronPattern: propTypes.string.isRequired,
|
||||
cronPattern: propTypes.string,
|
||||
onChange: propTypes.func,
|
||||
timezone: propTypes.string
|
||||
timezone: propTypes.string,
|
||||
value: propTypes.shape({
|
||||
cronPattern: propTypes.string.isRequired,
|
||||
timezone: propTypes.string
|
||||
})
|
||||
})
|
||||
export default class Scheduler extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this._onCronChange = newCrons => {
|
||||
const cronPattern = this.props.cronPattern.split(' ')
|
||||
const cronPattern = this._getCronPattern().split(' ')
|
||||
forEach(newCrons, (cron, unit) => {
|
||||
cronPattern[PICKTIME_TO_ID[unit]] = cron
|
||||
})
|
||||
|
||||
this.props.onChange({
|
||||
cronPattern: cronPattern.join(' '),
|
||||
timezone: this.props.timezone
|
||||
timezone: this._getTimezone()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -475,17 +482,24 @@ export default class Scheduler extends Component {
|
||||
|
||||
_onTimezoneChange = timezone => {
|
||||
this.props.onChange({
|
||||
cronPattern: this.props.cronPattern,
|
||||
cronPattern: this._getCronPattern(),
|
||||
timezone
|
||||
})
|
||||
}
|
||||
|
||||
_getCronPattern = () => {
|
||||
const { value, cronPattern = value.cronPattern } = this.props
|
||||
return cronPattern
|
||||
}
|
||||
|
||||
_getTimezone = () => {
|
||||
const { value, timezone = value && value.timezone } = this.props
|
||||
return timezone
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
cronPattern,
|
||||
timezone
|
||||
} = this.props
|
||||
const cronPatternArr = cronPattern.split(' ')
|
||||
const cronPatternArr = this._getCronPattern().split(' ')
|
||||
const timezone = this._getTimezone()
|
||||
|
||||
return (
|
||||
<div className='card-block'>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import Icon from 'icon'
|
||||
import store from 'store'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
import { parse as parseRemote } from 'xo-remote-parser'
|
||||
import {
|
||||
assign,
|
||||
@@ -26,10 +22,14 @@ import {
|
||||
} from 'lodash'
|
||||
|
||||
import _ from './intl'
|
||||
import uncontrollableInput from 'uncontrollable-input'
|
||||
import Button from './button'
|
||||
import Component from './base-component'
|
||||
import Icon from './icon'
|
||||
import propTypes from './prop-types'
|
||||
import renderXoItem from './render-xo-item'
|
||||
import store from './store'
|
||||
import Tooltip from './tooltip'
|
||||
import uncontrollableInput from 'uncontrollable-input'
|
||||
import { Select } from './form'
|
||||
import {
|
||||
createCollectionWrapper,
|
||||
@@ -150,19 +150,17 @@ export class GenericSelect extends Component {
|
||||
(containers, objects) => { // createCollectionWrapper with a depth?
|
||||
const { name } = this.constructor
|
||||
|
||||
let options = []
|
||||
if (!containers) {
|
||||
if (__DEV__ && !isArray(objects)) {
|
||||
throw new Error(`${name}: without xoContainers, xoObjects must be an array`)
|
||||
}
|
||||
|
||||
return map(objects, getOption)
|
||||
}
|
||||
|
||||
if (__DEV__ && isArray(objects)) {
|
||||
options = map(objects, getOption)
|
||||
} else if (__DEV__ && isArray(objects)) {
|
||||
throw new Error(`${name}: with xoContainers, xoObjects must be an object`)
|
||||
}
|
||||
|
||||
const options = []
|
||||
forEach(containers, container => {
|
||||
options.push({
|
||||
disabled: true,
|
||||
@@ -173,11 +171,13 @@ export class GenericSelect extends Component {
|
||||
options.push(getOption(object, container))
|
||||
})
|
||||
})
|
||||
|
||||
const values = this._getSelectValue()
|
||||
const objectsById = this._getObjectsById()
|
||||
forEach(values, val => {
|
||||
if (!objectsById[val]) {
|
||||
const addIfMissing = val => {
|
||||
if (val && !objectsById[val]) {
|
||||
options.push({
|
||||
disabled: true,
|
||||
id: val,
|
||||
label: val,
|
||||
value: val,
|
||||
@@ -187,7 +187,14 @@ export class GenericSelect extends Component {
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isArray(values)) {
|
||||
forEach(values, addIfMissing)
|
||||
} else {
|
||||
addIfMissing(values)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
)
|
||||
@@ -271,7 +278,7 @@ export class GenericSelect extends Component {
|
||||
{select}
|
||||
<span className='input-group-btn'>
|
||||
<Tooltip content={_('selectAll')}>
|
||||
<Button type='button' bsStyle='secondary' onClick={this._selectAll} style={ADDON_BUTTON_STYLE}>
|
||||
<Button onClick={this._selectAll} style={ADDON_BUTTON_STYLE}>
|
||||
<Icon icon='add' />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -347,7 +347,7 @@ export const createSortForType = invoke(() => {
|
||||
return (type, collection) => createSort(
|
||||
collection,
|
||||
autoSelector(type, getIteratees),
|
||||
autoSelector(type, getOrders),
|
||||
autoSelector(type, getOrders)
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import DropdownMenu from 'react-bootstrap-4/lib/DropdownMenu' // https://phabric
|
||||
import DropdownToggle from 'react-bootstrap-4/lib/DropdownToggle' // https://phabricator.babeljs.io/T6662 so Dropdown.Toggle won't work https://react-bootstrap.github.io/components.html#btn-dropdowns-custom
|
||||
import { Portal } from 'react-overlays'
|
||||
|
||||
import Button from '../button'
|
||||
import Component from '../base-component'
|
||||
import Icon from '../icon'
|
||||
import propTypes from '../prop-types'
|
||||
@@ -80,9 +81,9 @@ class TableFilter extends Component {
|
||||
className='form-control'
|
||||
/>
|
||||
<div className='input-group-btn'>
|
||||
<button className='btn btn-secondary' onClick={this._cleanFilter}>
|
||||
<Button onClick={this._cleanFilter}>
|
||||
<Icon icon='clear-search' />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -148,7 +149,8 @@ const DEFAULT_ITEMS_PER_PAGE = 10
|
||||
propTypes.func,
|
||||
propTypes.string
|
||||
]),
|
||||
sortOrder: propTypes.string
|
||||
sortOrder: propTypes.string,
|
||||
textAlign: propTypes.string
|
||||
})).isRequired,
|
||||
filterContainer: propTypes.func,
|
||||
filters: propTypes.object,
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class TimezonePicker extends Component {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
timezone: option && option.value || SERVER_TIMEZONE_TAG
|
||||
timezone: (option != null && option.value) || SERVER_TIMEZONE_TAG
|
||||
}, () =>
|
||||
this.props.onChange(this.state.timezone === SERVER_TIMEZONE_TAG ? null : this.state.timezone)
|
||||
)
|
||||
@@ -81,7 +81,6 @@ export default class TimezonePicker extends Component {
|
||||
/>
|
||||
<div className='pull-right'>
|
||||
<ActionButton
|
||||
btnStyle='secondary'
|
||||
handler={this._useLocalTime}
|
||||
icon='time'
|
||||
>
|
||||
|
||||
@@ -280,8 +280,8 @@ const getParent = (currentTarget) => {
|
||||
currentParent = currentParent.parentElement
|
||||
}
|
||||
|
||||
const parentTop = currentParent && currentParent.getBoundingClientRect().top || 0
|
||||
const parentLeft = currentParent && currentParent.getBoundingClientRect().left || 0
|
||||
const parentTop = currentParent && currentParent.getBoundingClientRect().top
|
||||
const parentLeft = currentParent && currentParent.getBoundingClientRect().left
|
||||
|
||||
return {parentTop, parentLeft}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import mapValues from 'lodash/mapValues'
|
||||
import React from 'react'
|
||||
import ReadableStream from 'readable-stream'
|
||||
import replace from 'lodash/replace'
|
||||
import startsWith from 'lodash/startsWith'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import _ from './intl'
|
||||
@@ -63,13 +64,22 @@ export const addSubscriptions = subscriptions => Component => {
|
||||
|
||||
componentWillMount () {
|
||||
this._unsubscribes = map(isFunction(subscriptions) ? subscriptions() : subscriptions, (subscribe, prop) =>
|
||||
subscribe(value => this.setState({ [prop]: value }))
|
||||
subscribe(value => this._setState({ [prop]: value }))
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._setState = this.setState
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
forEach(this._unsubscribes, unsubscribe => unsubscribe())
|
||||
this._unsubscribes = null
|
||||
delete this._setState
|
||||
}
|
||||
|
||||
_setState (nextState) {
|
||||
this.state = { ...this.state, nextState }
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -524,3 +534,6 @@ export const compareVersions = makeNiceCompare((v1, v2) => {
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
export const isXosanPack = ({ name }) =>
|
||||
startsWith(name, 'XOSAN')
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import classNames from 'classnames'
|
||||
import every from 'lodash/every'
|
||||
import map from 'lodash/map'
|
||||
import React, { Component, cloneElement } from 'react'
|
||||
|
||||
import _ from '../intl'
|
||||
@@ -10,9 +9,14 @@ import propTypes from '../prop-types'
|
||||
import styles from './index.css'
|
||||
|
||||
const Wizard = ({ children }) => {
|
||||
const allDone = every(React.Children.toArray(children), (child) => child.props.done || child.props.summary)
|
||||
const allDone = every(React.Children.toArray(children), child =>
|
||||
child.props.done || child.props.summary
|
||||
)
|
||||
|
||||
return <ul className={styles.wizard}>
|
||||
{map(React.Children.toArray(children), (child, key) => cloneElement(child, { allDone, key }))}
|
||||
{React.Children.map(children, (child, key) =>
|
||||
child && cloneElement(child, { allDone, key })
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
export { Wizard as default }
|
||||
|
||||
@@ -5,7 +5,7 @@ import getEventValue from '../get-event-value'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const getId = value => value != null && value.id || value
|
||||
const getId = value => (value != null && value.id) || value
|
||||
|
||||
export default class XoAbstractInput extends PureComponent {
|
||||
_onChange = event => {
|
||||
|
||||
@@ -381,7 +381,7 @@ export default class XoWeekCharts extends Component {
|
||||
<p className='mt-1'>
|
||||
{_('weeklyChartsScaleInfo')}
|
||||
{' '}
|
||||
<Toggle iconSize={1} icon='scale' className='btn btn-secondary' onChange={this._updateScale} />
|
||||
<Toggle iconSize={1} icon='scale' onChange={this._updateScale} />
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -41,6 +41,10 @@ export const XEN_DEFAULT_CPU_CAP = 0
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const XEN_VIDEORAM_VALUES = [1, 2, 4, 8, 16]
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const isSrWritable = sr => sr && sr.content_type !== 'iso' && sr.size > 0
|
||||
export const isSrShared = sr => sr && sr.$PBDs.length > 1
|
||||
export const isVmRunning = vm => vm && vm.power_state === 'Running'
|
||||
@@ -342,7 +346,7 @@ export const editPool = (pool, props) => (
|
||||
_call('pool.set', { id: resolveId(pool), ...props })
|
||||
)
|
||||
|
||||
import AddHostModalBody from './add-host-modal'
|
||||
import AddHostModalBody from './add-host-modal' // eslint-disable-line import/first
|
||||
export const addHostToPool = (pool, host) => {
|
||||
if (host) {
|
||||
return confirm({
|
||||
@@ -436,7 +440,7 @@ export const restartHostsAgents = hosts => {
|
||||
title: _('restartHostsAgentsModalTitle', { nHosts }),
|
||||
body: _('restartHostsAgentsModalMessage', { nHosts })
|
||||
}).then(
|
||||
() => map(hosts, restartHostAgent),
|
||||
() => Promise.all(map(hosts, restartHostAgent)),
|
||||
noop
|
||||
)
|
||||
}
|
||||
@@ -643,7 +647,7 @@ export const cloneVm = ({ id, name_label: nameLabel }, fullCopy = false) => (
|
||||
})
|
||||
)
|
||||
|
||||
import CopyVmModalBody from './copy-vm-modal'
|
||||
import CopyVmModalBody from './copy-vm-modal' // eslint-disable-line import/first
|
||||
export const copyVm = (vm, sr, name, compress) => {
|
||||
if (sr) {
|
||||
return confirm({
|
||||
@@ -667,7 +671,7 @@ export const copyVm = (vm, sr, name, compress) => {
|
||||
}
|
||||
}
|
||||
|
||||
import CopyVmsModalBody from './copy-vms-modal'
|
||||
import CopyVmsModalBody from './copy-vms-modal' // eslint-disable-line import/first
|
||||
export const copyVms = vms => {
|
||||
const _vms = resolveIds(vms)
|
||||
return confirm({
|
||||
@@ -685,7 +689,7 @@ export const copyVms = vms => {
|
||||
sr
|
||||
} = params
|
||||
Promise.all(map(_vms, (vm, index) =>
|
||||
_call('vm.copy', { vm, sr, compress, name: names[index] }),
|
||||
_call('vm.copy', { vm, sr, compress, name: names[index] })
|
||||
))
|
||||
},
|
||||
noop
|
||||
@@ -739,7 +743,7 @@ export const deleteSnapshot = vm => (
|
||||
)
|
||||
)
|
||||
|
||||
import MigrateVmModalBody from './migrate-vm-modal'
|
||||
import MigrateVmModalBody from './migrate-vm-modal' // eslint-disable-line import/first
|
||||
export const migrateVm = (vm, host) => (
|
||||
confirm({
|
||||
title: _('migrateVmModalTitle'),
|
||||
@@ -755,7 +759,7 @@ export const migrateVm = (vm, host) => (
|
||||
)
|
||||
)
|
||||
|
||||
import MigrateVmsModalBody from './migrate-vms-modal'
|
||||
import MigrateVmsModalBody from './migrate-vms-modal' // eslint-disable-line import/first
|
||||
export const migrateVms = vms => (
|
||||
confirm({
|
||||
title: _('migrateVmModalTitle'),
|
||||
@@ -838,7 +842,7 @@ export const importDeltaBackup = ({ remote, file, sr }) => (
|
||||
_call('vm.importDeltaBackup', resolveIds({ remote, filePath: file, sr }))
|
||||
)
|
||||
|
||||
import RevertSnapshotModalBody from './revert-snapshot-modal'
|
||||
import RevertSnapshotModalBody from './revert-snapshot-modal' // eslint-disable-line import/first
|
||||
export const revertSnapshot = vm => (
|
||||
confirm({
|
||||
title: _('revertVmModalTitle'),
|
||||
@@ -911,7 +915,7 @@ export const attachDiskToVm = (vdi, vm, { bootable, mode, position }) => (
|
||||
_call('vm.attachDisk', {
|
||||
bootable,
|
||||
mode,
|
||||
position: position && String(position) || undefined,
|
||||
position: (position && String(position)) || undefined,
|
||||
vdi: resolveId(vdi),
|
||||
vm: resolveId(vm)
|
||||
})
|
||||
@@ -960,7 +964,7 @@ export const migrateVdi = (vdi, sr) => (
|
||||
_call('vdi.migrate', { id: resolveId(vdi), sr_id: resolveId(sr) })
|
||||
)
|
||||
|
||||
// VDB ---------------------------------------------------------------
|
||||
// VBD ---------------------------------------------------------------
|
||||
|
||||
export const connectVbd = vbd => (
|
||||
_call('vbd.connect', { id: resolveId(vbd) })
|
||||
@@ -1010,7 +1014,7 @@ export const editNetwork = (network, props) => (
|
||||
_call('network.set', { ...props, id: resolveId(network) })
|
||||
)
|
||||
|
||||
import CreateNetworkModalBody from './create-network-modal'
|
||||
import CreateNetworkModalBody from './create-network-modal' // eslint-disable-line import/first
|
||||
export const createNetwork = container => (
|
||||
confirm({
|
||||
icon: 'network',
|
||||
@@ -1030,7 +1034,7 @@ export const createNetwork = container => (
|
||||
export const getBondModes = () =>
|
||||
_call('network.getBondModes')
|
||||
|
||||
import CreateBondedNetworkModalBody from './create-bonded-network-modal'
|
||||
import CreateBondedNetworkModalBody from './create-bonded-network-modal' // eslint-disable-line import/first
|
||||
export const createBondedNetwork = container => (
|
||||
confirm({
|
||||
icon: 'network',
|
||||
@@ -1321,7 +1325,7 @@ export const loadPlugin = async id => (
|
||||
_call('plugin.load', { id })::tap(
|
||||
subscribePlugins.forceRefresh
|
||||
)::rethrow(
|
||||
err => error(_('pluginError'), err && err.message || _('unknownPluginError'))
|
||||
err => error(_('pluginError'), (err && err.message) || _('unknownPluginError'))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1329,7 +1333,7 @@ export const unloadPlugin = id => (
|
||||
_call('plugin.unload', { id })::tap(
|
||||
subscribePlugins.forceRefresh
|
||||
)::rethrow(
|
||||
err => error(_('pluginError'), err && err.message || _('unknownPluginError'))
|
||||
err => error(_('pluginError'), (err && err.message) || _('unknownPluginError'))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1667,10 +1671,10 @@ const _setUserPreferences = preferences => (
|
||||
)
|
||||
)
|
||||
|
||||
import NewSshKeyModalBody from './new-ssh-key-modal'
|
||||
import NewSshKeyModalBody from './new-ssh-key-modal' // eslint-disable-line import/first
|
||||
export const addSshKey = key => {
|
||||
const { preferences } = xo.user
|
||||
const otherKeys = preferences && preferences.sshKeys || []
|
||||
const otherKeys = (preferences && preferences.sshKeys) || []
|
||||
if (key) {
|
||||
return _setUserPreferences({ sshKeys: [
|
||||
...otherKeys,
|
||||
@@ -1713,7 +1717,7 @@ export const deleteSshKey = key => (
|
||||
|
||||
// User filters --------------------------------------------------
|
||||
|
||||
import AddUserFilterModalBody from './add-user-filter-modal'
|
||||
import AddUserFilterModalBody from './add-user-filter-modal' // eslint-disable-line import/first
|
||||
export const addCustomFilter = (type, value) => {
|
||||
const { user } = xo
|
||||
return confirm({
|
||||
@@ -1823,7 +1827,7 @@ export const createXosanSR = ({ template, pif, vlan, srs, glusterType, redundanc
|
||||
|
||||
export const computeXosanPossibleOptions = lvmSrs => _call('xosan.computeXosanPossibleOptions', { lvmSrs })
|
||||
|
||||
import InstallXosanPackModal from './install-xosan-pack-modal'
|
||||
import InstallXosanPackModal from './install-xosan-pack-modal' // eslint-disable-line import/first
|
||||
export const downloadAndInstallXosanPack = pool =>
|
||||
confirm({
|
||||
title: _('xosanInstallPackTitle', { pool: pool.name_label }),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'intl'
|
||||
import Component from 'base-component'
|
||||
import React from 'react'
|
||||
import { connectStore, compareVersions } from 'utils'
|
||||
import { connectStore, compareVersions, isXosanPack } from 'utils'
|
||||
import { subscribeResourceCatalog, subscribePlugins } from 'xo'
|
||||
import { createGetObjectsOfType, createSelector, createCollectionWrapper } from 'selectors'
|
||||
import { satisfies as versionSatisfies } from 'semver'
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
every,
|
||||
filter,
|
||||
forEach,
|
||||
map
|
||||
map,
|
||||
some
|
||||
} from 'lodash'
|
||||
|
||||
const findLatestPack = (packs, hostsVersions) => {
|
||||
@@ -37,11 +38,16 @@ const findLatestPack = (packs, hostsVersions) => {
|
||||
return latestPack
|
||||
}
|
||||
|
||||
@connectStore({
|
||||
@connectStore(() => ({
|
||||
hosts: createGetObjectsOfType('host').filter(
|
||||
(_, { pool }) => host => pool && host.$pool === pool.id && !host.supplementalPacks['vates:XOSAN']
|
||||
createSelector(
|
||||
(_, { pool }) => pool != null && pool.id,
|
||||
poolId => poolId
|
||||
? host => host.$pool === poolId && !some(host.supplementalPacks, isXosanPack)
|
||||
: false
|
||||
)
|
||||
)
|
||||
}, { withRef: true })
|
||||
}), { withRef: true })
|
||||
export default class InstallXosanPackModal extends Component {
|
||||
componentDidMount () {
|
||||
this._unsubscribePlugins = subscribePlugins(plugins => this.setState({ plugins }))
|
||||
|
||||
@@ -240,7 +240,7 @@ class XoaUpdater extends EventEmitter {
|
||||
this.registerState = 'error'
|
||||
}
|
||||
} finally {
|
||||
this.emit('registerState', {state: this.registerState, email: this.token && this.token.registrationEmail || '', error: this.registerError})
|
||||
this.emit('registerState', {state: this.registerState, email: (this.token && this.token.registrationEmail) || '', error: this.registerError})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ class XoaUpdater extends EventEmitter {
|
||||
this.registerState = 'error'
|
||||
}
|
||||
} finally {
|
||||
this.emit('registerState', {state: this.registerState, email: this.token && this.token.registrationEmail || '', error: this.registerError})
|
||||
this.emit('registerState', {state: this.registerState, email: (this.token && this.token.registrationEmail) || '', error: this.registerError})
|
||||
if (this.registerState === 'registered') {
|
||||
this.update()
|
||||
}
|
||||
@@ -351,7 +351,7 @@ class XoaUpdater extends EventEmitter {
|
||||
}
|
||||
|
||||
log (level, message) {
|
||||
message = message && message.message || String(message)
|
||||
message = (message != null && message.message) || String(message)
|
||||
const date = new Date()
|
||||
this._log.unshift({
|
||||
date: date.toLocaleString(),
|
||||
|
||||
@@ -268,7 +268,7 @@ export default class RestoreFileModalBody extends Component {
|
||||
value={partition}
|
||||
/>
|
||||
]}
|
||||
{(partition || disk && !scanDiskError && noPartitions) && [
|
||||
{(partition || (disk && !scanDiskError && noPartitions)) && [
|
||||
<br />,
|
||||
<Container>
|
||||
<Row>
|
||||
@@ -280,7 +280,7 @@ export default class RestoreFileModalBody extends Component {
|
||||
<Col size={2}>
|
||||
<span className='pull-right'>
|
||||
<Tooltip content={_('restoreFilesSelectAllFiles')}>
|
||||
<ActionButton btnStyle='secondary' handler={this._selectAllFolderFiles} icon='add' size='small' />
|
||||
<ActionButton handler={this._selectAllFolderFiles} icon='add' size='small' />
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Col>
|
||||
@@ -322,12 +322,13 @@ export default class RestoreFileModalBody extends Component {
|
||||
<Col className='pl-0 pb-1' size={10}>
|
||||
<em>{_('restoreFilesSelectedFiles', { files: selectedFiles.length })}</em>
|
||||
</Col>
|
||||
<Col size={2}>
|
||||
<span className='pull-right'>
|
||||
<Tooltip content={_('restoreFilesUnselectAll')}>
|
||||
<ActionButton btnStyle='secondary' handler={this._unselectAllFiles} icon='remove' size='small' />
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Col size={2} className='text-xs-right'>
|
||||
<ActionButton
|
||||
handler={this._unselectAllFiles}
|
||||
icon='remove'
|
||||
size='small'
|
||||
tooltip={_('restoreFilesUnselectAll')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{map(selectedFiles, file =>
|
||||
@@ -335,10 +336,8 @@ export default class RestoreFileModalBody extends Component {
|
||||
<Col size={10}>
|
||||
<pre>{file.path}</pre>
|
||||
</Col>
|
||||
<Col size={2}>
|
||||
<span className='pull-right'>
|
||||
<ActionButton btnStyle='secondary' handler={this._unselectFile} handlerParam={file} icon='remove' size='small' />
|
||||
</span>
|
||||
<Col size={2} className='text-xs-right'>
|
||||
<ActionButton handler={this._unselectFile} handlerParam={file} icon='remove' size='small' />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
||||
@@ -1,32 +1,39 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import delay from 'lodash/delay'
|
||||
import forEach from 'lodash/forEach'
|
||||
import GenericInput from 'json-schema-input'
|
||||
import getEventValue from 'get-event-value'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
import moment from 'moment-timezone'
|
||||
import React from 'react'
|
||||
import Scheduler, { SchedulePreview } from 'scheduling'
|
||||
import startsWith from 'lodash/startsWith'
|
||||
import uncontrollableInput from 'uncontrollable-input'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import Wizard, { Section } from 'wizard'
|
||||
import { addSubscriptions } from 'utils'
|
||||
import { confirm } from 'modal'
|
||||
import { error } from 'notification'
|
||||
import { generateUiSchema } from 'xo-json-schema-input'
|
||||
import { SelectSubject } from 'select-objects'
|
||||
import { connectStore, EMPTY_OBJECT } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { createSelector } from 'reselect'
|
||||
import { generateUiSchema } from 'xo-json-schema-input'
|
||||
import { getUser } from 'selectors'
|
||||
import { SelectSubject } from 'select-objects'
|
||||
import {
|
||||
forEach,
|
||||
identity,
|
||||
isArray,
|
||||
map,
|
||||
mapValues,
|
||||
noop,
|
||||
startsWith
|
||||
} from 'lodash'
|
||||
|
||||
import {
|
||||
createJob,
|
||||
createSchedule,
|
||||
getRemote,
|
||||
editJob,
|
||||
editSchedule,
|
||||
subscribeCurrentUser
|
||||
editSchedule
|
||||
} from 'xo'
|
||||
|
||||
// ===================================================================
|
||||
@@ -52,13 +59,13 @@ const NO_SMART_UI_SCHEMA = generateUiSchema(NO_SMART_SCHEMA)
|
||||
const SMART_SCHEMA = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
power_state: {
|
||||
default: 'All', // FIXME: can't translate
|
||||
enum: [ 'All', 'Running', 'Halted' ], // FIXME: can't translate
|
||||
title: _('editBackupSmartStatusTitle'),
|
||||
description: 'The statuses of VMs to backup.' // FIXME: can't translate
|
||||
},
|
||||
poolsOptions: {
|
||||
$pool: {
|
||||
type: 'object',
|
||||
title: _('editBackupSmartPools'),
|
||||
properties: {
|
||||
@@ -67,7 +74,7 @@ const SMART_SCHEMA = {
|
||||
title: _('editBackupNot'),
|
||||
description: 'Toggle on to backup VMs that are NOT resident on these pools'
|
||||
},
|
||||
pools: {
|
||||
values: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
@@ -78,7 +85,7 @@ const SMART_SCHEMA = {
|
||||
}
|
||||
}
|
||||
},
|
||||
tagsOptions: {
|
||||
tags: {
|
||||
type: 'object',
|
||||
title: _('editBackupSmartTags'),
|
||||
properties: {
|
||||
@@ -87,7 +94,7 @@ const SMART_SCHEMA = {
|
||||
title: _('editBackupNot'),
|
||||
description: 'Toggle on to backup VMs that do NOT contain these tags'
|
||||
},
|
||||
tags: {
|
||||
values: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
@@ -99,7 +106,7 @@ const SMART_SCHEMA = {
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [ 'status', 'poolsOptions', 'tagsOptions' ]
|
||||
required: [ 'power_state', '$pool', 'tags' ]
|
||||
}
|
||||
const SMART_UI_SCHEMA = generateUiSchema(SMART_SCHEMA)
|
||||
|
||||
@@ -261,198 +268,186 @@ const BACKUP_METHOD_TO_INFO = {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const DEFAULT_CRON_PATTERN = '0 0 * * *'
|
||||
@uncontrollableInput()
|
||||
class TimeoutInput extends Component {
|
||||
_onChange = event => {
|
||||
const value = getEventValue(event).trim()
|
||||
this.props.onChange(value === '' ? null : +value * 1e3)
|
||||
}
|
||||
|
||||
function negatePattern (pattern, not = true) {
|
||||
render () {
|
||||
const { props } = this
|
||||
const { value } = props
|
||||
|
||||
return <input
|
||||
{...props}
|
||||
onChange={this._onChange}
|
||||
type='number'
|
||||
value={value == null ? '' : String(value / 1e3)}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const DEFAULT_CRON_PATTERN = '0 0 * * *'
|
||||
const DEFAULT_TIMEZONE = moment.tz.guess()
|
||||
|
||||
// xo-web v5.7.1 introduced a bug where an extra level
|
||||
// ({ id: { id: <id> } }) was introduced for the VM param.
|
||||
//
|
||||
// This code automatically unbox the ids.
|
||||
const extractId = value => {
|
||||
while (typeof value === 'object') {
|
||||
value = value.id
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const destructPattern = (pattern, valueTransform = identity) => pattern && ({
|
||||
not: !!pattern.__not,
|
||||
values: valueTransform((pattern.__not || pattern).__or)
|
||||
})
|
||||
|
||||
const constructPattern = ({ not, values } = EMPTY_OBJECT, valueTransform = identity) => {
|
||||
if (values == null || !values.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const pattern = { __or: valueTransform(values) }
|
||||
return not
|
||||
? { __not: pattern }
|
||||
: pattern
|
||||
}
|
||||
|
||||
@addSubscriptions({
|
||||
currentUser: subscribeCurrentUser
|
||||
@connectStore({
|
||||
currentUser: getUser
|
||||
})
|
||||
export default class New extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state.cronPattern = DEFAULT_CRON_PATTERN
|
||||
}
|
||||
|
||||
componentWillReceiveProps (props) {
|
||||
const { currentUser } = props
|
||||
const { owner } = this.state
|
||||
|
||||
if (currentUser && !owner) {
|
||||
this.setState({ owner: currentUser.id })
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { job, schedule } = this.props
|
||||
if (!job || !schedule) {
|
||||
if (job || schedule) { // Having only one of them is unexpected incomplete information
|
||||
error(_('backupEditNotFoundTitle'), _('backupEditNotFoundMessage'))
|
||||
_getParams = createSelector(
|
||||
() => this.props.job,
|
||||
() => this.props.schedule,
|
||||
(job, schedule) => {
|
||||
if (!job) {
|
||||
return { main: {}, vms: { vms: [] } }
|
||||
}
|
||||
this.setState({
|
||||
timezone: moment.tz.guess()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({
|
||||
backupInfo: BACKUP_METHOD_TO_INFO[job.method],
|
||||
cronPattern: schedule.cron,
|
||||
owner: job.userId,
|
||||
timeout: job.timeout && job.timeout / 1e3,
|
||||
timezone: schedule.timezone || null
|
||||
}, () => delay(this._populateForm, 250, job)) // Work around.
|
||||
// Without the delay, some selects are not always ready to load a value
|
||||
// Values are displayed, but html5 compliant browsers say the value is required and empty on submit
|
||||
}
|
||||
const { items } = job.paramsVector
|
||||
const enabled = schedule != null && schedule.enabled
|
||||
|
||||
_populateForm = job => {
|
||||
let values = job.paramsVector.items
|
||||
const {
|
||||
backupInput,
|
||||
vmsInput
|
||||
} = this.refs
|
||||
|
||||
if (values.length === 1) {
|
||||
// Older versions of XenOrchestra uses only values[0].
|
||||
const array = values[0].values
|
||||
const config = array[0]
|
||||
const reportWhen = config._reportWhen
|
||||
|
||||
backupInput.value = {
|
||||
...config,
|
||||
_reportWhen:
|
||||
// Fix old reportWhen values...
|
||||
(reportWhen === 'fail' && 'failure') ||
|
||||
(reportWhen === 'alway' && 'always') ||
|
||||
reportWhen
|
||||
// legacy backup jobs
|
||||
if (items.length === 1) {
|
||||
return {
|
||||
main: {
|
||||
enabled,
|
||||
...items[0].values[0]
|
||||
},
|
||||
vms: { vms: map(items[0].values.slice(1), extractId) }
|
||||
}
|
||||
}
|
||||
vmsInput.value = { vms: map(array, ({ id, vm }) => id || vm) }
|
||||
} else {
|
||||
if (values[1].type === 'map') {
|
||||
// Smart backup.
|
||||
const {
|
||||
$pool: poolsOptions = {},
|
||||
tags: tagsOptions = {},
|
||||
power_state: status = 'All'
|
||||
} = values[1].collection.pattern
|
||||
|
||||
backupInput.value = values[0].values[0]
|
||||
// smart backup
|
||||
if (items[1].type === 'map') {
|
||||
const { pattern } = items[1].collection
|
||||
const { $pool, tags } = pattern
|
||||
|
||||
this.setState({
|
||||
smartBackupMode: true
|
||||
}, () => {
|
||||
vmsInput.value = {
|
||||
poolsOptions: {
|
||||
pools: poolsOptions.__not ? poolsOptions.__not.__or : poolsOptions.__or,
|
||||
not: !!poolsOptions.__not
|
||||
},
|
||||
status,
|
||||
tagsOptions: {
|
||||
tags: map(tagsOptions.__not ? tagsOptions.__not.__or : tagsOptions.__or, tag => tag[0]),
|
||||
not: !!tagsOptions.__not
|
||||
}
|
||||
return {
|
||||
main: {
|
||||
enabled,
|
||||
...items[0].values[0]
|
||||
},
|
||||
vms: {
|
||||
$pool: destructPattern($pool),
|
||||
power_state: pattern.power_state,
|
||||
tags: destructPattern(tags, tags => map(tags, tag => isArray(tag) ? tag[0] : tag))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Normal backup.
|
||||
backupInput.value = values[1].values[0]
|
||||
}
|
||||
}
|
||||
|
||||
// xo-web v5.7.1 introduced a bug where an extra level ({ id: { id: <id> } }) was introduced for the VM param.
|
||||
//
|
||||
// This code automatically unbox the ids.
|
||||
const vms = map(values[0].values, id => {
|
||||
while (typeof id === 'object') {
|
||||
id = id.id
|
||||
}
|
||||
return id
|
||||
})
|
||||
|
||||
vmsInput.value = { vms }
|
||||
// normal backup
|
||||
return {
|
||||
main: {
|
||||
enabled,
|
||||
...items[1].values[0]
|
||||
},
|
||||
vms: { vms: map(items[0].values, extractId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
_getMainParams = () => this.state.mainParams || this._getParams().main
|
||||
_getVmsParam = () => this.state.vmsParam || this._getParams().vms
|
||||
|
||||
_getScheduling = createSelector(
|
||||
() => this.props.schedule,
|
||||
() => this.state.scheduling,
|
||||
(schedule, scheduling) => {
|
||||
if (scheduling !== undefined) {
|
||||
return scheduling
|
||||
}
|
||||
|
||||
const {
|
||||
cron = DEFAULT_CRON_PATTERN,
|
||||
timezone = DEFAULT_TIMEZONE
|
||||
} = schedule || EMPTY_OBJECT
|
||||
|
||||
return {
|
||||
cronPattern: cron,
|
||||
timezone
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
_handleSubmit = async () => {
|
||||
const { props, state } = this
|
||||
|
||||
const method = this._getValue('job', 'method')
|
||||
const backupInfo = BACKUP_METHOD_TO_INFO[method]
|
||||
|
||||
const {
|
||||
enabled,
|
||||
...callArgs
|
||||
} = this.refs.backupInput.value
|
||||
const vmsInputValue = this.refs.vmsInput.value
|
||||
|
||||
const {
|
||||
backupInfo,
|
||||
smartBackupMode,
|
||||
timeout,
|
||||
timezone,
|
||||
owner
|
||||
} = this.state
|
||||
|
||||
const { pools, not: notPools } = vmsInputValue.poolsOptions || {}
|
||||
const { tags, not: notTags } = vmsInputValue.tagsOptions || {}
|
||||
const formattedTags = map(tags, tag => [ tag ])
|
||||
|
||||
const paramsVector = !smartBackupMode
|
||||
? {
|
||||
type: 'crossProduct',
|
||||
items: [{
|
||||
type: 'set',
|
||||
values: map(vmsInputValue.vms, vm => ({ id: vm.id || vm }))
|
||||
}, {
|
||||
type: 'set',
|
||||
values: [ callArgs ]
|
||||
}]
|
||||
} : {
|
||||
type: 'crossProduct',
|
||||
items: [{
|
||||
type: 'set',
|
||||
values: [ callArgs ]
|
||||
}, {
|
||||
type: 'map',
|
||||
collection: {
|
||||
type: 'fetchObjects',
|
||||
pattern: {
|
||||
$pool: isEmpty(pools)
|
||||
? undefined
|
||||
: negatePattern({ __or: pools }, notPools),
|
||||
power_state: vmsInputValue.status === 'All' ? undefined : vmsInputValue.status,
|
||||
tags: isEmpty(tags)
|
||||
? undefined
|
||||
: negatePattern({ __or: formattedTags }, notTags),
|
||||
type: 'VM'
|
||||
}
|
||||
},
|
||||
iteratee: {
|
||||
type: 'extractProperties',
|
||||
mapping: { id: 'id' }
|
||||
}
|
||||
}]
|
||||
}
|
||||
...mainParams
|
||||
} = this._getMainParams()
|
||||
const vms = this._getVmsParam()
|
||||
|
||||
const job = {
|
||||
...state.job,
|
||||
|
||||
type: 'call',
|
||||
key: backupInfo.jobKey,
|
||||
method: backupInfo.method,
|
||||
paramsVector,
|
||||
userId: owner,
|
||||
timeout: timeout ? timeout * 1e3 : undefined
|
||||
paramsVector: {
|
||||
type: 'crossProduct',
|
||||
items: isArray(vms.vms)
|
||||
? [{
|
||||
type: 'set',
|
||||
values: map(vms.vms, vm => ({ id: extractId(vm) }))
|
||||
}, {
|
||||
type: 'set',
|
||||
values: [ mainParams ]
|
||||
}]
|
||||
: [{
|
||||
type: 'set',
|
||||
values: [ mainParams ]
|
||||
}, {
|
||||
type: 'map',
|
||||
collection: {
|
||||
type: 'fetchObjects',
|
||||
pattern: {
|
||||
$pool: constructPattern(vms.$pool),
|
||||
power_state: vms.power_state === 'All' ? undefined : vms.power_state,
|
||||
tags: constructPattern(vms.tags, tags => map(tags, tag => [ tag ])),
|
||||
type: 'VM'
|
||||
}
|
||||
},
|
||||
iteratee: {
|
||||
type: 'extractProperties',
|
||||
mapping: { id: 'id' }
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// Update backup schedule.
|
||||
const { job: oldJob, schedule: oldSchedule } = this.props
|
||||
|
||||
if (oldJob && oldSchedule) {
|
||||
job.id = oldJob.id
|
||||
return editJob(job).then(() => editSchedule({
|
||||
...oldSchedule,
|
||||
cron: this.state.cronPattern,
|
||||
timezone
|
||||
}))
|
||||
}
|
||||
const scheduling = this._getScheduling()
|
||||
|
||||
let remoteId
|
||||
if (job.type === 'call') {
|
||||
@@ -485,58 +480,80 @@ export default class New extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Update backup schedule.
|
||||
const oldJob = props.job
|
||||
if (oldJob) {
|
||||
job.id = oldJob.id
|
||||
await editJob(job)
|
||||
|
||||
return editSchedule({
|
||||
id: props.schedule.id,
|
||||
cron: scheduling.cronPattern,
|
||||
enabled,
|
||||
timezone: scheduling.timezone
|
||||
})
|
||||
}
|
||||
|
||||
if (job.timeout === null) {
|
||||
delete job.timeout // only needed for job edition
|
||||
}
|
||||
|
||||
// Create backup schedule.
|
||||
return createSchedule(await createJob(job), { cron: this.state.cronPattern, enabled, timezone })
|
||||
return createSchedule(await createJob(job), {
|
||||
cron: scheduling.cronPattern,
|
||||
enabled,
|
||||
timezone: scheduling.timezone
|
||||
})
|
||||
}
|
||||
|
||||
_handleReset = () => {
|
||||
const { backupInput } = this.refs
|
||||
|
||||
if (backupInput) {
|
||||
backupInput.value = undefined
|
||||
}
|
||||
|
||||
this.setState({
|
||||
cronPattern: DEFAULT_CRON_PATTERN
|
||||
})
|
||||
}
|
||||
|
||||
_updateCronPattern = value => {
|
||||
this.setState(value)
|
||||
}
|
||||
|
||||
_handleBackupSelection = event => {
|
||||
const method = event.target.value
|
||||
|
||||
this.setState({
|
||||
showVersionWarning: method === 'vm.rollingDeltaBackup' || method === 'vm.deltaCopy',
|
||||
backupInfo: BACKUP_METHOD_TO_INFO[method]
|
||||
})
|
||||
this.setState(mapValues(this.state, noop))
|
||||
}
|
||||
|
||||
_handleSmartBackupMode = event => {
|
||||
this.setState({
|
||||
smartBackupMode: event.target.value === 'smart'
|
||||
})
|
||||
this.setState(
|
||||
event.target.value === 'smart'
|
||||
? { vmsParam: {} }
|
||||
: { vmsParam: { vms: [] } }
|
||||
)
|
||||
}
|
||||
|
||||
_subjectPredicate = ({ type, permission }) =>
|
||||
type === 'user' && permission === 'admin'
|
||||
|
||||
render () {
|
||||
const { state } = this
|
||||
const {
|
||||
backupInfo,
|
||||
cronPattern,
|
||||
smartBackupMode,
|
||||
timezone,
|
||||
owner,
|
||||
showVersionWarning
|
||||
} = state
|
||||
_getValue = (ns, key, defaultValue) => {
|
||||
let tmp
|
||||
|
||||
return process.env.XOA_PLAN > 1
|
||||
? (
|
||||
<Wizard>
|
||||
// look in the state
|
||||
if (
|
||||
(tmp = this.state[ns]) != null &&
|
||||
(tmp = tmp[key]) !== undefined
|
||||
) {
|
||||
return tmp
|
||||
}
|
||||
|
||||
// look in the props
|
||||
if (
|
||||
(tmp = this.props[ns]) != null &&
|
||||
(tmp = tmp[key]) !== undefined
|
||||
) {
|
||||
return tmp
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
render () {
|
||||
const method = this._getValue('job', 'method', '')
|
||||
const scheduling = this._getScheduling()
|
||||
const vms = this._getVmsParam()
|
||||
|
||||
const backupInfo = BACKUP_METHOD_TO_INFO[method]
|
||||
const smartBackupMode = !isArray(vms.vms)
|
||||
|
||||
return (
|
||||
<Upgrade place='newBackup' required={2}>
|
||||
<Wizard><form id='form-new-vm-backup'>
|
||||
<Section icon='backup' title={this.props.job ? 'editVmBackup' : 'newVmBackup'}>
|
||||
<Container>
|
||||
<Row>
|
||||
@@ -544,92 +561,96 @@ export default class New extends Component {
|
||||
<fieldset className='form-group'>
|
||||
<label>{_('backupOwner')}</label>
|
||||
<SelectSubject
|
||||
onChange={this.linkState('owner', 'id')}
|
||||
onChange={this.linkState('job.userId', 'id')}
|
||||
predicate={this._subjectPredicate}
|
||||
required
|
||||
value={owner || null}
|
||||
value={this._getValue('job', 'userId', this.props.currentUser.id)}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className='form-group'>
|
||||
<label>{_('jobTimeoutPlaceHolder')}</label>
|
||||
<input type='number' onChange={this.linkState('timeout')} value={state.timeout} className='form-control' />
|
||||
<TimeoutInput
|
||||
className='form-control'
|
||||
onChange={this.linkState('job.timeout')}
|
||||
value={this._getValue('job', 'timeout')}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className='form-group'>
|
||||
<label htmlFor='selectBackup'>{_('newBackupSelection')}</label>
|
||||
<select
|
||||
className='form-control'
|
||||
value={(backupInfo && backupInfo.method) || ''}
|
||||
id='selectBackup'
|
||||
onChange={this._handleBackupSelection}
|
||||
onChange={this.linkState('job.method')}
|
||||
required
|
||||
value={method}
|
||||
>
|
||||
{_('noSelectedValue', message => <option value=''>{message}</option>)}
|
||||
{map(BACKUP_METHOD_TO_INFO, (info, key) =>
|
||||
_(info.label, message => <option key={key} value={key}>{message}</option>)
|
||||
_(info.label, message => <option key={key} value={key}>{message}</option>)
|
||||
)}
|
||||
</select>
|
||||
</fieldset>
|
||||
{showVersionWarning && <div className='alert alert-warning' role='alert'>
|
||||
{(method === 'vm.rollingDeltaBackup' || method === 'vm.deltaCopy') && <div className='alert alert-warning' role='alert'>
|
||||
<Icon icon='error' /> {_('backupVersionWarning')}
|
||||
</div>}
|
||||
<form id='form-new-vm-backup'>
|
||||
{backupInfo && <div>
|
||||
<GenericInput
|
||||
label={<span><Icon icon={backupInfo.icon} /> {_(backupInfo.label)}</span>}
|
||||
ref='backupInput'
|
||||
{backupInfo && <div>
|
||||
<GenericInput
|
||||
label={<span><Icon icon={backupInfo.icon} /> {_(backupInfo.label)}</span>}
|
||||
required
|
||||
schema={backupInfo.schema}
|
||||
uiSchema={backupInfo.uiSchema}
|
||||
onChange={this.linkState('mainParams')}
|
||||
value={this._getMainParams()}
|
||||
/>
|
||||
<fieldset className='form-group'>
|
||||
<label htmlFor='smartMode'>{_('smartBackupModeSelection')}</label>
|
||||
<select
|
||||
className='form-control'
|
||||
id='smartMode'
|
||||
onChange={this._handleSmartBackupMode}
|
||||
required
|
||||
schema={backupInfo.schema}
|
||||
uiSchema={backupInfo.uiSchema}
|
||||
/>
|
||||
<fieldset className='form-group'>
|
||||
<label htmlFor='smartMode'>{_('smartBackupModeSelection')}</label>
|
||||
<select
|
||||
className='form-control'
|
||||
id='smartMode'
|
||||
onChange={this._handleSmartBackupMode}
|
||||
required
|
||||
value={smartBackupMode ? 'smart' : 'normal'}
|
||||
>
|
||||
{_('normalBackup', message => <option value='normal'>{message}</option>)}
|
||||
{_('smartBackup', message => <option value='smart'>{message}</option>)}
|
||||
</select>
|
||||
</fieldset>
|
||||
{smartBackupMode
|
||||
? <Upgrade place='newBackup' required={3}>
|
||||
<GenericInput
|
||||
label={<span><Icon icon='vm' /> {_('vmsToBackup')}</span>}
|
||||
ref='vmsInput'
|
||||
required
|
||||
schema={SMART_SCHEMA}
|
||||
uiSchema={SMART_UI_SCHEMA}
|
||||
/>
|
||||
</Upgrade>
|
||||
: <GenericInput
|
||||
value={smartBackupMode ? 'smart' : 'normal'}
|
||||
>
|
||||
{_('normalBackup', message => <option value='normal'>{message}</option>)}
|
||||
{_('smartBackup', message => <option value='smart'>{message}</option>)}
|
||||
</select>
|
||||
</fieldset>
|
||||
{smartBackupMode
|
||||
? <Upgrade place='newBackup' required={3}>
|
||||
<GenericInput
|
||||
label={<span><Icon icon='vm' /> {_('vmsToBackup')}</span>}
|
||||
ref='vmsInput'
|
||||
onChange={this.linkState('vmsParam')}
|
||||
required
|
||||
schema={NO_SMART_SCHEMA}
|
||||
uiSchema={NO_SMART_UI_SCHEMA}
|
||||
schema={SMART_SCHEMA}
|
||||
uiSchema={SMART_UI_SCHEMA}
|
||||
value={vms}
|
||||
/>
|
||||
}
|
||||
</div>}
|
||||
</form>
|
||||
</Upgrade>
|
||||
: <GenericInput
|
||||
label={<span><Icon icon='vm' /> {_('vmsToBackup')}</span>}
|
||||
onChange={this.linkState('vmsParam')}
|
||||
required
|
||||
schema={NO_SMART_SCHEMA}
|
||||
uiSchema={NO_SMART_UI_SCHEMA}
|
||||
value={vms}
|
||||
/>
|
||||
}
|
||||
</div>}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Section>
|
||||
<Section icon='schedule' title='schedule'>
|
||||
<Scheduler
|
||||
cronPattern={cronPattern}
|
||||
onChange={this._updateCronPattern}
|
||||
timezone={timezone}
|
||||
onChange={this.linkState('scheduling')}
|
||||
value={scheduling}
|
||||
/>
|
||||
</Section>
|
||||
<Section icon='preview' title='preview' summary>
|
||||
<Container>
|
||||
<Row>
|
||||
<Col>
|
||||
<SchedulePreview cronPattern={cronPattern} />
|
||||
<SchedulePreview cronPattern={scheduling.cronPattern} />
|
||||
{process.env.XOA_PLAN < 4 && backupInfo && process.env.XOA_PLAN < REQUIRED_XOA_PLAN[backupInfo.jobKey]
|
||||
? <Upgrade place='newBackup' available={REQUIRED_XOA_PLAN[backupInfo.jobKey]} />
|
||||
: (smartBackupMode && process.env.XOA_PLAN < 3
|
||||
@@ -637,26 +658,27 @@ export default class New extends Component {
|
||||
: <fieldset className='pull-right pt-1'>
|
||||
<ActionButton
|
||||
btnStyle='primary'
|
||||
className='btn-lg mr-1'
|
||||
className='mr-1'
|
||||
disabled={!backupInfo}
|
||||
form='form-new-vm-backup'
|
||||
handler={this._handleSubmit}
|
||||
icon='save'
|
||||
redirectOnSuccess='/backup/overview'
|
||||
size='large'
|
||||
>
|
||||
{_('saveBackupJob')}
|
||||
</ActionButton>
|
||||
<button type='button' className='btn btn-lg btn-secondary' onClick={this._handleReset}>
|
||||
<Button onClick={this._handleReset} size='large'>
|
||||
{_('selectTableReset')}
|
||||
</button>
|
||||
</Button>
|
||||
</fieldset>)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Section>
|
||||
</Wizard>
|
||||
)
|
||||
: <Container><Upgrade place='newBackup' available={2} /></Container>
|
||||
</form></Wizard>
|
||||
</Upgrade>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import Component from 'base-component'
|
||||
import filter from 'lodash/filter'
|
||||
import find from 'lodash/find'
|
||||
@@ -15,7 +16,6 @@ import SortedTable from 'sorted-table'
|
||||
import StateButton from 'state-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { addSubscriptions } from 'utils'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { createSelector } from 'selectors'
|
||||
import {
|
||||
Card,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'intl'
|
||||
import ButtonGroup from 'button-group'
|
||||
import ChartistGraph from 'react-chartist'
|
||||
import Component from 'base-component'
|
||||
import forEach from 'lodash/forEach'
|
||||
@@ -10,7 +11,6 @@ import HostsPatchesTable from 'hosts-patches-table'
|
||||
import React from 'react'
|
||||
import size from 'lodash/size'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Card, CardBlock, CardHeader } from 'card'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
createGetObjectsOfType,
|
||||
createGetHostMetrics,
|
||||
createSelector,
|
||||
createTop
|
||||
createTop,
|
||||
isAdmin
|
||||
} from 'selectors'
|
||||
import {
|
||||
connectStore,
|
||||
@@ -136,6 +137,7 @@ class PatchesCard extends Component {
|
||||
return {
|
||||
hostMetrics: getHostMetrics,
|
||||
hosts: getHosts,
|
||||
isAdmin,
|
||||
nAlarmMessages: getNumberOfAlarmMessages,
|
||||
nHosts: getNumberOfHosts,
|
||||
nPools: getNumberOfPools,
|
||||
@@ -238,8 +240,8 @@ export default class Overview extends Component {
|
||||
/>
|
||||
<p className='text-xs-center'>
|
||||
{_('ofUsage', {
|
||||
total: `${props.vmMetrics.vcpus} vCPUs`,
|
||||
usage: `${props.hostMetrics.cpus} CPUs`
|
||||
total: `${props.hostMetrics.cpus} CPUs`,
|
||||
usage: `${props.vmMetrics.vcpus} vCPUs`
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
@@ -306,7 +308,10 @@ export default class Overview extends Component {
|
||||
</CardHeader>
|
||||
<CardBlock>
|
||||
<p className={styles.bigCardContent}>
|
||||
<Link to='/settings/users'>{nUsers}</Link>
|
||||
{props.isAdmin
|
||||
? <Link to='/settings/users'>{nUsers}</Link>
|
||||
: <p>{nUsers}</p>
|
||||
}
|
||||
</p>
|
||||
</CardBlock>
|
||||
</Card>
|
||||
|
||||
@@ -305,32 +305,22 @@ class SelectMetric extends Component {
|
||||
/>
|
||||
</div>
|
||||
<div className='btn-group mt-1' role='group'>
|
||||
<button
|
||||
className='btn btn-secondary'
|
||||
onClick={this._resetSelection}
|
||||
tooltip={_('dashboardStatsButtonRemoveAll')}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='remove' />
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-secondary'
|
||||
onClick={this._selectAllHosts}
|
||||
tooltip={_('dashboardStatsButtonAddAllHost')}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='host' />
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-secondary'
|
||||
onClick={this._selectAllVms}
|
||||
tooltip={_('dashboardStatsButtonAddAllVM')}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='vm' />
|
||||
</button>
|
||||
<ActionButton
|
||||
btnStyle='secondary'
|
||||
handler={this._resetSelection}
|
||||
icon='remove'
|
||||
tooltip={_('dashboardStatsButtonRemoveAll')}
|
||||
/>
|
||||
<ActionButton
|
||||
handler={this._selectAllHosts}
|
||||
icon='host'
|
||||
tooltip={_('dashboardStatsButtonAddAllHost')}
|
||||
/>
|
||||
<ActionButton
|
||||
handler={this._selectAllVms}
|
||||
icon='vm'
|
||||
tooltip={_('dashboardStatsButtonAddAllVM')}
|
||||
/>
|
||||
<ActionButton
|
||||
disabled={!objects.length}
|
||||
handler={this._validSelection}
|
||||
icon='success'
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as ComplexMatcher from 'complex-matcher'
|
||||
import * as homeFilters from 'home-filters'
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Button from 'button'
|
||||
import CenterPanel from 'center-panel'
|
||||
import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
@@ -75,7 +76,6 @@ import {
|
||||
getUser
|
||||
} from 'selectors'
|
||||
import {
|
||||
Button,
|
||||
DropdownButton,
|
||||
MenuItem,
|
||||
OverlayTrigger,
|
||||
@@ -313,7 +313,7 @@ export default class Home extends Component {
|
||||
const defaultFilter = this._getDefaultFilter(props)
|
||||
|
||||
if (defaultFilter != null) {
|
||||
this._setFilter(defaultFilter, props)
|
||||
this._setFilter(defaultFilter, props, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -359,13 +359,13 @@ export default class Home extends Component {
|
||||
|
||||
// Optionally can take the props to be able to use it in
|
||||
// componentWillReceiveProps().
|
||||
_setFilter (filter, props = this.props) {
|
||||
_setFilter (filter, props = this.props, replace) {
|
||||
if (!isString(filter)) {
|
||||
filter = filter::ComplexMatcher.toString()
|
||||
}
|
||||
|
||||
const { pathname, query } = props.location
|
||||
this.context.router.push({
|
||||
this.context.router[replace ? 'replace' : 'push']({
|
||||
pathname,
|
||||
query: { ...query, s: filter }
|
||||
})
|
||||
@@ -568,11 +568,9 @@ export default class Home extends Component {
|
||||
type='text'
|
||||
/>
|
||||
<div className='input-group-btn'>
|
||||
<a
|
||||
className='btn btn-secondary'
|
||||
onClick={this._clearFilter}>
|
||||
<Button onClick={this._clearFilter}>
|
||||
<Icon icon='clear-search' />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<div className='input-group-btn'>
|
||||
<ActionButton
|
||||
@@ -751,7 +749,6 @@ export default class Home extends Component {
|
||||
{map(mainActions, (action, key) => (
|
||||
<Tooltip content={action.tooltip} key={key}>
|
||||
<ActionButton
|
||||
btnStyle='secondary'
|
||||
{...action}
|
||||
handlerParam={this._getSelectedItemsIds()}
|
||||
/>
|
||||
@@ -785,7 +782,7 @@ export default class Home extends Component {
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Button className='btn-link'><Icon icon='pool' /> {_('homeAllPools')}</Button>
|
||||
<Button btnStyle='link'><Icon icon='pool' /> {_('homeAllPools')}</Button>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
{' '}
|
||||
@@ -805,7 +802,7 @@ export default class Home extends Component {
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Button className='btn-link'><Icon icon='host' /> {_('homeAllHosts')}</Button>
|
||||
<Button btnStyle='link'><Icon icon='host' /> {_('homeAllHosts')}</Button>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
{' '}
|
||||
@@ -826,7 +823,7 @@ export default class Home extends Component {
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Button className='btn-link'><Icon icon='tags' /> {_('homeAllTags')}</Button>
|
||||
<Button btnStyle='link'><Icon icon='tags' /> {_('homeAllTags')}</Button>
|
||||
</OverlayTrigger>
|
||||
{' '}
|
||||
<DropdownButton bsStyle='link' id='sort' title={_('homeSortBy')}>
|
||||
@@ -844,10 +841,9 @@ export default class Home extends Component {
|
||||
}
|
||||
</Col>
|
||||
<Col smallsize={1} mediumSize={1} className='text-xs-right'>
|
||||
<button className='btn btn-secondary'
|
||||
onClick={this._expandAll}>
|
||||
<Button onClick={this._expandAll}>
|
||||
<Icon icon='nav' />
|
||||
</button>
|
||||
</Button>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
{isEmpty(filteredItems)
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import _ from 'intl'
|
||||
import assign from 'lodash/assign'
|
||||
import HostActionBar from './action-bar'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
import Link from 'link'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
import Page from '../page'
|
||||
import pick from 'lodash/pick'
|
||||
import React, { cloneElement, Component } from 'react'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
import sum from 'lodash/sum'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Text } from 'editable'
|
||||
import { editHost, fetchHostStats, getHostMissingPatches, installAllHostPatches, installHostPatch } from 'xo'
|
||||
@@ -25,6 +19,15 @@ import {
|
||||
createGetObjectsOfType,
|
||||
createSelector
|
||||
} from 'selectors'
|
||||
import {
|
||||
assign,
|
||||
isEmpty,
|
||||
isString,
|
||||
map,
|
||||
pick,
|
||||
sortBy,
|
||||
sum
|
||||
} from 'lodash'
|
||||
|
||||
import TabAdvanced from './tab-advanced'
|
||||
import TabConsole from './tab-console'
|
||||
@@ -94,7 +97,7 @@ const isRunning = host => host && host.power_state === 'Running'
|
||||
const getHostPatches = createSelector(
|
||||
createGetObjectsOfType('pool_patch'),
|
||||
createGetObjectsOfType('host_patch').pick(
|
||||
createSelector(getHost, host => host.patches)
|
||||
createSelector(getHost, host => isString(host.patches[0]) ? host.patches : [])
|
||||
),
|
||||
(poolsPatches, hostsPatches) => map(hostsPatches, hostPatch => ({
|
||||
...hostPatch,
|
||||
|
||||
@@ -16,14 +16,11 @@ const ALLOW_INSTALL_SUPP_PACK = process.env.XOA_PLAN > 1
|
||||
|
||||
const forceReboot = host => restartHost(host, true)
|
||||
|
||||
const formatPack = (version, pack) => {
|
||||
const [ author, name ] = pack.split(':')
|
||||
|
||||
return <tr>
|
||||
<th>{_('supplementalPackTitle', { author, name })}</th>
|
||||
<td>{version}</td>
|
||||
</tr>
|
||||
}
|
||||
const formatPack = ({ name, author, description, version }) => <tr>
|
||||
<th>{_('supplementalPackTitle', { author, name })}</th>
|
||||
<td>{description}</td>
|
||||
<td>{version}</td>
|
||||
</tr>
|
||||
|
||||
export default ({
|
||||
host
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'intl'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import debounce from 'lodash/debounce'
|
||||
@@ -73,20 +74,19 @@ export default class extends Component {
|
||||
<input type='text' className='form-control' ref='clipboard' onChange={this._setRemoteClipboard} />
|
||||
<span className='input-group-btn'>
|
||||
<CopyToClipboard text={this.state.clipboard || ''}>
|
||||
<button className='btn btn-secondary'>
|
||||
<Button>
|
||||
<Icon icon='clipboard' /> {_('copyToClipboardLabel')}
|
||||
</button>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col mediumSize={2}>
|
||||
<button
|
||||
className='btn btn-secondary'
|
||||
<Button
|
||||
onClick={this._sendCtrlAltDel}
|
||||
>
|
||||
<Icon icon='vm-keyboard' /> {_('ctrlAltDelButtonLabel')}
|
||||
</button>
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className='console'>
|
||||
|
||||
@@ -8,9 +8,9 @@ import map from 'lodash/map'
|
||||
import pick from 'lodash/pick'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
import some from 'lodash/some'
|
||||
import StateButton from 'state-button'
|
||||
import TabButton from 'tab-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { confirm } from 'modal'
|
||||
import { connectStore, noop } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
@@ -166,40 +166,42 @@ class PifItem extends Component {
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{pif.carrier
|
||||
? <span className='tag tag-success'>
|
||||
{_('pifStatusConnected')}
|
||||
</span>
|
||||
: <span className='tag tag-default'>
|
||||
{_('pifStatusDisconnected')}
|
||||
</span>
|
||||
}
|
||||
<StateButton
|
||||
disabledLabel={_('pifDisconnected')}
|
||||
disabledHandler={connectPif}
|
||||
disabledTooltip={_('connectPif')}
|
||||
|
||||
enabledLabel={_('pifConnected')}
|
||||
enabledHandler={disconnectPif}
|
||||
enabledTooltip={_('disconnectPif')}
|
||||
|
||||
disabled={pif.attached && (pif.management || pif.disallowUnplug)}
|
||||
handlerParam={pif}
|
||||
state={pif.attached}
|
||||
/>
|
||||
{' '}
|
||||
<Tooltip content={pif.carrier ? _('pifPhysicallyConnected') : _('pifPhysicallyDisconnected')}>
|
||||
<Icon
|
||||
icon='network'
|
||||
size='lg'
|
||||
className={pif.carrier ? 'text-success' : 'text-muted'}
|
||||
/>
|
||||
</Tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<ButtonGroup className='pull-right'>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
disabled={pif.attached && (pif.management || pif.disallowUnplug)}
|
||||
handler={pif.attached ? disconnectPif : connectPif}
|
||||
handlerParam={pif}
|
||||
icon={pif.attached ? 'disconnect' : 'connect'}
|
||||
tooltip={pif.attached ? _('disconnectPif') : _('connectPif')}
|
||||
/>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
disabled={pif.physical || pif.disallowUnplug || pif.management}
|
||||
handler={deletePif}
|
||||
handlerParam={pif}
|
||||
icon='delete'
|
||||
tooltip={_('deletePif')}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<td className='text-xs-right'>
|
||||
<ActionRowButton
|
||||
disabled={pif.physical || pif.disallowUnplug || pif.management}
|
||||
handler={deletePif}
|
||||
handlerParam={pif}
|
||||
icon='delete'
|
||||
tooltip={_('deletePif')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
|
||||
export default (({
|
||||
export default ({
|
||||
host,
|
||||
networks,
|
||||
pifs,
|
||||
@@ -232,7 +234,7 @@ export default (({
|
||||
<th>{_('pifMtuLabel')}</th>
|
||||
<th>{_('defaultLockingMode')}</th>
|
||||
<th>{_('pifStatusLabel')}</th>
|
||||
<th />
|
||||
<th className='text-xs-right'>{_('pifAction')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -244,4 +246,4 @@ export default (({
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>)
|
||||
</Container>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import _ from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import React, { Component } from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import TabButton from 'tab-button'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { connectStore, formatSize } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { createDoesHostNeedRestart } from 'selectors'
|
||||
import { createDoesHostNeedRestart, createSelector } from 'selectors'
|
||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||
import { restartHost } from 'xo'
|
||||
import {
|
||||
isEmpty,
|
||||
isString
|
||||
} from 'lodash'
|
||||
|
||||
const MISSING_PATCH_COLUMNS = [
|
||||
{
|
||||
@@ -84,12 +87,56 @@ const INSTALLED_PATCH_COLUMNS = [
|
||||
}
|
||||
]
|
||||
|
||||
// support for software_version.platform_version ^2.1.1
|
||||
const INSTALLED_PATCH_COLUMNS_2 = [
|
||||
{
|
||||
default: true,
|
||||
name: _('patchNameLabel'),
|
||||
itemRenderer: patch => patch.name,
|
||||
sortCriteria: patch => patch.name
|
||||
},
|
||||
{
|
||||
name: _('patchDescription'),
|
||||
itemRenderer: patch => patch.description,
|
||||
sortCriteria: patch => patch.description
|
||||
},
|
||||
{
|
||||
name: _('patchSize'),
|
||||
itemRenderer: patch => formatSize(patch.size),
|
||||
sortCriteria: patch => patch.size
|
||||
}
|
||||
]
|
||||
|
||||
@connectStore(() => ({
|
||||
needsRestart: createDoesHostNeedRestart((_, props) => props.host)
|
||||
}))
|
||||
export default class HostPatches extends Component {
|
||||
_getPatches = createSelector(
|
||||
() => this.props.host,
|
||||
() => this.props.hostPatches,
|
||||
(host, hostPatches) => {
|
||||
if (isEmpty(host.patches) && isEmpty(hostPatches)) {
|
||||
return { patches: null }
|
||||
}
|
||||
|
||||
if (isString(host.patches[0])) {
|
||||
return {
|
||||
patches: hostPatches,
|
||||
columns: INSTALLED_PATCH_COLUMNS
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
patches: host.patches,
|
||||
columns: INSTALLED_PATCH_COLUMNS_2
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
render () {
|
||||
const { host, hostPatches, missingPatches, installAllPatches, installPatch } = this.props
|
||||
const { host, missingPatches, installAllPatches, installPatch } = this.props
|
||||
const { patches, columns } = this._getPatches()
|
||||
|
||||
return process.env.XOA_PLAN > 1
|
||||
? <Container>
|
||||
<Row>
|
||||
@@ -125,13 +172,12 @@ export default class HostPatches extends Component {
|
||||
</Row>}
|
||||
<Row>
|
||||
<Col>
|
||||
{!isEmpty(hostPatches)
|
||||
? (
|
||||
<span>
|
||||
<h3>{_('hostAppliedPatches')}</h3>
|
||||
<SortedTable collection={hostPatches} columns={INSTALLED_PATCH_COLUMNS} />
|
||||
</span>
|
||||
) : <h4 className='text-xs-center'>{_('patchNothing')}</h4>
|
||||
{patches
|
||||
? <span>
|
||||
<h3>{_('hostAppliedPatches')}</h3>
|
||||
<SortedTable collection={patches} columns={columns} />
|
||||
</span>
|
||||
: <h4 className='text-xs-center'>{_('patchNothing')}</h4>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -71,7 +71,6 @@ const SR_COLUMNS = [
|
||||
name: _('pbdAction'),
|
||||
itemRenderer: storage => !storage.attached &&
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
handler={deletePbd}
|
||||
handlerParam={storage.pbdId}
|
||||
icon='sr-forget'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import delay from 'lodash/delay'
|
||||
import find from 'lodash/find'
|
||||
@@ -55,7 +56,7 @@ const getType = function (param) {
|
||||
/**
|
||||
* Tries extracting Object targeted property
|
||||
*/
|
||||
const reduceObject = (value, propertyName = 'id') => value && value[propertyName] || value
|
||||
const reduceObject = (value, propertyName = 'id') => (value != null && value[propertyName]) || value
|
||||
|
||||
/**
|
||||
* Adapts all data "arrayed" by UI-multiple-selectors to job's cross-product trick
|
||||
@@ -387,7 +388,7 @@ export default class Jobs extends Component {
|
||||
{process.env.XOA_PLAN > 3
|
||||
? <span><ActionButton form='newJobForm' handler={this._handleSubmit} icon='save' btnStyle='primary'>{_('saveResourceSet')}</ActionButton>
|
||||
{' '}
|
||||
<button type='button' className='btn btn-default' onClick={this._reset}>{_('resetResourceSet')}</button></span>
|
||||
<Button onClick={this._reset}>{_('resetResourceSet')}</Button></span>
|
||||
: <span><Upgrade place='health' available={4} /></span>
|
||||
}
|
||||
</fieldset>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Button from 'button'
|
||||
import find from 'lodash/find'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
@@ -171,7 +172,7 @@ export default class Schedules extends Component {
|
||||
{process.env.XOA_PLAN > 3
|
||||
? <span><ActionButton form='newScheduleForm' handler={this._handleSubmit} icon='save' btnStyle='primary'>{_('saveBackupJob')}</ActionButton>
|
||||
{' '}
|
||||
<button type='button' className='btn btn-secondary' onClick={this._reset}>{_('selectTableReset')}</button></span>
|
||||
<Button onClick={this._reset}>{_('selectTableReset')}</Button></span>
|
||||
: <span><Upgrade place='health' available={4} /></span>
|
||||
}
|
||||
</div>
|
||||
@@ -195,9 +196,9 @@ export default class Schedules extends Component {
|
||||
<td className='hidden-xs-down'>{schedule.cron}</td>
|
||||
<td className='hidden-xs-down'>{schedule.timezone || _('jobServerTimezone')}</td>
|
||||
<td>
|
||||
<button type='button' className='btn btn-primary' onClick={() => this._edit(schedule.id)}><Icon icon='edit' /></button>
|
||||
<Button btnStyle='primary' onClick={() => this._edit(schedule.id)}><Icon icon='edit' /></Button>
|
||||
{' '}
|
||||
<button type='button' className='btn btn-danger' onClick={() => deleteSchedule(schedule)}><Icon icon='delete' /></button>
|
||||
<Button btnStyle='danger' onClick={() => deleteSchedule(schedule)}><Icon icon='delete' /></Button>
|
||||
</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _, { FormattedDuration } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import classnames from 'classnames'
|
||||
import forEach from 'lodash/forEach'
|
||||
import get from 'lodash/get'
|
||||
@@ -14,7 +15,6 @@ import renderXoItem from 'render-xo-item'
|
||||
import SortedTable from 'sorted-table'
|
||||
import Tooltip from 'tooltip'
|
||||
import { alert, confirm } from 'modal'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { connectStore } from 'utils'
|
||||
import { createGetObject } from 'selectors'
|
||||
import { FormattedDate } from 'react-intl'
|
||||
@@ -139,11 +139,11 @@ const LOG_COLUMNS = [
|
||||
<span className='pull-right'>
|
||||
<ButtonGroup>
|
||||
<Tooltip content={_('logDisplayDetails')}><ActionRowButton icon='preview' handler={showCalls} handlerParam={log} /></Tooltip>
|
||||
<Tooltip content={_('remove')}><ActionRowButton btnStyle='default' handler={deleteJobsLog} handlerParam={log.logKey} icon='delete' /></Tooltip>
|
||||
<Tooltip content={_('remove')}><ActionRowButton handler={deleteJobsLog} handlerParam={log.logKey} icon='delete' /></Tooltip>
|
||||
</ButtonGroup>
|
||||
</span>
|
||||
</span>,
|
||||
sortCriteria: log => log.hasErrors && ' ' || log.status
|
||||
sortCriteria: log => log.hasErrors ? ' ' : log.status
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import _ from 'intl'
|
||||
import Component from 'base-component'
|
||||
import classNames from 'classnames'
|
||||
import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Link from 'link'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
import { UpdateTag } from '../xoa-updates'
|
||||
import {
|
||||
addSubscriptions,
|
||||
@@ -32,6 +31,8 @@ import {
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
const returnTrue = () => true
|
||||
|
||||
@connectStore(() => ({
|
||||
isAdmin,
|
||||
nTasks: createGetObjectsOfType('task').count(
|
||||
@@ -70,8 +71,9 @@ export default class Menu extends Component {
|
||||
_checkPermissions = createSelector(
|
||||
() => this.props.isAdmin,
|
||||
() => this.props.permissions,
|
||||
(isAdmin, permissions) => ({ id }) =>
|
||||
isAdmin || permissions && permissions[id] && permissions[id].operate
|
||||
(isAdmin, permissions) => isAdmin
|
||||
? returnTrue
|
||||
: ({ id }) => permissions && permissions[id] && permissions[id].operate
|
||||
)
|
||||
|
||||
_getNoOperatablePools = createSelector(
|
||||
@@ -99,11 +101,22 @@ export default class Menu extends Component {
|
||||
return this.refs.content.offsetHeight
|
||||
}
|
||||
|
||||
_toggleCollapsed = () => {
|
||||
_toggleCollapsed = event => {
|
||||
event.preventDefault()
|
||||
this._removeListener()
|
||||
this.setState({ collapsed: !this.state.collapsed })
|
||||
}
|
||||
|
||||
_connect = event => {
|
||||
event.preventDefault()
|
||||
return connect()
|
||||
}
|
||||
|
||||
_signOut = event => {
|
||||
event.preventDefault()
|
||||
return signOut()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { isAdmin, nTasks, status, user, pools, nHosts } = this.props
|
||||
const noOperatablePools = this._getNoOperatablePools()
|
||||
@@ -149,7 +162,7 @@ export default class Menu extends Component {
|
||||
{ to: '/jobs/new', icon: 'menu-jobs-new', label: 'jobsNewPage' },
|
||||
{ to: '/jobs/schedules', icon: 'menu-jobs-schedule', label: 'jobsSchedulingPage' }
|
||||
]},
|
||||
{ to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
||||
isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
||||
{ to: '/tasks', icon: 'task', label: 'taskMenu', pill: nTasks },
|
||||
isAdmin && { to: '/xosan', icon: 'menu-xosan', label: 'xosan' },
|
||||
!(noOperatablePools && noResourceSets) && { to: '/vms/new', icon: 'menu-new', label: 'newMenu', subMenu: [
|
||||
@@ -175,9 +188,9 @@ export default class Menu extends Component {
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<Button onClick={this._toggleCollapsed}>
|
||||
<a className='nav-link' onClick={this._toggleCollapsed} href='#'>
|
||||
<Icon icon='menu-collapse' size='lg' fixedWidth />
|
||||
</Button>
|
||||
</a>
|
||||
</li>
|
||||
{map(items, (item, index) =>
|
||||
item && <MenuLinkItem key={index} item={item} />
|
||||
@@ -218,10 +231,10 @@ export default class Menu extends Component {
|
||||
<li> </li>
|
||||
<li> </li>
|
||||
<li className='nav-item xo-menu-item'>
|
||||
<Button className='nav-link' onClick={signOut}>
|
||||
<a className='nav-link' onClick={this._signOut} href='#'>
|
||||
<Icon icon='sign-out' size='lg' fixedWidth />
|
||||
<span className={styles.hiddenCollapsed}>{' '}{_('signOut')}</span>
|
||||
</Button>
|
||||
</a>
|
||||
</li>
|
||||
<li className='nav-item xo-menu-item'>
|
||||
<Link className='nav-link text-xs-center' to={'/user'}>
|
||||
@@ -236,9 +249,9 @@ export default class Menu extends Component {
|
||||
? <li className='nav-item text-xs-center'>{_('statusConnecting')}</li>
|
||||
: status === 'disconnected' &&
|
||||
<li className='nav-item text-xs-center xo-menu-item'>
|
||||
<Button className='nav-link' onClick={connect}>
|
||||
<a className='nav-link' onClick={this._connect} href='#'>
|
||||
<Icon icon='alarm' size='lg' fixedWidth /> {_('statusDisconnected')}
|
||||
</Button>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import BaseComponent from 'base-component'
|
||||
import Button from 'button'
|
||||
import classNames from 'classnames'
|
||||
import DebounceInput from 'react-debounce-input'
|
||||
import getEventValue from 'get-event-value'
|
||||
@@ -12,7 +13,6 @@ import store from 'store'
|
||||
import Tags from 'tags'
|
||||
import Tooltip from 'tooltip'
|
||||
import Wizard, { Section } from 'wizard'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { Limits } from 'usage'
|
||||
@@ -91,6 +91,8 @@ const NB_VMS_MAX = 100
|
||||
|
||||
const getObject = createGetObject((_, id) => id)
|
||||
|
||||
const returnTrue = () => true
|
||||
|
||||
// Sub-components
|
||||
|
||||
const SectionContent = ({ column, children }) => (
|
||||
@@ -175,7 +177,7 @@ class Vif extends BaseComponent {
|
||||
</span>
|
||||
</LineItem>
|
||||
<Item>
|
||||
<Button onClick={onDelete} bsStyle='secondary'>
|
||||
<Button onClick={onDelete}>
|
||||
<Icon icon='new-vm-remove' />
|
||||
</Button>
|
||||
</Item>
|
||||
@@ -444,7 +446,7 @@ export default class NewVm extends BaseComponent {
|
||||
cpuWeight: '',
|
||||
memoryDynamicMax: template.memory.dynamic[1],
|
||||
// installation
|
||||
installMethod: template.install_methods && template.install_methods[0] || 'SSH',
|
||||
installMethod: (template.install_methods != null && template.install_methods[0]) || 'SSH',
|
||||
sshKeys: this.props.userSshKeys && this.props.userSshKeys.length && [ 0 ],
|
||||
customConfig: '#cloud-config\n#hostname: myhostname\n#ssh_authorized_keys:\n# - ssh-rsa <myKey>\n#packages:\n# - htop\n',
|
||||
// interfaces
|
||||
@@ -492,9 +494,11 @@ export default class NewVm extends BaseComponent {
|
||||
)
|
||||
|
||||
_getCanOperate = createSelector(
|
||||
() => this.props.isAdmin,
|
||||
() => this.props.permissions,
|
||||
permissions => ({ id }) =>
|
||||
this.props.isAdmin || permissions && permissions[id] && permissions[id].operate
|
||||
(isAdmin, permissions) => isAdmin
|
||||
? returnTrue
|
||||
: ({ id }) => permissions && permissions[id] && permissions[id].operate
|
||||
)
|
||||
_getVmPredicate = createSelector(
|
||||
this._getIsInPool,
|
||||
@@ -796,7 +800,6 @@ export default class NewVm extends BaseComponent {
|
||||
</Wizard>
|
||||
<div className={styles.submitSection}>
|
||||
<ActionButton
|
||||
btnStyle='secondary'
|
||||
className={styles.button}
|
||||
handler={this._reset}
|
||||
icon='new-vm-reset'
|
||||
@@ -963,7 +966,7 @@ export default class NewVm extends BaseComponent {
|
||||
value={newSshKey}
|
||||
/>
|
||||
<span className='input-group-btn'>
|
||||
<Button className='btn btn-secondary' onClick={this._addNewSshKey} disabled={!newSshKey}>
|
||||
<Button onClick={this._addNewSshKey} disabled={!newSshKey}>
|
||||
<Icon icon='add' />
|
||||
</Button>
|
||||
</span>
|
||||
@@ -1116,6 +1119,7 @@ export default class NewVm extends BaseComponent {
|
||||
<SectionContent column>
|
||||
{map(VIFs, (vif, index) => <div key={index}>
|
||||
<Vif
|
||||
networkPredicate={this._getNetworkPredicate()}
|
||||
onChangeAddresses={this._linkState(`VIFs.${index}.addresses`, '*.id')}
|
||||
onChangeMac={this._linkState(`VIFs.${index}.mac`)}
|
||||
onChangeNetwork={this._linkState(`VIFs.${index}.network`, 'id')}
|
||||
@@ -1127,7 +1131,7 @@ export default class NewVm extends BaseComponent {
|
||||
{index < VIFs.length - 1 && <hr />}
|
||||
</div>)}
|
||||
<Item>
|
||||
<Button onClick={this._addInterface} bsStyle='secondary'>
|
||||
<Button onClick={this._addInterface}>
|
||||
<Icon icon='new-vm-add' />
|
||||
{' '}
|
||||
{_('newVmAddInterface')}
|
||||
@@ -1246,7 +1250,7 @@ export default class NewVm extends BaseComponent {
|
||||
/>
|
||||
</Item>
|
||||
<Item>
|
||||
<Button onClick={() => this._removeVdi(index)} bsStyle='secondary'>
|
||||
<Button onClick={() => this._removeVdi(index)}>
|
||||
<Icon icon='new-vm-remove' />
|
||||
</Button>
|
||||
</Item>
|
||||
@@ -1254,7 +1258,7 @@ export default class NewVm extends BaseComponent {
|
||||
{index < VDIs.length - 1 && <hr />}
|
||||
</div>)}
|
||||
<Item>
|
||||
<Button onClick={this._addVdi} bsStyle='secondary'>
|
||||
<Button onClick={this._addVdi}>
|
||||
<Icon icon='new-vm-add' />
|
||||
{' '}
|
||||
{_('newVmAddDisk')}
|
||||
@@ -1294,7 +1298,7 @@ export default class NewVm extends BaseComponent {
|
||||
const { formatMessage } = this.props.intl
|
||||
return <Section icon='new-vm-advanced' title='newVmAdvancedPanel' done={this._isAdvancedDone()}>
|
||||
<SectionContent column>
|
||||
<Button bsStyle='secondary' onClick={this._toggleState('showAdvanced')}>
|
||||
<Button onClick={this._toggleState('showAdvanced')}>
|
||||
{showAdvanced ? _('newVmHideAdvanced') : _('newVmShowAdvanced')}
|
||||
</Button>
|
||||
</SectionContent>
|
||||
@@ -1323,7 +1327,7 @@ export default class NewVm extends BaseComponent {
|
||||
<Tags labels={tags} onChange={this._linkState('tags')} />
|
||||
</Item>
|
||||
</SectionContent>,
|
||||
<SectionContent>
|
||||
this._getResourceSet() !== undefined && <SectionContent>
|
||||
<Item>
|
||||
<input
|
||||
checked={share}
|
||||
@@ -1407,7 +1411,7 @@ export default class NewVm extends BaseComponent {
|
||||
/>
|
||||
<span className='input-group-btn'>
|
||||
<Tooltip content={_('newVmNumberRecalculate')}>
|
||||
<Button bsStyle='secondary' disabled={!multipleVms} onClick={this._updateNbVms}>
|
||||
<Button disabled={!multipleVms} onClick={this._updateNbVms}>
|
||||
<Icon icon='arrow-right' />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -515,7 +515,7 @@ export default class New extends Component {
|
||||
type='text'
|
||||
/>
|
||||
<span className='input-group-btn'>
|
||||
<ActionButton icon='search' btnStyle='default' handler={this._handleSearchServer} />
|
||||
<ActionButton icon='search' handler={this._handleSearchServer} />
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -560,7 +560,7 @@ export default class New extends Component {
|
||||
ref='port'
|
||||
type='text'
|
||||
/>
|
||||
<ActionButton icon='search' btnStyle='default' handler={this._handleSearchServer} />
|
||||
<ActionButton icon='search' handler={this._handleSearchServer} />
|
||||
</div>
|
||||
{auth &&
|
||||
<fieldset>
|
||||
|
||||
@@ -92,7 +92,6 @@ import TabPatches from './tab-patches'
|
||||
}
|
||||
})
|
||||
export default class Pool extends Component {
|
||||
|
||||
_setNameDescription = nameDescription => editPool(this.props.pool, { name_description: nameDescription })
|
||||
_setNameLabel = nameLabel => editPool(this.props.pool, { name_label: nameLabel })
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import _ from 'intl'
|
||||
import ActionRow from 'action-row-button'
|
||||
import Button from 'button'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
import TabButton from 'tab-button'
|
||||
import React, { Component } from 'react'
|
||||
import TabButton from 'tab-button'
|
||||
import { deleteMessage } from 'xo'
|
||||
import { createPager } from 'selectors'
|
||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||
@@ -42,12 +43,12 @@ export default class TabLogs extends Component {
|
||||
: <div>
|
||||
<Row>
|
||||
<Col className='text-xs-right'>
|
||||
<button className='btn btn-lg btn-tab' onClick={this._previousPage}>
|
||||
<Button size='large' onClick={this._previousPage}>
|
||||
<
|
||||
</button>
|
||||
<button className='btn btn-lg btn-tab' onClick={this._nextPage}>
|
||||
</Button>
|
||||
<Button size='large' onClick={this._nextPage}>
|
||||
>
|
||||
</button>
|
||||
</Button>
|
||||
<TabButton
|
||||
btnStyle='danger'
|
||||
handler={this._removeAllLogs}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import _ from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import BaseComponent from 'base-component'
|
||||
import Button from 'button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
@@ -9,10 +11,9 @@ import some from 'lodash/some'
|
||||
import SortedTable from 'sorted-table'
|
||||
import TabButton from 'tab-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Button, ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Text, Number } from 'editable'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { connectStore } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { Text, Number } from 'editable'
|
||||
import { Toggle } from 'form'
|
||||
import {
|
||||
createFinder,
|
||||
@@ -205,10 +206,9 @@ class PifItem extends Component {
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<ButtonGroup className='pull-right'>
|
||||
<td className='text-xs-right'>
|
||||
<ButtonGroup>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
disabled={disableUnplug}
|
||||
handler={pif.attached ? disconnectPif : connectPif}
|
||||
handlerParam={pif}
|
||||
@@ -228,7 +228,7 @@ class PifsItem extends BaseComponent {
|
||||
|
||||
return <div>
|
||||
<Tooltip content={showPifs ? _('hidePifs') : _('showPifs')}>
|
||||
<Button bsSize='small' bsStyle='secondary' className='mb-1 pull-right' onClick={this.toggleState('showPifs')}>
|
||||
<Button size='small' className='mb-1 pull-right' onClick={this.toggleState('showPifs')}>
|
||||
<Icon icon={showPifs ? 'hidden' : 'shown'} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -272,9 +272,8 @@ class NetworkActions extends Component {
|
||||
render () {
|
||||
const { network, disableNetworkDelete } = this.props
|
||||
|
||||
return <ButtonGroup className='pull-right'>
|
||||
return <ButtonGroup>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
disabled={disableNetworkDelete}
|
||||
handler={deleteNetwork}
|
||||
handlerParam={network}
|
||||
@@ -324,7 +323,8 @@ const NETWORKS_COLUMNS = [
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
itemRenderer: network => <NetworkActions network={network} />
|
||||
itemRenderer: network => <NetworkActions network={network} />,
|
||||
textAlign: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -454,7 +454,7 @@ export class Edit extends Component {
|
||||
<input className='form-control' type='number' min={0} onChange={this.linkState(`ipPools.${index}.quantity`)} value={firstDefined(ipPool.quantity, '')} placeholder='∞' />
|
||||
</Col>
|
||||
<Col mediumSize={2}>
|
||||
<ActionButton btnStyle='secondary' icon='delete' handler={this._removeIpPool} handlerParam={index} />
|
||||
<ActionButton icon='delete' handler={this._removeIpPool} handlerParam={index} />
|
||||
</Col>
|
||||
</Row>)}
|
||||
<Row>
|
||||
@@ -465,7 +465,7 @@ export class Edit extends Component {
|
||||
<input className='form-control' type='number' min={0} onChange={this.linkState('newIpPoolQuantity')} value={state.newIpPoolQuantity || ''} placeholder='∞' />
|
||||
</Col>
|
||||
<Col mediumSize={2}>
|
||||
<ActionButton btnStyle='secondary' icon='add' handler={this._addIpPool} />
|
||||
<ActionButton icon='add' handler={this._addIpPool} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
@@ -478,7 +478,7 @@ export class Edit extends Component {
|
||||
<li className='list-group-item text-xs-center'>
|
||||
<div className='btn-toolbar'>
|
||||
<ActionButton btnStyle='primary' icon='save' handler={this._save} type='submit'>{_('saveResourceSet')}</ActionButton>
|
||||
<ActionButton btnStyle='secondary' icon='reset' handler={this._reset}>{_('resetResourceSet')}</ActionButton>
|
||||
<ActionButton icon='reset' handler={this._reset}>{_('resetResourceSet')}</ActionButton>
|
||||
{resourceSet && <ActionButton btnStyle='danger' icon='delete' handler={deleteResourceSet} handlerParam={resourceSet}>{_('deleteResourceSet')}</ActionButton>}
|
||||
</div>
|
||||
</li>
|
||||
@@ -686,7 +686,6 @@ export default class Self extends Component {
|
||||
{_('resourceSetNew')}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
btnStyle='secondary'
|
||||
handler={recomputeResourceSetsLimits}
|
||||
icon='refresh'
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import Component from 'base-component'
|
||||
import filter from 'lodash/filter'
|
||||
import forEach from 'lodash/forEach'
|
||||
@@ -19,7 +20,6 @@ import { connectStore } from 'utils'
|
||||
import { Container } from 'grid'
|
||||
import { error } from 'notification'
|
||||
import { SelectHighLevelObject, SelectRole, SelectSubject } from 'select-objects'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
@@ -161,7 +161,7 @@ export default class Acls extends Component {
|
||||
const newSomeTypeFilters = some(newTypeFilters)
|
||||
|
||||
// If some objects need to be removed from the selected objects
|
||||
if (!newTypeFilters[type] || !someTypeFilters && newSomeTypeFilters) {
|
||||
if (!newTypeFilters[type] || (!someTypeFilters && newSomeTypeFilters)) {
|
||||
this.setState({
|
||||
objects: filter(objects, ({ type }) => !newSomeTypeFilters || newTypeFilters[type])
|
||||
})
|
||||
@@ -172,7 +172,7 @@ export default class Acls extends Component {
|
||||
someTypeFilters: some(newTypeFilters)
|
||||
}, () => {
|
||||
// If some objects need to be removed from the selected objects
|
||||
if (!this.state.typeFilters[type] || !someTypeFilters && this.state.someTypeFilters) {
|
||||
if (!this.state.typeFilters[type] || (!someTypeFilters && this.state.someTypeFilters)) {
|
||||
this.setState({
|
||||
objects: filter(objects, this._getObjectPredicate())
|
||||
})
|
||||
@@ -243,7 +243,7 @@ export default class Acls extends Component {
|
||||
<SelectHighLevelObject multi onChange={this.linkState('objects')} value={objects} predicate={this._getObjectPredicate()} />
|
||||
</div>
|
||||
<div className='form-group mb-1'>
|
||||
<ButtonGroup className='mr-1'>
|
||||
<ButtonGroup>
|
||||
{map(TYPES, type =>
|
||||
<ActionButton
|
||||
btnStyle={typeFilters[type] ? 'success' : 'secondary'}
|
||||
@@ -256,7 +256,8 @@ export default class Acls extends Component {
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
<ActionButton tooltip='Select all' btnStyle='secondary' size='small' icon='add' handler={this._selectAll} />
|
||||
{' '}
|
||||
<ActionButton tooltip='Select all' size='small' icon='add' handler={this._selectAll} />
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<SelectRole onChange={this.linkState('action')} value={action} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import Dropzone from 'dropzone'
|
||||
import Icon from 'icon'
|
||||
@@ -77,7 +77,6 @@ export default class Config extends Component {
|
||||
{_('importConfig')}
|
||||
</ActionButton>
|
||||
<Button
|
||||
bsStyle='secondary'
|
||||
onClick={this._unselectFile}
|
||||
>
|
||||
{_('importVmsCleanList')}
|
||||
@@ -93,7 +92,7 @@ export default class Config extends Component {
|
||||
<br />
|
||||
<div className='mt-1'>
|
||||
<h2><Icon icon='export' /> {_('exportConfig')}</h2>
|
||||
<Button bsStyle='primary' onClick={exportConfig}>{_('downloadConfig')}</Button>
|
||||
<Button btnStyle='primary' onClick={exportConfig}>{_('downloadConfig')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class UserDisplay extends Component {
|
||||
const { id, users } = this.props
|
||||
|
||||
return <span>
|
||||
{id && (users && users[id] && users[id].email) || <em><{_('unknownUser')}></em>}
|
||||
{(id && users && users[id] && users[id].email) || <em><{_('unknownUser')}></em>}
|
||||
{' '}
|
||||
<ActionButton className='pull-right' btnStyle='primary' size='small' icon='remove' handler={this._removeUser} />
|
||||
</span>
|
||||
|
||||
@@ -93,7 +93,6 @@ class IpsCell extends BaseComponent {
|
||||
</Col>
|
||||
<Col mediumSize={1} offset={6}>
|
||||
<ActionRowButton
|
||||
btnStyle='secondary'
|
||||
handler={this._deleteIp}
|
||||
handlerParam={ip}
|
||||
icon='delete'
|
||||
@@ -121,7 +120,6 @@ class IpsCell extends BaseComponent {
|
||||
}</Col>
|
||||
<Col mediumSize={1}>
|
||||
<ActionRowButton
|
||||
btnStyle='secondary'
|
||||
handler={this._deleteIp}
|
||||
handlerParam={ip}
|
||||
icon='delete'
|
||||
@@ -196,7 +194,6 @@ class NetworksCell extends BaseComponent {
|
||||
</Col>
|
||||
<Col mediumSize={1}>
|
||||
<ActionRowButton
|
||||
btnStyle='secondary'
|
||||
handler={this._deleteNetwork}
|
||||
handlerParam={networkId}
|
||||
icon='delete'
|
||||
@@ -288,7 +285,7 @@ export default class Ips extends BaseComponent {
|
||||
{
|
||||
name: '',
|
||||
itemRenderer: ipPool => <span className='pull-right'>
|
||||
<ActionButton btnStyle='secondary' handler={deleteIpPool} handlerParam={ipPool.id} icon='delete' />
|
||||
<ActionButton handler={deleteIpPool} handlerParam={ipPool.id} icon='delete' />
|
||||
</span>
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import BaseComponent from 'base-component'
|
||||
import ButtonGroup from 'button-group'
|
||||
import Copiable from 'copiable'
|
||||
import find from 'lodash/find'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
@@ -11,7 +12,6 @@ import styles from './index.css'
|
||||
import TabButton from 'tab-button'
|
||||
import { addSubscriptions } from 'utils'
|
||||
import { alert, confirm } from 'modal'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { createSelector } from 'selectors'
|
||||
import { FormattedDate } from 'react-intl'
|
||||
import { subscribeApiLogs, subscribeUsers, deleteApiLog } from 'xo'
|
||||
@@ -62,7 +62,6 @@ const COLUMNS = [
|
||||
itemRenderer: (log, { showError }) => <div className='text-xs-right'>
|
||||
<ButtonGroup>
|
||||
<ActionRowButton
|
||||
btnStyle='secondary'
|
||||
handler={showError}
|
||||
handlerParam={log}
|
||||
icon='preview'
|
||||
@@ -76,7 +75,6 @@ const COLUMNS = [
|
||||
tooltip={_('logDelete')}
|
||||
/>
|
||||
{CAN_REPORT_BUG && <ActionRowButton
|
||||
btnStyle='secondary'
|
||||
handler={() => reportBug(log)}
|
||||
icon='bug'
|
||||
tooltip={_('reportBug')}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import ActionToggle from 'action-toggle'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import GenericInput from 'json-schema-input'
|
||||
import Icon from 'icon'
|
||||
@@ -149,9 +150,9 @@ class Plugin extends Component {
|
||||
</Col>
|
||||
<Col mediumSize={4}>
|
||||
<div className='form-group pull-right small'>
|
||||
<button type='button' className='btn btn-primary' onClick={this._updateExpanded}>
|
||||
<Button btnStyle='primary' onClick={this._updateExpanded}>
|
||||
<Icon icon={expanded ? 'minus' : 'plus'} />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -172,14 +173,13 @@ class Plugin extends Component {
|
||||
))}
|
||||
</select>
|
||||
<span className='input-group-btn'>
|
||||
<button
|
||||
className='btn btn-primary'
|
||||
<Button
|
||||
btnStyle='primary'
|
||||
disabled={!editedConfig}
|
||||
onClick={this._applyPredefinedConfiguration}
|
||||
type='button'
|
||||
>
|
||||
{_('applyPluginPreset')}
|
||||
</button>
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
<hr />
|
||||
@@ -196,14 +196,14 @@ class Plugin extends Component {
|
||||
<div className='form-group pull-right'>
|
||||
<div className='btn-toolbar'>
|
||||
<div className='btn-group'>
|
||||
<ActionButton disabled={!props.configuration} icon='delete' className='btn-danger' handler={this._deleteConfiguration}>
|
||||
<ActionButton btnStyle='danger' disabled={!props.configuration} icon='delete' handler={this._deleteConfiguration}>
|
||||
{_('deletePluginConfiguration')}
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='btn-group'>
|
||||
<button disabled={!editedConfig} type='reset' className='btn'>
|
||||
<Button disabled={!editedConfig} type='reset'>
|
||||
{_('cancelPluginEdition')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className='btn-group'>
|
||||
<ActionButton disabled={!editedConfig} form={this.configFormId} icon='save' className='btn-primary' handler={this._saveConfiguration}>
|
||||
|
||||
@@ -104,8 +104,8 @@ export default class Servers extends Component {
|
||||
enabledHandler={disconnectServer}
|
||||
enabledTooltip={_('serverDisconnect')}
|
||||
|
||||
disabled={server.status === 'connecting'}
|
||||
handlerParam={server}
|
||||
pending={server.status === 'connecting'}
|
||||
state={server.status === 'connected'}
|
||||
/>
|
||||
{' '}
|
||||
|
||||
@@ -52,7 +52,7 @@ import TabXosan from './tab-xosan'
|
||||
)
|
||||
|
||||
const getPbds = createGetObjectsOfType('PBD').pick(
|
||||
createSelector(getSr, sr => sr.$PBDs),
|
||||
createSelector(getSr, sr => sr.$PBDs)
|
||||
)
|
||||
|
||||
const getSrHosts = createGetObjectsOfType('host').pick(
|
||||
@@ -65,7 +65,7 @@ import TabXosan from './tab-xosan'
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const getVdis = createGetObjectsOfType('VDI').pick(
|
||||
createSelector(getSr, sr => sr.VDIs),
|
||||
createSelector(getSr, sr => sr.VDIs)
|
||||
).sort()
|
||||
|
||||
const getLogs = createGetObjectMessages(getSr)
|
||||
@@ -80,7 +80,7 @@ import TabXosan from './tab-xosan'
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const getVdiSnapshots = createGetObjectsOfType('VDI-snapshot').pick(
|
||||
createSelector(getSr, sr => sr.VDIs),
|
||||
createSelector(getSr, sr => sr.VDIs)
|
||||
).sort()
|
||||
|
||||
const getVdiSnapshotToVdi = createSelector(
|
||||
|
||||
@@ -74,6 +74,11 @@ const COLUMNS = [
|
||||
}
|
||||
]
|
||||
|
||||
const FILTERS = {
|
||||
filterNoSnapshots: 'type:!VDI-snapshot',
|
||||
filterOnlySnapshots: 'type:VDI-snapshot'
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default ({ vdis, vdiSnapshots, vdisToVmIds }) => (
|
||||
@@ -81,7 +86,7 @@ export default ({ vdis, vdiSnapshots, vdisToVmIds }) => (
|
||||
<Row>
|
||||
<Col>
|
||||
{!isEmpty(vdis)
|
||||
? <SortedTable collection={vdis.concat(vdiSnapshots)} userData={vdisToVmIds} columns={COLUMNS} />
|
||||
? <SortedTable collection={vdis.concat(vdiSnapshots)} userData={vdisToVmIds} columns={COLUMNS} filters={FILTERS} />
|
||||
: <h4 className='text-xs-center'>{_('srNoVdis')}</h4>
|
||||
}
|
||||
</Col>
|
||||
|
||||
@@ -52,7 +52,6 @@ const HOST_COLUMNS = [
|
||||
name: _('pbdAction'),
|
||||
itemRenderer: pbd => !pbd.attached &&
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
handler={deletePbd}
|
||||
handlerParam={pbd}
|
||||
icon='sr-forget'
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import _ from 'intl'
|
||||
import ActionRow from 'action-row-button'
|
||||
import Button from 'button'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import map from 'lodash/map'
|
||||
import TabButton from 'tab-button'
|
||||
import React, { Component } from 'react'
|
||||
import TabButton from 'tab-button'
|
||||
import { deleteMessage } from 'xo'
|
||||
import { createPager } from 'selectors'
|
||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||
@@ -42,12 +43,12 @@ export default class TabLogs extends Component {
|
||||
: <div>
|
||||
<Row>
|
||||
<Col className='text-xs-right'>
|
||||
<button className='btn btn-lg btn-tab' onClick={this._previousPage}>
|
||||
<Button size='large' onClick={this._previousPage}>
|
||||
<
|
||||
</button>
|
||||
<button className='btn btn-lg btn-tab' onClick={this._nextPage}>
|
||||
</Button>
|
||||
<Button size='large' onClick={this._nextPage}>
|
||||
>
|
||||
</button>
|
||||
</Button>
|
||||
<TabButton
|
||||
btnStyle='danger'
|
||||
handler={this._deleteAllLogs}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _, { messages } from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import CenterPanel from 'center-panel'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
@@ -9,7 +10,6 @@ import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Card, CardBlock, CardHeader } from 'card'
|
||||
import { connectStore } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
@@ -54,13 +54,11 @@ export const TaskItem = connectStore(() => ({
|
||||
<Col mediumSize={2}>
|
||||
<ButtonGroup>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
handler={cancelTask}
|
||||
handlerParam={task}
|
||||
icon='task-cancel'
|
||||
/>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
handler={destroyTask}
|
||||
handlerParam={task}
|
||||
icon='task-destroy'
|
||||
|
||||
@@ -253,9 +253,8 @@ const SshKeys = addSubscriptions({
|
||||
<Col size={8} style={SSH_KEY_STYLE}>
|
||||
{sshKey.key}
|
||||
</Col>
|
||||
<Col size={2}>
|
||||
<Col size={2} className='text-xs-right'>
|
||||
<ActionButton
|
||||
className='btn-secondary pull-right'
|
||||
icon='delete'
|
||||
handler={() => deleteSshKey(sshKey)}
|
||||
>
|
||||
@@ -372,6 +371,7 @@ export default class User extends Component {
|
||||
<option value='en'>English</option>
|
||||
<option value='fr'>Français</option>
|
||||
<option value='he'>עברי</option>
|
||||
<option value='pl'>Polski</option>
|
||||
<option value='pt'>Português</option>
|
||||
<option value='es'>Español</option>
|
||||
<option value='zh'>简体中文</option>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as FormGrid from 'form-grid'
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import Dropzone from 'dropzone'
|
||||
import Icon from 'icon'
|
||||
@@ -362,13 +363,11 @@ export default class Import extends Component {
|
||||
>
|
||||
{_('newImport')}
|
||||
</ActionButton>
|
||||
<button
|
||||
className='btn btn-secondary'
|
||||
<Button
|
||||
onClick={this._handleCleanSelectedVms}
|
||||
type='button'
|
||||
>
|
||||
{_('importVmsCleanList')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ActionBar from 'action-bar'
|
||||
import React from 'react'
|
||||
import { connectStore } from 'utils'
|
||||
import { includes } from 'lodash'
|
||||
import { isAdmin } from 'selectors'
|
||||
import {
|
||||
cloneVm,
|
||||
@@ -21,32 +22,40 @@ const vmActionBarByState = {
|
||||
{
|
||||
icon: 'vm-stop',
|
||||
label: 'stopVmLabel',
|
||||
handler: stopVm
|
||||
handler: stopVm,
|
||||
pending: includes(vm.current_operations, 'clean_shutdown')
|
||||
},
|
||||
{
|
||||
icon: 'vm-reboot',
|
||||
label: 'rebootVmLabel',
|
||||
handler: restartVm
|
||||
handler: restartVm,
|
||||
pending: includes(vm.current_operations, 'clean_reboot')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-migrate',
|
||||
label: 'migrateVmLabel',
|
||||
handler: migrateVm
|
||||
handler: migrateVm,
|
||||
pending:
|
||||
includes(vm.current_operations, 'migrate_send') ||
|
||||
includes(vm.current_operations, 'pool_migrate')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-snapshot',
|
||||
label: 'snapshotVmLabel',
|
||||
handler: snapshotVm
|
||||
handler: snapshotVm,
|
||||
pending: includes(vm.current_operations, 'snapshot')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'export',
|
||||
label: 'exportVmLabel',
|
||||
handler: exportVm
|
||||
handler: exportVm,
|
||||
pending: includes(vm.current_operations, 'export')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-copy',
|
||||
label: 'copyVmLabel',
|
||||
handler: copyVm
|
||||
handler: copyVm,
|
||||
pending: includes(vm.current_operations, 'copy')
|
||||
}
|
||||
]}
|
||||
display='icon'
|
||||
@@ -59,32 +68,38 @@ const vmActionBarByState = {
|
||||
{
|
||||
icon: 'vm-start',
|
||||
label: 'startVmLabel',
|
||||
handler: startVm
|
||||
handler: startVm,
|
||||
pending: includes(vm.current_operations, 'start')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-fast-clone',
|
||||
label: 'fastCloneVmLabel',
|
||||
handler: cloneVm
|
||||
handler: cloneVm,
|
||||
pending: includes(vm.current_operations, 'clone')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-migrate',
|
||||
label: 'migrateVmLabel',
|
||||
handler: migrateVm
|
||||
handler: migrateVm,
|
||||
pending: includes(vm.current_operations, 'pool_migrate')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-snapshot',
|
||||
label: 'snapshotVmLabel',
|
||||
handler: snapshotVm
|
||||
handler: snapshotVm,
|
||||
pending: includes(vm.current_operations, 'snapshot')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'export',
|
||||
label: 'exportVmLabel',
|
||||
handler: exportVm
|
||||
handler: exportVm,
|
||||
pending: includes(vm.current_operations, 'export')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-copy',
|
||||
label: 'copyVmLabel',
|
||||
handler: copyVm
|
||||
handler: copyVm,
|
||||
pending: includes(vm.current_operations, 'copy')
|
||||
}
|
||||
]}
|
||||
display='icon'
|
||||
@@ -97,22 +112,26 @@ const vmActionBarByState = {
|
||||
{
|
||||
icon: 'vm-start',
|
||||
label: 'resumeVmLabel',
|
||||
handler: resumeVm
|
||||
handler: resumeVm,
|
||||
pending: includes(vm.current_operations, 'start')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-snapshot',
|
||||
label: 'snapshotVmLabel',
|
||||
handler: snapshotVm
|
||||
handler: snapshotVm,
|
||||
pending: includes(vm.current_operations, 'snapshot')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'export',
|
||||
label: 'exportVmLabel',
|
||||
handler: exportVm
|
||||
handler: exportVm,
|
||||
pending: includes(vm.current_operations, 'export')
|
||||
},
|
||||
(isAdmin || !vm.resourceSet) && {
|
||||
icon: 'vm-copy',
|
||||
label: 'copyVmLabel',
|
||||
handler: copyVm
|
||||
handler: copyVm,
|
||||
pending: includes(vm.current_operations, 'copy')
|
||||
}
|
||||
]}
|
||||
display='icon'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'intl'
|
||||
import Component from 'base-component'
|
||||
import Copiable from 'copiable'
|
||||
import getEventValue from 'get-event-value'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import React from 'react'
|
||||
@@ -32,7 +33,8 @@ import {
|
||||
stopVm,
|
||||
suspendVm,
|
||||
XEN_DEFAULT_CPU_CAP,
|
||||
XEN_DEFAULT_CPU_WEIGHT
|
||||
XEN_DEFAULT_CPU_WEIGHT,
|
||||
XEN_VIDEORAM_VALUES
|
||||
} from 'xo'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
@@ -252,6 +254,28 @@ export default ({
|
||||
<AffinityHost vm={vm} />
|
||||
</td>
|
||||
</tr>
|
||||
{vm.virtualizationMode === 'hvm' &&
|
||||
<tr>
|
||||
<th>{_('vmVga')}</th>
|
||||
<td>
|
||||
<Toggle value={vm.vga === 'std'} onChange={value => editVm(vm, { vga: value ? 'std' : 'cirrus' })} />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{vm.vga === 'std' &&
|
||||
<tr>
|
||||
<th>{_('vmVideoram')}</th>
|
||||
<td>
|
||||
<select
|
||||
className='form-control'
|
||||
onChange={event => editVm(vm, { videoram: +getEventValue(event) })}
|
||||
value={vm.videoram}
|
||||
>
|
||||
{map(XEN_VIDEORAM_VALUES, val => <option value={val}>{formatSize(val * 1048576)}</option>)}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'intl'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import debounce from 'lodash/debounce'
|
||||
@@ -8,7 +9,6 @@ import IsoDevice from 'iso-device'
|
||||
import NoVnc from 'react-novnc'
|
||||
import React from 'react'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Button } from 'react-bootstrap-4/lib'
|
||||
import { resolveUrl, isVmRunning } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
@@ -109,20 +109,19 @@ export default class TabConsole extends Component {
|
||||
<input type='text' className='form-control' ref='clipboard' onChange={this._setRemoteClipboard} />
|
||||
<span className='input-group-btn'>
|
||||
<CopyToClipboard text={this.state.clipboard || ''}>
|
||||
<button className='btn btn-secondary'>
|
||||
<Button>
|
||||
<Icon icon='clipboard' /> {_('copyToClipboardLabel')}
|
||||
</button>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col mediumSize={2}>
|
||||
<button
|
||||
className='btn btn-secondary'
|
||||
<Button
|
||||
onClick={this._sendCtrlAltDel}
|
||||
>
|
||||
<Icon icon='vm-keyboard' /> {_('ctrlAltDelButtonLabel')}
|
||||
</button>
|
||||
</Button>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<input
|
||||
@@ -137,7 +136,7 @@ export default class TabConsole extends Component {
|
||||
</Col>
|
||||
<Col mediumSize={1}>
|
||||
<Tooltip content={minimalLayout ? _('showHeaderTooltip') : _('hideHeaderTooltip')}>
|
||||
<Button bsStyle='secondary' onClick={this._toggleMinimalLayout}>
|
||||
<Button onClick={this._toggleMinimalLayout}>
|
||||
<Icon icon={minimalLayout ? 'caret' : 'caret-up'} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import _ from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import React, { Component } from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import Tooltip from 'tooltip'
|
||||
import React, { Component } from 'react'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
@@ -85,7 +85,6 @@ const CONTAINER_COLUMNS = [
|
||||
]
|
||||
|
||||
export default class TabContainers extends Component {
|
||||
|
||||
render () {
|
||||
const { vm } = this.props
|
||||
if (isEmpty(vm.docker.containers)) {
|
||||
|
||||
@@ -82,7 +82,7 @@ class NewDisk extends Component {
|
||||
.then(diskId => {
|
||||
const mode = readOnly.value ? 'RO' : 'RW'
|
||||
return attachDiskToVm(diskId, vm, {
|
||||
bootable: bootable.value,
|
||||
bootable: bootable && bootable.value,
|
||||
mode
|
||||
})
|
||||
.then(onClose)
|
||||
@@ -110,19 +110,19 @@ class NewDisk extends Component {
|
||||
</div>
|
||||
<fieldset className='form-inline'>
|
||||
<div className='form-group'>
|
||||
<input type='text' ref='name' placeholder={formatMessage(messages.vdbNamePlaceHolder)} className='form-control' required />
|
||||
<input type='text' ref='name' placeholder={formatMessage(messages.vbdNamePlaceHolder)} className='form-control' required />
|
||||
</div>
|
||||
{' '}
|
||||
<div className='form-group'>
|
||||
<SizeInput ref='size' placeholder={formatMessage(messages.vdbSizePlaceHolder)} required />
|
||||
<SizeInput ref='size' placeholder={formatMessage(messages.vbdSizePlaceHolder)} required />
|
||||
</div>
|
||||
{' '}
|
||||
<div className='form-group'>
|
||||
{vm.virtualizationMode === 'pv' && <span>{_('vdbBootable')} <Toggle ref='bootable' /> </span>}
|
||||
<span>{_('vdbReadonly')} <Toggle ref='readOnly' /></span>
|
||||
{vm.virtualizationMode === 'pv' && <span>{_('vbdBootable')} <Toggle ref='bootable' /> </span>}
|
||||
<span>{_('vbdReadonly')} <Toggle ref='readOnly' /></span>
|
||||
</div>
|
||||
<span className='pull-right'>
|
||||
<ActionButton form='newDiskForm' icon='add' btnStyle='primary' handler={this._createDisk}>{_('vdbCreate')}</ActionButton>
|
||||
<ActionButton form='newDiskForm' icon='add' btnStyle='primary' handler={this._createDisk}>{_('vbdCreate')}</ActionButton>
|
||||
</span>
|
||||
</fieldset>
|
||||
</form>
|
||||
@@ -164,7 +164,7 @@ class AttachDisk extends Component {
|
||||
})
|
||||
const mode = readOnly.value || !_isFreeForWriting(vdi) ? 'RO' : 'RW'
|
||||
return attachDiskToVm(vdi, vm, {
|
||||
bootable: bootable.value,
|
||||
bootable: bootable && bootable.value,
|
||||
mode
|
||||
})
|
||||
.then(onClose)
|
||||
@@ -184,11 +184,11 @@ class AttachDisk extends Component {
|
||||
</div>
|
||||
{vdi && <fieldset className='form-inline'>
|
||||
<div className='form-group'>
|
||||
{vm.virtualizationMode === 'pv' && <span>{_('vdbBootable')} <Toggle ref='bootable' /> </span>}
|
||||
<span>{_('vdbReadonly')} <Toggle ref='readOnly' /></span>
|
||||
{vm.virtualizationMode === 'pv' && <span>{_('vbdBootable')} <Toggle ref='bootable' /> </span>}
|
||||
<span>{_('vbdReadonly')} <Toggle ref='readOnly' /></span>
|
||||
</div>
|
||||
<span className='pull-right'>
|
||||
<ActionButton icon='add' form='attachDiskForm' btnStyle='primary' handler={this._addVdi}>{_('vdbCreate')}</ActionButton>
|
||||
<ActionButton icon='add' form='attachDiskForm' btnStyle='primary' handler={this._addVdi}>{_('vbdCreate')}</ActionButton>
|
||||
</span>
|
||||
</fieldset>
|
||||
}
|
||||
@@ -231,8 +231,7 @@ const orderItemTarget = {
|
||||
isDragging: propTypes.bool.isRequired,
|
||||
id: propTypes.any.isRequired,
|
||||
item: propTypes.object.isRequired,
|
||||
move: propTypes.func.isRequired,
|
||||
showBootableFlag: propTypes.bool
|
||||
move: propTypes.func.isRequired
|
||||
})
|
||||
class OrderItem extends Component {
|
||||
_toggle = checked => {
|
||||
@@ -242,7 +241,7 @@ class OrderItem extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { item, connectDragSource, connectDropTarget, showBootableFlag } = this.props
|
||||
const { item, connectDragSource, connectDropTarget } = this.props
|
||||
return connectDragSource(connectDropTarget(
|
||||
<li className='list-group-item'>
|
||||
<Icon icon='grab' />
|
||||
@@ -250,9 +249,9 @@ class OrderItem extends Component {
|
||||
<Icon icon='grab' />
|
||||
{' '}
|
||||
{item.text}
|
||||
{showBootableFlag && <span className='pull-right'>
|
||||
<span className='pull-right'>
|
||||
<Toggle value={item.active} onChange={this._toggle} />
|
||||
</span>}
|
||||
</span>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
@@ -296,7 +295,6 @@ class BootOrder extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { vm } = this.props
|
||||
const { order } = this.state
|
||||
|
||||
return <form>
|
||||
@@ -308,7 +306,6 @@ class BootOrder extends Component {
|
||||
// FIXME missing translation
|
||||
item={item}
|
||||
move={this._moveOrderItem}
|
||||
showBootableFlag={vm.virtualizationMode === 'pv'}
|
||||
/>)}
|
||||
</ul>
|
||||
<fieldset className='form-inline'>
|
||||
@@ -424,12 +421,12 @@ export default class TabDisks extends Component {
|
||||
icon='disk'
|
||||
labelId='vdiAttachDeviceButton'
|
||||
/>
|
||||
<TabButton
|
||||
{vm.virtualizationMode !== 'pv' && <TabButton
|
||||
btnStyle={bootOrder ? 'info' : 'primary'}
|
||||
handler={this._toggleBootOrder} // TODO: boot order
|
||||
handler={this._toggleBootOrder}
|
||||
icon='sort'
|
||||
labelId='vdiBootOrder'
|
||||
/>
|
||||
/>}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
@@ -449,8 +446,8 @@ export default class TabDisks extends Component {
|
||||
<th>{_('vdiNameDescription')}</th>
|
||||
<th>{_('vdiSize')}</th>
|
||||
<th>{_('vdiSr')}</th>
|
||||
{vm.virtualizationMode === 'pv' && <th>{_('vdbBootableStatus')}</th>}
|
||||
<th>{_('vdbStatus')}</th>
|
||||
{vm.virtualizationMode === 'pv' && <th>{_('vbdBootableStatus')}</th>}
|
||||
<th>{_('vbdStatus')}</th>
|
||||
<th className='text-xs-right'>{_('vbdAction')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -493,13 +490,14 @@ export default class TabDisks extends Component {
|
||||
<td>
|
||||
<StateButton
|
||||
disabledLabel={_('vbdStatusDisconnected')}
|
||||
disabledHandler={isVmRunning(vm) && connectVbd}
|
||||
disabledHandler={connectVbd}
|
||||
disabledTooltip={_('vbdConnect')}
|
||||
|
||||
enabledLabel={_('vbdStatusConnected')}
|
||||
enabledHandler={disconnectVbd}
|
||||
enabledTooltip={_('vbdDisconnect')}
|
||||
|
||||
disabled={!(vbd.attached || isVmRunning(vm))}
|
||||
handlerParam={vbd}
|
||||
state={vbd.attached}
|
||||
/>
|
||||
@@ -507,7 +505,6 @@ export default class TabDisks extends Component {
|
||||
<td className='text-xs-right'>
|
||||
<Tooltip content={_('vdiMigrate')}>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
icon='vdi-migrate'
|
||||
handler={this._migrateVdi}
|
||||
handlerParam={vdi}
|
||||
@@ -517,7 +514,6 @@ export default class TabDisks extends Component {
|
||||
<span>
|
||||
<Tooltip content={_('vdiForget')}>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
icon='vdi-forget'
|
||||
handler={deleteVbd}
|
||||
handlerParam={vbd}
|
||||
@@ -525,7 +521,6 @@ export default class TabDisks extends Component {
|
||||
</Tooltip>
|
||||
<Tooltip content={_('vdiRemove')}>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
icon='vdi-remove'
|
||||
handler={deleteVdi}
|
||||
handlerParam={vdi}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import _ from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import React, { Component } from 'react'
|
||||
import TabButton from 'tab-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { connectStore } from 'utils'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { Text } from 'editable'
|
||||
@@ -43,6 +43,7 @@ export default class TabSnapshot extends Component {
|
||||
handlerParam={vm}
|
||||
icon='add'
|
||||
labelId='snapshotCreateButton'
|
||||
pending={includes(vm.current_operations, 'snapshot')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -2,6 +2,7 @@ import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import ansiUp from 'ansi_up'
|
||||
import assign from 'lodash/assign'
|
||||
import Button from 'button'
|
||||
import Component from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
@@ -141,9 +142,7 @@ export default class XoaUpdates extends Component {
|
||||
return xoaUpdater.requestTrial()
|
||||
.then(() => xoaUpdater.update())
|
||||
.catch(err => error('Request Trial', err.message || String(err)))
|
||||
} catch (_) {
|
||||
return
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
@@ -275,7 +274,7 @@ export default class XoaUpdates extends Component {
|
||||
<fieldset>
|
||||
<ActionButton icon='save' btnStyle='primary' handler={this._configure}>{_('saveResourceSet')}</ActionButton>
|
||||
{' '}
|
||||
<button type='button' className='btn btn-default' onClick={this._handleConfigReset} disabled={!configEdited}>{_('resetResourceSet')}</button>
|
||||
<Button onClick={this._handleConfigReset} disabled={!configEdited}>{_('resetResourceSet')}</Button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</CardBlock>
|
||||
|
||||
@@ -19,11 +19,12 @@ import {
|
||||
isEmpty,
|
||||
keys,
|
||||
map,
|
||||
pickBy
|
||||
mapValues,
|
||||
pickBy,
|
||||
some
|
||||
} from 'lodash'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
createGroupBy,
|
||||
createSelector,
|
||||
createSort
|
||||
} from 'selectors'
|
||||
@@ -31,7 +32,9 @@ import {
|
||||
addSubscriptions,
|
||||
compareVersions,
|
||||
connectStore,
|
||||
formatSize
|
||||
formatSize,
|
||||
isXosanPack,
|
||||
mapPlus
|
||||
} from 'utils'
|
||||
import {
|
||||
computeXosanPossibleOptions,
|
||||
@@ -39,6 +42,7 @@ import {
|
||||
downloadAndInstallXosanPack,
|
||||
getVolumeInfo,
|
||||
registerXosan,
|
||||
restartHostsAgents,
|
||||
subscribeIsInstallingXosan,
|
||||
subscribePlugins,
|
||||
subscribeResourceCatalog
|
||||
@@ -85,7 +89,7 @@ export class XosanVolumesTable extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { xosansrs } = this.props
|
||||
const { xosansrs, hosts } = this.props
|
||||
return <div>
|
||||
<h3>{_('xosanSrTitle')}</h3>
|
||||
<table className='table table-striped'>
|
||||
@@ -101,13 +105,14 @@ export class XosanVolumesTable extends Component {
|
||||
<tbody>
|
||||
{map(xosansrs, sr => {
|
||||
const configsMap = {}
|
||||
sr.PBDs.forEach(pbd => { configsMap[pbd.device_config['server']] = true })
|
||||
forEach(sr.pbds, pbd => { configsMap[pbd.device_config['server']] = true })
|
||||
|
||||
return <tr key={sr.id}>
|
||||
<td>
|
||||
<Link to={`/srs/${sr.id}/xosan`}>{sr.name_label}</Link>
|
||||
</td>
|
||||
<td>
|
||||
{ sr.PBDs.map(pbd => pbd.realHost.name_label).join(', ') }
|
||||
{ map(sr.pbds, ({ host }) => find(hosts, [ 'id', host ]).name_label).join(', ') }
|
||||
</td>
|
||||
<td>
|
||||
{ this.state.volumeConfig && this.state.volumeConfig[sr.id] && this.state.volumeConfig[sr.id]['Volume ID'] }
|
||||
@@ -157,12 +162,6 @@ class PoolAvailableSrs extends Component {
|
||||
selectedSrs: {}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.componentWillUnmount = subscribeIsInstallingXosan(this.props.pool, isInstallingXosan => {
|
||||
this.setState({ isInstallingXosan })
|
||||
})
|
||||
}
|
||||
|
||||
_selectSr = (event, srId) => {
|
||||
const selectedSrs = { ...this.state.selectedSrs }
|
||||
selectedSrs[srId] = event.target.checked
|
||||
@@ -231,12 +230,11 @@ class PoolAvailableSrs extends Component {
|
||||
|
||||
render () {
|
||||
const {
|
||||
hosts,
|
||||
lvmsrs,
|
||||
noPack,
|
||||
pool
|
||||
} = this.props
|
||||
const {
|
||||
isInstallingXosan,
|
||||
pif,
|
||||
selectedSrs,
|
||||
suggestion,
|
||||
@@ -245,18 +243,6 @@ class PoolAvailableSrs extends Component {
|
||||
vlan
|
||||
} = this.state
|
||||
|
||||
if (isInstallingXosan) {
|
||||
return <em><Icon icon='loading' /> {_('xosanInstalling')}</em>
|
||||
}
|
||||
|
||||
if (noPack) {
|
||||
return <div className='mb-3'>
|
||||
<Icon icon='error' /> {_('xosanNeedPack')}
|
||||
<br />
|
||||
<ActionButton btnStyle='success' icon='export' handler={downloadAndInstallXosanPack} handlerParam={pool}>{_('xosanInstallIt')}</ActionButton>
|
||||
</div>
|
||||
}
|
||||
|
||||
const disableSrCheckbox = this._getDisableSrCheckbox()
|
||||
|
||||
return <div className='mb-3'>
|
||||
@@ -273,7 +259,8 @@ class PoolAvailableSrs extends Component {
|
||||
</thead>
|
||||
<tbody>
|
||||
{map(lvmsrs, sr => {
|
||||
const host = sr.PBDs[0].realHost
|
||||
const host = find(hosts, [ 'id', sr.$container ])
|
||||
|
||||
return <tr key={sr.id}>
|
||||
<td>
|
||||
<input
|
||||
@@ -393,48 +380,133 @@ class PoolAvailableSrs extends Component {
|
||||
// ==================================================================
|
||||
|
||||
@connectStore(() => {
|
||||
const pools = createGetObjectsOfType('pool')
|
||||
const getIsInPool = createSelector(
|
||||
(_, { pool }) => pool != null && pool.id,
|
||||
poolId => obj => obj.$pool === poolId
|
||||
)
|
||||
|
||||
const hosts = createGetObjectsOfType('host').groupBy('$pool')
|
||||
const getPbdsBySr = createGetObjectsOfType('PBD').filter(getIsInPool).groupBy('SR')
|
||||
const getHosts = createGetObjectsOfType('host').filter(getIsInPool)
|
||||
|
||||
const lvmSrsByPool = createGroupBy(createSort(createSelector(
|
||||
createGetObjectsOfType('SR').filter([sr => !sr.shared && sr.SR_type === 'lvm']),
|
||||
createGetObjectsOfType('PBD').groupBy('SR'),
|
||||
createGetObjectsOfType('host'),
|
||||
(srs, pbds, hosts) => map(srs, sr => {
|
||||
const list = pbds[sr.id]
|
||||
sr.PBDs = list || []
|
||||
sr.PBDs.forEach(pbd => {
|
||||
pbd.realHost = hosts[pbd.host]
|
||||
})
|
||||
sr.PBDs.sort()
|
||||
return sr
|
||||
}).filter(sr => Boolean(sr.PBDs.length))
|
||||
), 'name_label'), '$pool')
|
||||
|
||||
const xosanSrsByPool = createGroupBy(createSort(createSelector(
|
||||
createGetObjectsOfType('SR').filter([sr => sr.shared && sr.SR_type === 'xosan']),
|
||||
createGetObjectsOfType('PBD').groupBy('SR'),
|
||||
createGetObjectsOfType('host'),
|
||||
(srs, pbds, hosts) => map(srs, sr => {
|
||||
const list = pbds[sr.id]
|
||||
sr.PBDs = list || []
|
||||
sr.PBDs.forEach(pbd => {
|
||||
pbd.realHost = hosts[pbd.host]
|
||||
})
|
||||
sr.PBDs.sort((pbd1, pbd2) => pbd1.realHost.name_label.localeCompare(pbd2.realHost.name_label))
|
||||
return sr
|
||||
// LVM SRs that are connected
|
||||
const getLvmSrs = createSort(createSelector(
|
||||
createGetObjectsOfType('SR').filter(
|
||||
createSelector(
|
||||
getHosts,
|
||||
getIsInPool,
|
||||
(hosts, isInPool) =>
|
||||
sr =>
|
||||
isInPool(sr) &&
|
||||
!sr.shared &&
|
||||
sr.SR_type === 'lvm' &&
|
||||
find(hosts, { id: sr.$container }).power_state === 'Running'
|
||||
)
|
||||
),
|
||||
getPbdsBySr,
|
||||
(srs, pbdsBySr) => mapPlus(srs, (sr, push) => {
|
||||
let pbds
|
||||
if ((pbds = pbdsBySr[sr.id]).length) {
|
||||
push({ ...sr, pbds })
|
||||
}
|
||||
})
|
||||
), 'name_label'), '$pool')
|
||||
), 'name_label')
|
||||
|
||||
const getXosanSrs = createSort(createSelector(
|
||||
createGetObjectsOfType('SR').filter(createSelector(
|
||||
(_, { pool }) => pool != null && pool.id,
|
||||
poolId =>
|
||||
sr => sr.$pool === poolId && sr.shared && sr.SR_type === 'xosan'
|
||||
)),
|
||||
getPbdsBySr,
|
||||
(srs, pbdsBySr) =>
|
||||
map(srs, sr => ({ ...sr, pbds: pbdsBySr[sr.id] }))
|
||||
), 'name_label')
|
||||
|
||||
const getTemplates = createSelector(
|
||||
(_, { catalog }) => catalog,
|
||||
catalog => filter(catalog.xosan, { type: 'xva' })
|
||||
)
|
||||
|
||||
// Hosts whose toolstack hasn't been restarted since XOSAN-pack installation
|
||||
const getHostsNeedRestart = createSelector(
|
||||
(_, { pool }) => pool && pool.xosanPackInstallationTime,
|
||||
getHosts,
|
||||
(xosanPackInstallationTime, hosts) => filter(hosts, host =>
|
||||
host.power_state === 'Running' &&
|
||||
xosanPackInstallationTime != null &&
|
||||
xosanPackInstallationTime > host.agentStartTime
|
||||
)
|
||||
)
|
||||
|
||||
const getIsMasterOffline = createSelector(
|
||||
getHosts,
|
||||
(_, { pool }) => pool.master,
|
||||
(hosts, id) => find(hosts, { id }).power_state !== 'Running'
|
||||
)
|
||||
|
||||
return {
|
||||
hosts,
|
||||
pools,
|
||||
xosanSrsByPool,
|
||||
lvmSrsByPool,
|
||||
networks: createGetObjectsOfType('network').groupBy('$pool')
|
||||
isMasterOffline: getIsMasterOffline,
|
||||
hosts: getHosts,
|
||||
lvmSrs: getLvmSrs,
|
||||
hostsNeedRestart: getHostsNeedRestart,
|
||||
templates: getTemplates,
|
||||
xosanSrs: getXosanSrs
|
||||
}
|
||||
})
|
||||
class Pool extends Component {
|
||||
componentDidMount () {
|
||||
this.componentWillUnmount = subscribeIsInstallingXosan(this.props.pool, isInstallingXosan => {
|
||||
this.setState({ isInstallingXosan })
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { xosanSrs, lvmSrs, hosts, noPack, templates, pool, hostsNeedRestart, isMasterOffline } = this.props
|
||||
const { isInstallingXosan } = this.state
|
||||
|
||||
if (isInstallingXosan) {
|
||||
return <em><Icon icon='loading' /> {_('xosanInstalling')}</em>
|
||||
}
|
||||
|
||||
if (noPack) {
|
||||
return <div className='mb-3'>
|
||||
<Icon icon='error' /> {_('xosanNeedPack')}
|
||||
<br />
|
||||
<ActionButton btnStyle='success' icon='export' handler={downloadAndInstallXosanPack} handlerParam={pool}>{_('xosanInstallIt')}</ActionButton>
|
||||
</div>
|
||||
}
|
||||
|
||||
if (isMasterOffline) {
|
||||
return <div className='mb-3'>
|
||||
<Icon icon='error' /> {_('xosanMasterOffline')}
|
||||
</div>
|
||||
}
|
||||
|
||||
if (!isEmpty(hostsNeedRestart)) {
|
||||
return <div className='mb-3'>
|
||||
<Icon icon='error' /> {_('xosanNeedRestart')}
|
||||
<br />
|
||||
<ActionButton btnStyle='success' icon='host-restart-agent' handler={restartHostsAgents} handlerParam={hostsNeedRestart}>{_('xosanRestartAgents')}</ActionButton>
|
||||
</div>
|
||||
}
|
||||
|
||||
return xosanSrs && xosanSrs.length
|
||||
? <XosanVolumesTable hosts={hosts} xosansrs={xosanSrs} lvmsrs={lvmSrs} />
|
||||
: <PoolAvailableSrs hosts={hosts} pool={pool} lvmsrs={lvmSrs} templates={templates} />
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
|
||||
@connectStore(() => ({
|
||||
pools: createGetObjectsOfType('pool'),
|
||||
noPacksByPool: createSelector(
|
||||
createGetObjectsOfType('host').groupBy('$pool'),
|
||||
hostsByPool => mapValues(hostsByPool, (poolHosts, poolId) =>
|
||||
!every(poolHosts, host => some(host.supplementalPacks, isXosanPack))
|
||||
)
|
||||
)
|
||||
}))
|
||||
@addSubscriptions({
|
||||
catalog: subscribeResourceCatalog,
|
||||
plugins: subscribePlugins
|
||||
@@ -473,7 +545,7 @@ export default class Xosan extends Component {
|
||||
)
|
||||
|
||||
render () {
|
||||
const { pools, xosanSrsByPool, lvmSrsByPool, catalog } = this.props
|
||||
const { pools, noPacksByPool, catalog } = this.props
|
||||
const error = this._getError()
|
||||
|
||||
return <Page header={HEADER} title='xosan' formatTitle>
|
||||
@@ -482,17 +554,11 @@ export default class Xosan extends Component {
|
||||
{error
|
||||
? <em>{error}</em>
|
||||
: map(pools, pool => {
|
||||
const poolXosanSrs = xosanSrsByPool[pool.id]
|
||||
const poolLvmSrs = lvmSrsByPool[pool.id]
|
||||
// TODO: check hosts supplementalPacks directly instead of checking each SR
|
||||
const noPack = !every(poolLvmSrs, sr => sr.PBDs[0].realHost.supplementalPacks['vates:XOSAN'])
|
||||
const noPack = noPacksByPool && noPacksByPool[pool.id]
|
||||
|
||||
return <Collapse key={pool.id} className='mb-1' buttonText={<span>{noPack && <Icon icon='error' />} {pool.name_label}</span>}>
|
||||
<div className='m-1'>
|
||||
{poolXosanSrs && poolXosanSrs.length
|
||||
? <XosanVolumesTable xosansrs={poolXosanSrs} lvmsrs={poolLvmSrs} />
|
||||
: <PoolAvailableSrs pool={pool} lvmsrs={poolLvmSrs} noPack={noPack} templates={filter(catalog.xosan, { type: 'xva' })} />
|
||||
}
|
||||
<Pool pool={pool} noPack={noPack} catalog={catalog} />
|
||||
</div>
|
||||
</Collapse>
|
||||
})
|
||||
|
||||
@@ -46,5 +46,5 @@ function main (args) {
|
||||
new Promise(function (resolve) {
|
||||
resolve(main(process.argv.slice(2)))
|
||||
}).catch(function (error) {
|
||||
console.log(error && (error.stack || error.message) || error)
|
||||
console.log((error != null && (error.stack || error.message)) || error)
|
||||
})
|
||||
|
||||
278
yarn.lock
278
yarn.lock
@@ -29,13 +29,13 @@ acorn-globals@^3.1.0:
|
||||
dependencies:
|
||||
acorn "^4.0.4"
|
||||
|
||||
acorn-jsx@^3.0.0, acorn-jsx@^3.0.1:
|
||||
acorn-jsx@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
|
||||
dependencies:
|
||||
acorn "^3.0.4"
|
||||
|
||||
acorn@4.X, acorn@^4.0.1, acorn@^4.0.4, acorn@~4.0.2:
|
||||
acorn@4.X, acorn@^4.0.4, acorn@~4.0.2:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a"
|
||||
|
||||
@@ -51,6 +51,10 @@ acorn@^3.0.4, acorn@^3.1.0, acorn@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
|
||||
|
||||
acorn@^5.0.1:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d"
|
||||
|
||||
ajv-keywords@^1.0.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.0.tgz#c11e6859eafff83e0dafc416929472eca946aa2c"
|
||||
@@ -188,6 +192,13 @@ array-unique@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
|
||||
|
||||
array.prototype.find@^2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90"
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
es-abstract "^1.7.0"
|
||||
|
||||
arrify@^1.0.0, arrify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||
@@ -1407,7 +1418,7 @@ buffer@^5.0.2:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
builtin-modules@^1.0.0:
|
||||
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
|
||||
@@ -1716,7 +1727,7 @@ concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
||||
concat-stream@^1.4.6, concat-stream@^1.4.8, concat-stream@^1.5.0, concat-stream@~1.5.0, concat-stream@~1.5.1:
|
||||
concat-stream@^1.4.8, concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@~1.5.0, concat-stream@~1.5.1:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
|
||||
dependencies:
|
||||
@@ -1745,6 +1756,10 @@ constants-browserify@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
|
||||
|
||||
contains-path@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
||||
|
||||
content-type-parser@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94"
|
||||
@@ -2148,18 +2163,18 @@ debug-log@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f"
|
||||
|
||||
debug@2.2.0, debug@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
|
||||
dependencies:
|
||||
ms "0.7.1"
|
||||
|
||||
debug@2.X, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b"
|
||||
dependencies:
|
||||
ms "0.7.2"
|
||||
|
||||
debug@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
|
||||
dependencies:
|
||||
ms "0.7.1"
|
||||
|
||||
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
@@ -2187,7 +2202,7 @@ defined@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
||||
|
||||
deglob@^2.0.0:
|
||||
deglob@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/deglob/-/deglob-2.1.0.tgz#4d44abe16ef32c779b4972bd141a80325029a14a"
|
||||
dependencies:
|
||||
@@ -2315,13 +2330,20 @@ dnd-core@^2.0.1:
|
||||
lodash "^4.2.0"
|
||||
redux "^3.2.0"
|
||||
|
||||
doctrine@^1.2.2:
|
||||
doctrine@1.5.0, doctrine@^1.2.2:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
isarray "^1.0.0"
|
||||
|
||||
doctrine@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
isarray "^1.0.0"
|
||||
|
||||
doctypes@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
|
||||
@@ -2477,6 +2499,15 @@ es-abstract@^1.6.1:
|
||||
is-callable "^1.1.3"
|
||||
is-regex "^1.0.3"
|
||||
|
||||
es-abstract@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
|
||||
dependencies:
|
||||
es-to-primitive "^1.1.1"
|
||||
function-bind "^1.1.0"
|
||||
is-callable "^1.1.3"
|
||||
is-regex "^1.0.3"
|
||||
|
||||
es-to-primitive@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
|
||||
@@ -2561,45 +2592,89 @@ escope@^3.6.0:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-config-standard-jsx@3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-3.2.0.tgz#c240e26ed919a11a42aa4de8059472b38268d620"
|
||||
eslint-config-standard-jsx@3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-3.3.0.tgz#cab0801a15a360bf63facb97ab22fbdd88d8a5e0"
|
||||
|
||||
eslint-config-standard@6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-6.2.1.tgz#d3a68aafc7191639e7ee441e7348739026354292"
|
||||
eslint-config-standard@10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-10.0.0.tgz#29f3625eafc490b89592506d25e3f4477770b973"
|
||||
|
||||
eslint-plugin-promise@~3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.4.0.tgz#6ba9048c2df57be77d036e0c68918bc9b4fc4195"
|
||||
|
||||
eslint-plugin-react@~6.7.1:
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.7.1.tgz#1af96aea545856825157d97c1b50d5a8fb64a5a7"
|
||||
eslint-import-resolver-node@^0.2.0:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c"
|
||||
dependencies:
|
||||
debug "^2.2.0"
|
||||
object-assign "^4.0.1"
|
||||
resolve "^1.1.6"
|
||||
|
||||
eslint-module-utils@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.0.0.tgz#a6f8c21d901358759cdc35dbac1982ae1ee58bce"
|
||||
dependencies:
|
||||
debug "2.2.0"
|
||||
pkg-dir "^1.0.0"
|
||||
|
||||
eslint-plugin-import@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e"
|
||||
dependencies:
|
||||
builtin-modules "^1.1.1"
|
||||
contains-path "^0.1.0"
|
||||
debug "^2.2.0"
|
||||
doctrine "1.5.0"
|
||||
eslint-import-resolver-node "^0.2.0"
|
||||
eslint-module-utils "^2.0.0"
|
||||
has "^1.0.1"
|
||||
lodash.cond "^4.3.0"
|
||||
minimatch "^3.0.3"
|
||||
pkg-up "^1.0.0"
|
||||
|
||||
eslint-plugin-node@~4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-4.2.2.tgz#82959ca9aed79fcbd28bb1b188d05cac04fb3363"
|
||||
dependencies:
|
||||
ignore "^3.0.11"
|
||||
minimatch "^3.0.2"
|
||||
object-assign "^4.0.1"
|
||||
resolve "^1.1.7"
|
||||
semver "5.3.0"
|
||||
|
||||
eslint-plugin-promise@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz#78fbb6ffe047201627569e85a6c5373af2a68fca"
|
||||
|
||||
eslint-plugin-react@~6.10.0:
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78"
|
||||
dependencies:
|
||||
array.prototype.find "^2.0.1"
|
||||
doctrine "^1.2.2"
|
||||
jsx-ast-utils "^1.3.3"
|
||||
has "^1.0.1"
|
||||
jsx-ast-utils "^1.3.4"
|
||||
object.assign "^4.0.4"
|
||||
|
||||
eslint-plugin-standard@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-2.0.1.tgz#3589699ff9c917f2c25f76a916687f641c369ff3"
|
||||
eslint-plugin-standard@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-2.2.0.tgz#61273ddbab958ccc88d94d6bb5b2db63ffbd368e"
|
||||
|
||||
eslint@~3.10.2:
|
||||
version "3.10.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.10.2.tgz#c9a10e8bf6e9d65651204778c503341f1eac3ce7"
|
||||
eslint@~3.19.0:
|
||||
version "3.19.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc"
|
||||
dependencies:
|
||||
babel-code-frame "^6.16.0"
|
||||
chalk "^1.1.3"
|
||||
concat-stream "^1.4.6"
|
||||
concat-stream "^1.5.2"
|
||||
debug "^2.1.1"
|
||||
doctrine "^1.2.2"
|
||||
doctrine "^2.0.0"
|
||||
escope "^3.6.0"
|
||||
espree "^3.3.1"
|
||||
espree "^3.4.0"
|
||||
esquery "^1.0.0"
|
||||
estraverse "^4.2.0"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^2.0.0"
|
||||
glob "^7.0.3"
|
||||
globals "^9.2.0"
|
||||
globals "^9.14.0"
|
||||
ignore "^3.2.0"
|
||||
imurmurhash "^0.1.4"
|
||||
inquirer "^0.12.0"
|
||||
@@ -2618,22 +2693,28 @@ eslint@~3.10.2:
|
||||
require-uncached "^1.0.2"
|
||||
shelljs "^0.7.5"
|
||||
strip-bom "^3.0.0"
|
||||
strip-json-comments "~1.0.1"
|
||||
strip-json-comments "~2.0.1"
|
||||
table "^3.7.8"
|
||||
text-table "~0.2.0"
|
||||
user-home "^2.0.0"
|
||||
|
||||
espree@^3.3.1:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-3.3.2.tgz#dbf3fadeb4ecb4d4778303e50103b3d36c88b89c"
|
||||
espree@^3.4.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.1.tgz#28a83ab4aaed71ed8fe0f5efe61b76a05c13c4d2"
|
||||
dependencies:
|
||||
acorn "^4.0.1"
|
||||
acorn "^5.0.1"
|
||||
acorn-jsx "^3.0.0"
|
||||
|
||||
esprima@^2.6.0, esprima@^2.7.1:
|
||||
version "2.7.3"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
|
||||
|
||||
esquery@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
|
||||
dependencies:
|
||||
estraverse "^4.0.0"
|
||||
|
||||
esrecurse@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220"
|
||||
@@ -2645,7 +2726,7 @@ estraverse@^1.9.1:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
|
||||
|
||||
estraverse@^4.1.1, estraverse@^4.2.0:
|
||||
estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||
|
||||
@@ -2684,6 +2765,10 @@ event-to-promise@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/event-to-promise/-/event-to-promise-0.7.0.tgz#cb07dfcd418da2221d90f77eab713bc235e2090f"
|
||||
|
||||
event-to-promise@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/event-to-promise/-/event-to-promise-0.8.0.tgz#4b84f11772b6f25f7752fc74d971531ac6f5b626"
|
||||
|
||||
events@^1.0.2, events@~1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||
@@ -2846,7 +2931,7 @@ find-up@^1.0.0:
|
||||
path-exists "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
find-up@^2.1.0:
|
||||
find-up@^2.0.0, find-up@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
|
||||
dependencies:
|
||||
@@ -3153,7 +3238,7 @@ global-prefix@^0.1.4:
|
||||
is-windows "^0.2.0"
|
||||
which "^1.2.12"
|
||||
|
||||
globals@^9.0.0, globals@^9.2.0:
|
||||
globals@^9.0.0, globals@^9.14.0:
|
||||
version "9.14.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034"
|
||||
|
||||
@@ -3527,9 +3612,9 @@ https-browserify@~0.0.0:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
|
||||
|
||||
human-format@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/human-format/-/human-format-0.7.0.tgz#b9bb10f65e2d7d99fb3b3950a418b8bef590a4b6"
|
||||
human-format@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/human-format/-/human-format-0.8.0.tgz#6146370a838fd52718d1e27f16f1916b43a06e92"
|
||||
|
||||
husky@^0.13.1:
|
||||
version "0.13.1"
|
||||
@@ -3560,7 +3645,7 @@ ieee754@^1.1.4:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
|
||||
|
||||
ignore@^3.0.9, ignore@^3.2.0:
|
||||
ignore@^3.0.11, ignore@^3.0.9, ignore@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435"
|
||||
|
||||
@@ -4381,11 +4466,10 @@ jstransformer@1.0.0:
|
||||
is-promise "^2.0.0"
|
||||
promise "^7.0.1"
|
||||
|
||||
jsx-ast-utils@^1.3.3:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.3.5.tgz#9ba6297198d9f754594d62e59496ffb923778dd4"
|
||||
jsx-ast-utils@^1.3.4:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.0.tgz#5afe38868f56bc8cc7aeaef0100ba8c75bd12591"
|
||||
dependencies:
|
||||
acorn-jsx "^3.0.1"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
keycode@^2.1.0:
|
||||
@@ -4490,6 +4574,15 @@ load-json-file@^1.0.0:
|
||||
pinkie-promise "^2.0.0"
|
||||
strip-bom "^2.0.0"
|
||||
|
||||
load-json-file@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
parse-json "^2.2.0"
|
||||
pify "^2.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
loader-utils@^0.2.16:
|
||||
version "0.2.16"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d"
|
||||
@@ -4574,6 +4667,10 @@ lodash.compact@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.compact/-/lodash.compact-3.0.1.tgz#540ce3837745975807471e16b4a2ba21e7256ca5"
|
||||
|
||||
lodash.cond@^4.3.0:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
|
||||
|
||||
lodash.curry@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
|
||||
@@ -5537,7 +5634,14 @@ pinkie@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||
|
||||
pkg-config@^1.0.1, pkg-config@^1.1.0:
|
||||
pkg-conf@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.0.0.tgz#071c87650403bccfb9c627f58751bfe47c067279"
|
||||
dependencies:
|
||||
find-up "^2.0.0"
|
||||
load-json-file "^2.0.0"
|
||||
|
||||
pkg-config@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/pkg-config/-/pkg-config-1.1.1.tgz#557ef22d73da3c8837107766c52eadabde298fe4"
|
||||
dependencies:
|
||||
@@ -5545,6 +5649,18 @@ pkg-config@^1.0.1, pkg-config@^1.1.0:
|
||||
find-root "^1.0.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
pkg-dir@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
|
||||
dependencies:
|
||||
find-up "^1.0.0"
|
||||
|
||||
pkg-up@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26"
|
||||
dependencies:
|
||||
find-up "^1.0.0"
|
||||
|
||||
platform@^1.3.0, platform@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.3.tgz#646c77011899870b6a0903e75e997e8e51da7461"
|
||||
@@ -6339,7 +6455,7 @@ require-uncached@^1.0.2:
|
||||
caller-path "^0.1.0"
|
||||
resolve-from "^1.0.0"
|
||||
|
||||
reselect@^2.2.1:
|
||||
reselect@^2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-2.5.4.tgz#b7d23fdf00b83fa7ad0279546f8dbbbd765c7047"
|
||||
|
||||
@@ -6457,7 +6573,7 @@ semver-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.3.0, semver@~5.3.0:
|
||||
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@5.3.0, semver@^5.3.0, semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
|
||||
@@ -6634,28 +6750,28 @@ stack-trace@0.0.9:
|
||||
version "0.0.9"
|
||||
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695"
|
||||
|
||||
standard-engine@~5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/standard-engine/-/standard-engine-5.2.0.tgz#400660ae5acce8afd4db60ff2214a9190ad790a3"
|
||||
standard-engine@~7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/standard-engine/-/standard-engine-7.0.0.tgz#ebb77b9c8fc2c8165ffa353bd91ba0dff41af690"
|
||||
dependencies:
|
||||
deglob "^2.0.0"
|
||||
find-root "^1.0.0"
|
||||
deglob "^2.1.0"
|
||||
get-stdin "^5.0.1"
|
||||
home-or-tmp "^2.0.0"
|
||||
minimist "^1.1.0"
|
||||
pkg-config "^1.0.1"
|
||||
pkg-conf "^2.0.0"
|
||||
|
||||
standard@^8.4.0:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/standard/-/standard-8.6.0.tgz#635132be7bfb567c2921005f30f9e350e4752aad"
|
||||
standard@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/standard/-/standard-10.0.0.tgz#913c77df56c294d9c4dff60dce6f307b2c779d0f"
|
||||
dependencies:
|
||||
eslint "~3.10.2"
|
||||
eslint-config-standard "6.2.1"
|
||||
eslint-config-standard-jsx "3.2.0"
|
||||
eslint-plugin-promise "~3.4.0"
|
||||
eslint-plugin-react "~6.7.1"
|
||||
eslint-plugin-standard "~2.0.1"
|
||||
standard-engine "~5.2.0"
|
||||
eslint "~3.19.0"
|
||||
eslint-config-standard "10.0.0"
|
||||
eslint-config-standard-jsx "3.3.0"
|
||||
eslint-plugin-import "~2.2.0"
|
||||
eslint-plugin-node "~4.2.2"
|
||||
eslint-plugin-promise "~3.5.0"
|
||||
eslint-plugin-react "~6.10.0"
|
||||
eslint-plugin-standard "~2.2.0"
|
||||
standard-engine "~7.0.0"
|
||||
|
||||
statuses@1:
|
||||
version "1.3.1"
|
||||
@@ -6787,10 +6903,14 @@ strip-indent@^1.0.1:
|
||||
dependencies:
|
||||
get-stdin "^4.0.1"
|
||||
|
||||
strip-json-comments@~1.0.1, strip-json-comments@~1.0.4:
|
||||
strip-json-comments@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
|
||||
styled-components@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-1.4.4.tgz#c944de423d8ae2363f2ba4ff8fc26d367e7dfa8f"
|
||||
@@ -7089,9 +7209,9 @@ unc-path-regex@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
|
||||
uncontrollable-input@^0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uncontrollable-input/-/uncontrollable-input-0.0.0.tgz#d2c758615599c2127dec1be7d0a36ef318709ba1"
|
||||
uncontrollable-input@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uncontrollable-input/-/uncontrollable-input-0.0.1.tgz#ff3f4730fa2ae81797889bafd1bc426aeb4936b9"
|
||||
|
||||
uncontrollable@^3.1.3:
|
||||
version "3.3.1"
|
||||
@@ -7466,9 +7586,9 @@ xo-acl-resolver@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/xo-acl-resolver/-/xo-acl-resolver-0.2.3.tgz#693f4181727379be0d969f7c22d660f3bddf935a"
|
||||
|
||||
xo-common@0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/xo-common/-/xo-common-0.1.0.tgz#6e02ada37691df6c2ca025be260689cf4c837197"
|
||||
xo-common@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/xo-common/-/xo-common-0.1.1.tgz#bdad9ea7926c1f27d8fdaecc92d672854c911815"
|
||||
dependencies:
|
||||
babel-runtime "^6.18.0"
|
||||
lodash "^4.16.6"
|
||||
|
||||
Reference in New Issue
Block a user