feat(xo-server-netbox): new plugin to synchronize pools with Netbox (#5783)

Fixes #5633
This commit is contained in:
Pierre Donias
2021-05-21 19:39:02 +02:00
committed by GitHub
parent 76b563fa88
commit 18ae664ba7
15 changed files with 1084 additions and 7 deletions
+2
View File
@@ -9,6 +9,7 @@
- [Metadata Backup] Add a warning on restoring a metadata backup (PR [#5769](https://github.com/vatesfr/xen-orchestra/pull/5769))
- [SAML] Compatible with users created with other authentication providers (PR [#5781](https://github.com/vatesfr/xen-orchestra/pull/5781))
- [Netbox] [Plugin](https://xen-orchestra.com/docs/advanced.html#netbox) to synchronize pools, VMs and IPs with [Netbox](https://netbox.readthedocs.io/en/stable/) (PR [#5783](https://github.com/vatesfr/xen-orchestra/pull/5783))
### Bug fixes
@@ -40,5 +41,6 @@
- xen-api minor
- xo-server-auth-saml minor
- xo-server-backup-reports patch
- xo-server-netbox minor
- xo-web minor
- xo-server patch
+33
View File
@@ -320,6 +320,39 @@ It works with few steps:
From there, you can even manage your existing resources with Terraform!
## Netbox
Synchronize your pools, VMs, network interfaces and IP addresses with your [Netbox](https://netbox.readthedocs.io/en/stable/) instance.
![](./assets/netbox.png)
- Go to your Netbox interface
- Configure prefixes:
- Go to IPAM > Prefixes > Add
- Manually create as many prefixes as needed for your infrastructure's IP addresses
:::warning
XO will try to find the right prefix for each IP address. If it can't find a prefix that fits, the IP address won't be synchronized.
:::
- Generate a token:
- Go to Admin > Tokens > Add token
- Create a token with "Write enabled"
- Add a UUID custom field:
- Got to Admin > Custom fields > Add custom field
- Create a custom field called "uuid"
- Assign it to object types `virtualization > cluster` and `virtualization > virtual machine`
![](./assets/customfield.png)
- Go to Xen Orchestra > Settings > Plugins > Netbox and fill out the configuration:
- Endpoint: the URL of your Netbox instance (e.g.: `https://netbox.company.net`)
- Token: the token you generated earlier
- Pools: the pools you wish to automatically synchronize with Netbox
- Interval: the time interval (in hours) between 2 auto-synchronizations. Leave empty if you don't want to synchronize automatically.
- Load the plugin (button next to the plugin's name)
- Manual synchronization: if you correctly configured and loaded the plugin, a "Synchronize with Netbox" button will appear in every pool's Advanced tab, which allows you to manually synchronize it with Netbox
## Recipes
:::tip
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

+1
View File
@@ -0,0 +1 @@
module.exports = require('../../@xen-orchestra/babel-config')(require('./package.json'))
+1
View File
@@ -0,0 +1 @@
../../scripts/babel-eslintrc.js
+1
View File
@@ -0,0 +1 @@
../../scripts/npmignore
+25
View File
@@ -0,0 +1,25 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# xo-server-netbox
> Synchronizes pools managed by Xen Orchestra with Netbox
## Usage
Like all other xo-server plugins, it can be configured directly via
the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later) © [Vates SAS](https://vates.fr)
+2
View File
@@ -0,0 +1,2 @@
Like all other xo-server plugins, it can be configured directly via
the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
+51
View File
@@ -0,0 +1,51 @@
{
"name": "xo-server-netbox",
"version": "0.0.0",
"license": "AGPL-3.0-or-later",
"description": "Synchronizes pools managed by Xen Orchestra with Netbox",
"keywords": [
"netbox",
"orchestra",
"plugin",
"web",
"xen",
"xen-orchestra",
"xo-server"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-netbox",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/xo-server-netbox",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"engines": {
"node": ">=14.6"
},
"devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.14.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/preset-env": "^7.14.1",
"@xen-orchestra/log": "^0.2.0",
"assert": "^2.0.0",
"cross-env": "^7.0.3",
"is-in-subnet": "^4.0.1",
"lodash": "^4.17.21"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
},
"private": true
}
+580
View File
@@ -0,0 +1,580 @@
import assert from 'assert'
import { createLogger } from '@xen-orchestra/log'
import { find, flatten, forEach, groupBy, isEmpty, keyBy, mapValues, trimEnd, zipObject } from 'lodash'
import { isInSubnet } from 'is-in-subnet'
const log = createLogger('xo:netbox')
const CLUSTER_TYPE = 'XCP-ng Pool'
const CHUNK_SIZE = 100
const NAME_MAX_LENGTH = 64
const REQUEST_TIMEOUT = 120e3 // 2min
const M = 1024 ** 2
const G = 1024 ** 3
const { push } = Array.prototype
const diff = (newer, older) => {
if (typeof newer !== 'object') {
return newer === older ? undefined : newer
}
newer = { ...newer }
Object.keys(newer).forEach(key => {
if (diff(newer[key], older[key]) === undefined) {
delete newer[key]
}
})
return isEmpty(newer) ? undefined : newer
}
const indexName = (name, index) => {
const suffix = ` (${index})`
return name.slice(0, NAME_MAX_LENGTH - suffix.length) + suffix
}
const onRequest = req => {
req.setTimeout(REQUEST_TIMEOUT)
req.on('timeout', req.abort)
}
class Netbox {
#endpoint
#intervalToken
#loaded
#pools
#removeApiMethods
#syncInterval
#token
#xo
constructor({ xo }) {
this.#xo = xo
}
configure(configuration) {
this.#endpoint = trimEnd(configuration.endpoint, '/')
if (!/^https?:\/\//.test(this.#endpoint)) {
this.#endpoint = 'http://' + this.#endpoint
}
this.#token = configuration.token
this.#pools = configuration.pools
this.#syncInterval = configuration.syncInterval && configuration.syncInterval * 60 * 60 * 1e3
// We don't want to start the auto-sync if the plugin isn't loaded
if (this.#loaded) {
clearInterval(this.#intervalToken)
if (this.#syncInterval !== undefined) {
this.#intervalToken = setInterval(this.#synchronize.bind(this), this.#syncInterval)
}
}
}
load() {
const synchronize = ({ pools }) => this.#synchronize(pools)
synchronize.description = 'Synchronize XO pools with Netbox'
synchronize.params = {
pools: { type: 'array', optional: true, items: { type: 'string' } },
}
this.#removeApiMethods = this.#xo.addApiMethods({
netbox: { synchronize },
})
if (this.#syncInterval !== undefined) {
this.#intervalToken = setInterval(this.#synchronize.bind(this), this.#syncInterval)
}
this.#loaded = true
}
unload() {
this.#removeApiMethods()
clearInterval(this.#intervalToken)
this.#loaded = false
}
async #makeRequest(path, method, data) {
log.debug(
`${method} ${path}`,
Array.isArray(data) && data.length > 2 ? [...data.slice(0, 2), `and ${data.length - 2} others`] : data
)
let url = this.#endpoint + '/api' + path
const options = {
headers: { 'Content-Type': 'application/json', Authorization: `Token ${this.#token}` },
method,
onRequest,
}
const httpRequest = async () => {
try {
const response = await this.#xo.httpRequest(url, options)
const body = await response.readAll()
if (body.length > 0) {
return JSON.parse(body)
}
} catch (error) {
try {
const body = await error.response.readAll()
if (body.length > 0) {
log.error(body.toString())
}
} catch {
throw error
}
throw error
}
}
let response = []
// Split long POST request into chunks of CHUNK_SIZE objects to avoid a Bad Gateway errors
if (Array.isArray(data)) {
let offset = 0
while (offset < data.length) {
options.body = JSON.stringify(data.slice(offset, offset + CHUNK_SIZE))
push.apply(response, await httpRequest())
offset += CHUNK_SIZE
}
} else {
if (data !== undefined) {
options.body = JSON.stringify(data)
}
response = await httpRequest()
}
if (method !== 'GET') {
return response
}
// Handle pagination for GET requests
const { results } = response
while (response.next !== null) {
const { pathname, search } = new URL(response.next)
url = this.#endpoint + pathname + search
response = await httpRequest()
push.apply(results, response.results)
}
return results
}
async #synchronize(pools = this.#pools) {
const xo = this.#xo
log.debug('synchronizing')
// Cluster type
const clusterTypes = await this.#makeRequest(
`/virtualization/cluster-types/?name=${encodeURIComponent(CLUSTER_TYPE)}`,
'GET'
)
if (clusterTypes.length > 1) {
throw new Error('Found more than 1 "XCP-ng Pool" cluster type')
}
let clusterType
if (clusterTypes.length === 0) {
clusterType = await this.#makeRequest('/virtualization/cluster-types/', 'POST', {
name: CLUSTER_TYPE,
slug: CLUSTER_TYPE.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
description: 'Created by Xen Orchestra',
})
} else {
clusterType = clusterTypes[0]
}
// Clusters
const clusters = keyBy(
await this.#makeRequest(`/virtualization/clusters/?type_id=${clusterType.id}`, 'GET'),
'custom_fields.uuid'
)
const clustersToCreate = []
const clustersToUpdate = []
for (const poolId of pools) {
const pool = xo.getObject(poolId)
const cluster = clusters[pool.uuid]
const updatedCluster = {
name: pool.name_label.slice(0, NAME_MAX_LENGTH),
type: clusterType.id,
custom_fields: { uuid: pool.uuid },
}
if (cluster === undefined) {
clustersToCreate.push(updatedCluster)
} else {
// `type` needs to be flattened so we can compare the 2 objects
const patch = diff(updatedCluster, { ...cluster, type: cluster.type.id })
if (patch !== undefined) {
clustersToUpdate.push({ ...patch, id: cluster.id })
}
}
}
Object.assign(
clusters,
keyBy(
flatten(
await Promise.all(
clustersToCreate.length === 0
? []
: await this.#makeRequest('/virtualization/clusters/', 'POST', clustersToCreate),
clustersToUpdate.length === 0
? []
: await this.#makeRequest('/virtualization/clusters/', 'PATCH', clustersToUpdate)
)
),
'custom_fields.uuid'
)
)
// VMs
const vms = xo.getObjects({ filter: object => object.type === 'VM' && pools.includes(object.$pool) })
const oldNetboxVms = keyBy(
flatten(
// FIXME: It should be doable with one request:
// `cluster_id=1&cluster_id=2` but it doesn't work
// https://netbox.readthedocs.io/en/stable/rest-api/filtering/#filtering-objects
await Promise.all(
pools.map(poolId =>
this.#makeRequest(`/virtualization/virtual-machines/?cluster_id=${clusters[poolId].id}`, 'GET')
)
)
),
'custom_fields.uuid'
)
// Build collections for later
const netboxVms = {} // VM UUID → Netbox VM
const vifsByVm = {} // VM UUID → VIF
const ipsByDeviceByVm = {} // VM UUID → (VIF device → IP)
const vmsToCreate = []
const vmsToUpdate = []
for (const vm of Object.values(vms)) {
vifsByVm[vm.uuid] = vm.VIFs
const vmIpsByDevice = (ipsByDeviceByVm[vm.uuid] = {})
forEach(vm.addresses, (address, key) => {
const device = key.split('/')[0]
if (vmIpsByDevice[device] === undefined) {
vmIpsByDevice[device] = []
}
vmIpsByDevice[device].push(address)
})
const oldNetboxVm = oldNetboxVms[vm.uuid]
delete oldNetboxVms[vm.uuid]
const cluster = clusters[vm.$pool]
assert(cluster !== undefined)
const disk = Math.floor(
vm.$VBDs
.map(vbdId => xo.getObject(vbdId))
.filter(vbd => !vbd.is_cd_drive)
.map(vbd => xo.getObject(vbd.VDI))
.reduce((total, vdi) => total + vdi.size, 0) / G
)
const updatedVm = {
name: vm.name_label.slice(0, NAME_MAX_LENGTH),
cluster: cluster.id,
vcpus: vm.CPUs.number,
disk,
memory: Math.floor(vm.memory.dynamic[1] / M),
status: vm.power_state === 'Running' ? 'active' : 'offline',
custom_fields: { uuid: vm.uuid },
}
if (oldNetboxVm === undefined) {
vmsToCreate.push(updatedVm)
} else {
// Some properties need to be flattened to match the expected POST
// request objects
let patch = diff(updatedVm, {
...oldNetboxVm,
cluster: oldNetboxVm.cluster.id,
status: oldNetboxVm.status?.value,
})
// Check if a name mismatch is due to a name deduplication
if (patch?.name !== undefined) {
let match
if ((match = oldNetboxVm.name.match(/.* \((\d+)\)$/)) !== null) {
if (indexName(patch.name, match[1]) === oldNetboxVm.name) {
delete patch.name
if (isEmpty(patch)) {
patch = undefined
}
}
}
}
if (patch !== undefined) {
// $cluster is needed to deduplicate the VM names within the same
// cluster. It will be removed at that step.
vmsToUpdate.push({ ...patch, id: oldNetboxVm.id, $cluster: cluster.id })
} else {
netboxVms[vm.uuid] = oldNetboxVm
}
}
}
// Deduplicate VM names
vmsToCreate.forEach((vm, i) => {
const name = vm.name
let nameIndex = 1
while (
find(netboxVms, netboxVm => netboxVm.cluster.id === vm.cluster && netboxVm.name === vm.name) !== undefined ||
find(
vmsToCreate,
(vmToCreate, j) => vmToCreate.cluster === vm.cluster && vmToCreate.name === vm.name && i !== j
) !== undefined
) {
if (nameIndex >= 1e3) {
throw new Error(`Cannot deduplicate name of VM ${name}`)
}
vm.name = indexName(name, nameIndex++)
}
})
vmsToUpdate.forEach((vm, i) => {
const name = vm.name
if (name === undefined) {
delete vm.$cluster
return
}
let nameIndex = 1
while (
find(netboxVms, netboxVm => netboxVm.cluster.id === vm.$cluster && netboxVm.name === vm.name) !== undefined ||
find(vmsToCreate, vmToCreate => vmToCreate.cluster === vm.$cluster && vmToCreate.name === vm.name) !==
undefined ||
find(
vmsToUpdate,
(vmToUpdate, j) => vmToUpdate.$cluster === vm.$cluster && vmToUpdate.name === vm.name && i !== j
) !== undefined
) {
if (nameIndex >= 1e3) {
throw new Error(`Cannot deduplicate name of VM ${name}`)
}
vm.name = indexName(name, nameIndex++)
}
delete vm.$cluster
})
const vmsToDelete = Object.values(oldNetboxVms).map(vm => ({ id: vm.id }))
Object.assign(
netboxVms,
keyBy(
flatten(
(
await Promise.all([
vmsToDelete.length !== 0 &&
(await this.#makeRequest('/virtualization/virtual-machines/', 'DELETE', vmsToDelete)),
vmsToCreate.length === 0
? []
: await this.#makeRequest('/virtualization/virtual-machines/', 'POST', vmsToCreate),
vmsToUpdate.length === 0
? []
: await this.#makeRequest('/virtualization/virtual-machines/', 'PATCH', vmsToUpdate),
])
).slice(1)
),
'custom_fields.uuid'
)
)
// Interfaces
// { vmUuid: { ifName: if } }
const oldInterfaces = mapValues(
groupBy(
flatten(
await Promise.all(
pools.map(poolId =>
this.#makeRequest(`/virtualization/interfaces/?cluster_id=${clusters[poolId].id}`, 'GET')
)
)
),
'virtual_machine.id'
),
interfaces => keyBy(interfaces, 'name')
)
const interfaces = {} // VIF UUID → interface
const interfacesToCreateByVif = {} // VIF UUID → interface
const interfacesToUpdateByVif = {} // VIF UUID → interface
for (const [vmUuid, vifs] of Object.entries(vifsByVm)) {
const netboxVmId = netboxVms[vmUuid].id
const vmInterfaces = oldInterfaces[netboxVmId] ?? {}
for (const vifId of vifs) {
const vif = xo.getObject(vifId)
const name = `eth${vif.device}`
const oldInterface = vmInterfaces[name]
delete vmInterfaces[name]
const updatedInterface = {
name,
mac_address: vif.MAC.toUpperCase(),
virtual_machine: netboxVmId,
}
if (oldInterface === undefined) {
interfacesToCreateByVif[vif.uuid] = updatedInterface
} else {
const patch = diff(updatedInterface, {
...oldInterface,
virtual_machine: oldInterface.virtual_machine.id,
})
if (patch !== undefined) {
interfacesToUpdateByVif[vif.uuid] = { ...patch, id: oldInterface.id }
} else {
interfaces[vif.uuid] = oldInterface
}
}
}
}
const interfacesToDelete = flatten(
Object.values(oldInterfaces).map(oldInterfacesByName =>
Object.values(oldInterfacesByName).map(oldInterface => ({ id: oldInterface.id }))
)
)
;(
await Promise.all([
interfacesToDelete.length !== 0 &&
this.#makeRequest('/virtualization/interfaces/', 'DELETE', interfacesToDelete),
isEmpty(interfacesToCreateByVif)
? {}
: this.#makeRequest(
'/virtualization/interfaces/',
'POST',
Object.values(interfacesToCreateByVif)
).then(interfaces => zipObject(Object.keys(interfacesToCreateByVif), interfaces)),
isEmpty(interfacesToUpdateByVif)
? {}
: this.#makeRequest(
'/virtualization/interfaces/',
'PATCH',
Object.values(interfacesToUpdateByVif)
).then(interfaces => zipObject(Object.keys(interfacesToUpdateByVif), interfaces)),
])
)
.slice(1)
.forEach(newInterfaces => Object.assign(interfaces, newInterfaces))
// IPs
const [oldNetboxIps, prefixes] = await Promise.all([
this.#makeRequest('/ipam/ip-addresses/', 'GET').then(addresses => groupBy(addresses, 'assigned_object_id')),
this.#makeRequest('/ipam/prefixes/', 'GET'),
])
const ipsToDelete = []
const ipsToCreate = []
const ignoredIps = []
for (const [vmUuid, vifs] of Object.entries(vifsByVm)) {
const vmIpsByDevice = ipsByDeviceByVm[vmUuid]
if (vmIpsByDevice === undefined) {
continue
}
for (const vifId of vifs) {
const vif = xo.getObject(vifId)
const vifIps = vmIpsByDevice[vif.device]
if (vifIps === undefined) {
continue
}
const interface_ = interfaces[vif.uuid]
const interfaceOldIps = oldNetboxIps[interface_.id] ?? []
for (const ip of vifIps) {
// FIXME: Should we compare the IPs with their range? ie: can 2 IPs
// look identical but belong to 2 different ranges?
const netboxIpIndex = interfaceOldIps.findIndex(netboxIp => netboxIp.address.split('/')[0] === ip)
if (netboxIpIndex >= 0) {
interfaceOldIps.splice(netboxIpIndex, 1)
} else {
const prefix = prefixes.find(({ prefix }) => isInSubnet(ip, prefix))
if (prefix === undefined) {
ignoredIps.push(ip)
continue
}
ipsToCreate.push({
address: `${ip}/${prefix.prefix.split('/')[1]}`,
assigned_object_type: 'virtualization.vminterface',
assigned_object_id: interface_.id,
})
}
}
ipsToDelete.push(...interfaceOldIps.map(oldIp => ({ id: oldIp.id })))
}
}
if (ignoredIps.length > 0) {
log.warn('Could not find prefix for some IPs: ignoring them.', { ips: ignoredIps })
}
await Promise.all([
ipsToDelete.length !== 0 && this.#makeRequest('/ipam/ip-addresses/', 'DELETE', ipsToDelete),
ipsToCreate.length !== 0 && this.#makeRequest('/ipam/ip-addresses/', 'POST', ipsToCreate),
])
log.debug('synchronized')
}
async test() {
const randomSuffix = Math.random().toString(36).slice(2)
const name = '[TMP] Xen Orchestra Netbox plugin test - ' + randomSuffix
await this.#makeRequest('/virtualization/cluster-types/', 'POST', {
name,
slug: 'xo-test-' + randomSuffix,
description:
"This type has been created by Xen Orchestra's Netbox plugin test. If it hasn't been properly deleted, you may delete it manually.",
})
const clusterTypes = await this.#makeRequest(
`/virtualization/cluster-types/?name=${encodeURIComponent(name)}`,
'GET'
)
if (clusterTypes.length !== 1) {
throw new Error('Could not properly write and read Netbox')
}
await this.#makeRequest('/virtualization/cluster-types/', 'DELETE', [{ id: clusterTypes[0].id }])
}
}
export const configurationSchema = ({ xo: { apiMethods } }) => ({
description:
'Synchronize pools managed by Xen Orchestra with Netbox. Configuration steps: https://xen-orchestra.com/docs/advanced.html#netbox.',
type: 'object',
properties: {
endpoint: {
type: 'string',
title: 'Endpoint',
description: 'Netbox URI',
},
token: {
type: 'string',
title: 'Token',
description: 'Generate a token with write permissions from your Netbox interface',
},
pools: {
type: 'array',
title: 'Pools',
description: 'Pools to synchronize with Netbox',
items: {
type: 'string',
$type: 'pool',
},
},
syncInterval: {
type: 'number',
title: 'Interval',
description: 'Synchronization interval in hours - leave empty to disable auto-sync',
},
},
required: ['endpoint', 'token', 'pools'],
})
export default opts => new Netbox(opts)
@@ -794,6 +794,8 @@ const messages = {
setpoolMaster: 'Master',
syslogRemoteHost: 'Remote syslog host',
defaultMigrationNetwork: 'Default migration network',
syncNetbox: 'Synchronize with Netbox',
syncNetboxWarning: 'Are you sure you want to synchronize with Netbox?',
// ----- Pool host tab -----
hostNameLabel: 'Name',
hostDescription: 'Description',
+9
View File
@@ -3001,3 +3001,12 @@ export const synchronizeLdapGroups = () =>
body: _('syncLdapGroupsWarning'),
icon: 'refresh',
}).then(() => _call('ldap.synchronizeGroups')::tap(subscribeGroups.forceRefresh), noop)
// Netbox plugin ---------------------------------------------------------------
export const synchronizeNetbox = pools =>
confirm({
title: _('syncNetbox'),
body: _('syncNetboxWarning'),
icon: 'refresh',
}).then(() => _call('netbox.synchronize', { pools: resolveIds(pools) }))
@@ -7,8 +7,9 @@ import Component from 'base-component'
import Icon from 'icon'
import renderXoItem, { Network } from 'render-xo-item'
import SelectFiles from 'select-files'
import TabButton from 'tab-button'
import Upgrade from 'xoa-upgrade'
import { connectStore } from 'utils'
import { addSubscriptions, connectStore } from 'utils'
import { Container, Row, Col } from 'grid'
import { CustomFields } from 'custom-fields'
import { injectIntl } from 'react-intl'
@@ -28,6 +29,8 @@ import {
setPoolMaster,
setRemoteSyslogHost,
setRemoteSyslogHosts,
subscribePlugins,
synchronizeNetbox,
} from 'xo'
@connectStore(() => ({
@@ -65,6 +68,9 @@ class PoolMaster extends Component {
migrationNetwork: createGetObject((_, { pool }) => pool.otherConfig['xo:migrationNetwork']),
}
})
@addSubscriptions({
plugins: subscribePlugins,
})
export default class TabAdvanced extends Component {
_getMigrationNetworkPredicate = createSelector(
createCollectionWrapper(
@@ -84,6 +90,11 @@ export default class TabAdvanced extends Component {
networkIds => network => networkIds.has(network.id)
)
_isNetboxPluginLoaded = createSelector(
() => this.props.plugins,
plugins => plugins !== undefined && plugins.some(plugin => plugin.name === 'netbox' && plugin.loaded)
)
_onChangeMigrationNetwork = migrationNetwork => editPool(this.props.pool, { migrationNetwork: migrationNetwork.id })
_removeMigrationNetwork = () => editPool(this.props.pool, { migrationNetwork: null })
@@ -101,6 +112,19 @@ export default class TabAdvanced extends Component {
return (
<div>
<Container>
{this._isNetboxPluginLoaded() && (
<Row>
<Col className='text-xs-right'>
<TabButton
btnStyle='primary'
handler={synchronizeNetbox}
handlerParam={[pool]}
icon='refresh'
labelId='syncNetbox'
/>
</Col>
</Row>
)}
<Row>
<Col>
<h3>{_('xenSettingsLabel')}</h3>
+352 -6
View File
@@ -19,6 +19,22 @@
"@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents"
chokidar "^3.4.0"
"@babel/cli@^7.13.16":
version "7.13.16"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.13.16.tgz#9d372e943ced0cc291f068204a9b010fd9cfadbc"
integrity sha512-cL9tllhqvsQ6r1+d9Invf7nNXg/3BlfL1vvvL/AdH9fZ2l5j0CeBcoq6UjsqHpvyN1v5nXSZgqJZoGeK+ZOAbw==
dependencies:
commander "^4.0.1"
convert-source-map "^1.1.0"
fs-readdir-recursive "^1.1.0"
glob "^7.0.0"
make-dir "^2.1.0"
slash "^2.0.0"
source-map "^0.5.0"
optionalDependencies:
"@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents"
chokidar "^3.4.0"
"@babel/code-frame@7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
@@ -38,6 +54,11 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.15.tgz#7e8eea42d0b64fda2b375b22d06c605222e848f4"
integrity sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==
"@babel/compat-data@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.0.tgz#a901128bce2ad02565df95e6ecbf195cf9465919"
integrity sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==
"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.5", "@babel/core@^7.1.6", "@babel/core@^7.11.0", "@babel/core@^7.13.8", "@babel/core@^7.3.3", "@babel/core@^7.4.4", "@babel/core@^7.7.2", "@babel/core@^7.7.4", "@babel/core@^7.7.5", "@babel/core@^7.8.4":
version "7.13.15"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.15.tgz#a6d40917df027487b54312202a06812c4f7792d0"
@@ -59,6 +80,27 @@
semver "^6.3.0"
source-map "^0.5.0"
"@babel/core@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.0.tgz#47299ff3ec8d111b493f1a9d04bf88c04e728d88"
integrity sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==
dependencies:
"@babel/code-frame" "^7.12.13"
"@babel/generator" "^7.14.0"
"@babel/helper-compilation-targets" "^7.13.16"
"@babel/helper-module-transforms" "^7.14.0"
"@babel/helpers" "^7.14.0"
"@babel/parser" "^7.14.0"
"@babel/template" "^7.12.13"
"@babel/traverse" "^7.14.0"
"@babel/types" "^7.14.0"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.1.2"
semver "^6.3.0"
source-map "^0.5.0"
"@babel/eslint-parser@^7.13.8":
version "7.13.14"
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.13.14.tgz#f80fd23bdd839537221914cb5d17720a5ea6ba3a"
@@ -77,6 +119,15 @@
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/generator@^7.14.0":
version "7.14.1"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.1.tgz#1f99331babd65700183628da186f36f63d615c93"
integrity sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==
dependencies:
"@babel/types" "^7.14.1"
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab"
@@ -102,6 +153,16 @@
browserslist "^4.14.5"
semver "^6.3.0"
"@babel/helper-compilation-targets@^7.13.16":
version "7.13.16"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz#6e91dccf15e3f43e5556dffe32d860109887563c"
integrity sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==
dependencies:
"@babel/compat-data" "^7.13.15"
"@babel/helper-validator-option" "^7.12.17"
browserslist "^4.14.5"
semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.13.11":
version "7.13.11"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz#30d30a005bca2c953f5653fc25091a492177f4f6"
@@ -113,6 +174,18 @@
"@babel/helper-replace-supers" "^7.13.0"
"@babel/helper-split-export-declaration" "^7.12.13"
"@babel/helper-create-class-features-plugin@^7.14.0":
version "7.14.1"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.1.tgz#1fe11b376f3c41650ad9fedc665b0068722ea76c"
integrity sha512-r8rsUahG4ywm0QpGcCrLaUSOuNAISR3IZCg4Fx05Ozq31aCUrQsTLH6KPxy0N5ULoQ4Sn9qjNdGNtbPWAC6hYg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.12.13"
"@babel/helper-function-name" "^7.12.13"
"@babel/helper-member-expression-to-functions" "^7.13.12"
"@babel/helper-optimise-call-expression" "^7.12.13"
"@babel/helper-replace-supers" "^7.13.12"
"@babel/helper-split-export-declaration" "^7.12.13"
"@babel/helper-create-regexp-features-plugin@^7.12.13":
version "7.12.17"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7"
@@ -194,6 +267,20 @@
"@babel/traverse" "^7.13.13"
"@babel/types" "^7.13.14"
"@babel/helper-module-transforms@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz#8fcf78be220156f22633ee204ea81f73f826a8ad"
integrity sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw==
dependencies:
"@babel/helper-module-imports" "^7.13.12"
"@babel/helper-replace-supers" "^7.13.12"
"@babel/helper-simple-access" "^7.13.12"
"@babel/helper-split-export-declaration" "^7.12.13"
"@babel/helper-validator-identifier" "^7.14.0"
"@babel/template" "^7.12.13"
"@babel/traverse" "^7.14.0"
"@babel/types" "^7.14.0"
"@babel/helper-optimise-call-expression@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea"
@@ -251,6 +338,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
"@babel/helper-validator-identifier@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==
"@babel/helper-validator-option@^7.12.17":
version "7.12.17"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831"
@@ -275,6 +367,15 @@
"@babel/traverse" "^7.13.0"
"@babel/types" "^7.13.0"
"@babel/helpers@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.0.tgz#ea9b6be9478a13d6f961dbb5f36bf75e2f3b8f62"
integrity sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==
dependencies:
"@babel/template" "^7.12.13"
"@babel/traverse" "^7.14.0"
"@babel/types" "^7.14.0"
"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
@@ -289,6 +390,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.15.tgz#8e66775fb523599acb6a289e12929fa5ab0954d8"
integrity sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==
"@babel/parser@^7.14.0":
version "7.14.1"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.1.tgz#1bd644b5db3f5797c4479d89ec1817fe02b84c47"
integrity sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12":
version "7.13.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a"
@@ -315,6 +421,14 @@
"@babel/helper-create-class-features-plugin" "^7.13.0"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-proposal-class-static-block@^7.13.11":
version "7.13.11"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.13.11.tgz#6fcbba4a962702c17e5371a0c7b39afde186d703"
integrity sha512-fJTdFI4bfnMjvxJyNuaf8i9mVcZ0UhetaGEUHaHV9KEnibLugJkZAtXikR8KcYj+NYmI4DZMS8yQAyg+hvfSqg==
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-syntax-class-static-block" "^7.12.13"
"@babel/plugin-proposal-decorators@^7.0.0", "@babel/plugin-proposal-decorators@^7.1.6", "@babel/plugin-proposal-decorators@^7.13.0", "@babel/plugin-proposal-decorators@^7.3.0", "@babel/plugin-proposal-decorators@^7.4.0", "@babel/plugin-proposal-decorators@^7.8.0", "@babel/plugin-proposal-decorators@^7.8.3":
version "7.13.15"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.15.tgz#e91ccfef2dc24dd5bd5dcc9fc9e2557c684ecfb8"
@@ -432,6 +546,16 @@
"@babel/helper-create-class-features-plugin" "^7.13.0"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-proposal-private-property-in-object@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz#b1a1f2030586b9d3489cc26179d2eb5883277636"
integrity sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.12.13"
"@babel/helper-create-class-features-plugin" "^7.14.0"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-syntax-private-property-in-object" "^7.14.0"
"@babel/plugin-proposal-throw-expressions@^7.0.0":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.12.13.tgz#48a6e4a5988041d16b0a2f1568a3b518f8b6c1d4"
@@ -469,6 +593,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.12.13"
"@babel/plugin-syntax-class-static-block@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz#8e3d674b0613e67975ceac2776c97b60cafc5c9c"
integrity sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A==
dependencies:
"@babel/helper-plugin-utils" "^7.12.13"
"@babel/plugin-syntax-decorators@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.13.tgz#fac829bf3c7ef4a1bc916257b403e58c6bdaf648"
@@ -574,6 +705,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.12.13"
"@babel/plugin-syntax-private-property-in-object@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz#762a4babec61176fec6c88480dec40372b140c0b"
integrity sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w==
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-syntax-throw-expressions@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.12.13.tgz#bb02bfbaf57d71ab69280ebf6a53aa45ad4c3f1a"
@@ -618,6 +756,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.12.13"
"@babel/plugin-transform-block-scoping@^7.14.1":
version "7.14.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.1.tgz#ac1b3a8e3d8cbb31efc6b9be2f74eb9823b74ab2"
integrity sha512-2mQXd0zBrwfp0O1moWIhPpEeTKDvxyHcnma3JATVP1l+CctWBuot6OJG8LQ4DnBj4ZZPSmlb/fm4mu47EOAnVA==
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-transform-classes@^7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b"
@@ -645,6 +790,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-transform-destructuring@^7.13.17":
version "7.13.17"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz#678d96576638c19d5b36b332504d3fd6e06dea27"
integrity sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-transform-dotall-regex@^7.12.13", "@babel/plugin-transform-dotall-regex@^7.4.4":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad"
@@ -706,6 +858,15 @@
"@babel/helper-plugin-utils" "^7.13.0"
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-modules-amd@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.0.tgz#589494b5b290ff76cf7f59c798011f6d77026553"
integrity sha512-CF4c5LX4LQ03LebQxJ5JZes2OYjzBuk1TdiF7cG7d5dK4lAdw9NZmaxq5K/mouUdNeqwz3TNjnW6v01UqUNgpQ==
dependencies:
"@babel/helper-module-transforms" "^7.14.0"
"@babel/helper-plugin-utils" "^7.13.0"
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-modules-commonjs@^7.13.8":
version "7.13.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b"
@@ -716,6 +877,16 @@
"@babel/helper-simple-access" "^7.12.13"
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-modules-commonjs@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz#52bc199cb581e0992edba0f0f80356467587f161"
integrity sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==
dependencies:
"@babel/helper-module-transforms" "^7.14.0"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/helper-simple-access" "^7.13.12"
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-modules-systemjs@^7.13.8":
version "7.13.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3"
@@ -735,6 +906,14 @@
"@babel/helper-module-transforms" "^7.13.0"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-transform-modules-umd@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz#2f8179d1bbc9263665ce4a65f305526b2ea8ac34"
integrity sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==
dependencies:
"@babel/helper-module-transforms" "^7.14.0"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-transform-named-capturing-groups-regex@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9"
@@ -956,6 +1135,85 @@
core-js-compat "^3.9.0"
semver "^6.3.0"
"@babel/preset-env@^7.14.1":
version "7.14.1"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.1.tgz#b55914e2e68885ea03f69600b2d3537e54574a93"
integrity sha512-0M4yL1l7V4l+j/UHvxcdvNfLB9pPtIooHTbEhgD/6UGyh8Hy3Bm1Mj0buzjDXATCSz3JFibVdnoJZCrlUCanrQ==
dependencies:
"@babel/compat-data" "^7.14.0"
"@babel/helper-compilation-targets" "^7.13.16"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/helper-validator-option" "^7.12.17"
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.13.12"
"@babel/plugin-proposal-async-generator-functions" "^7.13.15"
"@babel/plugin-proposal-class-properties" "^7.13.0"
"@babel/plugin-proposal-class-static-block" "^7.13.11"
"@babel/plugin-proposal-dynamic-import" "^7.13.8"
"@babel/plugin-proposal-export-namespace-from" "^7.12.13"
"@babel/plugin-proposal-json-strings" "^7.13.8"
"@babel/plugin-proposal-logical-assignment-operators" "^7.13.8"
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8"
"@babel/plugin-proposal-numeric-separator" "^7.12.13"
"@babel/plugin-proposal-object-rest-spread" "^7.13.8"
"@babel/plugin-proposal-optional-catch-binding" "^7.13.8"
"@babel/plugin-proposal-optional-chaining" "^7.13.12"
"@babel/plugin-proposal-private-methods" "^7.13.0"
"@babel/plugin-proposal-private-property-in-object" "^7.14.0"
"@babel/plugin-proposal-unicode-property-regex" "^7.12.13"
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-syntax-class-properties" "^7.12.13"
"@babel/plugin-syntax-class-static-block" "^7.12.13"
"@babel/plugin-syntax-dynamic-import" "^7.8.3"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"
"@babel/plugin-syntax-json-strings" "^7.8.3"
"@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
"@babel/plugin-syntax-numeric-separator" "^7.10.4"
"@babel/plugin-syntax-object-rest-spread" "^7.8.3"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-syntax-private-property-in-object" "^7.14.0"
"@babel/plugin-syntax-top-level-await" "^7.12.13"
"@babel/plugin-transform-arrow-functions" "^7.13.0"
"@babel/plugin-transform-async-to-generator" "^7.13.0"
"@babel/plugin-transform-block-scoped-functions" "^7.12.13"
"@babel/plugin-transform-block-scoping" "^7.14.1"
"@babel/plugin-transform-classes" "^7.13.0"
"@babel/plugin-transform-computed-properties" "^7.13.0"
"@babel/plugin-transform-destructuring" "^7.13.17"
"@babel/plugin-transform-dotall-regex" "^7.12.13"
"@babel/plugin-transform-duplicate-keys" "^7.12.13"
"@babel/plugin-transform-exponentiation-operator" "^7.12.13"
"@babel/plugin-transform-for-of" "^7.13.0"
"@babel/plugin-transform-function-name" "^7.12.13"
"@babel/plugin-transform-literals" "^7.12.13"
"@babel/plugin-transform-member-expression-literals" "^7.12.13"
"@babel/plugin-transform-modules-amd" "^7.14.0"
"@babel/plugin-transform-modules-commonjs" "^7.14.0"
"@babel/plugin-transform-modules-systemjs" "^7.13.8"
"@babel/plugin-transform-modules-umd" "^7.14.0"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13"
"@babel/plugin-transform-new-target" "^7.12.13"
"@babel/plugin-transform-object-super" "^7.12.13"
"@babel/plugin-transform-parameters" "^7.13.0"
"@babel/plugin-transform-property-literals" "^7.12.13"
"@babel/plugin-transform-regenerator" "^7.13.15"
"@babel/plugin-transform-reserved-words" "^7.12.13"
"@babel/plugin-transform-shorthand-properties" "^7.12.13"
"@babel/plugin-transform-spread" "^7.13.0"
"@babel/plugin-transform-sticky-regex" "^7.12.13"
"@babel/plugin-transform-template-literals" "^7.13.0"
"@babel/plugin-transform-typeof-symbol" "^7.12.13"
"@babel/plugin-transform-unicode-escapes" "^7.12.13"
"@babel/plugin-transform-unicode-regex" "^7.12.13"
"@babel/preset-modules" "^0.1.4"
"@babel/types" "^7.14.1"
babel-plugin-polyfill-corejs2 "^0.2.0"
babel-plugin-polyfill-corejs3 "^0.2.0"
babel-plugin-polyfill-regenerator "^0.2.0"
core-js-compat "^3.9.0"
semver "^6.3.0"
"@babel/preset-modules@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e"
@@ -1020,6 +1278,20 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.0.tgz#cea0dc8ae7e2b1dec65f512f39f3483e8cc95aef"
integrity sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==
dependencies:
"@babel/code-frame" "^7.12.13"
"@babel/generator" "^7.14.0"
"@babel/helper-function-name" "^7.12.13"
"@babel/helper-split-export-declaration" "^7.12.13"
"@babel/parser" "^7.14.0"
"@babel/types" "^7.14.0"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.14", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.6.1", "@babel/types@^7.9.6":
version "7.13.14"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d"
@@ -1029,6 +1301,14 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@babel/types@^7.14.0", "@babel/types@^7.14.1":
version "7.14.1"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.1.tgz#095bd12f1c08ab63eff6e8f7745fa7c9cc15a9db"
integrity sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==
dependencies:
"@babel/helper-validator-identifier" "^7.14.0"
to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -2654,6 +2934,16 @@ assert@^1.1.1, assert@^1.4.0:
object-assign "^4.1.1"
util "0.10.3"
assert@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32"
integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==
dependencies:
es6-object-assign "^1.1.0"
is-nan "^1.2.1"
object-is "^1.0.1"
util "^0.12.0"
assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
@@ -2759,6 +3049,13 @@ autoprefixer@^9.5.1, autoprefixer@^9.6.1:
postcss "^7.0.32"
postcss-value-parser "^4.1.0"
available-typed-arrays@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==
dependencies:
array-filter "^1.0.0"
aws-sdk@^2.686.0:
version "2.889.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.889.0.tgz#513457f488305a8ff5593747741b67e7b874bdf7"
@@ -4557,7 +4854,7 @@ create-react-class@^15.5.1, create-react-class@^15.6.0:
loose-envify "^1.3.1"
object-assign "^4.1.1"
cross-env@^7.0.2:
cross-env@^7.0.2, cross-env@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
@@ -6085,6 +6382,11 @@ es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.3:
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-object-assign@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=
es6-promise@^4.1.0:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@@ -9023,6 +9325,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-in-subnet@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-in-subnet/-/is-in-subnet-4.0.1.tgz#7a93bf67636021598dc483e934d8841cb8b7a537"
integrity sha512-D3mAuAo6vZ+/AxsLkEIZ3moTx7AIGQLLzLQslV6n0RRO/CzdUemXap+lj3OPAehKCbdkGPikxOVUYqRo0GGJAA==
is-installed-globally@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
@@ -9038,6 +9345,14 @@ is-ip@^3.1.0:
dependencies:
ip-regex "^4.0.0"
is-nan@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
dependencies:
call-bind "^1.0.0"
define-properties "^1.1.3"
is-negated-glob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
@@ -9210,6 +9525,17 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.1"
is-typed-array@^1.1.3:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e"
integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==
dependencies:
available-typed-arrays "^1.0.2"
call-bind "^1.0.2"
es-abstract "^1.18.0-next.2"
foreach "^2.0.5"
has-symbols "^1.0.1"
is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -10349,11 +10675,6 @@ limit-concurrency-decorator@^0.5.0:
resolved "https://registry.yarnpkg.com/limit-concurrency-decorator/-/limit-concurrency-decorator-0.5.0.tgz#7455fc7c8d12e93ce725cb98dc18b861397dd726"
integrity sha512-s5HqdnTpRJhvK/vleMY3qJ3yEfIQ1BUCUqbBJwtXCKngMSc+qpS1Rl6/rxdhr1Z/oQz3keYho6G4XCFSHb7nbA==
limit-concurrency-decorator@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/limit-concurrency-decorator/-/limit-concurrency-decorator-0.5.0.tgz#7455fc7c8d12e93ce725cb98dc18b861397dd726"
integrity sha512-s5HqdnTpRJhvK/vleMY3qJ3yEfIQ1BUCUqbBJwtXCKngMSc+qpS1Rl6/rxdhr1Z/oQz3keYho6G4XCFSHb7nbA==
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
@@ -16926,6 +17247,18 @@ util@^0.11.0:
dependencies:
inherits "2.0.3"
util@^0.12.0:
version "0.12.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.12.3.tgz#971bb0292d2cc0c892dab7c6a5d37c2bec707888"
integrity sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==
dependencies:
inherits "^2.0.3"
is-arguments "^1.0.4"
is-generator-function "^1.0.7"
is-typed-array "^1.1.3"
safe-buffer "^5.1.2"
which-typed-array "^1.1.2"
util@~0.10.1:
version "0.10.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
@@ -17520,6 +17853,19 @@ which-pm-runs@^1.0.0:
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
which-typed-array@^1.1.2:
version "1.1.4"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff"
integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==
dependencies:
available-typed-arrays "^1.0.2"
call-bind "^1.0.0"
es-abstract "^1.18.0-next.1"
foreach "^2.0.5"
function-bind "^1.1.1"
has-symbols "^1.0.1"
is-typed-array "^1.1.3"
which@1, which@^1.2.14, which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"