Compare commits

...

11 Commits

Author SHA1 Message Date
Julien Fontanet
30f6ca187d WiP: feat(xo-server/delta backup): handle VDI migration
Fixes #2896

Store its UUID in metadata to avoid a delta afteter a migration.
2019-09-25 11:31:08 +02:00
Julien Fontanet
b24400b21d feat(xen-api): support IPv6 addresses (#4521)
Fixes #4520
2019-09-24 13:59:49 +02:00
Pierre Donias
6c1d651687 fix(xo-server/host.isHostServerTimeConsistent): dont return false on check failure (#4540) 2019-09-24 10:46:43 +02:00
Pierre Donias
e7757b53e7 feat(xo-web/new-vm): remove cloud init plan limitation (#4543) 2019-09-24 10:24:27 +02:00
Julien Fontanet
a6d182e92d feat(xo-server/getBackupNgLogs): implement debounce (#4509) (#4541)
Similar to #4509

Fixes xoa-support#1676

For now, the delay is set to 10s which is the duration used by xo-web's
subscription, which makes it enough to make it independent of the number of
clients.

In the future, this could be configurable, but we may simply do the
consolidation only once during the backup execution.
2019-09-23 16:20:17 +02:00
Julien Fontanet
925eca1463 chore(xo-server/api): context has methods bound to XO
This makes sure the correct context is used, which is necessary for properties write and for debouncing.
2019-09-23 15:59:45 +02:00
Julien Fontanet
8b454f0d39 chore(xo-server/MultiKeyMap): add basic tests 2019-09-23 15:14:54 +02:00
Pierre Donias
7c4d110353 feat(xo-web/settings/logs): identify XAPI errors (#4385)
Fixes #4101
2019-09-23 10:31:34 +02:00
Julien Fontanet
6df55523b6 feat(xo-web): display node in list of packages 2019-09-20 17:22:21 +02:00
badrAZ
3ec6a24634 chore(CHANGELOG): update next 2019-09-20 16:36:04 +02:00
badrAZ
164b4218c4 feat(xo-web): 5.50.0 2019-09-20 16:21:38 +02:00
16 changed files with 308 additions and 218 deletions

View File

@@ -6,6 +6,12 @@
- [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)
- [Backups] Improve performance by caching VM backups listing (PR [#4509](https://github.com/vatesfr/xen-orchestra/pull/4509))
### Bug fixes
@@ -15,12 +21,25 @@
- [Network] Fix inability to create a bonded network (PR [#4489](https://github.com/vatesfr/xen-orchestra/pull/4489))
- [Backup restore & Replication] Don't copy `sm_config` to new VDIs which might leads to useless coalesces [#4482](https://github.com/vatesfr/xen-orchestra/issues/4482) (PR [#4484](https://github.com/vatesfr/xen-orchestra/pull/4484))
- [Home] Fix intermediary "no results" display showed on filtering items [#4420](https://github.com/vatesfr/xen-orchestra/issues/4420) (PR [#4456](https://github.com/vatesfr/xen-orchestra/pull/4456)
- [Backup NG/New schedule] Properly show user errors in the form [#3831](https://github.com/vatesfr/xen-orchestra/issues/3831) (PR [#4131](https://github.com/vatesfr/xen-orchestra/pull/4131))
- [VM/Advanced] Fix `"vm.set_domain_type" is not a function` error on switching virtualization mode (PV/HVM) [#4348](https://github.com/vatesfr/xen-orchestra/issues/4348) (PR [#4504](https://github.com/vatesfr/xen-orchestra/pull/4504))
- [Backup NG/logs] Show warning when zstd compression is selected but not supported [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PR [#4375](https://github.com/vatesfr/xen-orchestra/pull/4375)
- [Patches] Fix patches installation for CH 8.0 (PR [#4511](https://github.com/vatesfr/xen-orchestra/pull/4511))
- [Network] Fix inability to set a network name [#4514](https://github.com/vatesfr/xen-orchestra/issues/4514) (PR [4510](https://github.com/vatesfr/xen-orchestra/pull/4510))
- [Backup NG] Fix race conditions that could lead to disabled jobs still running (PR [4510](https://github.com/vatesfr/xen-orchestra/pull/4510))
- [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))
### Released packages
- xo-server-sdn-controller v0.2.1
- xo-server v5.49.0
- xo-web v5.49.0
- @xen-orchestra/cron v1.0.4
- xo-server-sdn-controller v0.3.0
- @xen-orchestra/template v0.1.0
- xo-server v5.50.0
- xo-web v5.50.0
## **5.38.0** (2019-08-29)

View File

@@ -7,27 +7,17 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [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)
- [Backups] Improve performance by caching VM backups listing (PR [#4509](https://github.com/vatesfr/xen-orchestra/pull/4509))
- [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
- [Backup NG/New schedule] Properly show user errors in the form [#3831](https://github.com/vatesfr/xen-orchestra/issues/3831) (PR [#4131](https://github.com/vatesfr/xen-orchestra/pull/4131))
> Users must be able to say: “I had this issue, happy to know it's fixed”
- [VM/Advanced] Fix `"vm.set_domain_type" is not a function` error on switching virtualization mode (PV/HVM) [#4348](https://github.com/vatesfr/xen-orchestra/issues/4348) (PR [#4504](https://github.com/vatesfr/xen-orchestra/pull/4504))
- [Backup NG/logs] Show warning when zstd compression is selected but not supported [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PR [#4375](https://github.com/vatesfr/xen-orchestra/pull/4375)
- [Patches] Fix patches installation for CH 8.0 (PR [#4511](https://github.com/vatesfr/xen-orchestra/pull/4511))
- [Network] Fix inability to set a network name [#4514](https://github.com/vatesfr/xen-orchestra/issues/4514) (PR [4510](https://github.com/vatesfr/xen-orchestra/pull/4510))
- [Backup NG] Fix race conditions that could lead to disabled jobs still running (PR [4510](https://github.com/vatesfr/xen-orchestra/pull/4510))
- [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
@@ -36,8 +26,6 @@
>
> Rule of thumb: add packages on top.
- @xen-orchestra/template v0.0.0
- @xen-orchestra/cron v1.0.4
- xo-server-sdn-controller v0.3.0
- xo-server v5.50.0
- xo-web v5.50.0
- xen-api v0.27.2
- xo-server v5.51.0
- xo-web v5.51.0

View File

@@ -1,4 +1,4 @@
const URL_RE = /^(?:(https?:)\/*)?(?:([^:]+):([^@]+)@)?([^/]+?)(?::([0-9]+))?\/?$/
const URL_RE = /^(?:(https?:)\/*)?(?:([^:]+):([^@]+)@)?(?:\[([^\]]+)\]|([^:/]+))(?::([0-9]+))?\/?$/
export default url => {
const matches = URL_RE.exec(url)
@@ -6,7 +6,15 @@ export default url => {
throw new Error('invalid URL: ' + url)
}
const [, protocol = 'https:', username, password, hostname, port] = matches
const [
,
protocol = 'https:',
username,
password,
ipv6,
hostname = ipv6,
port,
] = matches
const parsedUrl = { protocol, hostname, port }
if (username !== undefined) {
parsedUrl.username = decodeURIComponent(username)

View File

@@ -46,6 +46,7 @@
"archiver": "^3.0.0",
"async-iterator-to-stream": "^1.0.1",
"base64url": "^3.0.0",
"bind-property-descriptor": "^1.0.0",
"blocked": "^1.2.1",
"bluebird": "^3.5.1",
"body-parser": "^1.18.2",

View File

@@ -0,0 +1,34 @@
/* eslint-env jest */
import MultiKeyMap from './_MultiKeyMap'
describe('MultiKeyMap', () => {
it('works', () => {
const map = new MultiKeyMap()
const keys = [
// null key
[],
// simple key
['foo'],
// composite key
['foo', 'bar'],
// reverse composite key
['bar', 'foo'],
]
const values = keys.map(() => ({}))
// set all values first to make sure they are all stored and not only the
// last one
keys.forEach((key, i) => {
map.set(key, values[i])
})
keys.forEach((key, i) => {
// copy the key to make sure the array itself is not the key
expect(map.get(key.slice())).toBe(values[i])
map.delete(key.slice())
expect(map.get(key.slice())).toBe(undefined)
})
})
})

View File

@@ -221,12 +221,7 @@ emergencyShutdownHost.resolve = {
// -------------------------------------------------------------------
export async function isHostServerTimeConsistent({ host }) {
try {
await this.getXapi(host).assertConsistentHostServerTime(host._xapiRef)
return true
} catch (e) {
return false
}
return this.getXapi(host).isHostServerTimeConsistent(host._xapiRef)
}
isHostServerTimeConsistent.params = {

View File

@@ -2041,6 +2041,7 @@ export default class Xapi extends XapiBase {
)
)
}
@deferrable
async createNetwork(
$defer,
@@ -2358,14 +2359,22 @@ export default class Xapi extends XapiBase {
)
}
async assertConsistentHostServerTime(hostRef) {
const delta =
async _getHostServerTimeShift(hostRef) {
return Math.abs(
parseDateTime(await this.call('host.get_servertime', hostRef)).getTime() -
Date.now()
if (Math.abs(delta) > 30e3) {
Date.now()
)
}
async isHostServerTimeConsistent(hostRef) {
return (await this._getHostServerTimeShift(hostRef)) < 30e3
}
async assertConsistentHostServerTime(hostRef) {
if (!(await this.isHostServerTimeConsistent(hostRef))) {
throw new Error(
`host server time and XOA date are not consistent with each other (${ms(
delta
await this._getHostServerTimeShift(hostRef)
)})`
)
}

View File

@@ -3,6 +3,7 @@ import kindOf from 'kindof'
import ms from 'ms'
import schemaInspector from 'schema-inspector'
import { forEach, isFunction } from 'lodash'
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
import { MethodNotFound } from 'json-rpc-peer'
import * as methods from '../api'
@@ -219,17 +220,29 @@ export default class Api {
throw new MethodNotFound(name)
}
// FIXME: it can cause issues if there any property assignments in
// XO methods called from the API.
const context = Object.create(xo, {
api: {
// Used by system.*().
value: this,
},
session: {
value: session,
},
})
// create the context which is an augmented XO
const context = (() => {
const descriptors = {
api: {
// Used by system.*().
value: this,
},
session: {
value: session,
},
}
let obj = xo
do {
Object.getOwnPropertyNames(obj).forEach(name => {
if (!(name in descriptors)) {
descriptors[name] = getBoundPropertyDescriptor(obj, name, xo)
}
})
} while ((obj = Reflect.getPrototypeOf(obj)) !== null)
return Object.create(null, descriptors)
})()
// Fetch and inject the current user.
const userId = session.get('user_id', undefined)

View File

@@ -1,6 +1,8 @@
import ms from 'ms'
import { forEach, isEmpty, iteratee, sortedIndexBy } from 'lodash'
import { debounceWithKey } from '../_pDebounceWithKey'
const isSkippedError = error =>
error.message === 'no disks found' ||
error.message === 'no VMs match this pattern' ||
@@ -64,131 +66,138 @@ const taskTimeComparator = ({ start: s1, end: e1 }, { start: s2, end: e2 }) => {
// tasks?: Task[],
// }
export default {
async getBackupNgLogs(runId?: string) {
const [jobLogs, restoreLogs, restoreMetadataLogs] = await Promise.all([
this.getLogs('jobs'),
this.getLogs('restore'),
this.getLogs('metadataRestore'),
])
getBackupNgLogs: debounceWithKey(
async function getBackupNgLogs(runId?: string) {
const [jobLogs, restoreLogs, restoreMetadataLogs] = await Promise.all([
this.getLogs('jobs'),
this.getLogs('restore'),
this.getLogs('metadataRestore'),
])
const { runningJobs, runningRestores, runningMetadataRestores } = this
const consolidated = {}
const started = {}
const { runningJobs, runningRestores, runningMetadataRestores } = this
const consolidated = {}
const started = {}
const handleLog = ({ data, time, message }, id) => {
const { event } = data
if (event === 'job.start') {
if (
(data.type === 'backup' || data.key === undefined) &&
(runId === undefined || runId === id)
) {
const { scheduleId, jobId } = data
consolidated[id] = started[id] = {
const handleLog = ({ data, time, message }, id) => {
const { event } = data
if (event === 'job.start') {
if (
(data.type === 'backup' || data.key === undefined) &&
(runId === undefined || runId === id)
) {
const { scheduleId, jobId } = data
consolidated[id] = started[id] = {
data: data.data,
id,
jobId,
jobName: data.jobName,
message: 'backup',
scheduleId,
start: time,
status: runningJobs[jobId] === id ? 'pending' : 'interrupted',
}
}
} else if (event === 'job.end') {
const { runJobId } = data
const log = started[runJobId]
if (log !== undefined) {
delete started[runJobId]
log.end = time
log.status = computeStatusAndSortTasks(
getStatus((log.result = data.error)),
log.tasks
)
}
} else if (event === 'task.start') {
const task = {
data: data.data,
id,
jobId,
jobName: data.jobName,
message: 'backup',
scheduleId,
message,
start: time,
status: runningJobs[jobId] === id ? 'pending' : 'interrupted',
}
const { parentId } = data
let parent
if (parentId === undefined && (runId === undefined || runId === id)) {
// top level task
task.status =
(message === 'restore' && !runningRestores.has(id)) ||
(message === 'metadataRestore' &&
!runningMetadataRestores.has(id))
? 'interrupted'
: 'pending'
consolidated[id] = started[id] = task
} else if ((parent = started[parentId]) !== undefined) {
// sub-task for which the parent exists
task.status = parent.status
started[id] = task
;(parent.tasks || (parent.tasks = [])).push(task)
}
} else if (event === 'task.end') {
const { taskId } = data
const log = started[taskId]
if (log !== undefined) {
// TODO: merge/transfer work-around
delete started[taskId]
log.end = time
log.status = computeStatusAndSortTasks(
getStatus((log.result = data.result), data.status),
log.tasks
)
}
} else if (event === 'task.warning') {
const parent = started[data.taskId]
parent !== undefined &&
(parent.warnings || (parent.warnings = [])).push({
data: data.data,
message,
})
} else if (event === 'task.info') {
const parent = started[data.taskId]
parent !== undefined &&
(parent.infos || (parent.infos = [])).push({
data: data.data,
message,
})
} else if (event === 'jobCall.start') {
const parent = started[data.runJobId]
if (parent !== undefined) {
;(parent.tasks || (parent.tasks = [])).push(
(started[id] = {
data: {
type: 'VM',
id: data.params.id,
},
id,
start: time,
status: parent.status,
})
)
}
} else if (event === 'jobCall.end') {
const { runCallId } = data
const log = started[runCallId]
if (log !== undefined) {
delete started[runCallId]
log.end = time
log.status = computeStatusAndSortTasks(
getStatus((log.result = data.error)),
log.tasks
)
}
}
} else if (event === 'job.end') {
const { runJobId } = data
const log = started[runJobId]
if (log !== undefined) {
delete started[runJobId]
log.end = time
log.status = computeStatusAndSortTasks(
getStatus((log.result = data.error)),
log.tasks
)
}
} else if (event === 'task.start') {
const task = {
data: data.data,
id,
message,
start: time,
}
const { parentId } = data
let parent
if (parentId === undefined && (runId === undefined || runId === id)) {
// top level task
task.status =
(message === 'restore' && !runningRestores.has(id)) ||
(message === 'metadataRestore' && !runningMetadataRestores.has(id))
? 'interrupted'
: 'pending'
consolidated[id] = started[id] = task
} else if ((parent = started[parentId]) !== undefined) {
// sub-task for which the parent exists
task.status = parent.status
started[id] = task
;(parent.tasks || (parent.tasks = [])).push(task)
}
} else if (event === 'task.end') {
const { taskId } = data
const log = started[taskId]
if (log !== undefined) {
// TODO: merge/transfer work-around
delete started[taskId]
log.end = time
log.status = computeStatusAndSortTasks(
getStatus((log.result = data.result), data.status),
log.tasks
)
}
} else if (event === 'task.warning') {
const parent = started[data.taskId]
parent !== undefined &&
(parent.warnings || (parent.warnings = [])).push({
data: data.data,
message,
})
} else if (event === 'task.info') {
const parent = started[data.taskId]
parent !== undefined &&
(parent.infos || (parent.infos = [])).push({
data: data.data,
message,
})
} else if (event === 'jobCall.start') {
const parent = started[data.runJobId]
if (parent !== undefined) {
;(parent.tasks || (parent.tasks = [])).push(
(started[id] = {
data: {
type: 'VM',
id: data.params.id,
},
id,
start: time,
status: parent.status,
})
)
}
} else if (event === 'jobCall.end') {
const { runCallId } = data
const log = started[runCallId]
if (log !== undefined) {
delete started[runCallId]
log.end = time
log.status = computeStatusAndSortTasks(
getStatus((log.result = data.error)),
log.tasks
)
}
}
forEach(jobLogs, handleLog)
forEach(restoreLogs, handleLog)
forEach(restoreMetadataLogs, handleLog)
return runId === undefined ? consolidated : consolidated[runId]
},
10e3,
function keyFn(runId) {
return [this, runId]
}
forEach(jobLogs, handleLog)
forEach(restoreLogs, handleLog)
forEach(restoreMetadataLogs, handleLog)
return runId === undefined ? consolidated : consolidated[runId]
},
),
async getBackupNgLogsSorted({ after, before, filter, limit }) {
let logs = await this.getBackupNgLogs()

View File

@@ -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,

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "xo-web",
"version": "5.49.0",
"version": "5.50.0",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [

View File

@@ -92,5 +92,3 @@ export const DEFAULT_NETWORK_CONFIG_TEMPLATE = `#network:
# name: eth0
# subnets:
# - type: dhcp`
export const CAN_CLOUD_INIT = +process.env.XOA_PLAN > 3

View File

@@ -1318,7 +1318,6 @@ const messages = {
newVmSshKey: 'SSH key',
noConfigDrive: 'No config drive',
newVmCustomConfig: 'Custom config',
premiumOnly: 'Only available in Premium',
availableTemplateVarsInfo:
'Click here to see the available template variables',
availableTemplateVarsTitle: 'Available template variables',
@@ -1932,6 +1931,7 @@ const messages = {
logUser: 'User',
logMessage: 'Message',
logSuggestXcpNg: 'Use XCP-ng to get rid of restrictions',
logXapiError: 'This is a XenServer/XCP-ng error',
logError: 'Error',
logTitle: 'Logs',
logDisplayDetails: 'Display details',

View File

@@ -21,7 +21,6 @@ import { Container, Row, Col } from 'grid'
import { injectIntl } from 'react-intl'
import {
AvailableTemplateVars,
CAN_CLOUD_INIT,
DEFAULT_CLOUD_CONFIG_TEMPLATE,
DEFAULT_NETWORK_CONFIG_TEMPLATE,
NetworkConfigInfo,
@@ -1189,20 +1188,17 @@ export default class NewVm extends BaseComponent {
</LineItem>
<br />
<LineItem>
<Tooltip content={CAN_CLOUD_INIT ? undefined : _('premiumOnly')}>
<label>
<input
checked={installMethod === 'SSH'}
disabled={!CAN_CLOUD_INIT}
name='installMethod'
onChange={this._linkState('installMethod')}
type='radio'
value='SSH'
/>
&nbsp;
{_('newVmSshKey')}
</label>
</Tooltip>
<label>
<input
checked={installMethod === 'SSH'}
name='installMethod'
onChange={this._linkState('installMethod')}
type='radio'
value='SSH'
/>
&nbsp;
{_('newVmSshKey')}
</label>
&nbsp;
<span className={classNames('input-group', styles.fixedWidth)}>
<DebounceInput
@@ -1230,20 +1226,17 @@ export default class NewVm extends BaseComponent {
</LineItem>
<br />
<LineItem>
<Tooltip content={CAN_CLOUD_INIT ? undefined : _('premiumOnly')}>
<label>
<input
checked={installMethod === 'customConfig'}
disabled={!CAN_CLOUD_INIT}
name='installMethod'
onChange={this._linkState('installMethod')}
type='radio'
value='customConfig'
/>
&nbsp;
{_('newVmCustomConfig')}
</label>
</Tooltip>
<label>
<input
checked={installMethod === 'customConfig'}
name='installMethod'
onChange={this._linkState('installMethod')}
type='radio'
value='customConfig'
/>
&nbsp;
{_('newVmCustomConfig')}
</label>
&nbsp;
<AvailableTemplateVars />
&nbsp;

View File

@@ -12,6 +12,7 @@ import { addSubscriptions, downloadLog } from 'utils'
import { alert } from 'modal'
import { createSelector } from 'selectors'
import { CAN_REPORT_BUG, reportBug } from 'report-bug-button'
import { get } from '@xen-orchestra/defined'
import {
deleteApiLog,
deleteApiLogs,
@@ -33,6 +34,22 @@ const formatLog = log =>
2
)}\n${JSON.stringify(log.data.error, null, 2).replace(/\\n/g, '\n')}`
const LogMessage = ({ item: log }) => {
const { error } = log.data
return (
<span>
<pre className={styles.widthLimit}>{get(() => error.message)}</pre>
{get(() => error.code) === 'LICENCE_RESTRICTION' ? (
<a href='https://xcp-ng.org/' rel='noopener noreferrer' target='_blank'>
{_('logSuggestXcpNg')}
</a>
) : get(() => error.name) === 'XapiError' ? (
_('logXapiError')
) : null}
</span>
)
}
const COLUMNS = [
{
name: _('logUser'),
@@ -50,22 +67,7 @@ const COLUMNS = [
},
{
name: _('logMessage'),
itemRenderer: log => (
<span>
<pre className={styles.widthLimit}>
{log.data.error && log.data.error.message}
</pre>
{log.data.error && log.data.error.code === 'LICENCE_RESTRICTION' && (
<a
href='https://xcp-ng.org/'
rel='noopener noreferrer'
target='_blank'
>
{_('logSuggestXcpNg')}
</a>
)}
</span>
),
component: LogMessage,
sortCriteria: log => log.data.error && log.data.error.message,
},
{

View File

@@ -221,11 +221,12 @@ const Updates = decorate([
? () => ({ 'xen-orchestra': 'sources' })
: async function() {
const {
engine,
installer,
updater,
npm,
} = await xoaUpdater.getLocalManifest()
return { ...installer, ...updater, ...npm }
return { ...engine, ...installer, ...updater, ...npm }
},
isDisconnected: (_, { xoaUpdaterState }) =>
xoaUpdater === 'disconnected' || xoaUpdaterState === 'error',