Compare commits
134 Commits
v5.12.0
...
cbs-innova
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f8e6ebbef | ||
|
|
1a2f553094 | ||
|
|
4d69866532 | ||
|
|
495c97b44b | ||
|
|
e817b3254e | ||
|
|
dd6987efe9 | ||
|
|
d7f8d12d88 | ||
|
|
504895a730 | ||
|
|
cde92836f3 | ||
|
|
c787988b06 | ||
|
|
898434b267 | ||
|
|
6e44c65a07 | ||
|
|
03028bca50 | ||
|
|
c8669dc88f | ||
|
|
82240979c2 | ||
|
|
db5d495105 | ||
|
|
6e8dfe8833 | ||
|
|
242d9e20c4 | ||
|
|
e446eb0cd0 | ||
|
|
b63efe579a | ||
|
|
f3410f1491 | ||
|
|
b27ac11d56 | ||
|
|
a55d73614e | ||
|
|
25cd1957c7 | ||
|
|
abd97abc24 | ||
|
|
6ddfd909f0 | ||
|
|
e054eec555 | ||
|
|
e253657770 | ||
|
|
102e629e16 | ||
|
|
242a02836c | ||
|
|
6936f223f3 | ||
|
|
eb8dfc86ca | ||
|
|
02c715e1cc | ||
|
|
8cb53b0c4e | ||
|
|
629f68ffd7 | ||
|
|
d3691313e6 | ||
|
|
9aed4f6fba | ||
|
|
ef17cb1c6c | ||
|
|
ecc086f15d | ||
|
|
be9eb8ce91 | ||
|
|
18ca6b935c | ||
|
|
b3769019e5 | ||
|
|
506a6b0cf4 | ||
|
|
a18df93c4f | ||
|
|
684269321b | ||
|
|
d7eeeca268 | ||
|
|
4c21175ca7 | ||
|
|
377efcd054 | ||
|
|
072401f600 | ||
|
|
1ce7d94261 | ||
|
|
8178de8a6b | ||
|
|
eb37c7d7d8 | ||
|
|
9465459ef9 | ||
|
|
52a71cec91 | ||
|
|
2e1b32fadc | ||
|
|
0c8f3ea824 | ||
|
|
bf45cdd2b8 | ||
|
|
fd47403ec9 | ||
|
|
137b8e7f7f | ||
|
|
4a8c5a980a | ||
|
|
233aca7911 | ||
|
|
0178ce0a91 | ||
|
|
142233453a | ||
|
|
b12a804fb3 | ||
|
|
3fe5efbfab | ||
|
|
d71323a67d | ||
|
|
eb46711e34 | ||
|
|
748b09d8fa | ||
|
|
e6a32b53fc | ||
|
|
7ce9e8a959 | ||
|
|
b817cb86d0 | ||
|
|
3e1b2119c4 | ||
|
|
bfa31be3b7 | ||
|
|
584da2f56a | ||
|
|
6a071942a5 | ||
|
|
ac8787e930 | ||
|
|
ab60bc46cf | ||
|
|
b67310ae75 | ||
|
|
020618554a | ||
|
|
38ec7ac34f | ||
|
|
a78151c93e | ||
|
|
285b1fb36e | ||
|
|
16e8c87cc6 | ||
|
|
93ffb77e81 | ||
|
|
88f6d77047 | ||
|
|
3ff63927f3 | ||
|
|
d396593d99 | ||
|
|
62b64ad0b6 | ||
|
|
6f33a79644 | ||
|
|
67f31407d7 | ||
|
|
f05d2d0063 | ||
|
|
682deb4b56 | ||
|
|
64fac454b5 | ||
|
|
e9c60bc958 | ||
|
|
7a8c0831bd | ||
|
|
0ca1af8606 | ||
|
|
e81f88e676 | ||
|
|
e96a8af9ef | ||
|
|
d8393d8500 | ||
|
|
44b74e6135 | ||
|
|
f31417a85b | ||
|
|
1c6967594c | ||
|
|
59f8a58b21 | ||
|
|
4d1f647a89 | ||
|
|
86e5206b4d | ||
|
|
105ede5b1d | ||
|
|
bb8a25cc9d | ||
|
|
54c3d843be | ||
|
|
4a1407786c | ||
|
|
f5e3aef86c | ||
|
|
37c8a7c2b2 | ||
|
|
1a788fae7e | ||
|
|
8efc083a70 | ||
|
|
f196a9ebc4 | ||
|
|
06704ce467 | ||
|
|
8524db2903 | ||
|
|
60df3bc633 | ||
|
|
5014b95206 | ||
|
|
a2464fa968 | ||
|
|
033153c8b9 | ||
|
|
a74a857ffe | ||
|
|
f0fe369cfd | ||
|
|
457ba5f24c | ||
|
|
d41b04313a | ||
|
|
34be34e7b3 | ||
|
|
dbc9fdcfa6 | ||
|
|
76b20f0fb6 | ||
|
|
80ca2052c2 | ||
|
|
3e5d8be507 | ||
|
|
114e5e1fa0 | ||
|
|
c38d4e275b | ||
|
|
8cc9dea9aa | ||
|
|
d3dcf6d305 | ||
|
|
02439bd23d |
12
.eslintrc.js
Normal file
12
.eslintrc.js
Normal file
@@ -0,0 +1,12 @@
|
||||
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,
|
||||
}
|
||||
90
CHANGELOG.md
90
CHANGELOG.md
@@ -1,5 +1,95 @@
|
||||
# ChangeLog
|
||||
|
||||
## **5.15.0** (2017-12-29)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* VDI resize online method removed in 7.3 [#2542](https://github.com/vatesfr/xo-web/issues/2542)
|
||||
* Smart replace VDI.pool_migrate removed from XenServer 7.3 Free [#2541](https://github.com/vatesfr/xo-web/issues/2541)
|
||||
* New memory constraints in XenServer 7.3 [#2540](https://github.com/vatesfr/xo-web/issues/2540)
|
||||
* Link to Settings/Logs for admins in error notifications [#2516](https://github.com/vatesfr/xo-web/issues/2516)
|
||||
* [Self Service] Do not use placehodlers to describe inputs [#2509](https://github.com/vatesfr/xo-web/issues/2509)
|
||||
* Obfuscate password in log in LDAP plugin test [#2506](https://github.com/vatesfr/xo-web/issues/2506)
|
||||
* Log rotation [#2492](https://github.com/vatesfr/xo-web/issues/2492)
|
||||
* Continuous Replication TAG [#2473](https://github.com/vatesfr/xo-web/issues/2473)
|
||||
* Graphs in VM list view [#2469](https://github.com/vatesfr/xo-web/issues/2469)
|
||||
* [Delta Backups] Do not include merge duration in transfer speed stat [#2426](https://github.com/vatesfr/xo-web/issues/2426)
|
||||
* Warning for disperse mode [#2537](https://github.com/vatesfr/xo-web/issues/2537)
|
||||
|
||||
### Bugs
|
||||
|
||||
* VM console doesn't work when using IPv6 in URL [#2530](https://github.com/vatesfr/xo-web/issues/2530)
|
||||
* Retention issue with failed basic backup [#2524](https://github.com/vatesfr/xo-web/issues/2524)
|
||||
* [VM/Advanced] Check that the autopower on setting is working [#2489](https://github.com/vatesfr/xo-web/issues/2489)
|
||||
* Cloud config drive create fail on XenServer < 7 [#2478](https://github.com/vatesfr/xo-web/issues/2478)
|
||||
* VM create fails due to missing vGPU id [#2466](https://github.com/vatesfr/xo-web/issues/2466)
|
||||
|
||||
|
||||
## **5.14.0** (2017-10-31)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* VM snapshot description display [#2458](https://github.com/vatesfr/xo-web/issues/2458)
|
||||
* [Home] Ability to sort VM by number of snapshots [#2450](https://github.com/vatesfr/xo-web/issues/2450)
|
||||
* Display XS version in host view [#2439](https://github.com/vatesfr/xo-web/issues/2439)
|
||||
* [File restore]: Clarify the possibility to select multiple files [#2438](https://github.com/vatesfr/xo-web/issues/2438)
|
||||
* [Continuous Replication] Time in replicated VMs [#2431](https://github.com/vatesfr/xo-web/issues/2431)
|
||||
* [SortedTable] Active page in URL param [#2405](https://github.com/vatesfr/xo-web/issues/2405)
|
||||
* replace all '...' with the UTF-8 equivalent [#2391](https://github.com/vatesfr/xo-web/issues/2391)
|
||||
* [SortedTable] Explicit when no items [#2388](https://github.com/vatesfr/xo-web/issues/2388)
|
||||
* Handle patching licenses [#2382](https://github.com/vatesfr/xo-web/issues/2382)
|
||||
* Credential leaking in logs for messages regarding invalid credentials and "too fast authentication" [#2363](https://github.com/vatesfr/xo-web/issues/2363)
|
||||
* [SortedTable] Keyboard support [#2330](https://github.com/vatesfr/xo-web/issues/2330)
|
||||
* token.create should accept an expiration [#1769](https://github.com/vatesfr/xo-web/issues/1769)
|
||||
* On updater error, display link to documentation [#1610](https://github.com/vatesfr/xo-web/issues/1610)
|
||||
* Add basic vGPU support [#2413](https://github.com/vatesfr/xo-web/issues/2413)
|
||||
* Storage View - Disk Tab - real disk usage [#2475](https://github.com/vatesfr/xo-web/issues/2475)
|
||||
|
||||
### Bugs
|
||||
|
||||
* Config drive - Custom config not working properly [#2449](https://github.com/vatesfr/xo-web/issues/2449)
|
||||
* Snapshot sorted table breaks copyVm [#2446](https://github.com/vatesfr/xo-web/issues/2446)
|
||||
* [vm/snapshots] Incorrect default sort order [#2442](https://github.com/vatesfr/xo-web/issues/2442)
|
||||
* [Backups/Jobs] Incorrect months mapping [#2427](https://github.com/vatesfr/xo-web/issues/2427)
|
||||
* [Xapi#barrier()] Not compatible with XenServer < 6.1 [#2418](https://github.com/vatesfr/xo-web/issues/2418)
|
||||
* [SortedTable] Change page when no more items on the page [#2401](https://github.com/vatesfr/xo-web/issues/2401)
|
||||
* Review and fix creating a VM from a snapshot [#2343](https://github.com/vatesfr/xo-web/issues/2343)
|
||||
* Unable to edit / save restored backup job [#1922](https://github.com/vatesfr/xo-web/issues/1922)
|
||||
|
||||
|
||||
## **5.13.0** (2017-09-29)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* replace all '...' with the UTF-8 equivalent [#2391](https://github.com/vatesfr/xo-web/issues/2391)
|
||||
* [SortedTable] Explicit when no items [#2388](https://github.com/vatesfr/xo-web/issues/2388)
|
||||
* Auto select iqn or lun if there is only one [#2379](https://github.com/vatesfr/xo-web/issues/2379)
|
||||
* [Sparklines] Hide points [#2370](https://github.com/vatesfr/xo-web/issues/2370)
|
||||
* Allow xo-server-recover-account to generate a random password [#2360](https://github.com/vatesfr/xo-web/issues/2360)
|
||||
* Add disk in existing VM as self user [#2348](https://github.com/vatesfr/xo-web/issues/2348)
|
||||
* Sorted table for Settings/server [#2340](https://github.com/vatesfr/xo-web/issues/2340)
|
||||
* Sign in should be case insensitive [#2337](https://github.com/vatesfr/xo-web/issues/2337)
|
||||
* [SortedTable] Extend checkbox click to whole column [#2329](https://github.com/vatesfr/xo-web/issues/2329)
|
||||
* [SortedTable] Ability to select all items (across pages) [#2324](https://github.com/vatesfr/xo-web/issues/2324)
|
||||
* [SortedTable] Range selection [#2323](https://github.com/vatesfr/xo-web/issues/2323)
|
||||
* Warning on SMB remote creation [#2316](https://github.com/vatesfr/xo-web/issues/2316)
|
||||
* [Home | SortedTable] Add link to syntax doc in the filter input [#2305](https://github.com/vatesfr/xo-web/issues/2305)
|
||||
* [SortedTable] Add optional binding of filter to an URL query [#2301](https://github.com/vatesfr/xo-web/issues/2301)
|
||||
* [Home][Keyboard navigation] Allow selecting the objects [#2214](https://github.com/vatesfr/xo-web/issues/2214)
|
||||
* SR view / Disks: option to display non managed VDIs [#1724](https://github.com/vatesfr/xo-web/issues/1724)
|
||||
* Continuous Replication Retention [#1692](https://github.com/vatesfr/xo-web/issues/1692)
|
||||
|
||||
### Bugs
|
||||
|
||||
* iSCSI issue on LUN selector [#2374](https://github.com/vatesfr/xo-web/issues/2374)
|
||||
* Errors in VM copy are not properly reported [#2347](https://github.com/vatesfr/xo-web/issues/2347)
|
||||
* Removing a PIF IP fails [#2346](https://github.com/vatesfr/xo-web/issues/2346)
|
||||
* Review and fix creating a VM from a snapshot [#2343](https://github.com/vatesfr/xo-web/issues/2343)
|
||||
* iSCSI LUN Detection fails with authentification [#2339](https://github.com/vatesfr/xo-web/issues/2339)
|
||||
* Fix PoolActionBar to add a new SR [#2307](https://github.com/vatesfr/xo-web/issues/2307)
|
||||
* [VM migration] Error if default SR not accessible to target host [#2180](https://github.com/vatesfr/xo-web/issues/2180)
|
||||
* A job shouldn't executable more than once at the same time [#2053](https://github.com/vatesfr/xo-web/issues/2053)
|
||||
|
||||
## **5.12.0** (2017-08-31)
|
||||
|
||||
### Enhancements
|
||||
|
||||
138
gulpfile.js
138
gulpfile.js
@@ -2,17 +2,17 @@
|
||||
|
||||
// ===================================================================
|
||||
|
||||
var SRC_DIR = __dirname + '/src' // eslint-disable-line no-path-concat
|
||||
var DIST_DIR = __dirname + '/dist' // eslint-disable-line no-path-concat
|
||||
const SRC_DIR = __dirname + '/src' // eslint-disable-line no-path-concat
|
||||
const DIST_DIR = __dirname + '/dist' // eslint-disable-line no-path-concat
|
||||
|
||||
// Port to use for the livereload server.
|
||||
//
|
||||
// It must be available and if possible unique to not conflict with other projects.
|
||||
// http://www.random.org/integers/?num=1&min=1024&max=65535&col=1&base=10&format=plain&rnd=new
|
||||
var LIVERELOAD_PORT = 26242
|
||||
const LIVERELOAD_PORT = 26242
|
||||
|
||||
var PRODUCTION = process.env.NODE_ENV === 'production'
|
||||
var DEVELOPMENT = !PRODUCTION
|
||||
const PRODUCTION = process.env.NODE_ENV === 'production'
|
||||
const DEVELOPMENT = !PRODUCTION
|
||||
|
||||
if (!process.env.XOA_PLAN) {
|
||||
process.env.XOA_PLAN = '5' // Open Source
|
||||
@@ -20,12 +20,12 @@ if (!process.env.XOA_PLAN) {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
var gulp = require('gulp')
|
||||
const gulp = require('gulp')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
function lazyFn (factory) {
|
||||
var fn = function () {
|
||||
let fn = function () {
|
||||
fn = factory()
|
||||
return fn.apply(this, arguments)
|
||||
}
|
||||
@@ -37,19 +37,19 @@ function lazyFn (factory) {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
var livereload = lazyFn(function () {
|
||||
var livereload = require('gulp-refresh')
|
||||
const livereload = lazyFn(function () {
|
||||
const livereload = require('gulp-refresh')
|
||||
livereload.listen({
|
||||
port: LIVERELOAD_PORT
|
||||
port: LIVERELOAD_PORT,
|
||||
})
|
||||
|
||||
return livereload
|
||||
})
|
||||
|
||||
var pipe = lazyFn(function () {
|
||||
var current
|
||||
const pipe = lazyFn(function () {
|
||||
let current
|
||||
function pipeCore (streams) {
|
||||
var i, n, stream
|
||||
let i, n, stream
|
||||
for (i = 0, n = streams.length; i < n; ++i) {
|
||||
stream = streams[i]
|
||||
if (!stream) {
|
||||
@@ -57,14 +57,12 @@ var pipe = lazyFn(function () {
|
||||
} else if (stream instanceof Array) {
|
||||
pipeCore(stream)
|
||||
} else {
|
||||
current = current
|
||||
? current.pipe(stream)
|
||||
: stream
|
||||
current = current ? current.pipe(stream) : stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var push = Array.prototype.push
|
||||
const push = Array.prototype.push
|
||||
return function (streams) {
|
||||
try {
|
||||
if (!(streams instanceof Array)) {
|
||||
@@ -81,7 +79,7 @@ var pipe = lazyFn(function () {
|
||||
}
|
||||
})
|
||||
|
||||
var resolvePath = lazyFn(function () {
|
||||
const resolvePath = lazyFn(function () {
|
||||
return require('path').resolve
|
||||
})
|
||||
|
||||
@@ -89,37 +87,35 @@ var resolvePath = lazyFn(function () {
|
||||
|
||||
// Similar to `gulp.src()` but the pattern is relative to `SRC_DIR`
|
||||
// and files are automatically watched when not in production mode.
|
||||
var src = lazyFn(function () {
|
||||
const src = lazyFn(function () {
|
||||
function resolve (path) {
|
||||
return path
|
||||
? resolvePath(SRC_DIR, path)
|
||||
: SRC_DIR
|
||||
return path ? resolvePath(SRC_DIR, path) : SRC_DIR
|
||||
}
|
||||
|
||||
return PRODUCTION
|
||||
? function src (pattern, opts) {
|
||||
var base = resolve(opts && opts.base)
|
||||
const base = resolve(opts && opts.base)
|
||||
|
||||
return gulp.src(pattern, {
|
||||
base: base,
|
||||
cwd: base,
|
||||
passthrough: opts && opts.passthrough,
|
||||
sourcemaps: opts && opts.sourcemaps
|
||||
sourcemaps: opts && opts.sourcemaps,
|
||||
})
|
||||
}
|
||||
: function src (pattern, opts) {
|
||||
var base = resolve(opts && opts.base)
|
||||
const base = resolve(opts && opts.base)
|
||||
|
||||
return pipe(
|
||||
gulp.src(pattern, {
|
||||
base: base,
|
||||
cwd: base,
|
||||
passthrough: opts && opts.passthrough,
|
||||
sourcemaps: opts && opts.sourcemaps
|
||||
sourcemaps: opts && opts.sourcemaps,
|
||||
}),
|
||||
require('gulp-watch')(pattern, {
|
||||
base: base,
|
||||
cwd: base
|
||||
cwd: base,
|
||||
}),
|
||||
require('gulp-plumber')()
|
||||
)
|
||||
@@ -129,17 +125,13 @@ var src = lazyFn(function () {
|
||||
// Similar to `gulp.dest()` but the output directory is relative to
|
||||
// `DIST_DIR` and default to `./`, and files are automatically live-
|
||||
// reloaded when not in production mode.
|
||||
var dest = lazyFn(function () {
|
||||
const dest = lazyFn(function () {
|
||||
function resolve (path) {
|
||||
return path
|
||||
? resolvePath(DIST_DIR, path)
|
||||
: DIST_DIR
|
||||
return path ? resolvePath(DIST_DIR, path) : DIST_DIR
|
||||
}
|
||||
|
||||
var opts = {
|
||||
sourcemaps: {
|
||||
path: '.'
|
||||
}
|
||||
const opts = {
|
||||
sourcemaps: '.',
|
||||
}
|
||||
|
||||
return PRODUCTION
|
||||
@@ -147,7 +139,7 @@ var dest = lazyFn(function () {
|
||||
return gulp.dest(resolve(path), opts)
|
||||
}
|
||||
: function dest (path) {
|
||||
var stream = gulp.dest(resolve(path), opts)
|
||||
const stream = gulp.dest(resolve(path), opts)
|
||||
stream.pipe(livereload())
|
||||
return stream
|
||||
}
|
||||
@@ -160,7 +152,7 @@ function browserify (path, opts) {
|
||||
opts = {}
|
||||
}
|
||||
|
||||
var bundler = require('browserify')(path, {
|
||||
let bundler = require('browserify')(path, {
|
||||
basedir: SRC_DIR,
|
||||
debug: true,
|
||||
extensions: opts.extensions,
|
||||
@@ -170,12 +162,12 @@ function browserify (path, opts) {
|
||||
|
||||
// Required by Watchify.
|
||||
cache: {},
|
||||
packageCache: {}
|
||||
packageCache: {},
|
||||
})
|
||||
|
||||
var plugins = opts.plugins
|
||||
for (var i = 0, n = plugins && plugins.length; i < n; ++i) {
|
||||
var plugin = plugins[i]
|
||||
const plugins = opts.plugins
|
||||
for (let i = 0, n = plugins && plugins.length; i < n; ++i) {
|
||||
const plugin = plugins[i]
|
||||
bundler.plugin(require(plugin[0]), plugin[1])
|
||||
}
|
||||
|
||||
@@ -183,7 +175,11 @@ function browserify (path, opts) {
|
||||
// FIXME: does not work with react-intl (?!)
|
||||
// bundler.plugin('bundle-collapser/plugin')
|
||||
} else {
|
||||
bundler = require('watchify')(bundler)
|
||||
bundler = require('watchify')(bundler, {
|
||||
// do not watch in `node_modules`
|
||||
// https://github.com/browserify/watchify#options
|
||||
ignoreWatch: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Append the extension if necessary.
|
||||
@@ -192,11 +188,11 @@ function browserify (path, opts) {
|
||||
}
|
||||
path = resolvePath(SRC_DIR, path)
|
||||
|
||||
var stream = new (require('readable-stream'))({
|
||||
objectMode: true
|
||||
let stream = new (require('readable-stream'))({
|
||||
objectMode: true,
|
||||
})
|
||||
|
||||
var write
|
||||
let write
|
||||
function bundle () {
|
||||
bundler.bundle(function onBundle (error, buffer) {
|
||||
if (error) {
|
||||
@@ -204,11 +200,13 @@ function browserify (path, opts) {
|
||||
return
|
||||
}
|
||||
|
||||
write(new (require('vinyl'))({
|
||||
base: SRC_DIR,
|
||||
contents: buffer,
|
||||
path: path
|
||||
}))
|
||||
write(
|
||||
new (require('vinyl'))({
|
||||
base: SRC_DIR,
|
||||
contents: buffer,
|
||||
path: path,
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -238,11 +236,12 @@ function browserify (path, opts) {
|
||||
|
||||
gulp.task(function buildPages () {
|
||||
return pipe(
|
||||
src('index.pug', { sourcemaps: true }),
|
||||
src('index.pug'),
|
||||
require('gulp-pug')(),
|
||||
DEVELOPMENT && require('gulp-embedlr')({
|
||||
port: LIVERELOAD_PORT
|
||||
}),
|
||||
DEVELOPMENT &&
|
||||
require('gulp-embedlr')({
|
||||
port: LIVERELOAD_PORT,
|
||||
}),
|
||||
dest()
|
||||
)
|
||||
})
|
||||
@@ -252,10 +251,14 @@ gulp.task(function buildScripts () {
|
||||
browserify('./index.js', {
|
||||
plugins: [
|
||||
// ['css-modulesify', {
|
||||
['modular-css/browserify', {
|
||||
css: DIST_DIR + '/modules.css'
|
||||
}]
|
||||
]
|
||||
[
|
||||
'modular-cssify',
|
||||
{
|
||||
css: DIST_DIR + '/modules.css',
|
||||
from: undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
require('gulp-sourcemaps').init({ loadMaps: true }),
|
||||
PRODUCTION && require('gulp-uglify/composer')(require('uglify-es'))(),
|
||||
@@ -267,10 +270,7 @@ gulp.task(function buildStyles () {
|
||||
return pipe(
|
||||
src('index.scss', { sourcemaps: true }),
|
||||
require('gulp-sass')(),
|
||||
require('gulp-autoprefixer')([
|
||||
'last 1 version',
|
||||
'> 1%'
|
||||
]),
|
||||
require('gulp-autoprefixer')(['last 1 version', '> 1%']),
|
||||
PRODUCTION && require('gulp-csso')(),
|
||||
dest()
|
||||
)
|
||||
@@ -281,22 +281,20 @@ gulp.task(function copyAssets () {
|
||||
src(['assets/**/*', 'favicon.*']),
|
||||
src('fontawesome-webfont.*', {
|
||||
base: __dirname + '/node_modules/font-awesome/fonts', // eslint-disable-line no-path-concat
|
||||
passthrough: true
|
||||
passthrough: true,
|
||||
}),
|
||||
src(['!*.css', 'font-mfizz.*'], {
|
||||
base: __dirname + '/node_modules/font-mfizz/dist', // eslint-disable-line no-path-concat
|
||||
passthrough: true
|
||||
passthrough: true,
|
||||
}),
|
||||
dest()
|
||||
)
|
||||
})
|
||||
|
||||
gulp.task('build', gulp.parallel(
|
||||
'buildPages',
|
||||
'buildScripts',
|
||||
'buildStyles',
|
||||
'copyAssets'
|
||||
))
|
||||
gulp.task(
|
||||
'build',
|
||||
gulp.parallel('buildPages', 'buildScripts', 'buildStyles', 'copyAssets')
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
|
||||
180
package.json
180
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-web",
|
||||
"version": "5.12.0",
|
||||
"version": "5.15.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -31,10 +31,11 @@
|
||||
"npm": ">=3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nraynaud/novnc": "^0.6.1-1",
|
||||
"ansi_up": "^1.3.0",
|
||||
"asap": "^2.0.4",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"@nraynaud/novnc": "0.6.1",
|
||||
"ansi_up": "^2.0.2",
|
||||
"asap": "^2.0.6",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.1.2",
|
||||
"babel-plugin-dev": "^1.0.0",
|
||||
"babel-plugin-lodash": "^3.2.11",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
@@ -43,99 +44,109 @@
|
||||
"babel-plugin-transform-react-jsx-self": "^6.11.0",
|
||||
"babel-plugin-transform-react-jsx-source": "^6.9.0",
|
||||
"babel-plugin-transform-runtime": "^6.6.0",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"babel-register": "^6.16.3",
|
||||
"babel-runtime": "^6.6.1",
|
||||
"babelify": "^7.2.0",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"babelify": "^8.0.0",
|
||||
"benchmark": "^2.1.0",
|
||||
"bootstrap": "4.0.0-alpha.5",
|
||||
"browserify": "^14.1.0",
|
||||
"bundle-collapser": "^1.2.1",
|
||||
"browserify": "^15.1.0",
|
||||
"bundle-collapser": "^1.3.0",
|
||||
"chartist": "^0.10.1",
|
||||
"chartist-plugin-legend": "^0.6.1",
|
||||
"chartist-plugin-tooltip": "0.0.11",
|
||||
"classnames": "^2.2.3",
|
||||
"complex-matcher": "^0.1.1",
|
||||
"cookies-js": "^1.2.2",
|
||||
"d3": "^4.2.8",
|
||||
"dependency-check": "^2.5.1",
|
||||
"enzyme": "^2.6.0",
|
||||
"enzyme-to-json": "^1.4.4",
|
||||
"d3": "^4.12.2",
|
||||
"dependency-check": "^2.9.2",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-15": "^1.0.5",
|
||||
"enzyme-to-json": "^3.3.0",
|
||||
"eslint": "^4.14.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": "github:fizzed/font-mfizz",
|
||||
"get-stream": "^2.3.0",
|
||||
"globby": "^6.0.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-autoprefixer": "^4.0.0",
|
||||
"font-mfizz": "^2.4.1",
|
||||
"get-stream": "^3.0.0",
|
||||
"globby": "^7.1.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-autoprefixer": "^4.1.0",
|
||||
"gulp-csso": "^3.0.0",
|
||||
"gulp-embedlr": "^0.5.2",
|
||||
"gulp-plumber": "^1.1.0",
|
||||
"gulp-pug": "^3.1.0",
|
||||
"gulp-refresh": "^1.1.0",
|
||||
"gulp-sass": "^3.0.0",
|
||||
"gulp-sourcemaps": "^2.2.3",
|
||||
"gulp-sourcemaps": "^2.6.2",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"human-format": "^0.8.0",
|
||||
"husky": "^0.13.1",
|
||||
"immutable": "^3.8.1",
|
||||
"gulp-watch": "^5.0.0",
|
||||
"human-format": "^0.10.0",
|
||||
"husky": "^0.14.3",
|
||||
"immutable": "^3.8.2",
|
||||
"index-modules": "^0.3.0",
|
||||
"is-ip": "^1.0.0",
|
||||
"jest": "^20.0.4",
|
||||
"jsonrpc-websocket-client": "^0.1.1",
|
||||
"is-ip": "^2.0.0",
|
||||
"jest": "^22.0.4",
|
||||
"jsonrpc-websocket-client": "^0.2.0",
|
||||
"kindof": "^2.0.0",
|
||||
"later": "^1.2.0",
|
||||
"lint-staged": "^6.0.0",
|
||||
"lodash": "^4.6.1",
|
||||
"loose-envify": "^1.1.0",
|
||||
"make-error": "^1.2.1",
|
||||
"marked": "^0.3.5",
|
||||
"modular-css": "^5.1.6",
|
||||
"moment": "^2.13.0",
|
||||
"moment-timezone": "^0.5.4",
|
||||
"make-error": "^1.3.2",
|
||||
"marked": "^0.3.9",
|
||||
"modular-cssify": "^7.2.0",
|
||||
"moment": "^2.20.1",
|
||||
"moment-timezone": "^0.5.14",
|
||||
"notifyjs": "^3.0.0",
|
||||
"promise-toolbox": "^0.9.4",
|
||||
"prettier": "^1.9.2",
|
||||
"promise-toolbox": "^0.9.5",
|
||||
"prop-types": "^15.6.0",
|
||||
"random-password": "^0.1.2",
|
||||
"react": "^15.4.1",
|
||||
"react-addons-shallow-compare": "^15.1.0",
|
||||
"react-addons-test-utils": "^15.4.1",
|
||||
"react-addons-shallow-compare": "^15.6.2",
|
||||
"react-addons-test-utils": "^15.6.2",
|
||||
"react-bootstrap-4": "^0.29.1",
|
||||
"react-chartist": "^0.12.0",
|
||||
"react-copy-to-clipboard": "^5.0.0",
|
||||
"react-debounce-input": "^3.0.0",
|
||||
"react-dnd": "^2.1.4",
|
||||
"react-dnd-html5-backend": "^2.1.2",
|
||||
"react-chartist": "^0.13.0",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-dnd": "^2.5.4",
|
||||
"react-dnd-html5-backend": "^2.5.4",
|
||||
"react-document-title": "^2.0.2",
|
||||
"react-dom": "^15.4.1",
|
||||
"react-dropzone": "^3.5.0",
|
||||
"react-intl": "^2.0.1",
|
||||
"react-key-handler": "^0.3.0",
|
||||
"react-notify": "^2.0.1",
|
||||
"react-overlays": "^0.6.0",
|
||||
"react-redux": "^5.0.0",
|
||||
"react-dropzone": "^4.2.3",
|
||||
"react-intl": "^2.4.0",
|
||||
"react-key-handler": "^1.0.1",
|
||||
"react-notify": "^3.0.0",
|
||||
"react-overlays": "^0.8.3",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router": "^3.0.0",
|
||||
"react-select": "^1.0.0-rc.4",
|
||||
"react-shortcuts": "^1.3.1",
|
||||
"react-sparklines": "^1.5.0",
|
||||
"react-select": "^1.1.0",
|
||||
"react-shortcuts": "^2.0.0",
|
||||
"react-sparklines": "1.6.0",
|
||||
"react-test-renderer": "^15.6.2",
|
||||
"react-virtualized": "^8.0.8",
|
||||
"readable-stream": "^2.0.6",
|
||||
"redux": "^3.3.1",
|
||||
"redux-devtools": "^3.1.1",
|
||||
"redux-devtools-dock-monitor": "^1.1.0",
|
||||
"redux-devtools-log-monitor": "^1.0.5",
|
||||
"readable-stream": "^2.3.3",
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.0.1",
|
||||
"reselect": "^2.5.4",
|
||||
"semver": "^5.3.0",
|
||||
"standard": "^10.0.0",
|
||||
"styled-components": "^2.1.0",
|
||||
"superagent": "^3.5.0",
|
||||
"tar-stream": "^1.5.2",
|
||||
"uglify-es": "^3.0.18",
|
||||
"uncontrollable-input": "^0.0.1",
|
||||
"vinyl": "^2.0.0",
|
||||
"semver": "^5.4.1",
|
||||
"styled-components": "^2.4.0",
|
||||
"tar-stream": "^1.5.5",
|
||||
"uglify-es": "^3.3.4",
|
||||
"uncontrollable-input": "^0.1.1",
|
||||
"url-parse": "^1.2.0",
|
||||
"vinyl": "^2.1.0",
|
||||
"watchify": "^3.7.0",
|
||||
"xml2js": "^0.4.17",
|
||||
"whatwg-fetch": "^2.0.3",
|
||||
"xml2js": "^0.4.19",
|
||||
"xo-acl-resolver": "^0.2.3",
|
||||
"xo-common": "^0.1.1",
|
||||
"xo-lib": "^0.8.0",
|
||||
@@ -145,12 +156,16 @@
|
||||
"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",
|
||||
"clean": "gulp clean",
|
||||
"dev": "npm run build-indexes && NODE_ENV=development gulp build",
|
||||
"dev-test": "jest --watch",
|
||||
"lint": "standard",
|
||||
"posttest": "npm run lint",
|
||||
"prepublish": "npm run build",
|
||||
"lint-staged-stash": "touch .lint-staged && git stash save --include-untracked --keep-index && true",
|
||||
"lint-staged-unstash": "git stash pop && rm -f .lint-staged && true",
|
||||
"posttest": "eslint --ignore-path .gitignore src/",
|
||||
"prebuild": "npm run clean",
|
||||
"precommit": "lint-staged",
|
||||
"predev": "npm run clean",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "jest"
|
||||
},
|
||||
"browserify": {
|
||||
@@ -181,23 +196,32 @@
|
||||
"transform-runtime"
|
||||
],
|
||||
"presets": [
|
||||
"es2015",
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": ">2%"
|
||||
}
|
||||
}
|
||||
],
|
||||
"react",
|
||||
"stage-0"
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"setupTestFrameworkScriptFile": "./setup-tests.js",
|
||||
"snapshotSerializers": [
|
||||
"enzyme-to-json/serializer"
|
||||
]
|
||||
},
|
||||
"standard": {
|
||||
"globals": [
|
||||
"__DEV__"
|
||||
],
|
||||
"ignore": [
|
||||
"dist"
|
||||
],
|
||||
"parser": "babel-eslint"
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"lint-staged-stash",
|
||||
"prettier --write",
|
||||
"eslint --fix",
|
||||
"jest --findRelatedTests --passWithNoTests",
|
||||
"git add",
|
||||
"lint-staged-unstash"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
4
setup-tests.js
Normal file
4
setup-tests.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { configure } from 'enzyme'
|
||||
import Adapter from 'enzyme-adapter-react-15'
|
||||
|
||||
configure({ adapter: new Adapter() })
|
||||
@@ -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)
|
||||
|
||||
29
src/common/add-subscriptions.js
Normal file
29
src/common/add-subscriptions.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
|
||||
const call = fn => fn()
|
||||
|
||||
// `subscriptions` can be a function if we want to ensure that the subscription
|
||||
// callbacks have been correctly initialized when there are circular dependencies
|
||||
const addSubscriptions = subscriptions => Component =>
|
||||
class SubscriptionWrapper extends React.PureComponent {
|
||||
_unsubscribes = null
|
||||
|
||||
componentWillMount () {
|
||||
this._unsubscribes = map(
|
||||
typeof subscriptions === 'function' ? subscriptions(this.props) : subscriptions,
|
||||
(subscribe, prop) =>
|
||||
subscribe(value => this.setState({ [prop]: value }))
|
||||
)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this._unsubscribes.forEach(call)
|
||||
this._unsubscribes = null
|
||||
}
|
||||
|
||||
render () {
|
||||
return <Component {...this.props} {...this.state} />
|
||||
}
|
||||
}
|
||||
export { addSubscriptions as default }
|
||||
@@ -1,9 +1,6 @@
|
||||
import clone from 'lodash/clone'
|
||||
import includes from 'lodash/includes'
|
||||
import isArray from 'lodash/isArray'
|
||||
import forEach from 'lodash/forEach'
|
||||
import map from 'lodash/map'
|
||||
import { PureComponent } from 'react'
|
||||
import { cowSet } from 'utils'
|
||||
import { includes, isArray, forEach, map } from 'lodash'
|
||||
|
||||
import getEventValue from './get-event-value'
|
||||
|
||||
@@ -12,17 +9,6 @@ import getEventValue from './get-event-value'
|
||||
// Usually set to process.env.NODE_ENV !== 'production'.
|
||||
const VERBOSE = false
|
||||
|
||||
const cowSet = (object, path, value, depth) => {
|
||||
if (depth >= path.length) {
|
||||
return value
|
||||
}
|
||||
|
||||
object = object != null ? clone(object) : {}
|
||||
const prop = path[depth]
|
||||
object[prop] = cowSet(object[prop], path, value, depth + 1)
|
||||
return object
|
||||
}
|
||||
|
||||
const get = (object, path, depth) => {
|
||||
if (depth >= path.length) {
|
||||
return object
|
||||
@@ -54,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
|
||||
@@ -83,7 +67,7 @@ export default class BaseComponent extends PureComponent {
|
||||
|
||||
return (linkedState[key] = event => {
|
||||
this.setState({
|
||||
[name]: getValue(event)
|
||||
[name]: getValue(event),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -106,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 }
|
||||
|
||||
28
src/common/button-link.js
Normal file
28
src/common/button-link.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import { routerShape } from 'react-router/lib/PropTypes'
|
||||
|
||||
import Button from './button'
|
||||
import propTypes from './prop-types-decorator'
|
||||
|
||||
const ButtonLink = ({ to, ...props }, { router }) => {
|
||||
props.onClick = () => {
|
||||
router.push(to)
|
||||
}
|
||||
|
||||
return <Button {...props} />
|
||||
}
|
||||
|
||||
propTypes(
|
||||
{
|
||||
to: propTypes.oneOfType([
|
||||
propTypes.func,
|
||||
propTypes.object,
|
||||
propTypes.string,
|
||||
]),
|
||||
},
|
||||
{
|
||||
router: routerShape,
|
||||
}
|
||||
)(ButtonLink)
|
||||
|
||||
export { ButtonLink 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,18 +0,0 @@
|
||||
import {
|
||||
parse,
|
||||
toString
|
||||
} from './'
|
||||
import {
|
||||
ast,
|
||||
pattern
|
||||
} from './index.fixtures'
|
||||
|
||||
export default ({ benchmark }) => {
|
||||
benchmark('parse', () => {
|
||||
parse(pattern)
|
||||
})
|
||||
|
||||
benchmark('toString', () => {
|
||||
ast::toString()
|
||||
})
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import {
|
||||
createAnd,
|
||||
createOr,
|
||||
createNot,
|
||||
createProperty,
|
||||
createString,
|
||||
createTruthyProperty
|
||||
} from './'
|
||||
|
||||
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')
|
||||
])
|
||||
@@ -1,421 +0,0 @@
|
||||
import every from 'lodash/every'
|
||||
import filter from 'lodash/filter'
|
||||
import forEach from 'lodash/forEach'
|
||||
import isArray from 'lodash/isArray'
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import isString from 'lodash/isString'
|
||||
import map from 'lodash/map'
|
||||
import some from 'lodash/some'
|
||||
|
||||
import filterReduce from '../filter-reduce'
|
||||
import invoke from '../invoke'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const RAW_STRING_CHARS = invoke(() => {
|
||||
const chars = { __proto__: null }
|
||||
const add = (a, b = a) => {
|
||||
let i = a.charCodeAt(0)
|
||||
const j = b.charCodeAt(0)
|
||||
while (i <= j) {
|
||||
chars[String.fromCharCode(i++)] = true
|
||||
}
|
||||
}
|
||||
add('$')
|
||||
add('-')
|
||||
add('.')
|
||||
add('0', '9')
|
||||
add('_')
|
||||
add('A', 'Z')
|
||||
add('a', 'z')
|
||||
return chars
|
||||
})
|
||||
const isRawString = string => {
|
||||
const { length } = string
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (!RAW_STRING_CHARS[string[i]]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
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 createNot = child => ({ type: 'not', child })
|
||||
|
||||
export const createProperty = (name, child) => ({ type: 'property', name, child })
|
||||
|
||||
export const createString = value => ({ type: 'string', value })
|
||||
|
||||
export const createTruthyProperty = name => ({ type: 'truthyProperty', name })
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// *and = terms
|
||||
// terms = term+
|
||||
// term = ws (groupedAnd | or | not | property | truthyProperty | string) ws
|
||||
// ws = ' '*
|
||||
// groupedAnd = "(" and ")"
|
||||
// *or = "|" ws "(" terms ")"
|
||||
// *not = "!" term
|
||||
// *property = string ws ":" term
|
||||
// *truthyProperty = string ws "?"
|
||||
// *string = quotedString | rawString
|
||||
// quotedString = "\"" ( /[^"\]/ | "\\\\" | "\\\"" )+
|
||||
// rawString = /[a-z0-9-_.]+/i
|
||||
export const parse = invoke(() => {
|
||||
let i
|
||||
let n
|
||||
let input
|
||||
|
||||
// -----
|
||||
|
||||
const backtrace = parser => () => {
|
||||
const pos = i
|
||||
const node = parser()
|
||||
if (node != null) {
|
||||
return node
|
||||
}
|
||||
i = pos
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
const parseAnd = () => parseTerms(createAnd)
|
||||
const parseTerms = fn => {
|
||||
let term = parseTerm()
|
||||
if (!term) {
|
||||
return
|
||||
}
|
||||
|
||||
const terms = [ term ]
|
||||
while ((term = parseTerm())) {
|
||||
terms.push(term)
|
||||
}
|
||||
return fn(terms)
|
||||
}
|
||||
const parseTerm = () => {
|
||||
parseWs()
|
||||
|
||||
const child = (
|
||||
parseGroupedAnd() ||
|
||||
parseOr() ||
|
||||
parseNot() ||
|
||||
parseProperty() ||
|
||||
parseTruthyProperty() ||
|
||||
parseString()
|
||||
)
|
||||
if (child) {
|
||||
parseWs()
|
||||
return child
|
||||
}
|
||||
}
|
||||
const parseWs = () => {
|
||||
while (input[i] === ' ') {
|
||||
++i
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
const parseGroupedAnd = backtrace(() => {
|
||||
let and
|
||||
if (
|
||||
input[i++] === '(' &&
|
||||
(and = parseAnd()) &&
|
||||
input[i++] === ')'
|
||||
) {
|
||||
return and
|
||||
}
|
||||
})
|
||||
const parseOr = backtrace(() => {
|
||||
let or
|
||||
if (
|
||||
input[i++] === '|' &&
|
||||
parseWs() &&
|
||||
input[i++] === '(' &&
|
||||
(or = parseTerms(createOr)) &&
|
||||
input[i++] === ')'
|
||||
) {
|
||||
return or
|
||||
}
|
||||
})
|
||||
const parseNot = backtrace(() => {
|
||||
let child
|
||||
if (
|
||||
input[i++] === '!' &&
|
||||
(child = parseTerm())
|
||||
) {
|
||||
return createNot(child)
|
||||
}
|
||||
})
|
||||
const parseProperty = backtrace(() => {
|
||||
let name, child
|
||||
if (
|
||||
(name = parseString()) &&
|
||||
parseWs() &&
|
||||
(input[i++] === ':') &&
|
||||
(child = parseTerm())
|
||||
) {
|
||||
return createProperty(name.value, child)
|
||||
}
|
||||
})
|
||||
const parseString = () => {
|
||||
let value
|
||||
if (
|
||||
(value = parseQuotedString()) != null ||
|
||||
(value = parseRawString()) != null
|
||||
) {
|
||||
return createString(value)
|
||||
}
|
||||
}
|
||||
const parseQuotedString = backtrace(() => {
|
||||
if (input[i++] !== '"') {
|
||||
return
|
||||
}
|
||||
|
||||
const value = []
|
||||
let char
|
||||
while (i < n && (char = input[i++]) !== '"') {
|
||||
if (char === '\\') {
|
||||
char = input[i++]
|
||||
}
|
||||
value.push(char)
|
||||
}
|
||||
|
||||
return value.join('')
|
||||
})
|
||||
const parseRawString = () => {
|
||||
let value = ''
|
||||
let c
|
||||
while (
|
||||
(c = input[i]) &&
|
||||
RAW_STRING_CHARS[c]
|
||||
) {
|
||||
++i
|
||||
value += c
|
||||
}
|
||||
if (value.length) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
const parseTruthyProperty = backtrace(() => {
|
||||
let name
|
||||
if (
|
||||
(name = parseString()) &&
|
||||
parseWs() &&
|
||||
input[i++] === '?'
|
||||
) {
|
||||
return createTruthyProperty(name.value)
|
||||
}
|
||||
})
|
||||
|
||||
return input_ => {
|
||||
if (!input_) {
|
||||
return
|
||||
}
|
||||
|
||||
i = 0
|
||||
input = input_.split('')
|
||||
n = input.length
|
||||
|
||||
try {
|
||||
return parseAnd()
|
||||
} finally {
|
||||
input = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const _getPropertyClauseStrings = ({ child }) => {
|
||||
const { type } = child
|
||||
|
||||
if (type === 'or') {
|
||||
const strings = []
|
||||
forEach(child.children, child => {
|
||||
if (child.type === 'string') {
|
||||
strings.push(child.value)
|
||||
}
|
||||
})
|
||||
return strings
|
||||
}
|
||||
|
||||
if (type === 'string') {
|
||||
return [ child.value ]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// Find possible values for property clauses in a and clause.
|
||||
export const getPropertyClausesStrings = function () {
|
||||
if (!this) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const { type } = this
|
||||
|
||||
if (type === 'property') {
|
||||
return {
|
||||
[this.name]: _getPropertyClauseStrings(this)
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'and') {
|
||||
const strings = {}
|
||||
forEach(this.children, node => {
|
||||
if (node.type === 'property') {
|
||||
const { name } = node
|
||||
const values = strings[name]
|
||||
if (values) {
|
||||
values.push.apply(values, _getPropertyClauseStrings(node))
|
||||
} else {
|
||||
strings[name] = _getPropertyClauseStrings(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
return strings
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const removePropertyClause = function (name) {
|
||||
let type
|
||||
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 this
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const _addAndClause = (node, child, predicate, reducer) =>
|
||||
createAnd(filterReduce(
|
||||
node.type === 'and'
|
||||
? node.children
|
||||
: [ node ],
|
||||
predicate,
|
||||
reducer,
|
||||
child
|
||||
))
|
||||
|
||||
export const setPropertyClause = function (name, child) {
|
||||
const property = createProperty(
|
||||
name,
|
||||
isString(child) ? createString(child) : child
|
||||
)
|
||||
|
||||
if (!this) {
|
||||
return property
|
||||
}
|
||||
|
||||
return _addAndClause(
|
||||
this,
|
||||
property,
|
||||
node => node.type === 'property' && node.name === name
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
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])
|
||||
),
|
||||
truthyProperty: ({ name }, value) => !!value[name],
|
||||
string: invoke(() => {
|
||||
const match = (pattern, value) => {
|
||||
if (isString(value)) {
|
||||
return value.toLowerCase().indexOf(pattern) !== -1
|
||||
}
|
||||
|
||||
if (isArray(value) || isPlainObject(value)) {
|
||||
return some(value, value => match(pattern, value))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return ({ value: pattern }, value) => (
|
||||
match(pattern.toLowerCase(), value)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return function (value) {
|
||||
return visitors[this.type](this, value)
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const toString = invoke(() => {
|
||||
const toStringTerms = terms => map(terms, toString).join(' ')
|
||||
const toStringGroup = terms => `(${toStringTerms(terms)})`
|
||||
|
||||
const visitors = {
|
||||
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))}?`
|
||||
}
|
||||
|
||||
const toString = node => visitors[node.type](node)
|
||||
|
||||
// Special case for a root “and”: do not add braces.
|
||||
return function () {
|
||||
return !this
|
||||
? ''
|
||||
: this.type === 'and'
|
||||
? toStringTerms(this.children)
|
||||
: toString(this)
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const create = pattern => {
|
||||
pattern = parse(pattern)
|
||||
if (!pattern) {
|
||||
return
|
||||
}
|
||||
|
||||
return value => pattern::execute(value)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import {
|
||||
getPropertyClausesStrings,
|
||||
parse,
|
||||
setPropertyClause,
|
||||
toString
|
||||
} from './'
|
||||
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' ]
|
||||
})
|
||||
})
|
||||
|
||||
it('parse', () => {
|
||||
expect(parse(pattern)).toEqual(ast)
|
||||
})
|
||||
|
||||
it('setPropertyClause', () => {
|
||||
expect(
|
||||
null::setPropertyClause('foo', 'bar')::toString()
|
||||
).toBe('foo:bar')
|
||||
|
||||
expect(
|
||||
parse('baz')::setPropertyClause('foo', 'bar')::toString()
|
||||
).toBe('baz foo:bar')
|
||||
|
||||
expect(
|
||||
parse('plip foo:baz plop')::setPropertyClause('foo', 'bar')::toString()
|
||||
).toBe('plip plop foo:bar')
|
||||
|
||||
expect(
|
||||
parse('foo:|(baz plop)')::setPropertyClause('foo', 'bar')::toString()
|
||||
).toBe('foo:bar')
|
||||
})
|
||||
|
||||
it('toString', () => {
|
||||
expect(pattern).toBe(ast::toString())
|
||||
})
|
||||
@@ -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 }
|
||||
|
||||
60
src/common/debounce-component-decorator.js
Normal file
60
src/common/debounce-component-decorator.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
import getEventValue from './get-event-value'
|
||||
|
||||
const DEFAULT_DELAY = ({ debounceTimeout = 250 }) => debounceTimeout
|
||||
|
||||
const debounceComponentDecorator = (delay = DEFAULT_DELAY) => Component =>
|
||||
class DebouncedComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super()
|
||||
this.state = { value: props.value }
|
||||
|
||||
this._notify = debounce(event => {
|
||||
this.props.onChange(event)
|
||||
}, typeof delay === 'function' ? delay(props) : delay)
|
||||
|
||||
this._onChange = event => {
|
||||
this.setState({ value: getEventValue(event) })
|
||||
|
||||
event.persist()
|
||||
this._notify(event)
|
||||
}
|
||||
|
||||
this._wrappedInstance = null
|
||||
this._onRef = ref => {
|
||||
this._wrappedInstance = ref
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps ({ value }) {
|
||||
if (value !== this.props.value) {
|
||||
this._notify.cancel()
|
||||
this.setState({ value })
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this._notify.flush()
|
||||
}
|
||||
|
||||
getWrappedInstance () {
|
||||
return this._wrappedInstance
|
||||
}
|
||||
|
||||
render () {
|
||||
const props = {
|
||||
...this.props,
|
||||
onChange: this._onChange,
|
||||
ref: this._onRef,
|
||||
value: this.state.value,
|
||||
}
|
||||
return <Component {...props} />
|
||||
}
|
||||
}
|
||||
export { debounceComponentDecorator as default }
|
||||
|
||||
// common components
|
||||
export const Input = debounceComponentDecorator()('input')
|
||||
export const Textarea = debounceComponentDecorator()('textarea')
|
||||
@@ -1,21 +1,21 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } 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 +35,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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ import {
|
||||
SelectSr,
|
||||
SelectSubject,
|
||||
SelectTag,
|
||||
SelectVgpuType,
|
||||
SelectVm,
|
||||
SelectVmTemplate
|
||||
SelectVmTemplate,
|
||||
} from '../select-objects'
|
||||
|
||||
import styles from './index.css'
|
||||
@@ -34,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 })
|
||||
@@ -50,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 () {
|
||||
@@ -94,7 +88,7 @@ class Editable extends Component {
|
||||
this.setState({
|
||||
editing: true,
|
||||
error: null,
|
||||
saving: false
|
||||
saving: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -112,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) {
|
||||
@@ -138,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)
|
||||
}
|
||||
@@ -161,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 ? undefined : this._openEdition}
|
||||
onMouseDown={useLongClick ? this.__startTimer : undefined}
|
||||
onMouseUp={useLongClick ? this.__stopTimer : undefined}
|
||||
>
|
||||
{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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,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 () {
|
||||
@@ -217,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 () {
|
||||
@@ -247,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'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,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 () {
|
||||
@@ -300,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) {
|
||||
@@ -321,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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,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 => {
|
||||
@@ -352,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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,15 +400,13 @@ const MAP_TYPE_SELECT = {
|
||||
SR: SelectSr,
|
||||
subject: SelectSubject,
|
||||
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 () {
|
||||
@@ -401,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') {
|
||||
@@ -424,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 () {
|
||||
@@ -454,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
src/common/fetch.js
Normal file
11
src/common/fetch.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'whatwg-fetch'
|
||||
|
||||
const { fetch } = window
|
||||
export { fetch as default }
|
||||
|
||||
export const post = (url, body, opts) =>
|
||||
fetch(url, {
|
||||
...opts,
|
||||
body,
|
||||
method: 'POST',
|
||||
})
|
||||
@@ -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 {
|
||||
firstDefined,
|
||||
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(firstDefined(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) {
|
||||
@@ -97,7 +101,7 @@ export default class SelectPlainObject extends Component {
|
||||
|
||||
return (
|
||||
<Select
|
||||
autofocus={props.autoFocus}
|
||||
autoFocus={props.autoFocus}
|
||||
disabled={props.disabled}
|
||||
multi={props.multi}
|
||||
onChange={this._handleChange}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -116,8 +114,8 @@ export default class Select extends Component {
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
onClick={!disabled && (() => selectValue(option))}
|
||||
onMouseOver={!disabled && (() => focusOption(option))}
|
||||
onClick={disabled ? undefined : () => selectValue(option)}
|
||||
onMouseOver={disabled ? undefined : () => focusOption(option)}
|
||||
style={style}
|
||||
key={key}
|
||||
>
|
||||
@@ -129,6 +127,7 @@ export default class Select extends Component {
|
||||
render () {
|
||||
return (
|
||||
<ReactSelect
|
||||
closeOnSelect={!this.props.multi}
|
||||
{...this.props}
|
||||
backspaceToRemoveMessage=''
|
||||
menuRenderer={this._renderMenu}
|
||||
|
||||
@@ -9,18 +9,18 @@ import propTypes from '../prop-types-decorator'
|
||||
@uncontrollableInput()
|
||||
@propTypes({
|
||||
className: propTypes.string,
|
||||
onChange: propTypes.func,
|
||||
onChange: propTypes.func.isRequired,
|
||||
icon: propTypes.string,
|
||||
iconOn: propTypes.string,
|
||||
iconOff: propTypes.string,
|
||||
iconSize: propTypes.number,
|
||||
value: propTypes.bool
|
||||
value: propTypes.bool.isRequired,
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react'
|
||||
|
||||
import propTypes from './prop-types-decorator'
|
||||
|
||||
// A column can contain content or a row.
|
||||
export const Col = propTypes({
|
||||
className: propTypes.string,
|
||||
size: propTypes.number,
|
||||
@@ -12,47 +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:',
|
||||
}
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
import * as CM from 'complex-matcher'
|
||||
import React from 'react'
|
||||
|
||||
import Component from './base-component'
|
||||
import propTypes from './prop-types-decorator'
|
||||
import Tags from './tags'
|
||||
import { createString, createProperty, toString } from './complex-matcher'
|
||||
|
||||
@propTypes({
|
||||
labels: propTypes.arrayOf(React.PropTypes.string).isRequired,
|
||||
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(
|
||||
new CM.Property('tags', new CM.String(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,13 +1,10 @@
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import keys from 'lodash/keys'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import { Portal } from 'react-overlays'
|
||||
import { forEach, isEmpty, keys, map, noop } from 'lodash'
|
||||
|
||||
import _ from './intl'
|
||||
import ActionButton from './action-button'
|
||||
import Component from './base-component'
|
||||
import forEach from 'lodash/forEach'
|
||||
import Link from './link'
|
||||
import propTypes from './prop-types-decorator'
|
||||
import SortedTable from './sorted-table'
|
||||
@@ -16,12 +13,12 @@ import { connectStore } from './utils'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
createFilter,
|
||||
createSelector
|
||||
createSelector,
|
||||
} from './selectors'
|
||||
import {
|
||||
installAllHostPatches,
|
||||
installAllPatchesOnPool,
|
||||
subscribeHostMissingPatches
|
||||
subscribeHostMissingPatches,
|
||||
} from './xo'
|
||||
|
||||
// ===================================================================
|
||||
@@ -29,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'),
|
||||
@@ -51,30 +52,33 @@ 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'),
|
||||
})
|
||||
class HostsPatchesTable extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
@@ -90,16 +94,21 @@ class HostsPatchesTable extends Component {
|
||||
)
|
||||
|
||||
_subscribeMissingPatches = (hosts = this.props.hosts) => {
|
||||
const unsubs = map(hosts, host =>
|
||||
subscribeHostMissingPatches(
|
||||
host,
|
||||
patches => this.setState({
|
||||
missingPatches: {
|
||||
...this.state.missingPatches,
|
||||
[host.id]: patches.length
|
||||
}
|
||||
})
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
if (this.unsubscribeMissingPatches !== undefined) {
|
||||
@@ -115,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 () {
|
||||
@@ -144,7 +150,7 @@ class HostsPatchesTable extends Component {
|
||||
container,
|
||||
displayPools,
|
||||
pools,
|
||||
useTabButton
|
||||
useTabButton,
|
||||
} = this.props
|
||||
|
||||
const hosts = this._getHosts()
|
||||
@@ -152,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
|
||||
@@ -193,7 +201,7 @@ class HostsPatchesTable extends Component {
|
||||
const getPools = createGetObjectsOfType('pool')
|
||||
|
||||
return {
|
||||
pools: getPools
|
||||
pools: getPools,
|
||||
}
|
||||
})
|
||||
class HostsPatchesTableByPool extends Component {
|
||||
@@ -211,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,10 @@
|
||||
|
||||
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 PropTypes from 'prop-types'
|
||||
import React, { Component } 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 +36,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 +56,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 +66,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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ export default {
|
||||
editUserProfile: undefined,
|
||||
|
||||
// Original text: "Fetching data…"
|
||||
homeFetchingData: 'Recuperando datos...',
|
||||
homeFetchingData: 'Recuperando datos…',
|
||||
|
||||
// Original text: "Welcome on Xen Orchestra!"
|
||||
homeWelcome: '¡Bienvenido a Xen Orchestra!',
|
||||
@@ -267,7 +267,7 @@ export default {
|
||||
homeNoVms: '¡No hay VMs!',
|
||||
|
||||
// Original text: "Or…"
|
||||
homeNoVmsOr: 'O...',
|
||||
homeNoVmsOr: 'O…',
|
||||
|
||||
// Original text: "Import VM"
|
||||
homeImportVm: 'Importar VM',
|
||||
@@ -378,13 +378,14 @@ 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',
|
||||
|
||||
// Original text: "Migrate to…"
|
||||
homeMigrateTo: 'Migrar a...',
|
||||
homeMigrateTo: 'Migrar a…',
|
||||
|
||||
// Original text: 'Missing patches'
|
||||
homeMissingPaths: undefined,
|
||||
@@ -426,28 +427,28 @@ export default {
|
||||
selectSubjects: 'Elegir usuario(s) y/o grupo(s)',
|
||||
|
||||
// Original text: "Select Object(s)…"
|
||||
selectObjects: 'Elegir Objeto(s)...',
|
||||
selectObjects: 'Elegir Objeto(s)…',
|
||||
|
||||
// Original text: "Choose a role"
|
||||
selectRole: 'Elegir un rol',
|
||||
|
||||
// Original text: "Select Host(s)…"
|
||||
selectHosts: 'Elegir Host(s)...',
|
||||
selectHosts: 'Elegir Host(s)…',
|
||||
|
||||
// Original text: "Select object(s)…"
|
||||
selectHostsVms: 'Elegir objeto(s)...',
|
||||
selectHostsVms: 'Elegir objeto(s)…',
|
||||
|
||||
// Original text: "Select Network(s)…"
|
||||
selectNetworks: 'Elegir Red(es)...',
|
||||
selectNetworks: 'Elegir Red(es)…',
|
||||
|
||||
// Original text: "Select PIF(s)…"
|
||||
selectPifs: 'Elegir PIF(s)...',
|
||||
selectPifs: 'Elegir PIF(s)…',
|
||||
|
||||
// Original text: "Select Pool(s)…"
|
||||
selectPools: 'Elegir Pool(s)...',
|
||||
selectPools: 'Elegir Pool(s)…',
|
||||
|
||||
// Original text: "Select Remote(s)…"
|
||||
selectRemotes: 'Elegir almacenamiento(s) remoto(s)...',
|
||||
selectRemotes: 'Elegir almacenamiento(s) remoto(s)…',
|
||||
|
||||
// Original text: 'Select resource set(s)…'
|
||||
selectResourceSets: undefined,
|
||||
@@ -468,19 +469,19 @@ export default {
|
||||
selectSshKey: undefined,
|
||||
|
||||
// Original text: "Select SR(s)…"
|
||||
selectSrs: 'Elegir SR(s)...',
|
||||
selectSrs: 'Elegir SR(s)…',
|
||||
|
||||
// Original text: "Select VM(s)…"
|
||||
selectVms: 'Elegir VM(s)...',
|
||||
selectVms: 'Elegir VM(s)…',
|
||||
|
||||
// Original text: "Select VM template(s)…"
|
||||
selectVmTemplates: 'Elegir plantilla(s) de VM...',
|
||||
selectVmTemplates: 'Elegir plantilla(s) de VM…',
|
||||
|
||||
// Original text: "Select tag(s)…"
|
||||
selectTags: 'Elegir etiqueta(s)...',
|
||||
selectTags: 'Elegir etiqueta(s)…',
|
||||
|
||||
// Original text: "Select disk(s)…"
|
||||
selectVdis: 'Elegir disco(s)...',
|
||||
selectVdis: 'Elegir disco(s)…',
|
||||
|
||||
// Original text: 'Select timezone…'
|
||||
selectTimezone: undefined,
|
||||
@@ -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,
|
||||
@@ -1475,7 +1481,7 @@ export default {
|
||||
// Original text: 'Installation started'
|
||||
supplementalPackInstallStartedTitle: undefined,
|
||||
|
||||
// Original text: 'Installing new supplemental pack...'
|
||||
// Original text: 'Installing new supplemental pack…'
|
||||
supplementalPackInstallStartedMessage: undefined,
|
||||
|
||||
// Original text: 'Installation error'
|
||||
@@ -2283,7 +2289,7 @@ export default {
|
||||
statsDashboardSelectObjects: 'Seleccionar',
|
||||
|
||||
// Original text: "Loading…"
|
||||
metricsLoading: 'Cargando...',
|
||||
metricsLoading: 'Cargando…',
|
||||
|
||||
// Original text: "Coming soon!"
|
||||
comingSoon: '¡Próximamente!',
|
||||
@@ -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',
|
||||
@@ -2613,10 +2622,10 @@ export default {
|
||||
vmImportFailed: 'Falló la importación de VM',
|
||||
|
||||
// Original text: "Import starting…"
|
||||
startVmImport: 'Comenzando importación...',
|
||||
startVmImport: 'Comenzando importación…',
|
||||
|
||||
// Original text: "Export starting…"
|
||||
startVmExport: 'Comenzando export...',
|
||||
startVmExport: 'Comenzando export…',
|
||||
|
||||
// Original text: 'N CPUs'
|
||||
nCpus: undefined,
|
||||
@@ -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',
|
||||
@@ -2973,10 +2988,11 @@ export default {
|
||||
importBackupModalStart: 'Arrancar la VM tras la restauración',
|
||||
|
||||
// Original text: "Select your backup…"
|
||||
importBackupModalSelectBackup: 'Elige el backup...',
|
||||
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,
|
||||
@@ -3068,7 +3088,7 @@ export default {
|
||||
// Original text: 'Connection failed. Click for more information.'
|
||||
serverConnectionFailed: undefined,
|
||||
|
||||
// Original text: 'Connecting...'
|
||||
// Original text: 'Connecting…'
|
||||
serverConnecting: undefined,
|
||||
|
||||
// Original text: 'Connected'
|
||||
@@ -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',
|
||||
@@ -3770,7 +3800,7 @@ export default {
|
||||
// Original text: 'Create'
|
||||
xosanCreate: undefined,
|
||||
|
||||
// Original text: 'Installing XOSAN. Please wait...'
|
||||
// Original text: 'Installing XOSAN. Please wait…'
|
||||
xosanInstalling: undefined,
|
||||
|
||||
// Original text: 'No XOSAN available for Community Edition'
|
||||
@@ -3782,7 +3812,7 @@ export default {
|
||||
// Original text: 'Load cloud plugin first'
|
||||
xosanLoadCloudPlugin: undefined,
|
||||
|
||||
// Original text: 'Loading...'
|
||||
// Original text: 'Loading…'
|
||||
xosanLoading: undefined,
|
||||
|
||||
// Original text: 'XOSAN is not available at the moment'
|
||||
@@ -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',
|
||||
@@ -407,6 +410,22 @@ export default {
|
||||
// Original text: "Not shared {type}"
|
||||
srNotSharedType: '{type} non partagé',
|
||||
|
||||
// Original text: 'All of them are selected'
|
||||
sortedTableAllItemsSelected: 'Toutes sont sélectionnées',
|
||||
|
||||
// Original text: '{nFiltered, number} of {nTotal, number} items'
|
||||
sortedTableNumberOfFilteredItems:
|
||||
'{nFiltered, number} entrées sur {nTotal, number}',
|
||||
|
||||
// Original text: '{nTotal, number} items'
|
||||
sortedTableNumberOfItems: '{nTotal, number} entrées',
|
||||
|
||||
// Original text: '{nSelected, number} selected'
|
||||
sortedTableNumberOfSelectedItems: '{nSelected, number} sélectionnées',
|
||||
|
||||
// Original text: 'Click here to select all items'
|
||||
sortedTableSelectAllItems: 'Cliquez ici pour sélectionner toutes les entrées',
|
||||
|
||||
// Original text: "Add"
|
||||
add: 'Ajouter',
|
||||
|
||||
@@ -576,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',
|
||||
@@ -651,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é',
|
||||
@@ -666,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é.',
|
||||
@@ -690,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',
|
||||
@@ -729,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',
|
||||
@@ -882,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",
|
||||
@@ -1101,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',
|
||||
@@ -1113,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',
|
||||
@@ -1149,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',
|
||||
@@ -1341,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',
|
||||
@@ -1380,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',
|
||||
@@ -1470,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})',
|
||||
@@ -1478,20 +1515,23 @@ export default {
|
||||
// Original text: "Installation started"
|
||||
supplementalPackInstallStartedTitle: 'Installation démarrée',
|
||||
|
||||
// Original text: "Installing new supplemental pack..."
|
||||
supplementalPackInstallStartedMessage: "Installation d'un nouveau pack supplémentaire",
|
||||
// Original text: "Installing new supplemental pack…"
|
||||
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',
|
||||
@@ -1650,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',
|
||||
@@ -1983,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é',
|
||||
@@ -2004,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é',
|
||||
@@ -2024,6 +2067,9 @@ export default {
|
||||
// Original text: "Name"
|
||||
snapshotName: 'Nom',
|
||||
|
||||
// Original text: "Name"
|
||||
snapshotDescription: 'Description',
|
||||
|
||||
// Original text: "Action"
|
||||
snapshotAction: 'Action',
|
||||
|
||||
@@ -2139,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',
|
||||
@@ -2178,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}}',
|
||||
@@ -2247,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',
|
||||
@@ -2532,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 :',
|
||||
@@ -2559,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',
|
||||
@@ -2592,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.',
|
||||
@@ -2652,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',
|
||||
@@ -2679,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é',
|
||||
@@ -2781,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',
|
||||
@@ -2802,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',
|
||||
@@ -2832,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',
|
||||
@@ -2868,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',
|
||||
@@ -2907,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 :',
|
||||
@@ -2925,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 :',
|
||||
@@ -2952,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',
|
||||
@@ -2961,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',
|
||||
@@ -2979,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.',
|
||||
@@ -2994,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',
|
||||
@@ -3036,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',
|
||||
@@ -3069,10 +3162,11 @@ 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...',
|
||||
// Original text: "Connecting…"
|
||||
serverConnecting: 'Connexion…',
|
||||
|
||||
// Original text: "Connected"
|
||||
serverConnected: 'Connecté',
|
||||
@@ -3090,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',
|
||||
@@ -3126,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',
|
||||
@@ -3135,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',
|
||||
@@ -3213,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',
|
||||
@@ -3270,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',
|
||||
@@ -3321,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 :',
|
||||
@@ -3336,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",
|
||||
@@ -3372,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',
|
||||
@@ -3438,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é',
|
||||
@@ -3450,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',
|
||||
@@ -3486,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',
|
||||
@@ -3564,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',
|
||||
@@ -3642,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',
|
||||
@@ -3663,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',
|
||||
@@ -3672,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',
|
||||
@@ -3681,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',
|
||||
@@ -3690,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',
|
||||
@@ -3741,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',
|
||||
@@ -3773,8 +3896,8 @@ export default {
|
||||
// Original text: "Create"
|
||||
xosanCreate: 'Créer',
|
||||
|
||||
// Original text: "Installing XOSAN. Please wait..."
|
||||
xosanInstalling: 'Installation de XOSAN. Veuillez patienter...',
|
||||
// Original text: "Installing XOSAN. Please wait…"
|
||||
xosanInstalling: 'Installation de XOSAN. Veuillez patienter…',
|
||||
|
||||
// Original text: "No XOSAN available for Community Edition"
|
||||
xosanCommunity: 'XOSAN non disponible pour la Community Edition',
|
||||
@@ -3785,8 +3908,8 @@ export default {
|
||||
// Original text: "Load cloud plugin first"
|
||||
xosanLoadCloudPlugin: 'Charger le plugin cloud avant',
|
||||
|
||||
// Original text: "Loading..."
|
||||
xosanLoading: 'Chargement...',
|
||||
// Original text: "Loading…"
|
||||
xosanLoading: 'Chargement…',
|
||||
|
||||
// Original text: "XOSAN is not available at the moment"
|
||||
xosanNotAvailable: "XOSAN n'est pas disponible pour le moment",
|
||||
@@ -3795,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 :',
|
||||
@@ -3804,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 :',
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ export default {
|
||||
editUserProfile: undefined,
|
||||
|
||||
// Original text: "Fetching data…"
|
||||
homeFetchingData: 'מקבל נתונים, נא להמתין...',
|
||||
homeFetchingData: 'מקבל נתונים, נא להמתין…',
|
||||
|
||||
// Original text: "Welcome on Xen Orchestra!"
|
||||
homeWelcome: 'ברוכים הבאים',
|
||||
@@ -228,7 +228,7 @@ export default {
|
||||
homeNoVms: 'אין מכונות',
|
||||
|
||||
// Original text: "Or…"
|
||||
homeNoVmsOr: 'או...',
|
||||
homeNoVmsOr: 'או…',
|
||||
|
||||
// Original text: "Import VM"
|
||||
homeImportVm: 'ההלעה של מכונה',
|
||||
@@ -330,7 +330,7 @@ export default {
|
||||
homeMore: 'עוד',
|
||||
|
||||
// Original text: "Migrate to…"
|
||||
homeMigrateTo: 'העבר ל...',
|
||||
homeMigrateTo: 'העבר ל…',
|
||||
|
||||
// Original text: 'Missing patches'
|
||||
homeMissingPaths: undefined,
|
||||
@@ -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',
|
||||
@@ -1427,20 +1439,22 @@ export default {
|
||||
// Original text: "Installation started"
|
||||
supplementalPackInstallStartedTitle: 'Installation Started',
|
||||
|
||||
// Original text: "Installing new supplemental pack..."
|
||||
supplementalPackInstallStartedMessage: 'Installing new supplemental pack...',
|
||||
// Original text: "Installing new supplemental pack…"
|
||||
supplementalPackInstallStartedMessage: 'Installing new supplemental pack…',
|
||||
|
||||
// Original text: "Installation error"
|
||||
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ó',
|
||||
@@ -2894,8 +2936,8 @@ export default {
|
||||
// Original text: "Connection failed"
|
||||
serverConnectionFailed: 'Csatlakozás Sikertelen',
|
||||
|
||||
// Original text: "Connecting..."
|
||||
serverConnecting: 'Csatlakozás...',
|
||||
// Original text: "Connecting…"
|
||||
serverConnecting: 'Csatlakozás…',
|
||||
|
||||
// Original text: "Connected"
|
||||
serverConnected: 'Kapcsolódva',
|
||||
@@ -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',
|
||||
@@ -3557,7 +3623,7 @@ export default {
|
||||
// Original text: 'Create'
|
||||
xosanCreate: undefined,
|
||||
|
||||
// Original text: 'Installing XOSAN. Please wait...'
|
||||
// Original text: 'Installing XOSAN. Please wait…'
|
||||
xosanInstalling: undefined,
|
||||
|
||||
// Original text: 'You need XenServer 7.0 to install XOSAN'
|
||||
@@ -3572,7 +3638,7 @@ export default {
|
||||
// Original text: 'Load cloud plugin first'
|
||||
xosanLoadCloudPlugin: undefined,
|
||||
|
||||
// Original text: 'Loading...'
|
||||
// Original text: 'Loading…'
|
||||
xosanLoading: undefined,
|
||||
|
||||
// Original text: 'XOSAN is not available at the moment'
|
||||
@@ -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ć',
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ export default {
|
||||
editUserProfile: undefined,
|
||||
|
||||
// Original text: "Fetching data…"
|
||||
homeFetchingData: 'Obtendo dados...',
|
||||
homeFetchingData: 'Obtendo dados…',
|
||||
|
||||
// Original text: "Welcome on Xen Orchestra!"
|
||||
homeWelcome: 'Bem-vindo ao Xen Orchestra',
|
||||
@@ -228,7 +228,7 @@ export default {
|
||||
homeNoVms: 'Não foram encontradas VMs!',
|
||||
|
||||
// Original text: "Or…"
|
||||
homeNoVmsOr: 'Ou...',
|
||||
homeNoVmsOr: 'Ou…',
|
||||
|
||||
// Original text: "Import VM"
|
||||
homeImportVm: 'Importar VM',
|
||||
@@ -330,7 +330,7 @@ export default {
|
||||
homeMore: 'Mais',
|
||||
|
||||
// Original text: "Migrate to…"
|
||||
homeMigrateTo: 'Migrar para...',
|
||||
homeMigrateTo: 'Migrar para…',
|
||||
|
||||
// Original text: 'Missing patches'
|
||||
homeMissingPaths: undefined,
|
||||
@@ -360,28 +360,28 @@ export default {
|
||||
selectSubjects: 'Escolha um usuário(s) e/ou grupo(s)',
|
||||
|
||||
// Original text: "Select Object(s)…"
|
||||
selectObjects: 'Selecionar Objeto(s)...',
|
||||
selectObjects: 'Selecionar Objeto(s)…',
|
||||
|
||||
// Original text: "Choose a role"
|
||||
selectRole: 'Escolha uma função',
|
||||
|
||||
// Original text: "Select Host(s)…"
|
||||
selectHosts: 'Selecionar Host(s)...',
|
||||
selectHosts: 'Selecionar Host(s)…',
|
||||
|
||||
// Original text: "Select object(s)…"
|
||||
selectHostsVms: 'Selecionar Objeto(s)...',
|
||||
selectHostsVms: 'Selecionar Objeto(s)…',
|
||||
|
||||
// Original text: "Select Network(s)…"
|
||||
selectNetworks: 'Selecionar Rede(s)...',
|
||||
selectNetworks: 'Selecionar Rede(s)…',
|
||||
|
||||
// Original text: "Select PIF(s)…"
|
||||
selectPifs: 'Selecionar PIF(s)...',
|
||||
selectPifs: 'Selecionar PIF(s)…',
|
||||
|
||||
// Original text: "Select Pool(s)…"
|
||||
selectPools: 'Selecionar Pool(s)...',
|
||||
selectPools: 'Selecionar Pool(s)…',
|
||||
|
||||
// Original text: "Select Remote(s)…"
|
||||
selectRemotes: 'Selecionar Remote(s)...',
|
||||
selectRemotes: 'Selecionar Remote(s)…',
|
||||
|
||||
// Original text: 'Select resource set(s)…'
|
||||
selectResourceSets: undefined,
|
||||
@@ -402,19 +402,19 @@ export default {
|
||||
selectSshKey: undefined,
|
||||
|
||||
// Original text: "Select SR(s)…"
|
||||
selectSrs: 'Selecionar SR(s)...',
|
||||
selectSrs: 'Selecionar SR(s)…',
|
||||
|
||||
// Original text: "Select VM(s)…"
|
||||
selectVms: 'Selecionar VM(s)...',
|
||||
selectVms: 'Selecionar VM(s)…',
|
||||
|
||||
// Original text: "Select VM template(s)…"
|
||||
selectVmTemplates: 'Selecionar VM(s) modelo(s)...',
|
||||
selectVmTemplates: 'Selecionar VM(s) modelo(s)…',
|
||||
|
||||
// Original text: "Select tag(s)…"
|
||||
selectTags: 'Selecionar etiqueta(s)...',
|
||||
selectTags: 'Selecionar etiqueta(s)…',
|
||||
|
||||
// Original text: "Select disk(s)…"
|
||||
selectVdis: 'Selecionar disco(s)...',
|
||||
selectVdis: 'Selecionar disco(s)…',
|
||||
|
||||
// Original text: 'Select timezone…'
|
||||
selectTimezone: undefined,
|
||||
@@ -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',
|
||||
@@ -1968,7 +1976,7 @@ export default {
|
||||
statsDashboardSelectObjects: 'Selecionar',
|
||||
|
||||
// Original text: "Loading…"
|
||||
metricsLoading: 'Carregando...',
|
||||
metricsLoading: 'Carregando…',
|
||||
|
||||
// Original text: "Coming soon!"
|
||||
comingSoon: 'Em breve!',
|
||||
@@ -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',
|
||||
@@ -2292,10 +2302,10 @@ export default {
|
||||
vmImportFailed: 'Falha na importação',
|
||||
|
||||
// Original text: "Import starting…"
|
||||
startVmImport: 'Iniciando importação...',
|
||||
startVmImport: 'Iniciando importação…',
|
||||
|
||||
// Original text: "Export starting…"
|
||||
startVmExport: 'Iniciando exportação...',
|
||||
startVmExport: 'Iniciando exportação…',
|
||||
|
||||
// Original text: 'N CPUs'
|
||||
nCpus: undefined,
|
||||
@@ -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',
|
||||
@@ -2559,16 +2577,18 @@ export default {
|
||||
importBackupModalStart: 'Iniciar VM após restauração',
|
||||
|
||||
// Original text: "Select your backup…"
|
||||
importBackupModalSelectBackup: 'Selecionar backup...',
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ export default {
|
||||
homeMore: '更多',
|
||||
|
||||
// Original text: "Migrate to…"
|
||||
homeMigrateTo: '迁移至...',
|
||||
homeMigrateTo: '迁移至…',
|
||||
|
||||
// Original text: "Missing patches"
|
||||
homeMissingPaths: '缺少补丁',
|
||||
@@ -1467,7 +1467,7 @@ export default {
|
||||
statsDashboardSelectObjects: '选择',
|
||||
|
||||
// Original text: "Loading…"
|
||||
metricsLoading: '加载中....',
|
||||
metricsLoading: '加载中….',
|
||||
|
||||
// Original text: "Coming soon!"
|
||||
comingSoon: '即将呈现',
|
||||
@@ -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: '迁移虚拟机',
|
||||
@@ -1947,7 +1959,7 @@ export default {
|
||||
importBackupModalStart: '恢复后启动虚拟机',
|
||||
|
||||
// Original text: "Select your backup…"
|
||||
importBackupModalSelectBackup: '选择你的备份...',
|
||||
importBackupModalSelectBackup: '选择你的备份…',
|
||||
|
||||
// Original text: "Are you sure you want to remove all orphaned VDIs?"
|
||||
removeAllOrphanedModalWarning: '你确定要删除所有孤立的虚拟磁盘?',
|
||||
@@ -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',
|
||||
@@ -16,6 +16,7 @@ var messages = {
|
||||
editableLongClickPlaceholder: 'Long click to edit',
|
||||
editableClickPlaceholder: 'Click to edit',
|
||||
browseFiles: 'Browse files',
|
||||
showLogs: 'Show logs',
|
||||
|
||||
// ----- Modals -----
|
||||
alertOk: 'OK',
|
||||
@@ -30,6 +31,10 @@ var messages = {
|
||||
filterOnlyRegular: 'Normal disks',
|
||||
filterOnlySnapshots: 'Snapshot disks',
|
||||
filterOnlyUnmanaged: 'Unmanaged disks',
|
||||
filterSaveAs: 'Save…',
|
||||
filterSyntaxLinkTooltip: 'Explore the search syntax in the documentation',
|
||||
filterVifsOnlyConnected: 'Connected VIFs',
|
||||
filterVifsOnlyDisconnected: 'Disconnected VIFs',
|
||||
|
||||
// ----- Copiable component -----
|
||||
copyToClipboard: 'Copy to clipboard',
|
||||
@@ -52,7 +57,9 @@ var messages = {
|
||||
selfServicePage: 'Self service',
|
||||
backupPage: 'Backup',
|
||||
jobsPage: 'Jobs',
|
||||
xoaPage: 'XOA',
|
||||
updatePage: 'Updates',
|
||||
licensesPage: 'Licenses',
|
||||
settingsPage: 'Settings',
|
||||
settingsServersPage: 'Servers',
|
||||
settingsUsersPage: 'Users',
|
||||
@@ -90,6 +97,7 @@ var messages = {
|
||||
jobsSchedulingPage: 'Scheduling',
|
||||
customJob: 'Custom Job',
|
||||
userPage: 'User',
|
||||
xoa: 'XOA',
|
||||
|
||||
// ----- Support -----
|
||||
noSupport: 'No support',
|
||||
@@ -105,7 +113,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',
|
||||
@@ -140,15 +149,16 @@ var messages = {
|
||||
homeFilterHvmGuests: 'HVM guests',
|
||||
homeFilterTags: 'Tags',
|
||||
homeSortBy: 'Sort by',
|
||||
homeSortByCpus: 'CPUs',
|
||||
homeSortByName: 'Name',
|
||||
homeSortByPowerstate: 'Power state',
|
||||
homeSortByRAM: 'RAM',
|
||||
homeSortByvCPUs: 'vCPUs',
|
||||
homeSortByCpus: 'CPUs',
|
||||
homeSortByShared: 'Shared/Not shared',
|
||||
homeSortBySize: 'Size',
|
||||
homeSortByUsage: 'Usage',
|
||||
homeSortByType: 'Type',
|
||||
homeSortByUsage: 'Usage',
|
||||
homeSortByvCPUs: 'vCPUs',
|
||||
homeSortVmsBySnapshots: 'Snapshots',
|
||||
homeDisplayedItems: '{displayed, number}x {icon} (on {total, number})',
|
||||
homeSelectedItems: '{selected, number}x {icon} selected (on {total, number})',
|
||||
homeMore: 'More',
|
||||
@@ -161,7 +171,13 @@ var messages = {
|
||||
srNotSharedType: 'Not shared {type}',
|
||||
|
||||
// ----- Common components -----
|
||||
sortedTableSelectedItems: '{selected, number} selected on {total, number}',
|
||||
sortedTableAllItemsSelected: 'All of them are selected',
|
||||
sortedTableNoItems: 'No items found',
|
||||
sortedTableNumberOfFilteredItems:
|
||||
'{nFiltered, number} of {nTotal, number} items',
|
||||
sortedTableNumberOfItems: '{nTotal, number} items',
|
||||
sortedTableNumberOfSelectedItems: '{nSelected, number} selected',
|
||||
sortedTableSelectAllItems: 'Click here to select all items',
|
||||
|
||||
// ----- Forms -----
|
||||
add: 'Add',
|
||||
@@ -193,6 +209,7 @@ var messages = {
|
||||
selectTimezone: 'Select timezone…',
|
||||
selectIp: 'Select IP(s)…',
|
||||
selectIpPool: 'Select IP pool(s)…',
|
||||
selectVgpuType: 'Select VGPU type(s)…',
|
||||
fillRequiredInformations: 'Fill required informations.',
|
||||
fillOptionalInformations: 'Fill informations (optional)',
|
||||
selectTableReset: 'Reset',
|
||||
@@ -227,8 +244,10 @@ var messages = {
|
||||
successfulJobCall: 'Successful',
|
||||
failedJobCall: 'Failed',
|
||||
jobCallInProgess: 'In progress',
|
||||
jobTransferredDataSize: 'size:',
|
||||
jobTransferredDataSpeed: 'speed:',
|
||||
jobTransferredDataSize: 'Transfer size:',
|
||||
jobTransferredDataSpeed: 'Transfer speed:',
|
||||
jobMergedDataSize: 'Merge size:',
|
||||
jobMergedDataSpeed: 'Merge speed:',
|
||||
allJobCalls: 'All',
|
||||
job: 'Job',
|
||||
jobModalTitle: 'Job {job}',
|
||||
@@ -254,21 +273,29 @@ 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.',
|
||||
scheduleEdit: 'Edit',
|
||||
scheduleDelete: 'Delete',
|
||||
deleteSelectedSchedules: 'Delete selected schedules',
|
||||
noScheduledJobs: 'No scheduled jobs.',
|
||||
newSchedule: 'New schedule',
|
||||
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 -----
|
||||
@@ -277,8 +304,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',
|
||||
@@ -289,7 +318,7 @@ var messages = {
|
||||
editBackupTagTitle: 'Tag',
|
||||
editBackupReportTitle: 'Report',
|
||||
editBackupScheduleEnabled: 'Automatically run as scheduled',
|
||||
editBackupDepthTitle: 'Depth',
|
||||
editBackupRetentionTitle: 'Retention',
|
||||
editBackupRemoteTitle: 'Remote',
|
||||
deleteOldBackupsFirst: 'Delete the old backups first',
|
||||
|
||||
@@ -300,6 +329,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.',
|
||||
remoteTestTip: 'Test your remote',
|
||||
testRemote: 'Test Remote',
|
||||
remoteTestFailure: 'Test failed for {name}',
|
||||
@@ -411,7 +442,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',
|
||||
@@ -486,6 +518,7 @@ var messages = {
|
||||
poolHaEnabled: 'Enabled',
|
||||
poolHaDisabled: 'Disabled',
|
||||
setpoolMaster: 'Master',
|
||||
poolGpuGroups: 'GPU groups',
|
||||
// ----- Pool host tab -----
|
||||
hostNameLabel: 'Name',
|
||||
hostDescription: 'Description',
|
||||
@@ -512,8 +545,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',
|
||||
@@ -527,17 +562,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',
|
||||
@@ -551,6 +588,7 @@ var messages = {
|
||||
hostStartedSince: 'Host uptime',
|
||||
hostStackStartedSince: 'Toolstack uptime',
|
||||
hostCpusModel: 'CPU model',
|
||||
hostGpus: 'GPUs',
|
||||
hostCpusNumber: 'Core (socket)',
|
||||
hostManufacturerinfo: 'Manufacturer info',
|
||||
hostBiosinfo: 'BIOS info',
|
||||
@@ -563,11 +601,13 @@ var messages = {
|
||||
supplementalPackPoolNew: 'Install supplemental pack on every host',
|
||||
supplementalPackTitle: '{name} (by {author})',
|
||||
supplementalPackInstallStartedTitle: 'Installation started',
|
||||
supplementalPackInstallStartedMessage: 'Installing new supplemental pack...',
|
||||
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',
|
||||
@@ -623,7 +663,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 -----
|
||||
@@ -728,6 +769,7 @@ var messages = {
|
||||
vbdReadonly: 'Readonly',
|
||||
vbdAction: 'Action',
|
||||
vbdCreate: 'Create',
|
||||
vbdAttach: 'Attach',
|
||||
vbdNamePlaceHolder: 'Disk name',
|
||||
vbdSizePlaceHolder: 'Size',
|
||||
cdDriveNotInstalled: 'CD drive not completely installed',
|
||||
@@ -736,6 +778,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)',
|
||||
|
||||
// ----- VM network tab -----
|
||||
vifCreateDeviceButton: 'New device',
|
||||
@@ -750,12 +796,14 @@ var messages = {
|
||||
vifConnect: 'Connect',
|
||||
vifDisconnect: 'Disconnect',
|
||||
vifRemove: 'Remove',
|
||||
vifsRemove: 'Remove selected VIFs',
|
||||
vifIpAddresses: 'IP addresses',
|
||||
vifMacAutoGenerate: 'Auto-generated if empty',
|
||||
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',
|
||||
@@ -771,6 +819,7 @@ var messages = {
|
||||
exportSnapshot: 'Export this snapshot',
|
||||
snapshotDate: 'Creation date',
|
||||
snapshotName: 'Name',
|
||||
snapshotDescription: 'Description',
|
||||
snapshotAction: 'Action',
|
||||
snapshotQuiesce: 'Quiesced snapshot',
|
||||
|
||||
@@ -798,7 +847,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',
|
||||
@@ -816,12 +866,20 @@ 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',
|
||||
vmCoresPerSocketNone: 'None',
|
||||
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:',
|
||||
vmVgpu: 'vGPU',
|
||||
vmVgpus: 'GPUs',
|
||||
vmVgpuNone: 'None',
|
||||
vmAddVgpu: 'Add vGPU',
|
||||
vmSelectVgpuType: 'Select vGPU type',
|
||||
|
||||
// ----- VM placeholders -----
|
||||
|
||||
@@ -835,8 +893,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}}',
|
||||
@@ -958,13 +1018,19 @@ var messages = {
|
||||
noResourceSets: 'No resource sets.',
|
||||
loadingResourceSets: 'Loading resource sets',
|
||||
resourceSetName: 'Resource set name',
|
||||
resourceSetUsers: 'Users',
|
||||
resourceSetPools: 'Pools',
|
||||
resourceSetTemplates: 'Templates',
|
||||
resourceSetSrs: 'SRs',
|
||||
resourceSetNetworks: 'Networks',
|
||||
recomputeResourceSets: 'Recompute all limits',
|
||||
saveResourceSet: 'Save',
|
||||
resetResourceSet: 'Reset',
|
||||
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',
|
||||
@@ -973,9 +1039,10 @@ 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)',
|
||||
maxRam: 'Maximum RAM',
|
||||
maxDiskSpace: 'Maximum disk space',
|
||||
ipPool: 'IP pool',
|
||||
quantity: 'Quantity',
|
||||
@@ -986,7 +1053,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:',
|
||||
@@ -1020,7 +1088,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',
|
||||
@@ -1053,47 +1122,66 @@ 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}?',
|
||||
suspendVmsModalTitle: 'Suspend VM{vms, plural, one {} other {s}}',
|
||||
suspendVmsModalMessage:
|
||||
'Are you sure you want to suspend {vms, number} VM{vms, plural, one {} other {s}}?',
|
||||
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:',
|
||||
@@ -1114,29 +1202,44 @@ 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',
|
||||
deleteSchedulesModalTitle:
|
||||
'Delete schedule{nSchedules, plural, one {} other {s}}',
|
||||
deleteSchedulesModalMessage:
|
||||
'Are you sure you want to delete {nSchedules, number} schedule{nSchedules, plural, one {} other {s}}?',
|
||||
revertVmModalTitle: 'Revert your VM',
|
||||
deleteVifsModalTitle: 'Delete VIF{nVifs, plural, one {} other {s}}',
|
||||
deleteVifsModalMessage:
|
||||
'Are you sure you want to delete {nVifs, number} VIF{nVifs, plural, one {} other {s}}?',
|
||||
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',
|
||||
@@ -1147,7 +1250,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',
|
||||
@@ -1158,13 +1262,14 @@ var messages = {
|
||||
serverAddFailed: 'Adding server failed',
|
||||
serverStatus: 'Status',
|
||||
serverConnectionFailed: 'Connection failed. Click for more information.',
|
||||
serverConnecting: 'Connecting...',
|
||||
serverConnecting: 'Connecting…',
|
||||
serverConnected: 'Connected',
|
||||
serverDisconnected: 'Disconnected',
|
||||
serverAuthFailed: 'Authentication error',
|
||||
serverUnknownError: 'Unknown error',
|
||||
serverSelfSignedCertError: 'Invalid self-signed certificate',
|
||||
serverSelfSignedCertQuestion: 'Do you want to accept self-signed certificate for this server even though it would decrease security?',
|
||||
serverSelfSignedCertQuestion:
|
||||
'Do you want to accept self-signed certificate for this server even though it would decrease security?',
|
||||
|
||||
// ----- Copy VM -----
|
||||
copyVm: 'Copy VM',
|
||||
@@ -1180,18 +1285,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',
|
||||
@@ -1244,7 +1352,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',
|
||||
@@ -1258,34 +1367,45 @@ var messages = {
|
||||
proxySettingsPasswordPlaceHolder: 'Password',
|
||||
updateRegistrationEmailPlaceHolder: 'Your email account',
|
||||
updateRegistrationPasswordPlaceHolder: 'Your password',
|
||||
updaterTroubleshootingLink: 'Troubleshooting documentation',
|
||||
update: 'Update',
|
||||
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',
|
||||
@@ -1294,6 +1414,9 @@ var messages = {
|
||||
disconnectPifConfirm: 'Are you sure you want to disconnect this PIF?',
|
||||
deletePif: 'Delete PIF',
|
||||
deletePifConfirm: 'Are you sure you want to delete this PIF?',
|
||||
deletePifs: 'Delete PIFs',
|
||||
deletePifsConfirm:
|
||||
'Are you sure you want to delete {nPifs, number} PIF{nPifs, plural, one {} other {s}}?',
|
||||
pifConnected: 'Connected',
|
||||
pifDisconnected: 'Disconnected',
|
||||
pifPhysicallyConnected: 'Physically connected',
|
||||
@@ -1307,15 +1430,18 @@ 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',
|
||||
deleteSshKey: 'Delete',
|
||||
deleteSshKeys: 'Delete selected SSH keys',
|
||||
noSshKeys: 'No SSH keys',
|
||||
newSshKeyModalTitle: 'New SSH key',
|
||||
sshKeyErrorTitle: 'Invalid key',
|
||||
@@ -1323,7 +1449,11 @@ 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}?',
|
||||
deleteSshKeysConfirm: 'Delete SSH key{nKeys, plural, one {} other {s}}',
|
||||
deleteSshKeysConfirmMessage:
|
||||
'Are you sure you want to delete {nKeys, number} SSH key{nKeys, plural, one {} other {s}}?',
|
||||
|
||||
// ----- Usage -----
|
||||
others: 'Others',
|
||||
@@ -1340,6 +1470,10 @@ var messages = {
|
||||
logNoStackTrace: 'No stack trace',
|
||||
logNoParams: 'No params',
|
||||
logDelete: 'Delete log',
|
||||
logsDelete: 'Delete logs',
|
||||
logDeleteMultiple: 'Delete log{nLogs, plural, one {} other {s}}',
|
||||
logDeleteMultipleMessage:
|
||||
'Are you sure you want to delete {nLogs, number} log{nLogs, plural, one {} other {s}}?',
|
||||
logDeleteAll: 'Delete all logs',
|
||||
logDeleteAllTitle: 'Delete all logs',
|
||||
logDeleteAllMessage: 'Are you sure you want to delete all the logs?',
|
||||
@@ -1364,19 +1498,25 @@ var messages = {
|
||||
// ----- Shortcuts -----
|
||||
shortcutModalTitle: 'Keyboard shortcuts',
|
||||
shortcut_XoApp: 'Global',
|
||||
shortcut_GO_TO_HOSTS: 'Go to hosts list',
|
||||
shortcut_GO_TO_POOLS: 'Go to pools list',
|
||||
shortcut_GO_TO_VMS: 'Go to VMs list',
|
||||
shortcut_GO_TO_SRS: 'Go to SRs list',
|
||||
shortcut_CREATE_VM: 'Create a new VM',
|
||||
shortcut_UNFOCUS: 'Unfocus field',
|
||||
shortcut_HELP: 'Show shortcuts key bindings',
|
||||
shortcut_XoApp_GO_TO_HOSTS: 'Go to hosts list',
|
||||
shortcut_XoApp_GO_TO_POOLS: 'Go to pools list',
|
||||
shortcut_XoApp_GO_TO_VMS: 'Go to VMs list',
|
||||
shortcut_XoApp_GO_TO_SRS: 'Go to SRs list',
|
||||
shortcut_XoApp_CREATE_VM: 'Create a new VM',
|
||||
shortcut_XoApp_UNFOCUS: 'Unfocus field',
|
||||
shortcut_XoApp_HELP: 'Show shortcuts key bindings',
|
||||
shortcut_Home: 'Home',
|
||||
shortcut_SEARCH: 'Focus search bar',
|
||||
shortcut_NAV_DOWN: 'Next item',
|
||||
shortcut_NAV_UP: 'Previous item',
|
||||
shortcut_SELECT: 'Select item',
|
||||
shortcut_JUMP_INTO: 'Open',
|
||||
shortcut_Home_SEARCH: 'Focus search bar',
|
||||
shortcut_Home_NAV_DOWN: 'Next item',
|
||||
shortcut_Home_NAV_UP: 'Previous item',
|
||||
shortcut_Home_SELECT: 'Select item',
|
||||
shortcut_Home_JUMP_INTO: 'Open',
|
||||
shortcut_SortedTable: 'Supported tables',
|
||||
shortcut_SortedTable_SEARCH: 'Focus the table search bar',
|
||||
shortcut_SortedTable_NAV_DOWN: 'Next item',
|
||||
shortcut_SortedTable_NAV_UP: 'Previous item',
|
||||
shortcut_SortedTable_SELECT: 'Select item',
|
||||
shortcut_SortedTable_ROW_ACTION: 'Action',
|
||||
|
||||
// ----- Settings/ACLs -----
|
||||
settingsAclsButtonTooltipVM: 'VM',
|
||||
@@ -1387,7 +1527,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',
|
||||
@@ -1399,14 +1540,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',
|
||||
@@ -1416,6 +1562,8 @@ var messages = {
|
||||
xosanSrTitle: 'Xen Orchestra SAN SR',
|
||||
xosanAvailableSrsTitle: 'Select local SRs (lvm)',
|
||||
xosanSuggestions: 'Suggestions',
|
||||
xosanDisperseWarning:
|
||||
'Warning: using disperse layout is not recommended right now. Please read {link}.',
|
||||
xosanName: 'Name',
|
||||
xosanHost: 'Host',
|
||||
xosanHosts: 'Connected Hosts',
|
||||
@@ -1423,9 +1571,12 @@ var messages = {
|
||||
xosanVolumeId: 'Volume ID',
|
||||
xosanSize: 'Size',
|
||||
xosanUsedSpace: 'Used space',
|
||||
xosanLicense: 'License',
|
||||
xosanMultipleLicenses: 'This XOSAN has more than 1 license!',
|
||||
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}',
|
||||
@@ -1437,33 +1588,53 @@ var messages = {
|
||||
xosanDiskLossLegend: '* Can fail without data loss',
|
||||
xosanCreate: 'Create',
|
||||
xosanAdd: 'Add',
|
||||
xosanInstalling: 'Installing XOSAN. Please wait...',
|
||||
xosanInstalling: 'Installing XOSAN. Please wait…',
|
||||
xosanCommunity: 'No XOSAN available for Community Edition',
|
||||
xosanNew: 'New',
|
||||
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.',
|
||||
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.",
|
||||
xosanVlan: 'VLAN',
|
||||
xosanNoSrs: 'No XOSAN found',
|
||||
xosanPbdsDetached: 'Some SRs are detached from the XOSAN',
|
||||
xosanBadStatus: 'Something is wrong with: {badStatuses}',
|
||||
xosanRunning: 'Running',
|
||||
xosanDelete: 'Delete XOSAN',
|
||||
xosanFixIssue: 'Fix',
|
||||
xosanCreatingOn: 'Creating XOSAN on {pool}',
|
||||
xosanState_configuringNetwork: 'Configuring network…',
|
||||
xosanState_importingVm: 'Importing VM…',
|
||||
xosanState_copyingVms: 'Copying VMs…',
|
||||
xosanState_configuringVms: 'Configuring VMs…',
|
||||
xosanState_configuringGluster: 'Configuring gluster…',
|
||||
xosanState_creatingSr: 'Creating SR…',
|
||||
xosanState_scanningSr: 'Scanning SR…',
|
||||
// Pack download modal
|
||||
xosanInstallCloudPlugin: 'Install cloud plugin first',
|
||||
xosanLoadCloudPlugin: 'Load cloud plugin first',
|
||||
xosanLoading: 'Loading...',
|
||||
xosanRegister: 'Register your appliance first',
|
||||
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.',
|
||||
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',
|
||||
xosanFilesNeedHealing: 'Some XOSAN Virtual Machines have files needing healing',
|
||||
xosanFilesNeedingHealing: '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',
|
||||
xosanReplace: 'Replace',
|
||||
xosanReplace: 'Replace…',
|
||||
xosanOnSameVm: 'On same VM',
|
||||
xosanBrickName: 'Brick name',
|
||||
xosanBrickUuid: 'Brick UUID',
|
||||
xosanBrickSize: 'Brick size',
|
||||
xosanMemorySize: 'Memory size',
|
||||
@@ -1486,13 +1657,60 @@ var messages = {
|
||||
xosanRun: 'Run',
|
||||
xosanRemove: 'Remove',
|
||||
xosanVolume: 'Volume',
|
||||
xosanVolumeOptions: 'Volume options'
|
||||
xosanVolumeOptions: 'Volume options',
|
||||
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.',
|
||||
|
||||
// Licenses
|
||||
licensesTitle: 'Licenses',
|
||||
xosanUnregisteredDisclaimer:
|
||||
'You are not registered and therefore will not be able to create or manage your XOSAN SRs. {link}',
|
||||
xosanSourcesDisclaimer:
|
||||
'In order to create a XOSAN SR, you need to use the Xen Orchestra Appliance and buy a XOSAN license on {link}.',
|
||||
registerNow: 'Register now!',
|
||||
licensesUnregisteredDisclaimer:
|
||||
'You need to register your appliance to manage your licenses.',
|
||||
licenseProduct: 'Product',
|
||||
licenseBoundObject: 'Attached to',
|
||||
licensePurchaser: 'Purchaser',
|
||||
licenseExpires: 'Expires',
|
||||
licensePurchaserYou: 'You',
|
||||
productSupport: 'Support',
|
||||
licenseNotBoundXosan: 'No XOSAN attached',
|
||||
licenseBoundUnknownXosan: 'License attached to an unknown XOSAN',
|
||||
licensesManage: 'Manage the licenses',
|
||||
newLicense: 'New license',
|
||||
refreshLicenses: 'Refresh',
|
||||
xosanLicenseRestricted: 'Limited size because XOSAN is in trial',
|
||||
xosanAdminNoLicenseDisclaimer:
|
||||
'You need a license on this SR to manage the XOSAN.',
|
||||
xosanAdminExpiredLicenseDisclaimer:
|
||||
'Your XOSAN license has expired. You can still use the SR but cannot administrate it anymore.',
|
||||
xosanCheckLicenseError: 'Could not check the license on this XOSAN SR',
|
||||
xosanGetLicensesError: 'Could not fetch licenses',
|
||||
xosanLicenseHasExpired: 'License has expired.',
|
||||
xosanLicenseExpiresDate: 'License expires on {date}.',
|
||||
xosanUpdateLicenseMessage: 'Update the license now!',
|
||||
xosanUnknownSr: 'Unknown XOSAN SR.',
|
||||
contactUs: 'Contact us!',
|
||||
xosanNoLicense: 'No license.',
|
||||
xosanUnlockNow: 'Unlock now!',
|
||||
xosanBetaOverMessage:
|
||||
'XOSAN Beta is over. You may now delete and recreate previous existing XOSAN SRs.',
|
||||
selectLicense: 'Select a license',
|
||||
bindLicense: 'Bind license',
|
||||
expiresOn: 'expires on {date}',
|
||||
xosanInstallXoaPlugin: 'Install XOA plugin first',
|
||||
xosanLoadXoaPlugin: 'Load XOA plugin first',
|
||||
}
|
||||
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,15 +15,16 @@ const _IGNORED_TAGNAMES = {
|
||||
A: true,
|
||||
BUTTON: true,
|
||||
INPUT: true,
|
||||
SELECT: true
|
||||
SELECT: true,
|
||||
}
|
||||
|
||||
@propTypes({
|
||||
tagName: propTypes.string
|
||||
className: propTypes.string,
|
||||
tagName: propTypes.string,
|
||||
})
|
||||
export class BlockLink extends Component {
|
||||
static contextTypes = {
|
||||
router: routerShape
|
||||
router: routerShape,
|
||||
}
|
||||
|
||||
_style = { cursor: 'pointer' }
|
||||
@@ -54,10 +55,11 @@ export class BlockLink extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, tagName = 'div' } = this.props
|
||||
const { children, tagName = 'div', className } = this.props
|
||||
const Component = tagName
|
||||
return (
|
||||
<Component
|
||||
className={className}
|
||||
ref={this._addAuxClickListener}
|
||||
style={this._style}
|
||||
onClickCapture={this._onClickCapture}
|
||||
|
||||
@@ -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,29 @@ 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 = props => {
|
||||
const { collection } = props
|
||||
|
||||
if (collection == null) {
|
||||
return <img src='assets/loading.svg' alt='loading' />
|
||||
}
|
||||
|
||||
if (isEmpty(collection)) {
|
||||
return <p>{props.emptyMessage}</p>
|
||||
}
|
||||
|
||||
const { children, component: Component, ...otherProps } = props
|
||||
return children !== undefined ? (
|
||||
children(otherProps)
|
||||
) : (
|
||||
<Component {...otherProps} />
|
||||
)
|
||||
}
|
||||
|
||||
propTypes(NoObjects)({
|
||||
children: propTypes.node.isRequired,
|
||||
collection: propTypes.oneOfType([
|
||||
propTypes.array,
|
||||
propTypes.object
|
||||
]).isRequired,
|
||||
emptyMessage: propTypes.node.isRequired
|
||||
children: propTypes.func,
|
||||
collection: propTypes.oneOfType([propTypes.array, propTypes.object]),
|
||||
component: propTypes.func,
|
||||
emptyMessage: propTypes.node.isRequired,
|
||||
})
|
||||
export default NoObjects
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import _ from 'intl'
|
||||
import ButtonLink from 'button-link'
|
||||
import Icon from 'icon'
|
||||
import React, { Component } from 'react'
|
||||
import ReactNotify from 'react-notify'
|
||||
import { connectStore } from 'utils'
|
||||
import { isAdmin } from 'selectors'
|
||||
|
||||
let instance
|
||||
|
||||
@@ -7,6 +12,9 @@ export let error
|
||||
export let info
|
||||
export let success
|
||||
|
||||
@connectStore({
|
||||
isAdmin,
|
||||
})
|
||||
export class Notification extends Component {
|
||||
componentDidMount () {
|
||||
if (instance) {
|
||||
@@ -25,15 +33,38 @@ 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,
|
||||
this.props.isAdmin ? (
|
||||
<div>
|
||||
<div>{body}</div>
|
||||
<ButtonLink
|
||||
btnStyle='danger'
|
||||
className='mt-1'
|
||||
size='small'
|
||||
to='/settings/logs'
|
||||
>
|
||||
<Icon icon='logs' /> {_('showLogs')}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
) : (
|
||||
body
|
||||
),
|
||||
6e3
|
||||
)
|
||||
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 () {
|
||||
|
||||
125
src/common/pagination.js
Normal file
125
src/common/pagination.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const PageItem = ({ active, children, disabled, onClick, value }) =>
|
||||
active ? (
|
||||
<li className='active page-item'>
|
||||
<span className='page-link'>{children}</span>
|
||||
</li>
|
||||
) : disabled ? (
|
||||
<li className='disabled page-item'>
|
||||
<span className='page-link'>{children}</span>
|
||||
</li>
|
||||
) : (
|
||||
<li className='page-item'>
|
||||
<a className='page-link' href='#' onClick={onClick} data-value={value}>
|
||||
{children}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
|
||||
export default class Pagination extends React.PureComponent {
|
||||
static defaultProps = {
|
||||
ellipsis: true,
|
||||
maxButtons: 7,
|
||||
next: true,
|
||||
prev: true,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
ariaLabel: PropTypes.string,
|
||||
ellipsis: PropTypes.bool,
|
||||
maxButtons: PropTypes.number,
|
||||
next: PropTypes.bool,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
pages: PropTypes.number.isRequired,
|
||||
prev: PropTypes.bool,
|
||||
value: PropTypes.number.isRequired,
|
||||
}
|
||||
|
||||
_onClick (event) {
|
||||
event.preventDefault()
|
||||
this.props.onChange(+event.currentTarget.dataset.value)
|
||||
}
|
||||
_onClick = this._onClick.bind(this)
|
||||
|
||||
render () {
|
||||
const {
|
||||
ariaLabel,
|
||||
ellipsis,
|
||||
maxButtons,
|
||||
next,
|
||||
pages,
|
||||
prev,
|
||||
value,
|
||||
} = this.props
|
||||
const onClick = this._onClick
|
||||
|
||||
let min, max
|
||||
if (pages <= maxButtons) {
|
||||
min = 1
|
||||
max = pages
|
||||
} else {
|
||||
min = Math.max(
|
||||
1,
|
||||
Math.min(value - Math.floor(maxButtons / 2), pages - maxButtons + 1)
|
||||
)
|
||||
max = min + maxButtons - 1
|
||||
}
|
||||
|
||||
const pageButtons = []
|
||||
if (ellipsis && min !== 1) {
|
||||
pageButtons.push(
|
||||
<PageItem disabled key='firstEllipsis'>
|
||||
…
|
||||
</PageItem>
|
||||
)
|
||||
}
|
||||
for (let page = min; page <= max; ++page) {
|
||||
pageButtons.push(
|
||||
<PageItem
|
||||
active={page === value}
|
||||
key={page}
|
||||
onClick={onClick}
|
||||
value={page}
|
||||
>
|
||||
{page}
|
||||
</PageItem>
|
||||
)
|
||||
}
|
||||
if (ellipsis && max !== pages) {
|
||||
pageButtons.push(
|
||||
<PageItem disabled key='lastEllipsis'>
|
||||
…
|
||||
</PageItem>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<nav aria-label={ariaLabel}>
|
||||
<ul className='pagination'>
|
||||
{prev && (
|
||||
<PageItem
|
||||
aria-label='Previous'
|
||||
disabled={value === 1}
|
||||
onClick={onClick}
|
||||
value={value - 1}
|
||||
>
|
||||
‹
|
||||
</PageItem>
|
||||
)}
|
||||
{pageButtons}
|
||||
{next && (
|
||||
<PageItem
|
||||
aria-label='Next'
|
||||
disabled={value === pages}
|
||||
onClick={onClick}
|
||||
value={value + 1}
|
||||
>
|
||||
›
|
||||
</PageItem>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,17 @@
|
||||
import assign from 'lodash/assign'
|
||||
import { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// Deprecated because :
|
||||
// - unnecessary
|
||||
// - not standard in the React ecosystem
|
||||
if (__DEV__) {
|
||||
console.warn(`DEPRECATED: use prop-types directly:
|
||||
class MyComponent extends React.Component {
|
||||
static propTypes = {
|
||||
foo: PropTypes.string.isRequired
|
||||
}
|
||||
}`)
|
||||
}
|
||||
|
||||
// Decorators to help declaring properties and context types on React
|
||||
// components without using the tedious static properties syntax.
|
||||
@@ -16,13 +28,13 @@ const propTypes = (propTypes, contextTypes) => target => {
|
||||
if (propTypes !== undefined) {
|
||||
target.propTypes = {
|
||||
...target.propTypes,
|
||||
...propTypes
|
||||
...propTypes,
|
||||
}
|
||||
}
|
||||
if (contextTypes !== undefined) {
|
||||
target.contextTypes = {
|
||||
...target.contextTypes,
|
||||
...contextTypes
|
||||
...contextTypes,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
src/common/react-novnc.js
vendored
55
src/common/react-novnc.js
vendored
@@ -1,19 +1,17 @@
|
||||
import React, { Component } from 'react'
|
||||
import RFB from '@nraynaud/novnc/lib/rfb'
|
||||
import URL from 'url-parse'
|
||||
import { createBackoff } from 'jsonrpc-websocket-client'
|
||||
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 PROTOCOL_ALIASES = {
|
||||
'http:': 'ws:',
|
||||
'https:': 'wss:'
|
||||
'https:': 'wss:',
|
||||
}
|
||||
const fixProtocol = url => {
|
||||
const protocol = PROTOCOL_ALIASES[url.protocol]
|
||||
@@ -24,7 +22,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 +44,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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,25 +82,27 @@ export default class NoVnc extends Component {
|
||||
return
|
||||
}
|
||||
|
||||
const url = parseRelativeUrl(this.props.url)
|
||||
const url = new URL(this.props.url)
|
||||
fixProtocol(url)
|
||||
|
||||
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
|
||||
//
|
||||
// a leading slassh will be added by noVNC
|
||||
const clippedPath = url.path.replace(/^\/+/, '')
|
||||
const clippedPath = url.pathname.replace(/^\/+/, '')
|
||||
|
||||
// a port is required
|
||||
//
|
||||
@@ -152,13 +155,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,88 +1,95 @@
|
||||
import _ from 'intl'
|
||||
import React from 'react'
|
||||
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.
|
||||
export const PoolObjectItem = propTypes({
|
||||
object: propTypes.object.isRequired
|
||||
})(connectStore(() => {
|
||||
const getPool = createGetObject(
|
||||
(_, props) => props.object.$pool
|
||||
)
|
||||
const PoolObjectItem = propTypes({
|
||||
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.
|
||||
export const SrItem = propTypes({
|
||||
sr: propTypes.object.isRequired
|
||||
})(connectStore(() => {
|
||||
const getContainer = createGetObject(
|
||||
(_, props) => props.sr.$container
|
||||
)
|
||||
const SrItem = propTypes({
|
||||
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.
|
||||
export const VmItem = propTypes({
|
||||
vm: propTypes.object.isRequired
|
||||
})(connectStore(() => {
|
||||
const getContainer = createGetObject(
|
||||
(_, props) => props.vm.$container
|
||||
)
|
||||
const VmItem = propTypes({
|
||||
vm: propTypes.object.isRequired,
|
||||
})(
|
||||
connectStore(() => {
|
||||
const getContainer = createGetObject((_, props) => props.vm.$container)
|
||||
|
||||
return (state, props) => ({
|
||||
container: getContainer(state, 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>
|
||||
))
|
||||
)
|
||||
|
||||
const VgpuItem = connectStore(() => ({
|
||||
vgpuType: createGetObject((_, props) => props.vgpu.vgpuType),
|
||||
}))(({ vgpu, vgpuType }) => (
|
||||
<span>
|
||||
<Icon icon={`vm-${vm.power_state.toLowerCase()}`} /> {vm.name_label || vm.id}
|
||||
{container && ` (${container.name_label || container.id})`}
|
||||
<Icon icon='vgpu' /> {vgpuType.modelName}
|
||||
</span>
|
||||
)))
|
||||
))
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -98,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}
|
||||
@@ -123,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>
|
||||
}
|
||||
@@ -139,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>
|
||||
),
|
||||
|
||||
@@ -156,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>
|
||||
),
|
||||
|
||||
@@ -174,16 +180,38 @@ const xoItemToRender = {
|
||||
<span>
|
||||
<Icon icon='tag' /> {tag.value}
|
||||
</span>
|
||||
)
|
||||
),
|
||||
|
||||
// GPUs
|
||||
|
||||
vgpu: vgpu => <VgpuItem vgpu={vgpu} />,
|
||||
|
||||
vgpuType: type => (
|
||||
<span>
|
||||
<Icon icon='gpu' /> {type.modelName} ({type.vendorName}){' '}
|
||||
{type.maxResolutionX}x{type.maxResolutionY}
|
||||
</span>
|
||||
),
|
||||
|
||||
gpuGroup: group => (
|
||||
<span>
|
||||
{startsWith(group.name_label, 'Group of ')
|
||||
? group.name_label.slice(9)
|
||||
: 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) {
|
||||
@@ -218,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,20 +55,16 @@ 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 = []
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
hours[i] = []
|
||||
|
||||
for (let j = 0; j < 8; j++) {
|
||||
hours[i].push(8 * i + j)
|
||||
for (let j = 0; j < 6; j++) {
|
||||
hours[i].push(6 * i + j)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 () {
|
||||
@@ -147,6 +138,13 @@ export class SchedulePreview extends Component {
|
||||
const { value } = this.state
|
||||
|
||||
const cronSched = later.parse.cron(cronPattern)
|
||||
|
||||
// Due to implementation, the range used for months is 0-11
|
||||
// instead of 1-12
|
||||
forEach(cronSched.schedules[0].M, (v, i, a) => {
|
||||
a[i] = v + 1
|
||||
})
|
||||
|
||||
const dates = later.schedule(cronSched).next(value)
|
||||
|
||||
return (
|
||||
@@ -155,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) => (
|
||||
@@ -176,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 = () => {
|
||||
@@ -187,7 +190,11 @@ class ToggleTd extends Component {
|
||||
render () {
|
||||
const { props } = this
|
||||
return (
|
||||
<td style={CLICKABLE} className={props.value ? 'table-success' : ''} onClick={this._onClick}>
|
||||
<td
|
||||
className={classNames('text-xs-center', props.value && 'table-success')}
|
||||
onClick={this._onClick}
|
||||
style={CLICKABLE}
|
||||
>
|
||||
{props.children}
|
||||
</td>
|
||||
)
|
||||
@@ -201,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 = () => {
|
||||
@@ -234,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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,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 => {
|
||||
@@ -315,7 +317,7 @@ class TimePicker extends Component {
|
||||
this.setState({
|
||||
periodic,
|
||||
tableValue: periodic ? tableValue : newValue,
|
||||
rangeValue: periodic ? newValue : rangeValue
|
||||
rangeValue: periodic ? newValue : rangeValue,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -334,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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,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) {
|
||||
@@ -410,7 +422,7 @@ class DayPicker extends Component {
|
||||
}
|
||||
|
||||
_setWeekDayMode = weekDayMode => {
|
||||
this.props.onChange([ '*', '*' ])
|
||||
this.props.onChange(['*', '*'])
|
||||
this.setState({ weekDayMode })
|
||||
}
|
||||
|
||||
@@ -419,7 +431,7 @@ class DayPicker extends Component {
|
||||
|
||||
this.props.onChange([
|
||||
isMonthDayPattern ? cron : '*',
|
||||
isMonthDayPattern ? '*' : cron
|
||||
isMonthDayPattern ? '*' : cron,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -428,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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,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) {
|
||||
@@ -470,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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -504,7 +529,7 @@ export default class Scheduler extends Component {
|
||||
return (
|
||||
<div className='card-block'>
|
||||
<Row>
|
||||
<Col mediumSize={6}>
|
||||
<Col largeSize={6}>
|
||||
<TimePicker
|
||||
labelId='Month'
|
||||
optionRenderer={getMonthName}
|
||||
@@ -513,13 +538,17 @@ export default class Scheduler extends Component {
|
||||
range={MONTHS_RANGE}
|
||||
value={cronPatternArr[PICKTIME_TO_ID['month']]}
|
||||
/>
|
||||
</Col>
|
||||
<Col largeSize={6}>
|
||||
<DayPicker
|
||||
onChange={this._dayChange}
|
||||
monthDayPattern={cronPatternArr[PICKTIME_TO_ID['monthDay']]}
|
||||
weekDayPattern={cronPatternArr[PICKTIME_TO_ID['weekDay']]}
|
||||
/>
|
||||
</Col>
|
||||
<Col mediumSize={6}>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col largeSize={6}>
|
||||
<TimePicker
|
||||
labelId='Hour'
|
||||
options={HOURS}
|
||||
@@ -527,6 +556,8 @@ export default class Scheduler extends Component {
|
||||
onChange={this._hourChange}
|
||||
value={cronPatternArr[PICKTIME_TO_ID['hour']]}
|
||||
/>
|
||||
</Col>
|
||||
<Col largeSize={6}>
|
||||
<TimePicker
|
||||
labelId='Minute'
|
||||
options={MINS}
|
||||
@@ -539,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 }
|
||||
|
||||
@@ -10,3 +10,8 @@
|
||||
.clickableRow {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
outline: 2px solid #366e98;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import DockMonitor from 'redux-devtools-dock-monitor'
|
||||
import LogMonitor from 'redux-devtools-log-monitor'
|
||||
import React from 'react'
|
||||
import { createDevTools } from 'redux-devtools'
|
||||
|
||||
export default createDevTools(
|
||||
<DockMonitor
|
||||
changePositionKey='ctrl-q'
|
||||
toggleVisibilityKey='ctrl-h'
|
||||
>
|
||||
<LogMonitor />
|
||||
</DockMonitor>
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = false // process.env.NODE_ENV !== 'production' && require('./dev-tools.dev')
|
||||
@@ -1,27 +1,13 @@
|
||||
import reduxThunk from 'redux-thunk'
|
||||
import {
|
||||
applyMiddleware,
|
||||
combineReducers,
|
||||
compose,
|
||||
createStore
|
||||
} from 'redux'
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux'
|
||||
|
||||
import { connectStore as connectXo } from '../xo'
|
||||
|
||||
import DevTools from './dev-tools'
|
||||
import reducer from './reducer'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const enhancers = [
|
||||
applyMiddleware(reduxThunk)
|
||||
]
|
||||
DevTools && enhancers.push(DevTools.instrument())
|
||||
|
||||
const store = createStore(
|
||||
combineReducers(reducer),
|
||||
compose.apply(null, enhancers)
|
||||
)
|
||||
const store = createStore(combineReducers(reducer), applyMiddleware(reduxThunk))
|
||||
|
||||
connectXo(store)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'intl'
|
||||
import classNames from 'classnames'
|
||||
import React, { PropTypes, cloneElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { cloneElement } from 'react'
|
||||
import sum from 'lodash/sum'
|
||||
|
||||
import Tooltip from '../tooltip'
|
||||
@@ -12,20 +13,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 +38,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 +47,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,
|
||||
}
|
||||
|
||||
@@ -1,42 +1,49 @@
|
||||
import escapeRegExp from 'lodash/escapeRegExp'
|
||||
import every from 'lodash/every'
|
||||
import forEach from 'lodash/forEach'
|
||||
import getStream from 'get-stream'
|
||||
import humanFormat from 'human-format'
|
||||
import isArray from 'lodash/isArray'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import isFunction from 'lodash/isFunction'
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import isString from 'lodash/isString'
|
||||
import join from 'lodash/join'
|
||||
import keys from 'lodash/keys'
|
||||
import map from 'lodash/map'
|
||||
import mapValues from 'lodash/mapValues'
|
||||
import React from 'react'
|
||||
import ReadableStream from 'readable-stream'
|
||||
import replace from 'lodash/replace'
|
||||
import sample from 'lodash/sample'
|
||||
import startsWith from 'lodash/startsWith'
|
||||
import { connect } from 'react-redux'
|
||||
import { FormattedDate } from 'react-intl'
|
||||
import {
|
||||
clone,
|
||||
escapeRegExp,
|
||||
every,
|
||||
forEach,
|
||||
isArray,
|
||||
isEmpty,
|
||||
isFunction,
|
||||
isPlainObject,
|
||||
isString,
|
||||
join,
|
||||
keys,
|
||||
map,
|
||||
mapValues,
|
||||
replace,
|
||||
sample,
|
||||
startsWith,
|
||||
} from 'lodash'
|
||||
|
||||
import _ from './intl'
|
||||
import * as actions from './store/actions'
|
||||
import BaseComponent from './base-component'
|
||||
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 addSubscriptions from './add-subscriptions'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
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) => {
|
||||
@@ -53,83 +60,9 @@ export const propsEqual = (o1, o2, props) => {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
// `subscriptions` can be a function if we want to ensure that the subscription
|
||||
// callbacks have been correctly initialized when there are circular dependencies
|
||||
export const addSubscriptions = subscriptions => Component => {
|
||||
class SubscriptionWrapper extends BaseComponent {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this._unsubscribes = null
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this._unsubscribes = map(
|
||||
isFunction(subscriptions)
|
||||
? subscriptions(this.props)
|
||||
: subscriptions,
|
||||
(subscribe, prop) =>
|
||||
subscribe(value => this._setState({ [prop]: value }))
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._setState = this.setState
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
forEach(this._unsubscribes, unsubscribe => unsubscribe())
|
||||
this._unsubscribes = null
|
||||
delete this._setState
|
||||
}
|
||||
|
||||
_setState (nextState) {
|
||||
this.state = { ...this.state, nextState }
|
||||
}
|
||||
|
||||
render () {
|
||||
return <Component
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
return SubscriptionWrapper
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const checkPropsState = (propsNames, stateNames) => Component => {
|
||||
const nProps = propsNames && propsNames.length
|
||||
const nState = stateNames && stateNames.length
|
||||
|
||||
Component.prototype.shouldComponentUpdate = (newProps, newState) => {
|
||||
const { props, state } = this
|
||||
|
||||
for (let i = 0; i < nProps; ++i) {
|
||||
const name = propsNames[i]
|
||||
if (newProps[name] !== props[name]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < nState; ++i) {
|
||||
const name = stateNames[i]
|
||||
if (newState[name] !== state[name]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Component
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const _normalizeMapStateToProps = mapper => {
|
||||
if (isFunction(mapper)) {
|
||||
let factoryOrMapper = (state, props) => {
|
||||
const factoryOrMapper = (state, props) => {
|
||||
const result = mapper(state, props)
|
||||
|
||||
// Properly handles factory pattern.
|
||||
@@ -145,7 +78,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)
|
||||
}
|
||||
}
|
||||
@@ -181,7 +115,7 @@ export const connectStore = (mapStateToProps, opts = {}) => {
|
||||
},
|
||||
set (value) {
|
||||
this.getWrappedInstance().value = value
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -195,19 +129,6 @@ export { default as Debug } from './debug'
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Returns the first defined (non-undefined) value.
|
||||
export const firstDefined = function () {
|
||||
const n = arguments.length
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const arg = arguments[i]
|
||||
if (arg !== undefined) {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Returns the current XOA Plan or the Plan name if number given
|
||||
export const getXoaPlan = plan => {
|
||||
switch (plan || +process.env.XOA_PLAN) {
|
||||
@@ -240,43 +161,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 formatSizeRaw = bytes => humanFormat.raw(bytes, { scale: 'binary', unit: 'B' })
|
||||
export const formatSizeShort = bytes =>
|
||||
humanFormat(bytes, { scale: 'binary', unit: 'B', decimals: 0 })
|
||||
|
||||
export const formatSpeed = (bytes, milliseconds) => humanFormat(
|
||||
bytes * 1e3 / milliseconds,
|
||||
{ scale: 'binary', unit: 'B/s' }
|
||||
)
|
||||
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 parseSize = size => {
|
||||
let bytes = humanFormat.parse.raw(size, { scale: 'binary' })
|
||||
@@ -318,14 +245,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)
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +273,7 @@ export const routes = (indexRoute, childRoutes) => target => {
|
||||
|
||||
target.route = {
|
||||
indexRoute,
|
||||
childRoutes
|
||||
childRoutes,
|
||||
}
|
||||
|
||||
return target
|
||||
@@ -362,11 +289,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
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
@@ -382,7 +305,7 @@ export const resolveResourceSet = resourceSet => {
|
||||
...attrs,
|
||||
missingObjects: [],
|
||||
objectsByType: resolvedObjects,
|
||||
ipPools
|
||||
ipPools,
|
||||
}
|
||||
const state = store.getState()
|
||||
|
||||
@@ -398,7 +321,7 @@ export const resolveResourceSet = resourceSet => {
|
||||
const { type } = object
|
||||
|
||||
if (!resolvedObjects[type]) {
|
||||
resolvedObjects[type] = [ object ]
|
||||
resolvedObjects[type] = [object]
|
||||
} else {
|
||||
resolvedObjects[type].push(object)
|
||||
}
|
||||
@@ -430,10 +353,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
|
||||
})
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
@@ -472,9 +396,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) {
|
||||
@@ -493,28 +415,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('.')
|
||||
@@ -531,8 +454,7 @@ export const compareVersions = makeNiceCompare((v1, v2) => {
|
||||
return 0
|
||||
})
|
||||
|
||||
export const isXosanPack = ({ name }) =>
|
||||
startsWith(name, 'XOSAN')
|
||||
export const isXosanPack = ({ name }) => startsWith(name, 'XOSAN')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -544,7 +466,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)
|
||||
}
|
||||
}
|
||||
@@ -565,3 +491,36 @@ export const generateReadableRandomString = (() => {
|
||||
return result.join('')
|
||||
}
|
||||
})()
|
||||
|
||||
export const cowSet = (object, path, value, depth = 0) => {
|
||||
if (depth >= path.length) {
|
||||
return value
|
||||
}
|
||||
|
||||
object = object != null ? clone(object) : {}
|
||||
const prop = path[depth]
|
||||
object[prop] = cowSet(object[prop], path, value, depth + 1)
|
||||
return object
|
||||
}
|
||||
|
||||
// Generates a function that returns a value between 0 and 1
|
||||
// This function returns an estimated progress value between 0 and 1
|
||||
// based on the elapsed time since the createFakeProgress call and
|
||||
// the given estimated duration d
|
||||
//
|
||||
// const getProgress = createFakeProgress(120)
|
||||
// setInterval(() => console.log(`Progress: ${getProgress() * 100} %`), 1000)
|
||||
export const createFakeProgress = (() => {
|
||||
const S = 0.95 // Progress value after d seconds
|
||||
return d => {
|
||||
const startTime = Date.now() / 1e3
|
||||
return () => {
|
||||
const x = Date.now() / 1e3 - startTime
|
||||
return -Math.exp(x * Math.log(1 - S) / d) + 1
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
export const ShortDate = ({ timestamp }) => (
|
||||
<FormattedDate value={timestamp} month='short' day='numeric' year='numeric' />
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
62
src/common/xo-defined.js
Normal file
62
src/common/xo-defined.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Usage:
|
||||
//
|
||||
// ```js
|
||||
// const httpProxy = defined(
|
||||
// process.env.HTTP_PROXY,
|
||||
// process.env.http_proxy
|
||||
// )
|
||||
//
|
||||
// const httpProxy = defined([
|
||||
// process.env.HTTP_PROXY,
|
||||
// process.env.http_proxy
|
||||
// ])
|
||||
// ```
|
||||
export default function defined () {
|
||||
let args = arguments
|
||||
let n = args.length
|
||||
if (n === 1) {
|
||||
args = arguments[0]
|
||||
n = args.length
|
||||
}
|
||||
|
||||
for (let i = 0; i < n; ++i) {
|
||||
let arg = arguments[i]
|
||||
if (typeof arg === 'function') {
|
||||
arg = get(arg)
|
||||
}
|
||||
if (arg !== undefined) {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// ```js
|
||||
// const friendName = get(() => props.user.friends[0].name)
|
||||
//
|
||||
// // this form can be used to avoid recreating functions:
|
||||
// const getFriendName = _ => _.friends[0].name
|
||||
// const friendName = get(getFriendName, props.user)
|
||||
// ```
|
||||
export const get = (accessor, arg) => {
|
||||
try {
|
||||
return accessor(arg)
|
||||
} catch (error) {
|
||||
if (!(error instanceof TypeError)) {
|
||||
// avoid hiding other errors
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// ```js
|
||||
// const httpAgent = ifDef(
|
||||
// process.env.HTTP_PROXY,
|
||||
// _ => new ProxyAgent(_)
|
||||
// )
|
||||
// ```
|
||||
export const ifDef = (value, thenFn) =>
|
||||
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.array.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.array.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.array.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.array.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.array.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,46 +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 VifSparkLines = propTypes({
|
||||
data: propTypes.object.isRequired
|
||||
export const NetworkSparkLines = propTypes({
|
||||
data: propTypes.object.isRequired,
|
||||
})(({ data, width = WIDTH, height = HEIGHT, strokeWidth = STROKE_WIDTH }) => {
|
||||
const { vifs } = data.stats
|
||||
const { pifs, vifs: ifs = pifs } = data.stats
|
||||
|
||||
if (!vifs) {
|
||||
return templateError
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparklines style={STYLE} data={computeObjectsAvg(vifs)} min={0} width={width} height={height}>
|
||||
<SparklinesLine style={{ strokeWidth, stroke: '#eca649', fill: '#eca649', fillOpacity: 0.5 }} color='#ffd633' />
|
||||
</Sparklines>
|
||||
)
|
||||
})
|
||||
|
||||
export const PifSparkLines = propTypes({
|
||||
data: propTypes.object.isRequired
|
||||
})(({ data, width = WIDTH, height = HEIGHT, strokeWidth = STROKE_WIDTH }) => {
|
||||
const { pifs } = data.stats
|
||||
|
||||
if (!pifs) {
|
||||
return templateError
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparklines style={STYLE} data={computeObjectsAvg(pifs)} 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
|
||||
|
||||
@@ -116,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>
|
||||
)
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user