feat: prettier integration (#2490)

This commit is contained in:
Julien Fontanet 2017-11-14 15:25:02 +01:00 committed by GitHub
parent d71323a67d
commit 3fe5efbfab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
205 changed files with 19544 additions and 15317 deletions

15
.eslintrc.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
'extends': [
'standard',
'standard-jsx',
],
'globals': {
'__DEV__': true
},
'parser': 'babel-eslint',
'rules': {
'comma-dangle': ['error', 'always-multiline'],
'no-var': 'error',
'prefer-const': 'error'
}
}

4
.prettierrc.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
semi: false,
singleQuote: true
}

View File

@ -63,6 +63,14 @@
"enzyme": "^3.1.1",
"enzyme-adapter-react-15": "^1.0.5",
"enzyme-to-json": "^3.2.2",
"eslint": "^4.11.0",
"eslint-config-standard": "^10.2.1",
"eslint-config-standard-jsx": "^4.0.2",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.4.0",
"eslint-plugin-standard": "^3.0.1",
"event-to-promise": "^0.8.0",
"font-awesome": "^4.7.0",
"font-mfizz": "^2.4.1",
@ -88,6 +96,7 @@
"jsonrpc-websocket-client": "^0.2.0",
"kindof": "^2.0.0",
"later": "^1.2.0",
"lint-staged": "^5.0.0",
"lodash": "^4.6.1",
"loose-envify": "^1.1.0",
"make-error": "^1.2.1",
@ -96,6 +105,7 @@
"moment": "^2.19.1",
"moment-timezone": "^0.5.14",
"notifyjs": "^3.0.0",
"prettier": "^1.8.2",
"promise-toolbox": "^0.9.5",
"random-password": "^0.1.2",
"react": "^15.4.1",
@ -128,7 +138,6 @@
"redux-thunk": "^2.0.1",
"reselect": "^2.5.4",
"semver": "^5.4.1",
"standard": "^10.0.3",
"styled-components": "^2.2.3",
"superagent": "^3.8.0",
"tar-stream": "^1.5.2",
@ -146,11 +155,11 @@
"benchmarks": "./tools/run-benchmarks.js 'src/**/*.bench.js'",
"build": "npm run build-indexes && NODE_ENV=production gulp build",
"build-indexes": "index-modules --auto src",
"commitmsg": "npm test",
"dev": "npm run build-indexes && NODE_ENV=development gulp build",
"dev-test": "jest --watch",
"lint": "standard",
"posttest": "npm run lint",
"lint-staged-unstash": "git stash pop && true",
"posttest": "eslint",
"precommit": "lint-staged",
"prepublish": "npm run build",
"test": "jest"
},
@ -193,13 +202,14 @@
"enzyme-to-json/serializer"
]
},
"standard": {
"globals": [
"__DEV__"
],
"ignore": [
"dist"
],
"parser": "babel-eslint"
"lint-staged": {
"*.js": [
"git stash push --keep-index",
"prettier --write",
"eslint --fix",
"jest --findRelatedTests",
"git add",
"lint-staged-unstash"
]
}
}

View File

@ -5,7 +5,15 @@ import { noop } from 'lodash'
import ButtonGroup from './button-group'
export const Action = ({ display, handler, handlerParam, icon, label, pending, redirectOnSuccess }) =>
export const Action = ({
display,
handler,
handlerParam,
icon,
label,
pending,
redirectOnSuccess,
}) => (
<ActionButton
handler={handler}
handlerParam={handlerParam}
@ -17,17 +25,18 @@ export const Action = ({ display, handler, handlerParam, icon, label, pending, r
>
{display === 'both' && label}
</ActionButton>
)
Action.propTypes = {
display: propTypes.oneOf([ 'icon', 'both' ]),
display: propTypes.oneOf(['icon', 'both']),
handler: propTypes.func.isRequired,
icon: propTypes.string.isRequired,
label: propTypes.node,
pending: propTypes.bool,
redirectOnSuccess: propTypes.string
redirectOnSuccess: propTypes.string,
}
const ActionBar = ({ children, handlerParam = noop, display = 'both' }) =>
const ActionBar = ({ children, handlerParam = noop, display = 'both' }) => (
<ButtonGroup>
{React.Children.map(children, (child, key) => {
if (!child) {
@ -38,13 +47,14 @@ const ActionBar = ({ children, handlerParam = noop, display = 'both' }) =>
return cloneElement(child, {
display: props.display || display,
handlerParam: props.handlerParam || handlerParam,
key
key,
})
})}
</ButtonGroup>
)
ActionBar.propTypes = {
display: propTypes.oneOf([ 'icon', 'both' ]),
handlerParam: propTypes.any
display: propTypes.oneOf(['icon', 'both']),
handlerParam: propTypes.any,
}
export { ActionBar as default }

View File

@ -39,17 +39,14 @@ import { error as _error } from './notification'
//
// if a function, it will be called with the result of the action to
// compute the path
redirectOnSuccess: propTypes.oneOfType([
propTypes.func,
propTypes.string
]),
redirectOnSuccess: propTypes.oneOfType([propTypes.func, propTypes.string]),
// React element to use tooltip for the component
tooltip: propTypes.node
tooltip: propTypes.node,
})
export default class ActionButton extends Component {
static contextTypes = {
router: propTypes.object
router: propTypes.object,
}
async _execute () {
@ -57,17 +54,12 @@ export default class ActionButton extends Component {
return
}
const {
children,
handler,
handlerParam,
tooltip
} = this.props
const { children, handler, handlerParam, tooltip } = this.props
try {
this.setState({
error: undefined,
working: true
working: true,
})
const result = await handler(handlerParam)
@ -75,23 +67,28 @@ export default class ActionButton extends Component {
const { redirectOnSuccess } = this.props
if (redirectOnSuccess) {
return this.context.router.push(
isFunction(redirectOnSuccess) ? redirectOnSuccess(result) : redirectOnSuccess
isFunction(redirectOnSuccess)
? redirectOnSuccess(result)
: redirectOnSuccess
)
}
this.setState({
working: false
working: false,
})
} catch (error) {
this.setState({
error,
working: false
working: false,
})
// ignore when undefined because it usually means that the action has been canceled
if (error !== undefined) {
logError(error)
_error(children || tooltip || error.name, error.message || String(error))
_error(
children || tooltip || error.name,
error.message || String(error)
)
}
}
}
@ -106,7 +103,9 @@ export default class ActionButton extends Component {
const { form } = this.props
if (form) {
document.getElementById(form).addEventListener('submit', this._eventListener)
document
.getElementById(form)
.addEventListener('submit', this._eventListener)
}
}
@ -114,20 +113,16 @@ export default class ActionButton extends Component {
const { form } = this.props
if (form) {
document.getElementById(form).removeEventListener('submit', this._eventListener)
document
.getElementById(form)
.removeEventListener('submit', this._eventListener)
}
}
render () {
const {
props: {
children,
icon,
pending,
tooltip,
...props
},
state: { error, working }
props: { children, icon, pending, tooltip, ...props },
state: { error, working },
} = this
if (error !== undefined) {
@ -143,14 +138,14 @@ export default class ActionButton extends Component {
}
delete props.redirectOnSuccess
const button = <Button {...props}>
<Icon icon={pending || working ? 'loading' : icon} fixedWidth />
{children && ' '}
{children}
</Button>
const button = (
<Button {...props}>
<Icon icon={pending || working ? 'loading' : icon} fixedWidth />
{children && ' '}
{children}
</Button>
)
return tooltip
? <Tooltip content={tooltip}>{button}</Tooltip>
: button
return tooltip ? <Tooltip content={tooltip}>{button}</Tooltip> : button
}
}

View File

@ -5,10 +5,6 @@ import ActionButton from '../action-button'
import styles from './index.css'
const ActionRowButton = props => (
<ActionButton
{...props}
className={styles.button}
size='small'
/>
<ActionButton {...props} className={styles.button} size='small' />
)
export { ActionRowButton as default }

View File

@ -3,13 +3,14 @@ import React from 'react'
import ActionButton from './action-button'
import propTypes from './prop-types-decorator'
const ActionToggle = ({ className, value, ...props }) =>
const ActionToggle = ({ className, value, ...props }) => (
<ActionButton
{...props}
btnStyle={value ? 'success' : null}
icon={value ? 'toggle-on' : 'toggle-off'}
/>
)
export default propTypes({
value: propTypes.bool
value: propTypes.bool,
})(ActionToggle)

View File

@ -1,11 +1,6 @@
import { PureComponent } from 'react'
import { cowSet } from 'utils'
import {
includes,
isArray,
forEach,
map
} from 'lodash'
import { includes, isArray, forEach, map } from 'lodash'
import getEventValue from './get-event-value'
@ -45,9 +40,7 @@ export default class BaseComponent extends PureComponent {
// See https://preactjs.com/guide/linked-state
linkState (name, targetPath) {
const key = targetPath !== undefined
? `${name}##${targetPath}`
: name
const key = targetPath !== undefined ? `${name}##${targetPath}` : name
let linkedState = this._linkedState
let cb
@ -74,7 +67,7 @@ export default class BaseComponent extends PureComponent {
return (linkedState[key] = event => {
this.setState({
[name]: getValue(event)
[name]: getValue(event),
})
})
}
@ -97,7 +90,7 @@ export default class BaseComponent extends PureComponent {
return (linkedState[name] = () => {
this.setState({
[name]: !this.state[name]
[name]: !this.state[name],
})
})
}

View File

@ -8,7 +8,7 @@ const sendNotification = (title, body) => {
new Notify(title, {
body,
timeout: 5,
icon: 'assets/logo.png'
icon: 'assets/logo.png',
}).show()
}

View File

@ -1,8 +1,9 @@
import React from 'react'
const ButtonGroup = ({ children }) =>
const ButtonGroup = ({ children }) => (
<div className='btn-group' role='group'>
{children}
</div>
)
export { ButtonGroup as default }

View File

@ -43,14 +43,11 @@ propTypes({
'link',
'primary',
'success',
'warning'
'warning',
]),
outline: propTypes.bool,
size: propTypes.oneOf([
'large',
'small'
])
size: propTypes.oneOf(['large', 'small']),
})(Button)
export { Button as default }

View File

@ -3,25 +3,22 @@ import React from 'react'
import propTypes from './prop-types-decorator'
const CARD_STYLE = {
minHeight: '100%'
minHeight: '100%',
}
const CARD_STYLE_WITH_SHADOW = {
...CARD_STYLE,
boxShadow: '0 10px 6px -6px #777' // https://css-tricks.com/almanac/properties/b/box-shadow/
boxShadow: '0 10px 6px -6px #777', // https://css-tricks.com/almanac/properties/b/box-shadow/
}
const CARD_HEADER_STYLE = {
minHeight: '100%',
textAlign: 'center'
textAlign: 'center',
}
export const Card = propTypes({
shadow: propTypes.bool
})(({
shadow,
...props
}) => {
shadow: propTypes.bool,
})(({ shadow, ...props }) => {
props.className = 'card'
props.style = shadow ? CARD_STYLE_WITH_SHADOW : CARD_STYLE
@ -29,23 +26,15 @@ export const Card = propTypes({
})
export const CardHeader = propTypes({
className: propTypes.string
})(({
children,
className
}) => (
className: propTypes.string,
})(({ children, className }) => (
<h4 className={`card-header ${className || ''}`} style={CARD_HEADER_STYLE}>
{children}
</h4>
))
export const CardBlock = propTypes({
className: propTypes.string
})(({
children,
className
}) => (
<div className={`card-block ${className || ''}`}>
{children}
</div>
className: propTypes.string,
})(({ children, className }) => (
<div className={`card-block ${className || ''}`}>{children}</div>
))

View File

@ -2,11 +2,10 @@ import React from 'react'
import styles from './index.css'
const CenterPanel = ({ children }) =>
const CenterPanel = ({ children }) => (
<div className={styles.container}>
<div className={styles.content}>
{children}
</div>
<div className={styles.content}>{children}</div>
</div>
)
export { CenterPanel as default }

View File

@ -9,16 +9,16 @@ import propTypes from './prop-types-decorator'
children: propTypes.any.isRequired,
className: propTypes.string,
buttonText: propTypes.any.isRequired,
defaultOpen: propTypes.bool
defaultOpen: propTypes.bool,
})
export default class Collapse extends Component {
state = {
isOpened: this.props.defaultOpen
isOpened: this.props.defaultOpen,
}
_onClick = () => {
this.setState({
isOpened: !this.state.isOpened
isOpened: !this.state.isOpened,
})
}
@ -29,7 +29,8 @@ export default class Collapse extends Component {
return (
<div className={props.className}>
<Button block btnStyle='primary' size='large' onClick={this._onClick}>
{props.buttonText} <Icon icon={`chevron-${isOpened ? 'up' : 'down'}`} />
{props.buttonText}{' '}
<Icon icon={`chevron-${isOpened ? 'up' : 'down'}`} />
</Button>
{isOpened && props.children}
</div>

View File

@ -1,25 +1,22 @@
import React from 'react'
import uncontrollableInput from 'uncontrollable-input'
import { isEmpty, map } from 'lodash'
import {
DropdownButton,
MenuItem
} from 'react-bootstrap-4/lib'
import { DropdownButton, MenuItem } from 'react-bootstrap-4/lib'
import Component from './base-component'
import propTypes from './prop-types-decorator'
@uncontrollableInput({
defaultValue: ''
defaultValue: '',
})
@propTypes({
disabled: propTypes.bool,
options: propTypes.oneOfType([
propTypes.arrayOf(propTypes.string),
propTypes.objectOf(propTypes.string)
propTypes.objectOf(propTypes.string),
]),
onChange: propTypes.func.isRequired,
value: propTypes.string.isRequired
value: propTypes.string.isRequired,
})
export default class Combobox extends Component {
_handleChange = event => {
@ -50,11 +47,11 @@ export default class Combobox extends Component {
id='selectInput'
title=''
>
{map(options, option =>
{map(options, option => (
<MenuItem key={option} onClick={() => this._setText(option)}>
{option}
</MenuItem>
)}
))}
</DropdownButton>
</div>
{Input}

View File

@ -1,11 +1,5 @@
import {
parse,
toString
} from './'
import {
ast,
pattern
} from './index.fixtures'
import { parse, toString } from './'
import { ast, pattern } from './index.fixtures'
export default ({ benchmark }) => {
benchmark('parse', () => {
@ -13,6 +7,6 @@ export default ({ benchmark }) => {
})
benchmark('toString', () => {
ast::toString()
;ast::toString()
})
}

View File

@ -4,7 +4,7 @@ import {
createNot,
createProperty,
createString,
createTruthyProperty
createTruthyProperty,
} from './'
export const pattern = 'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape?'
@ -12,9 +12,9 @@ export const pattern = 'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape?'
export const ast = createAnd([
createString('foo'),
createNot(createString('\\ "')),
createProperty('name', createOr([
createString('wonderwoman'),
createString('batman')
])),
createTruthyProperty('hasCape')
createProperty(
'name',
createOr([createString('wonderwoman'), createString('batman')])
),
createTruthyProperty('hasCape'),
])

View File

@ -42,17 +42,19 @@ const isRawString = string => {
// -------------------------------------------------------------------
export const createAnd = children => children.length === 1
? children[0]
: { type: 'and', children }
export const createAnd = children =>
children.length === 1 ? children[0] : { type: 'and', children }
export const createOr = children => children.length === 1
? children[0]
: { type: 'or', children }
export const createOr = children =>
children.length === 1 ? children[0] : { type: 'or', children }
export const createNot = child => ({ type: 'not', child })
export const createProperty = (name, child) => ({ type: 'property', name, child })
export const createProperty = (name, child) => ({
type: 'property',
name,
child,
})
export const createString = value => ({ type: 'string', value })
@ -97,7 +99,7 @@ export const parse = invoke(() => {
return
}
const terms = [ term ]
const terms = [term]
while ((term = parseTerm())) {
terms.push(term)
}
@ -106,14 +108,13 @@ export const parse = invoke(() => {
const parseTerm = () => {
parseWs()
const child = (
const child =
parseGroupedAnd() ||
parseOr() ||
parseNot() ||
parseProperty() ||
parseTruthyProperty() ||
parseString()
)
if (child) {
parseWs()
return child
@ -128,11 +129,7 @@ export const parse = invoke(() => {
}
const parseGroupedAnd = backtrace(() => {
let and
if (
input[i++] === '(' &&
(and = parseAnd()) &&
input[i++] === ')'
) {
if (input[i++] === '(' && (and = parseAnd()) && input[i++] === ')') {
return and
}
})
@ -150,10 +147,7 @@ export const parse = invoke(() => {
})
const parseNot = backtrace(() => {
let child
if (
input[i++] === '!' &&
(child = parseTerm())
) {
if (input[i++] === '!' && (child = parseTerm())) {
return createNot(child)
}
})
@ -162,7 +156,7 @@ export const parse = invoke(() => {
if (
(name = parseString()) &&
parseWs() &&
(input[i++] === ':') &&
input[i++] === ':' &&
(child = parseTerm())
) {
return createProperty(name.value, child)
@ -196,10 +190,7 @@ export const parse = invoke(() => {
const parseRawString = () => {
let value = ''
let c
while (
(c = input[i]) &&
RAW_STRING_CHARS[c]
) {
while ((c = input[i]) && RAW_STRING_CHARS[c]) {
++i
value += c
}
@ -209,11 +200,7 @@ export const parse = invoke(() => {
}
const parseTruthyProperty = backtrace(() => {
let name
if (
(name = parseString()) &&
parseWs() &&
input[i++] === '?'
) {
if ((name = parseString()) && parseWs() && input[i++] === '?') {
return createTruthyProperty(name.value)
}
})
@ -251,7 +238,7 @@ const _getPropertyClauseStrings = ({ child }) => {
}
if (type === 'string') {
return [ child.value ]
return [child.value]
}
return []
@ -267,7 +254,7 @@ export const getPropertyClausesStrings = function () {
if (type === 'property') {
return {
[this.name]: _getPropertyClauseStrings(this)
[this.name]: _getPropertyClauseStrings(this),
}
}
@ -294,17 +281,17 @@ 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
}
if (type === 'and') {
return createAnd(filter(this.children, node =>
node.type !== 'property' || node.name !== name
))
return createAnd(
filter(
this.children,
node => node.type !== 'property' || node.name !== name
)
)
}
return this
@ -313,14 +300,14 @@ export const removePropertyClause = function (name) {
// -------------------------------------------------------------------
const _addAndClause = (node, child, predicate, reducer) =>
createAnd(filterReduce(
node.type === 'and'
? node.children
: [ node ],
predicate,
reducer,
child
))
createAnd(
filterReduce(
node.type === 'and' ? node.children : [node],
predicate,
reducer,
child
)
)
export const setPropertyClause = function (name, child) {
const property = createProperty(
@ -343,18 +330,12 @@ export const setPropertyClause = function (name, child) {
export const execute = invoke(() => {
const visitors = {
and: ({ children }, value) => (
every(children, child => child::execute(value))
),
not: ({ child }, value) => (
!child::execute(value)
),
or: ({ children }, value) => (
some(children, child => child::execute(value))
),
property: ({ name, child }, value) => (
value != null && child::execute(value[name])
),
and: ({ children }, value) =>
every(children, child => child::execute(value)),
not: ({ child }, value) => !child::execute(value),
or: ({ children }, value) => some(children, child => child::execute(value)),
property: ({ name, child }, value) =>
value != null && child::execute(value[name]),
truthyProperty: ({ name }, value) => !!value[name],
string: invoke(() => {
const match = (pattern, value) => {
@ -369,10 +350,8 @@ export const execute = invoke(() => {
return false
}
return ({ value: pattern }, value) => (
match(pattern.toLowerCase(), value)
)
})
return ({ value: pattern }, value) => match(pattern.toLowerCase(), value)
}),
}
return function (value) {
@ -390,11 +369,13 @@ export const toString = invoke(() => {
and: ({ children }) => toStringGroup(children),
not: ({ child }) => `!${toString(child)}`,
or: ({ children }) => `|${toStringGroup(children)}`,
property: ({ name, child }) => `${toString(createString(name))}:${toString(child)}`,
string: ({ value }) => isRawString(value)
? value
: `"${value.replace(/\\|"/g, match => `\\${match}`)}"`,
truthyProperty: ({ name }) => `${toString(createString(name))}?`
property: ({ name, child }) =>
`${toString(createString(name))}:${toString(child)}`,
string: ({ value }) =>
isRawString(value)
? value
: `"${value.replace(/\\|"/g, match => `\\${match}`)}"`,
truthyProperty: ({ name }) => `${toString(createString(name))}?`,
}
const toString = node => visitors[node.type](node)
@ -403,9 +384,7 @@ export const toString = invoke(() => {
return function () {
return !this
? ''
: this.type === 'and'
? toStringTerms(this.children)
: toString(this)
: this.type === 'and' ? toStringTerms(this.children) : toString(this)
}
})

View File

@ -4,18 +4,15 @@ import {
getPropertyClausesStrings,
parse,
setPropertyClause,
toString
toString,
} from './'
import {
ast,
pattern
} from './index.fixtures'
import { ast, pattern } from './index.fixtures'
it('getPropertyClausesStrings', () => {
const tmp = parse('foo bar:baz baz:|(foo bar)')::getPropertyClausesStrings()
expect(tmp).toEqual({
bar: [ 'baz' ],
baz: [ 'foo', 'bar' ]
bar: ['baz'],
baz: ['foo', 'bar'],
})
})
@ -24,20 +21,24 @@ it('parse', () => {
})
it('setPropertyClause', () => {
expect(
null::setPropertyClause('foo', 'bar')::toString()
).toBe('foo:bar')
expect(null::setPropertyClause('foo', 'bar')::toString()).toBe('foo:bar')
expect(
parse('baz')::setPropertyClause('foo', 'bar')::toString()
parse('baz')
::setPropertyClause('foo', 'bar')
::toString()
).toBe('baz foo:bar')
expect(
parse('plip foo:baz plop')::setPropertyClause('foo', 'bar')::toString()
parse('plip foo:baz plop')
::setPropertyClause('foo', 'bar')
::toString()
).toBe('plip plop foo:bar')
expect(
parse('foo:|(baz plop)')::setPropertyClause('foo', 'bar')::toString()
parse('foo:|(baz plop)')
::setPropertyClause('foo', 'bar')
::toString()
).toBe('foo:bar')
})

View File

@ -12,21 +12,23 @@ import styles from './index.css'
const Copiable = propTypes({
data: propTypes.string,
tagName: propTypes.string
})(({ className, tagName = 'span', ...props }) => createElement(
tagName,
{
...props,
className: classNames(styles.container, className)
},
props.children,
' ',
<Tooltip content={_('copyToClipboard')}>
<CopyToClipboard text={props.data || props.children}>
<Button className={styles.button} size='small'>
<Icon icon='clipboard' />
</Button>
</CopyToClipboard>
</Tooltip>
))
tagName: propTypes.string,
})(({ className, tagName = 'span', ...props }) =>
createElement(
tagName,
{
...props,
className: classNames(styles.container, className),
},
props.children,
' ',
<Tooltip content={_('copyToClipboard')}>
<CopyToClipboard text={props.data || props.children}>
<Button className={styles.button} size='small'>
<Icon icon='clipboard' />
</Button>
</CopyToClipboard>
</Tooltip>
)
)
export { Copiable as default }

View File

@ -48,7 +48,7 @@ const debounceComponentDecorator = (delay = DEFAULT_DELAY) => Component =>
...this.props,
onChange: this._onChange,
ref: this._onRef,
value: this.state.value
value: this.state.value,
}
return <Component {...props} />
}

View File

@ -1,21 +1,20 @@
import React, { Component, PropTypes } from 'react'
import { isPromise } from 'promise-toolbox'
const toString = value => value === undefined
? 'undefined'
: JSON.stringify(value, null, 2)
const toString = value =>
value === undefined ? 'undefined' : JSON.stringify(value, null, 2)
// This component does not handle changes in its `promise` property.
class DebugAsync extends Component {
static propTypes = {
promise: PropTypes.object.isRequired
promise: PropTypes.object.isRequired,
}
constructor (props) {
super()
this.state = {
status: 'pending'
status: 'pending',
}
props.promise.then(
@ -35,21 +34,26 @@ class DebugAsync extends Component {
return <pre>{'Promise { <pending> }'}</pre>
}
return <pre>
{'Promise { '}
{status === 'rejected' && '<rejected> '}
{toString(value)}
{' }'}
</pre>
return (
<pre>
{'Promise { '}
{status === 'rejected' && '<rejected> '}
{toString(value)}
{' }'}
</pre>
)
}
}
const Debug = ({ value }) => isPromise(value)
? <DebugAsync promise={value} />
: <pre>{toString(value)}</pre>
const Debug = ({ value }) =>
isPromise(value) ? (
<DebugAsync promise={value} />
) : (
<pre>{toString(value)}</pre>
)
Debug.propTypes = {
value: PropTypes.any.isRequired
value: PropTypes.any.isRequired,
}
export { Debug as default }

View File

@ -7,14 +7,20 @@ import styles from './index.css'
@propTypes({
onDrop: propTypes.func,
message: propTypes.node
message: propTypes.node,
})
export default class Dropzone extends Component {
render () {
const { onDrop, message } = this.props
return <ReactDropzone onDrop={onDrop} className={styles.dropzone} activeClassName={styles.activeDropzone}>
<div className={styles.dropzoneText}>{message}</div>
</ReactDropzone>
return (
<ReactDropzone
onDrop={onDrop}
className={styles.dropzone}
activeClassName={styles.activeDropzone}
>
<div className={styles.dropzoneText}>{message}</div>
</ReactDropzone>
)
}
}

View File

@ -27,7 +27,7 @@ import {
SelectTag,
SelectVgpuType,
SelectVm,
SelectVmTemplate
SelectVmTemplate,
} from '../select-objects'
import styles from './index.css'
@ -35,14 +35,14 @@ import styles from './index.css'
const LONG_CLICK = 400
@propTypes({
alt: propTypes.node.isRequired
alt: propTypes.node.isRequired,
})
class Hover extends Component {
constructor () {
super()
this.state = {
hover: false
hover: false,
}
this._onMouseEnter = () => this.setState({ hover: true })
@ -51,25 +51,18 @@ class Hover extends Component {
render () {
if (this.state.hover) {
return <span onMouseLeave={this._onMouseLeave}>
{this.props.alt}
</span>
return <span onMouseLeave={this._onMouseLeave}>{this.props.alt}</span>
}
return <span onMouseEnter={this._onMouseEnter}>
{this.props.children}
</span>
return <span onMouseEnter={this._onMouseEnter}>{this.props.children}</span>
}
}
@propTypes({
onChange: propTypes.func.isRequired,
onUndo: propTypes.oneOfType([
propTypes.bool,
propTypes.func
]),
onUndo: propTypes.oneOfType([propTypes.bool, propTypes.func]),
useLongClick: propTypes.bool,
value: propTypes.any.isRequired
value: propTypes.any.isRequired,
})
class Editable extends Component {
get value () {
@ -95,7 +88,7 @@ class Editable extends Component {
this.setState({
editing: true,
error: null,
saving: false
saving: false,
})
}
@ -113,10 +106,7 @@ class Editable extends Component {
}
_save () {
return this.__save(
() => this.value,
this.props.onChange
)
return this.__save(() => this.value, this.props.onChange)
}
async __save (getValue, saveValue) {
@ -139,7 +129,7 @@ class Editable extends Component {
this.setState({
// `error` may be undefined if the action has been cancelled
error: error !== undefined && (isString(error) ? error : error.message),
saving: false
saving: false,
})
logError(error)
}
@ -162,34 +152,59 @@ class Editable extends Component {
const { useLongClick } = props
const success = <Icon icon='success' />
return <span className={classNames(styles.clickToEdit, !useLongClick && styles.shortClick)}>
return (
<span
onClick={!useLongClick && this._openEdition}
onMouseDown={useLongClick && this.__startTimer}
onMouseUp={useLongClick && this.__stopTimer}
className={classNames(
styles.clickToEdit,
!useLongClick && styles.shortClick
)}
>
{this._renderDisplay()}
</span>
{previous != null && (onUndo !== false
? <Hover
alt={<a onClick={this._undo}><Icon icon='undo' /></a>}
<span
onClick={!useLongClick && this._openEdition}
onMouseDown={useLongClick && this.__startTimer}
onMouseUp={useLongClick && this.__stopTimer}
>
{success}
</Hover>
: success
)}
</span>
{this._renderDisplay()}
</span>
{previous != null &&
(onUndo !== false ? (
<Hover
alt={
<a onClick={this._undo}>
<Icon icon='undo' />
</a>
}
>
{success}
</Hover>
) : (
success
))}
</span>
)
}
const { error, saving } = state
return <span>
{this._renderEdition()}
{saving && <span>{' '}<Icon icon='loading' /></span>}
{error != null && <span>
{' '}<Tooltip content={error}><Icon icon='error' /></Tooltip>
</span>}
</span>
return (
<span>
{this._renderEdition()}
{saving && (
<span>
{' '}
<Icon icon='loading' />
</span>
)}
{error != null && (
<span>
{' '}
<Tooltip content={error}>
<Icon icon='error' />
</Tooltip>
</span>
)}
</span>
)
}
}
@ -198,7 +213,7 @@ class Editable extends Component {
maxLength: propTypes.number,
minLength: propTypes.number,
pattern: propTypes.string,
value: propTypes.string.isRequired
value: propTypes.string.isRequired,
})
export class Text extends Editable {
get value () {
@ -218,25 +233,22 @@ export class Text extends Editable {
}
_renderDisplay () {
const {
children,
value
} = this.props
const { children, value } = this.props
if (children || value) {
return <span> {children || value} </span>
}
const {
placeholder,
useLongClick
} = this.props
const { placeholder, useLongClick } = this.props
return <span className='text-muted'>
{placeholder ||
(useLongClick ? _('editableLongClickPlaceholder') : _('editableClickPlaceholder'))
}
</span>
return (
<span className='text-muted'>
{placeholder ||
(useLongClick
? _('editableLongClickPlaceholder')
: _('editableClickPlaceholder'))}
</span>
)
}
_renderEdition () {
@ -248,25 +260,26 @@ export class Text extends Editable {
'autoComplete',
'maxLength',
'minLength',
'pattern'
'pattern',
])
return <input
{...extraProps}
autoFocus
defaultValue={value}
onBlur={this._closeEdition}
onInput={this._onInput}
onKeyDown={this._onKeyDown}
readOnly={saving}
ref='input'
style={{
width: `${value.length + 1}ex`,
maxWidth: '50ex'
}}
type={this._isPassword ? 'password' : 'text'}
/>
return (
<input
{...extraProps}
autoFocus
defaultValue={value}
onBlur={this._closeEdition}
onInput={this._onInput}
onKeyDown={this._onKeyDown}
readOnly={saving}
ref='input'
style={{
width: `${value.length + 1}ex`,
maxWidth: '50ex',
}}
type={this._isPassword ? 'password' : 'text'}
/>
)
}
}
@ -278,7 +291,7 @@ export class Password extends Text {
@propTypes({
nullable: propTypes.bool,
value: propTypes.number
value: propTypes.number,
})
export class Number extends Component {
get value () {
@ -301,20 +314,19 @@ export class Number extends Component {
render () {
const { value } = this.props
return <Text
{...this.props}
onChange={this._onChange}
value={value === null ? '' : String(value)}
/>
return (
<Text
{...this.props}
onChange={this._onChange}
value={value === null ? '' : String(value)}
/>
)
}
}
@propTypes({
options: propTypes.oneOfType([
propTypes.array,
propTypes.object
]).isRequired,
renderer: propTypes.func
options: propTypes.oneOfType([propTypes.array, propTypes.object]).isRequired,
renderer: propTypes.func,
})
export class Select extends Editable {
componentWillReceiveProps (props) {
@ -322,7 +334,9 @@ export class Select extends Editable {
props.value !== this.props.value ||
props.options !== this.props.options
) {
this.setState({ valueKey: findKey(props.options, option => option === props.value) })
this.setState({
valueKey: findKey(props.options, option => option === props.value),
})
}
}
@ -337,12 +351,11 @@ export class Select extends Editable {
_optionToJsx = (option, key) => {
const { renderer } = this.props
return <option
key={key}
value={key}
>
{renderer ? renderer(option) : option}
</option>
return (
<option key={key} value={key}>
{renderer ? renderer(option) : option}
</option>
)
}
_onEditionMount = ref => {
@ -353,26 +366,27 @@ export class Select extends Editable {
_renderDisplay () {
const { children, renderer, value } = this.props
return children ||
<span>{renderer ? renderer(value) : value}</span>
return children || <span>{renderer ? renderer(value) : value}</span>
}
_renderEdition () {
const { saving, valueKey } = this.state
const { options } = this.props
return <select
autoFocus
className={classNames('form-control', styles.select)}
onBlur={this._closeEdition}
onChange={this._onChange}
onKeyDown={this._onKeyDown}
readOnly={saving}
ref={this._onEditionMount}
value={valueKey}
>
{map(options, this._optionToJsx)}
</select>
return (
<select
autoFocus
className={classNames('form-control', styles.select)}
onBlur={this._closeEdition}
onChange={this._onChange}
onKeyDown={this._onKeyDown}
readOnly={saving}
ref={this._onEditionMount}
value={valueKey}
>
{map(options, this._optionToJsx)}
</select>
)
}
}
@ -388,14 +402,11 @@ const MAP_TYPE_SELECT = {
tag: SelectTag,
vgpuType: SelectVgpuType,
VM: SelectVm,
'VM-template': SelectVmTemplate
'VM-template': SelectVmTemplate,
}
@propTypes({
value: propTypes.oneOfType([
propTypes.string,
propTypes.object
])
value: propTypes.oneOfType([propTypes.string, propTypes.object]),
})
export class XoSelect extends Editable {
get value () {
@ -403,19 +414,17 @@ export class XoSelect extends Editable {
}
_renderDisplay () {
return this.props.children ||
<span>{this.props.value[this.props.labelProp]}</span>
return (
this.props.children || (
<span>{this.props.value[this.props.labelProp]}</span>
)
)
}
_onChange = object =>
this.setState({ value: object }, object && this._save)
_onChange = object => this.setState({ value: object }, object && this._save)
_renderEdition () {
const {
saving,
xoType,
...props
} = this.props
const { saving, xoType, ...props } = this.props
const Select = MAP_TYPE_SELECT[xoType]
if (process.env.NODE_ENV !== 'production') {
@ -426,19 +435,21 @@ export class XoSelect extends Editable {
// Anchor is needed so that the BlockLink does not trigger a redirection
// when this element is clicked.
return <a onBlur={this._closeEdition}>
<Select
{...props}
autoFocus
disabled={saving}
onChange={this._onChange}
/>
</a>
return (
<a onBlur={this._closeEdition}>
<Select
{...props}
autoFocus
disabled={saving}
onChange={this._onChange}
/>
</a>
)
}
}
@propTypes({
value: propTypes.number.isRequired
value: propTypes.number.isRequired,
})
export class Size extends Editable {
get value () {
@ -456,27 +467,31 @@ export class Size extends Editable {
}, 10)
}
_focus = () => { this._focused = true }
_focus = () => {
this._focused = true
}
_renderEdition () {
const { saving } = this.state
const { value } = this.props
return <span
// SizeInput uses `input-group` which makes it behave as a block element (display: table).
// `form-inline` to use it as an inline element
className='form-inline'
onBlur={this._closeEditionIfUnfocused}
onFocus={this._focus}
onKeyDown={this._onKeyDown}
>
<SizeInput
autoFocus
className={styles.size}
ref='input'
readOnly={saving}
defaultValue={value}
/>
</span>
return (
<span
// SizeInput uses `input-group` which makes it behave as a block element (display: table).
// `form-inline` to use it as an inline element
className='form-inline'
onBlur={this._closeEditionIfUnfocused}
onFocus={this._focus}
onKeyDown={this._onKeyDown}
>
<SizeInput
autoFocus
className={styles.size}
ref='input'
readOnly={saving}
defaultValue={value}
/>
</span>
)
}
}

View File

@ -3,24 +3,22 @@ import React from 'react'
const ellipsisStyle = {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
whiteSpace: 'nowrap',
}
const ellipsisContainerStyle = {
display: 'flex'
display: 'flex',
}
const Ellipsis = ({ children }) => (
<span style={ellipsisStyle}>
{children}
</span>
)
const Ellipsis = ({ children }) => <span style={ellipsisStyle}>{children}</span>
export { Ellipsis as default }
export const EllipsisContainer = ({ children }) => (
<div style={ellipsisContainerStyle}>
{React.Children.map(children, child =>
child == null || child.type === Ellipsis ? child : <span>{child}</span>
{React.Children.map(
children,
child =>
child == null || child.type === Ellipsis ? child : <span>{child}</span>
)}
</div>
)

View File

@ -11,14 +11,8 @@ import identity from 'lodash/identity'
const filterReduce = (array, predicate, reducer, initial) => {
const { length } = array
let i
if (
!length ||
!predicate ||
(i = findIndex(array, predicate)) === -1
) {
return initial == null
? array.slice(0)
: array.concat(initial)
if (!length || !predicate || (i = findIndex(array, predicate)) === -1) {
return initial == null ? array.slice(0) : array.concat(initial)
}
if (reducer == null) {
@ -26,9 +20,7 @@ const filterReduce = (array, predicate, reducer, initial) => {
}
const result = array.slice(0, i)
let value = initial == null
? array[i]
: reducer(initial, array[i], i, array)
let value = initial == null ? array[i] : reducer(initial, array[i], i, array)
for (i = i + 1; i < length; ++i) {
const current = array[i]

View File

@ -3,23 +3,17 @@
import filterReduce from './filter-reduce'
const add = (a, b) => a + b
const data = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
const data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const isEven = x => !(x & 1)
it('filterReduce', () => {
// Returns all elements not matching the predicate and the result of
// a reduction over those who do.
expect(filterReduce(data, isEven, add)).toEqual(
[ 1, 3, 5, 7, 9, 20 ]
)
expect(filterReduce(data, isEven, add)).toEqual([1, 3, 5, 7, 9, 20])
// The default reducer is the identity.
expect(filterReduce(data, isEven)).toEqual(
[ 1, 3, 5, 7, 9, 0 ]
)
expect(filterReduce(data, isEven)).toEqual([1, 3, 5, 7, 9, 0])
// If an initial value is passed it is used.
expect(filterReduce(data, isEven, add, 22)).toEqual(
[ 1, 3, 5, 7, 9, 42 ]
)
expect(filterReduce(data, isEven, add, 22)).toEqual([1, 3, 5, 7, 9, 42])
})

View File

@ -4,21 +4,15 @@ import * as Grid from './grid'
import propTypes from './prop-types-decorator'
export const LabelCol = propTypes({
children: propTypes.any.isRequired
children: propTypes.any.isRequired,
})(({ children }) => (
<label className='col-md-2 form-control-label'>{children}</label>
))
export const InputCol = propTypes({
children: propTypes.any.isRequired
})(({ children }) => (
<Grid.Col mediumSize={10}>{children}</Grid.Col>
))
children: propTypes.any.isRequired,
})(({ children }) => <Grid.Col mediumSize={10}>{children}</Grid.Col>)
export const Row = propTypes({
children: propTypes.arrayOf(propTypes.element).isRequired
})(({ children }) => (
<Grid.Row className='form-group'>
{children}
</Grid.Row>
))
children: propTypes.arrayOf(propTypes.element).isRequired,
})(({ children }) => <Grid.Row className='form-group'>{children}</Grid.Row>)

View File

@ -7,20 +7,14 @@ import React from 'react'
import round from 'lodash/round'
import SingleLineRow from 'single-line-row'
import { Container, Col } from 'grid'
import {
DropdownButton,
MenuItem
} from 'react-bootstrap-4/lib'
import { DropdownButton, MenuItem } from 'react-bootstrap-4/lib'
import Button from '../button'
import Component from '../base-component'
import defined from '../xo-defined'
import getEventValue from '../get-event-value'
import propTypes from '../prop-types-decorator'
import {
formatSizeRaw,
parseSize
} from '../utils'
import { formatSizeRaw, parseSize } from '../utils'
export Select from './select'
export SelectPlainObject from './select-plain-object'
@ -28,7 +22,7 @@ export SelectPlainObject from './select-plain-object'
// ===================================================================
@propTypes({
enableGenerator: propTypes.bool
enableGenerator: propTypes.bool,
})
export class Password extends Component {
get value () {
@ -51,42 +45,42 @@ export class Password extends Component {
// FIXME: in controlled mode, visibility should only be updated
// when the value prop is changed according to the emitted value.
this.setState({
visible: true
visible: true,
})
}
_toggleVisibility = () => {
this.setState({
visible: !this.state.visible
visible: !this.state.visible,
})
}
render () {
const {
className,
enableGenerator = false,
...props
} = this.props
const { className, enableGenerator = false, ...props } = this.props
const { visible } = this.state
return <div className='input-group'>
{enableGenerator && <span className='input-group-btn'>
<Button onClick={this._generate}>
<Icon icon='password' />
</Button>
</span>}
<input
{...props}
className={classNames(className, 'form-control')}
ref='field'
type={visible ? 'text' : 'password'}
/>
<span className='input-group-btn'>
<Button onClick={this._toggleVisibility}>
<Icon icon={visible ? 'shown' : 'hidden'} />
</Button>
</span>
</div>
return (
<div className='input-group'>
{enableGenerator && (
<span className='input-group-btn'>
<Button onClick={this._generate}>
<Icon icon='password' />
</Button>
</span>
)}
<input
{...props}
className={classNames(className, 'form-control')}
ref='field'
type={visible ? 'text' : 'password'}
/>
<span className='input-group-btn'>
<Button onClick={this._toggleVisibility}>
<Icon icon={visible ? 'shown' : 'hidden'} />
</Button>
</span>
</div>
)
}
}
@ -97,7 +91,7 @@ export class Password extends Component {
min: propTypes.number.isRequired,
onChange: propTypes.func,
step: propTypes.number,
value: propTypes.number
value: propTypes.number,
})
export class Range extends Component {
componentDidMount () {
@ -108,30 +102,31 @@ export class Range extends Component {
}
}
_onChange = value =>
this.props.onChange(getEventValue(value))
_onChange = value => this.props.onChange(getEventValue(value))
render () {
const { max, min, step, value } = this.props
return <Container>
<SingleLineRow>
<Col size={2}>
<span className='pull-right'>{value}</span>
</Col>
<Col size={10}>
<input
className='form-control'
max={max}
min={min}
onChange={this._onChange}
step={step}
type='range'
value={value}
/>
</Col>
</SingleLineRow>
</Container>
return (
<Container>
<SingleLineRow>
<Col size={2}>
<span className='pull-right'>{value}</span>
</Col>
<Col size={10}>
<input
className='form-control'
max={max}
min={min}
onChange={this._onChange}
step={step}
type='range'
value={value}
/>
</Col>
</SingleLineRow>
</Container>
)
}
}
@ -149,16 +144,15 @@ const DEFAULT_UNIT = 'GiB'
readOnly: propTypes.bool,
required: propTypes.bool,
style: propTypes.object,
value: propTypes.oneOfType([
propTypes.number,
propTypes.oneOf([ null ])
])
value: propTypes.oneOfType([propTypes.number, propTypes.oneOf([null])]),
})
export class SizeInput extends BaseComponent {
constructor (props) {
super(props)
this.state = this._createStateFromBytes(defined(props.value, props.defaultValue, null))
this.state = this._createStateFromBytes(
defined(props.value, props.defaultValue, null)
)
}
componentWillReceiveProps (props) {
@ -172,21 +166,21 @@ export class SizeInput extends BaseComponent {
if (bytes === this._bytes) {
return {
input: this._input,
unit: this._unit
unit: this._unit,
}
}
if (bytes === null) {
return {
input: '',
unit: this.props.defaultUnit || DEFAULT_UNIT
unit: this.props.defaultUnit || DEFAULT_UNIT,
}
}
const { prefix, value } = formatSizeRaw(bytes)
return {
input: String(round(value, 2)),
unit: `${prefix}B`
unit: `${prefix}B`,
}
}
@ -214,9 +208,7 @@ export class SizeInput extends BaseComponent {
const { onChange } = this.props
// Empty input equals null.
const bytes = input
? parseSize(`${+input} ${unit}`)
: null
const bytes = input ? parseSize(`${+input} ${unit}`) : null
const isControlled = this.props.value !== undefined
if (isControlled) {
@ -246,8 +238,7 @@ export class SizeInput extends BaseComponent {
const number = +input
// NaN: do not ack this change.
if (number !== number) { // eslint-disable-line no-self-compare
if (Number.isNaN(number)) {
return
}
@ -278,38 +269,37 @@ export class SizeInput extends BaseComponent {
readOnly,
placeholder,
required,
style
style,
} = this.props
return <span className={classNames('input-group', className)} style={style}>
<input
autoFocus={autoFocus}
className='form-control'
disabled={readOnly}
onChange={this._updateNumber}
placeholder={placeholder}
required={required}
type='text'
value={this.state.input}
/>
<span className='input-group-btn'>
<DropdownButton
bsStyle='secondary'
id='size'
pullRight
return (
<span className={classNames('input-group', className)} style={style}>
<input
autoFocus={autoFocus}
className='form-control'
disabled={readOnly}
title={this.state.unit}
>
{map(UNITS, unit =>
<MenuItem
key={unit}
onClick={() => this._updateUnit(unit)}
>
{unit}
</MenuItem>
)}
</DropdownButton>
onChange={this._updateNumber}
placeholder={placeholder}
required={required}
type='text'
value={this.state.input}
/>
<span className='input-group-btn'>
<DropdownButton
bsStyle='secondary'
id='size'
pullRight
disabled={readOnly}
title={this.state.unit}
>
{map(UNITS, unit => (
<MenuItem key={unit} onClick={() => this._updateUnit(unit)}>
{unit}
</MenuItem>
))}
</DropdownButton>
</span>
</span>
</span>
)
}
}

View File

@ -18,7 +18,7 @@ import Select from './select'
placeholder: propTypes.node,
predicate: propTypes.func,
required: propTypes.bool,
value: propTypes.any
value: propTypes.any,
})
@uncontrollableInput()
export default class SelectPlainObject extends Component {
@ -27,7 +27,7 @@ export default class SelectPlainObject extends Component {
this.setState({
options: this._computeOptions(options),
value: this._computeValue(value, this.props)
value: this._computeValue(value, this.props),
})
}
@ -35,7 +35,7 @@ export default class SelectPlainObject extends Component {
if (newProps !== this.props) {
this.setState({
options: this._computeOptions(newProps.options),
value: this._computeValue(newProps.value, newProps)
value: this._computeValue(newProps.value, newProps),
})
}
}
@ -43,7 +43,8 @@ export default class SelectPlainObject extends Component {
_computeValue (value, props = this.props) {
let { optionKey } = props
optionKey || (optionKey = 'id')
const reduceValue = value => value != null ? (value[optionKey] || value) : ''
const reduceValue = value =>
value != null ? value[optionKey] || value : ''
if (props.multi) {
if (!Array.isArray(value)) {
value = [value]
@ -59,7 +60,7 @@ export default class SelectPlainObject extends Component {
const { optionRenderer = o => o.label || o[optionKey] || o } = this.props
return map(options, option => ({
value: option[optionKey] || option,
label: optionRenderer(option)
label: optionRenderer(option),
}))
}
@ -72,7 +73,10 @@ export default class SelectPlainObject extends Component {
const pickValue = value => {
value = value.value || value
return find(options, option => option[optionKey] === value || option === value)
return find(
options,
option => option[optionKey] === value || option === value
)
}
if (this.props.multi) {

View File

@ -2,43 +2,35 @@ import map from 'lodash/map'
import React, { Component } from 'react'
import ReactSelect from 'react-select'
import sum from 'lodash/sum'
import {
AutoSizer,
CellMeasurer,
List
} from 'react-virtualized'
import { AutoSizer, CellMeasurer, List } from 'react-virtualized'
import propTypes from '../prop-types-decorator'
const SELECT_MENU_STYLE = {
overflow: 'hidden'
overflow: 'hidden',
}
const SELECT_STYLE = {
minWidth: '10em'
minWidth: '10em',
}
const LIST_STYLE = {
whiteSpace: 'normal'
whiteSpace: 'normal',
}
const MAX_OPTIONS = 5
// See: https://github.com/bvaughn/react-virtualized-select/blob/master/source/VirtualizedSelect/VirtualizedSelect.js
@propTypes({
maxHeight: propTypes.number
maxHeight: propTypes.number,
})
export default class Select extends Component {
static defaultProps = {
maxHeight: 200,
optionRenderer: (option, labelKey) => option[labelKey]
optionRenderer: (option, labelKey) => option[labelKey],
}
_renderMenu = ({
focusedOption,
options,
...otherOptions
}) => {
_renderMenu = ({ focusedOption, options, ...otherOptions }) => {
const { maxHeight } = this.props
const focusedOptionIndex = options.indexOf(focusedOption)
@ -52,15 +44,17 @@ export default class Select extends Component {
key,
option: options[index],
options,
style
style,
})
return (
<AutoSizer disableHeight>
{({ width }) => (
{({ width }) =>
width ? (
<CellMeasurer
cellRenderer={({ rowIndex }) => wrappedRowRenderer({ index: rowIndex })}
cellRenderer={({ rowIndex }) =>
wrappedRowRenderer({ index: rowIndex })
}
columnCount={1}
rowCount={options.length}
// FIXME: 16 px: ugly workaround to take into account the scrollbar
@ -70,22 +64,26 @@ export default class Select extends Component {
>
{({ getRowHeight }) => {
if (options.length <= MAX_OPTIONS) {
height = sum(map(options, (_, index) => getRowHeight({ index })))
height = sum(
map(options, (_, index) => getRowHeight({ index }))
)
}
return <List
height={height}
rowCount={options.length}
rowHeight={getRowHeight}
rowRenderer={wrappedRowRenderer}
scrollToIndex={focusedOptionIndex}
style={LIST_STYLE}
width={width}
/>
return (
<List
height={height}
rowCount={options.length}
rowHeight={getRowHeight}
rowRenderer={wrappedRowRenderer}
scrollToIndex={focusedOptionIndex}
style={LIST_STYLE}
width={width}
/>
)
}}
</CellMeasurer>
) : null
)}
}
</AutoSizer>
)
}
@ -97,7 +95,7 @@ export default class Select extends Component {
labelKey,
option,
style,
selectValue
selectValue,
}) => {
let className = 'Select-option'

View File

@ -14,13 +14,13 @@ import propTypes from '../prop-types-decorator'
iconOn: propTypes.string,
iconOff: propTypes.string,
iconSize: propTypes.number,
value: propTypes.bool
value: propTypes.bool,
})
export default class Toggle extends Component {
static defaultProps = {
iconOn: 'toggle-on',
iconOff: 'toggle-off',
iconSize: 2
iconSize: 2,
}
_toggle = () => {

View File

@ -6,10 +6,8 @@ const getEventValue = event => {
return event
}
return (
target.nodeName.toLowerCase() === 'input' &&
return target.nodeName.toLowerCase() === 'input' &&
target.type.toLowerCase() === 'checkbox'
)
? target.checked
: target.value
}

View File

@ -13,50 +13,49 @@ export const Col = propTypes({
offset: propTypes.number,
smallOffset: propTypes.number,
mediumOffset: propTypes.number,
largeOffset: propTypes.number
})(({
children,
className,
size = 12,
smallSize = size,
mediumSize,
largeSize,
offset,
smallOffset = offset,
mediumOffset,
largeOffset,
style
}) => <div className={classNames(
className,
smallSize && `col-xs-${smallSize}`,
mediumSize && `col-md-${mediumSize}`,
largeSize && `col-lg-${largeSize}`,
smallOffset && `offset-xs-${smallOffset}`,
mediumOffset && `offset-md-${mediumOffset}`,
largeOffset && `offset-lg-${largeOffset}`
)}
style={style}
>
{children}
</div>)
largeOffset: propTypes.number,
})(
({
children,
className,
size = 12,
smallSize = size,
mediumSize,
largeSize,
offset,
smallOffset = offset,
mediumOffset,
largeOffset,
style,
}) => (
<div
className={classNames(
className,
smallSize && `col-xs-${smallSize}`,
mediumSize && `col-md-${mediumSize}`,
largeSize && `col-lg-${largeSize}`,
smallOffset && `offset-xs-${smallOffset}`,
mediumOffset && `offset-md-${mediumOffset}`,
largeOffset && `offset-lg-${largeOffset}`
)}
style={style}
>
{children}
</div>
)
)
// This is the root component of the grid layout, containers should not be
// nested.
export const Container = propTypes({
className: propTypes.string
})(({
children,
className
}) => <div className={classNames(className, 'container-fluid')}>
{children}
</div>)
className: propTypes.string,
})(({ children, className }) => (
<div className={classNames(className, 'container-fluid')}>{children}</div>
))
// Only columns can be children of a row.
export const Row = propTypes({
className: propTypes.string
})(({
children,
className
}) => <div className={`${className || ''} row`}>
{children}
</div>)
className: propTypes.string,
})(({ children, className }) => (
<div className={`${className || ''} row`}>{children}</div>
))

View File

@ -1,5 +1,5 @@
const common = {
homeFilterNone: ''
homeFilterNone: '',
}
export const VM = {
@ -8,26 +8,26 @@ export const VM = {
homeFilterNonRunningVms: '!power_state:running ',
homeFilterHvmGuests: 'virtualizationMode:hvm ',
homeFilterRunningVms: 'power_state:running ',
homeFilterTags: 'tags:'
homeFilterTags: 'tags:',
}
export const host = {
...common,
homeFilterRunningHosts: 'power_state:running ',
homeFilterTags: 'tags:'
homeFilterTags: 'tags:',
}
export const pool = {
...common,
homeFilterTags: 'tags:'
homeFilterTags: 'tags:',
}
export const vmTemplate = {
...common,
homeFilterTags: 'tags:'
homeFilterTags: 'tags:',
}
export const SR = {
...common,
homeFilterTags: 'tags:'
homeFilterTags: 'tags:',
}

View File

@ -10,27 +10,31 @@ import { createString, createProperty, toString } from './complex-matcher'
onAdd: propTypes.func,
onChange: propTypes.func,
onDelete: propTypes.func,
type: propTypes.string
type: propTypes.string,
})
export default class HomeTags extends Component {
static contextTypes = {
router: React.PropTypes.object
router: React.PropTypes.object,
}
_onClick = label => {
const s = encodeURIComponent(createProperty('tags', createString(label))::toString())
const s = encodeURIComponent(
createProperty('tags', createString(label))::toString()
)
const t = encodeURIComponent(this.props.type)
this.context.router.push(`/home?t=${t}&s=${s}`)
}
render () {
return <Tags
labels={this.props.labels}
onAdd={this.props.onAdd}
onChange={this.props.onChange}
onClick={this._onClick}
onDelete={this.props.onDelete}
/>
return (
<Tags
labels={this.props.labels}
onAdd={this.props.onAdd}
onChange={this.props.onChange}
onClick={this._onClick}
onDelete={this.props.onDelete}
/>
)
}
}

View File

@ -1,12 +1,6 @@
import React from 'react'
import { Portal } from 'react-overlays'
import {
forEach,
isEmpty,
keys,
map,
noop
} from 'lodash'
import { forEach, isEmpty, keys, map, noop } from 'lodash'
import _ from './intl'
import ActionButton from './action-button'
@ -19,12 +13,12 @@ import { connectStore } from './utils'
import {
createGetObjectsOfType,
createFilter,
createSelector
createSelector,
} from './selectors'
import {
installAllHostPatches,
installAllPatchesOnPool,
subscribeHostMissingPatches
subscribeHostMissingPatches,
} from './xo'
// ===================================================================
@ -32,18 +26,22 @@ import {
const MISSING_PATCHES_COLUMNS = [
{
name: _('srHost'),
itemRenderer: host => <Link to={`/hosts/${host.id}`}>{host.name_label}</Link>,
sortCriteria: host => host.name_label
itemRenderer: host => (
<Link to={`/hosts/${host.id}`}>{host.name_label}</Link>
),
sortCriteria: host => host.name_label,
},
{
name: _('hostDescription'),
itemRenderer: host => host.name_description,
sortCriteria: host => host.name_description
sortCriteria: host => host.name_description,
},
{
name: _('hostMissingPatches'),
itemRenderer: (host, { missingPatches }) => <Link to={`/hosts/${host.id}/patches`}>{missingPatches[host.id]}</Link>,
sortCriteria: (host, { missingPatches }) => missingPatches[host.id]
itemRenderer: (host, { missingPatches }) => (
<Link to={`/hosts/${host.id}/patches`}>{missingPatches[host.id]}</Link>
),
sortCriteria: (host, { missingPatches }) => missingPatches[host.id],
},
{
name: _('patchUpdateButton'),
@ -54,32 +52,32 @@ const MISSING_PATCHES_COLUMNS = [
handlerParam={host}
icon='host-patch-update'
/>
)
}
),
},
]
const POOLS_MISSING_PATCHES_COLUMNS = [{
name: _('srPool'),
itemRenderer: (host, { pools }) => {
const pool = pools[host.$pool]
return <Link to={`/pools/${pool.id}`}>{pool.name_label}</Link>
const POOLS_MISSING_PATCHES_COLUMNS = [
{
name: _('srPool'),
itemRenderer: (host, { pools }) => {
const pool = pools[host.$pool]
return <Link to={`/pools/${pool.id}`}>{pool.name_label}</Link>
},
sortCriteria: (host, { pools }) => pools[host.$pool].name_label,
},
sortCriteria: (host, { pools }) => pools[host.$pool].name_label
}].concat(MISSING_PATCHES_COLUMNS)
].concat(MISSING_PATCHES_COLUMNS)
// Small component to homogenize Button usage in HostsPatchesTable
const ActionButton_ = ({ children, labelId, ...props }) =>
<ActionButton
{...props}
tooltip={_(labelId)}
>
const ActionButton_ = ({ children, labelId, ...props }) => (
<ActionButton {...props} tooltip={_(labelId)}>
{children}
</ActionButton>
)
// ===================================================================
@connectStore({
hostsById: createGetObjectsOfType('host').groupBy('id')
hostsById: createGetObjectsOfType('host').groupBy('id'),
})
class HostsPatchesTable extends Component {
constructor (props) {
@ -98,17 +96,19 @@ class HostsPatchesTable extends Component {
_subscribeMissingPatches = (hosts = this.props.hosts) => {
const { hostsById } = this.props
const unsubs = map(hosts, host => hostsById
? subscribeHostMissingPatches(
hostsById[host.id][0],
patches => this.setState({
missingPatches: {
...this.state.missingPatches,
[host.id]: patches.length
}
})
)
: noop
const unsubs = map(
hosts,
host =>
hostsById
? subscribeHostMissingPatches(hostsById[host.id][0], patches =>
this.setState({
missingPatches: {
...this.state.missingPatches,
[host.id]: patches.length,
},
})
)
: noop
)
if (this.unsubscribeMissingPatches !== undefined) {
@ -124,10 +124,7 @@ class HostsPatchesTable extends Component {
pools[host.$pool] = true
})
return Promise.all(map(
keys(pools),
installAllPatchesOnPool
))
return Promise.all(map(keys(pools), installAllPatchesOnPool))
}
componentDidMount () {
@ -153,7 +150,7 @@ class HostsPatchesTable extends Component {
container,
displayPools,
pools,
useTabButton
useTabButton,
} = this.props
const hosts = this._getHosts()
@ -161,25 +158,27 @@ class HostsPatchesTable extends Component {
const Container = container || 'div'
const Button = useTabButton
? TabButton
: ActionButton_
const Button = useTabButton ? TabButton : ActionButton_
return (
<div>
{!noPatches
? (
<SortedTable
collection={hosts}
columns={displayPools ? POOLS_MISSING_PATCHES_COLUMNS : MISSING_PATCHES_COLUMNS}
userData={{
installAllHostPatches,
missingPatches: this.state.missingPatches,
pools
}}
/>
) : <p>{_('patchNothing')}</p>
}
{!noPatches ? (
<SortedTable
collection={hosts}
columns={
displayPools
? POOLS_MISSING_PATCHES_COLUMNS
: MISSING_PATCHES_COLUMNS
}
userData={{
installAllHostPatches,
missingPatches: this.state.missingPatches,
pools,
}}
/>
) : (
<p>{_('patchNothing')}</p>
)}
<Portal container={() => buttonsGroupContainer()}>
<Container>
<Button
@ -202,7 +201,7 @@ class HostsPatchesTable extends Component {
const getPools = createGetObjectsOfType('pool')
return {
pools: getPools
pools: getPools,
}
})
class HostsPatchesTableByPool extends Component {
@ -220,10 +219,14 @@ export default propTypes({
displayPools: propTypes.bool,
hosts: propTypes.oneOfType([
propTypes.arrayOf(propTypes.object),
propTypes.objectOf(propTypes.object)
propTypes.objectOf(propTypes.object),
]).isRequired,
useTabButton: propTypes.bool
})(props => props.displayPools
? <HostsPatchesTableByPool {...props} />
: <HostsPatchesTable {...props} />
useTabButton: propTypes.bool,
})(
props =>
props.displayPools ? (
<HostsPatchesTableByPool {...props} />
) : (
<HostsPatchesTable {...props} />
)
)

View File

@ -19,9 +19,6 @@ propTypes(Icon)({
color: propTypes.string,
fixedWidth: propTypes.bool,
icon: propTypes.string,
size: propTypes.oneOfType([
propTypes.string,
propTypes.number
])
size: propTypes.oneOfType([propTypes.string, propTypes.number]),
})
export default Icon

View File

@ -1,18 +1,9 @@
import isFunction from 'lodash/isFunction'
import isString from 'lodash/isString'
import moment from 'moment'
import React, {
Component,
PropTypes
} from 'react'
import {
connect
} from 'react-redux'
import {
FormattedMessage,
IntlProvider as IntlProvider_
} from 'react-intl'
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { FormattedMessage, IntlProvider as IntlProvider_ } from 'react-intl'
import messages from './messages'
import locales from './locales'
@ -44,14 +35,17 @@ const getMessage = (props, messageId, values, render) => {
values = undefined
}
return <FormattedMessage {...props} {...message} values={values}>
{render}
</FormattedMessage>
return (
<FormattedMessage {...props} {...message} values={values}>
{render}
</FormattedMessage>
)
}
getMessage.keyValue = (key, value) => getMessage('keyValue', {
key: <strong>{key}</strong>,
value
})
getMessage.keyValue = (key, value) =>
getMessage('keyValue', {
key: <strong>{key}</strong>,
value,
})
export { getMessage as default }
@ -61,8 +55,8 @@ export { messages }
export class IntlProvider extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
lang: PropTypes.string.isRequired
};
lang: PropTypes.string.isRequired,
}
render () {
const { lang, children } = this.props
@ -71,23 +65,25 @@ export class IntlProvider extends Component {
// https://github.com/yahoo/react-intl/wiki/Components#dynamic-language-selection
//
// FIXME: remove the key prop when React context propagation is fixed (https://github.com/facebook/react/issues/2517)
return <IntlProvider_
key={lang}
locale={lang}
messages={locales[lang]}
>
{children}
</IntlProvider_>
return (
<IntlProvider_ key={lang} locale={lang} messages={locales[lang]}>
{children}
</IntlProvider_>
)
}
}
@connect(({ lang }) => ({ lang }))
export class FormattedDuration extends Component {
render () {
const {
duration,
lang
} = this.props
return <span>{moment.duration(duration).locale(lang).humanize()}</span>
const { duration, lang } = this.props
return (
<span>
{moment
.duration(duration)
.locale(lang)
.humanize()}
</span>
)
}
}

View File

@ -378,7 +378,8 @@ export default {
homeDisplayedItems: '{displayed, number}x {icon} (sobre {total, number})',
// Original text: "{selected, number}x {icon} selected (on {total, number})"
homeSelectedItems: '{selected, number}x {icon} seleccionados (sobre {total, number})',
homeSelectedItems:
'{selected, number}x {icon} seleccionados (sobre {total, number})',
// Original text: "More"
homeMore: 'Más',
@ -663,16 +664,19 @@ export default {
deleteBackupSchedule: 'Eliminar tarea de backup',
// Original text: "Are you sure you want to delete this backup job?"
deleteBackupScheduleQuestion: '¿Estás seguro de querer borrar esta tarea de backup?',
deleteBackupScheduleQuestion:
'¿Estás seguro de querer borrar esta tarea de backup?',
// Original text: "Enable immediately after creation"
scheduleEnableAfterCreation: 'Activar inmediatamente tras la creación',
// Original text: "You are editing Schedule {name} ({id}). Saving will override previous schedule state."
scheduleEditMessage: 'Estás editando la Programación {name} ({id}). Se sobreescribirá el estado actual al guardar.',
scheduleEditMessage:
'Estás editando la Programación {name} ({id}). Se sobreescribirá el estado actual al guardar.',
// Original text: "You are editing job {name} ({id}). Saving will override previous job state."
jobEditMessage: 'Estás editando la Tarea {name} ({id}). Se sobreescribirá el estado actual al guardar.',
jobEditMessage:
'Estás editando la Tarea {name} ({id}). Se sobreescribirá el estado actual al guardar.',
// Original text: "No scheduled jobs."
noScheduledJobs: 'No hay tareas programadas',
@ -1098,7 +1102,8 @@ export default {
purgePluginConfiguration: 'Purgar la configuración de plugins',
// Original text: "Are you sure you want to purge this configuration ?"
purgePluginConfigurationQuestion: '¿Estás seguro de querer purgar esta configuración?',
purgePluginConfigurationQuestion:
'¿Estás seguro de querer purgar esta configuración?',
// Original text: "Edit"
editPluginConfiguration: 'Editar',
@ -1110,7 +1115,8 @@ export default {
pluginConfigurationSuccess: 'Configuración del Plugin',
// Original text: "Plugin configuration successfully saved!"
pluginConfigurationChanges: '¡Configuración del Plugin guardada correctamente!',
pluginConfigurationChanges:
'¡Configuración del Plugin guardada correctamente!',
// Original text: 'Predefined configuration'
pluginConfigurationPresetTitle: undefined,
@ -2529,7 +2535,8 @@ export default {
deleteResourceSetWarning: 'Borrar conjunto de recursos',
// Original text: "Are you sure you want to delete this resource set?"
deleteResourceSetQuestion: '¿Estás seguro de querer borrar este conjunto de recursos?',
deleteResourceSetQuestion:
'¿Estás seguro de querer borrar este conjunto de recursos?',
// Original text: "Missing objects:"
resourceSetMissingObjects: 'Objetos perdidos:',
@ -2556,7 +2563,8 @@ export default {
noHostsAvailable: 'No hay hosts disponibles',
// Original text: "VMs created from this resource set shall run on the following hosts."
availableHostsDescription: 'Las VMs creadas con este conjunto de recursos correrán en los siguientes hosts.',
availableHostsDescription:
'Las VMs creadas con este conjunto de recursos correrán en los siguientes hosts.',
// Original text: "Maximum CPUs"
maxCpus: 'Máximas CPUs',
@ -2589,7 +2597,8 @@ export default {
resourceSetNew: undefined,
// Original text: "Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files."
importVmsList: 'Haz drag & drop de backups aquí, o haz click para seleccionar qué backups subir. Sólo se aceptan ficheros .xva',
importVmsList:
'Haz drag & drop de backups aquí, o haz click para seleccionar qué backups subir. Sólo se aceptan ficheros .xva',
// Original text: "No selected VMs."
noSelectedVms: 'No se han seleccionado VMs',
@ -2832,7 +2841,8 @@ export default {
blockedStartVmsModalMessage: undefined,
// Original text: "Are you sure you want to start {vms, number} VM{vms, plural, one {} other {s}}?"
startVmsModalMessage: '¿Estás seguro de querar arrancar {vms} VM{vms, plural, one {} other {s}}?',
startVmsModalMessage:
'¿Estás seguro de querar arrancar {vms} VM{vms, plural, one {} other {s}}?',
// Original text: '{nVms, number} vm{nVms, plural, one {} other {s}} are failed. Please see your logs to get more information'
failedVmsErrorMessage: undefined,
@ -2850,7 +2860,8 @@ export default {
stopVmsModalTitle: 'Parar VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to stop {vms, number} VM{vms, plural, one {} other {s}}?"
stopVmsModalMessage: '¿Estás seguro de querer parar {vms, number} VM{vms, plural, one {} other {s}}?',
stopVmsModalMessage:
'¿Estás seguro de querer parar {vms, number} VM{vms, plural, one {} other {s}}?',
// Original text: 'Restart VM'
restartVmModalTitle: undefined,
@ -2868,25 +2879,29 @@ export default {
restartVmsModalTitle: 'Reiniciar VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {vms, number} VM{vms, plural, one {} other {s}}?"
restartVmsModalMessage: '¿Estás seguro de querer reiniciar {vms, number} VM{vms, plural, one {} other {s}}?',
restartVmsModalMessage:
'¿Estás seguro de querer reiniciar {vms, number} VM{vms, plural, one {} other {s}}?',
// Original text: "Snapshot VM{vms, plural, one {} other {s}}"
snapshotVmsModalTitle: 'Snapshot VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to snapshot {vms, number} VM{vms, plural, one {} other {s}}?"
snapshotVmsModalMessage: '¿Estás seguro de querer hacer snapshot de {vms, number} VM{vms, plural, one {} other {s}}?',
snapshotVmsModalMessage:
'¿Estás seguro de querer hacer snapshot de {vms, number} VM{vms, plural, one {} other {s}}?',
// Original text: "Delete VM{vms, plural, one {} other {s}}"
deleteVmsModalTitle: 'Borrar VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to delete {vms, number} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED"
deleteVmsModalMessage: '¿Estás seguro de querar borrar {vms, number} VM{vms, plural, one {} other {s}}? TODOS SUS DISCOS SERAN ELIMINADOS',
deleteVmsModalMessage:
'¿Estás seguro de querar borrar {vms, number} VM{vms, plural, one {} other {s}}? TODOS SUS DISCOS SERAN ELIMINADOS',
// Original text: "Delete VM"
deleteVmModalTitle: 'Borrar VM',
// Original text: "Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED"
deleteVmModalMessage: '¿Estás seguro de querer borrar esta VM? TODOS SUS DISCOS SERAN ELIMINADOS',
deleteVmModalMessage:
'¿Estás seguro de querer borrar esta VM? TODOS SUS DISCOS SERAN ELIMINADOS',
// Original text: "Migrate VM"
migrateVmModalTitle: 'Migrar VM',
@ -2976,7 +2991,8 @@ export default {
importBackupModalSelectBackup: 'Elige el backup…',
// Original text: "Are you sure you want to remove all orphaned snapshot VDIs?"
removeAllOrphanedModalWarning: '¿Estás seguro de querer borrar todos los VDIs huérfanos?',
removeAllOrphanedModalWarning:
'¿Estás seguro de querer borrar todos los VDIs huérfanos?',
// Original text: "Remove all logs"
removeAllLogsModalTitle: 'Borrar todos los logs',
@ -2991,25 +3007,29 @@ export default {
existingSrModalTitle: 'Uso anterior del SR',
// Original text: "This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingSrModalText: 'Esta ruta ya ha sido utilizada anteriormente como Almacenamiento por un host XenServer. Todos los datos existentes se perderán si continuas con la creación del SR.',
existingSrModalText:
'Esta ruta ya ha sido utilizada anteriormente como Almacenamiento por un host XenServer. Todos los datos existentes se perderán si continuas con la creación del SR.',
// Original text: "Previous LUN Usage"
existingLunModalTitle: 'Uso anterior de la LUN',
// Original text: "This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingLunModalText: 'Esta LUN ya ha sido utilizada anteriormente como Almacenamiento por un host XenServer. Todos los datos existentes se perderán si continuas con la creación del SR.',
existingLunModalText:
'Esta LUN ya ha sido utilizada anteriormente como Almacenamiento por un host XenServer. Todos los datos existentes se perderán si continuas con la creación del SR.',
// Original text: "Replace current registration?"
alreadyRegisteredModal: '¿Reemplazar el registro actual?',
// Original text: "Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?"
alreadyRegisteredModalText: 'Tu XOA ya está registrado en {email}, ¿quieres olvidar y reemplazar este registro?',
alreadyRegisteredModalText:
'Tu XOA ya está registrado en {email}, ¿quieres olvidar y reemplazar este registro?',
// Original text: "Ready for trial?"
trialReadyModal: '¿Preparado para el periodo de prueba?',
// Original text: "During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!"
trialReadyModalText: 'Durante el periodo de prueba, XOA necesita conexión a Internet. Esta limitación no aplica a los planes de pago',
trialReadyModalText:
'Durante el periodo de prueba, XOA necesita conexión a Internet. Esta limitación no aplica a los planes de pago',
// Original text: 'Label'
serverLabel: undefined,
@ -3267,7 +3287,8 @@ export default {
tryIt: '¡Pruébalo gratis!',
// Original text: "This feature is available starting from {plan} Edition"
availableIn: 'Esta característica está sólo disponible a partir de la Edición {plan}',
availableIn:
'Esta característica está sólo disponible a partir de la Edición {plan}',
// Original text: 'This feature is not available in your version, contact your administrator to know more.'
notAvailable: undefined,
@ -3318,10 +3339,12 @@ export default {
noUpdaterCommunity: 'No hay actualizador para la Edición Community',
// Original text: "Please consider subscribe and try it with all features for free during 15 days on {link}.""
considerSubscribe: 'Por favor plantéate la suscripción y pruébala con todas las características gratis durante 15 días {link}',
considerSubscribe:
'Por favor plantéate la suscripción y pruébala con todas las características gratis durante 15 días {link}',
// Original text: "Manual update could break your current installation due to dependencies issues, do it with caution"
noUpdaterWarning: 'La actualización manual podría romper tu instalación actual debido a problemas de dependencias, hazlo con precaución',
noUpdaterWarning:
'La actualización manual podría romper tu instalación actual debido a problemas de dependencias, hazlo con precaución',
// Original text: "Current version:"
currentVersion: 'Versión actual',
@ -3333,19 +3356,23 @@ export default {
editRegistration: undefined,
// Original text: "Please, take time to register in order to enjoy your trial."
trialRegistration: 'Por favor, regístrate para poder disfrutar del periodo de prueba',
trialRegistration:
'Por favor, regístrate para poder disfrutar del periodo de prueba',
// Original text: "Start trial"
trialStartButton: 'Comenzar prueba',
// Original text: "You can use a trial version until {date, date, medium}. Upgrade your appliance to get it."
trialAvailableUntil: 'Puedes usar la versión de prueba hasta {date, date, medium}. Actualiza tu instalación para obtenerla.',
trialAvailableUntil:
'Puedes usar la versión de prueba hasta {date, date, medium}. Actualiza tu instalación para obtenerla.',
// Original text: "Your trial has been ended. Contact us or downgrade to Free version"
trialConsumed: 'Tu periodo de prueba ha terminado. Contacta con nosotros o vuelve a la Edición Libre',
trialConsumed:
'Tu periodo de prueba ha terminado. Contacta con nosotros o vuelve a la Edición Libre',
// Original text: "Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service."
trialLocked: 'Tu servicio xoa-updater parece estar caído. XOA no puede funcionar correctamente sin contactar con este servicio',
trialLocked:
'Tu servicio xoa-updater parece estar caído. XOA no puede funcionar correctamente sin contactar con este servicio',
// Original text: "No update information available"
noUpdateInfo: 'No hay información de actualización',
@ -3375,13 +3402,16 @@ export default {
disclaimerTitle: 'Xen Orchestra desde código fuente',
// Original text: "You are using XO from the sources! That's great for a personal/non-profit usage."
disclaimerText1: '¡Estás utilizando XO a partir del código fuente! Estupendo para un uso personal/sin ánimo de lucro',
disclaimerText1:
'¡Estás utilizando XO a partir del código fuente! Estupendo para un uso personal/sin ánimo de lucro',
// Original text: "If you are a company, it's better to use it with our appliance + pro support included:"
disclaimerText2: 'Si eres una empresa, es mejor utilizarlo con nuestro appliance con soporte Pro incluído',
disclaimerText2:
'Si eres una empresa, es mejor utilizarlo con nuestro appliance con soporte Pro incluído',
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3: 'Esta versión no está creada para recibir soporte ni actualizaciones. Úsala con precaución para tareas críticas.',
disclaimerText3:
'Esta versión no está creada para recibir soporte ni actualizaciones. Úsala con precaución para tareas críticas.',
// Original text: "Connect PIF"
connectPif: 'Conectar PIF',
@ -3804,5 +3834,5 @@ export default {
xosanNoPackFound: undefined,
// Original text: 'At least one of these version requirements must be satisfied by all the hosts in this pool:'
xosanPackRequirements: undefined
xosanPackRequirements: undefined,
}

View File

@ -249,7 +249,8 @@ export default {
homeWelcomeText: 'Ajouter vos serveurs ou pools XenServer',
// Original text: "Some XenServers have been registered but are not connected"
homeConnectServerText: "Des XenServers sont enregistrés mais aucun n'est connecté",
homeConnectServerText:
"Des XenServers sont enregistrés mais aucun n'est connecté",
// Original text: "Want some help?"
homeHelp: "Besoin d'aide ?",
@ -282,7 +283,8 @@ export default {
homeRestoreBackup: 'Restaurer une sauvegarde',
// Original text: "Restore a backup from a remote store"
homeRestoreBackupMessage: 'Restaurer une sauvegarde depuis un stockage distant',
homeRestoreBackupMessage:
'Restaurer une sauvegarde depuis un stockage distant',
// Original text: "This will create a new VM"
homeNewVmMessage: 'Cela va créer une nouvelle VM',
@ -381,7 +383,8 @@ export default {
homeDisplayedItems: '{displayed, number}x {icon} (sur {total, number})',
// Original text: "{selected, number}x {icon} selected (on {total, number})"
homeSelectedItems: '{selected, number}x {icon} sélectionné{selected, plural, one {} other {s}} (sur {total, number})',
homeSelectedItems:
'{selected, number}x {icon} sélectionné{selected, plural, one {} other {s}} (sur {total, number})',
// Original text: "More"
homeMore: 'Plus',
@ -411,7 +414,8 @@ export default {
sortedTableAllItemsSelected: 'Toutes sont sélectionnées',
// Original text: '{nFiltered, number} of {nTotal, number} items'
sortedTableNumberOfFilteredItems: '{nFiltered, number} entrées sur {nTotal, number}',
sortedTableNumberOfFilteredItems:
'{nFiltered, number} entrées sur {nTotal, number}',
// Original text: '{nTotal, number} items'
sortedTableNumberOfItems: '{nTotal, number} entrées',
@ -591,7 +595,8 @@ export default {
backupEditNotFoundTitle: "Impossible d'éditer la sauvegarde",
// Original text: "Missing required info for edition"
backupEditNotFoundMessage: "Il manque les informations nécessaires à l'édition",
backupEditNotFoundMessage:
"Il manque les informations nécessaires à l'édition",
// Original text: "Successful"
successfulJobCall: 'Réussi',
@ -666,7 +671,8 @@ export default {
runJob: 'Lancer le job',
// Original text: "One shot running started. See overview for logs."
runJobVerbose: "Une exécution a été lancée. Voir l'overview pour plus de détails.",
runJobVerbose:
"Une exécution a été lancée. Voir l'overview pour plus de détails.",
// Original text: "Started"
jobStarted: 'Démarré',
@ -681,16 +687,19 @@ export default {
deleteBackupSchedule: 'Supprimer ce job de sauvegarde',
// Original text: "Are you sure you want to delete this backup job?"
deleteBackupScheduleQuestion: 'Êtes-vous sûr de vouloir supprimer ce job de sauvegarde ?',
deleteBackupScheduleQuestion:
'Êtes-vous sûr de vouloir supprimer ce job de sauvegarde ?',
// Original text: "Enable immediately after creation"
scheduleEnableAfterCreation: 'Activer aussitôt après la création',
// Original text: "You are editing Schedule {name} ({id}). Saving will override previous schedule state."
scheduleEditMessage: "Vous êtes en train d'éditer {name} ({id}). Enregistrer écrasera l'état planifié précédent.",
scheduleEditMessage:
"Vous êtes en train d'éditer {name} ({id}). Enregistrer écrasera l'état planifié précédent.",
// Original text: "You are editing job {name} ({id}). Saving will override previous job state."
jobEditMessage: "Vous êtes en train d'éditer le job {name} ({id}). Enregistrer écrasera l'état du job précédent.",
jobEditMessage:
"Vous êtes en train d'éditer le job {name} ({id}). Enregistrer écrasera l'état du job précédent.",
// Original text: "No scheduled jobs."
noScheduledJobs: 'Pas de job planifié.',
@ -705,7 +714,8 @@ export default {
jobActionPlaceHolder: "Sélectionnez une commande de l'API xo-server",
// Original text: "Timeout (number of seconds after which a VM is considered failed)"
jobTimeoutPlaceHolder: 'Temporisation (nombre de secondes autorisé pour chaque VM)',
jobTimeoutPlaceHolder:
'Temporisation (nombre de secondes autorisé pour chaque VM)',
// Original text: "Schedules"
jobSchedules: 'Planning',
@ -744,10 +754,12 @@ export default {
localRemoteWarningTitle: 'Emplacement local sélectionné',
// Original text: "Warning: local remotes will use limited XOA disk space. Only for advanced users."
localRemoteWarningMessage: "Attention : utiliser un emplacement local limite l'espace pour XO. Restreignez ceci aux utilisateurs avancés.",
localRemoteWarningMessage:
"Attention : utiliser un emplacement local limite l'espace pour XO. Restreignez ceci aux utilisateurs avancés.",
// Original text: "Warning: this feature works only with XenServer 6.5 or newer."
backupVersionWarning: "Attention : cette fonctionnalité ne fonctionne qu'avec XenServer 6.5 et plus récent.",
backupVersionWarning:
"Attention : cette fonctionnalité ne fonctionne qu'avec XenServer 6.5 et plus récent.",
// Original text: "VMs"
editBackupVmsTitle: 'VMs',
@ -897,7 +909,8 @@ export default {
remoteNfsPlaceHolderPath: 'chemin/de/la/sauvegarde',
// Original text: "subfolder [path\\to\\backup]"
remoteSmbPlaceHolderRemotePath: 'sous-répertoire [chemin\\vers\\la\\sauvegarde]',
remoteSmbPlaceHolderRemotePath:
'sous-répertoire [chemin\\vers\\la\\sauvegarde]',
// Original text: "Username"
remoteSmbPlaceHolderUsername: "Nom d'utilisateur",
@ -1116,7 +1129,8 @@ export default {
purgePluginConfiguration: 'Purger la configuration du greffon',
// Original text: "Are you sure you want to purge this configuration ?"
purgePluginConfigurationQuestion: 'Êtes-vous sûr de vouloir purger la configuration de ce greffon ?',
purgePluginConfigurationQuestion:
'Êtes-vous sûr de vouloir purger la configuration de ce greffon ?',
// Original text: "Edit"
editPluginConfiguration: 'Éditer',
@ -1128,7 +1142,8 @@ export default {
pluginConfigurationSuccess: 'Configuration du greffon',
// Original text: "Plugin configuration successfully saved!"
pluginConfigurationChanges: 'La configuration du greffon a été sauvegardée avec succés !',
pluginConfigurationChanges:
'La configuration du greffon a été sauvegardée avec succés !',
// Original text: "Predefined configuration"
pluginConfigurationPresetTitle: 'Configuration pré-définie',
@ -1164,7 +1179,8 @@ export default {
removeUserFilterTitle: 'Supprimer un filtre personnalisé',
// Original text: "Are you sure you want to remove custom filter?"
removeUserFilterBody: 'Êtes-vous sûr de vouloir supprimer ce filtre personnalisé ?',
removeUserFilterBody:
'Êtes-vous sûr de vouloir supprimer ce filtre personnalisé ?',
// Original text: "Default filter"
defaultFilter: 'Filtre par défaut',
@ -1356,16 +1372,19 @@ export default {
addHostLabel: 'Ajouter un hôte',
// Original text: "This host needs to install {patches, number} patch{patches, plural, one {} other {es}} before it can be added to the pool. This operation may be long."
hostNeedsPatchUpdate: "Cet hôte a besoin d'installer {patches, number} patch{patches, plural, one {} other {es}} avant de pouvoir être ajouté au pool. Cette opération peut être longue.",
hostNeedsPatchUpdate:
"Cet hôte a besoin d'installer {patches, number} patch{patches, plural, one {} other {es}} avant de pouvoir être ajouté au pool. Cette opération peut être longue.",
// Original text: "This host cannot be added to the pool because it's missing some patches."
hostNeedsPatchUpdateNoInstall: 'Cette hôte ne peut pas être ajouté au pool car il lui manque des patches.',
hostNeedsPatchUpdateNoInstall:
'Cette hôte ne peut pas être ajouté au pool car il lui manque des patches.',
// Original text: "Adding host failed"
addHostErrorTitle: "L'ajout de l'hôte a échoué.",
// Original text: "Host patches could not be homogenized."
addHostNotHomogeneousErrorMessage: "Les patches de l'hôte n'ont pas pu être homogénéisés.",
addHostNotHomogeneousErrorMessage:
"Les patches de l'hôte n'ont pas pu être homogénéisés.",
// Original text: "Disconnect"
disconnectServer: 'Déconnecter',
@ -1395,13 +1414,15 @@ export default {
noHostsAvailableErrorTitle: "Erreur lors du redémarrage de l'hôte",
// Original text: "Some VMs cannot be migrated before restarting this host. Please try force reboot."
noHostsAvailableErrorMessage: "Certaines VMs ne peuvent pas être migrées avant le redémarrage de l'hôte. Essayez de forcer le redémarrage.",
noHostsAvailableErrorMessage:
"Certaines VMs ne peuvent pas être migrées avant le redémarrage de l'hôte. Essayez de forcer le redémarrage.",
// Original text: "Error while restarting hosts"
failHostBulkRestartTitle: 'Erreur lors du redémarrage des hôtes',
// Original text: "{failedHosts, number}/{totalHosts, number} host{failedHosts, plural, one {} other {s}} could not be restarted."
failHostBulkRestartMessage: "{failedHosts, number}/{totalHosts, number} {failedHosts, plural, one {hôte n'a pas pu être redémarré} other {n'ont pas pu être redémarrés}} ",
failHostBulkRestartMessage:
"{failedHosts, number}/{totalHosts, number} {failedHosts, plural, one {hôte n'a pas pu être redémarré} other {n'ont pas pu être redémarrés}} ",
// Original text: "Reboot to apply updates"
rebootUpdateHostLabel: 'Redémarrer pour appliquer les mises à jour',
@ -1485,7 +1506,8 @@ export default {
supplementalPackNew: 'Installer un nouveau pack supplémentaire',
// Original text: "Install supplemental pack on every host"
supplementalPackPoolNew: 'Installer un pack supplémentaire sur tous les hôtes',
supplementalPackPoolNew:
'Installer un pack supplémentaire sur tous les hôtes',
// Original text: "{name} (by {author})"
supplementalPackTitle: '{name} (par {author})',
@ -1494,19 +1516,22 @@ export default {
supplementalPackInstallStartedTitle: 'Installation démarrée',
// Original text: "Installing new supplemental pack…"
supplementalPackInstallStartedMessage: "Installation d'un nouveau pack supplémentaire",
supplementalPackInstallStartedMessage:
"Installation d'un nouveau pack supplémentaire",
// Original text: "Installation error"
supplementalPackInstallErrorTitle: "Erreur d'installation",
// Original text: "The installation of the supplemental pack failed."
supplementalPackInstallErrorMessage: "L'installation du pack supplémentaire a échoué.",
supplementalPackInstallErrorMessage:
"L'installation du pack supplémentaire a échoué.",
// Original text: "Installation success"
supplementalPackInstallSuccessTitle: "Succès de l'installation",
// Original text: "Supplemental pack successfully installed."
supplementalPackInstallSuccessMessage: 'Le pack supplémentaire a été installé avec succès.',
supplementalPackInstallSuccessMessage:
'Le pack supplémentaire a été installé avec succès.',
// Original text: "Add a network"
networkCreateButton: 'Ajouter un réseau',
@ -1665,7 +1690,8 @@ export default {
installPatchWarningTitle: 'Installation de patch non recommandée',
// Original text: "This will install a patch only on this host. This is NOT the recommended way: please go into the Pool patch view and follow instructions there. If you are sure about this, you can continue anyway"
installPatchWarningContent: "Installer un patch sur un hôte seul est déconseillé. Il est recommandé d'aller sur la page du pool et de faire l'installation sur tous les hôtes.",
installPatchWarningContent:
"Installer un patch sur un hôte seul est déconseillé. Il est recommandé d'aller sur la page du pool et de faire l'installation sur tous les hôtes.",
// Original text: "Go to pool"
installPatchWarningReject: 'Aller au pool',
@ -1998,7 +2024,8 @@ export default {
vifLockedNetwork: 'Réseau verrouillé',
// Original text: "Network locked and no IPs are allowed for this interface"
vifLockedNetworkNoIps: "Le réseau est verrouillé et aucune IP n'est autorisée sur cette interface",
vifLockedNetworkNoIps:
"Le réseau est verrouillé et aucune IP n'est autorisée sur cette interface",
// Original text: "Network not locked"
vifUnLockedNetwork: 'Réseau non verrouillé',
@ -2019,7 +2046,8 @@ export default {
snapshotCreateButton: 'Nouvel instantané',
// Original text: "Just click on the snapshot button to create one!"
tipCreateSnapshotLabel: "Cliquer simplement sur le bouton d'instantané pour en créer un !",
tipCreateSnapshotLabel:
"Cliquer simplement sur le bouton d'instantané pour en créer un !",
// Original text: "Revert VM to this snapshot"
revertSnapshot: 'Restaurer la MV à cet instantané',
@ -2157,7 +2185,8 @@ export default {
vmChooseCoresPerSocket: 'Comportement par défaut',
// Original text: "{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket"
vmCoresPerSocket: '{nSockets, number} socket{nSockets, plural, one {} other {s}} avec {nCores, number} cœur{nCores, plural, one {} other {s}} par socket',
vmCoresPerSocket:
'{nSockets, number} socket{nSockets, plural, one {} other {s}} avec {nCores, number} cœur{nCores, plural, one {} other {s}} par socket',
// Original text: "Incorrect cores per socket value"
vmCoresPerSocketIncorrectValue: 'Valeur incorrecte de cœurs par socket',
@ -2196,10 +2225,12 @@ export default {
templateDelete: 'Supprimer le template',
// Original text: "Delete VM template{templates, plural, one {} other {s}}"
templateDeleteModalTitle: 'Supprimer le(s) template{templates, plural, one {} other {s}} de VMs',
templateDeleteModalTitle:
'Supprimer le(s) template{templates, plural, one {} other {s}} de VMs',
// Original text: "Are you sure you want to delete {templates, plural, one {this} other {these}} template{templates, plural, one {} other {s}}?"
templateDeleteModalBody: 'Êtes-vous sûr de vouloir supprimer ce(s) template(s) ?',
templateDeleteModalBody:
'Êtes-vous sûr de vouloir supprimer ce(s) template(s) ?',
// Original text: "Pool{pools, plural, one {} other {s}}"
poolPanel: 'Pool{pools, plural, one {} other {s}}',
@ -2265,7 +2296,8 @@ export default {
srTopUsageStatePanel: "Top 5 d'utilisation des SRs (en %)",
// Original text: "{running, number} running ({halted, number} halted)"
vmsStates: '{running} allumée{halted, plural, one {} other {s}} ({halted} éteinte{halted, plural, one {} other {s}})',
vmsStates:
'{running} allumée{halted, plural, one {} other {s}} ({halted} éteinte{halted, plural, one {} other {s}})',
// Original text: "Clear selection"
dashboardStatsButtonRemoveAll: 'Vider la sélection',
@ -2550,7 +2582,8 @@ export default {
deleteResourceSetWarning: 'Supprimer le jeu de ressources',
// Original text: "Are you sure you want to delete this resource set?"
deleteResourceSetQuestion: 'Êtes-vous sûr de vouloir supprimer ce jeu de ressources ?',
deleteResourceSetQuestion:
'Êtes-vous sûr de vouloir supprimer ce jeu de ressources ?',
// Original text: "Missing objects:"
resourceSetMissingObjects: 'Objets manquants :',
@ -2577,7 +2610,8 @@ export default {
noHostsAvailable: "Pas d'hôte disponible.",
// Original text: "VMs created from this resource set shall run on the following hosts."
availableHostsDescription: 'Les VMs créées sur ce jeu de ressources doivent être démarrées sur les hôtes suivants.',
availableHostsDescription:
'Les VMs créées sur ce jeu de ressources doivent être démarrées sur les hôtes suivants.',
// Original text: "Maximum CPUs"
maxCpus: 'CPUs maximum',
@ -2610,7 +2644,8 @@ export default {
resourceSetNew: 'Nouvelle',
// Original text: "Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files."
importVmsList: 'Essayez de déposer des fichiers de VMs ici, ou bien cliquez pour sélectionner des VMs à téléverser. Seuls les fichiers .xva/.ova sont acceptés.',
importVmsList:
'Essayez de déposer des fichiers de VMs ici, ou bien cliquez pour sélectionner des VMs à téléverser. Seuls les fichiers .xva/.ova sont acceptés.',
// Original text: "No selected VMs."
noSelectedVms: 'Pas de VM sélectionnée.',
@ -2670,7 +2705,8 @@ export default {
vmImportFileType: '{type} fichier:',
// Original text: "Please to check and/or modify the VM configuration."
vmImportConfigAlert: 'Merci de vérifier et/ou modifier la configuration de la VM.',
vmImportConfigAlert:
'Merci de vérifier et/ou modifier la configuration de la VM.',
// Original text: "No pending tasks"
noTasks: 'Pas de tâche en attente',
@ -2697,10 +2733,12 @@ export default {
restoreBackups: 'Restauration de sauvegardes',
// Original text: "Click on a VM to display restore options"
restoreBackupsInfo: 'Cliquez sur une VM pour afficher les options de récupération',
restoreBackupsInfo:
'Cliquez sur une VM pour afficher les options de récupération',
// Original text: "Only the files of Delta Backup which are not on a SMB remote can be restored"
restoreDeltaBackupsInfo: 'Seuls les fichiers de Delta Backup qui ne sont pas sur un emplacement SMB peuvent être restaurés',
restoreDeltaBackupsInfo:
'Seuls les fichiers de Delta Backup qui ne sont pas sur un emplacement SMB peuvent être restaurés',
// Original text: "Enabled"
remoteEnabled: 'activé',
@ -2799,16 +2837,19 @@ export default {
restoreFilesUnselectAll: 'Déselectionner tous les fichiers',
// Original text: "Emergency shutdown Host{nHosts, plural, one {} other {s}}"
emergencyShutdownHostsModalTitle: "Extinction d'urgence {nHosts, plural, one {de l'hôte} other {des hôtes}}",
emergencyShutdownHostsModalTitle:
"Extinction d'urgence {nHosts, plural, one {de l'hôte} other {des hôtes}}",
// Original text: "Are you sure you want to shutdown {nHosts, number} Host{nHosts, plural, one {} other {s}}?"
emergencyShutdownHostsModalMessage: 'Êtes-vous sûr de vouloir arrêter {nHosts} hôte{nHosts, plural, one {} other {s}}?',
emergencyShutdownHostsModalMessage:
'Êtes-vous sûr de vouloir arrêter {nHosts} hôte{nHosts, plural, one {} other {s}}?',
// Original text: "Shutdown host"
stopHostModalTitle: "Arrêter l'hôte",
// Original text: "This will shutdown your host. Do you want to continue? If it's the pool master, your connection to the pool will be lost"
stopHostModalMessage: "Vous allez éteindre cet hôte. Voulez-vous continuer ? Si c'est le Maître du Pool, la connexion à tout le Pool sera perdue.",
stopHostModalMessage:
"Vous allez éteindre cet hôte. Voulez-vous continuer ? Si c'est le Maître du Pool, la connexion à tout le Pool sera perdue.",
// Original text: "Add host"
addHostModalTitle: 'Ajouter un hôte',
@ -2820,22 +2861,28 @@ export default {
restartHostModalTitle: "Redémarrer l'hôte",
// Original text: "This will restart your host. Do you want to continue?"
restartHostModalMessage: 'Votre hôte va devoir redémarrer. Voulez-vous continuer ?',
restartHostModalMessage:
'Votre hôte va devoir redémarrer. Voulez-vous continuer ?',
// Original text: "Restart Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}"
restartHostsAgentsModalTitle: "Redémarrer les agents {nHosts, plural, one {de l'hôte} other {des hôtes}}",
restartHostsAgentsModalTitle:
"Redémarrer les agents {nHosts, plural, one {de l'hôte} other {des hôtes}}",
// Original text: "Are you sure you want to restart {nHosts, number} Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}?"
restartHostsAgentsModalMessage: "Êtes-vous sûr de vouloir redémarrer les agents {nHosts, plural, one {de l'hôte} other {des hôtes}} ?",
restartHostsAgentsModalMessage:
"Êtes-vous sûr de vouloir redémarrer les agents {nHosts, plural, one {de l'hôte} other {des hôtes}} ?",
// Original text: "Restart Host{nHosts, plural, one {} other {s}}"
restartHostsModalTitle: "Redémarrer {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}}",
restartHostsModalTitle:
"Redémarrer {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}}",
// Original text: "Are you sure you want to restart {nHosts, number} Host{nHosts, plural, one {} other {s}}?"
restartHostsModalMessage: "Êtes-vous sûr de vouloir redémarrer {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}} ?",
restartHostsModalMessage:
"Êtes-vous sûr de vouloir redémarrer {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}} ?",
// Original text: "Start VM{vms, plural, one {} other {s}}"
startVmsModalTitle: 'Démarrer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}}',
startVmsModalTitle:
'Démarrer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}}',
// Original text: "Start a copy"
cloneAndStartVM: 'Démarrer une copie',
@ -2850,28 +2897,35 @@ export default {
blockedStartVmModalMessage: 'Le démarrage est bloqué pour cette VM.',
// Original text: "Forbidden operation start for {nVms, number} vm{nVms, plural, one {} other {s}}."
blockedStartVmsModalMessage: 'Démarrage non autorisé pour {nVms, number} VM{nVms, plural, one {} other {s}}',
blockedStartVmsModalMessage:
'Démarrage non autorisé pour {nVms, number} VM{nVms, plural, one {} other {s}}',
// Original text: "Are you sure you want to start {vms, number} VM{vms, plural, one {} other {s}}?"
startVmsModalMessage: 'Êtes-vous sûr de vouloir démarrer {vms, plural, one {la} other {les}} {vms} VM{vms, plural, one {} other {s}} ?',
startVmsModalMessage:
'Êtes-vous sûr de vouloir démarrer {vms, plural, one {la} other {les}} {vms} VM{vms, plural, one {} other {s}} ?',
// Original text: "{nVms, number} vm{nVms, plural, one {} other {s}} are failed. Please see your logs to get more information"
failedVmsErrorMessage: "{nVms, number} VM{nVms, plural, one {} other {s}} ont échoué. Veuillez consulter les journaux pour plus d'informations",
failedVmsErrorMessage:
"{nVms, number} VM{nVms, plural, one {} other {s}} ont échoué. Veuillez consulter les journaux pour plus d'informations",
// Original text: "Start failed"
failedVmsErrorTitle: 'Echec du démarrage',
// Original text: "Stop Host{nHosts, plural, one {} other {s}}"
stopHostsModalTitle: "Arrêter {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}}",
stopHostsModalTitle:
"Arrêter {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}}",
// Original text: "Are you sure you want to stop {nHosts, number} Host{nHosts, plural, one {} other {s}}?"
stopHostsModalMessage: "Êtes-vous sûr de vouloir arrêter {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}} ?",
stopHostsModalMessage:
"Êtes-vous sûr de vouloir arrêter {nHosts, plural, one {l'} other {les}} hôte{nHosts, plural, one {} other {s}} ?",
// Original text: "Stop VM{vms, plural, one {} other {s}}"
stopVmsModalTitle: 'Éteindre {vms, plural, one {cette} other {ces}} VM{vms, plural, one {} other {s}}',
stopVmsModalTitle:
'Éteindre {vms, plural, one {cette} other {ces}} VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to stop {vms, number} VM{vms, plural, one {} other {s}}?"
stopVmsModalMessage: 'Êtes-vous sûr de vouloir éteindre {vms, plural, one {cette} other {ces}} {vms} VM{vms, plural, one {} other {s}} ?',
stopVmsModalMessage:
'Êtes-vous sûr de vouloir éteindre {vms, plural, one {cette} other {ces}} {vms} VM{vms, plural, one {} other {s}} ?',
// Original text: "Restart VM"
restartVmModalTitle: 'Redémarrer la VM',
@ -2886,28 +2940,35 @@ export default {
stopVmModalMessage: 'Êtes-vous sûr de vouloir arrêter {name}?',
// Original text: "Restart VM{vms, plural, one {} other {s}}"
restartVmsModalTitle: 'Redémarrer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}}',
restartVmsModalTitle:
'Redémarrer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {vms, number} VM{vms, plural, one {} other {s}}?"
restartVmsModalMessage: 'Êtes-vous sûr de vouloir redémarrer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}} {vms} ?',
restartVmsModalMessage:
'Êtes-vous sûr de vouloir redémarrer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}} {vms} ?',
// Original text: "Snapshot VM{vms, plural, one {} other {s}}"
snapshotVmsModalTitle: 'Faire un instantané {vms, plural, one {de la} other {des}} VM{vms, plural, one {} other {s}}',
snapshotVmsModalTitle:
'Faire un instantané {vms, plural, one {de la} other {des}} VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to snapshot {vms, number} VM{vms, plural, one {} other {s}}?"
snapshotVmsModalMessage: 'Êtes-vous sûr de vouloir faire un instantané {vms, plural, one {de la VM} other {des {vms} VMs}} ?',
snapshotVmsModalMessage:
'Êtes-vous sûr de vouloir faire un instantané {vms, plural, one {de la VM} other {des {vms} VMs}} ?',
// Original text: "Delete VM{vms, plural, one {} other {s}}"
deleteVmsModalTitle: 'Supprimer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}}',
deleteVmsModalTitle:
'Supprimer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to delete {vms, number} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED"
deleteVmsModalMessage: 'Êtes-vous sûr de vouloir supprimer {vms, plural, one {la VM} other {les {vms} VMs}} ? TOUS LES DISQUES ASSOCIÉS SERONT SUPPRIMÉS',
deleteVmsModalMessage:
'Êtes-vous sûr de vouloir supprimer {vms, plural, one {la VM} other {les {vms} VMs}} ? TOUS LES DISQUES ASSOCIÉS SERONT SUPPRIMÉS',
// Original text: "Delete VM"
deleteVmModalTitle: 'Supprimer la VM',
// Original text: "Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED"
deleteVmModalMessage: 'Êtes-vous sûr de vouloir supprimer cette VM ? TOUS LES DISQUES DE LA VM SERONT SUPPRIMÉS DEFINITIVEMENT',
deleteVmModalMessage:
'Êtes-vous sûr de vouloir supprimer cette VM ? TOUS LES DISQUES DE LA VM SERONT SUPPRIMÉS DEFINITIVEMENT',
// Original text: "Migrate VM"
migrateVmModalTitle: 'Migrer la VM',
@ -2925,7 +2986,8 @@ export default {
migrateVmsSelectSr: 'Sélectionner un SR de destination :',
// Original text: "Select a destination SR for local disks:"
migrateVmsSelectSrIntraPool: 'Choisir un SR de destination pour les disques locaux :',
migrateVmsSelectSrIntraPool:
'Choisir un SR de destination pour les disques locaux :',
// Original text: "Select a network on which to connect each VIF:"
migrateVmsSelectNetwork: 'Choisir un réseau pour chaque VIF :',
@ -2943,13 +3005,15 @@ export default {
migrateVmNoTargetHost: "Pas d'hôte cible",
// Original text: "A target host is required to migrate a VM"
migrateVmNoTargetHostMessage: 'Un hôte cible est nécessaire pour migrer une VM',
migrateVmNoTargetHostMessage:
'Un hôte cible est nécessaire pour migrer une VM',
// Original text: "No default SR"
migrateVmNoDefaultSrError: 'Pas de SR par défaut',
// Original text: "Default SR not connected to host"
migrateVmNotConnectedDefaultSrError: "Le SR par défaut n'est pas connecté à l'hôte",
migrateVmNotConnectedDefaultSrError:
"Le SR par défaut n'est pas connecté à l'hôte",
// Original text: "For each VDI, select an SR:"
chooseSrForEachVdisModalSelectSr: 'Pour chaque VDI, sélectionner un SR :',
@ -2970,7 +3034,8 @@ export default {
deleteVdiModalTitle: 'Supprimer le VDI',
// Original text: "Are you sure you want to delete this disk? ALL DATA ON THIS DISK WILL BE LOST"
deleteVdiModalMessage: 'Êtes-vous sûr de vouloir supprimer ce disque ? TOUTES LES DONNÉES CONTENUES SERONT PERDUES IRRÉMÉDIABLEMENT',
deleteVdiModalMessage:
'Êtes-vous sûr de vouloir supprimer ce disque ? TOUTES LES DONNÉES CONTENUES SERONT PERDUES IRRÉMÉDIABLEMENT',
// Original text: "Revert your VM"
revertVmModalTitle: 'Restaurer la VM',
@ -2979,10 +3044,12 @@ export default {
deleteSnapshotModalTitle: "Supprimer l'instantané",
// Original text: "Are you sure you want to delete this snapshot?"
deleteSnapshotModalMessage: 'Êtes-vous sûr de vouloir supprimer cet instantané ?',
deleteSnapshotModalMessage:
'Êtes-vous sûr de vouloir supprimer cet instantané ?',
// Original text: "Are you sure you want to revert this VM to the snapshot state? This operation is irreversible."
revertVmModalMessage: "Êtes-vous sûr de vouloir restaurer cette VM à l'état de cet instantané ? Cette opération est irrévocable.",
revertVmModalMessage:
"Êtes-vous sûr de vouloir restaurer cette VM à l'état de cet instantané ? Cette opération est irrévocable.",
// Original text: "Snapshot before"
revertVmModalSnapshotBefore: 'Faire un instantané avant',
@ -2997,13 +3064,15 @@ export default {
importBackupModalSelectBackup: 'Sélectionnez votre sauvegarde…',
// Original text: "Are you sure you want to remove all orphaned snapshot VDIs?"
removeAllOrphanedModalWarning: 'Êtes-vous sûr de vouloir supprimer tous les instantanés de VDIs orphelins ?',
removeAllOrphanedModalWarning:
'Êtes-vous sûr de vouloir supprimer tous les instantanés de VDIs orphelins ?',
// Original text: "Remove all logs"
removeAllLogsModalTitle: 'Supprimer tous les journaux',
// Original text: "Are you sure you want to remove all logs?"
removeAllLogsModalWarning: 'Êtes-vous sûr de vouloir supprimer tous les journaux ?',
removeAllLogsModalWarning:
'Êtes-vous sûr de vouloir supprimer tous les journaux ?',
// Original text: "This operation is definitive."
definitiveMessageModal: 'Cette action est irréversible.',
@ -3012,25 +3081,29 @@ export default {
existingSrModalTitle: 'Emplacement utilisé',
// Original text: "This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingSrModalText: 'Cet emplacement avait été utilisé auparavant comme un Stockage par un hôte XenServer. Toutes les données présentes seront perdues si vous décidez de continuer la création du SR.',
existingSrModalText:
'Cet emplacement avait été utilisé auparavant comme un Stockage par un hôte XenServer. Toutes les données présentes seront perdues si vous décidez de continuer la création du SR.',
// Original text: "Previous LUN Usage"
existingLunModalTitle: 'LUN utilisé',
// Original text: "This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingLunModalText: 'Ce LUN avait été utilisé auparavant comme un Stockage par un hôte XenServer. Toutes les données présentes seront perdues si vous décidez de continuer la création du SR.',
existingLunModalText:
'Ce LUN avait été utilisé auparavant comme un Stockage par un hôte XenServer. Toutes les données présentes seront perdues si vous décidez de continuer la création du SR.',
// Original text: "Replace current registration?"
alreadyRegisteredModal: "Remplacer l'enregistrement actuel ?",
// Original text: "Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?"
alreadyRegisteredModalText: 'Votre instance XOA est déjà enregistrée pour {email}, voulez-vous remplacer cet enregistrement ?',
alreadyRegisteredModalText:
'Votre instance XOA est déjà enregistrée pour {email}, voulez-vous remplacer cet enregistrement ?',
// Original text: "Ready for trial?"
trialReadyModal: "Prêt pour l'essai ?",
// Original text: "During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!"
trialReadyModalText: "Durant la période de démonstration, XOA nécessite une connexion internet fonctionnelle. Cette limitation disparaît avec la souscription d'une de nos formules.",
trialReadyModalText:
"Durant la période de démonstration, XOA nécessite une connexion internet fonctionnelle. Cette limitation disparaît avec la souscription d'une de nos formules.",
// Original text: "Label"
serverLabel: 'Nom',
@ -3054,10 +3127,12 @@ export default {
serverUnauthorizedCertificates: 'Certificats non approuvés',
// Original text: "Allow Unauthorized Certificates"
serverAllowUnauthorizedCertificates: 'Autoriser les certificats non approuvés',
serverAllowUnauthorizedCertificates:
'Autoriser les certificats non approuvés',
// Original text: "Enable it if your certificate is rejected, but it's not recommended because your connection will not be secured."
serverUnauthorizedCertificatesInfo: "Activez ceci si votre certificat est rejeté, mais ce n'est pas recommandé car votre connexion ne sera pas sécurisée.",
serverUnauthorizedCertificatesInfo:
"Activez ceci si votre certificat est rejeté, mais ce n'est pas recommandé car votre connexion ne sera pas sécurisée.",
// Original text: "Disconnect server"
serverDisconnect: 'Déconnecter le serveur',
@ -3087,7 +3162,8 @@ export default {
serverStatus: 'Statut',
// Original text: "Connection failed. Click for more information."
serverConnectionFailed: "Echec de connexion. Cliquer pour plus d'informations.",
serverConnectionFailed:
"Echec de connexion. Cliquer pour plus d'informations.",
// Original text: "Connecting…"
serverConnecting: 'Connexion…',
@ -3108,7 +3184,8 @@ export default {
serverSelfSignedCertError: 'Certificat auto-signé rejeté',
// Original text: "Do you want to accept self-signed certificate for this server even though it would decrease security?"
serverSelfSignedCertQuestion: 'Voulez-vous accepter un certificat auto-signé pour ce serveur même si cela réduit la sécurité ?',
serverSelfSignedCertQuestion:
'Voulez-vous accepter un certificat auto-signé pour ce serveur même si cela réduit la sécurité ?',
// Original text: "Copy VM"
copyVm: 'Copier la VM',
@ -3144,7 +3221,8 @@ export default {
detachHostModalTitle: "Détacher l'hôte",
// Original text: "Are you sure you want to detach {host} from its pool? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND REBOOT THE HOST."
detachHostModalMessage: "Êtes-vous sûr de vouloir détacher {host} de son pool? CELA SUPPRIMERA TOUTES LES VMs DE SON STOCKAGE LOCAL ET REDÉMARRERA L'HÔTE.",
detachHostModalMessage:
"Êtes-vous sûr de vouloir détacher {host} de son pool? CELA SUPPRIMERA TOUTES LES VMs DE SON STOCKAGE LOCAL ET REDÉMARRERA L'HÔTE.",
// Original text: "Detach"
detachHost: 'Détacher',
@ -3153,7 +3231,8 @@ export default {
forgetHostModalTitle: "Oublier l'hôte",
// Original text: "Are you sure you want to forget {host} from its pool? Be sure this host can't be back online, or use detach instead."
forgetHostModalMessage: 'Êtes-vous sûr de vouloir oublier {host} de son pool ? Soyez certain que cet hôte ne peut pas être de retour en ligne ou utilisez "Détacher" à la place.',
forgetHostModalMessage:
'Êtes-vous sûr de vouloir oublier {host} de son pool ? Soyez certain que cet hôte ne peut pas être de retour en ligne ou utilisez "Détacher" à la place.',
// Original text: "Forget"
forgetHost: 'Oublier',
@ -3231,7 +3310,8 @@ export default {
noProductionUse: 'Utilisez en production à vos risques et périls',
// Original text: "You can download our turnkey appliance at {website}"
downloadXoaFromWebsite: 'Téléchargez notre édition clef en main sur {website}',
downloadXoaFromWebsite:
'Téléchargez notre édition clef en main sur {website}',
// Original text: "Bug Tracker"
bugTracker: 'Gestionnaire de tickets',
@ -3288,10 +3368,12 @@ export default {
tryIt: 'Essayez gratuitement !',
// Original text: "This feature is available starting from {plan} Edition"
availableIn: "Cette fonctionnalité est disponible à partir de l'édition {plan}",
availableIn:
"Cette fonctionnalité est disponible à partir de l'édition {plan}",
// Original text: "This feature is not available in your version, contact your administrator to know more."
notAvailable: "Cette fonctionnalité n'est pas disponible dans cette édtition. Pour plus d'informations, contactez votre administrateur.",
notAvailable:
"Cette fonctionnalité n'est pas disponible dans cette édtition. Pour plus d'informations, contactez votre administrateur.",
// Original text: "Updates"
updateTitle: 'Mise à jour',
@ -3339,10 +3421,12 @@ export default {
noUpdaterCommunity: 'Pas de mise à jour sur la version Communautaire',
// Original text: "Please consider subscribe and try it with all features for free during 15 days on {link}."
considerSubscribe: 'Envisagez de souscrire, et essayez toutes les fonctionnalités gratuitement pendant 15 jours.',
considerSubscribe:
'Envisagez de souscrire, et essayez toutes les fonctionnalités gratuitement pendant 15 jours.',
// Original text: "Manual update could break your current installation due to dependencies issues, do it with caution"
noUpdaterWarning: 'Une mise à jour manuelle pourrait corrompre votre installation actuelle à cause des dépendances, soyez prudent.',
noUpdaterWarning:
'Une mise à jour manuelle pourrait corrompre votre installation actuelle à cause des dépendances, soyez prudent.',
// Original text: "Current version:"
currentVersion: 'Version actuelle :',
@ -3354,31 +3438,37 @@ export default {
editRegistration: "Éditer l'enregistrement",
// Original text: "Please, take time to register in order to enjoy your trial."
trialRegistration: 'Merci de prendre le temps de vous enregistrer afin de profiter de votre essai.',
trialRegistration:
'Merci de prendre le temps de vous enregistrer afin de profiter de votre essai.',
// Original text: "Start trial"
trialStartButton: "Commencer la période d'essai",
// Original text: "You can use a trial version until {date, date, medium}. Upgrade your appliance to get it."
trialAvailableUntil: "Vous pouvez utiliser une version d'essai jusqu'au {date, date, medium}. Mettez à jour votre XOA pour en profiter.",
trialAvailableUntil:
"Vous pouvez utiliser une version d'essai jusqu'au {date, date, medium}. Mettez à jour votre XOA pour en profiter.",
// Original text: "Your trial has been ended. Contact us or downgrade to Free version"
trialConsumed: "Votre période d'essai est terminé. Contactez-nous, ou régressez sur l'édition gratuite.",
trialConsumed:
"Votre période d'essai est terminé. Contactez-nous, ou régressez sur l'édition gratuite.",
// Original text: "Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service."
trialLocked: 'Votre service xoa-update semble inaccessible. Votre XOA ne peut pas fonctionner pleinement si elle ne peut pas joindre ce service.',
trialLocked:
'Votre service xoa-update semble inaccessible. Votre XOA ne peut pas fonctionner pleinement si elle ne peut pas joindre ce service.',
// Original text: "No update information available"
noUpdateInfo: "Pas d'informations de mises à jour disponible",
// Original text: "Update information may be available"
waitingUpdateInfo: 'Des informations de mises à jour sont peut-être disponibles',
waitingUpdateInfo:
'Des informations de mises à jour sont peut-être disponibles',
// Original text: "Your XOA is up-to-date"
upToDate: 'Votre XOA est à jour',
// Original text: "You need to update your XOA (new version is available)"
mustUpgrade: 'Vous devez mettre à jour votre XOA (une nouvelle version est disponible)',
mustUpgrade:
'Vous devez mettre à jour votre XOA (une nouvelle version est disponible)',
// Original text: "Your XOA is not registered for updates"
registerNeeded: "Votre XOA n'est pas enregistrée pour les mises à jour",
@ -3390,19 +3480,23 @@ export default {
promptUpgradeReloadTitle: 'Mise à jour réussie',
// Original text: "Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?"
promptUpgradeReloadMessage: "Votre XOA à été mise à jour avec brio, et votre navigateur doit rafraîchir l'application pour en profiter. Voulez-vous rafraîchir dès maintenant ?",
promptUpgradeReloadMessage:
"Votre XOA à été mise à jour avec brio, et votre navigateur doit rafraîchir l'application pour en profiter. Voulez-vous rafraîchir dès maintenant ?",
// Original text: "Xen Orchestra from the sources"
disclaimerTitle: 'Xen Orchestra depuis les sources',
// Original text: "You are using XO from the sources! That's great for a personal/non-profit usage."
disclaimerText1: "Vous utilisez XO depuis les sources. C'est parfait pour un usage personnel ou non lucratif.",
disclaimerText1:
"Vous utilisez XO depuis les sources. C'est parfait pour un usage personnel ou non lucratif.",
// Original text: "If you are a company, it's better to use it with our appliance + pro support included:"
disclaimerText2: "Si vous êtes une entrerprise, il est préférable d'utiliser notre applicance qui inclut du support professionel.",
disclaimerText2:
"Si vous êtes une entrerprise, il est préférable d'utiliser notre applicance qui inclut du support professionel.",
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3: "Cette version n'est fournie avec aucun support ni aucune mise à jour. Soyez prudent en cas d'utilisation pour des tâches importantes.",
disclaimerText3:
"Cette version n'est fournie avec aucun support ni aucune mise à jour. Soyez prudent en cas d'utilisation pour des tâches importantes.",
// Original text: "Connect PIF"
connectPif: 'Connecter la PIF',
@ -3456,7 +3550,8 @@ export default {
confirmationPasswordError: 'Confirmation du nouveau mot de passe invalide',
// Original text: "Password does not match the confirm password."
confirmationPasswordErrorBody: 'Le mot de passe ne correspond pas à la confirmation du mot de passe.',
confirmationPasswordErrorBody:
'Le mot de passe ne correspond pas à la confirmation du mot de passe.',
// Original text: "Password changed"
pwdChangeSuccess: 'Mot de passe modifié',
@ -3468,7 +3563,8 @@ export default {
pwdChangeError: 'Mot de passe invalide',
// Original text: "The old password provided is incorrect. Your password has not been changed."
pwdChangeErrorBody: "L'ancien mot de passe n'est pas valide. Votre mot de passe n'a pas été changé.",
pwdChangeErrorBody:
"L'ancien mot de passe n'est pas valide. Votre mot de passe n'a pas été changé.",
// Original text: "OK"
changePasswordOk: 'OK',
@ -3504,7 +3600,8 @@ export default {
deleteSshKeyConfirm: 'Supprimer la clef SSH',
// Original text: "Are you sure you want to delete the SSH key {title}?"
deleteSshKeyConfirmMessage: 'Êtes-vous sûr de vouloir supprimer la clef SSH {title}?',
deleteSshKeyConfirmMessage:
'Êtes-vous sûr de vouloir supprimer la clef SSH {title}?',
// Original text: "Others"
others: 'Autres',
@ -3582,7 +3679,8 @@ export default {
ipsDeleteAllTitle: "Supprimer toutes les plages d'IPs",
// Original text: "Are you sure you want to delete all the IP pools?"
ipsDeleteAllMessage: "Êtes-vous sûr de vouloir supprimer toutes les plages d'IPs ?",
ipsDeleteAllMessage:
"Êtes-vous sûr de vouloir supprimer toutes les plages d'IPs ?",
// Original text: "VIFs"
ipsVifs: 'VIFs',
@ -3660,7 +3758,8 @@ export default {
noConfigFile: 'Pas de fichier de configuration sélectionné',
// Original text: "Try dropping a config file here, or click to select a config file to upload."
importTip: 'Essayez de déposer un fichier de configuration ou cliquez pour sélectionner un fichier de configuration à importer.',
importTip:
'Essayez de déposer un fichier de configuration ou cliquez pour sélectionner un fichier de configuration à importer.',
// Original text: "Config"
config: 'Configuration',
@ -3681,7 +3780,8 @@ export default {
downloadConfig: 'Télécharger la configuration actuelle',
// Original text: "No config import available for Community Edition"
noConfigImportCommunity: 'Import de configuration non disponible pour la Community Edition',
noConfigImportCommunity:
'Import de configuration non disponible pour la Community Edition',
// Original text: "Reconnect all hosts"
srReconnectAllModalTitle: 'Reconnecter tous les hôtes',
@ -3690,7 +3790,8 @@ export default {
srReconnectAllModalMessage: 'Ceci reconnectera ce SR à tous ses hôtes',
// Original text: "This will reconnect each selected SR to its host (local SR) or to every hosts of its pool (shared SR)."
srsReconnectAllModalMessage: 'Ceci reconnectera tous les SRs sélectionnés à son hôte (SR local) ou à tous les hôtes de son pool (SR partagé).',
srsReconnectAllModalMessage:
'Ceci reconnectera tous les SRs sélectionnés à son hôte (SR local) ou à tous les hôtes de son pool (SR partagé).',
// Original text: "Disconnect all hosts"
srDisconnectAllModalTitle: 'Déconnecter tous les hôtes',
@ -3699,7 +3800,8 @@ export default {
srDisconnectAllModalMessage: 'Ceci déconnectera ce SR de tous ses hôtes.',
// Original text: "This will disconnect each selected SR from its host (local SR) or from every hosts of its pool (shared SR)."
srsDisconnectAllModalMessage: 'Ceci déconnectera tous les SRs sélectionnés de leur hôte (SR local) ou de tous les hôtes de leur pool (SR partagé).',
srsDisconnectAllModalMessage:
'Ceci déconnectera tous les SRs sélectionnés de leur hôte (SR local) ou de tous les hôtes de leur pool (SR partagé).',
// Original text: "Forget SR"
srForgetModalTitle: 'Oublier le SR',
@ -3708,10 +3810,12 @@ export default {
srsForgetModalTitle: 'Oublier les SRs sélectionnés',
// Original text: "Are you sure you want to forget this SR? VDIs on this storage won't be removed."
srForgetModalMessage: 'Êtes-vous sûr de vouloir oublier ce SR ? Les VDIs de ce stockage ne seront pas supprimés.',
srForgetModalMessage:
'Êtes-vous sûr de vouloir oublier ce SR ? Les VDIs de ce stockage ne seront pas supprimés.',
// Original text: "Are you sure you want to forget all the selected SRs? VDIs on these storages won't be removed."
srsForgetModalMessage: 'Êtes-vous sûr de vouloir oublier tous les SRs sélectionnés ? Les VDIs sur ces stockages ne seront pas supprimés.',
srsForgetModalMessage:
'Êtes-vous sûr de vouloir oublier tous les SRs sélectionnés ? Les VDIs sur ces stockages ne seront pas supprimés.',
// Original text: "Disconnected"
srAllDisconnected: 'Déconnectés',
@ -3759,7 +3863,8 @@ export default {
xosanInstallIt: 'Installer maintenant !',
// Original text: "Some hosts need their toolstack to be restarted before you can create an XOSAN"
xosanNeedRestart: 'Certains hôtes ont besoin que leur toolstack soit redémarrée avant de pouvoir créer un XOSAN',
xosanNeedRestart:
'Certains hôtes ont besoin que leur toolstack soit redémarrée avant de pouvoir créer un XOSAN',
// Original text: "Restart toolstacks"
xosanRestartAgents: 'Redémarrer les toolstacks',
@ -3813,7 +3918,8 @@ export default {
xosanRegisterBeta: 'Inscrivez-vous pour la beta de XOSAN',
// Original text: "You have successfully registered for the XOSAN beta. Please wait until your request has been approved."
xosanSuccessfullyRegistered: 'Vous êtes inscrit pour la beta de XOSAN. Veuillez attendre que votre demande soit approuvée.',
xosanSuccessfullyRegistered:
'Vous êtes inscrit pour la beta de XOSAN. Veuillez attendre que votre demande soit approuvée.',
// Original text: "Install XOSAN pack on these hosts:"
xosanInstallPackOnHosts: 'Installer le pack XOSAN sur ces hôtes :',
@ -3822,8 +3928,10 @@ export default {
xosanInstallPack: 'Installer {pack} v{version} ?',
// Original text: "No compatible XOSAN pack found for your XenServer versions."
xosanNoPackFound: 'Pas de pack XOSAN compatible pour vos versions de XenServers.',
xosanNoPackFound:
'Pas de pack XOSAN compatible pour vos versions de XenServers.',
// Original text: "At least one of these version requirements must be satisfied by all the hosts in this pool:"
xosanPackRequirements: 'Au moins une de ces condtions de version doit être satisfaite par tous les hôtes de ce pool :'
xosanPackRequirements:
'Au moins une de ces condtions de version doit être satisfaite par tous les hôtes de ce pool :',
}

View File

@ -3132,5 +3132,5 @@ export default {
settingsAclsButtonTooltipSR: undefined,
// Original text: 'Network'
settingsAclsButtonTooltipnetwork: undefined
settingsAclsButtonTooltipnetwork: undefined,
}

View File

@ -231,7 +231,8 @@ export default {
homeWelcomeText: 'Hozzáadása your XenServer kiszolgálók or pools',
// Original text: "Some XenServers have been registered but are not connected"
homeConnectServerText: 'Some XenServers have been registered but are not Kapcsolódva',
homeConnectServerText:
'Some XenServers have been registered but are not Kapcsolódva',
// Original text: "Want some help?"
homeHelp: 'Segítségre van szüksége?',
@ -363,7 +364,8 @@ export default {
homeDisplayedItems: '{displayed, number}x {icon} (on {total, number})',
// Original text: "{selected, number}x {icon} selected (on {total, number})"
homeSelectedItems: '{selected, number}x {icon} kiválasztott (on {total, number})',
homeSelectedItems:
'{selected, number}x {icon} kiválasztott (on {total, number})',
// Original text: "More"
homeMore: 'Több',
@ -618,7 +620,8 @@ export default {
runJob: 'Feladat futtatása',
// Original text: "One shot running started. See overview for logs."
runJobVerbose: 'Sikeresen elindítva. A logokat kérjük mindneképp nézze meg az eredményekhez.',
runJobVerbose:
'Sikeresen elindítva. A logokat kérjük mindneképp nézze meg az eredményekhez.',
// Original text: "Started"
jobStarted: 'Elindítva',
@ -633,16 +636,19 @@ export default {
deleteBackupSchedule: 'Mentési feladat eltávolítása',
// Original text: "Are you sure you want to delete this backup job?"
deleteBackupScheduleQuestion: 'Biztos benne, hogy törli ezt a mentési feladatot?',
deleteBackupScheduleQuestion:
'Biztos benne, hogy törli ezt a mentési feladatot?',
// Original text: "Enable immediately after creation"
scheduleEnableAfterCreation: 'Létrehozás utáni bekapcsolás engedélyezése',
// Original text: "You are editing Schedule {name} ({id}). Saving will override previous schedule state."
scheduleEditMessage: 'A következő Időzítést szerkeszti: {név} ({id}). A mentés felülírja az előző állapotot.',
scheduleEditMessage:
'A következő Időzítést szerkeszti: {név} ({id}). A mentés felülírja az előző állapotot.',
// Original text: "You are editing job {name} ({id}). Saving will override previous job state."
jobEditMessage: 'A következő Feladatot szerkeszti: {név} ({id}). A mentés felülírja az előző állapotot.',
jobEditMessage:
'A következő Feladatot szerkeszti: {név} ({id}). A mentés felülírja az előző állapotot.',
// Original text: "No scheduled jobs."
noScheduledJobs: 'Nincsenek időzített feladatok.',
@ -672,7 +678,8 @@ export default {
jobUserNotFound: 'A feladat létrehozója már nem érhető el a rendszerben',
// Original text: "This backup's creator no longer exists"
backupUserNotFound: 'A mentési feladat létrehozója már nem érhető el a rendszerben',
backupUserNotFound:
'A mentési feladat létrehozója már nem érhető el a rendszerben',
// Original text: "Backup owner"
backupOwner: 'Mentés tulajdonosa',
@ -693,10 +700,12 @@ export default {
localRemoteWarningTitle: 'Lokális távoli kiválasztva',
// Original text: "Warning: local remotes will use limited XOA disk space. Only for advanced users."
localRemoteWarningMessage: 'Figyelmeztetés: lokális távoli mentés korlátozott rendszer helyet használ. Kizárólag haladó felhasználóknak ajánlott, ha biztos benne, hogy ez a szervere elérhetőségét nem befolyásolja!.',
localRemoteWarningMessage:
'Figyelmeztetés: lokális távoli mentés korlátozott rendszer helyet használ. Kizárólag haladó felhasználóknak ajánlott, ha biztos benne, hogy ez a szervere elérhetőségét nem befolyásolja!.',
// Original text: "Warning: this feature works only with XenServer 6.5 or newer."
backupVersionWarning: 'Figyelmeztetés: 6.5 vagy újabb Xen támogatás szükséges!',
backupVersionWarning:
'Figyelmeztetés: 6.5 vagy újabb Xen támogatás szükséges!',
// Original text: "VMs"
editBackupVmsTitle: 'VPS-ek',
@ -1329,16 +1338,19 @@ export default {
noHostsAvailableErrorTitle: 'Hiba a kiszolgáló újraindítása közben',
// Original text: "Some VMs cannot be migrated before restarting this host. Please try force reboot."
noHostsAvailableErrorMessage: 'Some VMs cannot be migrated before restarting this Host. Please try force Restart.',
noHostsAvailableErrorMessage:
'Some VMs cannot be migrated before restarting this Host. Please try force Restart.',
// Original text: "Error while restarting hosts"
failHostBulkRestartTitle: 'Hiba lépett fel a kiszolgálók újraindítása közben',
// Original text: "{failedHosts}/{totalHosts} host{failedHosts, plural, one {} other {s}} could not be restarted."
failHostBulkRestartMessage: '{failedhosts}/{totalHosts} Kiszolgáló újraindítása nem sikerült.',
failHostBulkRestartMessage:
'{failedhosts}/{totalHosts} Kiszolgáló újraindítása nem sikerült.',
// Original text: "Reboot to apply updates"
rebootUpdateHostLabel: 'A változtatások életbe lépéséhez újraindítás szükséges',
rebootUpdateHostLabel:
'A változtatások életbe lépéséhez újraindítás szükséges',
// Original text: "Emergency mode"
emergencyModeLabel: 'Vészhelyzet üzem',
@ -1434,13 +1446,15 @@ export default {
supplementalPackInstallErrorTitle: 'Installation error',
// Original text: "The installation of the supplemental pack failed."
supplementalPackInstallErrorMessage: 'The installation of the supplemental pack failed.',
supplementalPackInstallErrorMessage:
'The installation of the supplemental pack failed.',
// Original text: "Installation success"
supplementalPackInstallSuccessTitle: 'Installation success',
// Original text: "Supplemental pack successfully installed."
supplementalPackInstallSuccessMessage: 'Supplemental pack successfully installed.',
supplementalPackInstallSuccessMessage:
'Supplemental pack successfully installed.',
// Original text: "Add a network"
networkCreateButton: 'Add a Hálózat',
@ -1713,7 +1727,8 @@ export default {
tipLabel: 'Tip:',
// Original text: "Due to a XenServer issue, non-US keyboard layouts aren't well supported. Switch your own layout to US to workaround it."
tipConsoleLabel: 'Rendszerkompatibilitás miatt egyedül amerikai (US) billentyűzetkiosztás működik a legstabilabban, ennek használata javasolt.',
tipConsoleLabel:
'Rendszerkompatibilitás miatt egyedül amerikai (US) billentyűzetkiosztás működik a legstabilabban, ennek használata javasolt.',
// Original text: "Hide infos"
hideHeaderTooltip: 'Információk elrejtése',
@ -1902,7 +1917,8 @@ export default {
vifLockedNetwork: 'Hálózat zárolva',
// Original text: "Network locked and no IPs are allowed for this interface"
vifLockedNetworkNoIps: 'Hálózat zárolva és nincsenek engedélyezve IP címek ehhez az interfészhez',
vifLockedNetworkNoIps:
'Hálózat zárolva és nincsenek engedélyezve IP címek ehhez az interfészhez',
// Original text: "Network not locked"
vifUnLockedNetwork: 'Hálózat nincs zárolva',
@ -1923,7 +1939,8 @@ export default {
snapshotCreateButton: 'Új Pillanatkép',
// Original text: "Just click on the snapshot button to create one!"
tipCreateSnapshotLabel: 'Csak kattintson a Pillanatkép gombra új pillanatkép készítéséhez!',
tipCreateSnapshotLabel:
'Csak kattintson a Pillanatkép gombra új pillanatkép készítéséhez!',
// Original text: "Revert VM to this snapshot"
revertSnapshot: 'VPS visszaállítása erre a pillanatképre',
@ -2070,10 +2087,12 @@ export default {
templateDelete: 'Sablon törlése',
// Original text: "Delete VM template{templates, plural, one {} other {s}}"
templateDeleteModalTitle: 'VPS sablon{Templates, plural, one {} other {ok}} törlése',
templateDeleteModalTitle:
'VPS sablon{Templates, plural, one {} other {ok}} törlése',
// Original text: "Are you sure you want to delete {templates, plural, one {this} other {these}} template{templates, plural, one {} other {s}}?"
templateDeleteModalBody: 'Biztos benne, hogy törölni kívánja a kiválasztott {templates, plural, one {this} other {these}} sablon{Templates, plural, one {} other {oka}}t?',
templateDeleteModalBody:
'Biztos benne, hogy törölni kívánja a kiválasztott {templates, plural, one {this} other {these}} sablon{Templates, plural, one {} other {oka}}t?',
// Original text: "Pool{pools, plural, one {} other {s}}"
poolPanel: 'Pool{pools, plural, one {} other {ok}}',
@ -2169,7 +2188,8 @@ export default {
statsDashboardGenericErrorTitle: 'Statisztikák hiba',
// Original text: "There is no stats available for:"
statsDashboardGenericErrorMessage: 'Jelenleg nincs elérhető statisztika a következőhöz:',
statsDashboardGenericErrorMessage:
'Jelenleg nincs elérhető statisztika a következőhöz:',
// Original text: "No selected metric"
noSelectedMetric: 'Nincs kiválasztott mérőszám',
@ -2235,7 +2255,8 @@ export default {
newVmCreateNewVmOn2: 'VPS létrehozása a következőn: {select1} vagy {select2}',
// Original text: "You have no permission to create a VM"
newVmCreateNewVmNoPermission: 'Sajnáljuk, nincs jogosultsága új VPS készítéséhez',
newVmCreateNewVmNoPermission:
'Sajnáljuk, nincs jogosultsága új VPS készítéséhez',
// Original text: "Infos"
newVmInfoPanel: 'Információk',
@ -2445,7 +2466,8 @@ export default {
noHostsAvailable: 'Nincs elérhető kiszolgáló.',
// Original text: "VMs created from this resource set shall run on the following hosts."
availableHostsDescription: 'Ezzel az erőforrás készlettel létrehozott VPS-ek a következő kiszolgálókon tudnak futni.',
availableHostsDescription:
'Ezzel az erőforrás készlettel létrehozott VPS-ek a következő kiszolgálókon tudnak futni.',
// Original text: "Maximum CPUs"
maxCpus: 'Maximum CPU',
@ -2478,7 +2500,8 @@ export default {
resourceSetNew: 'Új',
// Original text: "Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files."
importVmsList: 'Húzza ide a VPS fájlokat, vagy kattintson a VPS választásra a feltöltésre. Csak .xva/.ova fájlok támogatottak.',
importVmsList:
'Húzza ide a VPS fájlokat, vagy kattintson a VPS választásra a feltöltésre. Csak .xva/.ova fájlok támogatottak.',
// Original text: "No selected VMs."
noSelectedVms: 'Nincs kiválasztott VPS.',
@ -2565,7 +2588,8 @@ export default {
restoreBackups: 'Adatmentések Visszaállítása',
// Original text: "Click on a VM to display restore options"
restoreBackupsInfo: 'Kattintson egy VPS-re a visszaállítási lehetőségek megtekintéséhez',
restoreBackupsInfo:
'Kattintson egy VPS-re a visszaállítási lehetőségek megtekintéséhez',
// Original text: "Enabled"
remoteEnabled: 'Bekapcsolva',
@ -2655,25 +2679,29 @@ export default {
emergencyShutdownHostsModalTitle: 'Vészhelyzet Kiszolgáló Lekapcsolás',
// Original text: "Are you sure you want to shutdown {nHosts} Host{nHosts, plural, one {} other {s}}?"
emergencyShutdownHostsModalMessage: 'Biztos benne, hogy lekapcsolja ezeket a kiszolgálókat?',
emergencyShutdownHostsModalMessage:
'Biztos benne, hogy lekapcsolja ezeket a kiszolgálókat?',
// Original text: "Shutdown host"
stopHostModalTitle: 'Kiszolgáló Leállítása',
// Original text: "This will shutdown your host. Do you want to continue? If it's the pool master, your connection to the pool will be lost"
stopHostModalMessage: 'Ezzel le fogja kapcsolni a Kiszolgálót. Biztos benne? Amennyiben ez a pool master a kapcsolatot el fogja veszíteni!',
stopHostModalMessage:
'Ezzel le fogja kapcsolni a Kiszolgálót. Biztos benne? Amennyiben ez a pool master a kapcsolatot el fogja veszíteni!',
// Original text: "Add host"
addHostModalTitle: 'Kiszolgáló Hozzáadása',
// Original text: "Are you sure you want to add {host} to {pool}?"
addHostModalMessage: 'Biztos benne, hogy hozzádja a(z) {Host} kiszolgálót a következő poolhoz: {pool}?',
addHostModalMessage:
'Biztos benne, hogy hozzádja a(z) {Host} kiszolgálót a következő poolhoz: {pool}?',
// Original text: "Restart host"
restartHostModalTitle: 'Kiszolgáló Újraindítása',
// Original text: "This will restart your host. Do you want to continue?"
restartHostModalMessage: 'Ez újra fogja indítani a Kiszolgálót. Biztosan folytatja?',
restartHostModalMessage:
'Ez újra fogja indítani a Kiszolgálót. Biztosan folytatja?',
// Original text: "Restart Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}"
restartHostsAgentsModalTitle: 'Kiszolgáló(k) Újraindítása',
@ -2697,7 +2725,8 @@ export default {
stopHostsModalTitle: 'Kiszolgáló Leállítása',
// Original text: "Are you sure you want to stop {nHosts} Host{nHosts, plural, one {} other {s}}?"
stopHostsModalMessage: 'Biztos benne, hogy leállítja? Ha ez a master, a kapcsolat elveszhet!',
stopHostsModalMessage:
'Biztos benne, hogy leállítja? Ha ez a master, a kapcsolat elveszhet!',
// Original text: "Stop VM{vms, plural, one {} other {s}}"
stopVmsModalTitle: 'VPS Leállítás',
@ -2733,13 +2762,15 @@ export default {
deleteVmsModalTitle: 'VPS Törlés',
// Original text: "Are you sure you want to delete {vms} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED"
deleteVmsModalMessage: 'Biztos benne, hogy törli a VPS-t? ÖSSZES VPS DISZK ELTÁVOLÍTÁSRA KERÜL!',
deleteVmsModalMessage:
'Biztos benne, hogy törli a VPS-t? ÖSSZES VPS DISZK ELTÁVOLÍTÁSRA KERÜL!',
// Original text: "Delete VM"
deleteVmModalTitle: 'VPS Törlés',
// Original text: "Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED"
deleteVmModalMessage: 'Biztos benne, hogy törli a VPS-t? ÖSSZES VPS DISZK ELTÁVOLÍTÁSRA KERÜL!',
deleteVmModalMessage:
'Biztos benne, hogy törli a VPS-t? ÖSSZES VPS DISZK ELTÁVOLÍTÁSRA KERÜL!',
// Original text: "Migrate VM"
migrateVmModalTitle: 'VPS Migrálása',
@ -2760,10 +2791,12 @@ export default {
migrateVmsSelectSr: 'Válasszon cél Adattárolót:',
// Original text: "Select a destination SR for local disks:"
migrateVmsSelectSrIntraPool: 'Válasszon egy cél Adattárolót a helyi diszkek számára:',
migrateVmsSelectSrIntraPool:
'Válasszon egy cél Adattárolót a helyi diszkek számára:',
// Original text: "Select a network on which to connect each VIF:"
migrateVmsSelectNetwork: 'Válasszon egy Hálózatot amelyekhez csatlakoztasson minden VIF-et:',
migrateVmsSelectNetwork:
'Válasszon egy Hálózatot amelyekhez csatlakoztasson minden VIF-et:',
// Original text: "Smart mapping"
migrateVmsSmartMapping: 'Okos feltérképezés',
@ -2784,13 +2817,15 @@ export default {
migrateVmNoTargetHost: 'Nincs cél Kiszolgáló',
// Original text: "A target host is required to migrate a VM"
migrateVmNoTargetHostMessage: 'Egy cél Kiszolgáló szükséges a VPS migráláshoz!',
migrateVmNoTargetHostMessage:
'Egy cél Kiszolgáló szükséges a VPS migráláshoz!',
// Original text: "Delete VDI"
deleteVdiModalTitle: 'VDI Törlése',
// Original text: "Are you sure you want to delete this disk? ALL DATA ON THIS DISK WILL BE LOST"
deleteVdiModalMessage: 'Biztos benne, hogy törli a VPS diszkjét? ÖSSZES ADAT ELTÁVOLÍTÁSRA KERÜL!',
deleteVdiModalMessage:
'Biztos benne, hogy törli a VPS diszkjét? ÖSSZES ADAT ELTÁVOLÍTÁSRA KERÜL!',
// Original text: "Revert your VM"
revertVmModalTitle: 'VPS Visszaállítása',
@ -2799,10 +2834,12 @@ export default {
deleteSnapshotModalTitle: 'Pillanatkép Törlése',
// Original text: "Are you sure you want to delete this snapshot?"
deleteSnapshotModalMessage: 'Biztos benne, hogy törli a kiválasztott Pillanatképet?',
deleteSnapshotModalMessage:
'Biztos benne, hogy törli a kiválasztott Pillanatképet?',
// Original text: "Are you sure you want to revert this VM to the snapshot state? This operation is irreversible."
revertVmModalMessage: 'Biztos benne, hogy visszaállítja a VPS-t a kiválasztott Pillanatkép állapotra? A folyamat visszafordíthatatlan és minden adat elveszik ami a Pillanatkép készítése óta keletkezett!',
revertVmModalMessage:
'Biztos benne, hogy visszaállítja a VPS-t a kiválasztott Pillanatkép állapotra? A folyamat visszafordíthatatlan és minden adat elveszik ami a Pillanatkép készítése óta keletkezett!',
// Original text: "Snapshot before"
revertVmModalSnapshotBefore: 'Pillanatkép ezelőtt',
@ -2817,7 +2854,8 @@ export default {
importBackupModalSelectBackup: 'Válasszon mentést…',
// Original text: "Are you sure you want to remove all orphaned snapshot VDIs?"
removeAllOrphanedModalWarning: 'Biztos benne, hogy Eltávolítja az összes árvány hagyott Pillanatkép VDI-t?',
removeAllOrphanedModalWarning:
'Biztos benne, hogy Eltávolítja az összes árvány hagyott Pillanatkép VDI-t?',
// Original text: "Remove all logs"
removeAllLogsModalTitle: 'Összes Log Eltávolítása',
@ -2832,25 +2870,29 @@ export default {
existingSrModalTitle: 'Előző Adattároló használata',
// Original text: "This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingSrModalText: 'This path has been previously used as a Storage by a XenServer Host. All data will be lost if you choose to continue the Storage Creation.',
existingSrModalText:
'This path has been previously used as a Storage by a XenServer Host. All data will be lost if you choose to continue the Storage Creation.',
// Original text: "Previous LUN Usage"
existingLunModalTitle: 'Előző LUN használat',
// Original text: "This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingLunModalText: 'This LUN has been previously used as a Storage by a XenServer Host. All data will be lost if you choose to continue the Storage Creation.',
existingLunModalText:
'This LUN has been previously used as a Storage by a XenServer Host. All data will be lost if you choose to continue the Storage Creation.',
// Original text: "Replace current registration?"
alreadyRegisteredModal: 'Replace current registration?',
// Original text: "Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?"
alreadyRegisteredModalText: 'Your XO appliance is already registered to {email}, do you want to Elfelejt and replace this registration ?',
alreadyRegisteredModalText:
'Your XO appliance is already registered to {email}, do you want to Elfelejt and replace this registration ?',
// Original text: "Ready for trial?"
trialReadyModal: 'Ready for trial?',
// Original text: "During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!"
trialReadyModalText: 'During the trial period, XOA need to have a working internet Ceonnection This limitation does not apply for our paid plans!',
trialReadyModalText:
'During the trial period, XOA need to have a working internet Ceonnection This limitation does not apply for our paid plans!',
// Original text: "Host"
serverHost: 'Kiszolgáló',
@ -2913,7 +2955,8 @@ export default {
copyVm: 'VPS Másolás',
// Original text: "Are you sure you want to copy this VM to {SR}?"
copyVmConfirm: 'Biztos benne, hogy a VPS-t a következő Adattárolóra másolja? {Storage}?',
copyVmConfirm:
'Biztos benne, hogy a VPS-t a következő Adattárolóra másolja? {Storage}?',
// Original text: "Name"
copyVmName: 'Név',
@ -2943,7 +2986,8 @@ export default {
detachHostModalTitle: 'Detach Host',
// Original text: "Are you sure you want to detach {host} from its pool? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND REBOOT THE HOST."
detachHostModalMessage: 'Biztos benne?? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND RESTART THE HOST.',
detachHostModalMessage:
'Biztos benne?? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND RESTART THE HOST.',
// Original text: "Detach"
detachHost: 'Detach',
@ -3003,7 +3047,8 @@ export default {
addHostNoHost: 'Nincs Kiszolgáló',
// Original text: "No host selected to be added"
addHostNoHostMessage: 'Nincs Kiszolgáló kiválasztva amihez hozzá lehetne adni',
addHostNoHostMessage:
'Nincs Kiszolgáló kiválasztva amihez hozzá lehetne adni',
// Original text: "Xen Orchestra"
xenOrchestra: 'CLOUDXO',
@ -3081,7 +3126,8 @@ export default {
availableIn: 'This feature is available Starting from {plan} Edition',
// Original text: "This feature is not available in your version, contact your administrator to know more."
notAvailable: 'This feature is not available in your Version, contact your administrator to know more.',
notAvailable:
'This feature is not available in your Version, contact your administrator to know more.',
// Original text: "Updates"
updateTitle: 'UpDates',
@ -3129,10 +3175,12 @@ export default {
noUpdaterCommunity: 'No upDate available for Community Edition',
// Original text: "Please consider subscribe and try it with all features for free during 15 days on {link}."
considerSubscribe: 'Please consider subscribe and try it with all features for free during 15 days on {link}.',
considerSubscribe:
'Please consider subscribe and try it with all features for free during 15 days on {link}.',
// Original text: "Manual update could break your current installation due to dependencies issues, do it with caution"
noUpdaterWarning: 'Manual upDate could break your current installation due to dependencies issues, do it with caution',
noUpdaterWarning:
'Manual upDate could break your current installation due to dependencies issues, do it with caution',
// Original text: "Current version:"
currentVersion: 'Jelenlegi Verzió:',
@ -3144,19 +3192,23 @@ export default {
editRegistration: 'Szerkesztés registration',
// Original text: "Please, take time to register in order to enjoy your trial."
trialRegistration: 'Please, take time to register in order to enjoy your trial.',
trialRegistration:
'Please, take time to register in order to enjoy your trial.',
// Original text: "Start trial"
trialStartButton: 'Elindít trial',
// Original text: "You can use a trial version until {date, date, medium}. Upgrade your appliance to get it."
trialAvailableUntil: 'You can use a trial Verzió until {date, Dátum, medium}. Upgrade your appliance to get it.',
trialAvailableUntil:
'You can use a trial Verzió until {date, Dátum, medium}. Upgrade your appliance to get it.',
// Original text: "Your trial has been ended. Contact us or downgrade to Free version"
trialConsumed: 'Your trial has been ended. Contact us or downgrade to Free Verzió',
trialConsumed:
'Your trial has been ended. Contact us or downgrade to Free Verzió',
// Original text: "Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service."
trialLocked: 'Your xoa-upDátumr service appears to be down. Your XOA cannot run fully without reaching this service.',
trialLocked:
'Your xoa-upDátumr service appears to be down. Your XOA cannot run fully without reaching this service.',
// Original text: "No update information available"
noUpdateInfo: 'No upDátum information available',
@ -3180,19 +3232,23 @@ export default {
promptUpgradeReloadTitle: 'Upgrade successful',
// Original text: "Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?"
promptUpgradeReloadMessage: 'Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?',
promptUpgradeReloadMessage:
'Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?',
// Original text: "Xen Orchestra from the sources"
disclaimerTitle: 'Xen Orchestra from the sources',
// Original text: "You are using XO from the sources! That's great for a personal/non-profit usage."
disclaimerText1: "You are using XO from the sources! That's great for a personal/non-profit használat.",
disclaimerText1:
"You are using XO from the sources! That's great for a personal/non-profit használat.",
// Original text: "If you are a company, it's better to use it with our appliance + pro support included:"
disclaimerText2: "If you are a company, it's better to use it with our appliance + pro support included:",
disclaimerText2:
"If you are a company, it's better to use it with our appliance + pro support included:",
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3: 'This Verzió is not bundled with any support nor upDates. Use it with caution for critical tasks.',
disclaimerText3:
'This Verzió is not bundled with any support nor upDates. Use it with caution for critical tasks.',
// Original text: "Connect PIF"
connectPif: 'Csatlakozás PIF',
@ -3246,7 +3302,8 @@ export default {
pwdChangeError: 'Helytelen jelszó',
// Original text: "The old password provided is incorrect. Your password has not been changed."
pwdChangeErrorBody: 'A megadott régi jelszó helytelen, így a jelszó NEM lett megváltoztatva!',
pwdChangeErrorBody:
'A megadott régi jelszó helytelen, így a jelszó NEM lett megváltoztatva!',
// Original text: "OK"
changePasswordOk: 'OK',
@ -3282,7 +3339,8 @@ export default {
deleteSshKeyConfirm: 'SSH kulcs törlése',
// Original text: "Are you sure you want to delete the SSH key {title}?"
deleteSshKeyConfirmMessage: 'Biztos benne, hogy törli a(z) {title} SSH kulcsot?',
deleteSshKeyConfirmMessage:
'Biztos benne, hogy törli a(z) {title} SSH kulcsot?',
// Original text: "Others"
others: 'Egyebek',
@ -3435,7 +3493,8 @@ export default {
noConfigFile: 'Nincs kiválasztott konfigurációs fájl',
// Original text: "Try dropping a config file here, or click to select a config file to upload."
importTip: 'Try dropping a config file here, or click to choose a config file to upload.',
importTip:
'Try dropping a config file here, or click to choose a config file to upload.',
// Original text: "Config"
config: 'Konfiguráció',
@ -3456,25 +3515,30 @@ export default {
downloadConfig: 'Download current config',
// Original text: "No config import available for Community Edition"
noConfigImportCommunity: 'No config import available for Community Szerkesztésion',
noConfigImportCommunity:
'No config import available for Community Szerkesztésion',
// Original text: "Reconnect all hosts"
srReconnectAllModalTitle: 'Reconnect all hosts',
// Original text: "This will reconnect this SR to all its hosts."
srReconnectAllModalMessage: 'This will reconnecting this Storage to all its hosts.',
srReconnectAllModalMessage:
'This will reconnecting this Storage to all its hosts.',
// Original text: "This will reconnect each selected SR to its host (local SR) or to every hosts of its pool (shared SR)."
srsReconnectAllModalMessage: 'This will reconnecz each kiválasztott SR to its Kiszolgáló (local SR) or to every kiszolgálók of its pool (Megosztva Adattároló).',
srsReconnectAllModalMessage:
'This will reconnecz each kiválasztott SR to its Kiszolgáló (local SR) or to every kiszolgálók of its pool (Megosztva Adattároló).',
// Original text: "Disconnect all hosts"
srDisconnectAllModalTitle: 'Lecsatlakozás all kiszolgálók',
// Original text: "This will disconnect this SR from all its hosts."
srDisconnectAllModalMessage: 'This will Lecsatlakozás this Adattároló from all its kiszolgálók.',
srDisconnectAllModalMessage:
'This will Lecsatlakozás this Adattároló from all its kiszolgálók.',
// Original text: "This will disconnect each selected SR from its host (local SR) or from every hosts of its pool (shared SR)."
srsDisconnectAllModalMessage: 'This will Lecsatlakozás each kiválasztott SR from its Kiszolgáló (local SR) or from every kiszolgálók of its pool (Megosztva Adattároló).',
srsDisconnectAllModalMessage:
'This will Lecsatlakozás each kiválasztott SR from its Kiszolgáló (local SR) or from every kiszolgálók of its pool (Megosztva Adattároló).',
// Original text: "Forget SR"
srForgetModalTitle: 'Elfelejt Adattároló',
@ -3483,10 +3547,12 @@ export default {
srsForgetModalTitle: 'Elfelejt kiválasztott Adattárolók',
// Original text: "Are you sure you want to forget this SR? VDIs on this storage won't be removed."
srForgetModalMessage: "Biztos benne, hogyi to Elfelejt this Adattároló? VDIs on this storage won't be Eltávolításd.",
srForgetModalMessage:
"Biztos benne, hogyi to Elfelejt this Adattároló? VDIs on this storage won't be Eltávolításd.",
// Original text: "Are you sure you want to forget all the selected SRs? VDIs on these storages won't be removed."
srsForgetModalMessage: "Biztos benne, hogyi to Elfelejt all the kiválasztott Adattárolók? VDIs on these storages won't be Eltávolításd.",
srsForgetModalMessage:
"Biztos benne, hogyi to Elfelejt all the kiválasztott Adattárolók? VDIs on these storages won't be Eltávolításd.",
// Original text: "Disconnected"
srAllDisconnected: 'Lekapcsolódva',
@ -3588,5 +3654,5 @@ export default {
xosanInstallPackOnHosts: undefined,
// Original text: 'Install {pack} v{version}?'
xosanInstallPack: undefined
xosanInstallPack: undefined,
}

View File

@ -327,7 +327,8 @@ export default {
homeDisplayedItems: '{displayed, number}x {icon} (w {total, number})',
// Original text: "{selected, number}x {icon} selected (on {total, number})"
homeSelectedItems: '{selected, number}x {icon} wybrane {selected, plural, one {} other {s}} (w {total, number})',
homeSelectedItems:
'{selected, number}x {icon} wybrane {selected, plural, one {} other {s}} (w {total, number})',
// Original text: "More"
homeMore: 'Więcej',
@ -561,16 +562,19 @@ export default {
deleteBackupSchedule: 'Usuń zadanie kopii zapasowej',
// Original text: "Are you sure you want to delete this backup job?"
deleteBackupScheduleQuestion: 'Jesteś pewny że chcesz usunąć zadanie kopii zapasowej?',
deleteBackupScheduleQuestion:
'Jesteś pewny że chcesz usunąć zadanie kopii zapasowej?',
// Original text: "Enable immediately after creation"
scheduleEnableAfterCreation: 'Enable immediately after creation',
// Original text: "You are editing Schedule {name} ({id}). Saving will override previous schedule state."
scheduleEditMessage: 'Edytujesz harmonogram{name} ({id}). Zapisanie zastąpi poprzedni stan harmonogramu',
scheduleEditMessage:
'Edytujesz harmonogram{name} ({id}). Zapisanie zastąpi poprzedni stan harmonogramu',
// Original text: "You are editing job {name} ({id}). Saving will override previous job state."
jobEditMessage: 'Edytujesz zadanie {name} ({id}). Zapisanie zastąpi poprzednie zadanie',
jobEditMessage:
'Edytujesz zadanie {name} ({id}). Zapisanie zastąpi poprzednie zadanie',
// Original text: "No scheduled jobs."
noScheduledJobs: 'Brak zaplanowanych zadań',
@ -609,7 +613,8 @@ export default {
localRemoteWarningTitle: 'Local remote selected',
// Original text: "Warning: local remotes will use limited XOA disk space. Only for advanced users."
localRemoteWarningMessage: 'Warning: local remotes will use limited XOA disk space. Only for advanced users.',
localRemoteWarningMessage:
'Warning: local remotes will use limited XOA disk space. Only for advanced users.',
// Original text: "VMs"
editBackupVmsTitle: 'VMs',
@ -963,7 +968,8 @@ export default {
pluginConfigurationPresetTitle: 'Wstępnie zdefiniowana konfiguracja',
// Original text: "Choose a predefined configuration."
pluginConfigurationChoosePreset: 'Wybierz wstępnie zdefiniowaną konfigurację.',
pluginConfigurationChoosePreset:
'Wybierz wstępnie zdefiniowaną konfigurację.',
// Original text: "Apply"
applyPluginPreset: 'Akceptuj',
@ -1515,7 +1521,8 @@ export default {
tipLabel: 'Wskazówka:',
// Original text: "non-US keyboard could have issues with console: switch your own layout to US."
tipConsoleLabel: 'non-US keyboard could have issues with console: switch your own layout to US.',
tipConsoleLabel:
'non-US keyboard could have issues with console: switch your own layout to US.',
// Original text: "Hide infos"
hideHeaderTooltip: 'Ukryj informacje',
@ -1701,7 +1708,8 @@ export default {
vifLockedNetwork: 'Sieć zablokowana',
// Original text: "Network locked and no IPs are allowed for this interface"
vifLockedNetworkNoIps: 'Sieć zablokowana i żadne IPs nie są dopuszczone do tego interfejsu',
vifLockedNetworkNoIps:
'Sieć zablokowana i żadne IPs nie są dopuszczone do tego interfejsu',
// Original text: "Network not locked"
vifUnLockedNetwork: 'Sieć niezablokowana',
@ -1863,7 +1871,8 @@ export default {
templateDelete: 'Usuń szablon',
// Original text: "Delete VM template{templates, plural, one {} other {s}}"
templateDeleteModalTitle: 'Usuń szablon VM{templates, plural, one {} other {s}} de VMs',
templateDeleteModalTitle:
'Usuń szablon VM{templates, plural, one {} other {s}} de VMs',
// Original text: "Are you sure you want to delete {templates, plural, one {this} other {these}} template{templates, plural, one {} other {s}}?"
templateDeleteModalBody: 'Jesteś pewien że chcesz usunąć?',
@ -1932,7 +1941,8 @@ export default {
srTopUsageStatePanel: 'Top 5 SRs (w %)',
// Original text: "{running} running ({halted} halted)"
vmsStates: '{running} uruchomiona{halted, plural, one {} other {s}} ({halted} zatrzymana{halted, plural, one {} other {s}})',
vmsStates:
'{running} uruchomiona{halted, plural, one {} other {s}} ({halted} zatrzymana{halted, plural, one {} other {s}})',
// Original text: "Clear selection"
dashboardStatsButtonRemoveAll: 'Clear selection',
@ -2211,7 +2221,8 @@ export default {
deleteResourceSetWarning: 'Delete resource set',
// Original text: "Are you sure you want to delete this resource set?"
deleteResourceSetQuestion: 'Are you sure you want to delete this resource set?',
deleteResourceSetQuestion:
'Are you sure you want to delete this resource set?',
// Original text: "Missing objects:"
resourceSetMissingObjects: 'Brakujące obiekty:',
@ -2238,7 +2249,8 @@ export default {
noHostsAvailable: 'Brak dostępnych hostów.',
// Original text: "VMs created from this resource set shall run on the following hosts."
availableHostsDescription: 'VMs created from this resource set shall run on the following hosts.',
availableHostsDescription:
'VMs created from this resource set shall run on the following hosts.',
// Original text: "Maximum CPUs"
maxCpus: 'Maximum CPUs',
@ -2271,7 +2283,8 @@ export default {
resourceSetNew: 'Nowy',
// Original text: "Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files."
importVmsList: 'Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files.',
importVmsList:
'Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files.',
// Original text: "No selected VMs."
noSelectedVms: 'No selected VMs.',
@ -2400,16 +2413,19 @@ export default {
vmsToBackup: 'VMs do kopii zapasowej',
// Original text: "Emergency shutdown Host{nHosts, plural, one {} other {s}}"
emergencyShutdownHostsModalTitle: 'Wyłączenie awaryjne hosta {nHosts, plural, one {} other {s}}',
emergencyShutdownHostsModalTitle:
'Wyłączenie awaryjne hosta {nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to shutdown {nHosts} Host{nHosts, plural, one {} other {s}}?"
emergencyShutdownHostsModalMessage: 'Jesteś peweny że chcesz wyłączyć {nHosts} hosta{nHosts, plural, one {} other {s}}?',
emergencyShutdownHostsModalMessage:
'Jesteś peweny że chcesz wyłączyć {nHosts} hosta{nHosts, plural, one {} other {s}}?',
// Original text: "Shutdown host"
stopHostModalTitle: 'Wyłączenie hosta',
// Original text: "This will shutdown your host. Do you want to continue? If it's the pool master, your connection to the pool will be lost"
stopHostModalMessage: 'To wyłączy twojego hosta. Chcesz kontynuować? Jeżeli jest to zarządca puli, twoje połaczenie do puli zostanie utracone',
stopHostModalMessage:
'To wyłączy twojego hosta. Chcesz kontynuować? Jeżeli jest to zarządca puli, twoje połaczenie do puli zostanie utracone',
// Original text: "Add host"
addHostModalTitle: 'Dodaj hosta',
@ -2424,34 +2440,40 @@ export default {
restartHostModalMessage: 'To zrestartuje twojego hosta. Chcesz kontynuować?',
// Original text: "Restart Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}"
restartHostsAgentsModalTitle: 'Zrestartuj hosta{nHosts, plural, one {} other {s}}',
restartHostsAgentsModalTitle:
'Zrestartuj hosta{nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {nHosts} Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}?"
restartHostsAgentsModalMessage: "Êtes-vous sûr de vouloir redémarrer les agents {nHosts, plural, one {de l'hôte} other {des hôtes}} ?",
restartHostsAgentsModalMessage:
"Êtes-vous sûr de vouloir redémarrer les agents {nHosts, plural, one {de l'hôte} other {des hôtes}} ?",
// Original text: "Restart Host{nHosts, plural, one {} other {s}}"
restartHostsModalTitle: 'Restart hosta{nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {nHosts} Host{nHosts, plural, one {} other {s}}?"
restartHostsModalMessage: 'Czy na pewno chcesz zrestartować {nHosts} Host{nHosts, plural, one {} other {s}}?',
restartHostsModalMessage:
'Czy na pewno chcesz zrestartować {nHosts} Host{nHosts, plural, one {} other {s}}?',
// Original text: "Start VM{vms, plural, one {} other {s}}"
startVmsModalTitle: 'Uruchom VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to start {vms} 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} VM{vms, plural, one {} other {s}}?',
// Original text: "Stop Host{nHosts, plural, one {} other {s}}"
stopHostsModalTitle: 'Zatrzymaj hosta{nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to stop {nHosts} Host{nHosts, plural, one {} other {s}}?"
stopHostsModalMessage: 'Jesteś pewny że chcesz zatrzymać {nHosts} Host{nHosts, plural, one {} other {s}}?',
stopHostsModalMessage:
'Jesteś pewny że chcesz zatrzymać {nHosts} Host{nHosts, plural, one {} other {s}}?',
// Original text: "Stop VM{vms, plural, one {} other {s}}"
stopVmsModalTitle: 'Zatrzymaj VM {vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to stop {vms} VM{vms, plural, one {} other {s}}?"
stopVmsModalMessage: 'Jesteś pewien że chcesz zatrzymać {vms} VM{vms, plural, one {} other {s}}?',
stopVmsModalMessage:
'Jesteś pewien że chcesz zatrzymać {vms} VM{vms, plural, one {} other {s}}?',
// Original text: "Restart VM"
restartVmModalTitle: 'Restart VM',
@ -2469,25 +2491,29 @@ export default {
restartVmsModalTitle: 'Restart VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {vms} 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} VM{vms, plural, one {} other {s}}?',
// Original text: "Snapshot VM{vms, plural, one {} other {s}}"
snapshotVmsModalTitle: 'Snapshot VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to snapshot {vms} VM{vms, plural, one {} other {s}}?"
snapshotVmsModalMessage: 'Jesteś pewny że chcesz zrobić snapshot {vms} VM{vms, plural, one {} other {s}}?',
snapshotVmsModalMessage:
'Jesteś pewny że chcesz zrobić snapshot {vms} VM{vms, plural, one {} other {s}}?',
// Original text: "Delete VM{vms, plural, one {} other {s}}"
deleteVmsModalTitle: 'Delete VM{vms, plural, one {} other {s}}',
// Original text: "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} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED',
deleteVmsModalMessage:
'Are you sure you want to delete {vms} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED',
// Original text: "Delete VM"
deleteVmModalTitle: 'Usuń VM',
// Original text: "Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED"
deleteVmModalMessage: 'Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED',
deleteVmModalMessage:
'Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED',
// Original text: "Migrate VM"
migrateVmModalTitle: 'Migruj VM',
@ -2538,7 +2564,8 @@ export default {
deleteVdiModalTitle: 'Usuń VDI',
// Original text: "Are you sure you want to delete this disk? ALL DATA ON THIS DISK WILL BE LOST"
deleteVdiModalMessage: 'Jesteś pewien że chcesz usunąć dysk? Wszystkie dane na dysku zostaną utracone',
deleteVdiModalMessage:
'Jesteś pewien że chcesz usunąć dysk? Wszystkie dane na dysku zostaną utracone',
// Original text: "Revert your VM"
revertVmModalTitle: 'Revert your VM',
@ -2550,7 +2577,8 @@ export default {
deleteSnapshotModalMessage: 'Are you sure you want to delete this snapshot?',
// Original text: "Are you sure you want to revert this VM to the snapshot state? This operation is irreversible."
revertVmModalMessage: 'Are you sure you want to revert this VM to the snapshot state? This operation is irreversible.',
revertVmModalMessage:
'Are you sure you want to revert this VM to the snapshot state? This operation is irreversible.',
// Original text: "Snapshot before"
revertVmModalSnapshotBefore: 'Snapshot before',
@ -2565,7 +2593,8 @@ export default {
importBackupModalSelectBackup: 'Wybierz swój backup…',
// Original text: "Are you sure you want to remove all orphaned snapshot VDIs?"
removeAllOrphanedModalWarning: 'Are you sure you want to remove all orphaned snapshot VDIs?',
removeAllOrphanedModalWarning:
'Are you sure you want to remove all orphaned snapshot VDIs?',
// Original text: "Remove all logs"
removeAllLogsModalTitle: 'Usuń wszystkie logi',
@ -2580,25 +2609,29 @@ export default {
existingSrModalTitle: 'Previous SR Usage',
// Original text: "This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingSrModalText: 'This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
existingSrModalText:
'This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
// Original text: "Previous LUN Usage"
existingLunModalTitle: 'Previous LUN Usage',
// Original text: "This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingLunModalText: 'This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
existingLunModalText:
'This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
// Original text: "Replace current registration?"
alreadyRegisteredModal: 'Replace current registration?',
// Original text: "Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?"
alreadyRegisteredModalText: 'Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?',
alreadyRegisteredModalText:
'Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?',
// Original text: "Ready for trial?"
trialReadyModal: 'Ready for trial?',
// Original text: "During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!"
trialReadyModalText: 'During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!',
trialReadyModalText:
'During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!',
// Original text: "Host"
serverHost: 'Host',
@ -2664,7 +2697,8 @@ export default {
detachHostModalTitle: 'Detach host',
// Original text: "Are you sure you want to detach {host} from its pool? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND REBOOT THE HOST."
detachHostModalMessage: 'Are you sure you want to detach {host} from its pool? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND REBOOT THE HOST.',
detachHostModalMessage:
'Are you sure you want to detach {host} from its pool? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND REBOOT THE HOST.',
// Original text: "Detach"
detachHost: 'Detach',
@ -2802,7 +2836,8 @@ export default {
availableIn: 'This feature is available starting from {plan} Edition',
// Original text: "This feature is not available in your version, contact your administrator to know more."
notAvailable: 'This feature is not available in your version, contact your administrator to know more.',
notAvailable:
'This feature is not available in your version, contact your administrator to know more.',
// Original text: "Updates"
updateTitle: 'Aktualizuj',
@ -2850,10 +2885,12 @@ export default {
noUpdaterCommunity: 'No updater available for Community Edition',
// Original text: "Please consider subscribe and try it with all features for free during 15 days on"
considerSubscribe: 'Please consider subscribe and try it with all features for free during 15 days on',
considerSubscribe:
'Please consider subscribe and try it with all features for free during 15 days on',
// Original text: "Manual update could break your current installation due to dependencies issues, do it with caution"
noUpdaterWarning: 'Manual update could break your current installation due to dependencies issues, do it with caution',
noUpdaterWarning:
'Manual update could break your current installation due to dependencies issues, do it with caution',
// Original text: "Current version:"
currentVersion: 'Obecna wersja:',
@ -2865,19 +2902,23 @@ export default {
editRegistration: 'Edit registration',
// Original text: "Please, take time to register in order to enjoy your trial."
trialRegistration: 'Please, take time to register in order to enjoy your trial.',
trialRegistration:
'Please, take time to register in order to enjoy your trial.',
// Original text: "Start trial"
trialStartButton: 'Start trial',
// Original text: "You can use a trial version until {date, date, medium}. Upgrade your appliance to get it."
trialAvailableUntil: 'You can use a trial version until {date, date, medium}. Upgrade your appliance to get it.',
trialAvailableUntil:
'You can use a trial version until {date, date, medium}. Upgrade your appliance to get it.',
// Original text: "Your trial has been ended. Contact us or downgrade to Free version"
trialConsumed: 'Twoja wersja demonstracyjna właśnie się zakończyła. Skontaktuj się z nami żeby pobrać darmową wersję',
trialConsumed:
'Twoja wersja demonstracyjna właśnie się zakończyła. Skontaktuj się z nami żeby pobrać darmową wersję',
// Original text: "Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service."
trialLocked: 'Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service.',
trialLocked:
'Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service.',
// Original text: "No update information available"
noUpdateInfo: 'No update information available',
@ -2901,19 +2942,23 @@ export default {
promptUpgradeReloadTitle: 'Aktualizacja zakończona sukcesem',
// Original text: "Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?"
promptUpgradeReloadMessage: 'Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?',
promptUpgradeReloadMessage:
'Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?',
// Original text: "Xen Orchestra from the sources"
disclaimerTitle: 'Xen Orchestra z źródeł',
// Original text: "You are using XO from the sources! That's great for a personal/non-profit usage."
disclaimerText1: 'Używasz XO z źródeł!. To dobre rozwiązanie tylko do prywatnego/nieprodukcyjnego użytku',
disclaimerText1:
'Używasz XO z źródeł!. To dobre rozwiązanie tylko do prywatnego/nieprodukcyjnego użytku',
// Original text: "If you are a company, it's better to use it with our appliance + pro support included:"
disclaimerText2: "If you are a company, it's better to use it with our appliance + pro support included:",
disclaimerText2:
"If you are a company, it's better to use it with our appliance + pro support included:",
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3: 'This version is not bundled with any support nor updates. Use it with caution for critical tasks.',
disclaimerText3:
'This version is not bundled with any support nor updates. Use it with caution for critical tasks.',
// Original text: "Connect PIF"
connectPif: 'Connect PIF',
@ -2967,7 +3012,8 @@ export default {
pwdChangeError: 'Nieprawidłowe hasło',
// Original text: "The old password provided is incorrect. Your password has not been changed."
pwdChangeErrorBody: 'The old password provided is incorrect. Your password has not been changed.',
pwdChangeErrorBody:
'The old password provided is incorrect. Your password has not been changed.',
// Original text: "OK"
changePasswordOk: 'OK',
@ -3003,7 +3049,8 @@ export default {
deleteSshKeyConfirm: 'Usuń klucz SSH',
// Original text: "Are you sure you want to delete the SSH key {title}?"
deleteSshKeyConfirmMessage: 'Are you sure you want to delete the SSH key {title}?',
deleteSshKeyConfirmMessage:
'Are you sure you want to delete the SSH key {title}?',
// Original text: "Others"
others: 'Inne',
@ -3135,5 +3182,5 @@ export default {
settingsAclsButtonTooltipSR: 'SR',
// Original text: "Network"
settingsAclsButtonTooltipnetwork: 'Sieć'
settingsAclsButtonTooltipnetwork: 'Sieć',
}

View File

@ -543,7 +543,8 @@ export default {
runJob: 'Iniciar tarefa',
// Original text: "One shot running started. See overview for logs."
runJobVerbose: 'O backup manual foi executado. Clique em Visão Geral para ver os Logs',
runJobVerbose:
'O backup manual foi executado. Clique em Visão Geral para ver os Logs',
// Original text: "Started"
jobStarted: 'Iniciado',
@ -558,16 +559,19 @@ export default {
deleteBackupSchedule: 'Remover tarefa de backup',
// Original text: "Are you sure you want to delete this backup job?"
deleteBackupScheduleQuestion: 'Você tem certeza que você quer deletar esta tarefa de backup?',
deleteBackupScheduleQuestion:
'Você tem certeza que você quer deletar esta tarefa de backup?',
// Original text: "Enable immediately after creation"
scheduleEnableAfterCreation: 'Ativar imediatamente após criação',
// Original text: "You are editing Schedule {name} ({id}). Saving will override previous schedule state."
scheduleEditMessage: 'Você esta editando o Agendamento {name} ({id}). Este procedimento irá substituir o agendamento atual.',
scheduleEditMessage:
'Você esta editando o Agendamento {name} ({id}). Este procedimento irá substituir o agendamento atual.',
// Original text: "You are editing job {name} ({id}). Saving will override previous job state."
jobEditMessage: 'Você esta editando a Tarefa {name} ({id}). Este procedimento irá substituir a tarefa atual.',
jobEditMessage:
'Você esta editando a Tarefa {name} ({id}). Este procedimento irá substituir a tarefa atual.',
// Original text: "No scheduled jobs."
noScheduledJobs: 'Sem agendamentos',
@ -942,7 +946,8 @@ export default {
purgePluginConfiguration: 'Configuração de limpeza do plugin',
// Original text: "Are you sure you want to purge this configuration ?"
purgePluginConfigurationQuestion: 'Você tem certeza que deseja executar esta configuração?',
purgePluginConfigurationQuestion:
'Você tem certeza que deseja executar esta configuração?',
// Original text: "Edit"
editPluginConfiguration: 'Editar',
@ -954,7 +959,8 @@ export default {
pluginConfigurationSuccess: 'Configuração do Plugin',
// Original text: "Plugin configuration successfully saved!"
pluginConfigurationChanges: 'Configuração do plugin foi efetuada com sucesso!',
pluginConfigurationChanges:
'Configuração do plugin foi efetuada com sucesso!',
// Original text: 'Predefined configuration'
pluginConfigurationPresetTitle: undefined,
@ -1512,7 +1518,8 @@ export default {
tipLabel: 'Dica',
// Original text: "non-US keyboard could have issues with console: switch your own layout to US."
tipConsoleLabel: 'Teclados fora do padrão US-Keyboard podem apresentar problemas com o console: Altere seu teclado e verifique!',
tipConsoleLabel:
'Teclados fora do padrão US-Keyboard podem apresentar problemas com o console: Altere seu teclado e verifique!',
// Original text: 'Hide infos'
hideHeaderTooltip: undefined,
@ -1842,7 +1849,8 @@ export default {
vmHomeNamePlaceholder: 'Faça um longo clique para adicionar um nome',
// Original text: "Long click to add a description"
vmHomeDescriptionPlaceholder: 'Faça um longo clique para adicionar uma descrição',
vmHomeDescriptionPlaceholder:
'Faça um longo clique para adicionar uma descrição',
// Original text: "Click to add a name"
vmViewNamePlaceholder: 'Clique para adicionar um nome',
@ -2235,7 +2243,8 @@ export default {
noHostsAvailable: 'Sem hosts disponiveis',
// Original text: "VMs created from this resource set shall run on the following hosts."
availableHostsDescription: 'VMs criadas a partir desse conjunto de recursos deve ser executado nos hosts indicados.',
availableHostsDescription:
'VMs criadas a partir desse conjunto de recursos deve ser executado nos hosts indicados.',
// Original text: "Maximum CPUs"
maxCpus: 'Limite de CPUs',
@ -2268,7 +2277,8 @@ export default {
resourceSetNew: undefined,
// Original text: "Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files."
importVmsList: 'Tente soltar alguns backups aqui, ou clique para selecionar os backups para que seja feito o upload. Apenas arquivos .xva são aceitos.',
importVmsList:
'Tente soltar alguns backups aqui, ou clique para selecionar os backups para que seja feito o upload. Apenas arquivos .xva são aceitos.',
// Original text: "No selected VMs."
noSelectedVms: 'Nenhuma VM selecionada',
@ -2406,7 +2416,8 @@ export default {
stopHostModalTitle: 'Desligar host',
// Original text: "This will shutdown your host. Do you want to continue? If it's the pool master, your connection to the pool will be lost"
stopHostModalMessage: 'O host será desligado. Você tem certeza que deseja continuar?',
stopHostModalMessage:
'O host será desligado. Você tem certeza que deseja continuar?',
// Original text: 'Add host'
addHostModalTitle: undefined,
@ -2418,7 +2429,8 @@ export default {
restartHostModalTitle: 'Reiniciar host',
// Original text: "This will restart your host. Do you want to continue?"
restartHostModalMessage: 'O host será reiniciado. Você tem certeza que deseja continuar?',
restartHostModalMessage:
'O host será reiniciado. Você tem certeza que deseja continuar?',
// Original text: 'Restart Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}'
restartHostsAgentsModalTitle: undefined,
@ -2436,7 +2448,8 @@ export default {
startVmsModalTitle: 'Iniciar VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to start {vms} VM{vms, plural, one {} other {s}}?"
startVmsModalMessage: 'Você tem certeza que deseja iniciar {vms} VM{vms, plural, one {} other {s}}?',
startVmsModalMessage:
'Você tem certeza que deseja iniciar {vms} VM{vms, plural, one {} other {s}}?',
// Original text: 'Stop Host{nHosts, plural, one {} other {s}}'
stopHostsModalTitle: undefined,
@ -2448,7 +2461,8 @@ export default {
stopVmsModalTitle: 'Parar VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to stop {vms} VM{vms, plural, one {} other {s}}?"
stopVmsModalMessage: 'Você tem certeza que deseja parar {vms} VM{vms, plural, one {} other {s}}?',
stopVmsModalMessage:
'Você tem certeza que deseja parar {vms} VM{vms, plural, one {} other {s}}?',
// Original text: "Restart VM"
restartVmModalTitle: 'Reiniciar VM',
@ -2466,25 +2480,29 @@ export default {
restartVmsModalTitle: 'Reiniciar VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {vms} VM{vms, plural, one {} other {s}}?"
restartVmsModalMessage: 'Você tem certeza que deseja reiniciar {vms} VM{vms, plural, one {} other {s}}?',
restartVmsModalMessage:
'Você tem certeza que deseja reiniciar {vms} VM{vms, plural, one {} other {s}}?',
// Original text: "Snapshot VM{vms, plural, one {} other {s}}"
snapshotVmsModalTitle: 'Snapshot VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to snapshot {vms} VM{vms, plural, one {} other {s}}?"
snapshotVmsModalMessage: 'Você tem certeza que deseja executar snapshop para {vms} VM{vms, plural, one {} other {s}}?',
snapshotVmsModalMessage:
'Você tem certeza que deseja executar snapshop para {vms} VM{vms, plural, one {} other {s}}?',
// Original text: "Delete VM{vms, plural, one {} other {s}}"
deleteVmsModalTitle: 'Deletar VM{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to delete {vms} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED"
deleteVmsModalMessage: 'Você tem certeza que deseja deletar {vms} VM{vms, plural, one {} other {s}}? Todos os discos de VM serão removidos',
deleteVmsModalMessage:
'Você tem certeza que deseja deletar {vms} VM{vms, plural, one {} other {s}}? Todos os discos de VM serão removidos',
// Original text: "Delete VM"
deleteVmModalTitle: 'Deletar VM',
// Original text: "Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED"
deleteVmModalMessage: 'Você tem certeza que deseja deletar esta VM? Todos os discos de VM serão removidos',
deleteVmModalMessage:
'Você tem certeza que deseja deletar esta VM? Todos os discos de VM serão removidos',
// Original text: "Migrate VM"
migrateVmModalTitle: 'Migrar VM',
@ -2562,13 +2580,15 @@ export default {
importBackupModalSelectBackup: 'Selecionar backup…',
// Original text: "Are you sure you want to remove all orphaned snapshot VDIs?"
removeAllOrphanedModalWarning: 'Você tem certeza que deseja remover todos as VDIs orfãs?',
removeAllOrphanedModalWarning:
'Você tem certeza que deseja remover todos as VDIs orfãs?',
// Original text: "Remove all logs"
removeAllLogsModalTitle: 'Remover todos os logs',
// Original text: "Are you sure you want to remove all logs?"
removeAllLogsModalWarning: 'Você tem certeza que deseja remover todos os logs?',
removeAllLogsModalWarning:
'Você tem certeza que deseja remover todos os logs?',
// Original text: "This operation is definitive."
definitiveMessageModal: 'Esta operação é definitiva.',
@ -2577,25 +2597,29 @@ export default {
existingSrModalTitle: 'Uso anterior SR',
// Original text: "This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingSrModalText: 'Este caminho foi previamente utilizado como um dispositivo de armazenamento por um host XenServer. Todos os dados serão perdidos se você optar por continuar a criação do SR.',
existingSrModalText:
'Este caminho foi previamente utilizado como um dispositivo de armazenamento por um host XenServer. Todos os dados serão perdidos se você optar por continuar a criação do SR.',
// Original text: "Previous LUN Usage"
existingLunModalTitle: 'Uso anterior LUN',
// Original text: "This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingLunModalText: 'Este LUN foi previamente utilizado como um dispositivo de armazenamento por um host XenServer. Todos os dados serão perdidos se você optar por continuar a criação do SR.',
existingLunModalText:
'Este LUN foi previamente utilizado como um dispositivo de armazenamento por um host XenServer. Todos os dados serão perdidos se você optar por continuar a criação do SR.',
// Original text: "Replace current registration?"
alreadyRegisteredModal: 'Deseja substituir o registro atual?',
// Original text: "Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?"
alreadyRegisteredModalText: 'O seu XO appliance já foi registrado com o e-mail {email}, você tem certeza que gostaria de substituir este registro?',
alreadyRegisteredModalText:
'O seu XO appliance já foi registrado com o e-mail {email}, você tem certeza que gostaria de substituir este registro?',
// Original text: "Ready for trial?"
trialReadyModal: 'Pronto para iniciar o teste (trial)?',
// Original text: "During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!"
trialReadyModalText: 'Durante o período experimental, XOA precisa de uma conexão internet. Esta limitação não se aplica em nossos planos pagos!',
trialReadyModalText:
'Durante o período experimental, XOA precisa de uma conexão internet. Esta limitação não se aplica em nossos planos pagos!',
// Original text: "Host"
serverHost: 'Host',
@ -2844,13 +2868,16 @@ export default {
upgrade: 'Atualização (Upgrade)',
// Original text: "No updater available for Community Edition"
noUpdaterCommunity: 'Nenhuma atualização disponível para a versão Community Edition',
noUpdaterCommunity:
'Nenhuma atualização disponível para a versão Community Edition',
// Original text: "Please consider subscribe and try it with all features for free during 15 days on"
noUpdaterSubscribe: 'Oi, inscreva-se e venha testar todos nossos recursos e serviços gratuitamente por 15 dias!',
noUpdaterSubscribe:
'Oi, inscreva-se e venha testar todos nossos recursos e serviços gratuitamente por 15 dias!',
// Original text: "Manual update could break your current installation due to dependencies issues, do it with caution"
noUpdaterWarning: 'Atualização feita de forma manual pode corromper sua instalação atual devido a problema de dependências, tenha cuidado!',
noUpdaterWarning:
'Atualização feita de forma manual pode corromper sua instalação atual devido a problema de dependências, tenha cuidado!',
// Original text: "Current version:"
currentVersion: 'Versão atual:',
@ -2862,19 +2889,23 @@ export default {
editRegistration: undefined,
// Original text: "Please, take time to register in order to enjoy your trial."
trialRegistration: 'Por favor, tome seu tempo para se registrar a fim de desfrutar do seu período de teste (trial)',
trialRegistration:
'Por favor, tome seu tempo para se registrar a fim de desfrutar do seu período de teste (trial)',
// Original text: "Start trial"
trialStartButton: 'Iniciar teste (trial)',
// Original text: "You can use a trial version until {date, date, medium}. Upgrade your appliance to get it."
trialAvailableUntil: 'Sua versao de teste é válida até {date, date, medium}. Após esta data escolha um de nossos planos e continue a desfrutar de nosso software e serviços!',
trialAvailableUntil:
'Sua versao de teste é válida até {date, date, medium}. Após esta data escolha um de nossos planos e continue a desfrutar de nosso software e serviços!',
// Original text: "Your trial has been ended. Contact us or downgrade to Free version"
trialConsumed: 'Seu período de teste chegou ao fim. Entre em contato conosco ou faça o downgrade para a versão grátis',
trialConsumed:
'Seu período de teste chegou ao fim. Entre em contato conosco ou faça o downgrade para a versão grátis',
// Original text: "Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service."
trialLocked: 'Seu serviço de atualização XOA parece não funcionar. Seu XOA não pode funcionar corretamente sem este serviço.',
trialLocked:
'Seu serviço de atualização XOA parece não funcionar. Seu XOA não pode funcionar corretamente sem este serviço.',
// Original text: 'No update information available'
noUpdateInfo: undefined,
@ -2904,13 +2935,16 @@ export default {
disclaimerTitle: 'Xen Orchestra versão Open-Source',
// Original text: "You are using XO from the sources! That's great for a personal/non-profit usage."
disclaimerText1: 'Você está usando XO Open-Source! Isso é ótimo para um uso pessoal / sem fins lucrativos.',
disclaimerText1:
'Você está usando XO Open-Source! Isso é ótimo para um uso pessoal / sem fins lucrativos.',
// Original text: "If you are a company, it's better to use it with our appliance + pro support included:"
disclaimerText2: 'Se você é uma empresa, é melhor usá-lo com o nosso sistema appliance + suporte pro inclusos:',
disclaimerText2:
'Se você é uma empresa, é melhor usá-lo com o nosso sistema appliance + suporte pro inclusos:',
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3: 'Esta versão não está vinculada a qualquer tipo de suporte nem atualizações. Use-a com cuidado em se tratando de tarefas críticas.',
disclaimerText3:
'Esta versão não está vinculada a qualquer tipo de suporte nem atualizações. Use-a com cuidado em se tratando de tarefas críticas.',
// Original text: "Connect PIF"
connectPif: 'Conectar PIF',
@ -3132,5 +3166,5 @@ export default {
settingsAclsButtonTooltipSR: undefined,
// Original text: 'Network'
settingsAclsButtonTooltipnetwork: undefined
settingsAclsButtonTooltipnetwork: undefined,
}

View File

@ -1728,7 +1728,8 @@ export default {
usedResource: '已使用',
// Original text: "Try dropping some backups here, or click to select backups to upload. Accept only .xva files."
importVmsList: '尝试将备份文件拖拽到这里,或点击选择备份文件上传,仅支持.xva格式的文件',
importVmsList:
'尝试将备份文件拖拽到这里,或点击选择备份文件上传,仅支持.xva格式的文件',
// Original text: "No selected VMs."
noSelectedVms: '没有选择虚拟机',
@ -1812,10 +1813,12 @@ export default {
importBackupMessage: '开始你的备份导入',
// Original text: "Emergency shutdown Host{nHosts, plural, one {} other {s}}"
emergencyShutdownHostsModalTitle: '紧急关闭主机{nHosts, plural, one {} other {s}}',
emergencyShutdownHostsModalTitle:
'紧急关闭主机{nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to shutdown {nHosts} Host{nHosts, plural, one {} other {s}}?"
emergencyShutdownHostsModalMessage: '你确定要关闭 {nHosts} 主机{nHosts, plural, one {} other {s}}',
emergencyShutdownHostsModalMessage:
'你确定要关闭 {nHosts} 主机{nHosts, plural, one {} other {s}}',
// Original text: "Shutdown host"
stopHostModalTitle: '关闭主机',
@ -1830,34 +1833,40 @@ export default {
restartHostModalMessage: '此操作将重启你的主机,你确定要继续吗?',
// Original text: "Restart Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}"
restartHostsAgentsModalTitle: '重启主机{nHosts, plural, one {} other {s}} 代理{nHosts, plural, one {} other {s}}',
restartHostsAgentsModalTitle:
'重启主机{nHosts, plural, one {} other {s}} 代理{nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {nHosts} Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}?"
restartHostsAgentsModalMessage: '你确定要重启{nHosts}主机{nHosts, plural, one {} other {s}} 代理{nHosts, plural, one {} other {s}}',
restartHostsAgentsModalMessage:
'你确定要重启{nHosts}主机{nHosts, plural, one {} other {s}} 代理{nHosts, plural, one {} other {s}}',
// Original text: "Restart Host{nHosts, plural, one {} other {s}}"
restartHostsModalTitle: '重启主机{nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {nHosts} Host{nHosts, plural, one {} other {s}}?"
restartHostsModalMessage: '你确定要重启{nHosts}主机{nHosts, plural, one {} other {s}}',
restartHostsModalMessage:
'你确定要重启{nHosts}主机{nHosts, plural, one {} other {s}}',
// Original text: "Start VM{vms, plural, one {} other {s}}"
startVmsModalTitle: '启动虚拟机{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to start {vms} VM{vms, plural, one {} other {s}}?"
startVmsModalMessage: '你确定要启动 {vms} 虚拟机{vms, plural, one {} other {s}}',
startVmsModalMessage:
'你确定要启动 {vms} 虚拟机{vms, plural, one {} other {s}}',
// Original text: "Stop Host{nHosts, plural, one {} other {s}}"
stopHostsModalTitle: '停止主机{nHosts, plural, one {} other {s}}',
// Original text: "Are you sure you want to stop {nHosts} Host{nHosts, plural, one {} other {s}}?"
stopHostsModalMessage: '你确定要停止{nHosts}主机{nHosts, plural, one {} other {s}}',
stopHostsModalMessage:
'你确定要停止{nHosts}主机{nHosts, plural, one {} other {s}}',
// Original text: "Stop VM{vms, plural, one {} other {s}}"
stopVmsModalTitle: '停止虚拟机{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to stop {vms} VM{vms, plural, one {} other {s}}?"
stopVmsModalMessage: '你确定要停止{vms}虚拟机{vms, plural, one {} other {s}}',
stopVmsModalMessage:
'你确定要停止{vms}虚拟机{vms, plural, one {} other {s}}',
// Original text: "Restart VM"
restartVmModalTitle: '重新启动虚拟机',
@ -1875,13 +1884,15 @@ export default {
restartVmsModalTitle: '重新启动虚拟机{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to restart {vms} VM{vms, plural, one {} other {s}}?"
restartVmsModalMessage: '你确定要重新启动{vms}虚拟机{vms, plural, one {} other {s}}',
restartVmsModalMessage:
'你确定要重新启动{vms}虚拟机{vms, plural, one {} other {s}}',
// Original text: "Snapshot VM{vms, plural, one {} other {s}}"
snapshotVmsModalTitle: '执行虚拟机快照{vms, plural, one {} other {s}}',
// Original text: "Are you sure you want to snapshot {vms} VM{vms, plural, one {} other {s}}?"
snapshotVmsModalMessage: '你确定要执行虚拟机{vms}快照{vms, plural, one {} other {s}}',
snapshotVmsModalMessage:
'你确定要执行虚拟机{vms}快照{vms, plural, one {} other {s}}',
// Original text: "Delete VM"
deleteVmModalTitle: '删除虚拟机',
@ -1893,7 +1904,8 @@ export default {
deleteVmModalMessage: '你确定要删除此虚拟机?所有的虚拟机磁盘将被删除',
// Original text: "Are you sure you want to delete {vms} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED"
deleteVmsModalMessage: '你确定要删除 {vms}虚拟机{vms, plural, one {} other {s}}?所有的虚拟机磁盘将被删除',
deleteVmsModalMessage:
'你确定要删除 {vms}虚拟机{vms, plural, one {} other {s}}?所有的虚拟机磁盘将被删除',
// Original text: "Migrate VM"
migrateVmModalTitle: '迁移虚拟机',
@ -1965,25 +1977,29 @@ export default {
existingSrModalTitle: '之前存储库的使用情况',
// Original text: "This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingSrModalText: '这条路径之前已经被一台XenServer主机用来连接存储。如果你选择继续创建存储库所有的数据将丢失。',
existingSrModalText:
'这条路径之前已经被一台XenServer主机用来连接存储。如果你选择继续创建存储库所有的数据将丢失。',
// Original text: "Previous LUN Usage"
existingLunModalTitle: '之前LUN使用情况',
// Original text: "This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation."
existingLunModalText: '这个LUN之前已经被一台XenServer主机使用。如果你选择继续创建存储库所有的数据将丢失。',
existingLunModalText:
'这个LUN之前已经被一台XenServer主机使用。如果你选择继续创建存储库所有的数据将丢失。',
// Original text: "Replace current registration?"
alreadyRegisteredModal: '替换当前的注册?',
// Original text: "Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?"
alreadyRegisteredModalText: '你的XO设备已经注册给{email},你确定要删除并替换这个注册信息?',
alreadyRegisteredModalText:
'你的XO设备已经注册给{email},你确定要删除并替换这个注册信息?',
// Original text: "Ready for trial?"
trialReadyModal: '准备试用?',
// Original text: "During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!"
trialReadyModalText: '在试用期内XOA需要Internet连接才能正常使用如果您正式付费将不受此限制',
trialReadyModalText:
'在试用期内XOA需要Internet连接才能正常使用如果您正式付费将不受此限制',
// Original text: "Host"
serverHost: '主机',
@ -2160,7 +2176,8 @@ export default {
noUpdaterSubscribe: '请考虑订购或在15天内免费试用所有功能',
// Original text: "Manual update could break your current installation due to dependencies issues, do it with caution"
noUpdaterWarning: '由于相关依赖关系的问题,手动更新将跑坏你当前的安全,请小心使用',
noUpdaterWarning:
'由于相关依赖关系的问题,手动更新将跑坏你当前的安全,请小心使用',
// Original text: "Current version:"
currentVersion: '当前版本',
@ -2175,7 +2192,8 @@ export default {
trialStartButton: '开始试用',
// Original text: "You can use a trial version until {date, date, medium}. Upgrade your appliance to get it."
trialAvailableUntil: '你可以使用试用版本直到{date, date, medium}。更新你的设备来获取',
trialAvailableUntil:
'你可以使用试用版本直到{date, date, medium}。更新你的设备来获取',
// Original text: "Your trial has been ended. Contact us or downgrade to Free version"
trialConsumed: '你的使用已经结束,联系我们或下载免费版本',
@ -2205,7 +2223,8 @@ export default {
promptUpgradeReloadTitle: '更新成功',
// Original text: "Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?"
promptUpgradeReloadMessage: '你的XOA已经成功更新你的浏览器必须重新加载你要现在重新加载吗',
promptUpgradeReloadMessage:
'你的XOA已经成功更新你的浏览器必须重新加载你要现在重新加载吗',
// Original text: "Xen Orchestra from the sources"
disclaimerTitle: 'Xen Orchestra 源码版',
@ -2277,5 +2296,5 @@ export default {
changePasswordOk: '确认',
// Original text: "Others"
others: '其他'
others: '其他',
}

View File

@ -1,10 +1,10 @@
// This file is coded in ES5 and CommonJS to be compatible with
// `create-locale`.
var forEach = require('lodash/forEach')
var isString = require('lodash/isString')
const forEach = require('lodash/forEach')
const isString = require('lodash/isString')
var messages = {
const messages = {
keyValue: '{key}: {value}',
statusConnecting: 'Connecting',
@ -107,7 +107,8 @@ var messages = {
homeFetchingData: 'Fetching data…',
homeWelcome: 'Welcome to Xen Orchestra!',
homeWelcomeText: 'Add your XenServer hosts or pools',
homeConnectServerText: 'Some XenServers have been registered but are not connected',
homeConnectServerText:
'Some XenServers have been registered but are not connected',
homeHelp: 'Want some help?',
homeAddServer: 'Add server',
homeConnectServer: 'Connect servers',
@ -166,7 +167,8 @@ var messages = {
// ----- Common components -----
sortedTableAllItemsSelected: 'All of them are selected',
sortedTableNoItems: 'No items found',
sortedTableNumberOfFilteredItems: '{nFiltered, number} of {nTotal, number} items',
sortedTableNumberOfFilteredItems:
'{nFiltered, number} of {nTotal, number} items',
sortedTableNumberOfItems: '{nTotal, number} items',
sortedTableNumberOfSelectedItems: '{nSelected, number} selected',
sortedTableSelectAllItems: 'Click here to select all items',
@ -263,21 +265,25 @@ var messages = {
jobFinished: 'Finished',
saveBackupJob: 'Save',
deleteBackupSchedule: 'Remove backup job',
deleteBackupScheduleQuestion: 'Are you sure you want to delete this backup job?',
deleteBackupScheduleQuestion:
'Are you sure you want to delete this backup job?',
scheduleEnableAfterCreation: 'Enable immediately after creation',
scheduleEditMessage: 'You are editing Schedule {name} ({id}). Saving will override previous schedule state.',
jobEditMessage: 'You are editing job {name} ({id}). Saving will override previous job state.',
scheduleEditMessage:
'You are editing Schedule {name} ({id}). Saving will override previous schedule state.',
jobEditMessage:
'You are editing job {name} ({id}). Saving will override previous job state.',
noScheduledJobs: 'No scheduled jobs.',
noJobs: 'No jobs found.',
noSchedules: 'No schedules found',
jobActionPlaceHolder: 'Select a xo-server API command',
jobTimeoutPlaceHolder: 'Timeout (number of seconds after which a VM is considered failed)',
jobTimeoutPlaceHolder:
'Timeout (number of seconds after which a VM is considered failed)',
jobSchedules: 'Schedules',
jobScheduleNamePlaceHolder: 'Name of your schedule',
jobScheduleJobPlaceHolder: 'Select a Job',
jobOwnerPlaceholder: 'Job owner',
jobUserNotFound: 'This job\'s creator no longer exists',
backupUserNotFound: 'This backup\'s creator no longer exists',
jobUserNotFound: "This job's creator no longer exists",
backupUserNotFound: "This backup's creator no longer exists",
backupOwner: 'Backup owner',
// ------ New backup -----
@ -286,8 +292,10 @@ var messages = {
normalBackup: 'Normal backup',
smartBackup: 'Smart backup',
localRemoteWarningTitle: 'Local remote selected',
localRemoteWarningMessage: 'Warning: local remotes will use limited XOA disk space. Only for advanced users.',
backupVersionWarning: 'Warning: this feature works only with XenServer 6.5 or newer.',
localRemoteWarningMessage:
'Warning: local remotes will use limited XOA disk space. Only for advanced users.',
backupVersionWarning:
'Warning: this feature works only with XenServer 6.5 or newer.',
editBackupVmsTitle: 'VMs',
editBackupSmartStatusTitle: 'VMs statuses',
editBackupSmartResidentOn: 'Resident on',
@ -309,7 +317,8 @@ var messages = {
remoteTypeNfs: 'NFS',
remoteTypeSmb: 'SMB',
remoteType: 'Type',
remoteSmbWarningMessage: 'SMB remotes are meant to work on Windows Server. For other systems (Linux Samba, which means almost all NAS), please use NFS.',
remoteSmbWarningMessage:
'SMB remotes are meant to work on Windows Server. For other systems (Linux Samba, which means almost all NAS), please use NFS.',
remoteTestTip: 'Test your remote',
testRemote: 'Test Remote',
remoteTestFailure: 'Test failed for {name}',
@ -421,7 +430,8 @@ var messages = {
pluginError: 'Plugin error',
unknownPluginError: 'Unknown error',
purgePluginConfiguration: 'Purge plugin configuration',
purgePluginConfigurationQuestion: 'Are you sure you want to purge this configuration ?',
purgePluginConfigurationQuestion:
'Are you sure you want to purge this configuration ?',
editPluginConfiguration: 'Edit',
cancelPluginEdition: 'Cancel',
pluginConfigurationSuccess: 'Plugin configuration',
@ -523,8 +533,10 @@ var messages = {
addSrLabel: 'Add SR',
addVmLabel: 'Add VM',
addHostLabel: 'Add Host',
hostNeedsPatchUpdate: 'This host needs to install {patches, number} patch{patches, plural, one {} other {es}} before it can be added to the pool. This operation may be long.',
hostNeedsPatchUpdateNoInstall: 'This host cannot be added to the pool because it\'s missing some patches.',
hostNeedsPatchUpdate:
'This host needs to install {patches, number} patch{patches, plural, one {} other {es}} before it can be added to the pool. This operation may be long.',
hostNeedsPatchUpdateNoInstall:
"This host cannot be added to the pool because it's missing some patches.",
addHostErrorTitle: 'Adding host failed',
addHostNotHomogeneousErrorMessage: 'Host patches could not be homogenized.',
disconnectServer: 'Disconnect',
@ -538,17 +550,19 @@ var messages = {
forceRebootHostLabel: 'Force reboot',
rebootHostLabel: 'Reboot',
noHostsAvailableErrorTitle: 'Error while restarting host',
noHostsAvailableErrorMessage: 'Some VMs cannot be migrated before restarting this host. Please try force reboot.',
noHostsAvailableErrorMessage:
'Some VMs cannot be migrated before restarting this host. Please try force reboot.',
failHostBulkRestartTitle: 'Error while restarting hosts',
failHostBulkRestartMessage: '{failedHosts, number}/{totalHosts, number} 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 -----
storageTabName: 'Storage',
patchesTabName: 'Patches',
// ----- host stat tab -----
// ----- host stat tab -----
statLoad: 'Load average',
// ----- host advanced tab -----
// ----- host advanced tab -----
memoryHostState: 'RAM Usage: {memoryUsed}',
hardwareHostSettingsLabel: 'Hardware',
hostAddress: 'Address',
@ -577,9 +591,11 @@ var messages = {
supplementalPackInstallStartedTitle: 'Installation started',
supplementalPackInstallStartedMessage: 'Installing new supplemental pack…',
supplementalPackInstallErrorTitle: 'Installation error',
supplementalPackInstallErrorMessage: 'The installation of the supplemental pack failed.',
supplementalPackInstallErrorMessage:
'The installation of the supplemental pack failed.',
supplementalPackInstallSuccessTitle: 'Installation success',
supplementalPackInstallSuccessMessage: 'Supplemental pack successfully installed.',
supplementalPackInstallSuccessMessage:
'Supplemental pack successfully installed.',
// ----- Host net tabs -----
networkCreateButton: 'Add a network',
networkCreateBondedButton: 'Add a bonded network',
@ -635,7 +651,8 @@ var messages = {
hostMissingPatches: 'Missing patches',
hostUpToDate: 'Host up-to-date!',
installPatchWarningTitle: 'Non-recommended patch install',
installPatchWarningContent: 'This will install a patch only on this host. This is NOT the recommended way: please go into the Pool patch view and follow instructions there. If you are sure about this, you can continue anyway',
installPatchWarningContent:
'This will install a patch only on this host. This is NOT the recommended way: please go into the Pool patch view and follow instructions there. If you are sure about this, you can continue anyway',
installPatchWarningReject: 'Go to pool',
installPatchWarningResolve: 'Install',
// ----- Pool patch tabs -----
@ -748,8 +765,10 @@ var messages = {
resetBootOption: 'Reset',
deleteSelectedVdis: 'Delete selected VDIs',
deleteSelectedVdi: 'Delete selected VDI',
useQuotaWarning: 'Creating this disk will use the disk space quota from the resource set {resourceSet} ({spaceLeft} left)',
notEnoughSpaceInResourceSet: 'Not enough space in resource set {resourceSet} ({spaceLeft} left)',
useQuotaWarning:
'Creating this disk will use the disk space quota from the resource set {resourceSet} ({spaceLeft} left)',
notEnoughSpaceInResourceSet:
'Not enough space in resource set {resourceSet} ({spaceLeft} left)',
// ----- VM network tab -----
vifCreateDeviceButton: 'New device',
@ -769,7 +788,8 @@ var messages = {
vifAllowedIps: 'Allowed IPs',
vifNoIps: 'No IPs',
vifLockedNetwork: 'Network locked',
vifLockedNetworkNoIps: 'Network locked and no IPs are allowed for this interface',
vifLockedNetworkNoIps:
'Network locked and no IPs are allowed for this interface',
vifUnLockedNetwork: 'Network not locked',
vifUnknownNetwork: 'Unknown network',
vifAction: 'Action',
@ -813,7 +833,8 @@ var messages = {
xenToolsStatus: 'Xen tools status',
xenToolsStatusValue: {
defaultMessage: '{status}',
description: 'status can be `not-installed`, `unknown`, `out-of-date` & `up-to-date`'
description:
'status can be `not-installed`, `unknown`, `out-of-date` & `up-to-date`',
},
osName: 'OS name',
osKernel: 'OS kernel',
@ -831,9 +852,11 @@ var messages = {
vmCpuLimitsLabel: 'CPU limits',
vmCpuTopology: 'Topology',
vmChooseCoresPerSocket: 'Default behavior',
vmCoresPerSocket: '{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket',
vmCoresPerSocket:
'{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket',
vmCoresPerSocketIncorrectValue: 'Incorrect cores per socket value',
vmCoresPerSocketIncorrectValueSolution: 'Please change the selected value to fix it.',
vmCoresPerSocketIncorrectValueSolution:
'Please change the selected value to fix it.',
vmMemoryLimitsLabel: 'Memory limits (min/max)',
vmMaxVcpus: 'vCPUs max:',
vmMaxRam: 'Memory max:',
@ -855,8 +878,10 @@ var messages = {
templateHomeNamePlaceholder: 'Click to add a name',
templateHomeDescriptionPlaceholder: 'Click to add a description',
templateDelete: 'Delete template',
templateDeleteModalTitle: 'Delete VM template{templates, plural, one {} other {s}}',
templateDeleteModalBody: 'Are you sure you want to delete {templates, plural, one {this} other {these}} template{templates, plural, one {} other {s}}?',
templateDeleteModalTitle:
'Delete VM template{templates, plural, one {} other {s}}',
templateDeleteModalBody:
'Are you sure you want to delete {templates, plural, one {this} other {these}} template{templates, plural, one {} other {s}}?',
// ----- Dashboard -----
poolPanel: 'Pool{pools, plural, one {} other {s}}',
@ -984,7 +1009,8 @@ var messages = {
editResourceSet: 'Edit',
deleteResourceSet: 'Delete',
deleteResourceSetWarning: 'Delete resource set',
deleteResourceSetQuestion: 'Are you sure you want to delete this resource set?',
deleteResourceSetQuestion:
'Are you sure you want to delete this resource set?',
resourceSetMissingObjects: 'Missing objects:',
resourceSetVcpus: 'vCPUs',
resourceSetMemory: 'Memory',
@ -993,7 +1019,8 @@ var messages = {
availableHosts: 'Available hosts',
excludedHosts: 'Excluded hosts',
noHostsAvailable: 'No hosts available.',
availableHostsDescription: 'VMs created from this resource set shall run on the following hosts.',
availableHostsDescription:
'VMs created from this resource set shall run on the following hosts.',
maxCpus: 'Maximum CPUs',
maxRam: 'Maximum RAM (GiB)',
maxDiskSpace: 'Maximum disk space',
@ -1006,7 +1033,8 @@ var messages = {
resourceSetNew: 'New',
// ---- VM import ---
importVmsList: 'Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files.',
importVmsList:
'Try dropping some VMs files here, or click to select VMs to upload. Accept only .xva/.ova files.',
noSelectedVms: 'No selected VMs.',
vmImportToPool: 'To Pool:',
vmImportToSr: 'To SR:',
@ -1040,7 +1068,8 @@ var messages = {
delta: 'delta',
restoreBackups: 'Restore Backups',
restoreBackupsInfo: 'Click on a VM to display restore options',
restoreDeltaBackupsInfo: 'Only the files of Delta Backup which are not on a SMB remote can be restored',
restoreDeltaBackupsInfo:
'Only the files of Delta Backup which are not on a SMB remote can be restored',
remoteEnabled: 'Enabled',
remoteError: 'Error',
noBackup: 'No backup available',
@ -1073,47 +1102,63 @@ var messages = {
restoreFilesNoFilesSelected: 'No files selected',
restoreFilesSelectedFiles: 'Selected files ({files}):',
restoreFilesDiskError: 'Error while scanning disk',
restoreFilesSelectAllFiles: 'Select all this folder\'s files',
restoreFilesSelectAllFiles: "Select all this folder's files",
restoreFilesUnselectAll: 'Unselect all files',
// ----- Modals -----
emergencyShutdownHostsModalTitle: 'Emergency shutdown Host{nHosts, plural, one {} other {s}}',
emergencyShutdownHostsModalMessage: 'Are you sure you want to shutdown {nHosts, number} Host{nHosts, plural, one {} other {s}}?',
emergencyShutdownHostsModalTitle:
'Emergency shutdown 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',
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',
addHostModalMessage: 'Are you sure you want to add {host} to {pool}?',
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, number} Host{nHosts, plural, one {} other {s}} agent{nHosts, plural, one {} other {s}}?',
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, 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, number} 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}}',
cloneAndStartVM: 'Start a copy',
forceStartVm: 'Force start',
forceStartVmModalTitle: 'Forbidden operation',
blockedStartVmModalMessage: 'Start operation for this vm is blocked.',
blockedStartVmsModalMessage: 'Forbidden operation start for {nVms, number} vm{nVms, plural, one {} other {s}}.',
startVmsModalMessage: 'Are you sure you want to start {vms, number} VM{vms, plural, one {} other {s}}?',
failedVmsErrorMessage: '{nVms, number} vm{nVms, plural, one {} other {s}} are failed. Please see your logs to get more information',
blockedStartVmsModalMessage:
'Forbidden operation start for {nVms, number} vm{nVms, plural, one {} other {s}}.',
startVmsModalMessage:
'Are you sure you want to start {vms, number} VM{vms, plural, one {} other {s}}?',
failedVmsErrorMessage:
'{nVms, number} vm{nVms, plural, one {} other {s}} are failed. Please see your logs to get more information',
failedVmsErrorTitle: 'Start failed',
stopHostsModalTitle: 'Stop Host{nHosts, plural, one {} other {s}}',
stopHostsModalMessage: 'Are you sure you want to stop {nHosts, number} 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, number} 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, number} 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, number} 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, number} 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',
deleteVmModalMessage:
'Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED',
migrateVmModalTitle: 'Migrate VM',
migrateVmSelectHost: 'Select a destination host:',
migrateVmSelectMigrationNetwork: 'Select a migration network:',
@ -1134,29 +1179,37 @@ var messages = {
chooseSrForEachVdisModalSrLabel: 'SR*',
chooseSrForEachVdisModalOptionalEntry: '* optional',
deleteVdiModalTitle: 'Delete VDI',
deleteVdiModalMessage: 'Are you sure you want to delete this disk? ALL DATA ON THIS DISK WILL BE LOST',
deleteVdiModalMessage:
'Are you sure you want to delete this disk? ALL DATA ON THIS DISK WILL BE LOST',
deleteVdisModalTitle: 'Delete VDI{nVdis, plural, one {} other {s}}',
deleteVdisModalMessage: 'Are you sure you want to delete {nVdis, number} disk{nVdis, plural, one {} other {s}}? ALL DATA ON THESE DISKS WILL BE LOST',
deleteVdisModalMessage:
'Are you sure you want to delete {nVdis, number} disk{nVdis, plural, one {} other {s}}? ALL DATA ON THESE DISKS WILL BE LOST',
revertVmModalTitle: 'Revert your VM',
deleteSnapshotModalTitle: 'Delete snapshot',
deleteSnapshotModalMessage: 'Are you sure you want to delete this snapshot?',
revertVmModalMessage: 'Are you sure you want to revert this VM to the snapshot state? This operation is irreversible.',
revertVmModalMessage:
'Are you sure you want to revert this VM to the snapshot state? This operation is irreversible.',
revertVmModalSnapshotBefore: 'Snapshot before',
importBackupModalTitle: 'Import a {name} Backup',
importBackupModalStart: 'Start VM after restore',
importBackupModalSelectBackup: 'Select your backup…',
removeAllOrphanedModalWarning: 'Are you sure you want to remove all orphaned snapshot VDIs?',
removeAllOrphanedModalWarning:
'Are you sure you want to remove all orphaned snapshot VDIs?',
removeAllLogsModalTitle: 'Remove all logs',
removeAllLogsModalWarning: 'Are you sure you want to remove all logs?',
definitiveMessageModal: 'This operation is definitive.',
existingSrModalTitle: 'Previous SR Usage',
existingSrModalText: 'This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
existingSrModalText:
'This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
existingLunModalTitle: 'Previous LUN Usage',
existingLunModalText: 'This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
existingLunModalText:
'This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
alreadyRegisteredModal: 'Replace current registration?',
alreadyRegisteredModalText: 'Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?',
alreadyRegisteredModalText:
'Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?',
trialReadyModal: 'Ready for trial?',
trialReadyModalText: 'During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!',
trialReadyModalText:
'During the trial period, XOA need to have a working internet connection. This limitation does not apply for our paid plans!',
// ----- Servers -----
serverLabel: 'Label',
@ -1167,7 +1220,8 @@ var messages = {
serverReadOnly: 'Read Only',
serverUnauthorizedCertificates: 'Unauthorized Certificates',
serverAllowUnauthorizedCertificates: 'Allow Unauthorized Certificates',
serverUnauthorizedCertificatesInfo: 'Enable it if your certificate is rejected, but it\'s not recommended because your connection will not be secured.',
serverUnauthorizedCertificatesInfo:
"Enable it if your certificate is rejected, but it's not recommended because your connection will not be secured.",
serverDisconnect: 'Disconnect server',
serverPlaceHolderUser: 'username',
serverPlaceHolderPassword: 'password',
@ -1184,7 +1238,8 @@ var messages = {
serverAuthFailed: 'Authentication error',
serverUnknownError: 'Unknown error',
serverSelfSignedCertError: 'Invalid self-signed certificate',
serverSelfSignedCertQuestion: 'Do you want to accept self-signed certificate for this server even though it would decrease security?',
serverSelfSignedCertQuestion:
'Do you want to accept self-signed certificate for this server even though it would decrease security?',
// ----- Copy VM -----
copyVm: 'Copy VM',
@ -1200,18 +1255,21 @@ var messages = {
// ----- Detach host -----
detachHostModalTitle: 'Detach host',
detachHostModalMessage: 'Are you sure you want to detach {host} from its pool? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND REBOOT THE HOST.',
detachHostModalMessage:
'Are you sure you want to detach {host} from its pool? THIS WILL REMOVE ALL VMs ON ITS LOCAL STORAGE AND REBOOT THE HOST.',
detachHost: 'Detach',
// ----- Forget host -----
forgetHostModalTitle: 'Forget host',
forgetHostModalMessage: 'Are you sure you want to forget {host} from its pool? Be sure this host can\'t be back online, or use detach instead.',
forgetHostModalMessage:
"Are you sure you want to forget {host} from its pool? Be sure this host can't be back online, or use detach instead.",
forgetHost: 'Forget',
// ----- Set pool master -----
setPoolMasterModalTitle: 'Designate a new master',
setPoolMasterModalMessage: 'This operation may take several minutes. Do you want to continue?',
setPoolMasterModalMessage:
'This operation may take several minutes. Do you want to continue?',
// ----- Network -----
newNetworkCreate: 'Create network',
@ -1264,7 +1322,8 @@ var messages = {
or: 'Or',
tryIt: 'Try it for free!',
availableIn: 'This feature is available starting from {plan} Edition',
notAvailable: 'This feature is not available in your version, contact your administrator to know more.',
notAvailable:
'This feature is not available in your version, contact your administrator to know more.',
// ----- Updates View -----
updateTitle: 'Updates',
@ -1283,30 +1342,40 @@ var messages = {
refresh: 'Refresh',
upgrade: 'Upgrade',
noUpdaterCommunity: 'No updater available for Community Edition',
considerSubscribe: 'Please consider subscribing and trying it with all the features for free during 15 days on {link}.',
noUpdaterWarning: 'Manual update could break your current installation due to dependencies issues, do it with caution',
considerSubscribe:
'Please consider subscribing and trying it with all the features for free during 15 days on {link}.',
noUpdaterWarning:
'Manual update could break your current installation due to dependencies issues, do it with caution',
currentVersion: 'Current version:',
register: 'Register',
editRegistration: 'Edit registration',
trialRegistration: 'Please, take time to register in order to enjoy your trial.',
trialRegistration:
'Please, take time to register in order to enjoy your trial.',
trialStartButton: 'Start trial',
trialAvailableUntil: 'You can use a trial version until {date, date, medium}. Upgrade your appliance to get it.',
trialConsumed: 'Your trial has been ended. Contact us or downgrade to Free version',
trialLocked: 'Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service.',
trialAvailableUntil:
'You can use a trial version until {date, date, medium}. Upgrade your appliance to get it.',
trialConsumed:
'Your trial has been ended. Contact us or downgrade to Free version',
trialLocked:
'Your xoa-updater service appears to be down. Your XOA cannot run fully without reaching this service.',
noUpdateInfo: 'No update information available',
waitingUpdateInfo: 'Update information may be available',
upToDate: 'Your XOA is up-to-date',
mustUpgrade: 'You need to update your XOA (new version is available)',
registerNeeded: 'Your XOA is not registered for updates',
updaterError: 'Can\'t fetch update information',
updaterError: "Can't fetch update information",
promptUpgradeReloadTitle: 'Upgrade successful',
promptUpgradeReloadMessage: 'Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?',
promptUpgradeReloadMessage:
'Your XOA has successfully upgraded, and your browser must reload the application. Do you want to reload now ?',
// ----- OS Disclaimer -----
disclaimerTitle: 'Xen Orchestra from the sources',
disclaimerText1: 'You are using XO from the sources! That\'s great for a personal/non-profit usage.',
disclaimerText2: 'If you are a company, it\'s better to use it with our appliance + pro support included:',
disclaimerText3: 'This version is not bundled with any support nor updates. Use it with caution for critical tasks.',
disclaimerText1:
"You are using XO from the sources! That's great for a personal/non-profit usage.",
disclaimerText2:
"If you are a company, it's better to use it with our appliance + pro support included:",
disclaimerText3:
'This version is not bundled with any support nor updates. Use it with caution for critical tasks.',
// ----- PIF -----
connectPif: 'Connect PIF',
@ -1328,11 +1397,13 @@ var messages = {
newPasswordPlaceholder: 'New password',
confirmPasswordPlaceholder: 'Confirm new password',
confirmationPasswordError: 'Confirmation password incorrect',
confirmationPasswordErrorBody: 'Password does not match the confirm password.',
confirmationPasswordErrorBody:
'Password does not match the confirm password.',
pwdChangeSuccess: 'Password changed',
pwdChangeSuccessBody: 'Your password has been successfully changed.',
pwdChangeError: 'Incorrect password',
pwdChangeErrorBody: 'The old password provided is incorrect. Your password has not been changed.',
pwdChangeErrorBody:
'The old password provided is incorrect. Your password has not been changed.',
changePasswordOk: 'OK',
sshKeys: 'SSH keys',
newSshKey: 'New SSH key',
@ -1344,7 +1415,8 @@ var messages = {
title: 'Title',
key: 'Key',
deleteSshKeyConfirm: 'Delete SSH key',
deleteSshKeyConfirmMessage: 'Are you sure you want to delete the SSH key {title}?',
deleteSshKeyConfirmMessage:
'Are you sure you want to delete the SSH key {title}?',
// ----- Usage -----
others: 'Others',
@ -1414,7 +1486,8 @@ var messages = {
// ----- Config -----
noConfigFile: 'No config file selected',
importTip: 'Try dropping a config file here, or click to select a config file to upload.',
importTip:
'Try dropping a config file here, or click to select a config file to upload.',
config: 'Config',
importConfig: 'Import',
importConfigSuccess: 'Config file successfully imported',
@ -1426,14 +1499,19 @@ var messages = {
// ----- SR -----
srReconnectAllModalTitle: 'Reconnect all hosts',
srReconnectAllModalMessage: 'This will reconnect this SR to all its hosts.',
srsReconnectAllModalMessage: 'This will reconnect each selected SR to its host (local SR) or to every hosts of its pool (shared SR).',
srsReconnectAllModalMessage:
'This will reconnect each selected SR to its host (local SR) or to every hosts of its pool (shared SR).',
srDisconnectAllModalTitle: 'Disconnect all hosts',
srDisconnectAllModalMessage: 'This will disconnect this SR from all its hosts.',
srsDisconnectAllModalMessage: 'This will disconnect each selected SR from its host (local SR) or from every hosts of its pool (shared SR).',
srDisconnectAllModalMessage:
'This will disconnect this SR from all its hosts.',
srsDisconnectAllModalMessage:
'This will disconnect each selected SR from its host (local SR) or from every hosts of its pool (shared SR).',
srForgetModalTitle: 'Forget SR',
srsForgetModalTitle: 'Forget selected SRs',
srForgetModalMessage: 'Are you sure you want to forget this SR? VDIs on this storage won\'t be removed.',
srsForgetModalMessage: 'Are you sure you want to forget all the selected SRs? VDIs on these storages won\'t be removed.',
srForgetModalMessage:
"Are you sure you want to forget this SR? VDIs on this storage won't be removed.",
srsForgetModalMessage:
"Are you sure you want to forget all the selected SRs? VDIs on these storages won't be removed.",
srAllDisconnected: 'Disconnected',
srSomeConnected: 'Partially connected',
srAllConnected: 'Connected',
@ -1452,7 +1530,8 @@ 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',
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}',
@ -1470,7 +1549,8 @@ var messages = {
xosanAdvanced: 'Advanced',
xosanRemoveSubvolumes: 'Remove subvolumes',
xosanAddSubvolume: 'Add subvolume…',
xosanWarning: 'This version of XOSAN SR is from the first beta phase. You can keep using it, but to modify it you\'ll have to save your disks and re-create it.',
xosanWarning:
"This version of XOSAN SR is from the first beta phase. You can keep using it, but to modify it you'll have to save your disks and re-create it.",
xosanVlan: 'VLAN',
xosanNoSrs: 'No XOSAN found',
xosanPbdsDetached: 'Some SRs are detached from the XOSAN',
@ -1492,16 +1572,20 @@ var messages = {
xosanLoading: 'Loading…',
xosanNotAvailable: 'XOSAN is not available at the moment',
xosanRegisterBeta: 'Register for the XOSAN beta',
xosanSuccessfullyRegistered: 'You have successfully registered for the XOSAN beta. Please wait until your request has been approved.',
xosanSuccessfullyRegistered:
'You have successfully registered for the XOSAN beta. Please wait until your request has been approved.',
xosanInstallPackOnHosts: 'Install XOSAN pack on these hosts:',
xosanInstallPack: 'Install {pack} v{version}?',
xosanNoPackFound: 'No compatible XOSAN pack found for your XenServer versions.',
xosanPackRequirements: 'At least one of these version requirements must be satisfied by all the hosts in this pool:',
xosanNoPackFound:
'No compatible XOSAN pack found for your XenServer versions.',
xosanPackRequirements:
'At least one of these version requirements must be satisfied by all the hosts in this pool:',
// SR tab XOSAN
xosanVmsNotRunning: 'Some XOSAN Virtual Machines are not running',
xosanVmsNotFound: 'Some XOSAN Virtual Machines could not be found',
xosanFilesNeedingHealing: 'Files needing healing',
xosanFilesNeedHealing: 'Some XOSAN Virtual Machines have files needing healing',
xosanFilesNeedHealing:
'Some XOSAN Virtual Machines have files needing healing',
xosanHostNotInNetwork: 'Host {hostName} is not in XOSAN network',
xosanVm: 'VM controller',
xosanUnderlyingStorage: 'SR',
@ -1534,13 +1618,14 @@ var messages = {
xosanCouldNotFindVM: 'Could not find VM',
xosanUnderlyingStorageUsage: 'Using {usage}',
xosanCustomIpNetwork: 'Custom IP network (/24)',
xosanIssueHostNotInNetwork: 'Will configure the host xosan network device with a static IP address and plug it in.'
xosanIssueHostNotInNetwork:
'Will configure the host xosan network device with a static IP address and plug it in.',
}
forEach(messages, function (message, id) {
if (isString(message)) {
messages[id] = {
id,
defaultMessage: message
defaultMessage: message,
}
} else if (!message.id) {
message.id = id

View File

@ -13,19 +13,21 @@ export const isIpV6 = isIp.v6
const ipv4 = /^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?:\.(?!$)|$)){4}$/
function ip2hex (ip) {
let parts = ip.split('.').map(str => parseInt(str, 10))
const parts = ip.split('.').map(str => parseInt(str, 10))
let n = 0
n += parts[3]
n += parts[2] * 256 // 2^8
n += parts[1] * 65536 // 2^16
n += parts[2] * 256 // 2^8
n += parts[1] * 65536 // 2^16
n += parts[0] * 16777216 // 2^24
return n
}
function assertIpv4 (str, msg) {
if (!ipv4.test(str)) { throw new Error(msg) }
if (!ipv4.test(str)) {
throw new Error(msg)
}
}
function * range (ip1, ip2) {
@ -36,13 +38,14 @@ function * range (ip1, ip2) {
let hex2 = ip2hex(ip2)
if (hex > hex2) {
let tmp = hex
const tmp = hex
hex = hex2
hex2 = tmp
}
for (let i = hex; i <= hex2; i++) {
yield `${(i >> 24) & 0xff}.${(i >> 16) & 0xff}.${(i >> 8) & 0xff}.${i & 0xff}`
yield `${(i >> 24) & 0xff}.${(i >> 16) & 0xff}.${(i >> 8) & 0xff}.${i &
0xff}`
}
}
@ -50,7 +53,10 @@ function * range (ip1, ip2) {
export const getNextIpV4 = ip => {
const splitIp = ip.split('.')
if (splitIp.length !== 4 || some(splitIp, value => value < 0 || value > 255)) {
if (
splitIp.length !== 4 ||
some(splitIp, value => value < 0 || value > 255)
) {
return
}
let index
@ -85,10 +91,13 @@ export const formatIps = ips => {
if (splitIp2.length !== 4) {
return -1
}
return splitIp1[3] - splitIp2[3] +
return (
splitIp1[3] -
splitIp2[3] +
(splitIp1[2] - splitIp2[2]) * 256 +
(splitIp1[1] - splitIp2[1]) * 256 * 256 +
(splitIp1[0] - splitIp2[0]) * 256 * 256 * 256
)
})
const range = { first: '', last: '' }
const formattedIps = []
@ -96,7 +105,8 @@ export const formatIps = ips => {
forEach(sortedIps, ip => {
if (ip !== getNextIpV4(range.last)) {
if (range.first) {
formattedIps[index] = range.first === range.last ? range.first : { ...range }
formattedIps[index] =
range.first === range.last ? range.first : { ...range }
index++
}
range.first = range.last = ip

View File

@ -13,36 +13,29 @@ import {
createGetObjectsOfType,
createFinder,
createGetObject,
createSelector
createSelector,
} from './selectors'
import {
ejectCd,
insertCd
} from './xo'
import { ejectCd, insertCd } from './xo'
@propTypes({
vm: propTypes.object.isRequired
vm: propTypes.object.isRequired,
})
@connectStore(() => {
const getCdDrive = createFinder(
createGetObjectsOfType('VBD').pick(
(_, { vm }) => vm.$VBDs
),
[ vbd => vbd.is_cd_drive ]
createGetObjectsOfType('VBD').pick((_, { vm }) => vm.$VBDs),
[vbd => vbd.is_cd_drive]
)
const getMountedIso = createGetObject(
(state, props) => {
const cdDrive = getCdDrive(state, props)
if (cdDrive) {
return cdDrive.VDI
}
const getMountedIso = createGetObject((state, props) => {
const cdDrive = getCdDrive(state, props)
if (cdDrive) {
return cdDrive.VDI
}
)
})
return {
cdDrive: getCdDrive,
mountedIso: getMountedIso
mountedIso: getMountedIso,
}
})
export default class IsoDevice extends Component {
@ -77,7 +70,7 @@ export default class IsoDevice extends Component {
_showWarning = () => alert(_('cdDriveNotInstalled'), _('cdDriveInstallation'))
render () {
const {cdDrive, mountedIso} = this.props
const { cdDrive, mountedIso } = this.props
return (
<div className='input-group'>
@ -93,19 +86,17 @@ export default class IsoDevice extends Component {
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>
}
{mountedIso &&
!cdDrive.device && (
<Tooltip content={_('cdDriveNotInstalled')}>
<a
className='text-warning btn btn-link'
onClick={this._showWarning}
>
<Icon icon='alarm' size='lg' />
</a>
</Tooltip>
)}
</div>
)
}

View File

@ -9,10 +9,7 @@ import propTypes from '../prop-types-decorator'
import { EMPTY_ARRAY } from '../utils'
import GenericInput from './generic-input'
import {
descriptionRender,
forceDisplayOptionalAttr
} from './helpers'
import { descriptionRender, forceDisplayOptionalAttr } from './helpers'
@propTypes({
depth: propTypes.number,
@ -20,12 +17,12 @@ import {
label: propTypes.any.isRequired,
required: propTypes.bool,
schema: propTypes.object.isRequired,
uiSchema: propTypes.object
uiSchema: propTypes.object,
})
@uncontrollableInput()
export default class ObjectInput extends Component {
state = {
use: this.props.required || forceDisplayOptionalAttr(this.props)
use: this.props.required || forceDisplayOptionalAttr(this.props),
}
_onAddItem = () => {
@ -56,9 +53,9 @@ export default class ObjectInput extends Component {
required,
schema,
uiSchema,
value = EMPTY_ARRAY
value = EMPTY_ARRAY,
},
state: { use }
state: { use },
} = this
const childDepth = depth + 2
@ -68,56 +65,61 @@ export default class ObjectInput extends Component {
const itemLabel = itemSchema.title || _('item')
return (
<div style={{'paddingLeft': `${depth}em`}}>
<div style={{ paddingLeft: `${depth}em` }}>
<legend>{label}</legend>
{descriptionRender(schema.description)}
<hr />
{!required && <div className='checkbox'>
<label>
<input
checked={use}
{!required && (
<div className='checkbox'>
<label>
<input
checked={use}
disabled={disabled}
onChange={this.linkState('use')}
type='checkbox'
/>{' '}
{_('fillOptionalInformations')}
</label>
</div>
)}
{use && (
<div className='card-block'>
<ul style={{ paddingLeft: 0 }}>
{map(value, (value, key) => (
<li className='list-group-item clearfix' key={key}>
<GenericInput
depth={childDepth}
disabled={disabled}
label={itemLabel}
name={key}
onChange={this._onChangeItem}
required
schema={itemSchema}
uiSchema={itemUiSchema}
value={value}
/>
<Button
btnStyle='danger'
className='pull-right'
disabled={disabled}
name={key}
onClick={() => this._onRemoveItem(key)}
>
{_('remove')}
</Button>
</li>
))}
</ul>
<Button
btnStyle='primary'
className='pull-right mt-1 mr-1'
disabled={disabled}
onChange={this.linkState('use')}
type='checkbox'
/> {_('fillOptionalInformations')}
</label>
</div>}
{use && <div className='card-block'>
<ul style={{'paddingLeft': 0}} >
{map(value, (value, key) =>
<li className='list-group-item clearfix' key={key}>
<GenericInput
depth={childDepth}
disabled={disabled}
label={itemLabel}
name={key}
onChange={this._onChangeItem}
required
schema={itemSchema}
uiSchema={itemUiSchema}
value={value}
/>
<Button
btnStyle='danger'
className='pull-right'
disabled={disabled}
name={key}
onClick={() => this._onRemoveItem(key)}
>
{_('remove')}
</Button>
</li>
)}
</ul>
<Button
btnStyle='primary'
className='pull-right mt-1 mr-1'
disabled={disabled}
onClick={this._onAddItem}
>
{_('add')}
</Button>
</div>}
onClick={this._onAddItem}
>
{_('add')}
</Button>
</div>
)}
</div>
)
}

View File

@ -11,21 +11,12 @@ import { PrimitiveInputWrapper } from './helpers'
@uncontrollableInput()
export default class BooleanInput extends Component {
render () {
const {
disabled,
onChange,
value,
...props
} = this.props
const { disabled, onChange, value, ...props } = this.props
return (
<PrimitiveInputWrapper {...props}>
<div className='checkbox form-control'>
<Toggle
disabled={disabled}
onChange={onChange}
value={value}
/>
<Toggle disabled={disabled} onChange={onChange} value={value} />
</div>
</PrimitiveInputWrapper>
)

View File

@ -14,10 +14,7 @@ export default class EnumInput extends Component {
_getSelectedIndex = createSelector(
() => this.props.schema.enum,
() => {
const {
schema,
value = schema.default
} = this.props
const { schema, value = schema.default } = this.props
return value
},
(enumValues, value) => {
@ -34,7 +31,7 @@ export default class EnumInput extends Component {
const {
disabled,
schema: { enum: enumValues, enumNames = enumValues },
required
required,
} = this.props
return (
@ -47,9 +44,11 @@ export default class EnumInput extends Component {
value={this._getSelectedIndex()}
>
{_('noSelectedValue', message => <option value=''>{message}</option>)}
{map(enumNames, (name, index) =>
<option value={index} key={index}>{name}</option>
)}
{map(enumNames, (name, index) => (
<option value={index} key={index}>
{name}
</option>
))}
</select>
</PrimitiveInputWrapper>
)

View File

@ -23,7 +23,7 @@ const InputByType = {
integer: IntegerInput,
number: NumberInput,
object: ObjectInput,
string: StringInput
string: StringInput,
}
// ===================================================================
@ -34,7 +34,7 @@ const InputByType = {
label: propTypes.any.isRequired,
required: propTypes.bool,
schema: propTypes.object.isRequired,
uiSchema: propTypes.object
uiSchema: propTypes.object,
})
@uncontrollableInput()
export default class GenericInput extends Component {
@ -56,7 +56,7 @@ export default class GenericInput extends Component {
onChange: this._onChange,
schema,
uiSchema,
value
value,
}
// Enum, special case.

View File

@ -38,12 +38,21 @@ export const getXoType = schema => {
// ===================================================================
export const descriptionRender = description =>
<span className='text-muted' dangerouslySetInnerHTML={{__html: marked(description || '')}} />
export const descriptionRender = description => (
<span
className='text-muted'
dangerouslySetInnerHTML={{ __html: marked(description || '') }}
/>
)
// ===================================================================
export const PrimitiveInputWrapper = ({ label, required = false, schema, children }) => (
export const PrimitiveInputWrapper = ({
label,
required = false,
schema,
children,
}) => (
<Row>
<Col mediumSize={6}>
<div className='input-group'>
@ -54,9 +63,7 @@ export const PrimitiveInputWrapper = ({ label, required = false, schema, childre
{children}
</div>
</Col>
<Col mediumSize={6}>
{descriptionRender(schema.description)}
</Col>
<Col mediumSize={6}>{descriptionRender(schema.description)}</Col>
</Row>
)

View File

@ -9,10 +9,7 @@ import propTypes from '../prop-types-decorator'
import { EMPTY_OBJECT } from '../utils'
import GenericInput from './generic-input'
import {
descriptionRender,
forceDisplayOptionalAttr
} from './helpers'
import { descriptionRender, forceDisplayOptionalAttr } from './helpers'
@propTypes({
depth: propTypes.number,
@ -20,26 +17,24 @@ import {
label: propTypes.any.isRequired,
required: propTypes.bool,
schema: propTypes.object.isRequired,
uiSchema: propTypes.object
uiSchema: propTypes.object,
})
@uncontrollableInput()
export default class ObjectInput extends Component {
state = {
use: this.props.required || forceDisplayOptionalAttr(this.props)
use: this.props.required || forceDisplayOptionalAttr(this.props),
}
_onChildChange = (value, key) => {
this.props.onChange({
...this.props.value,
[key]: value
[key]: value,
})
}
_getRequiredProps = createSelector(
() => this.props.schema.required,
required => required
? keyBy(required)
: EMPTY_OBJECT
required => (required ? keyBy(required) : EMPTY_OBJECT)
)
render () {
@ -51,9 +46,9 @@ export default class ObjectInput extends Component {
required,
schema,
uiSchema,
value = EMPTY_OBJECT
value = EMPTY_OBJECT,
},
state: { use }
state: { use },
} = this
const childDepth = depth + 2
@ -61,37 +56,42 @@ export default class ObjectInput extends Component {
const requiredProps = this._getRequiredProps()
return (
<div style={{'paddingLeft': `${depth}em`}}>
<div style={{ paddingLeft: `${depth}em` }}>
<legend>{label}</legend>
{descriptionRender(schema.description)}
<hr />
{!required && <div className='checkbox'>
<label>
<input
checked={use}
disabled={disabled}
onChange={this.linkState('use')}
type='checkbox'
/> {_('fillOptionalInformations')}
</label>
</div>}
{use && <div className='card-block'>
{map(schema.properties, (childSchema, key) =>
<div className='pb-1' key={key}>
<GenericInput
depth={childDepth}
{!required && (
<div className='checkbox'>
<label>
<input
checked={use}
disabled={disabled}
label={childSchema.title || key}
name={key}
onChange={this._onChildChange}
required={Boolean(requiredProps[key])}
schema={childSchema}
uiSchema={properties[key]}
value={value[key]}
/>
</div>
)}
</div>}
onChange={this.linkState('use')}
type='checkbox'
/>{' '}
{_('fillOptionalInformations')}
</label>
</div>
)}
{use && (
<div className='card-block'>
{map(schema.properties, (childSchema, key) => (
<div className='pb-1' key={key}>
<GenericInput
depth={childDepth}
disabled={disabled}
label={childSchema.title || key}
name={key}
onChange={this._onChildChange}
required={Boolean(requiredProps[key])}
schema={childSchema}
uiSchema={properties[key]}
value={value[key]}
/>
</div>
))}
</div>
)}
</div>
)
}

View File

@ -11,7 +11,7 @@ import { PrimitiveInputWrapper } from './helpers'
// ===================================================================
@propTypes({
password: propTypes.bool
password: propTypes.bool,
})
@uncontrollableInput()
export default class StringInput extends Component {

View File

@ -15,16 +15,16 @@ const _IGNORED_TAGNAMES = {
A: true,
BUTTON: true,
INPUT: true,
SELECT: true
SELECT: true,
}
@propTypes({
className: propTypes.string,
tagName: propTypes.string
tagName: propTypes.string,
})
export class BlockLink extends Component {
static contextTypes = {
router: routerShape
router: routerShape,
}
_style = { cursor: 'pointer' }

View File

@ -11,7 +11,7 @@ import propTypes from './prop-types-decorator'
import Tooltip from './tooltip'
import {
disable as disableShortcuts,
enable as enableShortcuts
enable as enableShortcuts,
} from './shortcuts'
let instance
@ -26,16 +26,18 @@ const modal = (content, onClose) => {
}
@propTypes({
buttons: propTypes.arrayOf(propTypes.shape({
btnStyle: propTypes.string,
icon: propTypes.string,
label: propTypes.node.isRequired,
tooltip: propTypes.node,
value: propTypes.any
})).isRequired,
buttons: propTypes.arrayOf(
propTypes.shape({
btnStyle: propTypes.string,
icon: propTypes.string,
label: propTypes.node.isRequired,
tooltip: propTypes.node,
value: propTypes.any,
})
).isRequired,
children: propTypes.node.isRequired,
icon: propTypes.string,
title: propTypes.node.isRequired
title: propTypes.node.isRequired,
})
class GenericModal extends Component {
_getBodyValue = () => {
@ -58,75 +60,62 @@ class GenericModal extends Component {
}
render () {
const {
buttons,
icon,
title
} = this.props
const { buttons, icon, title } = this.props
const body = _addRef(this.props.children, 'body')
return <div>
<ReactModal.Header closeButton>
<ReactModal.Title>
{icon
? <span><Icon icon={icon} /> {title}</span>
: title
}
</ReactModal.Title>
</ReactModal.Header>
<ReactModal.Body>
{body}
</ReactModal.Body>
<ReactModal.Footer>
{map(buttons, ({
label,
tooltip,
value,
icon,
...props
}, key) => {
const button = <Button
onClick={() => this._resolve(value)}
{...props}
>
{icon !== undefined && <Icon icon={icon} fixedWidth />}
{label}
</Button>
return <span key={key}>
{tooltip !== undefined
? <Tooltip content={tooltip}>{button}</Tooltip>
: button
}
{' '}
</span>
})}
{this.props.reject !== undefined &&
<Button onClick={this._reject} >
{_('genericCancel')}
</Button>
}
</ReactModal.Footer>
</div>
return (
<div>
<ReactModal.Header closeButton>
<ReactModal.Title>
{icon ? (
<span>
<Icon icon={icon} /> {title}
</span>
) : (
title
)}
</ReactModal.Title>
</ReactModal.Header>
<ReactModal.Body>{body}</ReactModal.Body>
<ReactModal.Footer>
{map(buttons, ({ label, tooltip, value, icon, ...props }, key) => {
const button = (
<Button onClick={() => this._resolve(value)} {...props}>
{icon !== undefined && <Icon icon={icon} fixedWidth />}
{label}
</Button>
)
return (
<span key={key}>
{tooltip !== undefined ? (
<Tooltip content={tooltip}>{button}</Tooltip>
) : (
button
)}{' '}
</span>
)
})}
{this.props.reject !== undefined && (
<Button onClick={this._reject}>{_('genericCancel')}</Button>
)}
</ReactModal.Footer>
</div>
)
}
}
const ALERT_BUTTONS = [ { label: _('alertOk'), value: 'ok' } ]
const ALERT_BUTTONS = [{ label: _('alertOk'), value: 'ok' }]
export const alert = (title, body) => (
export const alert = (title, body) =>
new Promise(resolve => {
modal(
<GenericModal
buttons={ALERT_BUTTONS}
resolve={resolve}
title={title}
>
<GenericModal buttons={ALERT_BUTTONS} resolve={resolve} title={title}>
{body}
</GenericModal>,
resolve
)
})
)
const _addRef = (component, ref) => {
if (isString(component) || isArray(component)) {
@ -139,27 +128,17 @@ const _addRef = (component, ref) => {
return component
}
const CONFIRM_BUTTONS = [ { btnStyle: 'primary', label: _('confirmOk') } ]
const CONFIRM_BUTTONS = [{ btnStyle: 'primary', label: _('confirmOk') }]
export const confirm = ({
body,
icon = 'alarm',
title
}) => (
export const confirm = ({ body, icon = 'alarm', title }) =>
chooseAction({
body,
buttons: CONFIRM_BUTTONS,
icon,
title
title,
})
)
export const chooseAction = ({
body,
buttons,
icon,
title
}) => {
export const chooseAction = ({ body, buttons, icon, title }) => {
return new Promise((resolve, reject) => {
modal(
<GenericModal

View File

@ -13,18 +13,19 @@ import propTypes from './prop-types-decorator'
// {children}
// </NoObjects>
// ````
const NoObjects = ({ children, collection, emptyMessage }) => collection == null
? <img src='assets/loading.svg' alt='loading' />
: isEmpty(collection)
? <p>{emptyMessage}</p>
: <div>{children}</div>
const NoObjects = ({ children, collection, emptyMessage }) =>
collection == null ? (
<img src='assets/loading.svg' alt='loading' />
) : isEmpty(collection) ? (
<p>{emptyMessage}</p>
) : (
<div>{children}</div>
)
propTypes(NoObjects)({
children: propTypes.node.isRequired,
collection: propTypes.oneOfType([
propTypes.array,
propTypes.object
]).isRequired,
emptyMessage: propTypes.node.isRequired
collection: propTypes.oneOfType([propTypes.array, propTypes.object])
.isRequired,
emptyMessage: propTypes.node.isRequired,
})
export default NoObjects

View File

@ -25,15 +25,19 @@ export class Notification extends Component {
}
render () {
return <ReactNotify ref={notification => {
if (!notification) {
return
}
return (
<ReactNotify
ref={notification => {
if (!notification) {
return
}
error = (title, body) => notification.error(title, body, 3e3)
info = (title, body) => notification.info(title, body, 3e3)
success = (title, body) => notification.success(title, body, 3e3)
}} />
error = (title, body) => notification.error(title, body, 3e3)
info = (title, body) => notification.info(title, body, 3e3)
success = (title, body) => notification.success(title, body, 3e3)
}}
/>
)
}
}

View File

@ -5,7 +5,7 @@ import React, { Component } from 'react'
@connectStore(() => {
const object = createGetObject()
return (state, props) => ({object: object(state, props)})
return (state, props) => ({ object: object(state, props) })
})
export default class ObjectName extends Component {
render () {

View File

@ -16,13 +16,13 @@ const propTypes = (propTypes, contextTypes) => target => {
if (propTypes !== undefined) {
target.propTypes = {
...target.propTypes,
...propTypes
...propTypes,
}
}
if (contextTypes !== undefined) {
target.contextTypes = {
...target.contextTypes,
...contextTypes
...contextTypes,
}
}

View File

@ -1,19 +1,20 @@
import React, { Component } from 'react'
import RFB from '@nraynaud/novnc/lib/rfb'
import { createBackoff } from 'jsonrpc-websocket-client'
import { parse as parseUrl, resolve as resolveUrl } from 'url'
import {
parse as parseUrl,
resolve as resolveUrl
} from 'url'
import { enable as enableShortcuts, disable as disableShortcuts } from 'shortcuts'
enable as enableShortcuts,
disable as disableShortcuts,
} from 'shortcuts'
import propTypes from './prop-types-decorator'
const parseRelativeUrl = url => parseUrl(resolveUrl(String(window.location), url))
const parseRelativeUrl = url =>
parseUrl(resolveUrl(String(window.location), url))
const PROTOCOL_ALIASES = {
'http:': 'ws:',
'https:': 'wss:'
'https:': 'wss:',
}
const fixProtocol = url => {
const protocol = PROTOCOL_ALIASES[url.protocol]
@ -24,7 +25,7 @@ const fixProtocol = url => {
@propTypes({
onClipboardChange: propTypes.func,
url: propTypes.string.isRequired
url: propTypes.string.isRequired,
})
export default class NoVnc extends Component {
constructor (props) {
@ -46,7 +47,10 @@ export default class NoVnc extends Component {
}
clearTimeout(this._retryTimeout)
this._retryTimeout = setTimeout(this._connect, this._retryGen.next().value)
this._retryTimeout = setTimeout(
this._connect,
this._retryGen.next().value
)
}
}
@ -87,14 +91,16 @@ export default class NoVnc extends Component {
const isSecure = url.protocol === 'wss:'
const { onClipboardChange } = this.props
const rfb = this._rfb = new RFB({
const rfb = (this._rfb = new RFB({
encrypt: isSecure,
target: this.refs.canvas,
onClipboard: onClipboardChange && ((_, text) => {
onClipboardChange(text)
}),
onUpdateState: this._onUpdateState
})
onClipboard:
onClipboardChange &&
((_, text) => {
onClipboardChange(text)
}),
onUpdateState: this._onUpdateState,
}))
// remove leading slashes from the path
//
@ -152,13 +158,15 @@ export default class NoVnc extends Component {
}
render () {
return <canvas
className='center-block'
height='480'
onMouseEnter={this._focus}
onMouseLeave={this._unfocus}
ref='canvas'
width='640'
/>
return (
<canvas
className='center-block'
height='480'
onMouseEnter={this._focus}
onMouseLeave={this._unfocus}
ref='canvas'
width='640'
/>
)
}
}

View File

@ -1,96 +1,90 @@
import _ from 'intl'
import React from 'react'
import {
startsWith
} from 'lodash'
import { startsWith } from 'lodash'
import Icon from './icon'
import propTypes from './prop-types-decorator'
import { createGetObject } from './selectors'
import { isSrWritable } from './xo'
import {
connectStore,
formatSize
} from './utils'
import { connectStore, formatSize } from './utils'
// ===================================================================
const OBJECT_TYPE_TO_ICON = {
'VM-template': 'vm',
host: 'host',
network: 'network'
network: 'network',
}
// Host, Network, VM-template.
const PoolObjectItem = propTypes({
object: propTypes.object.isRequired
})(connectStore(() => {
const getPool = createGetObject(
(_, props) => props.object.$pool
)
object: propTypes.object.isRequired,
})(
connectStore(() => {
const getPool = createGetObject((_, props) => props.object.$pool)
return (state, props) => ({
pool: getPool(state, props)
return (state, props) => ({
pool: getPool(state, props),
})
})(({ object, pool }) => {
const icon = OBJECT_TYPE_TO_ICON[object.type]
const { id } = object
return (
<span>
<Icon icon={icon} /> {`${object.name_label || id} `}
{pool && `(${pool.name_label || pool.id})`}
</span>
)
})
})(({ object, pool }) => {
const icon = OBJECT_TYPE_TO_ICON[object.type]
const { id } = object
return (
<span>
<Icon icon={icon} /> {`${object.name_label || id} `}
{pool && `(${pool.name_label || pool.id})`}
</span>
)
}))
)
// SR.
const SrItem = propTypes({
sr: propTypes.object.isRequired
})(connectStore(() => {
const getContainer = createGetObject(
(_, props) => props.sr.$container
)
sr: propTypes.object.isRequired,
})(
connectStore(() => {
const getContainer = createGetObject((_, props) => props.sr.$container)
return (state, props) => ({
container: getContainer(state, props)
return (state, props) => ({
container: getContainer(state, props),
})
})(({ sr, container }) => {
let label = `${sr.name_label || sr.id}`
if (isSrWritable(sr)) {
label += ` (${formatSize(sr.size - sr.physical_usage)} free)`
}
return (
<span>
<Icon icon='sr' /> {label}
</span>
)
})
})(({ sr, container }) => {
let label = `${sr.name_label || sr.id}`
if (isSrWritable(sr)) {
label += ` (${formatSize(sr.size - sr.physical_usage)} free)`
}
return (
<span>
<Icon icon='sr' /> {label}
</span>
)
}))
)
// VM.
const VmItem = propTypes({
vm: propTypes.object.isRequired
})(connectStore(() => {
const getContainer = createGetObject(
(_, props) => props.vm.$container
)
vm: propTypes.object.isRequired,
})(
connectStore(() => {
const getContainer = createGetObject((_, props) => props.vm.$container)
return (state, props) => ({
container: getContainer(state, props)
})
})(({ vm, container }) => (
<span>
<Icon icon={`vm-${vm.power_state.toLowerCase()}`} /> {vm.name_label || vm.id}
{container && ` (${container.name_label || container.id})`}
</span>
)))
return (state, props) => ({
container: getContainer(state, props),
})
})(({ vm, container }) => (
<span>
<Icon icon={`vm-${vm.power_state.toLowerCase()}`} />{' '}
{vm.name_label || vm.id}
{container && ` (${container.name_label || container.id})`}
</span>
))
)
const VgpuItem = connectStore(() => ({
vgpuType: createGetObject(
(_, props) => props.vgpu.vgpuType
)
vgpuType: createGetObject((_, props) => props.vgpu.vgpuType),
}))(({ vgpu, vgpuType }) => (
<span>
<Icon icon='vgpu' /> {vgpuType.modelName}
@ -111,11 +105,7 @@ const xoItemToRender = {
<Icon icon='remote' /> {remote.value.name}
</span>
),
role: role => (
<span>
{role.name}
</span>
),
role: role => <span>{role.name}</span>,
user: user => (
<span>
<Icon icon='user' /> {user.email}
@ -136,7 +126,7 @@ const xoItemToRender = {
<Icon icon='ip' /> {ipPool.name}
</span>
),
ipAddress: ({label, used}) => {
ipAddress: ({ label, used }) => {
if (used) {
return <strong className='text-warning'>{label}</strong>
}
@ -152,7 +142,8 @@ const xoItemToRender = {
VDI: vdi => (
<span>
<Icon icon='disk' /> {vdi.name_label} {vdi.name_description && <span> ({vdi.name_description})</span>}
<Icon icon='disk' /> {vdi.name_label}{' '}
{vdi.name_description && <span> ({vdi.name_description})</span>}
</span>
),
@ -169,16 +160,18 @@ const xoItemToRender = {
'VM-snapshot': vm => <VmItem vm={vm} />,
'VM-controller': vm => (
<span>
<Icon icon='host' />
{' '}
<VmItem vm={vm} />
<Icon icon='host' /> <VmItem vm={vm} />
</span>
),
// PIF.
PIF: pif => (
<span>
<Icon icon='network' color={pif.carrier ? 'text-success' : 'text-danger'} /> {pif.device} ({pif.deviceName})
<Icon
icon='network'
color={pif.carrier ? 'text-success' : 'text-danger'}
/>{' '}
{pif.device} ({pif.deviceName})
</span>
),
@ -195,7 +188,8 @@ const xoItemToRender = {
vgpuType: type => (
<span>
<Icon icon='gpu' /> {type.modelName} ({type.vendorName}) {type.maxResolutionX}x{type.maxResolutionY}
<Icon icon='gpu' /> {type.modelName} ({type.vendorName}){' '}
{type.maxResolutionX}x{type.maxResolutionY}
</span>
),
@ -203,19 +197,21 @@ const xoItemToRender = {
<span>
{startsWith(group.name_label, 'Group of ')
? group.name_label.slice(9)
: group.name_label
}
: group.name_label}
</span>
)
),
}
const renderXoItem = (item, {
className
} = {}) => {
const renderXoItem = (item, { className } = {}) => {
const { id, type, label } = item
if (item.removed) {
return <span key={id} className='text-danger'> <Icon icon='alarm' /> {id}</span>
return (
<span key={id} className='text-danger'>
{' '}
<Icon icon='alarm' /> {id}
</span>
)
}
if (!type) {
@ -250,13 +246,17 @@ const GenericXoItem = connectStore(() => {
const getObject = createGetObject()
return (state, props) => ({
xoItem: getObject(state, props)
xoItem: getObject(state, props),
})
})(({ xoItem, ...props }) => xoItem
? renderXoItem(xoItem, props)
: renderXoUnknownItem()
})(
({ xoItem, ...props }) =>
xoItem ? renderXoItem(xoItem, props) : renderXoUnknownItem()
)
export const renderXoItemFromId = (id, props) => <GenericXoItem {...props} id={id} />
export const renderXoItemFromId = (id, props) => (
<GenericXoItem {...props} id={id} />
)
export const renderXoUnknownItem = () => <span className='text-muted'>{_('errorNoSuchItem')}</span>
export const renderXoUnknownItem = () => (
<span className='text-muted'>{_('errorNoSuchItem')}</span>
)

View File

@ -2,13 +2,7 @@ import classNames from 'classnames'
import later from 'later'
import React from 'react'
import { FormattedDate, FormattedTime } from 'react-intl'
import {
forEach,
includes,
isArray,
map,
sortedIndex
} from 'lodash'
import { forEach, includes, isArray, map, sortedIndex } from 'lodash'
import _ from './intl'
import Button from './button'
@ -33,7 +27,7 @@ const PREVIEW_SLIDER_STYLE = { width: '400px' }
// ===================================================================
const UNITS = [ 'minute', 'hour', 'monthDay', 'month', 'weekDay' ]
const UNITS = ['minute', 'hour', 'monthDay', 'month', 'weekDay']
const MINUTES_RANGE = [2, 30]
const HOURS_RANGE = [2, 12]
@ -43,12 +37,7 @@ const MONTHS_RANGE = [2, 6]
const MIN_PREVIEWS = 5
const MAX_PREVIEWS = 20
const MONTHS = [
[ 0, 1, 2 ],
[ 3, 4, 5 ],
[ 6, 7, 8 ],
[ 9, 10, 11 ]
]
const MONTHS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
const DAYS = (() => {
const days = []
@ -66,11 +55,7 @@ const DAYS = (() => {
return days
})()
const WEEK_DAYS = [
[ 0, 1, 2 ],
[ 3, 4, 5 ],
[ 6 ]
]
const WEEK_DAYS = [[0, 1, 2], [3, 4, 5], [6]]
const HOURS = (() => {
const hours = []
@ -105,7 +90,7 @@ const PICKTIME_TO_ID = {
hour: 1,
monthDay: 2,
month: 3,
weekDay: 4
weekDay: 4,
}
const TIME_FORMAT = {
@ -122,24 +107,30 @@ const TIME_FORMAT = {
// Therefore we can use UTC everywhere and say to the user that the
// previews are in the configured timezone.
timeZone: 'UTC'
timeZone: 'UTC',
}
// ===================================================================
// monthNum: [ 0 : 11 ]
const getMonthName = (monthNum) =>
const getMonthName = monthNum => (
<FormattedDate value={Date.UTC(1970, monthNum)} month='long' timeZone='UTC' />
)
// dayNum: [ 0 : 6 ]
const getDayName = (dayNum) =>
const getDayName = dayNum => (
// January, 1970, 5th => Monday
<FormattedDate value={Date.UTC(1970, 0, 4 + dayNum)} weekday='long' timeZone='UTC' />
<FormattedDate
value={Date.UTC(1970, 0, 4 + dayNum)}
weekday='long'
timeZone='UTC'
/>
)
// ===================================================================
@propTypes({
cronPattern: propTypes.string.isRequired
cronPattern: propTypes.string.isRequired,
})
export class SchedulePreview extends Component {
render () {
@ -162,7 +153,12 @@ export class SchedulePreview extends Component {
{_('cronPattern')} <strong>{cronPattern}</strong>
</div>
<div className='mb-1' style={PREVIEW_SLIDER_STYLE}>
<Range min={MIN_PREVIEWS} max={MAX_PREVIEWS} onChange={this.linkState('value')} value={+value} />
<Range
min={MIN_PREVIEWS}
max={MAX_PREVIEWS}
onChange={this.linkState('value')}
value={+value}
/>
</div>
<ul className='list-group'>
{map(dates, (date, id) => (
@ -183,7 +179,7 @@ export class SchedulePreview extends Component {
children: propTypes.any.isRequired,
onChange: propTypes.func.isRequired,
tdId: propTypes.number.isRequired,
value: propTypes.bool.isRequired
value: propTypes.bool.isRequired,
})
class ToggleTd extends Component {
_onClick = () => {
@ -195,10 +191,7 @@ class ToggleTd extends Component {
const { props } = this
return (
<td
className={classNames(
'text-xs-center',
props.value && 'table-success'
)}
className={classNames('text-xs-center', props.value && 'table-success')}
onClick={this._onClick}
style={CLICKABLE}
>
@ -215,11 +208,11 @@ class ToggleTd extends Component {
options: propTypes.array.isRequired,
optionRenderer: propTypes.func,
onChange: propTypes.func.isRequired,
value: propTypes.array.isRequired
value: propTypes.array.isRequired,
})
class TableSelect extends Component {
static defaultProps = {
optionRenderer: value => value
optionRenderer: value => value,
}
_reset = () => {
@ -248,38 +241,33 @@ class TableSelect extends Component {
}
render () {
const {
labelId,
options,
optionRenderer,
value
} = this.props
const { labelId, options, optionRenderer, value } = this.props
return <div>
<table className='table table-bordered table-sm'>
<tbody>
{map(options, (line, i) => (
<tr key={i}>
{map(line, tdOption => (
<ToggleTd
children={optionRenderer(tdOption)}
tdId={tdOption}
key={tdOption}
onChange={this._handleChange}
value={includes(value, tdOption)}
/>
))}
</tr>
))}
</tbody>
</table>
<Button
className='pull-right'
onClick={this._reset}
>
{_(`selectTableAll${labelId}`)} {value && !value.length && <Icon icon='success' />}
</Button>
</div>
return (
<div>
<table className='table table-bordered table-sm'>
<tbody>
{map(options, (line, i) => (
<tr key={i}>
{map(line, tdOption => (
<ToggleTd
children={optionRenderer(tdOption)}
tdId={tdOption}
key={tdOption}
onChange={this._handleChange}
value={includes(value, tdOption)}
/>
))}
</tr>
))}
</tbody>
</table>
<Button className='pull-right' onClick={this._reset}>
{_(`selectTableAll${labelId}`)}{' '}
{value && !value.length && <Icon icon='success' />}
</Button>
</div>
)
}
}
@ -317,7 +305,7 @@ const valueToCron = value => {
onChange: propTypes.func.isRequired,
range: propTypes.array,
labelId: propTypes.string.isRequired,
value: propTypes.any.isRequired
value: propTypes.any.isRequired,
})
class TimePicker extends Component {
_update = cron => {
@ -329,7 +317,7 @@ class TimePicker extends Component {
this.setState({
periodic,
tableValue: periodic ? tableValue : newValue,
rangeValue: periodic ? newValue : rangeValue
rangeValue: periodic ? newValue : rangeValue,
})
}
@ -348,53 +336,63 @@ class TimePicker extends Component {
}
_tableTab = () => this._onChange(this.state.tableValue || [])
_periodicTab = () => this._onChange(this.state.rangeValue || this.props.range[0])
_periodicTab = () =>
this._onChange(this.state.rangeValue || this.props.range[0])
render () {
const {
headerAddon,
labelId,
options,
optionRenderer,
range
} = this.props
const { headerAddon, labelId, options, optionRenderer, range } = this.props
const {
periodic,
tableValue,
rangeValue
} = this.state
const { periodic, tableValue, rangeValue } = this.state
return <Card>
<CardHeader>
{_(`scheduling${labelId}`)}
{headerAddon}
</CardHeader>
<CardBlock>
{range && <ul className='nav nav-tabs mb-1'>
<li className='nav-item'>
<a onClick={this._tableTab} className={classNames('nav-link', !periodic && 'active')} style={CLICKABLE}>
{_(`schedulingEachSelected${labelId}`)}
</a>
</li>
<li className='nav-item'>
<a onClick={this._periodicTab} className={classNames('nav-link', periodic && 'active')} style={CLICKABLE}>
{_(`schedulingEveryN${labelId}`)}
</a>
</li>
</ul>}
{periodic
? <Range ref='range' min={range[0]} max={range[1]} onChange={this._onChange} value={rangeValue} />
: <TableSelect
labelId={labelId}
onChange={this._onChange}
options={options}
optionRenderer={optionRenderer}
value={tableValue || []}
/>
}
</CardBlock>
</Card>
return (
<Card>
<CardHeader>
{_(`scheduling${labelId}`)}
{headerAddon}
</CardHeader>
<CardBlock>
{range && (
<ul className='nav nav-tabs mb-1'>
<li className='nav-item'>
<a
onClick={this._tableTab}
className={classNames('nav-link', !periodic && 'active')}
style={CLICKABLE}
>
{_(`schedulingEachSelected${labelId}`)}
</a>
</li>
<li className='nav-item'>
<a
onClick={this._periodicTab}
className={classNames('nav-link', periodic && 'active')}
style={CLICKABLE}
>
{_(`schedulingEveryN${labelId}`)}
</a>
</li>
</ul>
)}
{periodic ? (
<Range
ref='range'
min={range[0]}
max={range[1]}
onChange={this._onChange}
value={rangeValue}
/>
) : (
<TableSelect
labelId={labelId}
onChange={this._onChange}
options={options}
optionRenderer={optionRenderer}
value={tableValue || []}
/>
)}
</CardBlock>
</Card>
)
}
}
@ -408,11 +406,11 @@ const isWeekDayMode = ({ monthDayPattern, weekDayPattern }) => {
@propTypes({
monthDayPattern: propTypes.string.isRequired,
weekDayPattern: propTypes.string.isRequired
weekDayPattern: propTypes.string.isRequired,
})
class DayPicker extends Component {
state = {
weekDayMode: isWeekDayMode(this.props)
weekDayMode: isWeekDayMode(this.props),
}
componentWillReceiveProps (props) {
@ -424,7 +422,7 @@ class DayPicker extends Component {
}
_setWeekDayMode = weekDayMode => {
this.props.onChange([ '*', '*' ])
this.props.onChange(['*', '*'])
this.setState({ weekDayMode })
}
@ -433,7 +431,7 @@ class DayPicker extends Component {
this.props.onChange([
isMonthDayPattern ? cron : '*',
isMonthDayPattern ? '*' : cron
isMonthDayPattern ? '*' : cron,
])
}
@ -442,22 +440,34 @@ class DayPicker extends Component {
const { weekDayMode } = this.state
const dayModeToggle = (
<Tooltip content={_(weekDayMode ? 'schedulingSetMonthDayMode' : 'schedulingSetWeekDayMode')}>
<span className='pull-right'><Toggle onChange={this._setWeekDayMode} iconSize={1} value={weekDayMode} /></span>
<Tooltip
content={_(
weekDayMode ? 'schedulingSetMonthDayMode' : 'schedulingSetWeekDayMode'
)}
>
<span className='pull-right'>
<Toggle
onChange={this._setWeekDayMode}
iconSize={1}
value={weekDayMode}
/>
</span>
</Tooltip>
)
return <TimePicker
headerAddon={dayModeToggle}
key={weekDayMode ? 'week' : 'month'}
labelId='Day'
optionRenderer={weekDayMode ? getDayName : undefined}
options={weekDayMode ? WEEK_DAYS : DAYS}
onChange={this._onChange}
range={MONTH_DAYS_RANGE}
setWeekDayMode={this._setWeekDayMode}
value={weekDayMode ? weekDayPattern : monthDayPattern}
/>
return (
<TimePicker
headerAddon={dayModeToggle}
key={weekDayMode ? 'week' : 'month'}
labelId='Day'
optionRenderer={weekDayMode ? getDayName : undefined}
options={weekDayMode ? WEEK_DAYS : DAYS}
onChange={this._onChange}
range={MONTH_DAYS_RANGE}
setWeekDayMode={this._setWeekDayMode}
value={weekDayMode ? weekDayPattern : monthDayPattern}
/>
)
}
}
@ -469,8 +479,8 @@ class DayPicker extends Component {
timezone: propTypes.string,
value: propTypes.shape({
cronPattern: propTypes.string.isRequired,
timezone: propTypes.string
})
timezone: propTypes.string,
}),
})
export default class Scheduler extends Component {
constructor (props) {
@ -484,20 +494,21 @@ export default class Scheduler extends Component {
this.props.onChange({
cronPattern: cronPattern.join(' '),
timezone: this._getTimezone()
timezone: this._getTimezone(),
})
}
forEach(UNITS, unit => {
this[`_${unit}Change`] = cron => this._onCronChange({ [unit]: cron })
})
this._dayChange = ([ monthDay, weekDay ]) => this._onCronChange({ monthDay, weekDay })
this._dayChange = ([monthDay, weekDay]) =>
this._onCronChange({ monthDay, weekDay })
}
_onTimezoneChange = timezone => {
this.props.onChange({
cronPattern: this._getCronPattern(),
timezone
timezone,
})
}
@ -559,7 +570,10 @@ export default class Scheduler extends Component {
<Row>
<Col>
<hr />
<TimezonePicker value={timezone} onChange={this._onTimezoneChange} />
<TimezonePicker
value={timezone}
onChange={this._onTimezoneChange}
/>
</Col>
</Row>
</div>

View File

@ -8,7 +8,7 @@ import { omit } from 'lodash'
@propTypes({
multi: propTypes.bool,
label: propTypes.node,
onChange: propTypes.func.isRequired
onChange: propTypes.func.isRequired,
})
export default class SelectFiles extends Component {
_onChange = e => {
@ -19,14 +19,16 @@ export default class SelectFiles extends Component {
}
render () {
return <label className='btn btn-secondary btn-file hidden'>
<Icon icon='file' /> {this.props.label || _('browseFiles')}
<input
{...omit(this.props, [ 'hidden', 'label', 'onChange', 'multi' ])}
hidden
onChange={this._onChange}
type='file'
/>
</label>
return (
<label className='btn btn-secondary btn-file hidden'>
<Icon icon='file' /> {this.props.label || _('browseFiles')}
<input
{...omit(this.props, ['hidden', 'label', 'onChange', 'multi'])}
hidden
onChange={this._onChange}
type='file'
/>
</label>
)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ import {
orderBy,
pickBy,
size,
slice
slice,
} from 'lodash'
import invoke from './invoke'
@ -26,9 +26,8 @@ import { EMPTY_ARRAY, EMPTY_OBJECT } from './utils'
export {
// That's usually the name we want to import.
createSelector,
// But selectors.create is nice too :)
createSelector as create
createSelector as create,
} from 'reselect'
// -------------------------------------------------------------------
@ -93,9 +92,7 @@ const _create2 = (...inputs) => {
const args = new Array(n)
for (let i = 0, j = 0; i < n; ++i) {
const input = inputs[i]
args[i] = input === _SELECTOR_PLACEHOLDER
? arguments[j++]
: input
args[i] = input === _SELECTOR_PLACEHOLDER ? arguments[j++] : input
}
return resultFn.apply(this, args)
@ -106,23 +103,19 @@ const _create2 = (...inputs) => {
// Generic selector creators.
export const createCounter = (collection, predicate) =>
_create2(
collection,
predicate,
(collection, predicate) => {
if (!predicate) {
return size(collection)
}
let count = 0
forEach(collection, item => {
if (predicate(item)) {
++count
}
})
return count
_create2(collection, predicate, (collection, predicate) => {
if (!predicate) {
return size(collection)
}
)
let count = 0
forEach(collection, item => {
if (predicate(item)) {
++count
}
})
return count
})
// Creates an object selector from an object selector and a properties
// selector.
@ -130,19 +123,18 @@ export const createCounter = (collection, predicate) =>
// Should only be used with a reasonable number of properties.
export const createPicker = (object, props) =>
_create2(
object, props,
_createCollectionWrapper(
(object, props) => {
const values = {}
forEach(props, prop => {
const value = object[prop]
if (value) {
values[prop] = value
}
})
return values
}
)
object,
props,
_createCollectionWrapper((object, props) => {
const values = {}
forEach(props, prop => {
const value = object[prop]
if (value) {
values[prop] = value
}
})
return values
})
)
// Special cases:
@ -153,52 +145,38 @@ export const createFilter = (collection, predicate) =>
collection,
predicate,
_createCollectionWrapper(
(collection, predicate) => predicate === false
? (isArrayLike(collection) ? EMPTY_ARRAY : EMPTY_OBJECT)
: predicate
? (isArrayLike(collection) ? filter : pickBy)(collection, predicate)
: collection
(collection, predicate) =>
predicate === false
? isArrayLike(collection) ? EMPTY_ARRAY : EMPTY_OBJECT
: predicate
? (isArrayLike(collection) ? filter : pickBy)(collection, predicate)
: collection
)
)
export const createFinder = (collection, predicate) =>
_create2(
collection,
predicate,
find
)
_create2(collection, predicate, find)
export const createGroupBy = (collection, getter) =>
_create2(
collection,
getter,
groupBy
)
_create2(collection, getter, groupBy)
export const createPager = (array, page, n = 25) =>
_create2(
array,
page,
n,
_createCollectionWrapper(
(array, page, n) => {
const start = (page - 1) * n
return slice(array, start, start + n)
}
)
_createCollectionWrapper((array, page, n) => {
const start = (page - 1) * n
return slice(array, start, start + n)
})
)
export const createSort = (
collection,
getter = 'name_label',
order = 'asc'
) => _create2(collection, getter, order, orderBy)
export const createSort = (collection, getter = 'name_label', order = 'asc') =>
_create2(collection, getter, order, orderBy)
export const createSumBy = (itemsSelector, iterateeSelector) =>
_create2(
itemsSelector,
iterateeSelector,
(items, iteratee) => map(items, iteratee).reduce(add, 0)
_create2(itemsSelector, iterateeSelector, (items, iteratee) =>
map(items, iteratee).reduce(add, 0)
)
export const createTop = (collection, iteratee, n) =>
@ -206,15 +184,13 @@ export const createTop = (collection, iteratee, n) =>
collection,
iteratee,
n,
_createCollectionWrapper(
(objects, iteratee, n) => {
let results = orderBy(objects, iteratee, 'desc')
if (n < results.length) {
results.length = n
}
return results
_createCollectionWrapper((objects, iteratee, n) => {
const results = orderBy(objects, iteratee, 'desc')
if (n < results.length) {
results.length = n
}
)
return results
})
)
// ===================================================================
@ -222,9 +198,8 @@ export const createTop = (collection, iteratee, n) =>
export const areObjectsFetched = state => state.objects.fetched
const _getId = (state, { routeParams, id }) => routeParams
? routeParams.id
: id
const _getId = (state, { routeParams, id }) =>
routeParams ? routeParams.id : id
export const getLang = state => state.lang
@ -238,9 +213,10 @@ export const getCheckPermissions = invoke(() => {
state => state.objects,
(permissions, objects) => {
objects = objects.all
const getObject = id => (objects[id] || EMPTY_OBJECT)
const getObject = id => objects[id] || EMPTY_OBJECT
return (id, permission) => checkPermissions(permissions, getObject, id, permission)
return (id, permission) =>
checkPermissions(permissions, getObject, id, permission)
}
)
@ -268,7 +244,7 @@ const _getPermissionsPredicate = invoke(() => {
state => state.objects,
(permissions, objects) => {
objects = objects.all
const getObject = id => (objects[id] || EMPTY_OBJECT)
const getObject = id => objects[id] || EMPTY_OBJECT
return id => checkPermissions(permissions, getObject, id.id || id, 'view')
}
@ -298,33 +274,36 @@ export const isAdmin = (...args) => {
// Common selector creators.
// Creates an object selector from an id selector.
export const createGetObject = (idSelector = _getId) =>
(state, props, useResourceSet) => {
const object = state.objects.all[idSelector(state, props)]
if (!object) {
return
}
if (useResourceSet) {
return object
}
const predicate = _getPermissionsPredicate(state)
if (!predicate) {
if (predicate == null) {
return object // no filtering
}
// predicate is false.
return
}
if (predicate(object)) {
return object
}
export const createGetObject = (idSelector = _getId) => (
state,
props,
useResourceSet
) => {
const object = state.objects.all[idSelector(state, props)]
if (!object) {
return
}
if (useResourceSet) {
return object
}
const predicate = _getPermissionsPredicate(state)
if (!predicate) {
if (predicate == null) {
return object // no filtering
}
// predicate is false.
return
}
if (predicate(object)) {
return object
}
}
// Specialized createSort() configured for a given type.
export const createSortForType = invoke(() => {
const iterateesByType = {
@ -335,30 +314,27 @@ export const createSortForType = invoke(() => {
tag: tag => tag,
VBD: vbd => vbd.position,
'VDI-snapshot': snapshot => snapshot.snapshot_time,
'VM-snapshot': snapshot => snapshot.snapshot_time
'VM-snapshot': snapshot => snapshot.snapshot_time,
}
const defaultIteratees = [
object => object.$pool,
object => object.name_label
]
const defaultIteratees = [object => object.$pool, object => object.name_label]
const getIteratees = type => iterateesByType[type] || defaultIteratees
const ordersByType = {
message: 'desc',
'VDI-snapshot': 'desc',
'VM-snapshot': 'desc'
'VM-snapshot': 'desc',
}
const getOrders = type => ordersByType[type]
const autoSelector = (type, fn) => isFunction(type)
? (state, props) => fn(type(state, props))
: [ fn(type) ]
const autoSelector = (type, fn) =>
isFunction(type) ? (state, props) => fn(type(state, props)) : [fn(type)]
return (type, collection) => createSort(
collection,
autoSelector(type, getIteratees),
autoSelector(type, getOrders)
)
return (type, collection) =>
createSort(
collection,
autoSelector(type, getIteratees),
autoSelector(type, getOrders)
)
})
// Add utility methods to a collection selector.
@ -390,17 +366,17 @@ const _extendCollectionSelector = (selector, objectsType) => {
// count, groupBy and sort can be chained.
const _addFilter = selector => {
selector.filter = predicate => _addCount(_addGroupBy(_addSort(
createFilter(selector, predicate)
)))
selector.filter = predicate =>
_addCount(_addGroupBy(_addSort(createFilter(selector, predicate))))
return selector
}
_addFilter(selector)
// filter, groupBy and sort can be chained.
selector.pick = idsSelector => _addFind(_addFilter(_addGroupBy(_addSort(
createPicker(selector, idsSelector)
))))
selector.pick = idsSelector =>
_addFind(
_addFilter(_addGroupBy(_addSort(createPicker(selector, idsSelector))))
)
return selector
}
@ -426,10 +402,10 @@ export const createGetObjectsOfType = type => {
? (state, props) => state.objects.byType[type(state, props)] || EMPTY_OBJECT
: state => state.objects.byType[type] || EMPTY_OBJECT
return _extendCollectionSelector(createFilter(
getObjects,
_getPermissionsPredicate
), type)
return _extendCollectionSelector(
createFilter(getObjects, _getPermissionsPredicate),
type
)
}
export const createGetTags = collectionSelectors => {
@ -437,31 +413,34 @@ export const createGetTags = collectionSelectors => {
collectionSelectors = [
createGetObjectsOfType('host'),
createGetObjectsOfType('pool'),
createGetObjectsOfType('VM')
createGetObjectsOfType('VM'),
]
}
const getTags = create(
collectionSelectors,
(...collections) => {
const tags = {}
const getTags = create(collectionSelectors, (...collections) => {
const tags = {}
const addTag = tag => { tags[tag] = null }
const addItemTags = item => { forEach(item.tags, addTag) }
const addCollectionTags = collection => { forEach(collection, addItemTags) }
forEach(collections, addCollectionTags)
return keys(tags)
const addTag = tag => {
tags[tag] = null
}
)
const addItemTags = item => {
forEach(item.tags, addTag)
}
const addCollectionTags = collection => {
forEach(collection, addItemTags)
}
forEach(collections, addCollectionTags)
return keys(tags)
})
return _extendCollectionSelector(getTags, 'tag')
}
export const createGetVmLastShutdownTime = (getVmId = (_, {vm}) => vm != null ? vm.id : undefined) => create(
getVmId,
createGetObjectsOfType('message'),
(vmId, messages) => {
export const createGetVmLastShutdownTime = (
getVmId = (_, { vm }) => (vm != null ? vm.id : undefined)
) =>
create(getVmId, createGetObjectsOfType('message'), (vmId, messages) => {
let max = null
forEach(messages, message => {
if (
@ -473,16 +452,17 @@ export const createGetVmLastShutdownTime = (getVmId = (_, {vm}) => vm != null ?
}
})
return max
}
)
})
export const createGetObjectMessages = objectSelector =>
createGetObjectsOfType('message').filter(
create(
(...args) => objectSelector(...args).id,
id => message => message.$object === id
createGetObjectsOfType('message')
.filter(
create(
(...args) => objectSelector(...args).id,
id => message => message.$object === id
)
)
).sort()
.sort()
// Example of use:
// import store from 'store'
@ -492,27 +472,35 @@ export const getObject = createGetObject((_, id) => id)
export const createDoesHostNeedRestart = hostSelector => {
// XS < 7.1
const patchRequiresReboot = createGetObjectsOfType('pool_patch').pick(
// Returns the first patch of the host which requires it to be
// restarted.
create(
createGetObjectsOfType('host_patch').pick(
(state, props) => {
const host = hostSelector(state, props)
return host && host.patches
}
).filter(create(
(state, props) => {
const host = hostSelector(state, props)
return host && host.startTime
},
startTime => patch => patch.time > startTime
)),
hostPatches => map(hostPatches, hostPatch => hostPatch.pool_patch)
const patchRequiresReboot = createGetObjectsOfType('pool_patch')
.pick(
// Returns the first patch of the host which requires it to be
// restarted.
create(
createGetObjectsOfType('host_patch')
.pick((state, props) => {
const host = hostSelector(state, props)
return host && host.patches
})
.filter(
create(
(state, props) => {
const host = hostSelector(state, props)
return host && host.startTime
},
startTime => patch => patch.time > startTime
)
),
hostPatches => map(hostPatches, hostPatch => hostPatch.pool_patch)
)
)
).find([ ({ guidance }) => find(guidance, action =>
action === 'restartHost' || action === 'restartXapi'
) ])
.find([
({ guidance }) =>
find(
guidance,
action => action === 'restartHost' || action === 'restartXapi'
),
])
return create(
hostSelector,
@ -524,23 +512,21 @@ export const createDoesHostNeedRestart = hostSelector => {
export const createGetHostMetrics = hostSelector =>
create(
hostSelector,
_createCollectionWrapper(
hosts => {
const metrics = {
count: 0,
cpus: 0,
memoryTotal: 0,
memoryUsage: 0
}
forEach(hosts, host => {
metrics.count++
metrics.cpus += host.cpus.cores
metrics.memoryTotal += host.memory.size
metrics.memoryUsage += host.memory.usage
})
return metrics
_createCollectionWrapper(hosts => {
const metrics = {
count: 0,
cpus: 0,
memoryTotal: 0,
memoryUsage: 0,
}
)
forEach(hosts, host => {
metrics.count++
metrics.cpus += host.cpus.cores
metrics.memoryTotal += host.memory.size
metrics.memoryUsage += host.memory.usage
})
return metrics
})
)
export const createGetVmDisks = vmSelector =>
@ -549,10 +535,8 @@ export const createGetVmDisks = vmSelector =>
createGetObjectsOfType('VBD').pick(
(state, props) => vmSelector(state, props).$VBDs
),
_createCollectionWrapper(vbds => map(vbds, vbd =>
vbd.is_cd_drive
? undefined
: vbd.VDI
))
_createCollectionWrapper(vbds =>
map(vbds, vbd => (vbd.is_cd_drive ? undefined : vbd.VDI))
)
)
)

View File

@ -32,7 +32,8 @@ const shallowEqual = (c1, c2) => {
}
let n = 0
for (const _ in c2) { // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-unused-vars
for (const _ in c2) {
++n
}

View File

@ -6,14 +6,13 @@ const SINGLE_LINE_STYLE = { display: 'flex' }
const COL_STYLE = { marginTop: 'auto', marginBottom: 'auto' }
const SingleLineRow = propTypes({
className: propTypes.string
})(({
children,
className
}) => <div
className={`${className || ''} row`}
style={SINGLE_LINE_STYLE}
>
{React.Children.map(children, child => child && cloneElement(child, { style: COL_STYLE }))}
</div>)
className: propTypes.string,
})(({ children, className }) => (
<div className={`${className || ''} row`} style={SINGLE_LINE_STYLE}>
{React.Children.map(
children,
child => child && cloneElement(child, { style: COL_STYLE })
)}
</div>
))
export { SingleLineRow as default }

View File

@ -7,11 +7,7 @@ import Shortcuts from 'shortcuts'
import { Portal } from 'react-overlays'
import { routerShape } from 'react-router/lib/PropTypes'
import { Set } from 'immutable'
import {
Dropdown,
MenuItem,
Pagination
} from 'react-bootstrap-4/lib'
import { Dropdown, MenuItem, Pagination } from 'react-bootstrap-4/lib'
import {
ceil,
filter,
@ -19,7 +15,7 @@ import {
forEach,
isEmpty,
isFunction,
map
map,
} from 'lodash'
import ActionRowButton from '../action-row-button'
@ -40,7 +36,7 @@ import {
createFilter,
createPager,
createSelector,
createSort
createSort,
} from '../selectors'
import styles from './index.css'
@ -50,7 +46,7 @@ import styles from './index.css'
@propTypes({
filters: propTypes.object,
onChange: propTypes.func.isRequired,
value: propTypes.string.isRequired
value: propTypes.string.isRequired,
})
class TableFilter extends Component {
_cleanFilter = () => this._setFilter('')
@ -75,22 +71,26 @@ class TableFilter extends Component {
return (
<div className='input-group'>
{isEmpty(props.filters)
? <span className='input-group-addon'><Icon icon='search' /></span>
: <span className='input-group-btn'>
{isEmpty(props.filters) ? (
<span className='input-group-addon'>
<Icon icon='search' />
</span>
) : (
<span className='input-group-btn'>
<Dropdown id='filter'>
<DropdownToggle bsStyle='info'>
<Icon icon='search' />
</DropdownToggle>
<DropdownMenu>
{map(props.filters, (filter, label) =>
{map(props.filters, (filter, label) => (
<MenuItem key={label} onClick={() => this._setFilter(filter)}>
{_(label)}
</MenuItem>
)}
))}
</DropdownMenu>
</Dropdown>
</span>}
</span>
)}
<DebouncedInput
className='form-control'
onChange={this._onChange}
@ -122,7 +122,7 @@ class TableFilter extends Component {
columnId: propTypes.number.isRequired,
name: propTypes.node,
sort: propTypes.func,
sortIcon: propTypes.string
sortIcon: propTypes.string,
})
class ColumnHead extends Component {
_sort = () => {
@ -160,7 +160,7 @@ class ColumnHead extends Component {
// ===================================================================
@propTypes({
indeterminate: propTypes.bool.isRequired
indeterminate: propTypes.bool.isRequired,
})
class Checkbox extends Component {
componentDidUpdate () {
@ -185,81 +185,81 @@ class Checkbox extends Component {
// ===================================================================
const actionsShape = propTypes.arrayOf(propTypes.shape({
// groupedActions: the function will be called with an array of the selected items in parameters
// individualActions: the function will be called with the related item in parameters
disabled: propTypes.oneOfType([ propTypes.bool, propTypes.func ]),
handler: propTypes.func.isRequired,
icon: propTypes.string.isRequired,
label: propTypes.node.isRequired,
level: propTypes.oneOf([ 'warning', 'danger' ])
}))
const actionsShape = propTypes.arrayOf(
propTypes.shape({
// groupedActions: the function will be called with an array of the selected items in parameters
// individualActions: the function will be called with the related item in parameters
disabled: propTypes.oneOfType([propTypes.bool, propTypes.func]),
handler: propTypes.func.isRequired,
icon: propTypes.string.isRequired,
label: propTypes.node.isRequired,
level: propTypes.oneOf(['warning', 'danger']),
})
)
class IndividualAction extends Component {
_getIsDisabled = createSelector(
() => this.props.disabled,
() => this.props.item,
() => this.props.userData,
(disabled, item, userData) => isFunction(disabled)
? disabled(item, userData)
: disabled
(disabled, item, userData) =>
isFunction(disabled) ? disabled(item, userData) : disabled
)
render () {
const { icon, label, level, handler, item } = this.props
return <ActionRowButton
btnStyle={level}
disabled={this._getIsDisabled()}
handler={handler}
handlerParam={item}
icon={icon}
tooltip={label}
/>
return (
<ActionRowButton
btnStyle={level}
disabled={this._getIsDisabled()}
handler={handler}
handlerParam={item}
icon={icon}
tooltip={label}
/>
)
}
}
@propTypes({
defaultColumn: propTypes.number,
defaultFilter: propTypes.string,
collection: propTypes.oneOfType([
propTypes.array,
propTypes.object
]).isRequired,
columns: propTypes.arrayOf(propTypes.shape({
component: propTypes.func,
default: propTypes.bool,
name: propTypes.node,
itemRenderer: propTypes.func,
sortCriteria: propTypes.oneOfType([
propTypes.func,
propTypes.string
]),
sortOrder: propTypes.string,
textAlign: propTypes.string
})).isRequired,
filterContainer: propTypes.func,
filters: propTypes.object,
groupedActions: actionsShape,
individualActions: actionsShape,
itemsPerPage: propTypes.number,
paginationContainer: propTypes.func,
rowAction: propTypes.func,
rowLink: propTypes.oneOfType([
propTypes.func,
propTypes.string
]),
// DOM node selector like body or .my-class
// The shortcuts will be enabled when the node is focused
shortcutsTarget: propTypes.string,
stateUrlParam: propTypes.string,
userData: propTypes.any
}, {
router: routerShape
})
@propTypes(
{
defaultColumn: propTypes.number,
defaultFilter: propTypes.string,
collection: propTypes.oneOfType([propTypes.array, propTypes.object])
.isRequired,
columns: propTypes.arrayOf(
propTypes.shape({
component: propTypes.func,
default: propTypes.bool,
name: propTypes.node,
itemRenderer: propTypes.func,
sortCriteria: propTypes.oneOfType([propTypes.func, propTypes.string]),
sortOrder: propTypes.string,
textAlign: propTypes.string,
})
).isRequired,
filterContainer: propTypes.func,
filters: propTypes.object,
groupedActions: actionsShape,
individualActions: actionsShape,
itemsPerPage: propTypes.number,
paginationContainer: propTypes.func,
rowAction: propTypes.func,
rowLink: propTypes.oneOfType([propTypes.func, propTypes.string]),
// DOM node selector like body or .my-class
// The shortcuts will be enabled when the node is focused
shortcutsTarget: propTypes.string,
stateUrlParam: propTypes.string,
userData: propTypes.any,
},
{
router: routerShape,
}
)
export default class SortedTable extends Component {
static defaultProps = {
itemsPerPage: 10
itemsPerPage: 10,
}
constructor (props, context) {
@ -274,20 +274,18 @@ export default class SortedTable extends Component {
}
}
const state = this.state = {
const state = (this.state = {
all: false, // whether all items are selected (accross pages)
filter: defined(
() => props.filters[props.defaultFilter],
''
),
filter: defined(() => props.filters[props.defaultFilter], ''),
page: 1,
selectedColumn,
sortOrder: props.columns[selectedColumn].sortOrder === 'desc'
? 'desc'
: 'asc'
}
sortOrder:
props.columns[selectedColumn].sortOrder === 'desc' ? 'desc' : 'asc',
})
const urlState = get(() => context.router.location.query[props.stateUrlParam])
const urlState = get(
() => context.router.location.query[props.stateUrlParam]
)
if (urlState !== undefined) {
const i = urlState.indexOf('-')
if (i === -1) {
@ -301,23 +299,18 @@ export default class SortedTable extends Component {
this._getSelectedColumn = () =>
this.props.columns[this.state.selectedColumn]
this._getTotalNumberOfItems = createCounter(
() => this.props.collection
)
this._getTotalNumberOfItems = createCounter(() => this.props.collection)
this._getItems = createSort(
createFilter(
() => this.props.collection,
createSelector(
() => this.state.filter,
createMatcher
)
createSelector(() => this.state.filter, createMatcher)
),
createSelector(
() => this._getSelectedColumn().sortCriteria,
() => this.props.userData,
(sortCriteria, userData) =>
(typeof sortCriteria === 'function')
typeof sortCriteria === 'function'
? object => sortCriteria(object, userData)
: sortCriteria
),
@ -344,25 +337,45 @@ export default class SortedTable extends Component {
() => this.props.rowLink,
() => this.props.rowAction,
() => this.props.userData,
(visibleItems, hasGroupedActions, itemIndex, rowLink, rowAction, userData) => (command, event) => {
(
visibleItems,
hasGroupedActions,
itemIndex,
rowLink,
rowAction,
userData
) => (command, event) => {
event.preventDefault()
const item = itemIndex !== undefined ? visibleItems[itemIndex] : undefined
const item =
itemIndex !== undefined ? visibleItems[itemIndex] : undefined
switch (command) {
case 'SEARCH':
this.refs.filterInput.focus()
break
case 'NAV_DOWN':
if (hasGroupedActions || rowAction !== undefined || rowLink !== undefined) {
if (
hasGroupedActions ||
rowAction !== undefined ||
rowLink !== undefined
) {
this.setState({
highlighted: (itemIndex + visibleItems.length + 1) % visibleItems.length || 0
highlighted:
(itemIndex + visibleItems.length + 1) % visibleItems.length ||
0,
})
}
break
case 'NAV_UP':
if (hasGroupedActions || rowAction !== undefined || rowLink !== undefined) {
if (
hasGroupedActions ||
rowAction !== undefined ||
rowLink !== undefined
) {
this.setState({
highlighted: (itemIndex + visibleItems.length - 1) % visibleItems.length || 0
highlighted:
(itemIndex + visibleItems.length - 1) % visibleItems.length ||
0,
})
}
break
@ -374,9 +387,8 @@ export default class SortedTable extends Component {
case 'ROW_ACTION':
if (item !== undefined) {
if (rowLink !== undefined) {
this.context.router.push(isFunction(rowLink)
? rowLink(item, userData)
: rowLink
this.context.router.push(
isFunction(rowLink) ? rowLink(item, userData) : rowLink
)
} else if (rowAction !== undefined) {
rowAction(item, userData)
@ -403,18 +415,15 @@ export default class SortedTable extends Component {
let sortOrder
if (state.selectedColumn === columnId) {
sortOrder = state.sortOrder === 'desc'
? 'asc'
: 'desc'
sortOrder = state.sortOrder === 'desc' ? 'asc' : 'desc'
} else {
sortOrder = this.props.columns[columnId].sortOrder === 'desc'
? 'desc'
: 'asc'
sortOrder =
this.props.columns[columnId].sortOrder === 'desc' ? 'desc' : 'asc'
}
this.setState({
selectedColumn: columnId,
sortOrder
sortOrder,
})
}
@ -422,8 +431,13 @@ export default class SortedTable extends Component {
const { selectedItemsIds } = this.state
// Unselect items that are no longer visible
if ((this._visibleItemsRecomputations || 0) < (this._visibleItemsRecomputations = this._getVisibleItems.recomputations())) {
const newSelectedItems = selectedItemsIds.intersect(map(this._getVisibleItems(), 'id'))
if (
(this._visibleItemsRecomputations || 0) <
(this._visibleItemsRecomputations = this._getVisibleItems.recomputations())
) {
const newSelectedItems = selectedItemsIds.intersect(
map(this._getVisibleItems(), 'id')
)
if (newSelectedItems.size < selectedItemsIds.size) {
this.setState({ selectedItemsIds: newSelectedItems })
}
@ -443,8 +457,8 @@ export default class SortedTable extends Component {
...location,
query: {
...location.query,
[stateUrlParam]: `${page}-${filter}`
}
[stateUrlParam]: `${page}-${filter}`,
},
})
}
@ -453,7 +467,7 @@ export default class SortedTable extends Component {
this.setState({
filter,
page: 1,
highlighted: undefined
highlighted: undefined,
})
}
@ -486,7 +500,7 @@ export default class SortedTable extends Component {
all: false,
selectedItemsIds: event.target.checked
? this.state.selectedItemsIds.union(map(this._getVisibleItems(), 'id'))
: this.state.selectedItemsIds.clear()
: this.state.selectedItemsIds.clear(),
})
}
@ -500,9 +514,7 @@ export default class SortedTable extends Component {
return
}
this._toggleNestedCheckboxGuard = true
child.dispatchEvent(
new window.MouseEvent('click', event.nativeEvent)
)
child.dispatchEvent(new window.MouseEvent('click', event.nativeEvent))
this._toggleNestedCheckboxGuard = false
}
}
@ -522,31 +534,32 @@ export default class SortedTable extends Component {
selectedItemsIds.add(item.id)
})
selectedItemsIds.delete(item.id)
})
}),
})
}
let method = (
selected === undefined ? !selectedItemsIds.has(item.id) : selected
) ? 'add' : 'delete'
const method = (selected === undefined
? !selectedItemsIds.has(item.id)
: selected)
? 'add'
: 'delete'
let previous
this.setState({ selectedItemsIds:
(
range &&
(previous = this._previous) !== undefined
) ? selectedItemsIds.withMutations(selectedItemsIds => {
let i = previous
let end = current
if (previous > current) {
i = current
end = previous
}
for (; i <= end; ++i) {
selectedItemsIds[method](visibleItems[i].id)
}
})
: selectedItemsIds[method](item.id)
this.setState({
selectedItemsIds:
range && (previous = this._previous) !== undefined
? selectedItemsIds.withMutations(selectedItemsIds => {
let i = previous
let end = current
if (previous > current) {
i = current
end = previous
}
for (; i <= end; ++i) {
selectedItemsIds[method](visibleItems[i].id)
}
})
: selectedItemsIds[method](item.id),
})
this._previous = current
@ -580,69 +593,73 @@ export default class SortedTable extends Component {
const hasGroupedActions = this._hasGroupedActions()
const hasIndividualActions = !isEmpty(individualActions)
const columns = map(props.columns, ({
component: Component,
itemRenderer,
textAlign
}, key) =>
<td
className={textAlign && `text-xs-${textAlign}`}
key={key}
>
{Component !== undefined
? <Component item={item} userData={userData} />
: itemRenderer(item, userData)
}
</td>
const columns = map(
props.columns,
({ component: Component, itemRenderer, textAlign }, key) => (
<td className={textAlign && `text-xs-${textAlign}`} key={key}>
{Component !== undefined ? (
<Component item={item} userData={userData} />
) : (
itemRenderer(item, userData)
)}
</td>
)
)
const { id = i } = item
const selectionColumn = hasGroupedActions && <td
className='text-xs-center'
onClick={this._toggleNestedCheckbox}
>
<input
checked={state.all || state.selectedItemsIds.has(id)}
name={i} // position in visible items
onChange={this._onSelectItemCheckbox}
type='checkbox'
/>
</td>
const actionsColumn = hasIndividualActions && <td><div className='pull-right'>
<ButtonGroup>
{map(individualActions, (props, key) => <IndividualAction
{...props}
item={item}
key={key}
userData={userData}
/>)}
</ButtonGroup>
</div></td>
const selectionColumn = hasGroupedActions && (
<td className='text-xs-center' onClick={this._toggleNestedCheckbox}>
<input
checked={state.all || state.selectedItemsIds.has(id)}
name={i} // position in visible items
onChange={this._onSelectItemCheckbox}
type='checkbox'
/>
</td>
)
const actionsColumn = hasIndividualActions && (
<td>
<div className='pull-right'>
<ButtonGroup>
{map(individualActions, (props, key) => (
<IndividualAction
{...props}
item={item}
key={key}
userData={userData}
/>
))}
</ButtonGroup>
</div>
</td>
)
return rowLink != null
? <BlockLink
className={state.highlighted === i ? styles.highlight : undefined}
key={id}
tagName='tr'
to={isFunction(rowLink) ? rowLink(item, userData) : rowLink}
>
{selectionColumn}
{columns}
{actionsColumn}
</BlockLink>
: <tr
className={classNames(
rowAction && styles.clickableRow,
state.highlighted === i && styles.highlight
)}
key={id}
onClick={rowAction && (() => rowAction(item, userData))}
>
{selectionColumn}
{columns}
{actionsColumn}
</tr>
return rowLink != null ? (
<BlockLink
className={state.highlighted === i ? styles.highlight : undefined}
key={id}
tagName='tr'
to={isFunction(rowLink) ? rowLink(item, userData) : rowLink}
>
{selectionColumn}
{columns}
{actionsColumn}
</BlockLink>
) : (
<tr
className={classNames(
rowAction && styles.clickableRow,
state.highlighted === i && styles.highlight
)}
key={id}
onClick={rowAction && (() => rowAction(item, userData))}
>
{selectionColumn}
{columns}
{actionsColumn}
</tr>
)
}
render () {
@ -652,7 +669,7 @@ export default class SortedTable extends Component {
groupedActions,
itemsPerPage,
paginationContainer,
shortcutsTarget
shortcutsTarget,
} = props
const { all } = state
@ -667,11 +684,8 @@ export default class SortedTable extends Component {
const nColumns = props.columns.length + (hasIndividualActions ? 2 : 1)
const displayPagination =
paginationContainer === undefined &&
itemsPerPage < nAllItems
const displayFilter =
filterContainer === undefined &&
nAllItems !== 0
paginationContainer === undefined && itemsPerPage < nAllItems
const displayFilter = filterContainer === undefined && nAllItems !== 0
const paginationInstance = displayPagination && (
<Pagination
@ -697,12 +711,14 @@ export default class SortedTable extends Component {
return (
<div>
{shortcutsTarget !== undefined && <Shortcuts
handler={this._getShortcutsHandler()}
name='SortedTable'
stopPropagation
targetNodeSelector={shortcutsTarget}
/>}
{shortcutsTarget !== undefined && (
<Shortcuts
handler={this._getShortcutsHandler()}
name='SortedTable'
stopPropagation
targetNodeSelector={shortcutsTarget}
/>
)}
<table className='table'>
<thead className='thead-default'>
<tr>
@ -711,102 +727,126 @@ export default class SortedTable extends Component {
? _('sortedTableNumberOfItems', { nTotal: nItems })
: _('sortedTableNumberOfFilteredItems', {
nFiltered: nItems,
nTotal: nAllItems
})
}
{all
? <span>
{' '}-{' '}
nTotal: nAllItems,
})}
{all ? (
<span>
{' '}
-{' '}
<span className='text-danger'>
{_('sortedTableAllItemsSelected')}
</span>
</span>
: nSelectedItems !== 0 && <span>
{' '}-{' '}
{_('sortedTableNumberOfSelectedItems', {
nSelected: nSelectedItems
})}
{nSelectedItems === nVisibleItems && nSelectedItems < nItems &&
<Button
btnStyle='info'
className='ml-1'
onClick={this._selectAll}
size='small'
>
{_('sortedTableSelectAllItems')}
</Button>
}
</span>
}
{nSelectedItems !== 0 && <div className='pull-right'>
<ButtonGroup>
{map(groupedActions, ({ icon, label, level, handler }, key) => <ActionRowButton
btnStyle={level}
handler={this._executeGroupedAction}
handlerParam={handler}
icon={icon}
key={key}
tooltip={label}
/>)}
</ButtonGroup>
</div>}
) : (
nSelectedItems !== 0 && (
<span>
{' '}
-{' '}
{_('sortedTableNumberOfSelectedItems', {
nSelected: nSelectedItems,
})}
{nSelectedItems === nVisibleItems &&
nSelectedItems < nItems && (
<Button
btnStyle='info'
className='ml-1'
onClick={this._selectAll}
size='small'
>
{_('sortedTableSelectAllItems')}
</Button>
)}
</span>
)
)}
{nSelectedItems !== 0 && (
<div className='pull-right'>
<ButtonGroup>
{map(
groupedActions,
({ icon, label, level, handler }, key) => (
<ActionRowButton
btnStyle={level}
handler={this._executeGroupedAction}
handlerParam={handler}
icon={icon}
key={key}
tooltip={label}
/>
)
)}
</ButtonGroup>
</div>
)}
</th>
</tr>
<tr>
{hasGroupedActions && <th
className='text-xs-center'
onClick={this._toggleNestedCheckbox}
>
<Checkbox
onChange={this._selectAllVisibleItems}
checked={all || nSelectedItems !== 0}
indeterminate={!all && nSelectedItems !== 0 && nSelectedItems !== nVisibleItems}
/>
</th>}
{hasGroupedActions && (
<th
className='text-xs-center'
onClick={this._toggleNestedCheckbox}
>
<Checkbox
onChange={this._selectAllVisibleItems}
checked={all || nSelectedItems !== 0}
indeterminate={
!all &&
nSelectedItems !== 0 &&
nSelectedItems !== nVisibleItems
}
/>
</th>
)}
{map(props.columns, (column, key) => (
<ColumnHead
textAlign={column.textAlign}
columnId={key}
key={key}
name={column.name}
sort={column.sortCriteria && this._sort}
sortIcon={state.selectedColumn === key ? state.sortOrder : 'sort'}
/>
sortIcon={
state.selectedColumn === key ? state.sortOrder : 'sort'
}
/>
))}
{hasIndividualActions && <th />}
</tr>
</thead>
<tbody>
{nVisibleItems !== 0
? map(this._getVisibleItems(), this._renderItem)
: <tr><td className='text-info text-xs-center' colSpan={nColumns}>
{_('sortedTableNoItems')}
</td></tr>
}
{nVisibleItems !== 0 ? (
map(this._getVisibleItems(), this._renderItem)
) : (
<tr>
<td className='text-info text-xs-center' colSpan={nColumns}>
{_('sortedTableNoItems')}
</td>
</tr>
)}
</tbody>
</table>
{(displayFilter || displayPagination) && (
<Container>
<SingleLineRow>
<Col mediumSize={8}>
{displayPagination && (
paginationContainer !== undefined
{displayPagination &&
(paginationContainer !== undefined ? (
// Rebuild container function to refresh Portal component.
? <Portal container={() => paginationContainer()}>
<Portal container={() => paginationContainer()}>
{paginationInstance}
</Portal>
: paginationInstance
)}
) : (
paginationInstance
))}
</Col>
<Col mediumSize={4}>
{displayFilter && (
filterContainer
? <Portal container={() => filterContainer()}>
{displayFilter &&
(filterContainer ? (
<Portal container={() => filterContainer()}>
{filterInstance}
</Portal>
: filterInstance
)}
) : (
filterInstance
))}
</Col>
</SingleLineRow>
</Container>

View File

@ -0,0 +1,24 @@
import React from 'react'
import propTypes from '../prop-types-decorator'
const Null = () => null
export const Column = propTypes(Null)({
component: propTypes.func,
default: propTypes.bool,
name: propTypes.node,
itemRenderer: propTypes.func,
sortCriteria: propTypes.oneOfType([propTypes.func, propTypes.string]),
sortOrder: propTypes.string,
textAlign: propTypes.string,
})
export const SortedTable = Null
export const render = ({ vdis }) => (
<div>
<SortedTable collection={vdis}>
<Column name='Nom du VDI' sortCriteria='name_label' />
</SortedTable>
</div>
)

View File

@ -1,16 +1,16 @@
import React from 'react'
import styled from 'styled-components'
import {
omit
} from 'lodash'
import { omit } from 'lodash'
import ActionButton from './action-button'
import propTypes from './prop-types-decorator'
// do not forward `state` to ActionButton
const Button = styled(p => <ActionButton {...omit(p, 'state')} />)`
background-color: ${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateBg`]};
border: 2px solid ${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateColor`]};
background-color: ${p =>
p.theme[`${p.state ? 'enabled' : 'disabled'}StateBg`]};
border: 2px solid
${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateColor`]};
color: ${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateColor`]};
`
@ -27,7 +27,7 @@ const StateButton = ({
state,
...props
}) =>
}) => (
<Button
handler={state ? enabledHandler : disabledHandler}
handlerParam={state ? enabledHandlerParam : disabledHandlerParam}
@ -39,7 +39,8 @@ const StateButton = ({
>
{state ? enabledLabel : disabledLabel}
</Button>
)
export default propTypes({
state: propTypes.bool.isRequired
state: propTypes.bool.isRequired,
})(StateButton)

View File

@ -1,22 +1,24 @@
const createAction = (() => {
const { defineProperty } = Object
return (type, payloadCreator) => defineProperty(
payloadCreator
? (...args) => ({
type,
payload: payloadCreator(...args)
})
: (action => function () {
if (arguments.length) {
throw new Error('this action expects no payload!')
}
return (type, payloadCreator) =>
defineProperty(
payloadCreator
? (...args) => ({
type,
payload: payloadCreator(...args),
})
: (action =>
function () {
if (arguments.length) {
throw new Error('this action expects no payload!')
}
return action
})({ type }),
'toString',
{ value: () => type }
)
return action
})({ type }),
'toString',
{ value: () => type }
)
})()
// ===================================================================
@ -29,7 +31,10 @@ export const connected = createAction('CONNECTED')
export const disconnected = createAction('DISCONNECTED')
export const updateObjects = createAction('UPDATE_OBJECTS', updates => updates)
export const updatePermissions = createAction('UPDATE_PERMISSIONS', permissions => permissions)
export const updatePermissions = createAction(
'UPDATE_PERMISSIONS',
permissions => permissions
)
export const signedIn = createAction('SIGNED_IN', user => user)
export const signedOut = createAction('SIGNED_OUT')
@ -37,5 +42,11 @@ export const signedOut = createAction('SIGNED_OUT')
export const xoaUpdaterState = createAction('XOA_UPDATER_STATE', state => state)
export const xoaTrialState = createAction('XOA_TRIAL_STATE', state => state)
export const xoaUpdaterLog = createAction('XOA_UPDATER_LOG', log => log)
export const xoaRegisterState = createAction('XOA_REGISTER_STATE', registration => registration)
export const xoaConfiguration = createAction('XOA_CONFIGURATION', configuration => configuration)
export const xoaRegisterState = createAction(
'XOA_REGISTER_STATE',
registration => registration
)
export const xoaConfiguration = createAction(
'XOA_CONFIGURATION',
configuration => configuration
)

View File

@ -4,10 +4,7 @@ import React from 'react'
import { createDevTools } from 'redux-devtools'
export default createDevTools(
<DockMonitor
changePositionKey='ctrl-q'
toggleVisibilityKey='ctrl-h'
>
<DockMonitor changePositionKey='ctrl-q' toggleVisibilityKey='ctrl-h'>
<LogMonitor />
</DockMonitor>
)

View File

@ -1,10 +1,5 @@
import reduxThunk from 'redux-thunk'
import {
applyMiddleware,
combineReducers,
compose,
createStore
} from 'redux'
import { applyMiddleware, combineReducers, compose, createStore } from 'redux'
import { connectStore as connectXo } from '../xo'
@ -13,9 +8,7 @@ import reducer from './reducer'
// ===================================================================
const enhancers = [
applyMiddleware(reduxThunk)
]
const enhancers = [applyMiddleware(reduxThunk)]
DevTools && enhancers.push(DevTools.instrument())
const store = createStore(

View File

@ -54,19 +54,16 @@ const combineActionHandlers = invoke(
const actionType = firstProp(handlers)
const handler = handlers[actionType]
return (state = initialState, action) => (
return (state = initialState, action) =>
action.type === actionType
? handler(state, action.payload, action)
: state
)
}
return (state = initialState, action) => {
const handler = handlers[action.type]
return handler
? handler(state, action.payload, action)
: state
return handler ? handler(state, action.payload, action) : state
}
}
)
@ -79,78 +76,91 @@ export default {
cookies.set('lang', lang)
return lang
},
}),
permissions: combineActionHandlers(
{},
{
[actions.updatePermissions]: (_, permissions) => permissions,
}
}),
),
permissions: combineActionHandlers({}, {
[actions.updatePermissions]: (_, permissions) => permissions
}),
objects: combineActionHandlers(
{
all: {}, // Mutable for performance!
byType: {},
},
{
[actions.updateObjects]: ({ all, byType: prevByType }, updates) => {
const byType = { ...prevByType }
const get = type => {
const curr = byType[type]
const prev = prevByType[type]
return curr === prev ? (byType[type] = { ...prev }) : curr
}
objects: combineActionHandlers({
all: {}, // Mutable for performance!
byType: {}
}, {
[actions.updateObjects]: ({ all, byType: prevByType }, updates) => {
const byType = { ...prevByType }
const get = type => {
const curr = byType[type]
const prev = prevByType[type]
return curr === prev
? (byType[type] = { ...prev })
: curr
}
for (const id in updates) {
const object = updates[id]
const previous = all[id]
for (const id in updates) {
const object = updates[id]
const previous = all[id]
if (object) {
const { type } = object
if (object) {
const { type } = object
all[id] = object
get(type)[id] = object
all[id] = object
get(type)[id] = object
if (previous && previous.type !== type) {
if (previous && previous.type !== type) {
delete get(previous.type)[id]
}
} else if (previous) {
delete all[id]
delete get(previous.type)[id]
}
} else if (previous) {
delete all[id]
delete get(previous.type)[id]
}
}
return { all, byType, fetched: true }
return { all, byType, fetched: true }
},
}
}),
),
user: combineActionHandlers(null, {
[actions.signedIn]: {
next: (_, user) => user
}
next: (_, user) => user,
},
}),
status: combineActionHandlers('disconnected', {
[actions.connected]: () => 'connected',
[actions.disconnected]: () => 'disconnected'
[actions.disconnected]: () => 'disconnected',
}),
xoaUpdaterState: combineActionHandlers('disconnected', {
[actions.xoaUpdaterState]: (_, state) => state
[actions.xoaUpdaterState]: (_, state) => state,
}),
xoaTrialState: combineActionHandlers({}, {
[actions.xoaTrialState]: (_, state) => state
}),
xoaUpdaterLog: combineActionHandlers([], {
[actions.xoaUpdaterLog]: (_, log) => log
}),
xoaRegisterState: combineActionHandlers({state: '?'}, {
[actions.xoaRegisterState]: (_, registration) => registration
}),
xoaConfiguration: combineActionHandlers({proxyHost: '', proxyPort: '', proxyUser: ''}, { // defined values for controlled inputs
[actions.xoaConfiguration]: (_, configuration) => {
delete configuration.password
return configuration
xoaTrialState: combineActionHandlers(
{},
{
[actions.xoaTrialState]: (_, state) => state,
}
})
),
xoaUpdaterLog: combineActionHandlers([], {
[actions.xoaUpdaterLog]: (_, log) => log,
}),
xoaRegisterState: combineActionHandlers(
{ state: '?' },
{
[actions.xoaRegisterState]: (_, registration) => registration,
}
),
xoaConfiguration: combineActionHandlers(
{ proxyHost: '', proxyPort: '', proxyUser: '' },
{
// defined values for controlled inputs
[actions.xoaConfiguration]: (_, configuration) => {
delete configuration.password
return configuration
},
}
),
}

View File

@ -7,38 +7,24 @@ import Link from './link'
const STYLE = {
marginBottom: '1em',
marginLeft: '1em'
marginLeft: '1em',
}
const TabButton = ({
labelId,
...props
}) => (
<ActionButton
{...props}
size='large'
style={STYLE}
>
{labelId !== undefined && <span className='hidden-md-down'>{_(labelId)}</span>}
const TabButton = ({ labelId, ...props }) => (
<ActionButton {...props} size='large' style={STYLE}>
{labelId !== undefined && (
<span className='hidden-md-down'>{_(labelId)}</span>
)}
</ActionButton>
)
export { TabButton as default }
export const TabButtonLink = ({
labelId,
icon,
...props
}) => (
<Link
{...props}
className='btn btn-lg btn-primary'
style={STYLE}
>
export const TabButtonLink = ({ labelId, icon, ...props }) => (
<Link {...props} className='btn btn-lg btn-primary' style={STYLE}>
<span className='hidden-md-down'>
{icon && (
<span>
<Icon icon={icon} />
{' '}
<Icon icon={icon} />{' '}
</span>
)}
{_(labelId)}

View File

@ -9,7 +9,7 @@ import propTypes from './prop-types-decorator'
const INPUT_STYLE = {
margin: '2px',
maxWidth: '4em'
maxWidth: '4em',
}
const TAG_STYLE = {
backgroundColor: '#2598d9',
@ -19,18 +19,18 @@ const TAG_STYLE = {
margin: '0.2em',
marginTop: '-0.1em',
padding: '0.3em',
verticalAlign: 'middle'
verticalAlign: 'middle',
}
const LINK_STYLE = {
cursor: 'pointer'
cursor: 'pointer',
}
const ADD_TAG_STYLE = {
cursor: 'pointer',
fontSize: '0.8em',
marginLeft: '0.2em'
marginLeft: '0.2em',
}
const REMOVE_TAG_STYLE = {
cursor: 'pointer'
cursor: 'pointer',
}
@propTypes({
@ -38,11 +38,11 @@ const REMOVE_TAG_STYLE = {
onAdd: propTypes.func,
onChange: propTypes.func,
onClick: propTypes.func,
onDelete: propTypes.func
onDelete: propTypes.func,
})
export default class Tags extends Component {
componentWillMount () {
this.setState({editing: false})
this.setState({ editing: false })
}
_startEdit = () => {
@ -57,7 +57,7 @@ export default class Tags extends Component {
if (!includes(labels, newTag)) {
onAdd && onAdd(newTag)
onChange && onChange([ ...labels, newTag ])
onChange && onChange([...labels, newTag])
}
}
_deleteTag = tag => {
@ -85,30 +85,29 @@ export default class Tags extends Component {
}
render () {
const {
labels,
onAdd,
onChange,
onClick,
onDelete
} = this.props
const { labels, onAdd, onChange, onClick, onDelete } = this.props
const deleteTag = (onDelete || onChange) && this._deleteTag
return (
<span className='form-group' style={{ color: '#999' }}>
<Icon icon='tags' />
{' '}
<Icon icon='tags' />{' '}
<span>
{map(labels.sort(), (label, index) =>
<Tag label={label} onDelete={deleteTag} key={index} onClick={onClick} />
)}
{map(labels.sort(), (label, index) => (
<Tag
label={label}
onDelete={deleteTag}
key={index}
onClick={onClick}
/>
))}
</span>
{(onAdd || onChange) && !this.state.editing
? <span onClick={this._startEdit} style={ADD_TAG_STYLE}>
{(onAdd || onChange) && !this.state.editing ? (
<span onClick={this._startEdit} style={ADD_TAG_STYLE}>
<Icon icon='add-tag' />
</span>
: <span>
) : (
<span>
<input
type='text'
autoFocus
@ -117,7 +116,7 @@ export default class Tags extends Component {
onBlur={this._stopEdit}
/>
</span>
}
)}
</span>
)
}
@ -125,18 +124,24 @@ export default class Tags extends Component {
export const Tag = ({ type, label, onDelete, onClick }) => (
<span style={TAG_STYLE}>
<span onClick={onClick && (() => onClick(label))} style={onClick && LINK_STYLE}>
<span
onClick={onClick && (() => onClick(label))}
style={onClick && LINK_STYLE}
>
{label}
</span>
{' '}
{onDelete
? <span onClick={onDelete && (() => onDelete(label))} style={REMOVE_TAG_STYLE}>
</span>{' '}
{onDelete ? (
<span
onClick={onDelete && (() => onDelete(label))}
style={REMOVE_TAG_STYLE}
>
<Icon icon='remove-tag' />
</span>
: []
}
) : (
[]
)}
</span>
)
Tag.propTypes = {
label: React.PropTypes.string.isRequired
label: React.PropTypes.string.isRequired,
}

View File

@ -2,5 +2,5 @@ export default {
disabledStateBg: '#fff',
disabledStateColor: '#c0392b',
enabledStateBg: '#fff',
enabledStateColor: '#27ae60'
enabledStateColor: '#27ae60',
}

View File

@ -16,22 +16,23 @@ const LOCAL_TIMEZONE = moment.tz.guess()
defaultValue: propTypes.string,
onChange: propTypes.func.isRequired,
required: propTypes.bool,
value: propTypes.string
value: propTypes.string,
})
export default class TimezonePicker extends Component {
componentDidMount () {
getXoServerTimezone.then(serverTimezone => {
this.setState({
timezone: this.props.value || this.props.defaultValue || SERVER_TIMEZONE_TAG,
timezone:
this.props.value || this.props.defaultValue || SERVER_TIMEZONE_TAG,
options: [
...map(moment.tz.names(), value => ({ label: value, value })),
{
label: _('serverTimezoneOption', {
value: serverTimezone
value: serverTimezone,
}),
value: SERVER_TIMEZONE_TAG
}
]
value: SERVER_TIMEZONE_TAG,
},
],
})
})
}
@ -43,7 +44,9 @@ export default class TimezonePicker extends Component {
}
get value () {
return this.state.timezone === SERVER_TIMEZONE_TAG ? null : this.state.timezone
return this.state.timezone === SERVER_TIMEZONE_TAG
? null
: this.state.timezone
}
set value (value) {
@ -55,10 +58,16 @@ export default class TimezonePicker extends Component {
return
}
this.setState({
timezone: (option != null && option.value) || SERVER_TIMEZONE_TAG
}, () =>
this.props.onChange(this.state.timezone === SERVER_TIMEZONE_TAG ? null : this.state.timezone)
this.setState(
{
timezone: (option != null && option.value) || SERVER_TIMEZONE_TAG,
},
() =>
this.props.onChange(
this.state.timezone === SERVER_TIMEZONE_TAG
? null
: this.state.timezone
)
)
}
@ -80,10 +89,7 @@ export default class TimezonePicker extends Component {
value={timezone}
/>
<div className='pull-right'>
<ActionButton
handler={this._useLocalTime}
icon='time'
>
<ActionButton handler={this._useLocalTime} icon='time'>
{_('timezonePickerUseLocalTime')}
</ActionButton>
</div>

View File

@ -19,29 +19,35 @@
export default function (e, target, node, place, effect, offset) {
const tipWidth = node.clientWidth
const tipHeight = node.clientHeight
const {mouseX, mouseY} = getCurrentOffset(e, target, effect)
const defaultOffset = getDefaultPosition(effect, target.clientWidth, target.clientHeight, tipWidth, tipHeight)
const {extraOffsetX, extraOffsetY} = calculateOffset(offset)
const { mouseX, mouseY } = getCurrentOffset(e, target, effect)
const defaultOffset = getDefaultPosition(
effect,
target.clientWidth,
target.clientHeight,
tipWidth,
tipHeight
)
const { extraOffsetX, extraOffsetY } = calculateOffset(offset)
const windowWidth = window.innerWidth
const windowHeight = window.innerHeight
const {parentTop, parentLeft} = getParent(target)
const { parentTop, parentLeft } = getParent(target)
// Get the edge offset of the tooltip
const getTipOffsetLeft = (place) => {
const getTipOffsetLeft = place => {
const offsetX = defaultOffset[place].l
return mouseX + offsetX + extraOffsetX
}
const getTipOffsetRight = (place) => {
const getTipOffsetRight = place => {
const offsetX = defaultOffset[place].r
return mouseX + offsetX + extraOffsetX
}
const getTipOffsetTop = (place) => {
const getTipOffsetTop = place => {
const offsetY = defaultOffset[place].t
return mouseY + offsetY + extraOffsetY
}
const getTipOffsetBottom = (place) => {
const getTipOffsetBottom = place => {
const offsetY = defaultOffset[place].b
return mouseY + offsetY + extraOffsetY
}
@ -50,79 +56,103 @@ export default function (e, target, node, place, effect, offset) {
const outsideVertical = () => {
let result = false
let newPlace
if (getTipOffsetTop('left') < 0 &&
if (
getTipOffsetTop('left') < 0 &&
getTipOffsetBottom('left') <= windowHeight &&
getTipOffsetBottom('bottom') <= windowHeight) {
getTipOffsetBottom('bottom') <= windowHeight
) {
result = true
newPlace = 'bottom'
} else if (getTipOffsetBottom('left') > windowHeight &&
} else if (
getTipOffsetBottom('left') > windowHeight &&
getTipOffsetTop('left') >= 0 &&
getTipOffsetTop('top') >= 0) {
getTipOffsetTop('top') >= 0
) {
result = true
newPlace = 'top'
}
return {result, newPlace}
return { result, newPlace }
}
const outsideLeft = () => {
let {result, newPlace} = outsideVertical() // Deal with vertical as first priority
let { result, newPlace } = outsideVertical() // Deal with vertical as first priority
if (result && outsideHorizontal().result) {
return {result: false} // No need to change, if change to vertical will out of space
return { result: false } // No need to change, if change to vertical will out of space
}
if (!result && getTipOffsetLeft('left') < 0 && getTipOffsetRight('right') <= windowWidth) {
if (
!result &&
getTipOffsetLeft('left') < 0 &&
getTipOffsetRight('right') <= windowWidth
) {
result = true // If vertical ok, but let out of side and right won't out of side
newPlace = 'right'
}
return {result, newPlace}
return { result, newPlace }
}
const outsideRight = () => {
let {result, newPlace} = outsideVertical()
let { result, newPlace } = outsideVertical()
if (result && outsideHorizontal().result) {
return {result: false} // No need to change, if change to vertical will out of space
return { result: false } // No need to change, if change to vertical will out of space
}
if (!result && getTipOffsetRight('right') > windowWidth && getTipOffsetLeft('left') >= 0) {
if (
!result &&
getTipOffsetRight('right') > windowWidth &&
getTipOffsetLeft('left') >= 0
) {
result = true
newPlace = 'left'
}
return {result, newPlace}
return { result, newPlace }
}
const outsideHorizontal = () => {
let result = false
let newPlace
if (getTipOffsetLeft('top') < 0 &&
if (
getTipOffsetLeft('top') < 0 &&
getTipOffsetRight('top') <= windowWidth &&
getTipOffsetRight('right') <= windowWidth) {
getTipOffsetRight('right') <= windowWidth
) {
result = true
newPlace = 'right'
} else if (getTipOffsetRight('top') > windowWidth &&
} else if (
getTipOffsetRight('top') > windowWidth &&
getTipOffsetLeft('top') >= 0 &&
getTipOffsetLeft('left') >= 0) {
getTipOffsetLeft('left') >= 0
) {
result = true
newPlace = 'left'
}
return {result, newPlace}
return { result, newPlace }
}
const outsideTop = () => {
let {result, newPlace} = outsideHorizontal()
let { result, newPlace } = outsideHorizontal()
if (result && outsideVertical().result) {
return {result: false}
return { result: false }
}
if (!result && getTipOffsetTop('top') < 0 && getTipOffsetBottom('bottom') <= windowHeight) {
if (
!result &&
getTipOffsetTop('top') < 0 &&
getTipOffsetBottom('bottom') <= windowHeight
) {
result = true
newPlace = 'bottom'
}
return {result, newPlace}
return { result, newPlace }
}
const outsideBottom = () => {
let {result, newPlace} = outsideHorizontal()
let { result, newPlace } = outsideHorizontal()
if (result && outsideVertical().result) {
return {result: false}
return { result: false }
}
if (!result && getTipOffsetBottom('bottom') > windowHeight && getTipOffsetTop('top') >= 0) {
if (
!result &&
getTipOffsetBottom('bottom') > windowHeight &&
getTipOffsetTop('top') >= 0
) {
result = true
newPlace = 'top'
}
return {result, newPlace}
return { result, newPlace }
}
// Return new state to change the placement to the reverse if possible
@ -134,22 +164,22 @@ export default function (e, target, node, place, effect, offset) {
if (place === 'left' && outsideLeftResult.result) {
return {
isNewState: true,
newState: {place: outsideLeftResult.newPlace}
newState: { place: outsideLeftResult.newPlace },
}
} else if (place === 'right' && outsideRightResult.result) {
return {
isNewState: true,
newState: {place: outsideRightResult.newPlace}
newState: { place: outsideRightResult.newPlace },
}
} else if (place === 'top' && outsideTopResult.result) {
return {
isNewState: true,
newState: {place: outsideTopResult.newPlace}
newState: { place: outsideTopResult.newPlace },
}
} else if (place === 'bottom' && outsideBottomResult.result) {
return {
isNewState: true,
newState: {place: outsideBottomResult.newPlace}
newState: { place: outsideBottomResult.newPlace },
}
}
@ -158,8 +188,8 @@ export default function (e, target, node, place, effect, offset) {
isNewState: false,
position: {
left: getTipOffsetLeft(place) - parentLeft,
top: getTipOffsetTop(place) - parentTop
}
top: getTipOffsetTop(place) - parentTop,
},
}
}
@ -174,18 +204,24 @@ const getCurrentOffset = (e, currentTarget, effect) => {
if (effect === 'float') {
return {
mouseX: e.clientX,
mouseY: e.clientY
mouseY: e.clientY,
}
}
return {
mouseX: targetLeft + (targetWidth / 2),
mouseY: targetTop + (targetHeight / 2)
mouseX: targetLeft + targetWidth / 2,
mouseY: targetTop + targetHeight / 2,
}
}
// List all possibility of tooltip final offset
// This is useful in judging if it is necessary for tooltip to switch position when out of window
const getDefaultPosition = (effect, targetWidth, targetHeight, tipWidth, tipHeight) => {
const getDefaultPosition = (
effect,
targetWidth,
targetHeight,
tipWidth,
tipHeight
) => {
let top
let right
let bottom
@ -199,65 +235,65 @@ const getDefaultPosition = (effect, targetWidth, targetHeight, tipWidth, tipHeig
l: -(tipWidth / 2),
r: tipWidth / 2,
t: -(tipHeight + disToMouse + triangleHeight),
b: -disToMouse
b: -disToMouse,
}
bottom = {
l: -(tipWidth / 2),
r: tipWidth / 2,
t: disToMouse + cursorHeight,
b: tipHeight + disToMouse + triangleHeight + cursorHeight
b: tipHeight + disToMouse + triangleHeight + cursorHeight,
}
left = {
l: -(tipWidth + disToMouse + triangleHeight),
r: -disToMouse,
t: -(tipHeight / 2),
b: tipHeight / 2
b: tipHeight / 2,
}
right = {
l: disToMouse,
r: tipWidth + disToMouse + triangleHeight,
t: -(tipHeight / 2),
b: tipHeight / 2
b: tipHeight / 2,
}
} else if (effect === 'solid') {
top = {
l: -(tipWidth / 2),
r: tipWidth / 2,
t: -(targetHeight / 2 + tipHeight + triangleHeight),
b: -(targetHeight / 2)
b: -(targetHeight / 2),
}
bottom = {
l: -(tipWidth / 2),
r: tipWidth / 2,
t: targetHeight / 2,
b: targetHeight / 2 + tipHeight + triangleHeight
b: targetHeight / 2 + tipHeight + triangleHeight,
}
left = {
l: -(tipWidth + targetWidth / 2 + triangleHeight),
r: -(targetWidth / 2),
t: -(tipHeight / 2),
b: tipHeight / 2
b: tipHeight / 2,
}
right = {
l: targetWidth / 2,
r: tipWidth + targetWidth / 2 + triangleHeight,
t: -(tipHeight / 2),
b: tipHeight / 2
b: tipHeight / 2,
}
}
return {top, bottom, left, right}
return { top, bottom, left, right }
}
// Consider additional offset into position calculation
const calculateOffset = (offset) => {
const calculateOffset = offset => {
let extraOffsetX = 0
let extraOffsetY = 0
if (Object.prototype.toString.apply(offset) === '[object String]') {
offset = JSON.parse(offset.toString().replace(/'/g, '"'))
}
for (let key in offset) {
for (const key in offset) {
if (key === 'top') {
extraOffsetY -= parseInt(offset[key], 10)
} else if (key === 'bottom') {
@ -269,11 +305,11 @@ const calculateOffset = (offset) => {
}
}
return {extraOffsetX, extraOffsetY}
return { extraOffsetX, extraOffsetY }
}
// Get the offset of the parent elements
const getParent = (currentTarget) => {
const getParent = currentTarget => {
let currentParent = currentTarget
while (currentParent) {
if (currentParent.style.transform.length > 0) break
@ -283,5 +319,5 @@ const getParent = (currentTarget) => {
const parentTop = currentParent && currentParent.getBoundingClientRect().top
const parentLeft = currentParent && currentParent.getBoundingClientRect().left
return {parentTop, parentLeft}
return { parentTop, parentLeft }
}

View File

@ -32,21 +32,20 @@ export class TooltipViewer extends Component {
}
render () {
const {
className,
content,
place,
show,
style
} = this.state
const { className, content, place, show, style } = this.state
return (
<div
className={classNames(show ? styles.tooltipEnabled : styles.tooltipDisabled, className)}
className={classNames(
show ? styles.tooltipEnabled : styles.tooltipDisabled,
className
)}
style={{
marginTop: (place === 'top' && '-10px') || (place === 'bottom' && '10px'),
marginLeft: (place === 'left' && '-10px') || (place === 'right' && '10px'),
...style
marginTop:
(place === 'top' && '-10px') || (place === 'bottom' && '10px'),
marginLeft:
(place === 'left' && '-10px') || (place === 'right' && '10px'),
...style,
}}
>
{content}
@ -58,14 +57,11 @@ export class TooltipViewer extends Component {
// ===================================================================
@propTypes({
children: propTypes.oneOfType([
propTypes.element,
propTypes.string
]),
children: propTypes.oneOfType([propTypes.element, propTypes.string]),
className: propTypes.string,
content: propTypes.node,
style: propTypes.object,
tagName: propTypes.string
tagName: propTypes.string,
})
export default class Tooltip extends Component {
componentDidMount () {
@ -89,7 +85,7 @@ export default class Tooltip extends Component {
}
_addListeners () {
const node = this._node = ReactDOM.findDOMNode(this)
const node = (this._node = ReactDOM.findDOMNode(this))
node.addEventListener('mouseenter', this._showTooltip)
node.addEventListener('mouseleave', this._hideTooltip)
@ -118,7 +114,7 @@ export default class Tooltip extends Component {
className: props.className,
content: props.content,
show: true,
style: props.style
style: props.style,
})
}
@ -128,10 +124,19 @@ export default class Tooltip extends Component {
_updateTooltip = event => {
const node = ReactDOM.findDOMNode(instance)
const result = getPosition(event, event.currentTarget, node, instance.state.place, 'solid', {})
const result = getPosition(
event,
event.currentTarget,
node,
instance.state.place,
'solid',
{}
)
if (result.isNewState) {
return instance.setState(result.newState, () => this._updateTooltip(event))
return instance.setState(result.newState, () =>
this._updateTooltip(event)
)
}
const { position } = result

View File

@ -12,20 +12,19 @@ const Usage = ({ total, children }) => {
return value < limit && value
})
const othersTotal = sum(othersValues)
return <span className='usage'>
{React.Children.map(children, (child, index) =>
child.props.value > limit && cloneElement(child, { total })
)}
<Element
others
tooltip={_('others')}
total={total}
value={othersTotal}
/>
</span>
return (
<span className='usage'>
{React.Children.map(
children,
(child, index) =>
child.props.value > limit && cloneElement(child, { total })
)}
<Element others tooltip={_('others')} total={total} value={othersTotal} />
</span>
)
}
Usage.propTypes = {
total: PropTypes.number.isRequired
total: PropTypes.number.isRequired,
}
export { Usage as default }
@ -38,7 +37,7 @@ const Element = ({ highlight, href, others, tooltip, total, value }) => (
highlight && 'usage-element-highlight',
others && 'usage-element-others'
)}
style={{ width: (value / total) * 100 + '%' }}
style={{ width: value / total * 100 + '%' }}
/>
</Tooltip>
)
@ -47,26 +46,32 @@ Element.propTypes = {
href: PropTypes.string,
others: PropTypes.bool,
tooltip: PropTypes.node,
value: PropTypes.number.isRequired
value: PropTypes.number.isRequired,
}
export { Element as UsageElement }
export const Limits = ({ used, toBeUsed, limit }) => {
const available = limit - used
return <span className='limits'>
<span
className='limits-used'
style={{ width: ((used || 0) / limit) * 100 + '%' }}
/>
<span
className={toBeUsed > available ? 'limits-over-used' : 'limits-to-be-used'}
style={{ width: (Math.min((toBeUsed || 0), available) / limit) * 100 + '%' }}
/>
</span>
return (
<span className='limits'>
<span
className='limits-used'
style={{ width: (used || 0) / limit * 100 + '%' }}
/>
<span
className={
toBeUsed > available ? 'limits-over-used' : 'limits-to-be-used'
}
style={{
width: Math.min(toBeUsed || 0, available) / limit * 100 + '%',
}}
/>
</span>
)
}
Limits.propTypes = {
used: PropTypes.number,
toBeUsed: PropTypes.number,
limit: PropTypes.number.isRequired
limit: PropTypes.number.isRequired,
}

View File

@ -19,7 +19,7 @@ import {
mapValues,
replace,
sample,
startsWith
startsWith,
} from 'lodash'
import _ from './intl'
@ -29,17 +29,17 @@ import invoke from './invoke'
import store from './store'
import { getObject } from './selectors'
export const EMPTY_ARRAY = Object.freeze([ ])
export const EMPTY_OBJECT = Object.freeze({ })
export const EMPTY_ARRAY = Object.freeze([])
export const EMPTY_OBJECT = Object.freeze({})
// ===================================================================
export const ensureArray = (value) => {
export const ensureArray = value => {
if (value === undefined) {
return []
}
return Array.isArray(value) ? value : [ value ]
return Array.isArray(value) ? value : [value]
}
export const propsEqual = (o1, o2, props) => {
@ -68,9 +68,7 @@ export const addSubscriptions = subscriptions => Component => {
componentWillMount () {
this._unsubscribes = map(
isFunction(subscriptions)
? subscriptions(this.props)
: subscriptions,
isFunction(subscriptions) ? subscriptions(this.props) : subscriptions,
(subscribe, prop) =>
subscribe(value => this._setState({ [prop]: value }))
)
@ -91,10 +89,7 @@ export const addSubscriptions = subscriptions => Component => {
}
render () {
return <Component
{...this.props}
{...this.state}
/>
return <Component {...this.props} {...this.state} />
}
}
@ -132,7 +127,7 @@ export const checkPropsState = (propsNames, stateNames) => Component => {
const _normalizeMapStateToProps = mapper => {
if (isFunction(mapper)) {
let factoryOrMapper = (state, props) => {
const factoryOrMapper = (state, props) => {
const result = mapper(state, props)
// Properly handles factory pattern.
@ -148,7 +143,8 @@ const _normalizeMapStateToProps = mapper => {
}
if (every(result, isFunction)) {
indirection = (state, props) => mapValues(result, selector => selector(state, props))
indirection = (state, props) =>
mapValues(result, selector => selector(state, props))
return indirection(state, props)
}
}
@ -184,7 +180,7 @@ export const connectStore = (mapStateToProps, opts = {}) => {
},
set (value) {
this.getWrappedInstance().value = value
}
},
})
}
@ -230,45 +226,49 @@ export const noop = () => {}
// -------------------------------------------------------------------
export const osFamily = invoke({
centos: [ 'centos' ],
debian: [ 'debian' ],
docker: [ 'coreos' ],
fedora: [ 'fedora' ],
freebsd: [ 'freebsd' ],
gentoo: [ 'gentoo' ],
'linux-mint': [ 'linux-mint' ],
netbsd: [ 'netbsd' ],
oracle: [ 'oracle' ],
osx: [ 'osx' ],
redhat: [ 'redhat', 'rhel' ],
solaris: [ 'solaris' ],
suse: [ 'sles', 'suse' ],
ubuntu: [ 'ubuntu' ],
windows: [ 'windows' ]
}, osByFamily => {
const osToFamily = Object.create(null)
forEach(osByFamily, (list, family) => {
forEach(list, os => {
osToFamily[os] = family
export const osFamily = invoke(
{
centos: ['centos'],
debian: ['debian'],
docker: ['coreos'],
fedora: ['fedora'],
freebsd: ['freebsd'],
gentoo: ['gentoo'],
'linux-mint': ['linux-mint'],
netbsd: ['netbsd'],
oracle: ['oracle'],
osx: ['osx'],
redhat: ['redhat', 'rhel'],
solaris: ['solaris'],
suse: ['sles', 'suse'],
ubuntu: ['ubuntu'],
windows: ['windows'],
},
osByFamily => {
const osToFamily = Object.create(null)
forEach(osByFamily, (list, family) => {
forEach(list, os => {
osToFamily[os] = family
})
})
})
return osName => osName && osToFamily[osName.toLowerCase()]
})
return osName => osName && osToFamily[osName.toLowerCase()]
}
)
// -------------------------------------------------------------------
export const formatSize = bytes => humanFormat(bytes, { scale: 'binary', unit: 'B' })
export const formatSize = bytes =>
humanFormat(bytes, { scale: 'binary', unit: 'B' })
export const formatSizeShort = bytes => humanFormat(bytes, { scale: 'binary', unit: 'B', decimals: 0 })
export const formatSizeShort = bytes =>
humanFormat(bytes, { scale: 'binary', unit: 'B', decimals: 0 })
export const formatSizeRaw = bytes => humanFormat.raw(bytes, { scale: 'binary', unit: 'B' })
export const formatSizeRaw = bytes =>
humanFormat.raw(bytes, { scale: 'binary', unit: 'B' })
export const formatSpeed = (bytes, milliseconds) => humanFormat(
bytes * 1e3 / milliseconds,
{ scale: 'binary', unit: 'B/s' }
)
export const formatSpeed = (bytes, milliseconds) =>
humanFormat(bytes * 1e3 / milliseconds, { scale: 'binary', unit: 'B/s' })
export const parseSize = size => {
let bytes = humanFormat.parse.raw(size, { scale: 'binary' })
@ -310,14 +310,14 @@ export const routes = (indexRoute, childRoutes) => target => {
indexRoute = undefined
} else if (isFunction(indexRoute)) {
indexRoute = {
component: indexRoute
component: indexRoute,
}
} else if (isString(indexRoute)) {
indexRoute = {
onEnter: invoke(indexRoute, pathname => (state, replace) => {
const current = state.location.pathname
replace((current === '/' ? '' : current) + '/' + pathname)
})
}),
}
}
@ -338,7 +338,7 @@ export const routes = (indexRoute, childRoutes) => target => {
target.route = {
indexRoute,
childRoutes
childRoutes,
}
return target
@ -354,11 +354,7 @@ export const routes = (indexRoute, childRoutes) => target => {
// function foo (param = throwFn('param is required')) {}
// ```
export const throwFn = error => () => {
throw (
isString(error)
? new Error(error)
: error
)
throw isString(error) ? new Error(error) : error
}
// ===================================================================
@ -374,7 +370,7 @@ export const resolveResourceSet = resourceSet => {
...attrs,
missingObjects: [],
objectsByType: resolvedObjects,
ipPools
ipPools,
}
const state = store.getState()
@ -390,7 +386,7 @@ export const resolveResourceSet = resourceSet => {
const { type } = object
if (!resolvedObjects[type]) {
resolvedObjects[type] = [ object ]
resolvedObjects[type] = [object]
} else {
resolvedObjects[type].push(object)
}
@ -422,10 +418,11 @@ export const resolveResourceSets = resourceSets =>
// ```
export function buildTemplate (pattern, rules) {
const regExp = new RegExp(join(map(keys(rules), escapeRegExp), '|'), 'g')
return (...params) => replace(pattern, regExp, match => {
const rule = rules[match]
return isFunction(rule) ? rule(...params) : rule
})
return (...params) =>
replace(pattern, regExp, match => {
const rule = rules[match]
return isFunction(rule) ? rule(...params) : rule
})
}
// ===================================================================
@ -464,9 +461,7 @@ export const htmlFileToStream = file => {
// ===================================================================
export const resolveId = value =>
(value != null && typeof value === 'object' && 'id' in value)
? value.id
: value
value != null && typeof value === 'object' && 'id' in value ? value.id : value
export const resolveIds = params => {
for (const key in params) {
@ -485,28 +480,29 @@ const OPs = {
'<=': a => a <= 0,
'===': a => a === 0,
'>': a => a > 0,
'>=': a => a >= 0
'>=': a => a >= 0,
}
const makeNiceCompare = compare => function () {
const { length } = arguments
if (length === 2) {
return compare(arguments[0], arguments[1])
}
let i = 1
let v1 = arguments[0]
let op, v2
while (i < length) {
op = arguments[i++]
v2 = arguments[i++]
if (!OPs[op](compare(v1, v2))) {
return false
const makeNiceCompare = compare =>
function () {
const { length } = arguments
if (length === 2) {
return compare(arguments[0], arguments[1])
}
v1 = v2
let i = 1
let v1 = arguments[0]
let op, v2
while (i < length) {
op = arguments[i++]
v2 = arguments[i++]
if (!OPs[op](compare(v1, v2))) {
return false
}
v1 = v2
}
return true
}
return true
}
export const compareVersions = makeNiceCompare((v1, v2) => {
v1 = v1.split('.')
@ -523,8 +519,7 @@ export const compareVersions = makeNiceCompare((v1, v2) => {
return 0
})
export const isXosanPack = ({ name }) =>
startsWith(name, 'XOSAN')
export const isXosanPack = ({ name }) => startsWith(name, 'XOSAN')
// ===================================================================
@ -536,7 +531,11 @@ export const getCoresPerSocketPossibilities = (maxCoresPerSocket, vCPUs) => {
if (maxCoresPerSocket !== undefined && vCPUs !== '') {
const ratio = vCPUs / maxVCPUs
for (let coresPerSocket = maxCoresPerSocket; coresPerSocket >= ratio; coresPerSocket--) {
for (
let coresPerSocket = maxCoresPerSocket;
coresPerSocket >= ratio;
coresPerSocket--
) {
if (vCPUs % coresPerSocket === 0) options.push(coresPerSocket)
}
}
@ -579,7 +578,7 @@ export const createFakeProgress = (() => {
const startTime = Date.now() / 1e3
return () => {
const x = Date.now() / 1e3 - startTime
return -Math.exp((x * Math.log(1 - S)) / d) + 1
return -Math.exp(x * Math.log(1 - S) / d) + 1
}
}
})()

View File

@ -9,38 +9,36 @@ import propTypes from '../prop-types-decorator'
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}>
{React.Children.map(children, (child, key) =>
child && cloneElement(child, { allDone, key })
)}
</ul>
return (
<ul className={styles.wizard}>
{React.Children.map(
children,
(child, key) => child && cloneElement(child, { allDone, key })
)}
</ul>
)
}
export { Wizard as default }
@propTypes({
icon: propTypes.string.isRequired,
title: propTypes.string.isRequired
title: propTypes.string.isRequired,
})
export class Section extends Component {
componentWillMount () {
this.setState({isActive: false})
this.setState({ isActive: false })
}
_onFocus = () => this.setState({ isActive: true })
_onBlur = () => this.setState({ isActive: false })
render () {
const {
allDone,
icon,
title,
done,
children
} = this.props
const { allDone, icon, title, done, children } = this.props
return (
<li
className={classNames(
@ -52,11 +50,15 @@ export class Section extends Component {
onBlur={this._onBlur}
>
{/* TITLE */}
<div className={classNames(
styles.title,
(done || allDone) && styles.success
)}>
<h4>{icon && <Icon icon={icon} />} {_(title)}</h4>
<div
className={classNames(
styles.title,
(done || allDone) && styles.success
)}
>
<h4>
{icon && <Icon icon={icon} />} {_(title)}
</h4>
</div>
{/* CONTENT */}
<div

View File

@ -43,7 +43,8 @@ export const get = (accessor, arg) => {
try {
return accessor(arg)
} catch (error) {
if (!(error instanceof TypeError)) { // avoid hiding other errors
if (!(error instanceof TypeError)) {
// avoid hiding other errors
throw error
}
}
@ -58,6 +59,4 @@ export const get = (accessor, arg) => {
// )
// ```
export const ifDef = (value, thenFn) =>
value !== undefined
? thenFn(value)
: value
value !== undefined ? thenFn(value) : value

View File

@ -22,7 +22,7 @@ const XO_TYPE_TO_COMPONENT = {
subject: XoSubjectInput,
tag: XoTagInput,
vm: XoVmInput,
xoobject: XoHighLevelObjectInput
xoobject: XoHighLevelObjectInput,
}
// ===================================================================
@ -41,10 +41,10 @@ const _generateUiSchema = (schema, uiSchema, key) => {
const type = getType(schema)
if (type === 'object') {
const properties = uiSchema.properties = {}
const properties = (uiSchema.properties = {})
forEach(schema.properties, (schema, key) => {
const subUiSchema = properties[key] = {}
const subUiSchema = (properties[key] = {})
_generateUiSchema(schema, subUiSchema, key)
})
} else if (type === 'array') {
@ -54,7 +54,7 @@ const _generateUiSchema = (schema, uiSchema, key) => {
uiSchema.widget = widget
uiSchema.config = { multi: true }
} else {
const subUiSchema = uiSchema.items = {}
const subUiSchema = (uiSchema.items = {})
_generateUiSchema(schema.items, subUiSchema, key)
}
} else if (type === 'string') {

View File

@ -13,9 +13,7 @@ export default class XoAbstractInput extends PureComponent {
const { props } = this
return props.onChange(
props.schema.type === 'array'
? map(value, getId)
: getId(value)
props.schema.type === 'array' ? map(value, getId) : getId(value)
)
}
}

View File

@ -4,16 +4,7 @@ import ChartistTooltip from 'chartist-plugin-tooltip'
import React from 'react'
import { injectIntl } from 'react-intl'
import { messages } from 'intl'
import {
find,
flatten,
floor,
map,
max,
size,
sum,
values
} from 'lodash'
import { find, flatten, floor, map, max, size, sum, values } from 'lodash'
import propTypes from '../prop-types-decorator'
import { computeArraysSum } from '../xo-stats'
@ -35,26 +26,37 @@ const getStatsLength = stats => size(find(stats, stats => stats != null))
// ===================================================================
const makeOptions = ({ intl, nValues, endTimestamp, interval, valueTransform }) => ({
const makeOptions = ({
intl,
nValues,
endTimestamp,
interval,
valueTransform,
}) => ({
showPoint: true,
lineSmooth: false,
showArea: true,
height: 300,
low: 0,
axisX: {
labelInterpolationFnc: makeLabelInterpolationFnc(intl, nValues, endTimestamp, interval),
offset: LABEL_OFFSET_X
labelInterpolationFnc: makeLabelInterpolationFnc(
intl,
nValues,
endTimestamp,
interval
),
offset: LABEL_OFFSET_X,
},
axisY: {
labelInterpolationFnc: valueTransform,
offset: LABEL_OFFSET_Y
offset: LABEL_OFFSET_Y,
},
plugins: [
ChartistLegend(),
ChartistTooltip({
valueTransform: value => valueTransform(+value) // '+value' because tooltip gives a string value...
})
]
valueTransform: value => valueTransform(+value), // '+value' because tooltip gives a string value...
}),
],
})
// ===================================================================
@ -67,19 +69,22 @@ const makeLabelInterpolationFnc = (intl, nValues, endTimestamp, interval) => {
format = {
minute: 'numeric',
hour: 'numeric',
weekday: 'short'
weekday: 'short',
}
} else if (interval === 86400) {
format = {
day: 'numeric',
month: 'numeric',
year: 'numeric'
year: 'numeric',
}
}
return (value, index) =>
index % labelSpace === 0
? intl.formatTime((endTimestamp - (nValues - index - 1) * interval) * 1000, format)
? intl.formatTime(
(endTimestamp - (nValues - index - 1) * interval) * 1000,
format
)
: null
}
@ -96,7 +101,7 @@ const buildSeries = ({ stats, label, addSumSeries }) => {
if (data) {
series.push({
name: `${label}${letter} (${io})`,
data
data,
})
}
}
@ -105,7 +110,7 @@ const buildSeries = ({ stats, label, addSumSeries }) => {
series.push({
name: `All ${io}`,
data: computeArraysSum(values(ioData)),
className: styles.dashedLine
className: styles.dashedLine,
})
}
}
@ -113,411 +118,436 @@ const buildSeries = ({ stats, label, addSumSeries }) => {
return series
}
const templateError =
<div>
No stats.
</div>
const templateError = <div>No stats.</div>
// ===================================================================
export const CpuLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.cpus
const length = getStatsLength(stats)
export const CpuLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.cpus
const length = getStatsLength(stats)
if (!length) {
return templateError
}
if (!length) {
return templateError
}
const series = map(stats, (data, id) => ({
name: `Cpu${id}`,
data
}))
const series = map(stats, (data, id) => ({
name: `Cpu${id}`,
data,
}))
if (addSumSeries) {
series.push({
name: 'All Cpus',
data: computeArraysSum(stats),
className: styles.dashedLine
})
}
if (addSumSeries) {
series.push({
name: 'All Cpus',
data: computeArraysSum(stats),
className: styles.dashedLine,
})
}
return (
<ChartistGraph
type='Line'
data={{
series
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: value => `${floor(value)}%`
}),
high: !addSumSeries ? 100 : stats.length * 100,
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series,
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: value => `${floor(value)}%`,
}),
high: !addSumSeries ? 100 : stats.length * 100,
...options,
}}
/>
)
})
)
export const PoolCpuLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const length = getStatsLength(firstHostData.stats.cpus)
export const PoolCpuLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const length = getStatsLength(firstHostData.stats.cpus)
if (!length) {
return templateError
}
if (!length) {
return templateError
}
const series = map(data, ({ host, stats }) => ({
name: host,
data: computeArraysSum(stats.cpus)
}))
const series = map(data, ({ host, stats }) => ({
name: host,
data: computeArraysSum(stats.cpus),
}))
if (addSumSeries) {
series.push({
name: intl.formatMessage(messages.poolAllHosts),
data: computeArraysSum(map(series, 'data')),
className: styles.dashedLine
})
}
if (addSumSeries) {
series.push({
name: intl.formatMessage(messages.poolAllHosts),
data: computeArraysSum(map(series, 'data')),
className: styles.dashedLine,
})
}
const nbCpusByHost = map(data, ({ stats }) => stats.cpus.length)
const nbCpusByHost = map(data, ({ stats }) => stats.cpus.length)
return (
<ChartistGraph
type='Line'
data={{
series
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: value => `${floor(value)}%`
}),
high: 100 * (addSumSeries ? sum(nbCpusByHost) : max(nbCpusByHost)),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series,
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: value => `${floor(value)}%`,
}),
high: 100 * (addSumSeries ? sum(nbCpusByHost) : max(nbCpusByHost)),
...options,
}}
/>
)
})
)
export const MemoryLineChart = injectIntl(propTypes({
data: propTypes.object.isRequired,
options: propTypes.object
})(({ data, options = {}, intl }) => {
const {
memory,
memoryUsed
} = data.stats
export const MemoryLineChart = injectIntl(
propTypes({
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ data, options = {}, intl }) => {
const { memory, memoryUsed } = data.stats
if (!memory || !memoryUsed) {
return templateError
}
if (!memory || !memoryUsed) {
return templateError
}
return (
<ChartistGraph
type='Line'
data={{
series: [{
name: 'RAM',
data: memoryUsed
}]
}}
options={{
...makeOptions({
intl,
nValues: memoryUsed.length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize
}),
high: memory[memory.length - 1],
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series: [
{
name: 'RAM',
data: memoryUsed,
},
],
}}
options={{
...makeOptions({
intl,
nValues: memoryUsed.length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize,
}),
high: memory[memory.length - 1],
...options,
}}
/>
)
})
)
export const PoolMemoryLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const {
memory,
memoryUsed
} = firstHostData.stats
export const PoolMemoryLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const { memory, memoryUsed } = firstHostData.stats
if (!memory || !memoryUsed) {
return templateError
}
if (!memory || !memoryUsed) {
return templateError
}
const series = map(data, ({ host, stats }) => ({
name: host,
data: stats.memoryUsed
}))
const series = map(data, ({ host, stats }) => ({
name: host,
data: stats.memoryUsed,
}))
if (addSumSeries) {
series.push({
name: intl.formatMessage(messages.poolAllHosts),
data: computeArraysSum(map(data, 'stats.memoryUsed')),
className: styles.dashedLine
})
}
if (addSumSeries) {
series.push({
name: intl.formatMessage(messages.poolAllHosts),
data: computeArraysSum(map(data, 'stats.memoryUsed')),
className: styles.dashedLine,
})
}
const currentMemoryByHost = map(data, ({ stats }) => stats.memory[stats.memory.length - 1])
const currentMemoryByHost = map(
data,
({ stats }) => stats.memory[stats.memory.length - 1]
)
return (
<ChartistGraph
type='Line'
data={{
series
}}
options={{
...makeOptions({
intl,
nValues: firstHostData.stats.memoryUsed.length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: formatSize
}),
high: addSumSeries ? sum(currentMemoryByHost) : max(currentMemoryByHost),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series,
}}
options={{
...makeOptions({
intl,
nValues: firstHostData.stats.memoryUsed.length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: formatSize,
}),
high: addSumSeries
? sum(currentMemoryByHost)
: max(currentMemoryByHost),
...options,
}}
/>
)
})
)
export const XvdLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.xvds
const length = stats && getStatsLength(stats.r)
export const XvdLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.xvds
const length = stats && getStatsLength(stats.r)
if (!length) {
return templateError
}
if (!length) {
return templateError
}
return (
<ChartistGraph
type='Line'
data={{
series: buildSeries({ addSumSeries, stats, label: 'Xvd' })
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize
}),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series: buildSeries({ addSumSeries, stats, label: 'Xvd' }),
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize,
}),
...options,
}}
/>
)
})
)
export const VifLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.vifs
const length = stats && getStatsLength(stats.rx)
export const VifLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.vifs
const length = stats && getStatsLength(stats.rx)
if (!length) {
return templateError
}
if (!length) {
return templateError
}
return (
<ChartistGraph
type='Line'
data={{
series: buildSeries({ addSumSeries, stats, label: 'Vif' })
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize
}),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series: buildSeries({ addSumSeries, stats, label: 'Vif' }),
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize,
}),
...options,
}}
/>
)
})
)
export const PifLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.pifs
const length = stats && getStatsLength(stats.rx)
export const PifLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const stats = data.stats.pifs
const length = stats && getStatsLength(stats.rx)
if (!length) {
return templateError
}
if (!length) {
return templateError
}
return (
<ChartistGraph
type='Line'
data={{
series: buildSeries({ addSumSeries, stats, label: 'Pif' })
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize
}),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series: buildSeries({ addSumSeries, stats, label: 'Pif' }),
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: formatSize,
}),
...options,
}}
/>
)
})
)
const ios = ['rx', 'tx']
export const PoolPifLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const length = firstHostData.stats && getStatsLength(firstHostData.stats.pifs.rx)
export const PoolPifLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const length =
firstHostData.stats && getStatsLength(firstHostData.stats.pifs.rx)
if (!length) {
return templateError
}
if (!length) {
return templateError
}
const series = addSumSeries
? map(ios, io => ({
name: `${intl.formatMessage(messages.poolAllHosts)} (${io})`,
data: computeArraysSum(map(data, ({ stats }) => computeArraysSum(stats.pifs[io])))
}))
: flatten(map(data, ({ stats, host }) =>
map(ios, io => ({
name: `${host} (${io})`,
data: computeArraysSum(stats.pifs[io])
const series = addSumSeries
? map(ios, io => ({
name: `${intl.formatMessage(messages.poolAllHosts)} (${io})`,
data: computeArraysSum(
map(data, ({ stats }) => computeArraysSum(stats.pifs[io]))
),
}))
))
: flatten(
map(data, ({ stats, host }) =>
map(ios, io => ({
name: `${host} (${io})`,
data: computeArraysSum(stats.pifs[io]),
}))
)
)
return (
<ChartistGraph
type='Line'
data={{
series
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: formatSize
}),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series,
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: formatSize,
}),
...options,
}}
/>
)
})
)
export const LoadLineChart = injectIntl(propTypes({
data: propTypes.object.isRequired,
options: propTypes.object
})(({ data, options = {}, intl }) => {
const stats = data.stats.load
const { length } = stats || {}
export const LoadLineChart = injectIntl(
propTypes({
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ data, options = {}, intl }) => {
const stats = data.stats.load
const { length } = stats || {}
if (!length) {
return templateError
}
if (!length) {
return templateError
}
return (
<ChartistGraph
type='Line'
data={{
series: [{
name: 'Load average',
data: stats
}]
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: value => `${value.toPrecision(3)}`
}),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series: [
{
name: 'Load average',
data: stats,
},
],
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: data.endTimestamp,
interval: data.interval,
valueTransform: value => `${value.toPrecision(3)}`,
}),
...options,
}}
/>
)
})
)
export const PoolLoadLineChart = injectIntl(propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const length = firstHostData.stats && firstHostData.stats.load.length
export const PoolLoadLineChart = injectIntl(
propTypes({
addSumSeries: propTypes.bool,
data: propTypes.object.isRequired,
options: propTypes.object,
})(({ addSumSeries, data, options = {}, intl }) => {
const firstHostData = data[0]
const length = firstHostData.stats && firstHostData.stats.load.length
if (!length) {
return templateError
}
if (!length) {
return templateError
}
const series = map(data, ({ host, stats }) => ({
name: host,
data: stats.load
}))
const series = map(data, ({ host, stats }) => ({
name: host,
data: stats.load,
}))
if (addSumSeries) {
series.push({
name: intl.formatMessage(messages.poolAllHosts),
data: computeArraysSum(map(data, 'stats.load')),
className: styles.dashedLine
})
}
if (addSumSeries) {
series.push({
name: intl.formatMessage(messages.poolAllHosts),
data: computeArraysSum(map(data, 'stats.load')),
className: styles.dashedLine,
})
}
return (
<ChartistGraph
type='Line'
data={{
series
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: value => `${value.toPrecision(3)}`
}),
...options
}}
/>
)
}))
return (
<ChartistGraph
type='Line'
data={{
series,
}}
options={{
...makeOptions({
intl,
nValues: length,
endTimestamp: firstHostData.endTimestamp,
interval: firstHostData.interval,
valueTransform: value => `${value.toPrecision(3)}`,
}),
...options,
}}
/>
)
})
)

View File

@ -33,7 +33,7 @@ const SVG_STYLE = {
left: 0,
position: 'absolute',
top: 0,
width: '100%'
width: '100%',
}
const SVG_CONTAINER_STYLE = {
@ -41,34 +41,34 @@ const SVG_CONTAINER_STYLE = {
'vertical-align': 'middle',
overflow: 'hidden',
position: 'relative',
width: '100%'
width: '100%',
}
const SVG_CONTENT = {
'font-size': `${CHART_WIDTH / 100}px`
'font-size': `${CHART_WIDTH / 100}px`,
}
const COLUMN_TITLE_STYLE = {
'font-size': '100%',
'font-weight': 'bold',
'text-anchor': 'middle'
'text-anchor': 'middle',
}
const COLUMN_VALUES_STYLE = {
'font-size': '100%'
'font-size': '100%',
}
const LINES_CONTAINER_STYLE = {
'stroke-opacity': 0.5,
'stroke-width': CHART_WIDTH / DEFAULT_STROKE_WIDTH_FACTOR,
fill: 'none',
stroke: 'red'
stroke: 'red',
}
const TOOLTIP_STYLE = {
'fill': 'white',
fill: 'white',
'font-size': '125%',
'font-weight': 'bold'
'font-weight': 'bold',
}
// ===================================================================
@ -78,11 +78,11 @@ const TOOLTIP_STYLE = {
propTypes.shape({
data: propTypes.object.isRequired,
label: propTypes.string.isRequired,
objectId: propTypes.string.isRequired
objectId: propTypes.string.isRequired,
})
).isRequired,
labels: propTypes.object.isRequired,
renderers: propTypes.object
renderers: propTypes.object,
})
export default class XoParallelChart extends Component {
_line = d3.line()
@ -92,17 +92,17 @@ export default class XoParallelChart extends Component {
_handleBrush = () => {
// 1. Get selected brushes.
const brushes = []
this._svg.selectAll('.chartColumn')
this._svg
.selectAll('.chartColumn')
.selectAll('.brush')
.each((_1, _2, [ brush ]) => {
.each((_1, _2, [brush]) => {
if (d3.brushSelection(brush) != null) {
brushes.push(brush)
}
})
// 2. Change stroke of selected lines.
const lines = this._svg.select('.linesContainer')
.selectAll('path')
const lines = this._svg.select('.linesContainer').selectAll('path')
lines.each((elem, lineId, lines) => {
const { data } = elem
@ -112,7 +112,10 @@ export default class XoParallelChart extends Component {
const columnId = brush.__data__
const { invert } = this._y[columnId] // Range to domain.
return invert(selection[1]) <= data[columnId] && data[columnId] <= invert(selection[0])
return (
invert(selection[1]) <= data[columnId] &&
data[columnId] <= invert(selection[0])
)
})
const line = d3.select(lines[lineId])
@ -125,9 +128,13 @@ export default class XoParallelChart extends Component {
})
}
_brush = d3.brushY()
_brush = d3
.brushY()
// Brush area: (x0, y0), (x1, y1)
.extent([[-BRUSH_SELECTION_WIDTH / 2, 0], [BRUSH_SELECTION_WIDTH / 2, CHART_HEIGHT]])
.extent([
[-BRUSH_SELECTION_WIDTH / 2, 0],
[BRUSH_SELECTION_WIDTH / 2, CHART_HEIGHT],
])
.on('brush', this._handleBrush)
.on('end', this._handleBrush)
@ -135,9 +142,7 @@ export default class XoParallelChart extends Component {
const svg = this._svg
// Reset tooltip.
svg
.selectAll('.objectTooltip')
.remove()
svg.selectAll('.objectTooltip').remove()
// Reset all lines.
svg
@ -155,17 +160,19 @@ export default class XoParallelChart extends Component {
const { label } = elem
const tooltip = svg.append('g')
.attr('class', 'objectTooltip')
const tooltip = svg.append('g').attr('class', 'objectTooltip')
const bbox = tooltip.append('text')
const bbox = tooltip
.append('text')
.text(label)
.attr('x', position[0])
.attr('y', position[1] - 30)
::setStyles(TOOLTIP_STYLE)
.node().getBBox()
.node()
.getBBox()
tooltip.insert('rect', '*')
tooltip
.insert('rect', '*')
.attr('x', bbox.x - TOOLTIP_PADDING)
.attr('y', bbox.y - TOOLTIP_PADDING)
.attr('width', bbox.width + TOOLTIP_PADDING * 2)
@ -177,7 +184,7 @@ export default class XoParallelChart extends Component {
this._highlight(elem, d3.mouse(paths[pathId]))
}
_handleMouseOut = (elem) => {
_handleMouseOut = elem => {
this._highlight()
}
@ -187,47 +194,49 @@ export default class XoParallelChart extends Component {
const columnsIds = keys(labels)
const spacing = (CHART_WIDTH - 200) / (columnsIds.length - 1)
const x = d3.scaleOrdinal()
.domain(columnsIds).range(
times(columnsIds.length, n => n * spacing)
)
const x = d3
.scaleOrdinal()
.domain(columnsIds)
.range(times(columnsIds.length, n => n * spacing))
// 1. Remove old nodes.
svg
.selectAll('.chartColumn')
.remove()
svg.selectAll('.chartColumn').remove()
svg
.selectAll('.linesContainer')
.remove()
svg.selectAll('.linesContainer').remove()
// 2. Build Ys.
const y = this._y = {}
const y = (this._y = {})
forEach(columnsIds, (columnId, index) => {
const max = d3.max(dataSet, elem => elem.data[columnId])
y[columnId] = d3.scaleLinear()
y[columnId] = d3
.scaleLinear()
.domain([0, max])
.range([CHART_HEIGHT, 0])
})
// 3. Build columns.
const columns = svg.selectAll('.chartColumn')
const columns = svg
.selectAll('.chartColumn')
.data(columnsIds)
.enter().append('g')
.attr('class', 'chartColumn')
.attr('transform', d => `translate(${x(d)})`)
.enter()
.append('g')
.attr('class', 'chartColumn')
.attr('transform', d => `translate(${x(d)})`)
// 4. Draw titles.
columns.append('text')
;columns
.append('text')
.text(columnId => labels[columnId])
.attr('y', -50)
::setStyles(COLUMN_TITLE_STYLE)
// 5. Draw axis.
columns.append('g')
;columns
.append('g')
.each((columnId, axisId, axes) => {
const axis = d3.axisLeft()
const axis = d3
.axisLeft()
.ticks(N_TICKS, ',f')
.tickSize(TICK_SIZE)
.scale(y[columnId])
@ -244,42 +253,54 @@ export default class XoParallelChart extends Component {
::setStyles(COLUMN_VALUES_STYLE)
// 6. Draw lines.
const path = elem => this._line(map(columnsIds.map(
columnId => [x(columnId), y[columnId](elem.data[columnId])]
)))
svg.append('g')
const path = elem =>
this._line(
map(
columnsIds.map(columnId => [
x(columnId),
y[columnId](elem.data[columnId]),
])
)
)
;svg
.append('g')
.attr('class', 'linesContainer')
::setStyles(LINES_CONTAINER_STYLE)
.selectAll('path')
.data(dataSet)
.enter().append('path')
.attr('d', path)
.attr('class', 'chartLine')
.attr('id', elem => 'chartLine-' + elem.objectId)
.attr('stroke', elem => this._color(elem.label))
.attr('shape-rendering', 'optimizeQuality')
.attr('stroke-linecap', 'round')
.attr('stroke-linejoin', 'round')
.on('mouseover', this._handleMouseOver)
.on('mouseout', this._handleMouseOut)
.data(dataSet)
.enter()
.append('path')
.attr('d', path)
.attr('class', 'chartLine')
.attr('id', elem => 'chartLine-' + elem.objectId)
.attr('stroke', elem => this._color(elem.label))
.attr('shape-rendering', 'optimizeQuality')
.attr('stroke-linecap', 'round')
.attr('stroke-linejoin', 'round')
.on('mouseover', this._handleMouseOver)
.on('mouseout', this._handleMouseOut)
// 7. Brushes.
columns.append('g')
columns
.append('g')
.attr('class', 'brush')
.each((_, brushId, brushes) => { d3.select(brushes[brushId]).call(this._brush) })
.each((_, brushId, brushes) => {
d3.select(brushes[brushId]).call(this._brush)
})
}
componentDidMount () {
this._svg = d3.select(this.refs.chart)
this._svg = d3
.select(this.refs.chart)
.append('div')
::setStyles(SVG_CONTAINER_STYLE)
.append('svg')
::setStyles(SVG_STYLE)
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox', `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`)
.append('g')
.attr('transform', `translate(${100}, ${100})`)
::setStyles(SVG_CONTENT)
.append('svg')
::setStyles(SVG_STYLE)
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox', `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`)
.append('g')
.attr('transform', `translate(${100}, ${100})`)
::setStyles(SVG_CONTENT)
this._draw()
}

View File

@ -1,14 +1,8 @@
import React from 'react'
import {
Sparklines,
SparklinesLine
} from 'react-sparklines'
import { Sparklines, SparklinesLine } from 'react-sparklines'
import propTypes from './prop-types-decorator'
import {
computeArraysAvg,
computeObjectsAvg
} from './xo-stats'
import { computeArraysAvg, computeObjectsAvg } from './xo-stats'
const STYLE = {}
@ -18,15 +12,12 @@ const STROKE_WIDTH = 0.5
// ===================================================================
const templateError =
<div>
No stats.
</div>
const templateError = <div>No stats.</div>
// ===================================================================
export const CpuSparkLines = propTypes({
data: propTypes.object.isRequired
data: propTypes.object.isRequired,
})(({ data, width = WIDTH, height = HEIGHT, strokeWidth = STROKE_WIDTH }) => {
const { cpus } = data.stats
@ -35,14 +26,29 @@ export const CpuSparkLines = propTypes({
}
return (
<Sparklines style={STYLE} data={computeArraysAvg(cpus)} max={100} min={0} width={width} height={height}>
<SparklinesLine style={{ strokeWidth, stroke: '#366e98', fill: '#366e98', fillOpacity: 0.5 }} color='#2598d9' />
<Sparklines
style={STYLE}
data={computeArraysAvg(cpus)}
max={100}
min={0}
width={width}
height={height}
>
<SparklinesLine
style={{
strokeWidth,
stroke: '#366e98',
fill: '#366e98',
fillOpacity: 0.5,
}}
color='#2598d9'
/>
</Sparklines>
)
})
export const MemorySparkLines = propTypes({
data: propTypes.object.isRequired
data: propTypes.object.isRequired,
})(({ data, width = WIDTH, height = HEIGHT, strokeWidth = STROKE_WIDTH }) => {
const { memory, memoryUsed } = data.stats
@ -51,14 +57,29 @@ export const MemorySparkLines = propTypes({
}
return (
<Sparklines style={STYLE} data={memoryUsed} max={memory[memory.length - 1]} min={0} width={width} height={height}>
<SparklinesLine style={{ strokeWidth, stroke: '#990822', fill: '#990822', fillOpacity: 0.5 }} color='#cc0066' />
<Sparklines
style={STYLE}
data={memoryUsed}
max={memory[memory.length - 1]}
min={0}
width={width}
height={height}
>
<SparklinesLine
style={{
strokeWidth,
stroke: '#990822',
fill: '#990822',
fillOpacity: 0.5,
}}
color='#cc0066'
/>
</Sparklines>
)
})
export const XvdSparkLines = propTypes({
data: propTypes.object.isRequired
data: propTypes.object.isRequired,
})(({ data, width = WIDTH, height = HEIGHT, strokeWidth = STROKE_WIDTH }) => {
const { xvds } = data.stats
@ -67,26 +88,56 @@ export const XvdSparkLines = propTypes({
}
return (
<Sparklines style={STYLE} data={computeObjectsAvg(xvds)} min={0} width={width} height={height}>
<SparklinesLine style={{ strokeWidth, stroke: '#089944', fill: '#089944', fillOpacity: 0.5 }} color='#33cc33' />
<Sparklines
style={STYLE}
data={computeObjectsAvg(xvds)}
min={0}
width={width}
height={height}
>
<SparklinesLine
style={{
strokeWidth,
stroke: '#089944',
fill: '#089944',
fillOpacity: 0.5,
}}
color='#33cc33'
/>
</Sparklines>
)
})
export const NetworkSparkLines = propTypes({
data: propTypes.object.isRequired
data: propTypes.object.isRequired,
})(({ data, width = WIDTH, height = HEIGHT, strokeWidth = STROKE_WIDTH }) => {
const { pifs, vifs: ifs = pifs } = data.stats
return ifs === undefined
? templateError
: <Sparklines style={STYLE} data={computeObjectsAvg(ifs)} min={0} width={width} height={height}>
<SparklinesLine style={{ strokeWidth, stroke: '#eca649', fill: '#eca649', fillOpacity: 0.5 }} color='#ffd633' />
return ifs === undefined ? (
templateError
) : (
<Sparklines
style={STYLE}
data={computeObjectsAvg(ifs)}
min={0}
width={width}
height={height}
>
<SparklinesLine
style={{
strokeWidth,
stroke: '#eca649',
fill: '#eca649',
fillOpacity: 0.5,
}}
color='#ffd633'
/>
</Sparklines>
)
})
export const LoadSparkLines = propTypes({
data: propTypes.object.isRequired
data: propTypes.object.isRequired,
})(({ data, width = WIDTH, height = HEIGHT, strokeWidth = STROKE_WIDTH }) => {
const { load } = data.stats
@ -96,7 +147,15 @@ export const LoadSparkLines = propTypes({
return (
<Sparklines style={STYLE} data={load} min={0} width={width} height={height}>
<SparklinesLine style={{ strokeWidth, stroke: '#33cc33', fill: '#33cc33', fillOpacity: 0.5 }} color='#33cc33' />
<SparklinesLine
style={{
strokeWidth,
stroke: '#33cc33',
fill: '#33cc33',
fillOpacity: 0.5,
}}
color='#33cc33'
/>
</Sparklines>
)
})

View File

@ -52,14 +52,17 @@ const _computeArraysAvg = arrays => {
//
// It's a fix to avoid error like `Uncaught TypeError: Cannot read property 'length' of null`.
// FIXME: Repare this bug in xo-server. (Warning: Can break the stats of xo-web v4.)
const removeUndefinedArrays = arrays => mapPlus(arrays, (array, push) => {
if (array != null) {
push(array)
}
})
const removeUndefinedArrays = arrays =>
mapPlus(arrays, (array, push) => {
if (array != null) {
push(array)
}
})
export const computeArraysSum = arrays => _computeArraysSum(removeUndefinedArrays(arrays))
export const computeArraysAvg = arrays => _computeArraysAvg(removeUndefinedArrays(arrays))
export const computeArraysSum = arrays =>
_computeArraysSum(removeUndefinedArrays(arrays))
export const computeArraysAvg = arrays =>
_computeArraysAvg(removeUndefinedArrays(arrays))
// More complex than computeArraysAvg.
//
@ -72,8 +75,6 @@ export const computeArraysAvg = arrays => _computeArraysAvg(removeUndefinedArray
// Note: The parameter can be also an 3D array.
export const computeObjectsAvg = objects => {
return _computeArraysAvg(
map(objects, object =>
computeArraysAvg(values(object))
)
map(objects, object => computeArraysAvg(values(object)))
)
}

View File

@ -8,14 +8,8 @@ import _ from '../intl'
import propTypes from '../prop-types-decorator'
import { Toggle } from '../form'
import { setStyles } from '../d3-utils'
import {
createGetObject,
createSelector
} from '../selectors'
import {
connectStore,
propsEqual
} from '../utils'
import { createGetObject, createSelector } from '../selectors'
import { connectStore, propsEqual } from '../utils'
import styles from './index.css'
@ -24,33 +18,33 @@ import styles from './index.css'
const X_AXIS_STYLE = {
'shape-rendering': 'crispEdges',
fill: 'none',
stroke: '#000'
stroke: '#000',
}
const X_AXIS_TEXT_STYLE = {
'font-size': '125%',
fill: 'black',
stroke: 'transparent'
stroke: 'transparent',
}
const LABEL_STYLE = {
'font-size': '125%'
'font-size': '125%',
}
const MOUSE_AREA_STYLE = {
'pointer-events': 'all',
fill: 'none'
fill: 'none',
}
const HOVER_LINE_STYLE = {
'stroke-width': '2px',
'stroke-dasharray': '5 5',
stroke: 'red',
fill: 'none'
fill: 'none',
}
const HOVER_TEXT_STYLE = {
fill: 'black'
fill: 'black',
}
const HORIZON_AREA_N_STEPS = 4
@ -59,7 +53,7 @@ const HORIZON_AREA_PATH_STYLE = {
'fill-opacity': 0.25,
'stroke-opacity': 0.3,
fill: 'darkgreen',
stroke: 'transparent'
stroke: 'transparent',
}
// ===================================================================
@ -70,20 +64,18 @@ const HORIZON_AREA_PATH_STYLE = {
data: propTypes.arrayOf(
propTypes.shape({
date: propTypes.number.isRequired,
value: propTypes.number.isRequired
value: propTypes.number.isRequired,
})
).isRequired,
maxValue: propTypes.number,
objectId: propTypes.string.isRequired,
onTooltipChange: propTypes.func.isRequired,
tooltipX: propTypes.number.isRequired,
valueRenderer: propTypes.func
valueRenderer: propTypes.func,
})
@connectStore(() => {
const label = createSelector(
createGetObject(
(_, props) => props.objectId
),
createGetObject((_, props) => props.objectId),
object => object.name_label
)
@ -93,7 +85,7 @@ class XoWeekChart extends Component {
static defaultProps = {
chartHeight: 70,
chartWidth: 300,
valueRenderer: value => value
valueRenderer: value => value,
}
_x = d3.scaleTime()
@ -101,10 +93,10 @@ class XoWeekChart extends Component {
_bisectDate = d3.bisector(elem => elem.date).left
_xAxis = d3.axisBottom()
.scale(this._x)
_xAxis = d3.axisBottom().scale(this._x)
_line = d3.line()
_line = d3
.line()
.x(elem => this._x(elem.date))
.y(elem => this._y(elem.value))
@ -115,10 +107,12 @@ class XoWeekChart extends Component {
// Start.
let date = new Date(data[0].date)
for (let i = 0; i < HORIZON_AREA_N_STEPS; i++) {
splittedData[i] = [{
date,
value: 0
}]
splittedData[i] = [
{
date,
value: 0,
},
]
}
// Middle.
@ -127,7 +121,10 @@ class XoWeekChart extends Component {
for (let i = 0; i < HORIZON_AREA_N_STEPS; i++) {
splittedData[i].push({
date,
value: Math.min(Math.max(0, elem.value - intervalSize * i), intervalSize)
value: Math.min(
Math.max(0, elem.value - intervalSize * i),
intervalSize
),
})
}
})
@ -137,7 +134,7 @@ class XoWeekChart extends Component {
for (let i = 0; i < HORIZON_AREA_N_STEPS; i++) {
splittedData[i].push({
date,
value: 0
value: 0,
})
}
@ -146,13 +143,17 @@ class XoWeekChart extends Component {
const svg = this._svg
svg.select('.horizon-area').selectAll('path').remove()
svg
.select('.horizon-area')
.selectAll('path')
.remove()
forEach(splittedData, data => {
svg.select('.horizon-area')
;svg
.select('.horizon-area')
.append('path')
.datum(data)
.attr('d', this._line)
::setStyles(HORIZON_AREA_PATH_STYLE)
.datum(data)
.attr('d', this._line)
::setStyles(HORIZON_AREA_PATH_STYLE)
})
}
@ -173,25 +174,28 @@ class XoWeekChart extends Component {
.attr('width', width)
.attr('height', height)
.select('.mouse-area')
.attr('width', horizonAreaWidth)
.attr('height', horizonAreaHeight)
.attr('width', horizonAreaWidth)
.attr('height', horizonAreaHeight)
svg.select('.hover-container')
svg
.select('.hover-container')
.select('.hover-line')
.attr('y2', horizonAreaHeight)
.attr('y2', horizonAreaHeight)
// 2. Draw horizon area.
this._drawHorizonArea(props.data, props.maxValue)
// 3. Update x axis.
svg.select('.x-axis')
;svg
.select('.x-axis')
.call(this._xAxis)
.attr('transform', `translate(0, ${props.chartHeight})`)
.selectAll('text')
::setStyles(X_AXIS_TEXT_STYLE)
::setStyles(X_AXIS_TEXT_STYLE)
// 4. Update label.
svg.select('.label')
svg
.select('.label')
.attr('dx', 5)
.attr('dy', 20)
.text(props.label)
@ -223,11 +227,13 @@ class XoWeekChart extends Component {
const { props } = this
const hover = this._svg.select('.hover-container')
hover.select('.hover-line')
hover
.select('.hover-line')
.attr('x1', x)
.attr('x2', x)
hover.select('.hover-text')
hover
.select('.hover-text')
.attr('dx', x + 5)
.attr('dy', props.chartHeight / 2)
.text(props.valueRenderer(elem.value))
@ -236,38 +242,44 @@ class XoWeekChart extends Component {
componentDidMount () {
// Horizon area ----------------------------------------
const svg = this._svg = d3.select(this.refs.chart)
const svg = (this._svg = d3
.select(this.refs.chart)
.append('svg')
.attr('transform', `translate(${HORIZON_AREA_MARGIN}, 0)`)
.attr('transform', `translate(${HORIZON_AREA_MARGIN}, 0)`))
svg.append('g')
;svg
.append('g')
.attr('class', 'x-axis')
::setStyles(X_AXIS_STYLE)
svg.append('g')
.attr('class', 'horizon-area')
svg.append('g').attr('class', 'horizon-area')
svg.append('text')
;svg
.append('text')
.attr('class', 'label')
::setStyles(LABEL_STYLE)
// Tooltip ---------------------------------------------
svg.append('rect')
;svg
.append('rect')
.attr('class', 'mouse-area')
.on('mousemove', this._handleMouseMove)
::setStyles(MOUSE_AREA_STYLE)
const hover = svg.append('g')
const hover = svg
.append('g')
.attr('class', 'hover-container')
::setStyles('pointer-events', 'none')
hover.append('line')
;hover
.append('line')
.attr('class', 'hover-line')
.attr('y1', 0)
::setStyles(HOVER_LINE_STYLE)
hover.append('text')
;hover
.append('text')
.attr('class', 'hover-text')
::setStyles(HOVER_TEXT_STYLE)
@ -277,11 +289,14 @@ class XoWeekChart extends Component {
componentWillReceiveProps (nextProps) {
const { props } = this
if (!propsEqual(
props,
nextProps,
[ 'chartHeight', 'chartWidth', 'data', 'maxValue' ]
)) {
if (
!propsEqual(props, nextProps, [
'chartHeight',
'chartWidth',
'data',
'maxValue',
])
) {
this._draw(nextProps)
}
@ -304,19 +319,19 @@ class XoWeekChart extends Component {
data: propTypes.arrayOf(
propTypes.shape({
date: propTypes.number.isRequired,
value: propTypes.number.isRequired
value: propTypes.number.isRequired,
})
).isRequired,
objectId: propTypes.string.isRequired
objectId: propTypes.string.isRequired,
})
).isRequired,
valueRenderer: propTypes.func
valueRenderer: propTypes.func,
})
export default class XoWeekCharts extends Component {
_handleResize = () => {
const { container } = this.refs
this.setState({
chartsWidth: container && container.offsetWidth
chartsWidth: container && container.offsetWidth,
})
}
@ -337,7 +352,7 @@ export default class XoWeekCharts extends Component {
}
this.setState({
maxValue: max
maxValue: max,
})
}
@ -348,7 +363,7 @@ export default class XoWeekCharts extends Component {
componentWillMount () {
this.setState({
tooltipX: 0
tooltipX: 0,
})
}
@ -366,39 +381,29 @@ export default class XoWeekCharts extends Component {
}
render () {
const {
props,
state: {
chartsWidth,
maxValue,
tooltipX
}
} = this
const { props, state: { chartsWidth, maxValue, tooltipX } } = this
return (
<div>
<div>
<p className='mt-1'>
{_('weeklyChartsScaleInfo')}
{' '}
{_('weeklyChartsScaleInfo')}{' '}
<Toggle iconSize={1} icon='scale' onChange={this._updateScale} />
</p>
</div>
<div
ref='container'
className={styles.container}
>
{chartsWidth && map(props.series, (series, key) => (
<XoWeekChart
{...series}
chartWidth={chartsWidth}
key={key}
maxValue={maxValue}
onTooltipChange={this._handleTooltipChange}
tooltipX={tooltipX}
valueRenderer={props.valueRenderer}
/>
))}
<div ref='container' className={styles.container}>
{chartsWidth &&
map(props.series, (series, key) => (
<XoWeekChart
{...series}
chartWidth={chartsWidth}
key={key}
maxValue={maxValue}
onTooltipChange={this._handleTooltipChange}
tooltipX={tooltipX}
valueRenderer={props.valueRenderer}
/>
))}
</div>
</div>
)

View File

@ -4,11 +4,7 @@ import map from 'lodash/map'
import moment from 'moment'
import sortBy from 'lodash/sortBy'
import times from 'lodash/times'
import {
extent,
interpolateViridis,
scaleSequential
} from 'd3'
import { extent, interpolateViridis, scaleSequential } from 'd3'
import { FormattedTime } from 'react-intl'
import _ from '../intl'
@ -22,7 +18,7 @@ import styles from './index.css'
const DAY_TIME_FORMAT = {
day: 'numeric',
month: 'numeric'
month: 'numeric',
}
// ===================================================================
@ -32,7 +28,7 @@ const computeColorGen = days => {
let max = Number.MIN_VALUE
forEach(days, day => {
const [ _min, _max ] = extent(day.hours, value => value && value.value)
const [_min, _max] = extent(day.hours, value => value && value.value)
if (_min < min) {
min = _min
@ -62,12 +58,10 @@ const computeMissingDays = days => {
if (diff > 1) {
const missingDays = times(diff - 1, () => ({
hours,
timestamp: a.subtract(1, 'days').valueOf()
timestamp: a.subtract(1, 'days').valueOf(),
})).reverse()
correctedDays.splice.apply(
correctedDays, [i, 0].concat(missingDays)
)
correctedDays.splice.apply(correctedDays, [i, 0].concat(missingDays))
}
a = b
@ -83,13 +77,13 @@ const computeMissingDays = days => {
data: propTypes.arrayOf(
propTypes.shape({
date: propTypes.number.isRequired,
value: propTypes.number.isRequired
value: propTypes.number.isRequired,
})
).isRequired
).isRequired,
})
export default class XoWeekHeatmap extends Component {
static defaultProps = {
cellRenderer: value => value
cellRenderer: value => value,
}
componentWillReceiveProps (nextProps) {
@ -114,7 +108,7 @@ export default class XoWeekHeatmap extends Component {
if (!days[dayId]) {
days[dayId] = {
hours: new Array(24),
timestamp: elem.date
timestamp: elem.date,
}
}
@ -124,7 +118,7 @@ export default class XoWeekHeatmap extends Component {
hours[hourId] = {
date,
nb: 1,
value
value,
}
} else {
const hour = hours[hourId]
@ -146,9 +140,7 @@ export default class XoWeekHeatmap extends Component {
})
this.setState({
days: computeMissingDays(
sortBy(days, 'timestamp')
)
days: computeMissingDays(sortBy(days, 'timestamp')),
})
}
@ -158,16 +150,26 @@ export default class XoWeekHeatmap extends Component {
<tbody>
<tr>
<th />
{times(24, hour => <th key={hour} className='text-xs-center'>{hour}</th>)}
{times(24, hour => (
<th key={hour} className='text-xs-center'>
{hour}
</th>
))}
</tr>
{map(this.state.days, (day, key) => (
<tr key={key}>
<th><FormattedTime value={day.timestamp} {...DAY_TIME_FORMAT} /></th>
<th>
<FormattedTime value={day.timestamp} {...DAY_TIME_FORMAT} />
</th>
{map(day.hours, (hour, key) => (
<Tooltip
content={hour
? _('weekHeatmapData', { date: hour.date, value: this.props.cellRenderer(hour.value) })
: _('weekHeatmapNoData')
content={
hour
? _('weekHeatmapData', {
date: hour.date,
value: this.props.cellRenderer(hour.value),
})
: _('weekHeatmapNoData')
}
key={key}
>

View File

@ -5,43 +5,46 @@ import React from 'react'
import SingleLineRow from 'single-line-row'
import { Col } from 'grid'
import { connectStore } from 'utils'
import { createCollectionWrapper, createGetObjectsOfType, createSelector, createGetObject } from 'selectors'
import { SelectHost } from 'select-objects'
import {
differenceBy,
forEach
} from 'lodash'
createCollectionWrapper,
createGetObjectsOfType,
createSelector,
createGetObject,
} from 'selectors'
import { SelectHost } from 'select-objects'
import { differenceBy, forEach } from 'lodash'
@connectStore(() => ({
singleHosts: createSelector(
(_, { pool }) => pool && pool.id,
createGetObjectsOfType('host'),
createCollectionWrapper((poolId, hosts) => {
const visitedPools = {}
const singleHosts = {}
forEach(hosts, host => {
const { $pool } = host
if ($pool !== poolId) {
const previousHost = visitedPools[$pool]
if (previousHost) {
delete singleHosts[previousHost]
} else {
const { id } = host
singleHosts[id] = true
visitedPools[$pool] = id
@connectStore(
() => ({
singleHosts: createSelector(
(_, { pool }) => pool && pool.id,
createGetObjectsOfType('host'),
createCollectionWrapper((poolId, hosts) => {
const visitedPools = {}
const singleHosts = {}
forEach(hosts, host => {
const { $pool } = host
if ($pool !== poolId) {
const previousHost = visitedPools[$pool]
if (previousHost) {
delete singleHosts[previousHost]
} else {
const { id } = host
singleHosts[id] = true
visitedPools[$pool] = id
}
}
}
})
return singleHosts
})
return singleHosts
})
),
poolMasterPatches: createSelector(
createGetObject(
(_, props) => props.pool.master
),
({ patches }) => patches
)
}), { withRef: true })
poolMasterPatches: createSelector(
createGetObject((_, props) => props.pool.master),
({ patches }) => patches
),
}),
{ withRef: true }
)
export default class AddHostModal extends BaseComponent {
get value () {
if (process.env.XOA_PLAN < 2 && this.state.nMissingPatches) {
@ -60,36 +63,41 @@ export default class AddHostModal extends BaseComponent {
this.setState({
host,
nMissingPatches: host
? differenceBy(this.props.poolMasterPatches, host.patches, 'name').length
: undefined
? differenceBy(this.props.poolMasterPatches, host.patches, 'name')
.length
: undefined,
})
}
render () {
const { nMissingPatches } = this.state
return <div>
<SingleLineRow>
<Col size={6}>{_('addHostSelectHost')}</Col>
<Col size={6}>
<SelectHost
onChange={this._onChangeHost}
predicate={this._getHostPredicate()}
value={this.state.host}
/>
</Col>
</SingleLineRow>
<br />
{nMissingPatches > 0 && <SingleLineRow>
<Col>
<span className='text-danger'>
<Icon icon='error' /> {process.env.XOA_PLAN > 1
? _('hostNeedsPatchUpdate', { patches: nMissingPatches })
: _('hostNeedsPatchUpdateNoInstall')
}
</span>
</Col>
</SingleLineRow>}
</div>
return (
<div>
<SingleLineRow>
<Col size={6}>{_('addHostSelectHost')}</Col>
<Col size={6}>
<SelectHost
onChange={this._onChangeHost}
predicate={this._getHostPredicate()}
value={this.state.host}
/>
</Col>
</SingleLineRow>
<br />
{nMissingPatches > 0 && (
<SingleLineRow>
<Col>
<span className='text-danger'>
<Icon icon='error' />{' '}
{process.env.XOA_PLAN > 1
? _('hostNeedsPatchUpdate', { patches: nMissingPatches })
: _('hostNeedsPatchUpdateNoInstall')}
</span>
</Col>
</SingleLineRow>
)}
</div>
)
}
}

View File

@ -11,7 +11,7 @@ import { createSelector } from '../../selectors'
@propTypes({
type: propTypes.string.isRequired,
user: propTypes.object.isRequired,
value: propTypes.string.isRequired
value: propTypes.string.isRequired,
})
export default class SaveNewUserFilterModalBody extends Component {
get value () {
@ -19,12 +19,11 @@ export default class SaveNewUserFilterModalBody extends Component {
}
_getFilterOptions = createSelector(
tmp => (
tmp =>
(tmp = this.props.user) &&
(tmp = tmp.preferences) &&
(tmp = tmp.filters) &&
tmp[this.props.type]
),
tmp[this.props.type],
keys
)

View File

@ -9,35 +9,35 @@ import SingleLineRow from '../../single-line-row'
import { createSelector } from '../../selectors'
import { SelectSr } from '../../select-objects'
import { isSrWritable } from 'xo'
import {
Container,
Col
} from 'grid'
import { Container, Col } from 'grid'
// Can 2 SRs on the same pool have 2 VDIs used by the same VM
const areSrsCompatible = (sr1, sr2) =>
sr1.shared || sr2.shared || sr1.$container === sr2.$container
const Collapsible = ({collapsible, children, ...props}) => collapsible
? <Collapse {...props}>{children}</Collapse>
: <div>
<span>{props.buttonText}</span>
<br />
{children}
</div>
const Collapsible = ({ collapsible, children, ...props }) =>
collapsible ? (
<Collapse {...props}>{children}</Collapse>
) : (
<div>
<span>{props.buttonText}</span>
<br />
{children}
</div>
)
Collapsible.propTypes = {
collapsible: propTypes.bool.isRequired,
children: propTypes.node.isRequired
children: propTypes.node.isRequired,
}
@propTypes({
vdis: propTypes.array.isRequired,
predicate: propTypes.func
predicate: propTypes.func,
})
export default class ChooseSrForEachVdisModal extends Component {
state = {
mapVdisSrs: {}
mapVdisSrs: {},
}
componentWillReceiveProps (newProps) {
@ -47,7 +47,7 @@ export default class ChooseSrForEachVdisModal extends Component {
) {
this.state = {
mainSr: undefined,
mapVdisSrs: {}
mapVdisSrs: {},
}
}
}
@ -62,20 +62,25 @@ export default class ChooseSrForEachVdisModal extends Component {
if (oldSr == null || newSr == null || oldSr.$pool !== newSr.$pool) {
this.setState({
mapVdisSrs: {}
mapVdisSrs: {},
})
} else if (!newSr.shared) {
const mapVdisSrs = {...this.state.mapVdisSrs}
const mapVdisSrs = { ...this.state.mapVdisSrs }
forEach(mapVdisSrs, (sr, vdi) => {
if (sr != null && newSr !== sr && sr.$container !== newSr.$container && !sr.shared) {
if (
sr != null &&
newSr !== sr &&
sr.$container !== newSr.$container &&
!sr.shared
) {
delete mapVdisSrs[vdi]
}
})
this._onChange({mapVdisSrs})
this._onChange({ mapVdisSrs })
}
this._onChange({
mainSr: newSr
mainSr: newSr,
})
}
@ -86,54 +91,69 @@ export default class ChooseSrForEachVdisModal extends Component {
isSrWritable(sr) &&
mainSr.$pool === sr.$pool &&
areSrsCompatible(mainSr, sr) &&
every(mapVdisSrs, selectedSr => selectedSr == null || areSrsCompatible(selectedSr, sr))
every(
mapVdisSrs,
selectedSr => selectedSr == null || areSrsCompatible(selectedSr, sr)
)
)
render () {
const { props, state } = this
const { vdis } = props
const {
mainSr,
mapVdisSrs
} = state
const { mainSr, mapVdisSrs } = state
const srPredicate = props.predicate || this._getSrPredicate()
return <div>
<SelectSr
onChange={mainSr => props.predicate !== undefined
? this._onChange({mainSr})
: this._onChangeMainSr(mainSr)
}
predicate={props.predicate || isSrWritable}
placeholder={_('chooseSrForEachVdisModalMainSr')}
value={mainSr}
/>
<br />
{vdis != null && mainSr != null &&
<Collapsible collapsible={vdis.length >= 3} buttonText={_('chooseSrForEachVdisModalSelectSr')}>
<br />
<Container>
<SingleLineRow>
<Col size={6}><strong>{_('chooseSrForEachVdisModalVdiLabel')}</strong></Col>
<Col size={6}><strong>{_('chooseSrForEachVdisModalSrLabel')}</strong></Col>
</SingleLineRow>
{map(vdis, vdi =>
<SingleLineRow key={vdi.uuid}>
<Col size={6}>{ vdi.name_label || vdi.name }</Col>
<Col size={6}>
<SelectSr
onChange={sr => this._onChange({ mapVdisSrs: { ...mapVdisSrs, [vdi.uuid]: sr } })}
value={mapVdisSrs[vdi.uuid]}
predicate={srPredicate}
/>
</Col>
</SingleLineRow>
)}
<i>{_('chooseSrForEachVdisModalOptionalEntry')}</i>
</Container>
</Collapsible>
}
</div>
return (
<div>
<SelectSr
onChange={mainSr =>
props.predicate !== undefined
? this._onChange({ mainSr })
: this._onChangeMainSr(mainSr)
}
predicate={props.predicate || isSrWritable}
placeholder={_('chooseSrForEachVdisModalMainSr')}
value={mainSr}
/>
<br />
{vdis != null &&
mainSr != null && (
<Collapsible
collapsible={vdis.length >= 3}
buttonText={_('chooseSrForEachVdisModalSelectSr')}
>
<br />
<Container>
<SingleLineRow>
<Col size={6}>
<strong>{_('chooseSrForEachVdisModalVdiLabel')}</strong>
</Col>
<Col size={6}>
<strong>{_('chooseSrForEachVdisModalSrLabel')}</strong>
</Col>
</SingleLineRow>
{map(vdis, vdi => (
<SingleLineRow key={vdi.uuid}>
<Col size={6}>{vdi.name_label || vdi.name}</Col>
<Col size={6}>
<SelectSr
onChange={sr =>
this._onChange({
mapVdisSrs: { ...mapVdisSrs, [vdi.uuid]: sr },
})
}
value={mapVdisSrs[vdi.uuid]}
predicate={srPredicate}
/>
</Col>
</SingleLineRow>
))}
<i>{_('chooseSrForEachVdisModalOptionalEntry')}</i>
</Container>
</Collapsible>
)}
</div>
)
}
}

View File

@ -16,27 +16,22 @@ class CopyVmModalBody extends Component {
return {
compress: state.compress,
name: this.state.name || this.props.vm.name_label,
sr: state.sr.id
sr: state.sr.id,
}
}
_onChangeSr = sr =>
this.setState({ sr })
_onChangeName = event =>
this.setState({ name: event.target.value })
_onChangeCompress = compress =>
this.setState({ compress })
_onChangeSr = sr => this.setState({ sr })
_onChangeName = event => this.setState({ name: event.target.value })
_onChangeCompress = compress => this.setState({ compress })
render () {
const { formatMessage } = this.props.intl
return process.env.XOA_PLAN > 2
? <div>
return process.env.XOA_PLAN > 2 ? (
<div>
<SingleLineRow>
<Col size={6}>{_('copyVmSelectSr')}</Col>
<Col size={6}>
<SelectSr
onChange={this._onChangeSr}
/>
<SelectSr onChange={this._onChangeSr} />
</Col>
</SingleLineRow>
&nbsp;
@ -55,13 +50,15 @@ class CopyVmModalBody extends Component {
<SingleLineRow>
<Col size={6}>{_('copyVmCompress')}</Col>
<Col size={6}>
<Toggle
onChange={this._onChangeCompress}
/>
<Toggle onChange={this._onChangeCompress} />
</Col>
</SingleLineRow>
</div>
: <div><Upgrade place='vmCopy' available={3} /></div>
) : (
<div>
<Upgrade place='vmCopy' available={3} />
</div>
)
}
}
export default injectIntl(CopyVmModalBody, { withRef: true })

View File

@ -10,19 +10,17 @@ import { Col } from 'grid'
import { createGetObjectsOfType } from 'selectors'
import { SelectSr } from 'select-objects'
import { Toggle } from 'form'
import {
buildTemplate,
connectStore
} from 'utils'
import { buildTemplate, connectStore } from 'utils'
@connectStore(() => {
const getVms = createGetObjectsOfType('VM').pick(
(_, props) => props.vms
)
return {
vms: getVms
}
}, { withRef: true })
@connectStore(
() => {
const getVms = createGetObjectsOfType('VM').pick((_, props) => props.vms)
return {
vms: getVms,
}
},
{ withRef: true }
)
class CopyVmsModalBody extends BaseComponent {
get value () {
const { state } = this
@ -33,44 +31,42 @@ class CopyVmsModalBody extends BaseComponent {
const { namePattern } = state
const names = namePattern
? map(vms, buildTemplate(namePattern, {
'{name}': vm => vm.name_label,
'{id}': vm => vm.id
}))
? map(
vms,
buildTemplate(namePattern, {
'{name}': vm => vm.name_label,
'{id}': vm => vm.id,
})
)
: map(vms, vm => vm.name_label)
return {
compress: state.compress,
names,
sr: state.sr.id
sr: state.sr.id,
}
}
componentWillMount () {
this.setState({
compress: false,
namePattern: '{name}_COPY'
namePattern: '{name}_COPY',
})
}
_onChangeSr = sr =>
this.setState({ sr })
_onChangeSr = sr => this.setState({ sr })
_onChangeNamePattern = event =>
this.setState({ namePattern: event.target.value })
_onChangeCompress = compress =>
this.setState({ compress })
_onChangeCompress = compress => this.setState({ compress })
render () {
const { formatMessage } = this.props.intl
const { compress, namePattern, sr } = this.state
return process.env.XOA_PLAN > 2
? <div>
return process.env.XOA_PLAN > 2 ? (
<div>
<SingleLineRow>
<Col size={6}>{_('copyVmSelectSr')}</Col>
<Col size={6}>
<SelectSr
onChange={this.linkState('sr')}
value={sr}
/>
<SelectSr onChange={this.linkState('sr')} value={sr} />
</Col>
</SingleLineRow>
&nbsp;
@ -90,14 +86,15 @@ class CopyVmsModalBody extends BaseComponent {
<SingleLineRow>
<Col size={6}>{_('copyVmCompress')}</Col>
<Col size={6}>
<Toggle
onChange={this.linkState('compress')}
value={compress}
/>
<Toggle onChange={this.linkState('compress')} value={compress} />
</Col>
</SingleLineRow>
</div>
: <div><Upgrade place='vmCopy' available={3} /></div>
) : (
<div>
<Upgrade place='vmCopy' available={3} />
</div>
)
}
}
export default injectIntl(CopyVmsModalBody, { withRef: true })

Some files were not shown because too many files have changed in this diff Show More