Compare commits
1 Commits
fs-glob
...
backup-han
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30f6ca187d |
@@ -40,13 +40,6 @@ module.exports = {
|
||||
|
||||
'react/jsx-handler-names': 'off',
|
||||
|
||||
// disabled because not always relevant, we might reconsider in the future
|
||||
//
|
||||
// enabled by https://github.com/standard/eslint-config-standard/commit/319b177750899d4525eb1210686f6aca96190b2f
|
||||
//
|
||||
// example: https://github.com/vatesfr/xen-orchestra/blob/31ed3767c67044ca445658eb6b560718972402f2/packages/xen-api/src/index.js#L156-L157
|
||||
'lines-between-class-members': 'off',
|
||||
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'no-var': 'error',
|
||||
'node/no-extraneous-import': 'error',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"golike-defer": "^0.4.1",
|
||||
"xen-api": "^0.27.2"
|
||||
"xen-api": "^0.27.1"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import escapeRegExp from 'lodash/escapeRegExp'
|
||||
|
||||
const compileFragment = pattern =>
|
||||
new RegExp(
|
||||
`^${pattern
|
||||
.split('*')
|
||||
.map(escapeRegExp)
|
||||
.join('[^]*')}$`
|
||||
)
|
||||
|
||||
export function parseGlob(pattern) {
|
||||
const parts = []
|
||||
while (pattern.length !== 0) {
|
||||
const i = pattern.indexOf('*')
|
||||
if (i === -1) {
|
||||
parts.push(pattern)
|
||||
break
|
||||
}
|
||||
|
||||
let fragmentStart = pattern.lastIndexOf('/', i)
|
||||
if (fragmentStart === -1) {
|
||||
fragmentStart = 0
|
||||
} else {
|
||||
parts.push(pattern.slice(0, fragmentStart))
|
||||
++fragmentStart
|
||||
}
|
||||
|
||||
let fragmentEnd = pattern.indexOf('/', i)
|
||||
if (fragmentEnd === -1) {
|
||||
fragmentEnd = pattern.length
|
||||
}
|
||||
|
||||
parts.push(compileFragment(pattern.slice(fragmentStart, fragmentEnd)))
|
||||
|
||||
pattern = pattern.slice(fragmentEnd + 1)
|
||||
}
|
||||
return parts
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import { parseGlob } from './_parseGlob'
|
||||
|
||||
describe('parseGlob', () => {
|
||||
it.each([['foo/*/bar*baz/qux', ['foo', /^[^]*$/, /^bar[^]*baz$/, 'qux']]])(
|
||||
'parse %j correctly',
|
||||
(pattern, result) => {
|
||||
expect(parseGlob(pattern)).toEqual(result)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -14,7 +14,6 @@ import { type Readable, type Writable } from 'stream'
|
||||
|
||||
import normalizePath from './_normalizePath'
|
||||
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
|
||||
import { parseGlob } from './_parseGlob'
|
||||
|
||||
const { dirname } = path.posix
|
||||
|
||||
@@ -259,12 +258,6 @@ export default class RemoteHandlerAbstract {
|
||||
)
|
||||
}
|
||||
|
||||
// basic glob support, only `*` is supported
|
||||
async glob(pattern) {
|
||||
const parts = parseGlob(pattern)
|
||||
// TODO
|
||||
}
|
||||
|
||||
async list(
|
||||
dir: string,
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xen-orchestra/log",
|
||||
"version": "0.2.0",
|
||||
"version": "0.1.4",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
|
||||
@@ -19,8 +19,7 @@ const createTransport = config => {
|
||||
}
|
||||
}
|
||||
|
||||
let { filter } = config
|
||||
let transport = createTransport(config.transport)
|
||||
let { filter, transport } = config
|
||||
const level = resolve(config.level)
|
||||
|
||||
if (filter !== undefined) {
|
||||
@@ -52,12 +51,11 @@ const symbol =
|
||||
? Symbol.for('@xen-orchestra/log')
|
||||
: '@@@xen-orchestra/log'
|
||||
|
||||
const { env } = process
|
||||
global[symbol] = createTransport({
|
||||
// display warnings or above, and all that are enabled via DEBUG or
|
||||
// NODE_DEBUG env
|
||||
filter: [env.DEBUG, env.NODE_DEBUG].filter(Boolean).join(','),
|
||||
level: resolve(env.LOG_LEVEL, LEVELS.INFO),
|
||||
filter: process.env.DEBUG || process.env.NODE_DEBUG,
|
||||
level: LEVELS.INFO,
|
||||
|
||||
transport: createConsoleTransport(),
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import createTransport from './transports/console'
|
||||
import LEVELS, { resolve } from './levels'
|
||||
import LEVELS from './levels'
|
||||
|
||||
const symbol =
|
||||
typeof Symbol !== 'undefined'
|
||||
@@ -9,8 +9,7 @@ if (!(symbol in global)) {
|
||||
// the default behavior, without requiring `configure` is to avoid
|
||||
// logging anything unless it's a real error
|
||||
const transport = createTransport()
|
||||
const level = resolve(process.env.LOG_LEVEL, LEVELS.WARN)
|
||||
global[symbol] = log => log.level >= level && transport(log)
|
||||
global[symbol] = log => log.level > LEVELS.WARN && transport(log)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -73,5 +72,5 @@ prototype.wrap = function(message, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
export const createLogger = namespace => new Logger(namespace)
|
||||
const createLogger = namespace => new Logger(namespace)
|
||||
export { createLogger as default }
|
||||
|
||||
@@ -13,22 +13,11 @@ for (const name in LEVELS) {
|
||||
NAMES[LEVELS[name]] = name
|
||||
}
|
||||
|
||||
// resolves to the number representation of a level
|
||||
//
|
||||
// returns `defaultLevel` if invalid
|
||||
export const resolve = (level, defaultLevel) => {
|
||||
const type = typeof level
|
||||
if (type === 'number') {
|
||||
if (level in NAMES) {
|
||||
return level
|
||||
}
|
||||
} else if (type === 'string') {
|
||||
const nLevel = LEVELS[level.toUpperCase()]
|
||||
if (nLevel !== undefined) {
|
||||
return nLevel
|
||||
}
|
||||
export const resolve = level => {
|
||||
if (typeof level === 'string') {
|
||||
level = LEVELS[level.toUpperCase()]
|
||||
}
|
||||
return defaultLevel
|
||||
return level
|
||||
}
|
||||
|
||||
Object.freeze(LEVELS)
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import LEVELS, { NAMES } from '../levels'
|
||||
|
||||
// Bind console methods (necessary for browsers)
|
||||
/* eslint-disable no-console */
|
||||
const debugConsole = console.log.bind(console)
|
||||
const infoConsole = console.info.bind(console)
|
||||
const warnConsole = console.warn.bind(console)
|
||||
const errorConsole = console.error.bind(console)
|
||||
/* eslint-enable no-console */
|
||||
|
||||
const { ERROR, INFO, WARN } = LEVELS
|
||||
|
||||
const consoleTransport = ({ data, level, namespace, message, time }) => {
|
||||
const fn =
|
||||
/* eslint-disable no-console */
|
||||
level < INFO
|
||||
? console.log
|
||||
? debugConsole
|
||||
: level < WARN
|
||||
? console.info
|
||||
? infoConsole
|
||||
: level < ERROR
|
||||
? console.warn
|
||||
: console.error
|
||||
/* eslint-enable no-console */
|
||||
? warnConsole
|
||||
: errorConsole
|
||||
|
||||
const args = [time.toISOString(), namespace, NAMES[level], message]
|
||||
if (data != null) {
|
||||
args.push(data)
|
||||
}
|
||||
fn.apply(console, args)
|
||||
fn('%s - %s - [%s] %s', time.toISOString(), namespace, NAMES[level], message)
|
||||
data != null && fn(data)
|
||||
}
|
||||
export default () => consoleTransport
|
||||
|
||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -4,37 +4,14 @@
|
||||
|
||||
### Enhancements
|
||||
|
||||
### Bug fixes
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-server v5.51.0
|
||||
- xo-web v5.51.0
|
||||
|
||||
|
||||
## **5.39.0** (2019-09-30)
|
||||
|
||||

|
||||
|
||||
### Highlights
|
||||
|
||||
- [VM/console] Add a button to connect to the VM via the local SSH client (PR [#4415](https://github.com/vatesfr/xen-orchestra/pull/4415))
|
||||
- [SDN Controller] Add possibility to encrypt private networks (PR [#4441](https://github.com/vatesfr/xen-orchestra/pull/4441))
|
||||
- [Backups] Improve performance by caching VM backups listing (PR [#4509](https://github.com/vatesfr/xen-orchestra/pull/4509))
|
||||
- [HUB] VM template store [#1918](https://github.com/vatesfr/xen-orchestra/issues/1918) (PR [#4442](https://github.com/vatesfr/xen-orchestra/pull/4442))
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [SR/new] Clarify address formats [#4450](https://github.com/vatesfr/xen-orchestra/issues/4450) (PR [#4460](https://github.com/vatesfr/xen-orchestra/pull/4460))
|
||||
- [Backup NG/New] Show warning if zstd compression is not supported on a VM [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PRs [#4411](https://github.com/vatesfr/xen-orchestra/pull/4411))
|
||||
- [VM/disks] Don't hide disks that are attached to the same VM twice [#4400](https://github.com/vatesfr/xen-orchestra/issues/4400) (PR [#4414](https://github.com/vatesfr/xen-orchestra/pull/4414))
|
||||
- [VM/console] Add a button to connect to the VM via the local SSH client (PR [#4415](https://github.com/vatesfr/xen-orchestra/pull/4415))
|
||||
- [SDN Controller] Add possibility to encrypt private networks (PR [#4441](https://github.com/vatesfr/xen-orchestra/pull/4441))
|
||||
- [SDN Controller] Ability to configure MTU for private networks (PR [#4491](https://github.com/vatesfr/xen-orchestra/pull/4491))
|
||||
- [VM Export] Filenames are now prefixed with datetime [#4503](https://github.com/vatesfr/xen-orchestra/issues/4503)
|
||||
- [Settings/Logs] Differenciate XS/XCP-ng errors from XO errors [#4101](https://github.com/vatesfr/xen-orchestra/issues/4101) (PR [#4385](https://github.com/vatesfr/xen-orchestra/pull/4385))
|
||||
- [Backups] Improve performance by caching logs consolidation (PR [#4541](https://github.com/vatesfr/xen-orchestra/pull/4541))
|
||||
- [New VM] Cloud Init available for all plans (PR [#4543](https://github.com/vatesfr/xen-orchestra/pull/4543))
|
||||
- [Servers] IPv6 addresses can be used [#4520](https://github.com/vatesfr/xen-orchestra/issues/4520) (PR [#4521](https://github.com/vatesfr/xen-orchestra/pull/4521)) \
|
||||
Note: They must enclosed in brackets to differentiate with the port, e.g.: `[2001:db8::7334]` or `[ 2001:db8::7334]:4343`
|
||||
- [Backups] Improve performance by caching VM backups listing (PR [#4509](https://github.com/vatesfr/xen-orchestra/pull/4509))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -53,23 +30,20 @@
|
||||
- [XOA] Remove "Updates" and "Licenses" tabs for non admin users (PR [#4526](https://github.com/vatesfr/xen-orchestra/pull/4526))
|
||||
- [New VM] Ability to escape [cloud config template](https://xen-orchestra.com/blog/xen-orchestra-5-21/#cloudconfigtemplates) variables [#4486](https://github.com/vatesfr/xen-orchestra/issues/4486) (PR [#4501](https://github.com/vatesfr/xen-orchestra/pull/4501))
|
||||
- [Backup NG] Properly log and report if job is already running [#4497](https://github.com/vatesfr/xen-orchestra/issues/4497) (PR [4534](https://github.com/vatesfr/xen-orchestra/pull/4534))
|
||||
- [Host] Fix an issue where host was wrongly reporting time inconsistency (PR [#4540](https://github.com/vatesfr/xen-orchestra/pull/4540))
|
||||
|
||||
|
||||
### Released packages
|
||||
|
||||
- xen-api v0.27.2
|
||||
- xo-server-cloud v0.3.0
|
||||
- @xen-orchestra/cron v1.0.4
|
||||
- xo-server-sdn-controller v0.3.0
|
||||
- @xen-orchestra/template v0.1.0
|
||||
- xo-server v5.50.1
|
||||
- xo-web v5.50.2
|
||||
- xo-server v5.50.0
|
||||
- xo-web v5.50.0
|
||||
|
||||
|
||||
## **5.38.0** (2019-08-29)
|
||||
|
||||

|
||||

|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -97,6 +71,8 @@
|
||||
|
||||
## **5.37.1** (2019-08-06)
|
||||
|
||||

|
||||
|
||||
### Enhancements
|
||||
|
||||
- [SDN Controller] Let the user choose on which PIF to create a private network (PR [#4379](https://github.com/vatesfr/xen-orchestra/pull/4379))
|
||||
|
||||
@@ -7,11 +7,17 @@
|
||||
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
|
||||
- [Settings/Logs] Differenciate XS/XCP-ng errors from XO errors [#4101](https://github.com/vatesfr/xen-orchestra/issues/4101) (PR [#4385](https://github.com/vatesfr/xen-orchestra/pull/4385))
|
||||
- [Backups] Improve performance by caching logs consolidation (PR [#4541](https://github.com/vatesfr/xen-orchestra/pull/4541))
|
||||
- [New VM] Cloud Init available for all plans (PR [#4543](https://github.com/vatesfr/xen-orchestra/pull/4543))
|
||||
- [Servers] IPv6 addresses can be used [#4520](https://github.com/vatesfr/xen-orchestra/issues/4520) (PR [#4521](https://github.com/vatesfr/xen-orchestra/pull/4521)) \
|
||||
Note: They must enclosed in brackets to differentiate with the port, e.g.: `[2001:db8::7334]` or `[ 2001:db8::7334]:4343`
|
||||
|
||||
### Bug fixes
|
||||
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
|
||||
- [VM/new-vm] Fix template selection on creating new VM for resource sets [#4565](https://github.com/vatesfr/xen-orchestra/issues/4565) (PR [#4568](https://github.com/vatesfr/xen-orchestra/pull/4568))
|
||||
- [Host] Fix an issue where host was wrongly reporting time inconsistency (PR [#4540](https://github.com/vatesfr/xen-orchestra/pull/4540))
|
||||
|
||||
### Released packages
|
||||
|
||||
@@ -20,5 +26,6 @@
|
||||
>
|
||||
> Rule of thumb: add packages on top.
|
||||
|
||||
- xen-api v0.27.2
|
||||
- xo-server v5.51.0
|
||||
- xo-web v5.51.0
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
### Check list
|
||||
|
||||
> Check if done.
|
||||
>
|
||||
> Strikethrough if not relevant: ~~example~~ ([doc](https://help.github.com/en/articles/basic-writing-and-formatting-syntax)).
|
||||
> Check items when done or if not relevant
|
||||
|
||||
- [ ] PR reference the relevant issue (e.g. `Fixes #007` or `See xoa-support#42`)
|
||||
- [ ] PR reference the relevant issue (e.g. `Fixes #007`)
|
||||
- [ ] if UI changes, a screenshot has been added to the PR
|
||||
- [ ] if `xo-server` API changes, the corresponding test has been added to/updated on [`xo-server-test`](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-test)
|
||||
- [ ] `CHANGELOG.unreleased.md`:
|
||||
- enhancement/bug fix entry added
|
||||
- list of packages to release updated (`${name} v${new version}`)
|
||||
- [ ] documentation updated
|
||||
- `CHANGELOG.unreleased.md`:
|
||||
- [ ] enhancement/bug fix entry added
|
||||
- [ ] list of packages to release updated (`${name} v${new version}`)
|
||||
- **I have tested added/updated features** (and impacted code)
|
||||
- [ ] unit tests (e.g. [`cron/parse.spec.js`](https://github.com/vatesfr/xen-orchestra/blob/b24400b21de1ebafa1099c56bac1de5c988d9202/%40xen-orchestra/cron/src/parse.spec.js))
|
||||
- [ ] if `xo-server` API changes, the corresponding test has been added to/updated on [`xo-server-test`](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-test)
|
||||
- [ ] at least manual testing
|
||||
- [ ] **I have tested added/updated features** (and impacted code)
|
||||
|
||||
### Process
|
||||
|
||||
@@ -21,10 +17,3 @@
|
||||
1. mark it as `WiP:` (Work in Progress) if not ready to be merged
|
||||
1. when you want a review, add a reviewer (and only one)
|
||||
1. if necessary, update your PR, and re- add a reviewer
|
||||
|
||||
From [_the Four Agreements_](https://en.wikipedia.org/wiki/Don_Miguel_Ruiz#The_Four_Agreements):
|
||||
|
||||
1. Be impeccable with your word.
|
||||
1. Don't take anything personally.
|
||||
1. Don't make assumptions.
|
||||
1. Always do your best.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB |
@@ -41,20 +41,6 @@ However, if you want to start a manual check, you can do it by clicking on the "
|
||||
|
||||

|
||||
|
||||
#### Release channel
|
||||
In Xen Orchestra, you can make a choice between two different release channels.
|
||||
|
||||
##### Stable
|
||||
The stable channel is intended to be a version of Xen Orchestra that is already **one month old** (and therefore will benefit from one month of community feedback and various fixes). This way, users more concerned with the stability of their appliance will have the option to stay on a slightly older (and tested) version of XO (still supported by our pro support).
|
||||
|
||||
##### Latest
|
||||
|
||||
The latest channel will include all the latest improvements available in Xen Orchestra. The version available in latest has already been QA'd by our team, but issues may still occur once deployed in vastly varying environments, such as our user base has.
|
||||
|
||||
> To select the release channel of your choice, go to the XOA > Updates view.
|
||||
|
||||

|
||||
|
||||
#### Upgrade
|
||||
|
||||
If a new version is found, you'll have an upgrade button and its tooltip displayed:
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"human-format": "^0.10.0",
|
||||
"lodash": "^4.17.4",
|
||||
"pw": "^0.0.4",
|
||||
"xen-api": "^0.27.2"
|
||||
"xen-api": "^0.27.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.1.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xen-api",
|
||||
"version": "0.27.2",
|
||||
"version": "0.27.1",
|
||||
"license": "ISC",
|
||||
"description": "Connector to the Xen API",
|
||||
"keywords": [
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"@xen-orchestra/log": "^0.1.4",
|
||||
"human-format": "^0.10.0",
|
||||
"lodash": "^4.13.1",
|
||||
"moment-timezone": "^0.5.13"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-cloud",
|
||||
"version": "0.3.0",
|
||||
"version": "0.2.4",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
|
||||
@@ -20,13 +20,9 @@ class XoServerCloud {
|
||||
}
|
||||
|
||||
async load() {
|
||||
const getResourceCatalog = this._getCatalog.bind(this)
|
||||
getResourceCatalog.description =
|
||||
"Get the list of user's available resources"
|
||||
const getResourceCatalog = () => this._getCatalog()
|
||||
getResourceCatalog.description = 'Get the list of all available resources'
|
||||
getResourceCatalog.permission = 'admin'
|
||||
getResourceCatalog.params = {
|
||||
filters: { type: 'object', optional: true },
|
||||
}
|
||||
|
||||
const registerResource = ({ namespace }) =>
|
||||
this._registerResource(namespace)
|
||||
@@ -38,29 +34,8 @@ class XoServerCloud {
|
||||
}
|
||||
registerResource.permission = 'admin'
|
||||
|
||||
const downloadAndInstallResource = this._downloadAndInstallResource.bind(
|
||||
this
|
||||
)
|
||||
|
||||
downloadAndInstallResource.description =
|
||||
'Download and install a resource via cloud plugin'
|
||||
|
||||
downloadAndInstallResource.params = {
|
||||
id: { type: 'string' },
|
||||
namespace: { type: 'string' },
|
||||
version: { type: 'string' },
|
||||
sr: { type: 'string' },
|
||||
}
|
||||
|
||||
downloadAndInstallResource.resolve = {
|
||||
sr: ['sr', 'SR', 'administrate'],
|
||||
}
|
||||
|
||||
downloadAndInstallResource.permission = 'admin'
|
||||
|
||||
this._unsetApiMethods = this._xo.addApiMethods({
|
||||
cloud: {
|
||||
downloadAndInstallResource,
|
||||
getResourceCatalog,
|
||||
registerResource,
|
||||
},
|
||||
@@ -91,8 +66,8 @@ class XoServerCloud {
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
async _getCatalog({ filters } = {}) {
|
||||
const catalog = await this._updater.call('getResourceCatalog', { filters })
|
||||
async _getCatalog() {
|
||||
const catalog = await this._updater.call('getResourceCatalog')
|
||||
|
||||
if (!catalog) {
|
||||
throw new Error('cannot get catalog')
|
||||
@@ -115,26 +90,6 @@ class XoServerCloud {
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
async _downloadAndInstallResource({ id, namespace, sr, version }) {
|
||||
const stream = await this._requestResource({
|
||||
hub: true,
|
||||
id,
|
||||
namespace,
|
||||
version,
|
||||
})
|
||||
const vm = await this._xo.getXapi(sr.$poolId).importVm(stream, {
|
||||
srId: sr.id,
|
||||
type: 'xva',
|
||||
})
|
||||
await vm.update_other_config({
|
||||
'xo:resource:namespace': namespace,
|
||||
'xo:resource:xva:version': version,
|
||||
'xo:resource:xva:id': id,
|
||||
})
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
async _registerResource(namespace) {
|
||||
const _namespace = (await this._getNamespaces())[namespace]
|
||||
|
||||
@@ -151,10 +106,8 @@ class XoServerCloud {
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
async _getNamespaceCatalog({ hub, namespace }) {
|
||||
const namespaceCatalog = (await this._getCatalog({ filters: { hub } }))[
|
||||
namespace
|
||||
]
|
||||
async _getNamespaceCatalog(namespace) {
|
||||
const namespaceCatalog = (await this._getCatalog())[namespace]
|
||||
|
||||
if (!namespaceCatalog) {
|
||||
throw new Error(`cannot get catalog: ${namespace} not registered`)
|
||||
@@ -165,17 +118,14 @@ class XoServerCloud {
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
async _requestResource({ hub = false, id, namespace, version }) {
|
||||
async _requestResource(namespace, id, version) {
|
||||
const _namespace = (await this._getNamespaces())[namespace]
|
||||
|
||||
if (!hub && (!_namespace || !_namespace.registered)) {
|
||||
if (!_namespace || !_namespace.registered) {
|
||||
throw new Error(`cannot get resource: ${namespace} not registered`)
|
||||
}
|
||||
|
||||
const { _token: token } = await this._getNamespaceCatalog({
|
||||
hub,
|
||||
namespace,
|
||||
})
|
||||
const { _token: token } = await this._getNamespaceCatalog(namespace)
|
||||
|
||||
// 2018-03-20 Extra check: getResourceDownloadToken seems to be called without a token in some cases
|
||||
if (token === undefined) {
|
||||
|
||||
@@ -680,7 +680,7 @@ ${entry.listItem}
|
||||
},
|
||||
}
|
||||
if (xapiObject.$type === 'VM') {
|
||||
payload.vm_uuid = xapiObject.uuid
|
||||
payload['vm_uuid'] = xapiObject.uuid
|
||||
}
|
||||
// JSON is not well formed, can't use the default node parser
|
||||
return JSON5.parse(
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"cross-env": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"@xen-orchestra/log": "^0.1.4",
|
||||
"lodash": "^4.17.11",
|
||||
"node-openssl-cert": "^0.0.97",
|
||||
"promise-toolbox": "^0.13.0",
|
||||
|
||||
@@ -210,10 +210,6 @@ class XoConnection extends Xo {
|
||||
return backups
|
||||
}
|
||||
|
||||
getBackupLogs(filter) {
|
||||
return this.call('backupNg.getLogs', { _forceRefresh: true, ...filter })
|
||||
}
|
||||
|
||||
async _cleanDisposers(disposers) {
|
||||
for (let n = disposers.length - 1; n > 0; ) {
|
||||
const params = disposers[n--]
|
||||
|
||||
@@ -221,7 +221,7 @@ describe('backupNg', () => {
|
||||
expect(typeof schedule).toBe('object')
|
||||
|
||||
await xo.call('backupNg.runJob', { id: jobId, schedule: schedule.id })
|
||||
const [log] = await xo.getBackupLogs({
|
||||
const [log] = await xo.call('backupNg.getLogs', {
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
expect(log.warnings).toMatchSnapshot()
|
||||
@@ -260,7 +260,7 @@ describe('backupNg', () => {
|
||||
tasks: [vmTask],
|
||||
...log
|
||||
},
|
||||
] = await xo.getBackupLogs({
|
||||
] = await xo.call('backupNg.getLogs', {
|
||||
jobId,
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
@@ -319,7 +319,7 @@ describe('backupNg', () => {
|
||||
tasks: [task],
|
||||
...log
|
||||
},
|
||||
] = await xo.getBackupLogs({
|
||||
] = await xo.call('backupNg.getLogs', {
|
||||
jobId,
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
@@ -415,7 +415,7 @@ describe('backupNg', () => {
|
||||
tasks: [{ tasks: subTasks, ...vmTask }],
|
||||
...log
|
||||
},
|
||||
] = await xo.getBackupLogs({
|
||||
] = await xo.call('backupNg.getLogs', {
|
||||
jobId,
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
@@ -506,7 +506,7 @@ describe('backupNg', () => {
|
||||
expect(backups.length).toBe(exportRetention)
|
||||
)
|
||||
|
||||
const backupLogs = await xo.getBackupLogs({
|
||||
const backupLogs = await xo.call('backupNg.getLogs', {
|
||||
jobId,
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"dependencies": {
|
||||
"@xen-orchestra/async-map": "^0.0.0",
|
||||
"@xen-orchestra/cron": "^1.0.4",
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"@xen-orchestra/log": "^0.1.4",
|
||||
"handlebars": "^4.0.6",
|
||||
"html-minifier": "^4.0.0",
|
||||
"human-format": "^0.10.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-server",
|
||||
"version": "5.50.1",
|
||||
"version": "5.50.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Server part of Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -39,7 +39,7 @@
|
||||
"@xen-orchestra/defined": "^0.0.0",
|
||||
"@xen-orchestra/emit-async": "^0.0.0",
|
||||
"@xen-orchestra/fs": "^0.10.1",
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"@xen-orchestra/log": "^0.1.4",
|
||||
"@xen-orchestra/mixin": "^0.0.0",
|
||||
"ajv": "^6.1.1",
|
||||
"app-conf": "^0.7.0",
|
||||
@@ -124,7 +124,7 @@
|
||||
"value-matcher": "^0.2.0",
|
||||
"vhd-lib": "^0.7.0",
|
||||
"ws": "^6.0.0",
|
||||
"xen-api": "^0.27.2",
|
||||
"xen-api": "^0.27.1",
|
||||
"xml2js": "^0.4.19",
|
||||
"xo-acl-resolver": "^0.4.1",
|
||||
"xo-collection": "^0.4.1",
|
||||
|
||||
@@ -16,10 +16,6 @@ function scheduleRemoveCacheEntry(keys, expires) {
|
||||
|
||||
const defaultKeyFn = () => []
|
||||
|
||||
const { slice } = Array.prototype
|
||||
|
||||
export const REMOVE_CACHE_ENTRY = {}
|
||||
|
||||
// debounce an async function so that all subsequent calls in a delay receive
|
||||
// the same result
|
||||
//
|
||||
@@ -30,14 +26,7 @@ export const REMOVE_CACHE_ENTRY = {}
|
||||
export const debounceWithKey = (fn, delay, keyFn = defaultKeyFn) => {
|
||||
const cache = new MultiKeyMap()
|
||||
const delayFn = typeof delay === 'number' ? () => delay : delay
|
||||
return function(arg) {
|
||||
if (arg === REMOVE_CACHE_ENTRY) {
|
||||
return removeCacheEntry(
|
||||
cache,
|
||||
ensureArray(keyFn.apply(this, slice.call(arguments, 1)))
|
||||
)
|
||||
}
|
||||
|
||||
return function() {
|
||||
const keys = ensureArray(keyFn.apply(this, arguments))
|
||||
let promise = cache.get(keys)
|
||||
if (promise === undefined) {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import { debounceWithKey, REMOVE_CACHE_ENTRY } from './_pDebounceWithKey'
|
||||
|
||||
describe('REMOVE_CACHE_ENTRY', () => {
|
||||
it('clears the cache', async () => {
|
||||
let i = 0
|
||||
const debouncedFn = debounceWithKey(
|
||||
function() {
|
||||
return Promise.resolve(++i)
|
||||
},
|
||||
Infinity,
|
||||
id => id
|
||||
)
|
||||
|
||||
// not cached accross keys
|
||||
expect(await debouncedFn(1)).toBe(1)
|
||||
expect(await debouncedFn(2)).toBe(2)
|
||||
|
||||
// retrieve the already cached values
|
||||
expect(await debouncedFn(1)).toBe(1)
|
||||
expect(await debouncedFn(2)).toBe(2)
|
||||
|
||||
// an entry for a specific key can be removed
|
||||
debouncedFn(REMOVE_CACHE_ENTRY, 1)
|
||||
expect(await debouncedFn(1)).toBe(3)
|
||||
expect(await debouncedFn(2)).toBe(2)
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,6 @@ import { fromCallback } from 'promise-toolbox'
|
||||
import { pipeline } from 'readable-stream'
|
||||
|
||||
import createNdJsonStream from '../_createNdJsonStream'
|
||||
import { REMOVE_CACHE_ENTRY } from '../_pDebounceWithKey'
|
||||
import { safeDateFormat } from '../utils'
|
||||
|
||||
export function createJob({ schedules, ...job }) {
|
||||
@@ -185,20 +184,7 @@ getAllLogs.params = {
|
||||
ndjson: { type: 'boolean', optional: true },
|
||||
}
|
||||
|
||||
export function getLogs({
|
||||
after,
|
||||
before,
|
||||
limit,
|
||||
|
||||
// TODO: it's a temporary work-around which should be removed
|
||||
// when the consolidated logs will be stored in the DB
|
||||
_forceRefresh = false,
|
||||
|
||||
...filter
|
||||
}) {
|
||||
if (_forceRefresh) {
|
||||
this.getBackupNgLogs(REMOVE_CACHE_ENTRY)
|
||||
}
|
||||
export function getLogs({ after, before, limit, ...filter }) {
|
||||
return this.getBackupNgLogsSorted({ after, before, limit, filter })
|
||||
}
|
||||
|
||||
|
||||
@@ -777,7 +777,7 @@ export async function probeIscsiExists({
|
||||
)
|
||||
|
||||
const srs = []
|
||||
forEach(ensureArray(xml.SRlist.SR), sr => {
|
||||
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
||||
// get the UUID of SR connected to this LUN
|
||||
srs.push({ uuid: sr.UUID.trim() })
|
||||
})
|
||||
@@ -845,7 +845,7 @@ export async function probeNfsExists({ host, server, serverPath }) {
|
||||
|
||||
const srs = []
|
||||
|
||||
forEach(ensureArray(xml.SRlist.SR), sr => {
|
||||
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
||||
// get the UUID of SR connected to this LUN
|
||||
srs.push({ uuid: sr.UUID.trim() })
|
||||
})
|
||||
|
||||
@@ -85,7 +85,7 @@ async function rateLimitedRetry(action, shouldRetry, retryCount = 20) {
|
||||
function createVolumeInfoTypes() {
|
||||
function parseHeal(parsed) {
|
||||
const bricks = []
|
||||
parsed.healInfo.bricks.brick.forEach(brick => {
|
||||
parsed['healInfo']['bricks']['brick'].forEach(brick => {
|
||||
bricks.push(brick)
|
||||
if (brick.file) {
|
||||
brick.file = ensureArray(brick.file)
|
||||
@@ -96,21 +96,21 @@ function createVolumeInfoTypes() {
|
||||
|
||||
function parseStatus(parsed) {
|
||||
const brickDictByUuid = {}
|
||||
const volume = parsed.volStatus.volumes.volume
|
||||
volume.node.forEach(node => {
|
||||
const volume = parsed['volStatus']['volumes']['volume']
|
||||
volume['node'].forEach(node => {
|
||||
brickDictByUuid[node.peerid] = brickDictByUuid[node.peerid] || []
|
||||
brickDictByUuid[node.peerid].push(node)
|
||||
})
|
||||
return {
|
||||
commandStatus: true,
|
||||
result: { nodes: brickDictByUuid, tasks: volume.tasks },
|
||||
result: { nodes: brickDictByUuid, tasks: volume['tasks'] },
|
||||
}
|
||||
}
|
||||
|
||||
async function parseInfo(parsed) {
|
||||
const volume = parsed.volInfo.volumes.volume
|
||||
volume.bricks = volume.bricks.brick
|
||||
volume.options = volume.options.option
|
||||
const volume = parsed['volInfo']['volumes']['volume']
|
||||
volume['bricks'] = volume['bricks']['brick']
|
||||
volume['options'] = volume['options']['option']
|
||||
return { commandStatus: true, result: volume }
|
||||
}
|
||||
|
||||
@@ -118,23 +118,23 @@ function createVolumeInfoTypes() {
|
||||
return async function(sr) {
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(sr)
|
||||
const cmdShouldRetry = result =>
|
||||
!result.commandStatus &&
|
||||
((result.parsed && result.parsed.cliOutput.opErrno === '30802') ||
|
||||
!result['commandStatus'] &&
|
||||
((result.parsed && result.parsed['cliOutput']['opErrno'] === '30802') ||
|
||||
result.stderr.match(/Another transaction is in progress/))
|
||||
const runCmd = async () =>
|
||||
glusterCmd(glusterEndpoint, 'volume ' + command, true)
|
||||
const commandResult = await rateLimitedRetry(runCmd, cmdShouldRetry, 30)
|
||||
return commandResult.commandStatus
|
||||
? this::handler(commandResult.parsed.cliOutput, sr)
|
||||
return commandResult['commandStatus']
|
||||
? this::handler(commandResult.parsed['cliOutput'], sr)
|
||||
: commandResult
|
||||
}
|
||||
}
|
||||
|
||||
async function profileType(sr) {
|
||||
async function parseProfile(parsed) {
|
||||
const volume = parsed.volProfile
|
||||
volume.bricks = ensureArray(volume.brick)
|
||||
delete volume.brick
|
||||
const volume = parsed['volProfile']
|
||||
volume['bricks'] = ensureArray(volume['brick'])
|
||||
delete volume['brick']
|
||||
return { commandStatus: true, result: volume }
|
||||
}
|
||||
|
||||
@@ -143,9 +143,9 @@ function createVolumeInfoTypes() {
|
||||
|
||||
async function profileTopType(sr) {
|
||||
async function parseTop(parsed) {
|
||||
const volume = parsed.volTop
|
||||
volume.bricks = ensureArray(volume.brick)
|
||||
delete volume.brick
|
||||
const volume = parsed['volTop']
|
||||
volume['bricks'] = ensureArray(volume['brick'])
|
||||
delete volume['brick']
|
||||
return { commandStatus: true, result: volume }
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ async function remoteSsh(glusterEndpoint, cmd, ignoreError = false) {
|
||||
}
|
||||
messageArray.push(`${key}: ${result[key]}`)
|
||||
}
|
||||
messageArray.push('command: ' + result.command.join(' '))
|
||||
messageArray.push('command: ' + result['command'].join(' '))
|
||||
messageKeys.splice(messageKeys.indexOf('command'), 1)
|
||||
for (const key of messageKeys) {
|
||||
messageArray.push(`${key}: ${JSON.stringify(result[key])}`)
|
||||
@@ -343,7 +343,7 @@ async function remoteSsh(glusterEndpoint, cmd, ignoreError = false) {
|
||||
})
|
||||
break
|
||||
} catch (exception) {
|
||||
if (exception.code !== 'HOST_OFFLINE') {
|
||||
if (exception['code'] !== 'HOST_OFFLINE') {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
@@ -370,17 +370,19 @@ async function remoteSsh(glusterEndpoint, cmd, ignoreError = false) {
|
||||
}
|
||||
|
||||
function findErrorMessage(commandResut) {
|
||||
if (commandResut.exit === 0 && commandResut.parsed) {
|
||||
const cliOut = commandResut.parsed.cliOutput
|
||||
if (cliOut.opErrstr && cliOut.opErrstr.length) {
|
||||
return cliOut.opErrstr
|
||||
if (commandResut['exit'] === 0 && commandResut.parsed) {
|
||||
const cliOut = commandResut.parsed['cliOutput']
|
||||
if (cliOut['opErrstr'] && cliOut['opErrstr'].length) {
|
||||
return cliOut['opErrstr']
|
||||
}
|
||||
// "peer probe" returns it's "already in peer" error in cliOutput/output
|
||||
if (cliOut.output && cliOut.output.length) {
|
||||
return cliOut.output
|
||||
if (cliOut['output'] && cliOut['output'].length) {
|
||||
return cliOut['output']
|
||||
}
|
||||
}
|
||||
return commandResut.stderr.length ? commandResut.stderr : commandResut.stdout
|
||||
return commandResut['stderr'].length
|
||||
? commandResut['stderr']
|
||||
: commandResut['stdout']
|
||||
}
|
||||
|
||||
async function glusterCmd(glusterEndpoint, cmd, ignoreError = false) {
|
||||
@@ -390,15 +392,15 @@ async function glusterCmd(glusterEndpoint, cmd, ignoreError = false) {
|
||||
true
|
||||
)
|
||||
try {
|
||||
result.parsed = parseXml(result.stdout)
|
||||
result.parsed = parseXml(result['stdout'])
|
||||
} catch (e) {
|
||||
// pass, we never know if a message can be parsed or not, so we just try
|
||||
}
|
||||
if (result.exit === 0) {
|
||||
const cliOut = result.parsed.cliOutput
|
||||
if (result['exit'] === 0) {
|
||||
const cliOut = result.parsed['cliOutput']
|
||||
// we have found cases where opErrno is !=0 and opRet was 0, albeit the operation was an error.
|
||||
result.commandStatus =
|
||||
cliOut.opRet.trim() === '0' && cliOut.opErrno.trim() === '0'
|
||||
cliOut['opRet'].trim() === '0' && cliOut['opErrno'].trim() === '0'
|
||||
result.error = findErrorMessage(result)
|
||||
} else {
|
||||
result.commandStatus = false
|
||||
@@ -791,7 +793,7 @@ export const createSR = defer(async function(
|
||||
host: param.host.$id,
|
||||
vm: { id: param.vm.$id, ip: param.address },
|
||||
underlyingSr: param.underlyingSr.$id,
|
||||
arbiter: !!param.arbiter,
|
||||
arbiter: !!param['arbiter'],
|
||||
}))
|
||||
await xapi.xo.setData(xosanSrRef, 'xosan_config', {
|
||||
version: 'beta2',
|
||||
@@ -1162,11 +1164,11 @@ async function _prepareGlusterVm(
|
||||
}
|
||||
|
||||
async function _importGlusterVM(xapi, template, lvmsrId) {
|
||||
const templateStream = await this.requestResource({
|
||||
id: template.id,
|
||||
namespace: 'xosan',
|
||||
version: template.version,
|
||||
})
|
||||
const templateStream = await this.requestResource(
|
||||
'xosan',
|
||||
template.id,
|
||||
template.version
|
||||
)
|
||||
const newVM = await xapi.importVm(templateStream, {
|
||||
srId: lvmsrId,
|
||||
type: 'xva',
|
||||
@@ -1298,7 +1300,7 @@ export const addBricks = defer(async function(
|
||||
underlyingSr: newSr,
|
||||
})
|
||||
}
|
||||
const arbiterNode = data.nodes.find(n => n.arbiter)
|
||||
const arbiterNode = data.nodes.find(n => n['arbiter'])
|
||||
if (arbiterNode) {
|
||||
await glusterCmd(
|
||||
glusterEndpoint,
|
||||
@@ -1533,11 +1535,8 @@ export async function downloadAndInstallXosanPack({ id, version, pool }) {
|
||||
}
|
||||
|
||||
const xapi = this.getXapi(pool.id)
|
||||
const res = await this.requestResource({
|
||||
id,
|
||||
namespace: 'xosan',
|
||||
version,
|
||||
})
|
||||
const res = await this.requestResource('xosan', id, version)
|
||||
|
||||
await xapi.installSupplementalPackOnAllHosts(res)
|
||||
await xapi.pool.update_other_config(
|
||||
'xosan_pack_installation_time',
|
||||
|
||||
@@ -6,7 +6,6 @@ import ndjson from 'ndjson'
|
||||
import parseArgs from 'minimist'
|
||||
import sublevel from 'level-sublevel'
|
||||
import util from 'util'
|
||||
import { join as joinPath } from 'path'
|
||||
import { repair as repairDb } from 'level'
|
||||
|
||||
import { forEach } from './utils'
|
||||
@@ -175,7 +174,6 @@ export default async function main() {
|
||||
}
|
||||
|
||||
const config = await appConf.load('xo-server', {
|
||||
appDir: joinPath(__dirname, '..'),
|
||||
ignoreUnknownFormats: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -1576,7 +1576,7 @@ export default class Xapi extends XapiBase {
|
||||
}
|
||||
} else {
|
||||
// Find the original template by name (*sigh*).
|
||||
const templateNameLabel = vm.other_config.base_template_name
|
||||
const templateNameLabel = vm.other_config['base_template_name']
|
||||
const template =
|
||||
templateNameLabel &&
|
||||
find(
|
||||
|
||||
@@ -498,6 +498,13 @@ const disableVmHighAvailability = async (xapi: Xapi, vm: Vm) => {
|
||||
// - for copies/replications only, added after complete transfer
|
||||
// - `other_config[xo:backup:sr]` = sr.uuid
|
||||
//
|
||||
// Attributes on the VDIs of the backed-up VMs:
|
||||
//
|
||||
// - `other_config`:
|
||||
// - `xo:backup:diskId`: identifier used for the disk`
|
||||
// this is automatically filled with its `uuid` if missing
|
||||
// this is used to keep the identity of the VDI accross migrations
|
||||
//
|
||||
// Task logs emitted in a backup execution:
|
||||
//
|
||||
// job.start(data: { mode: Mode, reportWhen: ReportWhen })
|
||||
@@ -1045,6 +1052,18 @@ export default class BackupNg {
|
||||
xapi._assertHealthyVdiChains(vm)
|
||||
}
|
||||
|
||||
{
|
||||
const disks = getVmDisks(vm)
|
||||
await Promise.all(
|
||||
Object.keys(disks).map(ref => {
|
||||
const disk = disks[ref]
|
||||
if (!('xo:backup:diskId' in disk.other_config)) {
|
||||
return disk.update_other_config('xo:backup:diskId', disk.uuid)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const offlineSnapshot: boolean = getSetting(settings, 'offlineSnapshot', [
|
||||
vmUuid,
|
||||
'',
|
||||
@@ -1423,8 +1442,9 @@ export default class BackupNg {
|
||||
|
||||
await asyncMap(remotes, ({ handler }) => {
|
||||
return asyncMap(vdis, async vdi => {
|
||||
const snapshotOf = vdi.$snapshot_of
|
||||
const dir = `${vmDir}/vdis/${jobId}/${snapshotOf.uuid}`
|
||||
const dir = `${vmDir}/vdis/${jobId}/${
|
||||
vdi.other_config['xo:backup:diskId']
|
||||
}`
|
||||
const files = await handler
|
||||
.list(dir, { filter: isVhd })
|
||||
.catch(_ => [])
|
||||
@@ -1455,7 +1475,7 @@ export default class BackupNg {
|
||||
await handler.unlink(`${dir}/${file}`)
|
||||
})
|
||||
if (full) {
|
||||
fullRequired[snapshotOf.$id] = true
|
||||
fullRequired[vdi.$snapshot_of.$id] = true
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1486,7 +1506,7 @@ export default class BackupNg {
|
||||
deltaExport.vdis,
|
||||
vdi =>
|
||||
`vdis/${jobId}/${
|
||||
(xapi.getObject(vdi.snapshot_of): Object).uuid
|
||||
vdi.other_config['xo:backup:diskId']
|
||||
}/${basename}.vhd`
|
||||
),
|
||||
vm,
|
||||
|
||||
@@ -43,6 +43,6 @@ test('VMDKDirectParser reads OK', async () => {
|
||||
}
|
||||
expect(harvested.length).toEqual(2)
|
||||
expect(harvested[0].offsetBytes).toEqual(0)
|
||||
expect(harvested[0].data.length).toEqual(header.grainSizeSectors * 512)
|
||||
expect(harvested[1].offsetBytes).toEqual(header.grainSizeSectors * 512)
|
||||
expect(harvested[0].data.length).toEqual(header['grainSizeSectors'] * 512)
|
||||
expect(harvested[1].offsetBytes).toEqual(header['grainSizeSectors'] * 512)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-web",
|
||||
"version": "5.50.2",
|
||||
"version": "5.50.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
|
||||
@@ -2143,21 +2143,6 @@ const messages = {
|
||||
xosanIssueHostNotInNetwork:
|
||||
'Will configure the host xosan network device with a static IP address and plug it in.',
|
||||
|
||||
// Hub
|
||||
hubPage: 'Hub',
|
||||
noDefaultSr: 'The selected pool has no default SR',
|
||||
successfulInstall: 'VM installed successfully',
|
||||
vmNoAvailable: 'No VMs available ',
|
||||
create: 'Create',
|
||||
hubResourceAlert: 'Resource alert',
|
||||
os: 'OS',
|
||||
version: 'Version',
|
||||
size: 'Size',
|
||||
totalDiskSize: 'Total disk size',
|
||||
hideInstalledPool: 'Already installed templates are hidden',
|
||||
hubSrErrorTitle: 'Missing property',
|
||||
hubImportNotificationTitle: 'XVA import',
|
||||
|
||||
// Licenses
|
||||
xosanUnregisteredDisclaimer:
|
||||
'You are not registered and therefore will not be able to create or manage your XOSAN SRs. {link}',
|
||||
|
||||
@@ -468,14 +468,14 @@ export default class Scheduler extends Component {
|
||||
optionRenderer={getMonthName}
|
||||
options={MONTHS}
|
||||
onChange={this._monthChange}
|
||||
value={cronPatternArr[PICKTIME_TO_ID.month]}
|
||||
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]}
|
||||
monthDayPattern={cronPatternArr[PICKTIME_TO_ID['monthDay']]}
|
||||
weekDayPattern={cronPatternArr[PICKTIME_TO_ID['weekDay']]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -485,7 +485,7 @@ export default class Scheduler extends Component {
|
||||
labelId='Hour'
|
||||
options={HOURS}
|
||||
onChange={this._hourChange}
|
||||
value={cronPatternArr[PICKTIME_TO_ID.hour]}
|
||||
value={cronPatternArr[PICKTIME_TO_ID['hour']]}
|
||||
/>
|
||||
</Col>
|
||||
<Col largeSize={6}>
|
||||
@@ -493,7 +493,7 @@ export default class Scheduler extends Component {
|
||||
labelId='Minute'
|
||||
options={MINS}
|
||||
onChange={this._minuteChange}
|
||||
value={cronPatternArr[PICKTIME_TO_ID.minute]}
|
||||
value={cronPatternArr[PICKTIME_TO_ID['minute']]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -916,7 +916,7 @@ export class SelectResourceSetsVdi extends React.PureComponent {
|
||||
() => this.props.resourceSet,
|
||||
({ objectsByType }) => {
|
||||
const { srPredicate } = this.props
|
||||
const srs = objectsByType.SR
|
||||
const srs = objectsByType['SR']
|
||||
return srPredicate ? filter(srs, srPredicate) : srs
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,11 +58,3 @@ export const setHomeVmIdsSelection = createAction(
|
||||
'SET_HOME_VM_IDS_SELECTION',
|
||||
homeVmIdsSelection => homeVmIdsSelection
|
||||
)
|
||||
export const markHubResourceAsInstalling = createAction(
|
||||
'MARK_HUB_RESOURCE_AS_INSTALLING',
|
||||
id => id
|
||||
)
|
||||
export const markHubResourceAsInstalled = createAction(
|
||||
'MARK_HUB_RESOURCE_AS_INSTALLED',
|
||||
id => id
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import cookies from 'cookies-js'
|
||||
import { omit } from 'lodash'
|
||||
|
||||
import invoke from '../invoke'
|
||||
|
||||
@@ -93,19 +92,6 @@ export default {
|
||||
homeVmIdsSelection,
|
||||
}),
|
||||
|
||||
// whether a resource is currently being installed: `hubInstallingResources[<template id>]`
|
||||
hubInstallingResources: combineActionHandlers(
|
||||
{},
|
||||
{
|
||||
[actions.markHubResourceAsInstalling]: (
|
||||
prevHubInstallingResources,
|
||||
id
|
||||
) => ({ ...prevHubInstallingResources, [id]: true }),
|
||||
[actions.markHubResourceAsInstalled]: (prevHubInstallingResources, id) =>
|
||||
omit(prevHubInstallingResources, id),
|
||||
}
|
||||
),
|
||||
|
||||
objects: combineActionHandlers(
|
||||
{
|
||||
all: {}, // Mutable for performance!
|
||||
|
||||
@@ -252,7 +252,7 @@ export const parseSize = size => {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const NotFound = () => <h1>{_('errorPageNotFound')}</h1>
|
||||
const _NotFound = () => <h1>{_('errorPageNotFound')}</h1>
|
||||
|
||||
// Decorator to declare routes on a component.
|
||||
//
|
||||
@@ -286,7 +286,7 @@ export const routes = (indexRoute, childRoutes) => target => {
|
||||
}
|
||||
|
||||
if (childRoutes) {
|
||||
childRoutes.push({ component: NotFound, path: '*' })
|
||||
childRoutes.push({ component: _NotFound, path: '*' })
|
||||
}
|
||||
|
||||
target.route = {
|
||||
@@ -629,7 +629,7 @@ export const adminOnly = Component =>
|
||||
connectStore({
|
||||
_isAdmin: isAdmin,
|
||||
})(({ _isAdmin, ...props }) =>
|
||||
_isAdmin ? <Component {...props} /> : <NotFound />
|
||||
_isAdmin ? <Component {...props} /> : <_NotFound />
|
||||
)
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -349,10 +349,6 @@ export const subscribeResourceCatalog = createSubscription(() =>
|
||||
_call('cloud.getResourceCatalog')
|
||||
)
|
||||
|
||||
export const subscribeHubResourceCatalog = createSubscription(() =>
|
||||
_call('cloud.getResourceCatalog', { filters: { hub: true } })
|
||||
)
|
||||
|
||||
const getNotificationCookie = () => {
|
||||
const notificationCookie = cookies.get(
|
||||
`notifications:${store.getState().user.id}`
|
||||
@@ -2875,10 +2871,7 @@ export const fixHostNotInXosanNetwork = (xosanSr, host) =>
|
||||
|
||||
// XOSAN packs -----------------------------------------------------------------
|
||||
|
||||
export const getResourceCatalog = ({ filters } = {}) =>
|
||||
_call('cloud.getResourceCatalog', { filters })
|
||||
|
||||
export const getAllResourceCatalog = () => _call('cloud.getAllResourceCatalog')
|
||||
export const getResourceCatalog = () => _call('cloud.getResourceCatalog')
|
||||
|
||||
const downloadAndInstallXosanPack = (pack, pool, { version }) =>
|
||||
_call('xosan.downloadAndInstallXosanPack', {
|
||||
@@ -2887,14 +2880,6 @@ const downloadAndInstallXosanPack = (pack, pool, { version }) =>
|
||||
pool: resolveId(pool),
|
||||
})
|
||||
|
||||
export const downloadAndInstallResource = ({ namespace, id, version, sr }) =>
|
||||
_call('cloud.downloadAndInstallResource', {
|
||||
namespace,
|
||||
id,
|
||||
version,
|
||||
sr: resolveId(sr),
|
||||
})
|
||||
|
||||
import UpdateXosanPacksModal from './update-xosan-packs-modal' // eslint-disable-line import/first
|
||||
export const updateXosanPacks = pool =>
|
||||
confirm({
|
||||
|
||||
@@ -277,10 +277,6 @@
|
||||
@extend .fa;
|
||||
@extend .fa-thumbs-up;
|
||||
}
|
||||
&-deploy {
|
||||
@extend .fa;
|
||||
@extend .fa-rocket;
|
||||
}
|
||||
|
||||
// Backups
|
||||
&-backup {
|
||||
@@ -890,10 +886,6 @@
|
||||
@extend .fa;
|
||||
@extend .fa-database;
|
||||
}
|
||||
&-menu-hub {
|
||||
@extend .fa;
|
||||
@extend .fa-cubes;
|
||||
}
|
||||
// New VM
|
||||
&-new-vm {
|
||||
&-infos {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import _ from 'intl'
|
||||
import decorate from 'apply-decorators'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import { addSubscriptions, adminOnly } from 'utils'
|
||||
import { Container, Col, Row } from 'grid'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { isEmpty, map, omit, orderBy } from 'lodash'
|
||||
import { subscribeHubResourceCatalog } from 'xo'
|
||||
|
||||
import Page from '../page'
|
||||
import Resource from './resource'
|
||||
|
||||
// ==================================================================
|
||||
|
||||
const HEADER = (
|
||||
<h2>
|
||||
<Icon icon='menu-hub' /> {_('hubPage')}
|
||||
</h2>
|
||||
)
|
||||
|
||||
export default decorate([
|
||||
adminOnly,
|
||||
addSubscriptions({
|
||||
catalog: subscribeHubResourceCatalog,
|
||||
}),
|
||||
provideState({
|
||||
computed: {
|
||||
resources: (_, { catalog }) =>
|
||||
orderBy(
|
||||
map(omit(catalog, '_namespaces'), (entry, namespace) => ({
|
||||
namespace,
|
||||
...entry.xva,
|
||||
})),
|
||||
'name',
|
||||
'asc'
|
||||
),
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({ state: { resources } }) => (
|
||||
<Page header={HEADER} title='hubPage' formatTitle>
|
||||
<Container>
|
||||
<Row>
|
||||
{isEmpty(resources) ? (
|
||||
<Col>
|
||||
<h2 className='text-muted'>
|
||||
{_('vmNoAvailable')}
|
||||
<Icon icon='alarm' color='yellow' />
|
||||
</h2>
|
||||
</Col>
|
||||
) : (
|
||||
resources.map(data => (
|
||||
<Col key={data.namespace} mediumSize={6} largeSize={4}>
|
||||
<Resource {...data} />
|
||||
</Col>
|
||||
))
|
||||
)}
|
||||
</Row>
|
||||
</Container>
|
||||
</Page>
|
||||
),
|
||||
])
|
||||
@@ -1,60 +0,0 @@
|
||||
import * as FormGrid from 'form-grid'
|
||||
import _ from 'intl'
|
||||
import decorate from 'apply-decorators'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Container } from 'grid'
|
||||
import { SelectPool } from 'select-objects'
|
||||
import { error } from 'notification'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
|
||||
export default decorate([
|
||||
provideState({
|
||||
initialState: ({ multi }) => ({
|
||||
pools: multi ? [] : undefined,
|
||||
}),
|
||||
effects: {
|
||||
onChangePool(__, pools) {
|
||||
const noDefaultSr = Array.isArray(pools)
|
||||
? pools.some(pool => pool.default_SR === undefined)
|
||||
: pools.default_SR === undefined
|
||||
if (noDefaultSr) {
|
||||
error(_('hubSrErrorTitle'), _('noDefaultSr'))
|
||||
} else {
|
||||
this.props.onChange({
|
||||
pools,
|
||||
pool: pools,
|
||||
})
|
||||
return {
|
||||
pools,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({ effects, install, multi, state, poolPredicate }) => (
|
||||
<Container>
|
||||
<FormGrid.Row>
|
||||
<label>
|
||||
{_('vmImportToPool')}
|
||||
|
||||
{install && (
|
||||
<Tooltip content={_('hideInstalledPool')}>
|
||||
<Icon icon='info' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</label>
|
||||
<SelectPool
|
||||
className='mb-1'
|
||||
multi={multi}
|
||||
onChange={effects.onChangePool}
|
||||
predicate={poolPredicate}
|
||||
required
|
||||
value={state.pools}
|
||||
/>
|
||||
</FormGrid.Row>
|
||||
</Container>
|
||||
),
|
||||
])
|
||||
@@ -1,255 +0,0 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import decorate from 'apply-decorators'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import { Card, CardBlock, CardHeader } from 'card'
|
||||
import { Col, Row } from 'grid'
|
||||
import { alert, form } from 'modal'
|
||||
import { connectStore, formatSize, getXoaPlan } from 'utils'
|
||||
import { createGetObjectsOfType } from 'selectors'
|
||||
import { downloadAndInstallResource, deleteTemplates } from 'xo'
|
||||
import { error, success } from 'notification'
|
||||
import { find, filter } from 'lodash'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { withRouter } from 'react-router'
|
||||
|
||||
import ResourceForm from './resource-form'
|
||||
|
||||
const subscribeAlert = () =>
|
||||
alert(
|
||||
_('hubResourceAlert'),
|
||||
<div>
|
||||
<p>
|
||||
{_('considerSubscribe', {
|
||||
link: 'https://xen-orchestra.com',
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default decorate([
|
||||
withRouter,
|
||||
connectStore(() => {
|
||||
const getTemplates = createGetObjectsOfType('VM-template').sort()
|
||||
const getPools = createGetObjectsOfType('pool')
|
||||
return {
|
||||
templates: getTemplates,
|
||||
pools: getPools,
|
||||
hubInstallingResources: state => state.hubInstallingResources,
|
||||
}
|
||||
}),
|
||||
provideState({
|
||||
initialState: () => ({
|
||||
selectedInstallPools: [],
|
||||
}),
|
||||
effects: {
|
||||
async install() {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
namespace,
|
||||
markHubResourceAsInstalled,
|
||||
markHubResourceAsInstalling,
|
||||
version,
|
||||
} = this.props
|
||||
const { isTemplateInstalled } = this.state
|
||||
if (getXoaPlan() === 'Community') {
|
||||
subscribeAlert()
|
||||
return
|
||||
}
|
||||
const resourceParams = await form({
|
||||
render: props => (
|
||||
<ResourceForm
|
||||
install
|
||||
multi
|
||||
poolPredicate={isTemplateInstalled}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
header: (
|
||||
<span>
|
||||
<Icon icon='add-vm' /> {name}
|
||||
</span>
|
||||
),
|
||||
size: 'medium',
|
||||
})
|
||||
|
||||
markHubResourceAsInstalling(id)
|
||||
try {
|
||||
await Promise.all(
|
||||
resourceParams.pools.map(pool =>
|
||||
downloadAndInstallResource({
|
||||
namespace,
|
||||
id,
|
||||
version,
|
||||
sr: pool.default_SR,
|
||||
})
|
||||
)
|
||||
)
|
||||
success(_('hubImportNotificationTitle'), _('successfulInstall'))
|
||||
} catch (_error) {
|
||||
error(_('hubImportNotificationTitle'), _error.message)
|
||||
}
|
||||
markHubResourceAsInstalled(id)
|
||||
},
|
||||
async create() {
|
||||
const { isPoolCreated, installedTemplates } = this.state
|
||||
const { name } = this.props
|
||||
if (getXoaPlan() === 'Community') {
|
||||
subscribeAlert()
|
||||
return
|
||||
}
|
||||
const resourceParams = await form({
|
||||
render: props => (
|
||||
<ResourceForm poolPredicate={isPoolCreated} {...props} />
|
||||
),
|
||||
header: (
|
||||
<span>
|
||||
<Icon icon='add-vm' /> {name}
|
||||
</span>
|
||||
),
|
||||
size: 'medium',
|
||||
})
|
||||
const { $pool } = resourceParams.pool
|
||||
const template = find(installedTemplates, { $pool })
|
||||
if (template !== undefined) {
|
||||
this.props.router.push(
|
||||
`/vms/new?pool=${$pool}&template=${template.id}`
|
||||
)
|
||||
} else {
|
||||
throw new Error(`can't find template for pool: ${$pool}`)
|
||||
}
|
||||
},
|
||||
async deleteTemplates(__, { name }) {
|
||||
const { isPoolCreated } = this.state
|
||||
const resourceParams = await form({
|
||||
render: props => (
|
||||
<ResourceForm
|
||||
delete
|
||||
multi
|
||||
poolPredicate={isPoolCreated}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
header: (
|
||||
<span>
|
||||
<Icon icon='vm-delete' /> {name}
|
||||
</span>
|
||||
),
|
||||
size: 'medium',
|
||||
})
|
||||
const _templates = filter(this.state.installedTemplates, template =>
|
||||
find(resourceParams.pools, { $pool: template.$pool })
|
||||
)
|
||||
await deleteTemplates(_templates)
|
||||
},
|
||||
updateSelectedInstallPools(_, selectedInstallPools) {
|
||||
return {
|
||||
selectedInstallPools,
|
||||
}
|
||||
},
|
||||
updateSelectedCreatePool(_, selectedCreatePool) {
|
||||
return {
|
||||
selectedCreatePool,
|
||||
}
|
||||
},
|
||||
redirectToTaskPage() {
|
||||
this.props.router.push('/tasks')
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
installedTemplates: (_, { id, templates }) =>
|
||||
filter(templates, ['other.xo:resource:xva:id', id]),
|
||||
isTemplateInstalledOnAllPools: ({ installedTemplates }, { pools }) =>
|
||||
installedTemplates.length > 0 &&
|
||||
pools.every(
|
||||
pool =>
|
||||
installedTemplates.find(template => template.$pool === pool.id) !==
|
||||
undefined
|
||||
),
|
||||
isTemplateInstalled: ({ installedTemplates }) => pool =>
|
||||
installedTemplates.find(template => template.$pool === pool.id) ===
|
||||
undefined,
|
||||
isPoolCreated: ({ installedTemplates }) => pool =>
|
||||
installedTemplates.find(template => template.$pool === pool.id) !==
|
||||
undefined,
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({
|
||||
effects,
|
||||
hubInstallingResources,
|
||||
id,
|
||||
name,
|
||||
os,
|
||||
size,
|
||||
state,
|
||||
totalDiskSize,
|
||||
version,
|
||||
}) => (
|
||||
<Card shadow>
|
||||
<CardHeader>
|
||||
{name}
|
||||
<ActionButton
|
||||
className='pull-right'
|
||||
color='light'
|
||||
data-name={name}
|
||||
disabled={state.installedTemplates.length === 0}
|
||||
handler={effects.deleteTemplates}
|
||||
icon='delete'
|
||||
size='small'
|
||||
style={{ border: 'none' }}
|
||||
tooltip={_('remove')}
|
||||
/>
|
||||
<br />
|
||||
</CardHeader>
|
||||
<CardBlock className='text-center'>
|
||||
<div>
|
||||
<span className='text-muted'>{_('os')}</span> <strong>{os}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted'>{_('version')}</span>
|
||||
{' '}
|
||||
<strong>{version}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted'>{_('size')}</span>
|
||||
{' '}
|
||||
<strong>{formatSize(size)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted'>{_('totalDiskSize')}</span>
|
||||
{' '}
|
||||
<strong>{formatSize(totalDiskSize)}</strong>
|
||||
</div>
|
||||
<hr />
|
||||
<Row>
|
||||
<Col mediumSize={6}>
|
||||
<ActionButton
|
||||
block
|
||||
disabled={state.isTemplateInstalledOnAllPools}
|
||||
form={state.idInstallForm}
|
||||
handler={effects.install}
|
||||
icon='add'
|
||||
pending={hubInstallingResources[id]}
|
||||
>
|
||||
{_('install')}
|
||||
</ActionButton>
|
||||
</Col>
|
||||
<Col mediumSize={6}>
|
||||
<ActionButton
|
||||
block
|
||||
disabled={state.installedTemplates.length === 0}
|
||||
form={state.idCreateForm}
|
||||
handler={effects.create}
|
||||
icon='deploy'
|
||||
>
|
||||
{_('create')}
|
||||
</ActionButton>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBlock>
|
||||
</Card>
|
||||
),
|
||||
])
|
||||
@@ -27,7 +27,6 @@ import BackupNg from './backup-ng'
|
||||
import Dashboard from './dashboard'
|
||||
import Home from './home'
|
||||
import Host from './host'
|
||||
import Hub from './hub'
|
||||
import Jobs from './jobs'
|
||||
import Menu from './menu'
|
||||
import Modal, { alert, FormModal } from 'modal'
|
||||
@@ -94,7 +93,6 @@ const BODY_STYLE = {
|
||||
'vms/:id': Vm,
|
||||
xoa: Xoa,
|
||||
xosan: Xosan,
|
||||
hub: Hub,
|
||||
})
|
||||
@connectStore(state => {
|
||||
return {
|
||||
|
||||
@@ -354,11 +354,6 @@ export default class Menu extends Component {
|
||||
},
|
||||
],
|
||||
},
|
||||
isAdmin && {
|
||||
to: '/hub',
|
||||
icon: 'menu-hub',
|
||||
label: 'hubPage',
|
||||
},
|
||||
isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
|
||||
!noOperatablePools && {
|
||||
to: '/tasks',
|
||||
|
||||
@@ -259,7 +259,7 @@ class Vif extends BaseComponent {
|
||||
props.pool === undefined // to get objects as a self user
|
||||
),
|
||||
srs: getSrs(state, props),
|
||||
template: getTemplate(state, props, props.pool === undefined),
|
||||
template: getTemplate(state, props),
|
||||
templates: getTemplates(state, props),
|
||||
userSshKeys: getUserSshKeys(state, props),
|
||||
})
|
||||
@@ -280,12 +280,7 @@ export default class NewVm extends BaseComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._reset(() => {
|
||||
const { template } = this.props
|
||||
if (template !== undefined) {
|
||||
this._initTemplate(this.props.template)
|
||||
}
|
||||
})
|
||||
this._reset()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -343,31 +338,28 @@ export default class NewVm extends BaseComponent {
|
||||
|
||||
// Actions ---------------------------------------------------------------------
|
||||
|
||||
_reset = callback => {
|
||||
this._replaceState(
|
||||
{
|
||||
bootAfterCreate: true,
|
||||
CPUs: '',
|
||||
cpuCap: '',
|
||||
cpuWeight: '',
|
||||
existingDisks: {},
|
||||
fastClone: true,
|
||||
hvmBootFirmware: '',
|
||||
installMethod: 'noConfigDrive',
|
||||
multipleVms: false,
|
||||
name_label: '',
|
||||
name_description: '',
|
||||
nameLabels: map(Array(NB_VMS_MIN), (_, index) => `VM_${index + 1}`),
|
||||
namePattern: '{name}%',
|
||||
nbVms: NB_VMS_MIN,
|
||||
VDIs: [],
|
||||
VIFs: [],
|
||||
seqStart: 1,
|
||||
share: false,
|
||||
tags: [],
|
||||
},
|
||||
callback
|
||||
)
|
||||
_reset = () => {
|
||||
this._replaceState({
|
||||
bootAfterCreate: true,
|
||||
CPUs: '',
|
||||
cpuCap: '',
|
||||
cpuWeight: '',
|
||||
existingDisks: {},
|
||||
fastClone: true,
|
||||
hvmBootFirmware: '',
|
||||
installMethod: 'noConfigDrive',
|
||||
multipleVms: false,
|
||||
name_label: '',
|
||||
name_description: '',
|
||||
nameLabels: map(Array(NB_VMS_MIN), (_, index) => `VM_${index + 1}`),
|
||||
namePattern: '{name}%',
|
||||
nbVms: NB_VMS_MIN,
|
||||
VDIs: [],
|
||||
VIFs: [],
|
||||
seqStart: 1,
|
||||
share: false,
|
||||
tags: [],
|
||||
})
|
||||
}
|
||||
|
||||
_selfCreate = () => {
|
||||
|
||||
@@ -475,7 +475,7 @@ export default class New extends Component {
|
||||
}
|
||||
|
||||
_handleAuthChoice = () => {
|
||||
const auth = this.refs.auth.checked
|
||||
const auth = this.refs['auth'].checked
|
||||
this.setState({
|
||||
auth,
|
||||
})
|
||||
|
||||
@@ -154,7 +154,7 @@ class IpsCell extends BaseComponent {
|
||||
value={newIps || ''}
|
||||
/>{' '}
|
||||
<ActionButton
|
||||
form='newIpForm'
|
||||
form={`newIpForm`}
|
||||
icon='save'
|
||||
btnStyle='primary'
|
||||
handler={this._addIps}
|
||||
|
||||
@@ -65,7 +65,7 @@ require('exec-promise')(() =>
|
||||
const originalScripts = scripts
|
||||
|
||||
if (!pkg.private && !('postversion' in scripts)) {
|
||||
scripts = { ...scripts, postversion: 'npm publish --access public' }
|
||||
scripts = { ...scripts, postversion: 'npm publish' }
|
||||
}
|
||||
|
||||
const prepublish = scripts.prepublish
|
||||
|
||||
Reference in New Issue
Block a user