Compare commits

...

13 Commits

Author SHA1 Message Date
Julien Fontanet
4c7c9f9156 WiP: feat(fs/glob): basic glob implementation 2019-10-06 23:15:04 +02:00
Julien Fontanet
093fb7f959 fix(xo-server-logs): explicit appDir
May fix #4576
2019-10-03 16:02:10 +02:00
Julien Fontanet
f6472424ad fix(eslint): disable lines-between-class-members rule 2019-10-02 15:52:51 +02:00
Julien Fontanet
31ed3767c6 chore: fix some lint
Mainly: `obj['prop']` → `obj.prop`
2019-10-02 15:45:32 +02:00
marcpezin
366acb65ea doc(updater): release channels (#4572) 2019-10-02 15:24:45 +02:00
Pierre Donias
7c6946931b chore(xo-web): PascalCase NotFound component (#4567)
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
2019-10-01 17:21:00 +02:00
HamadaBrest
5d971433a5 fix(xo-web/new-vm/self): template selection (#4568)
Fixes #4565
Introduced by 9efc3dd1fb
2019-10-01 16:59:59 +02:00
Rajaa.BARHTAOUI
05264b326b chore(CHANGELOG): 5.39.0 (#4561) 2019-09-30 16:08:56 +02:00
BARHTAOUI
fdd5c6bfd8 chore(CHANGELOG): update next 2019-09-30 15:13:05 +02:00
BARHTAOUI
42c3528c2f feat(xo-web): 5.50.2 2019-09-30 15:13:05 +02:00
HamadaBrest
18640714f1 fix(xo-web/hub/resource): "remove" button icon (#4559) 2019-09-30 14:43:31 +02:00
HamadaBrest
cda4d3399b fix(xo-web/hub): responsive hub VM templates (#4558)
Fixes #4557
2019-09-30 14:42:01 +02:00
Julien Fontanet
4da8af6e69 fix(log/configure): process nested transport 2019-09-27 14:55:26 +02:00
24 changed files with 161 additions and 67 deletions

View File

@@ -40,6 +40,13 @@ 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',

View File

@@ -0,0 +1,38 @@
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
}

View File

@@ -0,0 +1,12 @@
/* 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)
}
)
})

View File

@@ -14,6 +14,7 @@ import { type Readable, type Writable } from 'stream'
import normalizePath from './_normalizePath'
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
import { parseGlob } from './_parseGlob'
const { dirname } = path.posix
@@ -258,6 +259,12 @@ export default class RemoteHandlerAbstract {
)
}
// basic glob support, only `*` is supported
async glob(pattern) {
const parts = parseGlob(pattern)
// TODO
}
async list(
dir: string,
{

View File

@@ -19,7 +19,8 @@ const createTransport = config => {
}
}
let { filter, transport } = config
let { filter } = config
let transport = createTransport(config.transport)
const level = resolve(config.level)
if (filter !== undefined) {

View File

@@ -4,20 +4,37 @@
### Enhancements
### Bug fixes
### Released packages
- xo-server v5.51.0
- xo-web v5.51.0
## **5.39.0** (2019-09-30)
![Channel: latest](https://badgen.net/badge/channel/latest/yellow)
### 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)
- [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`
- [HUB] VM template store [#1918](https://github.com/vatesfr/xen-orchestra/issues/1918) (PR [#4442](https://github.com/vatesfr/xen-orchestra/pull/4442))
### Bug fixes
@@ -47,12 +64,12 @@
- xo-server-sdn-controller v0.3.0
- @xen-orchestra/template v0.1.0
- xo-server v5.50.1
- xo-web v5.50.1
- xo-web v5.50.2
## **5.38.0** (2019-08-29)
![Channel: latest](https://badgen.net/badge/channel/latest/yellow)
![Channel: stable](https://badgen.net/badge/channel/stable/green)
### Enhancements
@@ -80,8 +97,6 @@
## **5.37.1** (2019-08-06)
![Channel: stable](https://badgen.net/badge/channel/stable/green)
### 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))

View File

@@ -7,11 +7,11 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”
### 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))
### Released packages

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -41,6 +41,20 @@ However, if you want to start a manual check, you can do it by clicking on the "
![](./assets/xo5updatebutton.png)
#### 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.
![](./assets/release-channels.png)
#### Upgrade
If a new version is found, you'll have an upgrade button and its tooltip displayed:

View File

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

View File

@@ -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() })
})

View File

@@ -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,19 +370,17 @@ 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) {
@@ -392,15 +390,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
@@ -793,7 +791,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',
@@ -1300,7 +1298,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,

View File

@@ -6,6 +6,7 @@ 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'
@@ -174,6 +175,7 @@ export default async function main() {
}
const config = await appConf.load('xo-server', {
appDir: joinPath(__dirname, '..'),
ignoreUnknownFormats: true,
})

View File

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

View File

@@ -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)
})

View File

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

View File

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

View File

@@ -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
}
)

View File

@@ -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 />
)
// ===================================================================

View File

@@ -51,7 +51,7 @@ export default decorate([
</Col>
) : (
resources.map(data => (
<Col key={data.namespace} mediumSize={3}>
<Col key={data.namespace} mediumSize={6} largeSize={4}>
<Resource {...data} />
</Col>
))

View File

@@ -197,11 +197,11 @@ export default decorate([
data-name={name}
disabled={state.installedTemplates.length === 0}
handler={effects.deleteTemplates}
icon='delete'
size='small'
style={{ border: 'none' }}
>
<Icon icon='delete' size='xs' />
</ActionButton>
tooltip={_('remove')}
/>
<br />
</CardHeader>
<CardBlock className='text-center'>

View File

@@ -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),
template: getTemplate(state, props, props.pool === undefined),
templates: getTemplates(state, props),
userSshKeys: getUserSshKeys(state, props),
})

View File

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

View File

@@ -154,7 +154,7 @@ class IpsCell extends BaseComponent {
value={newIps || ''}
/>{' '}
<ActionButton
form={`newIpForm`}
form='newIpForm'
icon='save'
btnStyle='primary'
handler={this._addIps}