Compare commits
5 Commits
xo5/rebind
...
linkSuspen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3624ae804 | ||
|
|
73c607a0a7 | ||
|
|
dcaa2ed75b | ||
|
|
eedaca0195 | ||
|
|
9ffa52cc01 |
@@ -9,6 +9,7 @@
|
||||
|
||||
- Disable search engine indexing via a `robots.txt`
|
||||
- [Stats] Support format used by XAPI 23.31
|
||||
- [Storage/Disks] Handle link to VM for suspended VDIs (PR [#7391](https://github.com/vatesfr/xen-orchestra/pull/7391))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -40,6 +41,6 @@
|
||||
- vhd-lib patch
|
||||
- xo-server minor
|
||||
- xo-server-audit patch
|
||||
- xo-web patch
|
||||
- xo-web minor
|
||||
|
||||
<!--packages-end-->
|
||||
|
||||
15
docs/xoa.md
15
docs/xoa.md
@@ -93,6 +93,21 @@ Follow the instructions:
|
||||
|
||||
You can also download XOA from xen-orchestra.com in an XVA file. Once you've got the XVA file, you can import it with `xe vm-import filename=xoa_unified.xva` or via XenCenter.
|
||||
|
||||
If you want to use static IP address for your appliance:
|
||||
|
||||
```sh
|
||||
xe vm-param-set uuid="$uuid" \
|
||||
xenstore-data:vm-data/ip="$ip" \
|
||||
xenstore-data:vm-data/netmask="$netmask" \
|
||||
xenstore-data:vm-data/gateway="$gateway"
|
||||
```
|
||||
|
||||
If you want to replace the default DNS server:
|
||||
|
||||
```sh
|
||||
xe vm-param-set uuid="$uuid" xenstore-data:vm-data/dns="$dns"
|
||||
```
|
||||
|
||||
After the VM is imported, you just need to start it with `xe vm-start vm="XOA"` or with XenCenter.
|
||||
|
||||
## First console connection
|
||||
|
||||
@@ -413,6 +413,7 @@ const TRANSFORMS = {
|
||||
startTime: metrics && toTimestamp(metrics.start_time),
|
||||
secureBoot: obj.platform.secureboot === 'true',
|
||||
suspendSr: link(obj, 'suspend_SR'),
|
||||
suspendVdi: link(obj, 'suspend_VDI'),
|
||||
tags: obj.tags,
|
||||
VIFs: link(obj, 'VIFs'),
|
||||
VTPMs: link(obj, 'VTPMs'),
|
||||
@@ -449,7 +450,6 @@ const TRANSFORMS = {
|
||||
|
||||
vm.snapshot_time = toTimestamp(obj.snapshot_time)
|
||||
vm.$snapshot_of = link(obj, 'snapshot_of')
|
||||
vm.suspendVdi = link(obj, 'suspend_VDI')
|
||||
} else if (obj.is_a_template) {
|
||||
const defaultTemplate = isDefaultTemplate(obj)
|
||||
vm.type += '-template'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy.js'
|
||||
import { basename } from 'path'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
import { format, parse } from 'xo-remote-parser'
|
||||
import {
|
||||
DEFAULT_ENCRYPTION_ALGORITHM,
|
||||
@@ -17,17 +18,35 @@ import { Remotes } from '../models/remote.mjs'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const { warn } = createLogger('xo:mixins:remotes')
|
||||
|
||||
const obfuscateRemote = ({ url, ...remote }) => {
|
||||
const parsedUrl = parse(url)
|
||||
remote.url = format(sensitiveValues.obfuscate(parsedUrl))
|
||||
return remote
|
||||
}
|
||||
|
||||
function validatePath(url) {
|
||||
const { path } = parse(url)
|
||||
// these properties should be defined on the remote object itself and not as
|
||||
// part of the remote URL
|
||||
//
|
||||
// there is a bug somewhere that keep putting them into the URL, this list
|
||||
// is here to help track it
|
||||
const INVALID_URL_PARAMS = ['benchmarks', 'id', 'info', 'name', 'proxy', 'enabled', 'error', 'url']
|
||||
|
||||
function validateUrl(url) {
|
||||
const parsedUrl = parse(url)
|
||||
|
||||
const { path } = parsedUrl
|
||||
if (path !== undefined && basename(path) === 'xo-vm-backups') {
|
||||
throw invalidParameters('remote url should not end with xo-vm-backups')
|
||||
}
|
||||
|
||||
for (const param of INVALID_URL_PARAMS) {
|
||||
if (Object.hasOwn(parsedUrl, param)) {
|
||||
// log with stack trace
|
||||
warn(new Error('invalid remote URL param ' + param))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class {
|
||||
@@ -182,6 +201,22 @@ export default class {
|
||||
if (remote === undefined) {
|
||||
throw noSuchObject(id, 'remote')
|
||||
}
|
||||
|
||||
const parsedUrl = parse(remote.url)
|
||||
let fixed = false
|
||||
for (const param of INVALID_URL_PARAMS) {
|
||||
if (Object.hasOwn(parsedUrl, param)) {
|
||||
// delete the value to trace its real origin when it's added back
|
||||
// with `updateRemote()`
|
||||
delete parsedUrl[param]
|
||||
fixed = true
|
||||
}
|
||||
}
|
||||
if (fixed) {
|
||||
remote.url = format(parsedUrl)
|
||||
this._remotes.update(remote).catch(warn)
|
||||
}
|
||||
|
||||
return remote
|
||||
}
|
||||
|
||||
@@ -202,7 +237,7 @@ export default class {
|
||||
}
|
||||
|
||||
async createRemote({ name, options, proxy, url }) {
|
||||
validatePath(url)
|
||||
validateUrl(url)
|
||||
|
||||
const params = {
|
||||
enabled: false,
|
||||
@@ -219,6 +254,10 @@ export default class {
|
||||
}
|
||||
|
||||
updateRemote(id, { enabled, name, options, proxy, url }) {
|
||||
if (url !== undefined) {
|
||||
validateUrl(url)
|
||||
}
|
||||
|
||||
const handlers = this._handlers
|
||||
const handler = handlers[id]
|
||||
if (handler !== undefined) {
|
||||
@@ -238,7 +277,7 @@ export default class {
|
||||
@synchronized()
|
||||
async _updateRemote(id, { url, ...props }) {
|
||||
if (url !== undefined) {
|
||||
validatePath(url)
|
||||
validateUrl(url)
|
||||
}
|
||||
|
||||
const remote = await this._getRemote(id)
|
||||
|
||||
@@ -12,7 +12,7 @@ import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import TabButton from 'tab-button'
|
||||
import renderXoItem, { Vdi } from 'render-xo-item'
|
||||
import renderXoItem, { Vdi, Vm } from 'render-xo-item'
|
||||
import { confirm } from 'modal'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { Text } from 'editable'
|
||||
@@ -123,67 +123,76 @@ const COLUMNS = [
|
||||
vms: getAllVms(state, props),
|
||||
vbds: getVbds(state, props),
|
||||
})
|
||||
})(({ vbds, vms }) => {
|
||||
})(({ item: vdi, vbds, vms, userData: { vmsSnapshotsBySuspendVdi } }) => {
|
||||
const vmSnapshot = vmsSnapshotsBySuspendVdi[vdi.uuid]?.[0]
|
||||
if (isEmpty(vms)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{map(vbds, (vbd, index) => {
|
||||
const vm = vms[vbd.VM]
|
||||
{vbds.length > 0 ? (
|
||||
map(vbds, (vbd, index) => {
|
||||
const vm = vms[vbd.VM]
|
||||
|
||||
if (vm === undefined) {
|
||||
return null
|
||||
}
|
||||
if (vm === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const type = vm.type
|
||||
let link
|
||||
if (type === 'VM') {
|
||||
link = `/vms/${vm.id}`
|
||||
} else if (type === 'VM-template') {
|
||||
link = `/home?s=${vm.id}&t=VM-template`
|
||||
} else {
|
||||
link = vm.$snapshot_of === undefined ? '/dashboard/health' : `/vms/${vm.$snapshot_of}/snapshots`
|
||||
}
|
||||
const type = vm.type
|
||||
let link
|
||||
if (type === 'VM') {
|
||||
link = `/vms/${vm.id}`
|
||||
} else if (type === 'VM-template') {
|
||||
link = `/home?s=${vm.id}&t=VM-template`
|
||||
} else {
|
||||
link = vm.$snapshot_of === undefined ? '/dashboard/health' : `/vms/${vm.$snapshot_of}/snapshots`
|
||||
}
|
||||
|
||||
return (
|
||||
<Row className={index > 0 && 'mt-1'}>
|
||||
<Col mediumSize={8}>
|
||||
<Link to={link}>{renderXoItem(vm)}</Link>
|
||||
</Col>
|
||||
<Col mediumSize={4}>
|
||||
<ButtonGroup>
|
||||
{vbd.attached ? (
|
||||
return (
|
||||
<Row className={index > 0 && 'mt-1'}>
|
||||
<Col mediumSize={8}>
|
||||
<Link to={link}>{renderXoItem(vm)}</Link>
|
||||
</Col>
|
||||
<Col mediumSize={4}>
|
||||
<ButtonGroup>
|
||||
{vbd.attached ? (
|
||||
<ActionRowButton
|
||||
btnStyle='danger'
|
||||
handler={disconnectVbd}
|
||||
handlerParam={vbd}
|
||||
icon='disconnect'
|
||||
tooltip={_('vbdDisconnect')}
|
||||
/>
|
||||
) : (
|
||||
<ActionRowButton
|
||||
btnStyle='primary'
|
||||
disabled={some(vbds, 'attached') || !isVmRunning(vm)}
|
||||
handler={connectVbd}
|
||||
handlerParam={vbd}
|
||||
icon='connect'
|
||||
tooltip={_('vbdConnect')}
|
||||
/>
|
||||
)}
|
||||
<ActionRowButton
|
||||
btnStyle='danger'
|
||||
handler={disconnectVbd}
|
||||
handler={deleteVbd}
|
||||
handlerParam={vbd}
|
||||
icon='disconnect'
|
||||
tooltip={_('vbdDisconnect')}
|
||||
icon='vdi-forget'
|
||||
tooltip={_('vdiForget')}
|
||||
/>
|
||||
) : (
|
||||
<ActionRowButton
|
||||
btnStyle='primary'
|
||||
disabled={some(vbds, 'attached') || !isVmRunning(vm)}
|
||||
handler={connectVbd}
|
||||
handlerParam={vbd}
|
||||
icon='connect'
|
||||
tooltip={_('vbdConnect')}
|
||||
/>
|
||||
)}
|
||||
<ActionRowButton
|
||||
btnStyle='danger'
|
||||
handler={deleteVbd}
|
||||
handlerParam={vbd}
|
||||
icon='vdi-forget'
|
||||
tooltip={_('vdiForget')}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
})}
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<Col mediumSize={8}>
|
||||
<Link to={`/vms/${vmSnapshot.$snapshot_of}/snapshots`}>
|
||||
<Vm id={vmSnapshot.$snapshot_of} />
|
||||
</Link>
|
||||
</Col>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}),
|
||||
@@ -304,6 +313,7 @@ class NewDisk extends Component {
|
||||
@connectStore(() => ({
|
||||
checkPermissions: getCheckPermissions,
|
||||
vbds: createGetObjectsOfType('VBD'),
|
||||
vmsSnapshotsBySuspendVdi: createGetObjectsOfType('VM-snapshot').groupBy('suspendVdi'),
|
||||
}))
|
||||
export default class SrDisks extends Component {
|
||||
_closeNewDiskForm = () => this.setState({ newDisk: false })
|
||||
@@ -434,6 +444,7 @@ export default class SrDisks extends Component {
|
||||
columns={COLUMNS}
|
||||
data-isVdiAttached={this._getIsVdiAttached()}
|
||||
data-vdisByBaseCopy={this._getVdisByBaseCopy()}
|
||||
data-vmsSnapshotsBySuspendVdi={this.props.vmsSnapshotsBySuspendVdi}
|
||||
defaultFilter='filterOnlyManaged'
|
||||
filters={FILTERS}
|
||||
groupedActions={GROUPED_ACTIONS}
|
||||
|
||||
Reference in New Issue
Block a user