Compare commits

...

14 Commits

Author SHA1 Message Date
Mohamedox
a1c19c92a9 fix(xo-web/ips): bad range formatting
Fixes #3170
2019-05-28 15:17:38 +02:00
HamadaBrest
fdd79885f9 feat(xo-web/VM): display VDI size in migrate modal (#4250)
Fixes #2534
2019-05-27 16:56:45 +02:00
Julien Fontanet
b2eb970796 fix(xo-server/vm.set): cast weight to string
Follow-up of 49e1b0ba7
2019-05-27 16:23:38 +02:00
Julien Fontanet
3ee9c1b550 chore(xo-server/Xapi): remove unused setVcpuWeight 2019-05-27 16:23:37 +02:00
HamadaBrest
2566c24753 fix(xo-web/host): incorrect hypervisor name in RAM usage tooltip (#4248)
Fixes #4246
2019-05-27 15:44:59 +02:00
Julien Fontanet
49e1b0ba7e fix(xo-server/vm.set): cast logical numbers to XAPI string values 2019-05-27 11:10:19 +02:00
Julien Fontanet
453c329f14 fix(xo-server/vm.set): videoram is strictly a number 2019-05-27 11:04:44 +02:00
badrAZ
27193f38f3 feat(xo-web): 5.42.0 2019-05-24 15:32:16 +02:00
badrAZ
d3dc94e210 feat(xo-server): 5.42.0 2019-05-24 15:31:54 +02:00
Julien Fontanet
6dad860635 fix(xo-server/getRemoteHandler): only cache on success
Otherwise subsequent calls will use an invalid handler.

Related to xoa-support#1498
2019-05-23 18:13:58 +02:00
Julien Fontanet
0362ac8909 feat(xo-web/home): case-sensitive filtering 2019-05-23 17:34:19 +02:00
Pierre Donias
e7b79f83d1 fix(xo-web): cast XOA_PLAN for strict equality tests (#4241) 2019-05-23 17:17:48 +02:00
Pierre Donias
62379c1e41 feat(xo-web/settings/logs): suggest XCP-ng when LICENCE_RESTRICTION (#4238)
Fixes #3876
2019-05-23 17:16:39 +02:00
Pierre Donias
23b422e3df feat(xo-server,xo-web/user): forget all authentication tokens (#4224)
Fixes #4214
2019-05-23 17:13:27 +02:00
20 changed files with 130 additions and 50 deletions

View File

@@ -5,6 +5,9 @@
- [VM/general] Display 'Started... ago' instead of 'Halted... ago' for paused state [#3750](https://github.com/vatesfr/xen-orchestra/issues/3750) (PR [#4170](https://github.com/vatesfr/xen-orchestra/pull/4170))
- [Metadata backup] Ability to define when the backup report will be sent (PR [#4149](https://github.com/vatesfr/xen-orchestra/pull/4149))
- [XOA/Update] Ability to select release channel [#4200](https://github.com/vatesfr/xen-orchestra/issues/4200) (PR [#4202](https://github.com/vatesfr/xen-orchestra/pull/4202))
- [User] Forget connection tokens on password change or on demand [#4214](https://github.com/vatesfr/xen-orchestra/issues/4214) (PR [#4224](https://github.com/vatesfr/xen-orchestra/pull/4224))
- [Settings/Logs] LICENCE_RESTRICTION errors: suggest XCP-ng as an Open Source alternative [#3876](https://github.com/vatesfr/xen-orchestra/issues/3876) (PR [#4238](https://github.com/vatesfr/xen-orchestra/pull/4238))
- [VM/Migrate] Display VDI size on migrate modal [#2534](https://github.com/vatesfr/xen-orchestra/issues/2534) (PR [#4250](https://github.com/vatesfr/xen-orchestra/pull/4250))
### Bug fixes
@@ -13,6 +16,8 @@
- [Upgrade] Fix alert before upgrade while running backup jobs (PR [#4235](https://github.com/vatesfr/xen-orchestra/pull/4235))
- [Import] Fix import OVA files (PR [#4232](https://github.com/vatesfr/xen-orchestra/pull/4232))
- [VM/network] Fix duplicate IPv4 (PR [#4239](https://github.com/vatesfr/xen-orchestra/pull/4239))
- [Remotes] Fix disconnected remotes which may appear to work
- [Host] Fix incorrect hypervisor name [#4246](https://github.com/vatesfr/xen-orchestra/issues/4246) (PR [#4248](https://github.com/vatesfr/xen-orchestra/pull/4248))
### Released packages

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "xo-server",
"version": "5.41.0",
"version": "5.42.0",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [

View File

@@ -34,3 +34,25 @@ delete_.permission = 'admin'
delete_.params = {
token: { type: 'string' },
}
// -------------------------------------------------------------------
export async function deleteAll({ except }) {
await this.deleteAuthenticationTokens({
filter: {
user_id: this.session.get('user_id'),
id: {
__not: except,
},
},
})
}
deleteAll.description =
'delete all tokens of the current user except the current one'
deleteAll.permission = ''
deleteAll.params = {
except: { type: 'string', optional: true },
}

View File

@@ -603,7 +603,7 @@ set.params = {
// Switch from Cirrus video adaptor to VGA adaptor
vga: { type: 'string', optional: true },
videoram: { type: ['string', 'number'], optional: true },
videoram: { type: 'number', optional: true },
coresPerSocket: { type: ['string', 'number', 'null'], optional: true },

View File

@@ -1574,13 +1574,6 @@ export default class Xapi extends XapiBase {
return /* await */ this._snapshotVm(this.getObject(vmId), nameLabel)
}
async setVcpuWeight(vmId, weight) {
await this.getObject(vmId).update_VCPUs_params(
'weight',
weight || null // Take all falsy values as a removal (0 included)
)
}
async _startVm(vm, host, force) {
log.debug(`Starting VM ${vm.name_label}`)

View File

@@ -294,7 +294,7 @@ export default {
coresPerSocket: {
set: (coresPerSocket, vm) =>
vm.update_platform('cores-per-socket', coresPerSocket),
vm.update_platform('cores-per-socket', String(coresPerSocket)),
},
CPUs: 'cpus',
@@ -318,7 +318,7 @@ export default {
cpuCap: {
get: vm => vm.VCPUs_params.cap && +vm.VCPUs_params.cap,
set: (cap, vm) => vm.update_VCPUs_params('cap', cap),
set: (cap, vm) => vm.update_VCPUs_params('cap', String(cap)),
},
cpuMask: {
@@ -341,7 +341,11 @@ export default {
cpuWeight: {
get: vm => vm.VCPUs_params.weight && +vm.VCPUs_params.weight,
set: (weight, vm) => vm.update_VCPUs_params('weight', weight),
set: (weight, vm) =>
vm.update_VCPUs_params(
'weight',
weight === null ? null : String(weight)
),
},
highAvailability: {
@@ -443,7 +447,7 @@ export default {
`The different values that the video RAM can take are: ${XEN_VIDEORAM_VALUES}`
)
}
return vm.update_platform('videoram', videoram)
return vm.update_platform('videoram', String(videoram))
},
},

View File

@@ -1,4 +1,5 @@
import createLogger from '@xen-orchestra/log'
import { createPredicate } from 'value-matcher'
import { ignoreErrors } from 'promise-toolbox'
import { invalidCredentials, noSuchObject } from 'xo-common/api-errors'
@@ -193,6 +194,14 @@ export default class {
}
}
async deleteAuthenticationTokens({ filter }) {
return Promise.all(
(await this._tokens.get())
.filter(createPredicate(filter))
.map(({ id }) => this.deleteAuthenticationToken(id))
)
}
async getAuthenticationToken(id) {
let token = await this._tokens.first(id)
if (token === undefined) {

View File

@@ -67,7 +67,7 @@ export default class {
const handlers = this._handlers
let handler = handlers[id]
if (handler === undefined) {
handler = handlers[id] = getHandler(remote, this._remoteOptions)
handler = getHandler(remote, this._remoteOptions)
try {
await handler.sync()
@@ -76,6 +76,8 @@ export default class {
ignoreErrors.call(this._updateRemote(id, { error: error.message }))
throw error
}
handlers[id] = handler
}
return handler

View File

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

View File

@@ -1839,6 +1839,11 @@ const messages = {
pwdChangeErrorBody:
'The old password provided is incorrect. Your password has not been changed.',
changePasswordOk: 'OK',
forgetTokens: 'Forget all connection tokens',
forgetTokensExplained:
'This will prevent other clients from authenticating with existing tokens but will not kill active sessions',
forgetTokensSuccess: 'Successfully forgot connection tokens',
forgetTokensError: 'Error while forgetting connection tokens',
sshKeys: 'SSH keys',
newSshKey: 'New SSH key',
deleteSshKey: 'Delete',
@@ -1867,6 +1872,7 @@ const messages = {
// ----- Logs -----
logUser: 'User',
logMessage: 'Message',
logSuggestXcpNg: 'Use XCP-ng to get rid of restrictions',
logError: 'Error',
logTitle: 'Logs',
logDisplayDetails: 'Display details',

View File

@@ -1,7 +1,9 @@
import forEachRight from 'lodash/forEachRight'
import forEach from 'lodash/forEach'
import forEachRight from 'lodash/forEachRight'
import head from 'lodash/head'
import isArray from 'lodash/isArray'
import isIp from 'is-ip'
import last from 'lodash/last'
import some from 'lodash/some'
export { isIp }
@@ -82,6 +84,9 @@ export const formatIps = ips => {
if (ips.length === 0) {
return []
}
if (ips.length === 1) {
return ips
}
const sortedIps = ips.sort((ip1, ip2) => {
const splitIp1 = ip1.split('.')
const splitIp2 = ip2.split('.')
@@ -99,24 +104,8 @@ export const formatIps = ips => {
(splitIp1[0] - splitIp2[0]) * 256 * 256 * 256
)
})
const range = { first: '', last: '' }
const formattedIps = []
let index = 0
forEach(sortedIps, ip => {
if (ip !== getNextIpV4(range.last)) {
if (range.first) {
formattedIps[index] =
range.first === range.last ? range.first : { ...range }
index++
}
range.first = range.last = ip
} else {
range.last = ip
}
})
formattedIps[index] = range.first === range.last ? range.first : range
return formattedIps
return [{ first: head(sortedIps), last: last(sortedIps) }]
}
export const parseIpPattern = pattern => {

View File

@@ -283,7 +283,7 @@ export const Vdi = decorate([
sr: getSr(state, props),
})
}),
({ id, sr, vdi }) => {
({ id, showSize, showSr, sr, vdi }) => {
if (vdi === undefined) {
return unknowItem(id, 'VDI')
}
@@ -291,9 +291,12 @@ export const Vdi = decorate([
return (
<span>
<Icon icon='disk' /> {vdi.name_label}
{sr !== undefined && (
{sr !== undefined && showSr && (
<span className='text-muted'> - {sr.name_label}</span>
)}
{showSize && (
<span className='text-muted'> ({formatSize(vdi.size)})</span>
)}
</span>
)
},
@@ -302,10 +305,13 @@ export const Vdi = decorate([
Vdi.propTypes = {
id: PropTypes.string.isRequired,
self: PropTypes.bool,
showSize: PropTypes.bool,
}
Vdi.defaultProps = {
self: false,
showSize: false,
showSr: false,
}
// ===================================================================
@@ -432,8 +438,8 @@ const xoItemToRender = {
// XO objects.
pool: ({ id }) => <Pool id={id} />,
VDI: ({ id }) => <Vdi id={id} />,
'VDI-resourceSet': ({ id }) => <Vdi id={id} self />,
VDI: ({ id }) => <Vdi id={id} showSr />,
'VDI-resourceSet': ({ id }) => <Vdi id={id} self showSr />,
// Pool objects.
'VM-template': ({ id }) => <VmTemplate id={id} />,

View File

@@ -59,7 +59,7 @@ export const constructSmartPattern = (
const valueToComplexMatcher = pattern => {
if (typeof pattern === 'string') {
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`)
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`, 'i')
}
if (Array.isArray(pattern)) {

View File

@@ -3,6 +3,7 @@ import Component from 'base-component'
import PropTypes from 'prop-types'
import React from 'react'
import { map } from 'lodash'
import { Vdi } from 'render-xo-item'
import _ from '../../intl'
import SingleLineRow from '../../single-line-row'
@@ -85,7 +86,9 @@ export default class ChooseSrForEachVdisModal extends Component {
</SingleLineRow>
{map(props.vdis, vdi => (
<SingleLineRow key={vdi.uuid}>
<Col size={6}>{vdi.name_label || vdi.name}</Col>
<Col size={6}>
<Vdi id={vdi.id} showSize />
</Col>
<Col size={6}>
<SelectSr
onChange={sr =>

View File

@@ -376,7 +376,7 @@ export const dismissNotification = id => {
export const subscribeNotifications = createSubscription(async () => {
const { user, xoaUpdaterState } = store.getState()
if (
process.env.XOA_PLAN === 5 ||
+process.env.XOA_PLAN === 5 ||
xoaUpdaterState === 'disconnected' ||
xoaUpdaterState === 'error'
) {
@@ -2508,14 +2508,25 @@ export const editUser = (user, { email, password, permission }) =>
subscribeUsers.forceRefresh
)
const _signOutFromEverywhereElse = () =>
_call('token.deleteAll', { except: cookies.get('token') })
export const signOutFromEverywhereElse = () =>
_signOutFromEverywhereElse().then(
() => success(_('forgetTokens'), _('forgetTokensSuccess')),
() => error(_('forgetTokens'), _('forgetTokensError'))
)
export const changePassword = (oldPassword, newPassword) =>
_call('user.changePassword', {
oldPassword,
newPassword,
}).then(
() => success(_('pwdChangeSuccess'), _('pwdChangeSuccessBody')),
() => error(_('pwdChangeError'), _('pwdChangeErrorBody'))
)
})
.then(_signOutFromEverywhereElse)
.then(
() => success(_('pwdChangeSuccess'), _('pwdChangeSuccessBody')),
() => error(_('pwdChangeError'), _('pwdChangeErrorBody'))
)
const _setUserPreferences = preferences =>
_call('user.set', {

View File

@@ -750,7 +750,8 @@ export default class Home extends Component {
new ComplexMatcher.Or(
map(
tags,
tag => new ComplexMatcher.RegExp(`^${escapeRegExp(tag.id)}$`)
tag =>
new ComplexMatcher.RegExp(`^${escapeRegExp(tag.id)}$`, 'i')
)
)
)

View File

@@ -118,7 +118,9 @@ export default ({ statsOverview, host, nVms, vmController, vms }) => {
<Usage total={host.memory.size}>
<UsageElement
highlight
tooltip={`XenServer (${formatSize(vmController.memory.size)})`}
tooltip={`${host.productBrand} (${formatSize(
vmController.memory.size
)})`}
value={vmController.memory.size}
/>
{map(vms, vm => (

View File

@@ -51,9 +51,20 @@ const COLUMNS = [
{
name: _('logMessage'),
itemRenderer: log => (
<pre className={styles.widthLimit}>
{log.data.error && log.data.error.message}
</pre>
<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>
),
sortCriteria: log => log.data.error && log.data.error.message,
},

View File

@@ -7,6 +7,7 @@ import Icon from 'icon'
import PropTypes from 'prop-types'
import React from 'react'
import SortedTable from 'sorted-table'
import Tooltip from 'tooltip'
import { Text } from 'editable'
import { alert } from 'modal'
import { Container, Row, Col } from 'grid'
@@ -24,6 +25,7 @@ import {
editCustomFilter,
removeCustomFilter,
setDefaultHomeFilter,
signOutFromEverywhereElse,
subscribeCurrentUser,
} from 'xo'
@@ -400,6 +402,20 @@ export default class User extends Component {
</Col>
</Row>
<br />
<Row>
<Col smallSize={10} offset={2}>
<Tooltip content={_('forgetTokensExplained')}>
<ActionButton
btnStyle='danger'
handler={signOutFromEverywhereElse}
icon='disconnect'
>
{_('forgetTokens')}
</ActionButton>
</Tooltip>
</Col>
</Row>
<br />
<Row>
<Col smallSize={2}>
<strong>{_('language')}</strong>

View File

@@ -301,7 +301,7 @@ export default class NewXosan extends Component {
}
render() {
if (process.env.XOA_PLAN === 5) {
if (+process.env.XOA_PLAN === 5) {
return (
<em>
{_('xosanSourcesDisclaimer', {