feat: prettier integration (#2490)
This commit is contained in:
15
.eslintrc.js
Normal file
15
.eslintrc.js
Normal 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
4
.prettierrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
semi: false,
|
||||
singleQuote: true
|
||||
}
|
||||
34
package.json
34
package.json
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const sendNotification = (title, body) => {
|
||||
new Notify(title, {
|
||||
body,
|
||||
timeout: 5,
|
||||
icon: 'assets/logo.png'
|
||||
icon: 'assets/logo.png',
|
||||
}).show()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
))
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
])
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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])
|
||||
})
|
||||
|
||||
@@ -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>)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
))
|
||||
|
||||
@@ -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:',
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 :',
|
||||
}
|
||||
|
||||
@@ -3132,5 +3132,5 @@ export default {
|
||||
settingsAclsButtonTooltipSR: undefined,
|
||||
|
||||
// Original text: 'Network'
|
||||
settingsAclsButtonTooltipnetwork: undefined
|
||||
settingsAclsButtonTooltipnetwork: undefined,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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ć',
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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: '其他',
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PrimitiveInputWrapper } from './helpers'
|
||||
// ===================================================================
|
||||
|
||||
@propTypes({
|
||||
password: propTypes.bool
|
||||
password: propTypes.bool,
|
||||
})
|
||||
@uncontrollableInput()
|
||||
export default class StringInput extends Component {
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
52
src/common/react-novnc.js
vendored
52
src/common/react-novnc.js
vendored
@@ -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'
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
|
||||
24
src/common/sorted-table/index2.js
Normal file
24
src/common/sorted-table/index2.js
Normal 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>
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ export default {
|
||||
disabledStateBg: '#fff',
|
||||
disabledStateColor: '#c0392b',
|
||||
enabledStateBg: '#fff',
|
||||
enabledStateColor: '#27ae60'
|
||||
enabledStateColor: '#27ae60',
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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)))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user