From 8afe8d02f5447268f2174234d93f513cc574aa8d Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 2 Jul 2019 17:41:23 -0500 Subject: [PATCH] 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 --- ui/app/app.js | 5 + ui/app/components/console/ui-panel.js | 37 +++- ui/app/lib/console-helpers.js | 23 +-- ui/app/models/role-jwt.js | 2 +- ui/app/router.js | 1 + ui/app/styles/core/message.scss | 3 + ui/app/templates/components/alert-popup.hbs | 4 +- .../templates/components/console/log-help.hbs | 3 +- ui/app/templates/vault/cluster.hbs | 2 +- ui/config/environment.js | 1 + ui/ember-cli-build.js | 1 + .../components/namespace-reminder.hbs | 12 +- ui/{app => lib/core/addon}/utils/parse-url.js | 0 .../addon/components/swagger-ui.js | 105 ++++++++++++ .../addon/controllers/index.js | 6 + ui/lib/open-api-explorer/addon/engine.js | 18 ++ ui/lib/open-api-explorer/addon/resolver.js | 3 + ui/lib/open-api-explorer/addon/routes.js | 5 + .../open-api-explorer/addon/routes/index.js | 20 +++ .../open-api-explorer/addon/styles/addon.css | 159 ++++++++++++++++++ .../addon/templates/application.hbs | 1 + .../addon/templates/components/swagger-ui.hbs | 44 +++++ .../addon/templates/index.hbs | 4 + .../open-api-explorer/config/environment.js | 14 ++ ui/lib/open-api-explorer/index.js | 25 +++ ui/lib/open-api-explorer/package.json | 18 ++ ui/package.json | 8 +- ui/yarn.lock | 89 ++++++++-- 28 files changed, 559 insertions(+), 54 deletions(-) rename ui/{app => lib/core/addon}/utils/parse-url.js (100%) create mode 100644 ui/lib/open-api-explorer/addon/components/swagger-ui.js create mode 100644 ui/lib/open-api-explorer/addon/controllers/index.js create mode 100644 ui/lib/open-api-explorer/addon/engine.js create mode 100644 ui/lib/open-api-explorer/addon/resolver.js create mode 100644 ui/lib/open-api-explorer/addon/routes.js create mode 100644 ui/lib/open-api-explorer/addon/routes/index.js create mode 100644 ui/lib/open-api-explorer/addon/styles/addon.css create mode 100644 ui/lib/open-api-explorer/addon/templates/application.hbs create mode 100644 ui/lib/open-api-explorer/addon/templates/components/swagger-ui.hbs create mode 100644 ui/lib/open-api-explorer/addon/templates/index.hbs create mode 100644 ui/lib/open-api-explorer/config/environment.js create mode 100644 ui/lib/open-api-explorer/index.js create mode 100644 ui/lib/open-api-explorer/package.json diff --git a/ui/app/app.js b/ui/app/app.js index b8675a7383..067a6f8c87 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -14,6 +14,11 @@ App = Application.extend({ podModulePrefix: config.podModulePrefix, Resolver, engines: { + openApiExplorer: { + dependencies: { + services: ['auth', 'flash-messages', 'namespace', 'router', 'version'], + }, + }, replication: { dependencies: { services: [ diff --git a/ui/app/components/console/ui-panel.js b/ui/app/components/console/ui-panel.js index 9d68704bfd..788db5ed1b 100644 --- a/ui/app/components/console/ui-panel.js +++ b/ui/app/components/console/ui-panel.js @@ -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); diff --git a/ui/app/lib/console-helpers.js b/ui/app/lib/console-helpers.js index f68e494627..65ea1b3c56 100644 --- a/ui/app/lib/console-helpers.js +++ b/ui/app/lib/console-helpers.js @@ -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; } diff --git a/ui/app/models/role-jwt.js b/ui/app/models/role-jwt.js index 0083b6e4d9..fda75df709 100644 --- a/ui/app/models/role-jwt.js +++ b/ui/app/models/role-jwt.js @@ -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 = { diff --git a/ui/app/router.js b/ui/app/router.js index 1845a619f1..10796047ba 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -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() { diff --git a/ui/app/styles/core/message.scss b/ui/app/styles/core/message.scss index e3dc315d80..f1a60248ea 100644 --- a/ui/app/styles/core/message.scss +++ b/ui/app/styles/core/message.scss @@ -35,6 +35,9 @@ border: 0; margin-top: $spacing-xxs; } + .message-body.pre { + white-space: pre-wrap; + } p { font-size: $size-8; diff --git a/ui/app/templates/components/alert-popup.hbs b/ui/app/templates/components/alert-popup.hbs index 44a44d4e5d..c6d73c1fa9 100644 --- a/ui/app/templates/components/alert-popup.hbs +++ b/ui/app/templates/components/alert-popup.hbs @@ -18,9 +18,7 @@ {{type.text}} {{#if message}} -

- {{message}} -

+

{{message}}

{{/if}} diff --git a/ui/app/templates/components/console/log-help.hbs b/ui/app/templates/components/console/log-help.hbs index e882420336..6d9aa8419f 100644 --- a/ui/app/templates/components/console/log-help.hbs +++ b/ui/app/templates/components/console/log-help.hbs @@ -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 diff --git a/ui/app/templates/vault/cluster.hbs b/ui/app/templates/vault/cluster.hbs index 7e5d3bb888..397527efd3 100644 --- a/ui/app/templates/vault/cluster.hbs +++ b/ui/app/templates/vault/cluster.hbs @@ -95,7 +95,7 @@ {{#if flash.componentName}} {{component flash.componentName content=flash.content}} {{else}} - + {{/if}} {{/flash-message}} {{/each}} diff --git a/ui/config/environment.js b/ui/config/environment.js index 25ca22414d..0d19d1a85d 100644 --- a/ui/config/environment.js +++ b/ui/config/environment.js @@ -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, diff --git a/ui/ember-cli-build.js b/ui/ember-cli-build.js index faa4bb2989..13f3c441d4 100644 --- a/ui/ember-cli-build.js +++ b/ui/ember-cli-build.js @@ -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. // diff --git a/ui/lib/core/addon/templates/components/namespace-reminder.hbs b/ui/lib/core/addon/templates/components/namespace-reminder.hbs index 0263044d3e..5db43b5fc3 100644 --- a/ui/lib/core/addon/templates/components/namespace-reminder.hbs +++ b/ui/lib/core/addon/templates/components/namespace-reminder.hbs @@ -1,5 +1,11 @@ {{#if showMessage}} -

- This {{noun}} will be {{modeVerb}} in the {{namespace.path}}/namespace. -

+ {{#if (has-block)}} +

+ {{yield (hash namespace=namespace)}} +

+ {{else}} +

+ This {{noun}} will be {{modeVerb}} in the {{namespace.path}}/namespace. +

+ {{/if}} {{/if}} diff --git a/ui/app/utils/parse-url.js b/ui/lib/core/addon/utils/parse-url.js similarity index 100% rename from ui/app/utils/parse-url.js rename to ui/lib/core/addon/utils/parse-url.js diff --git a/ui/lib/open-api-explorer/addon/components/swagger-ui.js b/ui/lib/open-api-explorer/addon/components/swagger-ui.js new file mode 100644 index 0000000000..e5ab142488 --- /dev/null +++ b/ui/lib/open-api-explorer/addon/components/swagger-ui.js @@ -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); + }, + }, +}); diff --git a/ui/lib/open-api-explorer/addon/controllers/index.js b/ui/lib/open-api-explorer/addon/controllers/index.js new file mode 100644 index 0000000000..704157fa82 --- /dev/null +++ b/ui/lib/open-api-explorer/addon/controllers/index.js @@ -0,0 +1,6 @@ +import Controller from '@ember/controller'; + +export default Controller.extend({ + queryParams: ['filter'], + filter: '', +}); diff --git a/ui/lib/open-api-explorer/addon/engine.js b/ui/lib/open-api-explorer/addon/engine.js new file mode 100644 index 0000000000..9555408ea4 --- /dev/null +++ b/ui/lib/open-api-explorer/addon/engine.js @@ -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; diff --git a/ui/lib/open-api-explorer/addon/resolver.js b/ui/lib/open-api-explorer/addon/resolver.js new file mode 100644 index 0000000000..2fb563d6c0 --- /dev/null +++ b/ui/lib/open-api-explorer/addon/resolver.js @@ -0,0 +1,3 @@ +import Resolver from 'ember-resolver'; + +export default Resolver; diff --git a/ui/lib/open-api-explorer/addon/routes.js b/ui/lib/open-api-explorer/addon/routes.js new file mode 100644 index 0000000000..8d86db5431 --- /dev/null +++ b/ui/lib/open-api-explorer/addon/routes.js @@ -0,0 +1,5 @@ +import buildRoutes from 'ember-engines/routes'; + +export default buildRoutes(function() { + // Define your engine's route map here +}); diff --git a/ui/lib/open-api-explorer/addon/routes/index.js b/ui/lib/open-api-explorer/addon/routes/index.js new file mode 100644 index 0000000000..fef65b0eae --- /dev/null +++ b/ui/lib/open-api-explorer/addon/routes/index.js @@ -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, + }); + }, +}); diff --git a/ui/lib/open-api-explorer/addon/styles/addon.css b/ui/lib/open-api-explorer/addon/styles/addon.css new file mode 100644 index 0000000000..cebb78cbbc --- /dev/null +++ b/ui/lib/open-api-explorer/addon/styles/addon.css @@ -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." +} diff --git a/ui/lib/open-api-explorer/addon/templates/application.hbs b/ui/lib/open-api-explorer/addon/templates/application.hbs new file mode 100644 index 0000000000..c24cd68950 --- /dev/null +++ b/ui/lib/open-api-explorer/addon/templates/application.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/ui/lib/open-api-explorer/addon/templates/components/swagger-ui.hbs b/ui/lib/open-api-explorer/addon/templates/components/swagger-ui.hbs new file mode 100644 index 0000000000..8cf1bee558 --- /dev/null +++ b/ui/lib/open-api-explorer/addon/templates/components/swagger-ui.hbs @@ -0,0 +1,44 @@ + + +

+ + Vault API explorer +

+
+
+ + +
+

+ + +

+
+ +
+
+ +
+ + Requests use the header X-Vault-Namespace: {{R.namespace.path}}. You can also use {{R.namespace.path}} as an API prefix. See docs for examples. + +
+
diff --git a/ui/lib/open-api-explorer/addon/templates/index.hbs b/ui/lib/open-api-explorer/addon/templates/index.hbs new file mode 100644 index 0000000000..df01a2b2dc --- /dev/null +++ b/ui/lib/open-api-explorer/addon/templates/index.hbs @@ -0,0 +1,4 @@ + diff --git a/ui/lib/open-api-explorer/config/environment.js b/ui/lib/open-api-explorer/config/environment.js new file mode 100644 index 0000000000..8df04ffac1 --- /dev/null +++ b/ui/lib/open-api-explorer/config/environment.js @@ -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; +}; diff --git a/ui/lib/open-api-explorer/index.js b/ui/lib/open-api-explorer/index.js new file mode 100644 index 0000000000..01b80dae05 --- /dev/null +++ b/ui/lib/open-api-explorer/index.js @@ -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; + }, +}); diff --git a/ui/lib/open-api-explorer/package.json b/ui/lib/open-api-explorer/package.json new file mode 100644 index 0000000000..f08b841fe3 --- /dev/null +++ b/ui/lib/open-api-explorer/package.json @@ -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" + ] + } +} diff --git a/ui/package.json b/ui/package.json index 2ddefe70fb..6f85f2209f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -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" ] } } diff --git a/ui/yarn.lock b/ui/yarn.lock index 5da92fdd5e..1b1af87e24 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -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"