UI - Vault API explorer engine (#7044)

* open-api-explorer engine with embedded swagger-ui

* move swagger config to a component, rely directly on swagger-ui

* filter operations by endpoint, hook up filter to query param, add namespace handling

* fix namespace handling

* update ember-engines so that we can app.import in a lazy engine

* use engine's included hook to move swagger-ui to engine-vendor.* files

* show flash message about this being a live vault server

* show a namespace reminder and override some styles from swagger-ui

* switch filter to use includes instead of startsWith

* move flash-message to alert-banner and fix namespace reminder with a block

* adds explore web-cli command to navigate to the api-explorer engine

* allow passing a preformatted string to flash messages

* add multi-line flash-message to api explorer

* invert control and trigger events on react app so we can control the layout more and use our components

* tweak styling some more and adjust message on the flash

* change web cli command from 'explore' to 'api'

* shorten namespace warning

* fix console

* fix comments
This commit is contained in:
Matthew Irish 2019-07-02 17:41:23 -05:00 committed by GitHub
parent 9baf59dcdc
commit 311cc49c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 559 additions and 54 deletions

View File

@ -14,6 +14,11 @@ App = Application.extend({
podModulePrefix: config.podModulePrefix,
Resolver,
engines: {
openApiExplorer: {
dependencies: {
services: ['auth', 'flash-messages', 'namespace', 'router', 'version'],
},
},
replication: {
dependencies: {
services: [

View File

@ -45,13 +45,13 @@ export default Component.extend({
let serviceArgs;
if (
executeUICommand(
command,
args => this.logAndOutput(args),
args => service.clearLog(args),
() => this.toggleProperty('isFullscreen'),
() => this.get('refreshRoute').perform()
)
executeUICommand(command, args => this.logAndOutput(args), {
api: () => this.routeToExplore.perform(command),
clearall: () => service.clearLog(true),
clear: () => service.clearLog(),
fullscreen: () => this.toggleProperty('isFullscreen'),
refresh: () => this.refreshRoute.perform(),
})
) {
return;
}
@ -104,6 +104,29 @@ export default Component.extend({
}
}),
routeToExplore: task(function*(command) {
let filter = command.replace('api', '').trim();
try {
yield this.router.transitionTo('vault.cluster.open-api-explorer.index', {
queryParams: { filter },
});
let content =
'Welcome to the Vault API explorer! \nYou can search for endpoints, see what parameters they accept, and even execute requests with your current token.';
if (filter) {
content = `Welcome to the Vault API explorer! \nWe've filtered the list of endpoints for '${filter}'.`;
}
this.logAndOutput(null, {
type: 'success',
content,
});
} catch (error) {
this.logAndOutput(null, {
type: 'error',
content: 'There was a problem navigating to the api explorer.',
});
}
}),
shiftCommandIndex(keyCode) {
this.get('console').shiftCommandIndex(keyCode, val => {
this.set('inputValue', val);

View File

@ -2,7 +2,7 @@ import keys from 'vault/lib/keycodes';
import argTokenizer from 'yargs-parser/lib/tokenize-arg-string.js';
const supportedCommands = ['read', 'write', 'list', 'delete'];
const uiCommands = ['clearall', 'clear', 'fullscreen', 'refresh'];
const uiCommands = ['api', 'clearall', 'clear', 'fullscreen', 'refresh'];
export function extractDataAndFlags(data, flags) {
return data.concat(flags).reduce(
@ -32,26 +32,15 @@ export function extractDataAndFlags(data, flags) {
);
}
export function executeUICommand(command, logAndOutput, clearLog, toggleFullscreen, refreshFn) {
const isUICommand = uiCommands.includes(command);
export function executeUICommand(command, logAndOutput, commandFns) {
let cmd = command.startsWith('api') ? 'api' : command;
let isUICommand = uiCommands.includes(cmd);
if (isUICommand) {
logAndOutput(command);
}
switch (command) {
case 'clearall':
clearLog(true);
break;
case 'clear':
clearLog();
break;
case 'fullscreen':
toggleFullscreen();
break;
case 'refresh':
refreshFn();
break;
if (typeof commandFns[cmd] === 'function') {
commandFns[cmd]();
}
return isUICommand;
}

View File

@ -1,6 +1,6 @@
import DS from 'ember-data';
import { computed } from '@ember/object';
import parseURL from 'vault/utils/parse-url';
import parseURL from 'core/utils/parse-url';
const { attr } = DS;
const DOMAIN_STRINGS = {

View File

@ -13,6 +13,7 @@ Router.map(function() {
this.route('auth');
this.route('init');
this.route('logout');
this.mount('open-api-explorer', { path: '/api-explorer' });
this.route('license');
this.route('requests', { path: '/metrics/requests' });
this.route('settings', function() {

View File

@ -35,6 +35,9 @@
border: 0;
margin-top: $spacing-xxs;
}
.message-body.pre {
white-space: pre-wrap;
}
p {
font-size: $size-8;

View File

@ -18,9 +18,7 @@
{{type.text}}
</div>
{{#if message}}
<p class="message-body" data-test-flash-message-body="true">
{{message}}
</p>
<p class="message-body {{if @isPreformatted 'pre'}}" data-test-flash-message-body="true">{{message}}</p>
{{/if}}
</div>
</div>

View File

@ -9,9 +9,10 @@ Commands:
list List data or secrets
Web CLI Commands:
fullscreen Toggle fullscreen display
api Navigate to the Vault API explorer. Use 'api [filter]' to prefilter the list.
clear Clear output from the log
clearall Clear output and command history
fullscreen Toggle fullscreen display
refresh Refresh the data on the current screen under the CLI window
</pre>
</div>

View File

@ -95,7 +95,7 @@
{{#if flash.componentName}}
{{component flash.componentName content=flash.content}}
{{else}}
<AlertPopup @type={{message-types flash.type}} @message={{flash.message}} @close={{close}}/>
<AlertPopup @type={{message-types flash.type}} @message={{flash.message}} @close={{close}} @isPreformatted={{flash.preformatted}} />
{{/if}}
{{/flash-message}}
{{/each}}

View File

@ -23,6 +23,7 @@ module.exports = function(environment) {
POLLING_URLS: ['sys/health', 'sys/replication/status', 'sys/seal-status'],
// endpoints that UI uses to determine the cluster state
// calls to these endpoints will always go to the root namespace
// these also need to be updated in the open-api-explorer engine
NAMESPACE_ROOT_URLS: ['sys/health', 'sys/seal-status', 'sys/license/features'],
// number of records to show on a single page by default - this is used by the client-side pagination
DEFAULT_PAGE_SIZE: 100,

View File

@ -80,6 +80,7 @@ module.exports = function(defaults) {
app.import('node_modules/@hashicorp/structure-icons/dist/loading.css');
app.import('node_modules/@hashicorp/structure-icons/dist/run.css');
// Use `app.import` to add additional libraries to the generated
// output files.
//

View File

@ -1,5 +1,11 @@
{{#if showMessage}}
<p class="namespace-reminder">
This {{noun}} will be {{modeVerb}} in the <span class="tag">{{namespace.path}}/</span>namespace.
</p>
{{#if (has-block)}}
<p class="namespace-reminder">
{{yield (hash namespace=namespace)}}
</p>
{{else}}
<p class="namespace-reminder">
This {{noun}} will be {{modeVerb}} in the <span class="tag">{{namespace.path}}/</span>namespace.
</p>
{{/if}}
{{/if}}

View File

@ -0,0 +1,105 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import parseURL from 'core/utils/parse-url';
import config from 'open-api-explorer/config/environment';
import Swag from 'swagger-ui-dist';
const { SwaggerUIBundle } = Swag;
const { APP } = config;
const SearchFilterPlugin = () => {
return {
fn: {
opsFilter: (taggedOps, phrase) => {
// map over the options and filter out operations where the path doesn't match what's typed
return (
taggedOps
.map(tagObj => {
let operations = tagObj.get('operations').filter(operationObj => {
return operationObj.get('path').includes(phrase);
});
return tagObj.set('operations', operations);
})
// then traverse again and remove the top level item if there are no operations left after filtering
.filter(tagObj => !!tagObj.get('operations').size)
);
},
},
};
};
const CONFIG = (componentInstance, initialFilter) => {
return {
dom_id: `#${componentInstance.elementId}-swagger`,
url: '/v1/sys/internal/specs/openapi',
deepLinking: false,
presets: [SwaggerUIBundle.presets.apis],
plugins: [SwaggerUIBundle.plugins.DownloadUrl, SearchFilterPlugin],
// 'list' expands tags, but not operations
docExpansion: 'list',
operationsSorter: 'alpha',
filter: initialFilter || true,
// this makes sure we show the x-vault- options
showExtensions: true,
// we don't have any models defined currently
defaultModelsExpandDepth: -1,
defaultModelExpandDepth: 1,
requestInterceptor: req => {
// we need to add vault authorization header
// and namepace headers for things to work properly
req.headers['X-Vault-Token'] = componentInstance.auth.currentToken;
let namespace = componentInstance.namespaceService.path;
if (namespace && !APP.NAMESPACE_ROOT_URLS.some(str => req.url.includes(str))) {
req.headers['X-Vault-Namespace'] = namespace;
}
// we want to link to the right JSON in swagger UI so
// it's already been pre-pended
if (!req.loadSpec) {
let { protocol, host, pathname } = parseURL(req.url);
//paths in the spec don't have /v1 in them, so we need to add that here
// http(s): vlt.io:4200 /sys/mounts
req.url = `${protocol}//${host}/v1${pathname}`;
}
return req;
},
onComplete: () => {
componentInstance.set('swaggerLoading', false);
},
};
};
export default Component.extend({
auth: service(),
namespaceService: service('namespace'),
initialFilter: null,
onFilterChange() {},
swaggerLoading: true,
didInsertElement() {
this._super(...arguments);
// trim any initial slashes
let initialFilter = this.initialFilter.replace(/^(\/)+/, '');
SwaggerUIBundle(CONFIG(this, initialFilter));
},
actions: {
// sets the filter so the query param is updated so we get sharable URLs
updateFilter(e) {
this.onFilterChange(e.target.value || '');
},
proxyEvent(e) {
let swaggerInput = this.element.querySelector('.operation-filter-input');
// if this breaks because of a react upgrade,
// change this to
//let originalSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
//originalSetter.call(swaggerInput, e.target.value);
// see post on triggering react events externally for an explanation of
// why this works: https://stackoverflow.com/a/46012210
let evt = new Event('input', { bubbles: true });
evt.simulated = true;
swaggerInput.value = e.target.value.replace(/^(\/)+/, '');
swaggerInput.dispatchEvent(evt);
},
},
});

View File

@ -0,0 +1,6 @@
import Controller from '@ember/controller';
export default Controller.extend({
queryParams: ['filter'],
filter: '',
});

View File

@ -0,0 +1,18 @@
import Engine from 'ember-engines/engine';
import loadInitializers from 'ember-load-initializers';
import Resolver from './resolver';
import config from './config/environment';
const { modulePrefix } = config;
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
const Eng = Engine.extend({
modulePrefix,
Resolver,
dependencies: {
services: ['auth', 'flash-messages', 'namespace', 'router', 'version'],
},
});
loadInitializers(Eng, modulePrefix);
export default Eng;

View File

@ -0,0 +1,3 @@
import Resolver from 'ember-resolver';
export default Resolver;

View File

@ -0,0 +1,5 @@
import buildRoutes from 'ember-engines/routes';
export default buildRoutes(function() {
// Define your engine's route map here
});

View File

@ -0,0 +1,20 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default Route.extend({
flashMessages: service(),
// without an empty model hook here, ember likes to use the parent model, and then things get weird with
// query params, so here we're no-op'ing the model hook
model() {},
afterModel() {
let warning = `The "Try it out" functionality in this API explorer will make requests to this Vault server on your behalf.
IF YOUR TOKEN HAS THE PROPER CAPABILITIES, THIS WILL CREATE AND DELETE ITEMS ON THE VAULT SERVER.
Your token will also be shown on the screen in the example curl command output.`;
this.flashMessages.warning(warning, {
sticky: true,
preformatted: true,
});
},
});

View File

@ -0,0 +1,159 @@
/*THIS FILE LOADS AFTER THE SWAGGER-UI CSS, SO WE'LL USE IT TO OVERRIDE STYLES */
.swagger-ui .wrapper {
padding: 0;
}
.swagger-ui .info {
margin: 25px 0;
}
/*hide the swagger-ui headers*/
.swagger-ui .filter-container,
.swagger-ui .information-container.wrapper {
display: none;
}
/*some general de-rounding and removing backgrounds and drop shadows*/
.swagger-ui .btn {
border-width: 1px;
box-shadow: none;
border-radius: 0px;
}
.swagger-ui .opblock {
background: none;
border-width: 1px;
border-radius: 2px;
box-shadow: none;
}
/*START: customize method, path, description so that it's formatted like this:*/
/* {method} {path/to/api} */
/* {A lengthy description goes here} */
.swagger-ui .opblock .opblock-summary,
.swagger-ui .opblock .opblock-summary-description {
display: block;
margin: 0;
padding: 0;
}
.swagger-ui .opblock .opblock-summary {
padding: 1rem;
}
.swagger-ui .opblock .opblock-summary-description {
font-size: 14px;
}
.swagger-ui .opblock .opblock-summary-method,
.swagger-ui .opblock .opblock-summary-path{
display: inline-block;
margin: 0;
padding: 0;
}
.swagger-ui .opblock .opblock-summary-method {
border-radius: 1px;
min-width: auto;
text-align: left;
font-size: 10px;
box-shadow: 0 0 0 1px currentColor;
position: relative;
top: -2px;
padding: 0 2px;
margin-right: 8px;
}
/*END: customize method, path, description*/
/*START: make tags look like list items */
.swagger-ui .opblock-tag{
font-size: 16px;
}
.swagger-ui .opblock-tag-section .opblock-tag {
color: #0a0a0a;
font-weight: 600 !important;
font-size: 1rem !important;
transition: box-shadow 150ms, margin 150ms, padding 150ms;
will-change: box-shadow, margin, padding;
background-color: white;
border-radius: 0;
padding: 1.25rem;
margin: 0;
}
.swagger-ui .opblock-tag:hover,
.swagger-ui .opblock-tag:focus,
.swagger-ui .opblock-tag:active {
margin-left: -0.75rem !important;
margin-right: -0.75rem !important;
padding-left: 0.75rem;
padding-right: 0.75rem;
position: relative;
box-shadow: 0 2px 0 -1px #BAC1CC, 0 -2px 0 -1px #BAC1CC, 0 0 0 1px #BAC1CC, 0 8px 4px -4px rgba(10, 10, 10, 0.1), 0 6px 8px -2px rgba(10, 10, 10, 0.05);
}
/*shrink the size of the arrows*/
.swagger-ui .expand-methods svg, .swagger-ui .expand-operation svg {
height: 12px;
width: 12px;
}
/*END: make tags look like list items */
/*operation box - GET (blue) */
.swagger-ui .opblock.opblock-get {
background: #f5f8ff;
border: 1px solid #bfd4ff;
}
/*operation label*/
.swagger-ui .opblock.opblock-get .opblock-summary-method {
color: #1563ff;
background: none;
}
/*and expanded tab highlight */
.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after {
background: #1563ff;
}
/*operation box - POST (green) */
.swagger-ui .opblock.opblock-post {
background: #fafdfa;
border: 1px solid #c6e9c9;
}
.swagger-ui .opblock.opblock-post .opblock-summary-method {
color: #2eb039;
background: none;
}
.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after {
background: #2eb039;
}
/*operation box - POST (red) */
.swagger-ui .opblock.opblock-delete {
background: #fdfafb;
border: 1px solid #f9ecee;
}
.swagger-ui .opblock.opblock-delete .opblock-summary-method {
color: #c73445;
background: none;
}
.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after {
background: #c73445;
}
/*remove "LOADING" from initial loading spinner*/
.swagger-ui .loading-container .loading::after {
content: "";
}
/*add text about requests to a live vault server*/
.swagger-ui .btn.execute::after {
content: " - send a request with your token to Vault."
}

View File

@ -0,0 +1 @@
{{outlet}}

View File

@ -0,0 +1,44 @@
<PageHeader as |p|>
<p.levelLeft>
<h1 class="title is-3">
<Icon
@glyph="code"
@size="l"
class="has-text-grey-light"
/>
Vault API explorer
</h1>
</p.levelLeft>
</PageHeader>
<Toolbar>
<ToolbarFilters>
<div class="field is-marginless">
<p class="control has-icons-left">
<input
oninput={{queue (action "updateFilter") (action "proxyEvent")}}
value={{@initialFilter}}
disabled={{this.swaggerLoading}}
class="filter input"
placeholder="Filter ops by path"
/>
<Icon
@glyph="search"
@size="l"
class="search-icon has-text-grey-light"
/>
</p>
</div>
<AlertInline
@type="info"
@message="All API paths are prefixed with /v1/"
class="is-marginless input-hint"
/>
</ToolbarFilters>
</Toolbar>
<div class="box is-fullwidth is-sideless">
<NamespaceReminder as |R|>
Requests use the header <code>X-Vault-Namespace: {{R.namespace.path}}</code>. You can also use <code>{{R.namespace.path}}</code> as an API prefix. See <DocLink @path="/api/overview#namespaces">docs</DocLink> for examples.
</NamespaceReminder>
<div id="{{this.elementId}}-swagger"></div>
</div>

View File

@ -0,0 +1,4 @@
<SwaggerUi
@onFilterChange={{action (mut this.filter)}}
@initialFilter={{this.filter}}
/>

View File

@ -0,0 +1,14 @@
/* eslint-env node */
'use strict';
module.exports = function(environment) {
let ENV = {
modulePrefix: 'open-api-explorer',
environment,
APP: {
NAMESPACE_ROOT_URLS: ['sys/health', 'sys/seal-status', 'sys/license/features'],
},
};
return ENV;
};

View File

@ -0,0 +1,25 @@
/* eslint-env node */
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
'use strict';
const EngineAddon = require('ember-engines/lib/engine-addon');
module.exports = EngineAddon.extend({
name: 'open-api-explorer',
included() {
this._super.included && this._super.included.apply(this, arguments);
// we want to lazy load these deps, importing them here will result in them being added to the
// engine-vendor files that will be lazy loaded with the engine
this.import('node_modules/swagger-ui-dist/swagger-ui-bundle.js');
this.import('node_modules/swagger-ui-dist/swagger-ui.css');
},
lazyLoading: {
enabled: true,
},
isDevelopingAddon() {
return true;
},
});

View File

@ -0,0 +1,18 @@
{
"name": "open-api-explorer",
"keywords": [
"ember-addon",
"ember-engine"
],
"dependencies": {
"ember-cli-htmlbars": "*",
"ember-cli-babel": "*",
"ember-auto-import": "*",
"swagger-ui-dist": "*"
},
"ember-addon": {
"paths": [
"../core"
]
}
}

View File

@ -93,7 +93,7 @@
"ember-copy": "^1.0.0",
"ember-data": "~3.4.0",
"ember-data-model-fragments": "^3.3.0",
"ember-engines": "^0.7.0",
"ember-engines": "^0.8.0",
"ember-export-application-global": "^2.0.0",
"ember-fetch": "^6.5.1",
"ember-inflector": "^3.0.0",
@ -130,6 +130,7 @@
"sass-svg-uri": "^1.0.0",
"string.prototype.endswith": "^0.2.0",
"string.prototype.startswith": "^0.2.0",
"swagger-ui-dist": "^3.22.3",
"text-encoder-lite": "1.0.0",
"walk-sync": "^0.3.3",
"xstate": "^3.3.3",
@ -156,8 +157,9 @@
"paths": [
"lib/core",
"lib/css",
"lib/replication",
"lib/kmip"
"lib/kmip",
"lib/open-api-explorer",
"lib/replication"
]
}
}

View File

@ -3279,6 +3279,13 @@ babel-plugin-ember-modules-api-polyfill@^2.8.0:
dependencies:
ember-rfc176-data "^0.3.8"
babel-plugin-ember-modules-api-polyfill@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.9.0.tgz#8503e7b4192aeb336b00265e6235258ff6b754aa"
integrity sha512-c03h50291phJ2gQxo/aIOvFQE2c6glql1A7uagE3XbPXpKVAJOUxtVDjvWG6UAB6BC5ynsJfMWvY0w4TPRKIHQ==
dependencies:
ember-rfc176-data "^0.3.9"
babel-plugin-emotion@^10.0.7:
version "10.0.7"
resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.0.7.tgz#3634ada6dee762140f27db07387feaec8d2cb619"
@ -4246,7 +4253,7 @@ broccoli-babel-transpiler@^7.0.0:
rsvp "^4.8.3"
workerpool "^2.3.1"
broccoli-babel-transpiler@^7.1.2:
broccoli-babel-transpiler@^7.1.2, broccoli-babel-transpiler@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-7.2.0.tgz#5c0d694c4055106abb385e2d3d88936d35b7cb18"
integrity sha512-lkP9dNFfK810CRHHWsNl9rjyYqcXH3qg0kArnA6tV9Owx3nlZm3Eyr0cGo6sMUQCNLH+2oKrRjOdUGSc6Um6Cw==
@ -6594,7 +6601,7 @@ debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.2, debug@^2.1.3, debug@^2.2.
dependencies:
ms "2.0.0"
debug@^3.2.5:
debug@^3.0.1, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@ -7136,10 +7143,10 @@ ember-api-actions@^0.1.8:
"@mike-north/js-lib-semantic-release-config" "^0.0.0-development"
semantic-release "^15.9.12"
ember-asset-loader@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/ember-asset-loader/-/ember-asset-loader-0.5.1.tgz#7a1a1b2a1c6a4185b222a2ead7214b0a6368d619"
integrity sha512-+suNUO9Ncxj6S3YSyZpatD46UYKhynVHOv0Y3VpKe2esB/HWDM5LZYHCQAHoM2ea8pIYvMCLqwmCZurYznbqmA==
ember-asset-loader@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/ember-asset-loader/-/ember-asset-loader-0.6.1.tgz#2eb81221406164d19127eba5b3d10f908df89a17"
integrity sha512-e2zafQJBMLhzl69caTG/+mQMH20uMHYrm7KcmdbmnX0oY2dZ48bhm0Wh1SPLXS/6G2T9NsNMWX6J2pVSnI+xyA==
dependencies:
broccoli-caching-writer "^3.0.3"
broccoli-funnel "^2.0.2"
@ -7368,6 +7375,33 @@ ember-cli-babel@^7.4.3:
ensure-posix-path "^1.0.2"
semver "^5.5.0"
ember-cli-babel@^7.8.0:
version "7.8.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.8.0.tgz#e596500eca0f5a7c9aaee755f803d1542f578acf"
integrity sha512-xUBgJQ81fqd7k/KIiGU+pjpoXhrmmRf9pUrqLenNSU5N+yeNFT5a1+w0b+p1F7oBphfXVwuxApdZxrmAHOdA3Q==
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.3.4"
"@babel/plugin-proposal-decorators" "^7.3.0"
"@babel/plugin-transform-modules-amd" "^7.0.0"
"@babel/plugin-transform-runtime" "^7.2.0"
"@babel/polyfill" "^7.0.0"
"@babel/preset-env" "^7.0.0"
"@babel/runtime" "^7.2.0"
amd-name-resolver "^1.2.1"
babel-plugin-debug-macros "^0.3.0"
babel-plugin-ember-modules-api-polyfill "^2.9.0"
babel-plugin-module-resolver "^3.1.1"
broccoli-babel-transpiler "^7.1.2"
broccoli-debug "^0.6.4"
broccoli-funnel "^2.0.1"
broccoli-source "^1.1.0"
clone "^2.1.2"
ember-cli-babel-plugin-helpers "^1.1.0"
ember-cli-version-checker "^2.1.2"
ensure-posix-path "^1.0.2"
semver "^5.5.0"
ember-cli-broccoli-sane-watcher@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-2.1.1.tgz#1687adada9022de26053fba833dc7dd10f03dd08"
@ -7590,6 +7624,16 @@ ember-cli-preprocess-registry@^3.1.2:
process-relative-require "^1.0.0"
silent-error "^1.0.0"
ember-cli-preprocess-registry@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/ember-cli-preprocess-registry/-/ember-cli-preprocess-registry-3.3.0.tgz#685837a314fbe57224bd54b189f4b9c23907a2de"
integrity sha512-60GYpw7VPeB7TvzTLZTuLTlHdOXvayxjAQ+IxM2T04Xkfyu75O2ItbWlftQW7NZVGkaCsXSRAmn22PG03VpLMA==
dependencies:
broccoli-clean-css "^1.1.0"
broccoli-funnel "^2.0.1"
debug "^3.0.1"
process-relative-require "^1.0.0"
ember-cli-pretender@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/ember-cli-pretender/-/ember-cli-pretender-3.1.1.tgz#289c41683de266fec8bfaf5b7b7f6026aaefc8cf"
@ -7694,7 +7738,7 @@ ember-cli-version-checker@^2.0.0, ember-cli-version-checker@^2.1.0, ember-cli-ve
resolve "^1.3.3"
semver "^5.3.0"
ember-cli-version-checker@^3.0.1, ember-cli-version-checker@^3.1.2:
ember-cli-version-checker@^3.1.2, ember-cli-version-checker@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-3.1.3.tgz#7c9b4f5ff30fdebcd480b1c06c4de43bb51c522c"
integrity sha512-PZNSvpzwWgv68hcXxyjREpj3WWb81A7rtYNQq1lLEgrWIchF8ApKJjWP3NBpHjaatwILkZAV8klair5WFlXAKg==
@ -7904,14 +7948,14 @@ ember-debug-handlers-polyfill@^1.1.1:
resolved "https://registry.yarnpkg.com/ember-debug-handlers-polyfill/-/ember-debug-handlers-polyfill-1.1.1.tgz#e9ae0a720271a834221179202367421b580002ef"
integrity sha512-lO7FBAqJjzbL+IjnWhVfQITypPOJmXdZngZR/Vdn513W4g/Q6Sjicao/mDzeDCb48Y70C4Facwk0LjdIpSZkRg==
ember-engines@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/ember-engines/-/ember-engines-0.7.0.tgz#77e2d9c6bcb3878e8e087ea354df4fc8cc187954"
integrity sha512-XuKg7J2yl+KJVnYDHxr9RYon5/Dcm6zb1YyWL9GZvSWm+SPwoIPa/5awSOOAT2vH76t7JfYVP6Nz/6zaGgNqtw==
ember-engines@^0.8.0:
version "0.8.2"
resolved "https://registry.yarnpkg.com/ember-engines/-/ember-engines-0.8.2.tgz#d1be1929217c5454b37ec2e6b07a0057075447b8"
integrity sha512-Lhwkj02b9/MjOyl3MFToL4Pa1djFtDjQOxI8SY86P81XUBbRDdeZ3pg5tDxU/upEeQ7La7uepoZWTBRj6Lxx0Q==
dependencies:
amd-name-resolver "1.3.1"
babel-plugin-compact-reexports "^1.1.0"
broccoli-babel-transpiler "^7.1.2"
broccoli-babel-transpiler "^7.2.0"
broccoli-concat "^3.7.3"
broccoli-debug "^0.6.5"
broccoli-dependency-funnel "^2.1.2"
@ -7919,13 +7963,12 @@ ember-engines@^0.7.0:
broccoli-funnel "^2.0.2"
broccoli-merge-trees "^3.0.2"
broccoli-test-helper "^2.0.0"
calculate-cache-key-for-tree "^1.1.0"
ember-asset-loader "^0.5.1"
ember-cli-babel "^7.4.3"
ember-cli-preprocess-registry "^3.1.2"
calculate-cache-key-for-tree "^2.0.0"
ember-asset-loader "^0.6.1"
ember-cli-babel "^7.8.0"
ember-cli-preprocess-registry "^3.3.0"
ember-cli-string-utils "^1.1.0"
ember-cli-version-checker "^3.0.1"
ember-maybe-import-regenerator "^0.1.6"
ember-cli-version-checker "^3.1.3"
lodash "^4.17.11"
ember-export-application-global@^2.0.0:
@ -8092,6 +8135,11 @@ ember-rfc176-data@^0.3.8:
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.8.tgz#d46bbef9a0d57c803217b258cfd2e90d8e191848"
integrity sha512-SQup3iG7SDLZNuf7nMMx5BC5truO8AYKRi80gApeQ07NsbuXV4LH75i5eOaxF0i8l9+H1tzv34kGe6rEh0C1NQ==
ember-rfc176-data@^0.3.9:
version "0.3.9"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.9.tgz#44b6e051ead6c044ea87bd551f402e2cf89a7e3d"
integrity sha512-EiTo5YQS0Duy0xp9gCP8ekzv9vxirNi7MnIB4zWs+thtWp/mEKgf5mkiiLU2+oo8C5DuavVHhoPQDmyxh8Io1Q==
ember-router-generator@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee"
@ -17485,6 +17533,11 @@ svgo@0.6.6:
sax "~1.2.1"
whet.extend "~0.9.9"
swagger-ui-dist@^3.22.3:
version "3.22.3"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.22.3.tgz#f2042966f8e0eef0c5730cf90891a89dab7810e1"
integrity sha512-tmjAsqT43pqg5UoiQ2805c+juX0ASSoI/Ash/0c19jjAOFtTfE93ZrzmFd9hjqVgre935CYeXT0uaku42Lu8xg==
symbol-observable@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"