Compare commits

..

21 Commits

Author SHA1 Message Date
Julien Fontanet
093bda7039 feat(fs): 0.4.1 2018-11-05 09:53:43 +01:00
Julien Fontanet
4e35b19ac5 fix(xo-web/xoa/update): fix re-registration 2018-11-05 09:46:22 +01:00
Julien Fontanet
244d8a51e8 chore(xo-web/xoa/update): dont hide error 2018-11-05 09:42:59 +01:00
Julien Fontanet
9d6cc77cc8 chore(changelog): add timeout entry 2018-11-03 17:45:50 +01:00
Julien Fontanet
d5e0150880 chore: update promise-toolbox to v0.11
`timeout()` now provides better stack traces and support 0 delay to
disable the timeout.
2018-11-03 17:45:50 +01:00
Julien Fontanet
5cf29a98b3 feat(fs): configurable timeout 2018-11-03 17:45:50 +01:00
Julien Fontanet
165c2262c0 fix(nfs): default timeout 10s → 10m 2018-11-03 17:45:50 +01:00
badrAZ
74f5d2e0cd fix(xo-server/backup-ng): "vms" should be a dictionary (#3635) 2018-11-02 16:29:44 +01:00
badrAZ
2d93456f52 feat(xo-server/backup-ng): log scheduled VMs if concurrency above 0 (#3633)
See #3603
2018-11-02 14:56:46 +01:00
Julien Fontanet
fd401ca335 fix(fs/nfs): opts param is optional 2018-11-02 10:09:19 +01:00
Julien Fontanet
97ba93a9ad chore(fs): configurable mounts dir (#3413) 2018-11-02 09:37:35 +01:00
Pierre Donias
0788c25710 feat(xo-web): 5.29.1 2018-10-31 17:20:15 +01:00
Julien Fontanet
82bba951db feat(xo-web/xoa-updater/logs): from old to new
Fixes #2708
2018-10-31 17:18:53 +01:00
Julien Fontanet
6efd611b80 feat(xo-web/xoa/update): use pre for updater logs 2018-10-31 17:18:53 +01:00
Julien Fontanet
b7d43b42b9 fix(xo-web/xoa/update): toggleState not compatible with ActionButton 2018-10-31 17:18:52 +01:00
Julien Fontanet
801b71d9ae fix(xo-web/xoa/update): typo 2018-10-31 17:18:52 +01:00
Pierre Donias
0ff7c2188a feat(xo-server): 5.29.2 2018-10-31 16:56:23 +01:00
Julien Fontanet
bc1667440f feat(log): 0.1.4 2018-10-31 16:55:11 +01:00
Julien Fontanet
227b464a8e fix(log): paths in transports/* 2018-10-31 16:54:35 +01:00
Pierre Donias
f6c43650b4 feat(xo-server): 5.29.1 2018-10-31 16:43:29 +01:00
Pierre Donias
597689fde0 chore(xo-server): use log 0.1.3 2018-10-31 16:42:03 +01:00
32 changed files with 163 additions and 101 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/fs",
"version": "0.4.0",
"version": "0.4.1",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
@@ -25,7 +25,7 @@
"fs-extra": "^7.0.0",
"get-stream": "^4.0.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"through2": "^2.0.3",
"tmp": "^0.0.33",
"xo-remote-parser": "^0.5.0"

View File

@@ -17,11 +17,13 @@ type File = FileDescriptor | string
const checksumFile = file => file + '.checksum'
export const DEFAULT_TIMEOUT = 10000
const DEFAULT_TIMEOUT = 6e5 // 10 min
export default class RemoteHandlerAbstract {
_remote: Object
constructor (remote: any) {
_timeout: number
constructor (remote: any, options: Object = {}) {
if (remote.url === 'test://') {
this._remote = remote
} else {
@@ -30,6 +32,7 @@ export default class RemoteHandlerAbstract {
throw new Error('Incorrect remote type')
}
}
;({ timeout: this._timeout = DEFAULT_TIMEOUT } = options)
}
get type (): string {
@@ -127,7 +130,7 @@ export default class RemoteHandlerAbstract {
newPath: string,
{ checksum = false }: Object = {}
) {
let p = timeout.call(this._rename(oldPath, newPath), DEFAULT_TIMEOUT)
let p = timeout.call(this._rename(oldPath, newPath), this._timeout)
if (checksum) {
p = Promise.all([
p,
@@ -148,7 +151,7 @@ export default class RemoteHandlerAbstract {
prependDir = false,
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
): Promise<string[]> {
let entries = await timeout.call(this._list(dir), DEFAULT_TIMEOUT)
let entries = await timeout.call(this._list(dir), this._timeout)
if (filter !== undefined) {
entries = entries.filter(filter)
}
@@ -172,7 +175,7 @@ export default class RemoteHandlerAbstract {
): Promise<LaxReadable> {
const path = typeof file === 'string' ? file : file.path
const streamP = timeout
.call(this._createReadStream(file, options), DEFAULT_TIMEOUT)
.call(this._createReadStream(file, options), this._timeout)
.then(stream => {
// detect early errors
let promise = fromEvent(stream, 'readable')
@@ -233,7 +236,7 @@ export default class RemoteHandlerAbstract {
async openFile (path: string, flags?: string): Promise<FileDescriptor> {
return {
fd: await timeout.call(this._openFile(path, flags), DEFAULT_TIMEOUT),
fd: await timeout.call(this._openFile(path, flags), this._timeout),
path,
}
}
@@ -243,7 +246,7 @@ export default class RemoteHandlerAbstract {
}
async closeFile (fd: FileDescriptor): Promise<void> {
await timeout.call(this._closeFile(fd.fd), DEFAULT_TIMEOUT)
await timeout.call(this._closeFile(fd.fd), this._timeout)
}
async _closeFile (fd: mixed): Promise<void> {
@@ -268,7 +271,7 @@ export default class RemoteHandlerAbstract {
flags: 'wx',
...options,
}),
DEFAULT_TIMEOUT
this._timeout
)
if (!checksum) {
@@ -304,7 +307,7 @@ export default class RemoteHandlerAbstract {
ignoreErrors.call(this._unlink(checksumFile(file)))
}
await timeout.call(this._unlink(file), DEFAULT_TIMEOUT)
await timeout.call(this._unlink(file), this._timeout)
}
async _unlink (file: mixed): Promise<void> {
@@ -312,7 +315,7 @@ export default class RemoteHandlerAbstract {
}
async getSize (file: mixed): Promise<number> {
return timeout.call(this._getSize(file), DEFAULT_TIMEOUT)
return timeout.call(this._getSize(file), this._timeout)
}
async _getSize (file: mixed): Promise<number> {

View File

@@ -2,11 +2,13 @@
import { TimeoutError } from 'promise-toolbox'
import AbstractHandler, { DEFAULT_TIMEOUT } from './abstract'
import AbstractHandler from './abstract'
const TIMEOUT = 10e3
class TestHandler extends AbstractHandler {
constructor (impl) {
super({ url: 'test://' })
super({ url: 'test://' }, { timeout: TIMEOUT })
Object.keys(impl).forEach(method => {
this[`_${method}`] = impl[method]
@@ -15,97 +17,97 @@ class TestHandler extends AbstractHandler {
}
describe('rename()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
rename: () => new Promise(() => {}),
})
const promise = testHandler.rename('oldPath', 'newPath')
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('list()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
list: () => new Promise(() => {}),
})
const promise = testHandler.list()
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('createReadStream()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
createReadStream: () => new Promise(() => {}),
})
const promise = testHandler.createReadStream('file')
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('openFile()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
openFile: () => new Promise(() => {}),
})
const promise = testHandler.openFile('path')
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('closeFile()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
closeFile: () => new Promise(() => {}),
})
const promise = testHandler.closeFile({ fd: undefined, path: '' })
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('createOutputStream()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
createOutputStream: () => new Promise(() => {}),
})
const promise = testHandler.createOutputStream('File')
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('unlink()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
unlink: () => new Promise(() => {}),
})
const promise = testHandler.unlink('')
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('getSize()', () => {
it(`return TimeoutError after ${DEFAULT_TIMEOUT} ms`, async () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
getSize: () => new Promise(() => {}),
})
const promise = testHandler.getSize('')
jest.advanceTimersByTime(DEFAULT_TIMEOUT)
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})

View File

@@ -14,7 +14,7 @@ const HANDLERS = {
nfs: RemoteHandlerNfs,
}
export const getHandler = (remote: Remote): RemoteHandler => {
export const getHandler = (remote: Remote, ...rest: any): RemoteHandler => {
// FIXME: should be done in xo-remote-parser.
const type = remote.url.split('://')[0]
@@ -22,5 +22,5 @@ export const getHandler = (remote: Remote): RemoteHandler => {
if (!Handler) {
throw new Error('Unhandled remote type')
}
return new Handler(remote)
return new Handler(remote, ...rest)
}

View File

@@ -1,17 +1,28 @@
import execa from 'execa'
import fs from 'fs-extra'
import { join } from 'path'
import { tmpdir } from 'os'
import LocalHandler from './local'
const DEFAULT_NFS_OPTIONS = 'vers=3'
export default class NfsHandler extends LocalHandler {
constructor (
remote,
{ mountsDir = join(tmpdir(), 'xo-fs-mounts'), ...opts } = {}
) {
super(remote, opts)
this._realPath = join(mountsDir, remote.id)
}
get type () {
return 'nfs'
}
_getRealPath () {
return `/run/xo-server/mounts/${this._remote.id}`
return this._realPath
}
async _mount () {

View File

@@ -23,8 +23,8 @@ const normalizeError = error => {
}
export default class SmbHandler extends RemoteHandlerAbstract {
constructor (remote) {
super(remote)
constructor (remote, opts) {
super(remote, opts)
this._forget = noop
}

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/log",
"version": "0.1.3",
"version": "0.1.4",
"license": "ISC",
"description": "",
"keywords": [],
@@ -30,7 +30,7 @@
},
"dependencies": {
"lodash": "^4.17.4",
"promise-toolbox": "^0.10.1"
"promise-toolbox": "^0.11.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -1 +1 @@
module.exports = require('./dist/transports/console.js')
module.exports = require('../dist/transports/console.js')

View File

@@ -1 +1 @@
module.exports = require('./dist/transports/email.js')
module.exports = require('../dist/transports/email.js')

View File

@@ -1 +1 @@
module.exports = require('./dist/transports/memory.js')
module.exports = require('../dist/transports/memory.js')

View File

@@ -1 +1 @@
module.exports = require('./dist/transports/syslog.js')
module.exports = require('../dist/transports/syslog.js')

View File

@@ -6,6 +6,8 @@
### Bug fixes
- [Backup NG] Increase timeout in stale remotes detection to limit false positives (PR [#3632](https://github.com/vatesfr/xen-orchestra/pull/3632))
### Released packages
- xo-server v5.30.0

View File

@@ -21,7 +21,7 @@
"jest": "^23.0.1",
"lodash": "^4.17.4",
"prettier": "^1.10.2",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"sorted-object": "^2.0.1"
},
"engines": {

View File

@@ -26,7 +26,7 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/fs": "^0.4.0",
"@xen-orchestra/fs": "^0.4.1",
"cli-progress": "^2.0.0",
"exec-promise": "^0.7.0",
"struct-fu": "^1.2.0",
@@ -40,7 +40,7 @@
"cross-env": "^5.1.3",
"execa": "^1.0.0",
"index-modules": "^0.3.0",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"rimraf": "^2.6.1",
"tmp": "^0.0.33"
},

View File

@@ -25,7 +25,7 @@
"from2": "^2.3.0",
"fs-extra": "^7.0.0",
"limit-concurrency-decorator": "^0.4.0",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"struct-fu": "^1.2.0",
"uuid": "^3.0.1"
},
@@ -34,7 +34,7 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@xen-orchestra/fs": "^0.4.0",
"@xen-orchestra/fs": "^0.4.1",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",

View File

@@ -45,7 +45,7 @@
"make-error": "^1.3.0",
"minimist": "^1.2.0",
"ms": "^2.1.1",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"pw": "0.0.4",
"xmlrpc": "^1.3.2",
"xo-collection": "^0.4.1"

View File

@@ -42,7 +42,7 @@
"nice-pipe": "0.0.0",
"pretty-ms": "^4.0.0",
"progress-stream": "^2.0.0",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"pump": "^3.0.0",
"pw": "^0.0.4",
"strip-indent": "^2.0.0",

View File

@@ -38,7 +38,7 @@
"inquirer": "^6.0.0",
"ldapjs": "^1.0.1",
"lodash": "^4.17.4",
"promise-toolbox": "^0.10.1"
"promise-toolbox": "^0.11.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -33,7 +33,7 @@
"dependencies": {
"nodemailer": "^4.4.1",
"nodemailer-markdown": "^1.0.1",
"promise-toolbox": "^0.10.1"
"promise-toolbox": "^0.11.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -32,7 +32,7 @@
"node": ">=6"
},
"dependencies": {
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"slack-node": "^0.1.8"
},
"devDependencies": {

View File

@@ -40,7 +40,7 @@
"html-minifier": "^3.5.8",
"human-format": "^0.10.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.10.1"
"promise-toolbox": "^0.11.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -34,6 +34,12 @@
// Necessary for external authentication providers.
"createUserOnFirstSignin": true,
"remoteOptions": {
"mountsDir": "/run/xo-server/mounts",
"timeout": 600e3
},
// Whether API logs should contains the full request/response on
// errors.
//

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server",
"version": "5.29.0",
"version": "5.29.2",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [
@@ -34,8 +34,8 @@
"@xen-orchestra/async-map": "^0.0.0",
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/emit-async": "^0.0.0",
"@xen-orchestra/fs": "^0.4.0",
"@xen-orchestra/log": "^0.1.2",
"@xen-orchestra/fs": "^0.4.1",
"@xen-orchestra/log": "^0.1.4",
"@xen-orchestra/mixin": "^0.0.0",
"ajv": "^6.1.1",
"app-conf": "^0.5.0",
@@ -95,7 +95,7 @@
"passport": "^0.4.0",
"passport-local": "^1.0.0",
"pretty-format": "^23.0.0",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"proxy-agent": "^3.0.0",
"pug": "^2.0.0-rc.4",
"pump": "^3.0.0",

View File

@@ -0,0 +1,15 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
event: {
enum: ['task.info'],
},
taskId: {
type: 'string',
description: 'identifier of the parent task or job',
},
data: {},
},
required: ['event', 'taskId'],
}

View File

@@ -129,6 +129,13 @@ export default {
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) {

View File

@@ -521,31 +521,30 @@ export default class BackupNg {
vmsId !== undefined ||
(vmsId = extractIdsFromSimplePattern(vmsPattern)) !== undefined
) {
vms = vmsId
.map(id => {
try {
return app.getObject(id, 'VM')
} catch (error) {
const taskId: string = logger.notice(
`Starting backup of ${id}. (${job.id})`,
{
event: 'task.start',
parentId: runJobId,
data: {
type: 'VM',
id,
},
}
)
logger.error(`Backuping ${id} has failed. (${job.id})`, {
event: 'task.end',
taskId,
status: 'failure',
result: serializeError(error),
})
}
})
.filter(vm => vm !== undefined)
vms = {}
vmsId.forEach(id => {
try {
vms[id] = app.getObject(id, 'VM')
} catch (error) {
const taskId: string = logger.notice(
`Starting backup of ${id}. (${job.id})`,
{
event: 'task.start',
parentId: runJobId,
data: {
type: 'VM',
id,
},
}
)
logger.error(`Backuping ${id} has failed. (${job.id})`, {
event: 'task.end',
taskId,
status: 'failure',
result: serializeError(error),
})
}
})
} else {
vms = app.getObjects({
filter: createPredicate({
@@ -646,6 +645,13 @@ export default class BackupNg {
])
if (concurrency !== 0) {
handleVm = limitConcurrency(concurrency)(handleVm)
logger.notice('vms', {
event: 'task.info',
taskId: runJobId,
data: {
vms: Object.keys(vms),
},
})
}
await asyncMap(vms, handleVm)
}

View File

@@ -10,7 +10,8 @@ import { Remotes } from '../models/remote'
// ===================================================================
export default class {
constructor (xo) {
constructor (xo, { remoteOptions }) {
this._remoteOptions = remoteOptions
this._remotes = new Remotes({
connection: xo._redis,
prefix: 'xo:remote',
@@ -57,7 +58,7 @@ export default class {
const handlers = this._handlers
let handler = handlers[id]
if (handler === undefined) {
handler = handlers[id] = getHandler(remote)
handler = handlers[id] = getHandler(remote, this._remoteOptions)
}
try {

View File

@@ -26,7 +26,7 @@
"child-process-promise": "^2.0.3",
"core-js": "3.0.0-beta.3",
"pipette": "^0.9.3",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"tmp": "^0.0.33",
"vhd-lib": "^0.4.0"
},

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "xo-web",
"version": "5.29.0",
"version": "5.29.1",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [
@@ -94,7 +94,7 @@
"moment": "^2.20.1",
"moment-timezone": "^0.5.14",
"notifyjs": "^3.0.0",
"promise-toolbox": "^0.10.1",
"promise-toolbox": "^0.11.0",
"prop-types": "^15.6.0",
"random-password": "^0.1.2",
"reaclette": "^0.7.0",

View File

@@ -359,13 +359,13 @@ class XoaUpdater extends EventEmitter {
log (level, message) {
message = (message != null && message.message) || String(message)
const date = new Date()
this._log.unshift({
this._log.push({
date: date.toLocaleString(),
level,
message,
})
while (this._log.length > 10) {
this._log.pop()
this._log.shift()
}
this.emit('log', map(this._log, item => assign({}, item)))
}

View File

@@ -113,13 +113,16 @@ const Updates = decorate([
body: (
<p>
{_('alreadyRegisteredModalText', {
email: this.props.registration.email,
email: this.props.xoaRegisterState.email,
})}
</p>
),
})
} catch (_) {
return
} catch (error) {
if (error === null) {
return
}
throw error
}
}
@@ -164,7 +167,7 @@ const Updates = decorate([
isProxyConfigEdited: state =>
PROXY_ENTRIES.some(entry => state[entry] !== undefined),
isRegistered: (_, { xoaRegisterState }) =>
xoaRegisterState.state === 'register',
xoaRegisterState.state === 'registered',
isTrialAllowed: (_, { xoaTrialState }) =>
xoaTrialState.state === 'default' && exposeTrial(xoaTrialState.trial),
isTrialAvailable: (_, { xoaTrialState }) =>
@@ -251,9 +254,9 @@ const Updates = decorate([
: _('downgrade')}
</ActionButton>
<hr />
<div>
<pre>
{map(xoaUpdaterLog, (log, key) => (
<p key={key}>
<div key={key}>
<span className={LEVELS_TO_CLASSES[log.level]}>
{log.date}
</span>
@@ -263,9 +266,9 @@ const Updates = decorate([
__html: ansiUp.ansi_to_html(log.message),
}}
/>
</p>
</div>
))}
</div>
</pre>
</CardBlock>
</Card>
</Col>
@@ -388,14 +391,13 @@ const Updates = decorate([
</ActionButton>
</form>
) : (
<ActionButton
<Button
btnStyle='primary'
handler={effects.toggleState}
icon='edit'
name='askRegisterAgain'
onClick={effects.toggleState}
>
{_('editRegistration')}
</ActionButton>
<Icon fixedWidth icon='edit' /> {_('editRegistration')}
</Button>
)}
{+process.env.XOA_PLAN === 1 && (
<div>

View File

@@ -10323,6 +10323,13 @@ promise-toolbox@^0.10.1:
dependencies:
make-error "^1.3.2"
promise-toolbox@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/promise-toolbox/-/promise-toolbox-0.11.0.tgz#9ed928355355395072dace3f879879504e07d1bc"
integrity sha512-bjHk0kq+Ke3J3zbkbbJH6kXCyQZbFHwOTrE/Et7vS0uS0tluoV+PLqU/kEyxl8aARM7v04y2wFoDo/wWAEPvjA==
dependencies:
make-error "^1.3.2"
promise-toolbox@^0.8.0:
version "0.8.3"
resolved "https://registry.yarnpkg.com/promise-toolbox/-/promise-toolbox-0.8.3.tgz#b757232a21d246d8702df50da6784932dd0f5348"