vault/ui/lib/open-api-explorer/addon/components/swagger-ui.js
Matthew Irish 8afe8d02f5
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
2019-07-02 17:41:23 -05:00

106 lines
3.7 KiB
JavaScript

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