diff --git a/builtin/logical/ssh/path_roles.go b/builtin/logical/ssh/path_roles.go index ce3fb24756..450d768447 100644 --- a/builtin/logical/ssh/path_roles.go +++ b/builtin/logical/ssh/path_roles.go @@ -218,7 +218,7 @@ func pathRoles(b *backend) *framework.Path { The maximum allowed lease duration `, DisplayAttrs: &framework.DisplayAttributes{ - Value: "Max TTL", + Name: "Max TTL", }, }, "allowed_critical_options": &framework.FieldSchema{ diff --git a/ui/app/adapters/kmip/base.js b/ui/app/adapters/kmip/base.js new file mode 100644 index 0000000000..174249e534 --- /dev/null +++ b/ui/app/adapters/kmip/base.js @@ -0,0 +1,59 @@ +import ApplicationAdapter from '../application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; + +export default ApplicationAdapter.extend({ + namespace: 'v1', + pathForType(type) { + return type.replace('kmip/', ''); + }, + + _url(modelType, meta = {}, id) { + let { backend, scope, role } = meta; + let type = this.pathForType(modelType); + let base; + switch (type) { + case 'scope': + base = `${encodePath(backend)}/scope`; + break; + case 'role': + base = `${encodePath(backend)}/scope/${encodePath(scope)}/role`; + break; + case 'credential': + base = `${encodePath(backend)}/scope/${encodePath(scope)}/role/${encodePath(role)}/credential`; + break; + } + if (id && type === 'credential') { + return `/v1/${base}/lookup?serial_number=${encodePath(id)}`; + } + + if (id) { + return `/v1/${base}/${encodePath(id)}`; + } + return `/v1/${base}`; + }, + + urlForQuery(query, modelType) { + let base = this._url(modelType, query); + return base + '?list=true'; + }, + + query(store, type, query) { + return this.ajax(this.urlForQuery(query, type.modelName), 'GET'); + }, + + queryRecord(store, type, query) { + let id = query.id; + delete query.id; + return this.ajax(this._url(type.modelName, query, id), 'GET').then(resp => { + resp.id = id; + resp = { ...resp, ...query }; + return resp; + }); + }, + buildURL(modelName, id, snapshot, requestType, query) { + if (requestType === 'createRecord') { + return this._super(...arguments); + } + return this._super(`${modelName}`, id, snapshot, requestType, query); + }, +}); diff --git a/ui/app/adapters/kmip/config.js b/ui/app/adapters/kmip/config.js new file mode 100644 index 0000000000..32f27803f8 --- /dev/null +++ b/ui/app/adapters/kmip/config.js @@ -0,0 +1,19 @@ +import BaseAdapter from './base'; + +export default BaseAdapter.extend({ + _url(id, modelName, snapshot) { + let name = this.pathForType(modelName); + // id here will be the mount path, + // modelName will be config so we want to transpose the first two call args + return this.buildURL(id, name, snapshot); + }, + urlForFindRecord() { + return this._url(...arguments); + }, + urlForCreateRecord(modelName, snapshot) { + return this._url(snapshot.id, modelName, snapshot); + }, + urlForUpdateRecord() { + return this._url(...arguments); + }, +}); diff --git a/ui/app/adapters/kmip/credential.js b/ui/app/adapters/kmip/credential.js new file mode 100644 index 0000000000..b485d1b26c --- /dev/null +++ b/ui/app/adapters/kmip/credential.js @@ -0,0 +1,16 @@ +import BaseAdapter from './base'; + +export default BaseAdapter.extend({ + createRecord(store, type, snapshot) { + let url = this._url(type.modelName, { + backend: snapshot.record.backend, + scope: snapshot.record.scope, + role: snapshot.record.role, + }); + url = `${url}/generate`; + return this.ajax(url, 'POST', { data: snapshot.serialize() }).then(model => { + model.data.id = model.data.serial_number; + return model; + }); + }, +}); diff --git a/ui/app/adapters/kmip/role.js b/ui/app/adapters/kmip/role.js new file mode 100644 index 0000000000..0777e85518 --- /dev/null +++ b/ui/app/adapters/kmip/role.js @@ -0,0 +1,25 @@ +import BaseAdapter from './base'; + +export default BaseAdapter.extend({ + createRecord(store, type, snapshot) { + let name = snapshot.id || snapshot.attr('name'); + let url = this._url( + type.modelName, + { + backend: snapshot.record.backend, + scope: snapshot.record.scope, + }, + name + ); + return this.ajax(url, 'POST', { data: snapshot.serialize() }).then(() => { + return { + id: name, + name, + }; + }); + }, + + updateRecord() { + return this.createRecord(...arguments); + }, +}); diff --git a/ui/app/adapters/kmip/scope.js b/ui/app/adapters/kmip/scope.js new file mode 100644 index 0000000000..db3f27374b --- /dev/null +++ b/ui/app/adapters/kmip/scope.js @@ -0,0 +1,15 @@ +import BaseAdapter from './base'; + +export default BaseAdapter.extend({ + createRecord(store, type, snapshot) { + let name = snapshot.attr('name'); + return this.ajax(this._url(type.modelName, { backend: snapshot.record.backend }, name), 'POST').then( + () => { + return { + id: name, + name, + }; + } + ); + }, +}); diff --git a/ui/app/app.js b/ui/app/app.js index a215b634c9..b8675a7383 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -28,6 +28,24 @@ App = Application.extend({ ], }, }, + kmip: { + dependencies: { + services: [ + 'auth', + 'flash-messages', + 'namespace', + 'path-help', + 'router', + 'store', + 'version', + 'wizard', + 'secret-mount-path', + ], + externalRoutes: { + secrets: 'vault.cluster.secrets.backends', + }, + }, + }, }, }); diff --git a/ui/app/components/empty-action-namespaces.js b/ui/app/components/empty-action-namespaces.js deleted file mode 100644 index 96167992d7..0000000000 --- a/ui/app/components/empty-action-namespaces.js +++ /dev/null @@ -1,2 +0,0 @@ -import OuterHTML from './outer-html'; -export default OuterHTML.extend(); diff --git a/ui/app/components/field-group-show.js b/ui/app/components/field-group-show.js deleted file mode 100644 index f045077d06..0000000000 --- a/ui/app/components/field-group-show.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @module FieldGroupShow - * FieldGroupShow components loop through a Model's fieldGroups - * to display their attributes - * - * @example - * ```js - * - * ``` - * - * @param model {Object} - the model - * @param [showAllFields=false] {boolean} - whether to show fields with empty values - */ -import Component from '@ember/component'; -import layout from '../templates/components/field-group-show'; - -export default Component.extend({ - layout, - model: null, - showAllFields: false, -}); diff --git a/ui/app/components/http-requests-bar-chart.js b/ui/app/components/http-requests-bar-chart.js index a9ad00343d..35328ac98b 100644 --- a/ui/app/components/http-requests-bar-chart.js +++ b/ui/app/components/http-requests-bar-chart.js @@ -5,7 +5,7 @@ import d3Axis from 'd3-axis'; import d3TimeFormat from 'd3-time-format'; import { assign } from '@ember/polyfills'; import { computed } from '@ember/object'; -import { run, debounce } from '@ember/runloop'; +import { run } from '@ember/runloop'; import { task, waitForEvent } from 'ember-concurrency'; /** @@ -31,6 +31,8 @@ const HEIGHT = 240; export default Component.extend({ classNames: ['http-requests-bar-chart-container'], counters: null, + + /* eslint-disable ember/avoid-leaking-state-in-ember-objects */ margin: { top: 24, right: 16, bottom: 24, left: 16 }, padding: 0.04, width: 0, diff --git a/ui/app/components/mount-backend-form.js b/ui/app/components/mount-backend-form.js index 83f7194478..683e5f29d2 100644 --- a/ui/app/components/mount-backend-form.js +++ b/ui/app/components/mount-backend-form.js @@ -3,7 +3,7 @@ import { computed } from '@ember/object'; import Component from '@ember/component'; import { task } from 'ember-concurrency'; import { methods } from 'vault/helpers/mountable-auth-methods'; -import { engines } from 'vault/helpers/mountable-secret-engines'; +import { engines, KMIP } from 'vault/helpers/mountable-secret-engines'; const METHODS = methods(); const ENGINES = engines(); @@ -12,6 +12,7 @@ export default Component.extend({ store: service(), wizard: service(), flashMessages: service(), + version: service(), /* * @param Function @@ -51,7 +52,15 @@ export default Component.extend({ }, mountTypes: computed('mountType', function() { - return this.mountType === 'secret' ? ENGINES : METHODS; + return this.mountType === 'secret' ? this.engines : METHODS; + }), + + engines: computed('version.features[]', function() { + if (this.version.hasFeature('KMIP')) { + return ENGINES.concat([KMIP]); + } else { + return ENGINES; + } }), willDestroy() { diff --git a/ui/app/controllers/vault/cluster/access/identity/aliases/index.js b/ui/app/controllers/vault/cluster/access/identity/aliases/index.js index bac858c14d..7c7ef6ddda 100644 --- a/ui/app/controllers/vault/cluster/access/identity/aliases/index.js +++ b/ui/app/controllers/vault/cluster/access/identity/aliases/index.js @@ -1,5 +1,5 @@ import Controller from '@ember/controller'; -import ListController from 'vault/mixins/list-controller'; +import ListController from 'core/mixins/list-controller'; export default Controller.extend(ListController, { actions: { diff --git a/ui/app/controllers/vault/cluster/access/identity/index.js b/ui/app/controllers/vault/cluster/access/identity/index.js index 088cdcc7de..0ab1d28276 100644 --- a/ui/app/controllers/vault/cluster/access/identity/index.js +++ b/ui/app/controllers/vault/cluster/access/identity/index.js @@ -1,6 +1,6 @@ import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; -import ListController from 'vault/mixins/list-controller'; +import ListController from 'core/mixins/list-controller'; export default Controller.extend(ListController, { flashMessages: service(), diff --git a/ui/app/controllers/vault/cluster/access/leases/list.js b/ui/app/controllers/vault/cluster/access/leases/list.js index 985502d25a..7c9ae9b06f 100644 --- a/ui/app/controllers/vault/cluster/access/leases/list.js +++ b/ui/app/controllers/vault/cluster/access/leases/list.js @@ -2,7 +2,7 @@ import { inject as service } from '@ember/service'; import { computed } from '@ember/object'; import Controller, { inject as controller } from '@ember/controller'; import utils from 'vault/lib/key-utils'; -import ListController from 'vault/mixins/list-controller'; +import ListController from 'core/mixins/list-controller'; export default Controller.extend(ListController, { flashMessages: service(), diff --git a/ui/app/controllers/vault/cluster/secrets/backend/list.js b/ui/app/controllers/vault/cluster/secrets/backend/list.js index fdd006fb57..6aa11e2259 100644 --- a/ui/app/controllers/vault/cluster/secrets/backend/list.js +++ b/ui/app/controllers/vault/cluster/secrets/backend/list.js @@ -4,7 +4,7 @@ import Controller from '@ember/controller'; import utils from 'vault/lib/key-utils'; import BackendCrumbMixin from 'vault/mixins/backend-crumb'; import WithNavToNearestAncestor from 'vault/mixins/with-nav-to-nearest-ancestor'; -import ListController from 'vault/mixins/list-controller'; +import ListController from 'core/mixins/list-controller'; export default Controller.extend(ListController, BackendCrumbMixin, WithNavToNearestAncestor, { flashMessages: service(), diff --git a/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js b/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js index 432d5f32ff..c9358cec84 100644 --- a/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js +++ b/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js @@ -10,7 +10,11 @@ export default Controller.extend({ onMountSuccess: function(type, path) { let transition; if (SUPPORTED_BACKENDS.includes(type)) { - transition = this.transitionToRoute('vault.cluster.secrets.backend.index', path); + if (type === 'kmip') { + transition = this.transitionToRoute('vault.cluster.secrets.backend.kmip.scopes', path); + } else { + transition = this.transitionToRoute('vault.cluster.secrets.backend.index', path); + } } else { transition = this.transitionToRoute('vault.cluster.secrets.backends'); } diff --git a/ui/app/helpers/mountable-secret-engines.js b/ui/app/helpers/mountable-secret-engines.js index 7256b3dce2..19af064a5a 100644 --- a/ui/app/helpers/mountable-secret-engines.js +++ b/ui/app/helpers/mountable-secret-engines.js @@ -1,5 +1,12 @@ import { helper as buildHelper } from '@ember/component/helper'; +export const KMIP = { + displayName: 'KMIP', + value: 'kmip', + type: 'kmip', + category: 'generic', +}; + const MOUNTABLE_SECRET_ENGINES = [ { displayName: 'Active Directory', diff --git a/ui/app/helpers/supported-secret-backends.js b/ui/app/helpers/supported-secret-backends.js index 748ab5abb9..9c86859b72 100644 --- a/ui/app/helpers/supported-secret-backends.js +++ b/ui/app/helpers/supported-secret-backends.js @@ -1,6 +1,6 @@ import { helper as buildHelper } from '@ember/component/helper'; -const SUPPORTED_SECRET_BACKENDS = ['aws', 'cubbyhole', 'generic', 'kv', 'pki', 'ssh', 'transit']; +const SUPPORTED_SECRET_BACKENDS = ['aws', 'cubbyhole', 'generic', 'kv', 'pki', 'ssh', 'transit', 'kmip']; export function supportedSecretBackends() { return SUPPORTED_SECRET_BACKENDS; diff --git a/ui/app/mixins/list-route.js b/ui/app/mixins/list-route.js deleted file mode 100644 index eb1e713018..0000000000 --- a/ui/app/mixins/list-route.js +++ /dev/null @@ -1,12 +0,0 @@ -import Mixin from '@ember/object/mixin'; - -export default Mixin.create({ - queryParams: { - page: { - refreshModel: true, - }, - pageFilter: { - refreshModel: true, - }, - }, -}); diff --git a/ui/app/models/kmip/config.js b/ui/app/models/kmip/config.js new file mode 100644 index 0000000000..fffb3a08f2 --- /dev/null +++ b/ui/app/models/kmip/config.js @@ -0,0 +1,18 @@ +import DS from 'ember-data'; +import { computed } from '@ember/object'; +import { combineFieldGroups } from 'vault/utils/openapi-to-attrs'; +import fieldToAttrs from 'vault/utils/field-to-attrs'; + +export default DS.Model.extend({ + useOpenAPI: true, + getHelpUrl(path) { + return `/v1/${path}/config?help=1`; + }, + + fieldGroups: computed(function() { + let groups = [{ default: ['listenAddrs', 'connectionTimeout'] }]; + + groups = combineFieldGroups(groups, this.newFields, []); + return fieldToAttrs(this, groups); + }), +}); diff --git a/ui/app/models/kmip/credential.js b/ui/app/models/kmip/credential.js new file mode 100644 index 0000000000..bf24c97418 --- /dev/null +++ b/ui/app/models/kmip/credential.js @@ -0,0 +1,30 @@ +import DS from 'ember-data'; +import fieldToAttrs from 'vault/utils/field-to-attrs'; +import { computed } from '@ember/object'; +const { attr } = DS; + +export default DS.Model.extend({ + backend: attr({ readOnly: true }), + scope: attr({ readOnly: true }), + role: attr({ readOnly: true }), + certificate: attr('string', { readOnly: true }), + caChain: attr({ readOnly: true }), + privateKey: attr('string', { + readOnly: true, + sensitive: true, + }), + format: attr('string', { + possibleValues: ['pem', 'der', 'pem_bundle'], + defaultValue: 'pem', + label: 'Certificate format', + }), + fieldGroups: computed(function() { + const groups = [ + { + default: ['format'], + }, + ]; + + return fieldToAttrs(this, groups); + }), +}); diff --git a/ui/app/models/kmip/role.js b/ui/app/models/kmip/role.js new file mode 100644 index 0000000000..a553125e5d --- /dev/null +++ b/ui/app/models/kmip/role.js @@ -0,0 +1,28 @@ +import DS from 'ember-data'; +import fieldToAttrs from 'vault/utils/field-to-attrs'; +import { computed } from '@ember/object'; + +const { attr } = DS; +export default DS.Model.extend({ + useOpenAPI: true, + backend: attr({ readOnly: true }), + scope: attr({ readOnly: true }), + getHelpUrl(path) { + return `/v1/${path}/scope/example/role/example?help=1`; + }, + + name: attr('string'), + allowedOperations: attr(), + fieldGroups: computed(function() { + let fields = this.newFields.without('role'); + + const groups = [ + { + default: ['name'], + }, + { 'Allowed Operations': fields }, + ]; + + return fieldToAttrs(this, groups); + }), +}); diff --git a/ui/app/models/kmip/scope.js b/ui/app/models/kmip/scope.js new file mode 100644 index 0000000000..107eb8d5c7 --- /dev/null +++ b/ui/app/models/kmip/scope.js @@ -0,0 +1,12 @@ +import { computed } from '@ember/object'; +import DS from 'ember-data'; + +const { attr } = DS; +import { expandAttributeMeta } from 'vault/utils/field-to-attrs'; + +export default DS.Model.extend({ + name: attr('string'), + attrs: computed(function() { + return expandAttributeMeta(this, ['name']); + }), +}); diff --git a/ui/app/router.js b/ui/app/router.js index dc97d69051..1845a619f1 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -83,6 +83,7 @@ Router.map(function() { this.route('secrets', function() { this.route('backends', { path: '/' }); this.route('backend', { path: '/:backend' }, function() { + this.mount('kmip'); this.route('index', { path: '/' }); this.route('configuration'); // because globs / params can't be empty, @@ -124,6 +125,7 @@ Router.map(function() { if (config.addRootMounts) { config.addRootMounts.call(this); } + this.route('not-found', { path: '/*path' }); }); this.route('not-found', { path: '/*path' }); diff --git a/ui/app/routes/vault/cluster/access/identity/aliases/index.js b/ui/app/routes/vault/cluster/access/identity/aliases/index.js index abee732e5b..b661ef1750 100644 --- a/ui/app/routes/vault/cluster/access/identity/aliases/index.js +++ b/ui/app/routes/vault/cluster/access/identity/aliases/index.js @@ -1,5 +1,5 @@ import Route from '@ember/routing/route'; -import ListRoute from 'vault/mixins/list-route'; +import ListRoute from 'core/mixins/list-route'; export default Route.extend(ListRoute, { model(params) { diff --git a/ui/app/routes/vault/cluster/access/identity/index.js b/ui/app/routes/vault/cluster/access/identity/index.js index a9a983ecdd..cdd6faabd1 100644 --- a/ui/app/routes/vault/cluster/access/identity/index.js +++ b/ui/app/routes/vault/cluster/access/identity/index.js @@ -1,5 +1,5 @@ import Route from '@ember/routing/route'; -import ListRoute from 'vault/mixins/list-route'; +import ListRoute from 'core/mixins/list-route'; export default Route.extend(ListRoute, { model(params) { diff --git a/ui/app/routes/vault/cluster/policies/index.js b/ui/app/routes/vault/cluster/policies/index.js index a1aac6911f..93214b9875 100644 --- a/ui/app/routes/vault/cluster/policies/index.js +++ b/ui/app/routes/vault/cluster/policies/index.js @@ -1,7 +1,7 @@ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import ClusterRoute from 'vault/mixins/cluster-route'; -import ListRoute from 'vault/mixins/list-route'; +import ListRoute from 'core/mixins/list-route'; export default Route.extend(ClusterRoute, ListRoute, { version: service(), diff --git a/ui/app/routes/vault/cluster/secrets/backend.js b/ui/app/routes/vault/cluster/secrets/backend.js index 9003842e01..1dafeda49a 100644 --- a/ui/app/routes/vault/cluster/secrets/backend.js +++ b/ui/app/routes/vault/cluster/secrets/backend.js @@ -2,9 +2,11 @@ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; export default Route.extend({ flashMessages: service(), + secretMountPath: service(), oldModel: null, model(params) { let { backend } = params; + this.secretMountPath.update(backend); return this.store .query('secret-engine', { path: backend, diff --git a/ui/app/services/path-help.js b/ui/app/services/path-help.js index 4f584005d1..1b5acfc7ec 100644 --- a/ui/app/services/path-help.js +++ b/ui/app/services/path-help.js @@ -197,7 +197,7 @@ export default Service.extend({ //we need list and create paths to set the correct urls for actions const { list, create } = paths; return generatedItemAdapter.extend({ - urlForItem(method, id, type) { + urlForItem(method, id) { let listPath = list.find(pathInfo => pathInfo.path.includes(itemType)); let { tag, path } = listPath; let url = `${this.buildURL()}/${tag}/${backend}${path}/`; @@ -211,7 +211,7 @@ export default Service.extend({ return this.urlForItem(modelName, id, snapshot); }, - urlForUpdateRecord(id, modelName, snapshot) { + urlForUpdateRecord(id) { let { tag, path } = create[0]; path = path.slice(0, path.indexOf('{') - 1); return `${this.buildURL()}/${tag}/${backend}${path}/${id}`; @@ -224,7 +224,7 @@ export default Service.extend({ return `${this.buildURL()}/${tag}/${backend}${path}/${id}`; }, - urlForDeleteRecord(id, modelName, snapshot) { + urlForDeleteRecord(id) { let { tag, path } = paths.delete[0]; path = path.slice(0, path.indexOf('{') - 1); return `${this.buildURL()}/${tag}/${backend}${path}/${id}`; diff --git a/ui/app/services/secret-mount-path.js b/ui/app/services/secret-mount-path.js new file mode 100644 index 0000000000..4d6caa8309 --- /dev/null +++ b/ui/app/services/secret-mount-path.js @@ -0,0 +1,14 @@ +import Service from '@ember/service'; + +// this service tracks the path of the currently viewed secret mount +// so that we can access that inside of engines where parent route params +// are not accessible +export default Service.extend({ + currentPath: null, + update(path) { + this.set('currentPath', path); + }, + get() { + return this.currentPath; + }, +}); diff --git a/ui/app/templates/components/empty-action-namespaces.hbs b/ui/app/templates/components/empty-action-namespaces.hbs deleted file mode 100644 index 99298fb606..0000000000 --- a/ui/app/templates/components/empty-action-namespaces.hbs +++ /dev/null @@ -1,7 +0,0 @@ -{{#link-to "vault.cluster.access.namespaces.create"}} - Create Namespace -{{/link-to}} - - - Learn more - diff --git a/ui/app/templates/components/list-view.hbs b/ui/app/templates/components/list-view.hbs deleted file mode 100644 index da3bf430b2..0000000000 --- a/ui/app/templates/components/list-view.hbs +++ /dev/null @@ -1,13 +0,0 @@ -{{#if items.length}} -
- {{#each items as |item|}} - {{yield (hash deleteItem=deleteItem saveItem=saveItem item=item)}} - {{/each}} -
-{{else}} - -{{/if}} diff --git a/ui/app/templates/vault/cluster/access/namespaces/index.hbs b/ui/app/templates/vault/cluster/access/namespaces/index.hbs index 518971bfc9..990c36761b 100644 --- a/ui/app/templates/vault/cluster/access/namespaces/index.hbs +++ b/ui/app/templates/vault/cluster/access/namespaces/index.hbs @@ -18,42 +18,54 @@ - - - - {{list.item.id}} - - - {{#with (concat currentNamespace (if currentNamespace "/") list.item.id) as |targetNamespace|}} - {{#if (contains targetNamespace accessibleNamespaces)}} -
  • - {{#link-to "vault.cluster.secrets" (query-params namespace=targetNamespace) class="is-block"}} - Switch to Namespace - {{/link-to}} -
  • - {{/if}} - {{/with}} -
  • - - Delete - -
  • -
    -
    + + {{#if list.empty}} + + {{#link-to "vault.cluster.access.namespaces.create"}} + Create Namespace + {{/link-to}} + + + Learn more + + + {{else}} + + + {{list.item.id}} + + + {{#with (concat currentNamespace (if currentNamespace "/") list.item.id) as |targetNamespace|}} + {{#if (contains targetNamespace accessibleNamespaces)}} +
  • + {{#link-to "vault.cluster.secrets" (query-params namespace=targetNamespace) class="is-block"}} + Switch to Namespace + {{/link-to}} +
  • + {{/if}} + {{/with}} +
  • + + Delete + +
  • +
    +
    + {{/if}}
    {{else}} diff --git a/ui/app/templates/vault/cluster/secrets/backends.hbs b/ui/app/templates/vault/cluster/secrets/backends.hbs index 7434df0016..40ed9ffddf 100644 --- a/ui/app/templates/vault/cluster/secrets/backends.hbs +++ b/ui/app/templates/vault/cluster/secrets/backends.hbs @@ -19,7 +19,10 @@ {{#each supportedBackends as |backend|}} {{#linked-block - "vault.cluster.secrets.backend.list-root" + (if (eq backend.engineType "kmip") + "vault.cluster.secrets.backend.kmip.scopes" + "vault.cluster.secrets.backend.list-root" + ) backend.id class="list-item-row" data-test-secret-backend-row=backend.id @@ -30,7 +33,7 @@ @@ -41,7 +44,12 @@ - {{#link-to "vault.cluster.secrets.backend.list-root" backend.id + {{#link-to + (if (eq backend.engineType "kmip") + "vault.cluster.secrets.backend.kmip.scopes" + "vault.cluster.secrets.backend.list-root" + ) + backend.id class="has-text-black has-text-weight-semibold" data-test-secret-path=true }} diff --git a/ui/app/utils/openapi-to-attrs.js b/ui/app/utils/openapi-to-attrs.js index 6b25de0b03..fd516304ed 100644 --- a/ui/app/utils/openapi-to-attrs.js +++ b/ui/app/utils/openapi-to-attrs.js @@ -23,7 +23,6 @@ export const expandOpenApiProps = function(props) { } let attrDefn = { editType, - type: type, helpText: description, sensitive: sensitive, label: name || label, @@ -33,6 +32,11 @@ export const expandOpenApiProps = function(props) { readOnly: isId, defaultValue: value || null, }; + // ttls write as a string and read as a number + // so setting type on them runs the wrong transform + if (editType !== 'ttl') { + attrDefn.type = type; + } // loop to remove empty vals for (let attrProp in attrDefn) { if (attrDefn[attrProp] == null) { diff --git a/ui/app/components/edit-form.js b/ui/lib/core/addon/components/edit-form.js similarity index 77% rename from ui/app/components/edit-form.js rename to ui/lib/core/addon/components/edit-form.js index 67521649b6..a1c3e3c8ee 100644 --- a/ui/app/components/edit-form.js +++ b/ui/lib/core/addon/components/edit-form.js @@ -2,8 +2,11 @@ import { inject as service } from '@ember/service'; import Component from '@ember/component'; import { task } from 'ember-concurrency'; import DS from 'ember-data'; +import layout from '../templates/components/edit-form'; +import { next } from '@ember/runloop'; export default Component.extend({ + layout, flashMessages: service(), // public API @@ -22,6 +25,10 @@ export default Component.extend({ */ onSave: () => {}, + // onSave may need values updated in render in a helper - if this + // is the case, set this value to true + callOnSaveAfterRender: false, + save: task(function*(model, options = { method: 'save' }) { let { method } = options; let messageKey = method === 'save' ? 'successMessage' : 'deleteSuccessMessage'; @@ -36,6 +43,12 @@ export default Component.extend({ return; } this.get('flashMessages').success(this.get(messageKey)); + if (this.callOnSaveAfterRender) { + next(() => { + this.get('onSave')({ saveType: method, model }); + }); + return; + } yield this.get('onSave')({ saveType: method, model }); }) .drop() diff --git a/ui/lib/core/addon/components/field-group-show.js b/ui/lib/core/addon/components/field-group-show.js new file mode 100644 index 0000000000..1d0a71bf03 --- /dev/null +++ b/ui/lib/core/addon/components/field-group-show.js @@ -0,0 +1,20 @@ +/** + * @module FieldGroupShow + * FieldGroupShow components are used to... + * + * @example + * ```js + * + * ``` + * + * @param param1 {String} - param1 is... + * @param [param2=value] {String} - param2 is... //brackets mean it is optional and = sets the default value + */ +import Component from '@ember/component'; +import layout from '../templates/components/field-group-show'; + +export default Component.extend({ + layout, + model: null, + showAllFields: false, +}); diff --git a/ui/app/components/form-field-groups.js b/ui/lib/core/addon/components/form-field-groups.js similarity index 93% rename from ui/app/components/form-field-groups.js rename to ui/lib/core/addon/components/form-field-groups.js index a2a5983f56..3ba85b0a61 100644 --- a/ui/app/components/form-field-groups.js +++ b/ui/lib/core/addon/components/form-field-groups.js @@ -1,5 +1,6 @@ import Component from '@ember/component'; import { computed } from '@ember/object'; +import layout from '../templates/components/form-field-groups'; /** * @module FormFieldGroups @@ -27,6 +28,7 @@ import { computed } from '@ember/object'; */ export default Component.extend({ + layout, tagName: '', renderGroup: computed(function() { diff --git a/ui/app/components/form-field.js b/ui/lib/core/addon/components/form-field.js similarity index 97% rename from ui/app/components/form-field.js rename to ui/lib/core/addon/components/form-field.js index 1d1ac2e32a..3d69ec4a6d 100644 --- a/ui/app/components/form-field.js +++ b/ui/lib/core/addon/components/form-field.js @@ -3,6 +3,7 @@ import { computed } from '@ember/object'; import { capitalize } from 'vault/helpers/capitalize'; import { humanize } from 'vault/helpers/humanize'; import { dasherize } from 'vault/helpers/dasherize'; +import layout from '../templates/components/form-field'; /** * @module FormField @@ -22,6 +23,7 @@ import { dasherize } from 'vault/helpers/dasherize'; */ export default Component.extend({ + layout, 'data-test-field': true, classNames: ['field'], diff --git a/ui/app/components/info-tooltip.js b/ui/lib/core/addon/components/info-tooltip.js similarity index 71% rename from ui/app/components/info-tooltip.js rename to ui/lib/core/addon/components/info-tooltip.js index f39f4bc58b..a03b259410 100644 --- a/ui/app/components/info-tooltip.js +++ b/ui/lib/core/addon/components/info-tooltip.js @@ -1,6 +1,8 @@ import Component from '@ember/component'; +import layout from '../templates/components/info-tooltip'; export default Component.extend({ + layout, 'data-test-component': 'info-tooltip', tagName: 'span', classNames: ['is-inline-block'], diff --git a/ui/app/components/linked-block.js b/ui/lib/core/addon/components/linked-block.js similarity index 85% rename from ui/app/components/linked-block.js rename to ui/lib/core/addon/components/linked-block.js index 1cd3e897f2..a6d3b57309 100644 --- a/ui/app/components/linked-block.js +++ b/ui/lib/core/addon/components/linked-block.js @@ -11,6 +11,7 @@ let LinkedBlockComponent = Component.extend({ classNames: 'linked-block', queryParams: null, + linkPrefix: null, encode: false, @@ -35,6 +36,11 @@ let LinkedBlockComponent = Component.extend({ if (queryParams) { params.push({ queryParams }); } + if (this.linkPrefix) { + let targetRoute = this.params[0]; + targetRoute = `${this.linkPrefix}.${targetRoute}`; + this.params[0] = targetRoute; + } this.get('router').transitionTo(...params); } }, diff --git a/ui/app/components/list-item.js b/ui/lib/core/addon/components/list-item.js similarity index 91% rename from ui/app/components/list-item.js rename to ui/lib/core/addon/components/list-item.js index c0a4396d11..f89ccd1319 100644 --- a/ui/app/components/list-item.js +++ b/ui/lib/core/addon/components/list-item.js @@ -1,8 +1,10 @@ import { inject as service } from '@ember/service'; import Component from '@ember/component'; import { task } from 'ember-concurrency'; +import layout from '../templates/components/list-item'; export default Component.extend({ + layout, flashMessages: service(), tagName: '', linkParams: null, diff --git a/ui/app/components/list-item/content.js b/ui/lib/core/addon/components/list-item/content.js similarity index 55% rename from ui/app/components/list-item/content.js rename to ui/lib/core/addon/components/list-item/content.js index 4798652642..2475a7f891 100644 --- a/ui/app/components/list-item/content.js +++ b/ui/lib/core/addon/components/list-item/content.js @@ -1,5 +1,7 @@ import Component from '@ember/component'; +import layout from '../../templates/components/list-item/content'; export default Component.extend({ + layout, tagName: '', }); diff --git a/ui/app/components/list-item/popup-menu.js b/ui/lib/core/addon/components/list-item/popup-menu.js similarity index 61% rename from ui/app/components/list-item/popup-menu.js rename to ui/lib/core/addon/components/list-item/popup-menu.js index b1f4a59f1d..467a1a6860 100644 --- a/ui/app/components/list-item/popup-menu.js +++ b/ui/lib/core/addon/components/list-item/popup-menu.js @@ -1,6 +1,8 @@ import Component from '@ember/component'; +import layout from '../../templates/components/list-item/popup-menu'; export default Component.extend({ + layout, tagName: '', item: null, hasMenu: null, diff --git a/ui/app/components/list-pagination.js b/ui/lib/core/addon/components/list-pagination.js similarity index 94% rename from ui/app/components/list-pagination.js rename to ui/lib/core/addon/components/list-pagination.js index d973566c1b..16aea03c18 100644 --- a/ui/app/components/list-pagination.js +++ b/ui/lib/core/addon/components/list-pagination.js @@ -2,8 +2,10 @@ import { gt } from '@ember/object/computed'; import Component from '@ember/component'; import { computed } from '@ember/object'; import { range } from 'ember-composable-helpers/helpers/range'; +import layout from '../templates/components/list-pagination'; export default Component.extend({ + layout, classNames: ['box', 'is-shadowless', 'list-pagination'], page: null, lastPage: null, diff --git a/ui/app/components/list-view.js b/ui/lib/core/addon/components/list-view.js similarity index 70% rename from ui/app/components/list-view.js rename to ui/lib/core/addon/components/list-view.js index 6c21340897..37882a35cd 100644 --- a/ui/app/components/list-view.js +++ b/ui/lib/core/addon/components/list-view.js @@ -1,14 +1,21 @@ import Component from '@ember/component'; import { computed } from '@ember/object'; import { pluralize } from 'ember-inflector'; +import layout from '../templates/components/list-view'; export default Component.extend({ + layout, tagName: '', items: null, itemNoun: 'item', // the dasherized name of a component to render // in the EmptyState component if there are no items in items.length emptyActions: '', + showPagination: computed('paginationRouteName', 'items.meta{lastPage,total}', function() { + return this.paginationRouteName && this.items.meta.lastPage > 1 && this.items.meta.total > 0; + }), + + paginationRouteName: '', emptyTitle: computed('itemNoun', function() { let items = pluralize(this.get('itemNoun')); diff --git a/ui/app/components/masked-input.js b/ui/lib/core/addon/components/masked-input.js similarity index 96% rename from ui/app/components/masked-input.js rename to ui/lib/core/addon/components/masked-input.js index 584d7afb0b..3d9e818b3b 100644 --- a/ui/app/components/masked-input.js +++ b/ui/lib/core/addon/components/masked-input.js @@ -1,6 +1,7 @@ import Component from '@ember/component'; import { computed } from '@ember/object'; import autosize from 'autosize'; +import layout from '../templates/components/masked-input'; /** * @module MaskedInput @@ -24,6 +25,7 @@ import autosize from 'autosize'; */ export default Component.extend({ + layout, value: null, placeholder: 'value', didInsertElement() { diff --git a/ui/app/components/namespace-reminder.js b/ui/lib/core/addon/components/namespace-reminder.js similarity index 87% rename from ui/app/components/namespace-reminder.js rename to ui/lib/core/addon/components/namespace-reminder.js index 464f4b6c64..a534ac6613 100644 --- a/ui/app/components/namespace-reminder.js +++ b/ui/lib/core/addon/components/namespace-reminder.js @@ -2,8 +2,10 @@ import { inject as service } from '@ember/service'; import { not } from '@ember/object/computed'; import Component from '@ember/component'; import { computed } from '@ember/object'; +import layout from '../templates/components/namespace-reminder'; export default Component.extend({ + layout, namespace: service(), showMessage: not('namespace.inRootNamespace'), //public API diff --git a/ui/app/components/navigate-input.js b/ui/lib/core/addon/components/navigate-input.js similarity index 86% rename from ui/app/components/navigate-input.js rename to ui/lib/core/addon/components/navigate-input.js index ce408b20e6..6740656dc2 100644 --- a/ui/app/components/navigate-input.js +++ b/ui/lib/core/addon/components/navigate-input.js @@ -1,12 +1,16 @@ import { schedule, debounce } from '@ember/runloop'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; + +//TODO MOVE THESE TO THE ADDON import utils from 'vault/lib/key-utils'; import keys from 'vault/lib/keycodes'; import FocusOnInsertMixin from 'vault/mixins/focus-on-insert'; import { encodePath } from 'vault/utils/path-encoding-helpers'; -const routeFor = function(type, mode) { +import layout from '../templates/components/navigate-input'; + +const routeFor = function(type, mode, urls) { const MODES = { secrets: 'vault.cluster.secrets.backend', 'secrets-cert': 'vault.cluster.secrets.backend', @@ -14,6 +18,11 @@ const routeFor = function(type, mode) { 'policy-list': 'vault.cluster.policies', leases: 'vault.cluster.access.leases', }; + // urls object should have create, list, show keys + // so we'll return that here + if (urls) { + return urls[type.replace('-root', '')]; + } let useSuffix = true; const typeVal = mode === 'secrets' || mode === 'leases' ? type : type.replace('-root', ''); const modeKey = mode + '-' + typeVal; @@ -26,9 +35,11 @@ const routeFor = function(type, mode) { }; export default Component.extend(FocusOnInsertMixin, { + layout, router: service(), classNames: ['navigate-filter'], + urls: null, // these get passed in from the outside // actions that get passed in @@ -75,19 +86,25 @@ export default Component.extend(FocusOnInsertMixin, { return; } if (this.get('filterMatchesKey') && !utils.keyIsFolder(val)) { - let params = [routeFor('show', mode), extraParams, this.keyForNav(val)].compact(); + let params = [routeFor('show', mode, this.urls), extraParams, this.keyForNav(val)].compact(); this.transitionToRoute(...params); } else { if (mode === 'policies') { return; } - let route = routeFor('create', mode); + let route = routeFor('create', mode, this.urls); if (baseKey) { this.transitionToRoute(route, this.keyForNav(baseKey), { queryParams: { initialKey: val, }, }); + } else if (this.urls) { + this.transitionToRoute(route, { + queryParams: { + initialKey: this.keyForNav(val), + }, + }); } else { this.transitionToRoute(route + '-root', { queryParams: { @@ -136,7 +153,7 @@ export default Component.extend(FocusOnInsertMixin, { }, navigate(key, mode, pageFilter) { - const route = routeFor(key ? 'list' : 'list-root', mode); + const route = routeFor(key ? 'list' : 'list-root', mode, this.urls); let args = [route]; if (key) { args.push(key); @@ -161,7 +178,7 @@ export default Component.extend(FocusOnInsertMixin, { filterUpdatedNoNav: function(val, mode) { var key = val ? val.trim() : null; - this.transitionToRoute(routeFor('list-root', mode), { + this.transitionToRoute(routeFor('list-root', mode, this.urls), { queryParams: { pageFilter: key, page: 1, diff --git a/ui/app/components/string-list.js b/ui/lib/core/addon/components/string-list.js similarity index 97% rename from ui/app/components/string-list.js rename to ui/lib/core/addon/components/string-list.js index 3936e66711..bc9f9369d4 100644 --- a/ui/app/components/string-list.js +++ b/ui/lib/core/addon/components/string-list.js @@ -1,8 +1,10 @@ import ArrayProxy from '@ember/array/proxy'; import Component from '@ember/component'; import { set, computed } from '@ember/object'; +import layout from '../templates/components/string-list'; export default Component.extend({ + layout, 'data-test-component': 'string-list', classNames: ['field', 'string-list', 'form-section'], diff --git a/ui/app/components/tool-tip.js b/ui/lib/core/addon/components/tool-tip.js similarity index 73% rename from ui/app/components/tool-tip.js rename to ui/lib/core/addon/components/tool-tip.js index 6ea25baead..20ce3a48f6 100644 --- a/ui/app/components/tool-tip.js +++ b/ui/lib/core/addon/components/tool-tip.js @@ -1,6 +1,8 @@ import HoverDropdown from 'ember-basic-dropdown-hover/components/basic-dropdown-hover'; +import layout from '../templates/components/tool-tip'; export default HoverDropdown.extend({ + layout, delay: 0, horizontalPosition: 'auto-right', }); diff --git a/ui/app/components/toolbar-filters.js b/ui/lib/core/addon/components/toolbar-filters.js similarity index 74% rename from ui/app/components/toolbar-filters.js rename to ui/lib/core/addon/components/toolbar-filters.js index 045aa3b3f1..7a7bc3a022 100644 --- a/ui/app/components/toolbar-filters.js +++ b/ui/lib/core/addon/components/toolbar-filters.js @@ -17,6 +17,10 @@ * */ -import OuterHTML from './outer-html'; +import Component from '@ember/component'; +import layout from '../templates/components/toolbar-filters'; -export default OuterHTML.extend({}); +export default Component.extend({ + layout, + tagName: '', +}); diff --git a/ui/app/helpers/path-or-array.js b/ui/lib/core/addon/helpers/path-or-array.js similarity index 100% rename from ui/app/helpers/path-or-array.js rename to ui/lib/core/addon/helpers/path-or-array.js diff --git a/ui/app/mixins/list-controller.js b/ui/lib/core/addon/mixins/list-controller.js similarity index 96% rename from ui/app/mixins/list-controller.js rename to ui/lib/core/addon/mixins/list-controller.js index dbe234cf5e..28047330f9 100644 --- a/ui/app/mixins/list-controller.js +++ b/ui/lib/core/addon/mixins/list-controller.js @@ -1,7 +1,7 @@ import { computed } from '@ember/object'; import Mixin from '@ember/object/mixin'; import escapeStringRegexp from 'escape-string-regexp'; -import commonPrefix from 'vault/utils/common-prefix'; +import commonPrefix from 'core/utils/common-prefix'; export default Mixin.create({ queryParams: { diff --git a/ui/lib/core/addon/mixins/list-route.js b/ui/lib/core/addon/mixins/list-route.js new file mode 100644 index 0000000000..8799b7a008 --- /dev/null +++ b/ui/lib/core/addon/mixins/list-route.js @@ -0,0 +1,30 @@ +import Mixin from '@ember/object/mixin'; +import { get } from '@ember/object'; + +export default Mixin.create({ + queryParams: { + page: { + refreshModel: true, + }, + pageFilter: { + refreshModel: true, + }, + }, + + setupController(controller, resolvedModel) { + let { pageFilter } = this.paramsFor(this.routeName); + this._super(...arguments); + controller.setProperties({ + filter: pageFilter || '', + page: get(resolvedModel || {}, 'meta.currentPage') || 1, + }); + }, + + resetController(controller, isExiting) { + this._super(...arguments); + if (isExiting) { + controller.set('pageFilter', null); + controller.set('filter', null); + } + }, +}); diff --git a/ui/app/templates/components/list-item/content.hbs b/ui/lib/core/addon/templates/components/content.hbs similarity index 100% rename from ui/app/templates/components/list-item/content.hbs rename to ui/lib/core/addon/templates/components/content.hbs diff --git a/ui/app/templates/components/edit-form.hbs b/ui/lib/core/addon/templates/components/edit-form.hbs similarity index 77% rename from ui/app/templates/components/edit-form.hbs rename to ui/lib/core/addon/templates/components/edit-form.hbs index 8fcff4462d..b439bea5c1 100644 --- a/ui/app/templates/components/edit-form.hbs +++ b/ui/lib/core/addon/templates/components/edit-form.hbs @@ -18,9 +18,21 @@
    - {{#each model.fields as |attr|}} - {{form-field data-test-field attr=attr model=model}} - {{/each}} + {{#if (or model.fields model.attrs)}} + {{#each (or model.fields model.attrs) as |attr|}} + + {{/each}} + {{else if model.fieldGroups}} + + {{/if}}
    diff --git a/ui/app/templates/components/field-group-show.hbs b/ui/lib/core/addon/templates/components/field-group-show.hbs similarity index 63% rename from ui/app/templates/components/field-group-show.hbs rename to ui/lib/core/addon/templates/components/field-group-show.hbs index 44f7434384..9207497ca5 100644 --- a/ui/app/templates/components/field-group-show.hbs +++ b/ui/lib/core/addon/templates/components/field-group-show.hbs @@ -3,11 +3,11 @@ {{#each-in fieldGroup as |group fields|}} {{#if (or (eq group "default") (eq group "Options"))}} {{#each fields as |attr|}} - {{#unless (eq attr.options.fieldValue 'id')}} - - {{/unless}} + {{/each}} {{else}}
    @@ -15,12 +15,14 @@ {{group}} {{#each fields as |attr|}} - + @value={{get @model attr.name}} + /> {{/each}}
    {{/if}} {{/each-in}} {{/each}} -
    \ No newline at end of file +
    diff --git a/ui/app/templates/components/form-field-groups.hbs b/ui/lib/core/addon/templates/components/form-field-groups.hbs similarity index 100% rename from ui/app/templates/components/form-field-groups.hbs rename to ui/lib/core/addon/templates/components/form-field-groups.hbs diff --git a/ui/app/templates/components/form-field.hbs b/ui/lib/core/addon/templates/components/form-field.hbs similarity index 98% rename from ui/app/templates/components/form-field.hbs rename to ui/lib/core/addon/templates/components/form-field.hbs index 64d91c9428..000cc0cd0b 100644 --- a/ui/app/templates/components/form-field.hbs +++ b/ui/lib/core/addon/templates/components/form-field.hbs @@ -100,7 +100,7 @@ initialValue=(or (get model valuePath) attr.options.defaultValue) labelText=labelString warning=attr.options.warning - setDefaultValue=(or attr.options.setDefault false) + setDefaultValue=(or (get model valuePath) attr.options.setDefault false) onChange=(action (action "setAndBroadcast" valuePath)) }} {{else if (eq attr.options.editType "stringArray")}} diff --git a/ui/app/templates/components/info-tooltip.hbs b/ui/lib/core/addon/templates/components/info-tooltip.hbs similarity index 100% rename from ui/app/templates/components/info-tooltip.hbs rename to ui/lib/core/addon/templates/components/info-tooltip.hbs diff --git a/ui/app/templates/components/list-item.hbs b/ui/lib/core/addon/templates/components/list-item.hbs similarity index 92% rename from ui/app/templates/components/list-item.hbs rename to ui/lib/core/addon/templates/components/list-item.hbs index fa760d3c56..08e224b462 100644 --- a/ui/app/templates/components/list-item.hbs +++ b/ui/lib/core/addon/templates/components/list-item.hbs @@ -1,7 +1,7 @@ {{#if componentName}} {{component componentName item=item}} {{else if linkParams}} - +
    {{#link-to params=linkParams class="has-text-weight-semibold has-text-black is-display-flex is-flex-1 is-no-underline"}} diff --git a/ui/lib/core/addon/templates/components/list-item/content.hbs b/ui/lib/core/addon/templates/components/list-item/content.hbs new file mode 100644 index 0000000000..889d9eeadc --- /dev/null +++ b/ui/lib/core/addon/templates/components/list-item/content.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/ui/app/templates/components/list-item/popup-menu.hbs b/ui/lib/core/addon/templates/components/list-item/popup-menu.hbs similarity index 100% rename from ui/app/templates/components/list-item/popup-menu.hbs rename to ui/lib/core/addon/templates/components/list-item/popup-menu.hbs diff --git a/ui/app/templates/components/list-pagination.hbs b/ui/lib/core/addon/templates/components/list-pagination.hbs similarity index 100% rename from ui/app/templates/components/list-pagination.hbs rename to ui/lib/core/addon/templates/components/list-pagination.hbs diff --git a/ui/lib/core/addon/templates/components/list-view.hbs b/ui/lib/core/addon/templates/components/list-view.hbs new file mode 100644 index 0000000000..76d779ec31 --- /dev/null +++ b/ui/lib/core/addon/templates/components/list-view.hbs @@ -0,0 +1,23 @@ +{{#if + (or + (and items.meta items.meta.total) + items.length + ) +}} +
    + {{#each items as |item|}} + {{yield (hash item=item)}} + {{else}} + {{yield}} + {{/each}} + {{#if showPagination}} + + {{/if}} +
    +{{else}} + {{yield (hash empty=(component "empty-state" title=this.emptyTitle message=this.emptyMessage))}} +{{/if}} diff --git a/ui/app/templates/components/masked-input.hbs b/ui/lib/core/addon/templates/components/masked-input.hbs similarity index 100% rename from ui/app/templates/components/masked-input.hbs rename to ui/lib/core/addon/templates/components/masked-input.hbs diff --git a/ui/app/templates/components/namespace-reminder.hbs b/ui/lib/core/addon/templates/components/namespace-reminder.hbs similarity index 100% rename from ui/app/templates/components/namespace-reminder.hbs rename to ui/lib/core/addon/templates/components/namespace-reminder.hbs diff --git a/ui/app/templates/components/navigate-input.hbs b/ui/lib/core/addon/templates/components/navigate-input.hbs similarity index 100% rename from ui/app/templates/components/navigate-input.hbs rename to ui/lib/core/addon/templates/components/navigate-input.hbs diff --git a/ui/app/templates/components/string-list.hbs b/ui/lib/core/addon/templates/components/string-list.hbs similarity index 100% rename from ui/app/templates/components/string-list.hbs rename to ui/lib/core/addon/templates/components/string-list.hbs diff --git a/ui/app/templates/components/tool-tip.hbs b/ui/lib/core/addon/templates/components/tool-tip.hbs similarity index 100% rename from ui/app/templates/components/tool-tip.hbs rename to ui/lib/core/addon/templates/components/tool-tip.hbs diff --git a/ui/app/templates/components/toolbar-filters.hbs b/ui/lib/core/addon/templates/components/toolbar-filters.hbs similarity index 100% rename from ui/app/templates/components/toolbar-filters.hbs rename to ui/lib/core/addon/templates/components/toolbar-filters.hbs diff --git a/ui/app/utils/common-prefix.js b/ui/lib/core/addon/utils/common-prefix.js similarity index 100% rename from ui/app/utils/common-prefix.js rename to ui/lib/core/addon/utils/common-prefix.js diff --git a/ui/lib/core/app/components/edit-form.js b/ui/lib/core/app/components/edit-form.js new file mode 100644 index 0000000000..912355721a --- /dev/null +++ b/ui/lib/core/app/components/edit-form.js @@ -0,0 +1 @@ +export { default } from 'core/components/edit-form'; diff --git a/ui/lib/core/app/components/field-group-show.js b/ui/lib/core/app/components/field-group-show.js new file mode 100644 index 0000000000..c081c09c18 --- /dev/null +++ b/ui/lib/core/app/components/field-group-show.js @@ -0,0 +1 @@ +export { default } from 'core/components/field-group-show'; diff --git a/ui/lib/core/app/components/form-field-groups.js b/ui/lib/core/app/components/form-field-groups.js new file mode 100644 index 0000000000..188b5dee46 --- /dev/null +++ b/ui/lib/core/app/components/form-field-groups.js @@ -0,0 +1 @@ +export { default } from 'core/components/form-field-groups'; diff --git a/ui/lib/core/app/components/form-field.js b/ui/lib/core/app/components/form-field.js new file mode 100644 index 0000000000..e1a443b6fa --- /dev/null +++ b/ui/lib/core/app/components/form-field.js @@ -0,0 +1 @@ +export { default } from 'core/components/form-field'; diff --git a/ui/lib/core/app/components/info-tooltip.js b/ui/lib/core/app/components/info-tooltip.js new file mode 100644 index 0000000000..f60c56421f --- /dev/null +++ b/ui/lib/core/app/components/info-tooltip.js @@ -0,0 +1 @@ +export { default } from 'core/components/info-tooltip'; diff --git a/ui/lib/core/app/components/linked-block.js b/ui/lib/core/app/components/linked-block.js new file mode 100644 index 0000000000..04a77b6185 --- /dev/null +++ b/ui/lib/core/app/components/linked-block.js @@ -0,0 +1 @@ +export { default } from 'core/components/linked-block'; diff --git a/ui/lib/core/app/components/list-item.js b/ui/lib/core/app/components/list-item.js new file mode 100644 index 0000000000..8498eaaddd --- /dev/null +++ b/ui/lib/core/app/components/list-item.js @@ -0,0 +1 @@ +export { default } from 'core/components/list-item'; diff --git a/ui/lib/core/app/components/list-item/content.js b/ui/lib/core/app/components/list-item/content.js new file mode 100644 index 0000000000..2976bd9c4d --- /dev/null +++ b/ui/lib/core/app/components/list-item/content.js @@ -0,0 +1 @@ +export { default } from 'core/components/list-item/content'; diff --git a/ui/lib/core/app/components/list-item/popup-menu.js b/ui/lib/core/app/components/list-item/popup-menu.js new file mode 100644 index 0000000000..e0017879a1 --- /dev/null +++ b/ui/lib/core/app/components/list-item/popup-menu.js @@ -0,0 +1 @@ +export { default } from 'core/components/list-item/popup-menu'; diff --git a/ui/lib/core/app/components/list-pagination.js b/ui/lib/core/app/components/list-pagination.js new file mode 100644 index 0000000000..87760c2e88 --- /dev/null +++ b/ui/lib/core/app/components/list-pagination.js @@ -0,0 +1 @@ +export { default } from 'core/components/list-pagination'; diff --git a/ui/lib/core/app/components/list-view.js b/ui/lib/core/app/components/list-view.js new file mode 100644 index 0000000000..d2f2b145c7 --- /dev/null +++ b/ui/lib/core/app/components/list-view.js @@ -0,0 +1 @@ +export { default } from 'core/components/list-view'; diff --git a/ui/lib/core/app/components/masked-input.js b/ui/lib/core/app/components/masked-input.js new file mode 100644 index 0000000000..1cd0be5419 --- /dev/null +++ b/ui/lib/core/app/components/masked-input.js @@ -0,0 +1 @@ +export { default } from 'core/components/masked-input'; diff --git a/ui/lib/core/app/components/namespace-reminder.js b/ui/lib/core/app/components/namespace-reminder.js new file mode 100644 index 0000000000..4e400178d7 --- /dev/null +++ b/ui/lib/core/app/components/namespace-reminder.js @@ -0,0 +1 @@ +export { default } from 'core/components/namespace-reminder'; diff --git a/ui/lib/core/app/components/navigate-input.js b/ui/lib/core/app/components/navigate-input.js new file mode 100644 index 0000000000..d81c0e805d --- /dev/null +++ b/ui/lib/core/app/components/navigate-input.js @@ -0,0 +1 @@ +export { default } from 'core/components/navigate-input'; diff --git a/ui/lib/core/app/components/string-list.js b/ui/lib/core/app/components/string-list.js new file mode 100644 index 0000000000..1c05052cd3 --- /dev/null +++ b/ui/lib/core/app/components/string-list.js @@ -0,0 +1 @@ +export { default } from 'core/components/string-list'; diff --git a/ui/lib/core/app/components/tool-tip.js b/ui/lib/core/app/components/tool-tip.js new file mode 100644 index 0000000000..03985964d8 --- /dev/null +++ b/ui/lib/core/app/components/tool-tip.js @@ -0,0 +1 @@ +export { default } from 'core/components/tool-tip'; diff --git a/ui/lib/core/app/components/toolbar-filters.js b/ui/lib/core/app/components/toolbar-filters.js new file mode 100644 index 0000000000..f83c9e1f68 --- /dev/null +++ b/ui/lib/core/app/components/toolbar-filters.js @@ -0,0 +1 @@ +export { default } from 'core/components/toolbar-filters'; diff --git a/ui/lib/core/app/helpers/path-or-array.js b/ui/lib/core/app/helpers/path-or-array.js new file mode 100644 index 0000000000..67628b392f --- /dev/null +++ b/ui/lib/core/app/helpers/path-or-array.js @@ -0,0 +1 @@ +export { default } from 'core/helpers/path-or-array'; diff --git a/ui/lib/core/app/mixins/list-controller.js b/ui/lib/core/app/mixins/list-controller.js new file mode 100644 index 0000000000..df52684542 --- /dev/null +++ b/ui/lib/core/app/mixins/list-controller.js @@ -0,0 +1 @@ +export { default } from 'core/mixins/list-controller'; diff --git a/ui/lib/core/app/mixins/list-route.js b/ui/lib/core/app/mixins/list-route.js new file mode 100644 index 0000000000..9118b4ca3c --- /dev/null +++ b/ui/lib/core/app/mixins/list-route.js @@ -0,0 +1 @@ +export { default } from 'core/mixins/list-route'; diff --git a/ui/lib/core/package.json b/ui/lib/core/package.json index 18ed96256c..b0499daf29 100644 --- a/ui/lib/core/package.json +++ b/ui/lib/core/package.json @@ -4,10 +4,12 @@ "ember-addon" ], "dependencies": { + "autosize": "*", "Duration.js": "*", "base64-js": "*", "ember-auto-import": "*", "ember-basic-dropdown": "*", + "ember-basic-dropdown-hover": "*", "ember-cli-babel": "*", "ember-cli-clipboard": "*", "ember-cli-htmlbars": "*", @@ -17,7 +19,9 @@ "ember-concurrency": "*", "ember-maybe-in-element": "*", "ember-radio-button": "*", + "ember-router-helpers": "*", "ember-svg-jar": "*", - "ember-truth-helpers": "*" + "ember-truth-helpers": "*", + "escape-string-regexp": "*" } } diff --git a/ui/lib/kmip/addon/components/header-credentials.js b/ui/lib/kmip/addon/components/header-credentials.js new file mode 100644 index 0000000000..2a88fac97b --- /dev/null +++ b/ui/lib/kmip/addon/components/header-credentials.js @@ -0,0 +1,11 @@ +import Component from '@ember/component'; +import { inject as service } from '@ember/service'; +import layout from '../templates/components/header-credentials'; + +export default Component.extend({ + layout, + tagName: '', + secretMountPath: service(), + scope: null, + role: null, +}); diff --git a/ui/lib/kmip/addon/components/header-scope.js b/ui/lib/kmip/addon/components/header-scope.js new file mode 100644 index 0000000000..fb9153aea3 --- /dev/null +++ b/ui/lib/kmip/addon/components/header-scope.js @@ -0,0 +1,9 @@ +import Component from '@ember/component'; +import { inject as service } from '@ember/service'; +import layout from '../templates/components/header-scope'; + +export default Component.extend({ + layout, + tagName: '', + secretMountPath: service(), +}); diff --git a/ui/lib/kmip/addon/components/kmip-breadcrumb.js b/ui/lib/kmip/addon/components/kmip-breadcrumb.js new file mode 100644 index 0000000000..533da3cbfc --- /dev/null +++ b/ui/lib/kmip/addon/components/kmip-breadcrumb.js @@ -0,0 +1,15 @@ +import Component from '@ember/component'; +import { inject as service } from '@ember/service'; +import layout from '../templates/components/kmip-breadcrumb'; +import { or } from '@ember/object/computed'; + +export default Component.extend({ + layout, + tagName: '', + secretMountPath: service(), + shouldShowPath: or('showPath', 'scope', 'role'), + showPath: false, + path: null, + scope: null, + role: null, +}); diff --git a/ui/lib/kmip/addon/controllers/credentials/index.js b/ui/lib/kmip/addon/controllers/credentials/index.js new file mode 100644 index 0000000000..259ff2249b --- /dev/null +++ b/ui/lib/kmip/addon/controllers/credentials/index.js @@ -0,0 +1,10 @@ +import ListController from 'core/mixins/list-controller'; +import Controller from '@ember/controller'; +import { computed } from '@ember/object'; +import { getOwner } from '@ember/application'; + +export default Controller.extend(ListController, { + mountPoint: computed(function() { + return getOwner(this).mountPoint; + }), +}); diff --git a/ui/lib/kmip/addon/controllers/scope/roles.js b/ui/lib/kmip/addon/controllers/scope/roles.js new file mode 100644 index 0000000000..259ff2249b --- /dev/null +++ b/ui/lib/kmip/addon/controllers/scope/roles.js @@ -0,0 +1,10 @@ +import ListController from 'core/mixins/list-controller'; +import Controller from '@ember/controller'; +import { computed } from '@ember/object'; +import { getOwner } from '@ember/application'; + +export default Controller.extend(ListController, { + mountPoint: computed(function() { + return getOwner(this).mountPoint; + }), +}); diff --git a/ui/lib/kmip/addon/controllers/scopes/index.js b/ui/lib/kmip/addon/controllers/scopes/index.js new file mode 100644 index 0000000000..259ff2249b --- /dev/null +++ b/ui/lib/kmip/addon/controllers/scopes/index.js @@ -0,0 +1,10 @@ +import ListController from 'core/mixins/list-controller'; +import Controller from '@ember/controller'; +import { computed } from '@ember/object'; +import { getOwner } from '@ember/application'; + +export default Controller.extend(ListController, { + mountPoint: computed(function() { + return getOwner(this).mountPoint; + }), +}); diff --git a/ui/lib/kmip/addon/engine.js b/ui/lib/kmip/addon/engine.js new file mode 100644 index 0000000000..f8f9070a58 --- /dev/null +++ b/ui/lib/kmip/addon/engine.js @@ -0,0 +1,29 @@ +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', + 'path-help', + 'router', + 'store', + 'version', + 'wizard', + 'secret-mount-path', + ], + externalRoutes: ['secrets'], + }, +}); + +loadInitializers(Eng, modulePrefix); + +export default Eng; diff --git a/ui/lib/kmip/addon/resolver.js b/ui/lib/kmip/addon/resolver.js new file mode 100644 index 0000000000..2fb563d6c0 --- /dev/null +++ b/ui/lib/kmip/addon/resolver.js @@ -0,0 +1,3 @@ +import Resolver from 'ember-resolver'; + +export default Resolver; diff --git a/ui/lib/kmip/addon/routes.js b/ui/lib/kmip/addon/routes.js new file mode 100644 index 0000000000..4c4c6abf38 --- /dev/null +++ b/ui/lib/kmip/addon/routes.js @@ -0,0 +1,21 @@ +import buildRoutes from 'ember-engines/routes'; + +export default buildRoutes(function() { + this.route('configuration'); + this.route('configure'); + this.route('scopes', { path: '/scopes' }, function() { + this.route('index', { path: '/' }); + this.route('create'); + }); + this.route('scope', { path: '/scopes/:scope_name/roles' }, function() { + this.route('roles', { path: '/' }); + this.route('roles.create', { path: '/create' }); + }); + this.route('role', { path: '/scopes/:scope_name/roles/:role_name' }); + this.route('role.edit', { path: '/scopes/:scope_name/roles/:role_name/edit' }); + this.route('credentials', { path: '/scopes/:scope_name/roles/:role_name/credentials' }, function() { + this.route('index', { path: '/' }); + this.route('generate'); + this.route('show', { path: '/:serial' }); + }); +}); diff --git a/ui/lib/kmip/addon/routes/configuration.js b/ui/lib/kmip/addon/routes/configuration.js new file mode 100644 index 0000000000..251bcd4813 --- /dev/null +++ b/ui/lib/kmip/addon/routes/configuration.js @@ -0,0 +1,20 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + pathHelp: service(), + beforeModel() { + return this.pathHelp.getNewModel('kmip/config', this.secretMountPath.currentPath); + }, + model() { + return this.store.findRecord('kmip/config', this.secretMountPath.currentPath).catch(err => { + if (err.httpStatus === 404) { + return; + } else { + throw err; + } + }); + }, +}); diff --git a/ui/lib/kmip/addon/routes/configure.js b/ui/lib/kmip/addon/routes/configure.js new file mode 100644 index 0000000000..fc681c7482 --- /dev/null +++ b/ui/lib/kmip/addon/routes/configure.js @@ -0,0 +1,22 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + pathHelp: service(), + beforeModel() { + return this.pathHelp.getNewModel('kmip/config', this.secretMountPath.currentPath); + }, + model() { + return this.store.findRecord('kmip/config', this.secretMountPath.currentPath).catch(err => { + if (err.httpStatus === 404) { + let model = this.store.createRecord('kmip/config'); + model.set('id', this.secretMountPath.currentPath); + return model; + } else { + throw err; + } + }); + }, +}); diff --git a/ui/lib/kmip/addon/routes/credentials/generate.js b/ui/lib/kmip/addon/routes/credentials/generate.js new file mode 100644 index 0000000000..1dec607b66 --- /dev/null +++ b/ui/lib/kmip/addon/routes/credentials/generate.js @@ -0,0 +1,22 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + pathHelp: service(), + model() { + const params = this.paramsFor('credentials'); + return this.store.createRecord('kmip/credential', { + backend: this.secretMountPath.currentPath, + scope: params.scope_name, + role: params.role_name, + }); + }, + + setupController(controller) { + this._super(...arguments); + let { scope_name: scope, role_name: role } = this.paramsFor('credentials'); + controller.setProperties({ role, scope }); + }, +}); diff --git a/ui/lib/kmip/addon/routes/credentials/index.js b/ui/lib/kmip/addon/routes/credentials/index.js new file mode 100644 index 0000000000..0c73634286 --- /dev/null +++ b/ui/lib/kmip/addon/routes/credentials/index.js @@ -0,0 +1,40 @@ +import Route from '@ember/routing/route'; +import ListRoute from 'core/mixins/list-route'; +import { inject as service } from '@ember/service'; + +export default Route.extend(ListRoute, { + store: service(), + secretMountPath: service(), + credParams() { + let { role_name: role, scope_name: scope } = this.paramsFor('credentials'); + return { + role, + scope, + }; + }, + model(params) { + let { role, scope } = this.credParams(); + return this.store + .lazyPaginatedQuery('kmip/credential', { + role, + scope, + backend: this.secretMountPath.currentPath, + responsePath: 'data.keys', + page: params.page, + pageFilter: params.pageFilter, + }) + .catch(err => { + if (err.httpStatus === 404) { + return []; + } else { + throw err; + } + }); + }, + + setupController(controller) { + let { role, scope } = this.credParams(); + this._super(...arguments); + controller.setProperties({ role, scope }); + }, +}); diff --git a/ui/lib/kmip/addon/routes/credentials/show.js b/ui/lib/kmip/addon/routes/credentials/show.js new file mode 100644 index 0000000000..682543389e --- /dev/null +++ b/ui/lib/kmip/addon/routes/credentials/show.js @@ -0,0 +1,29 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + credParams() { + let { role_name: role, scope_name: scope } = this.paramsFor('credentials'); + return { + role, + scope, + }; + }, + model(params) { + let { role, scope } = this.credParams(); + return this.store.queryRecord('kmip/credential', { + role, + scope, + backend: this.secretMountPath.currentPath, + id: params.serial, + }); + }, + + setupController(controller) { + let { role, scope } = this.credParams(); + this._super(...arguments); + controller.setProperties({ role, scope }); + }, +}); diff --git a/ui/lib/kmip/addon/routes/role.js b/ui/lib/kmip/addon/routes/role.js new file mode 100644 index 0000000000..7ffd57b578 --- /dev/null +++ b/ui/lib/kmip/addon/routes/role.js @@ -0,0 +1,26 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + pathHelp: service(), + + beforeModel() { + return this.pathHelp.getNewModel('kmip/role', this.secretMountPath.currentPath); + }, + + model(params) { + return this.store.queryRecord('kmip/role', { + backend: this.secretMountPath.currentPath, + scope: params.scope_name, + id: params.role_name, + }); + }, + + setupController(controller) { + this._super(...arguments); + let { scope_name: scope, role_name: role } = this.paramsFor('role'); + controller.setProperties({ role, scope }); + }, +}); diff --git a/ui/lib/kmip/addon/routes/role/edit.js b/ui/lib/kmip/addon/routes/role/edit.js new file mode 100644 index 0000000000..bede947e22 --- /dev/null +++ b/ui/lib/kmip/addon/routes/role/edit.js @@ -0,0 +1,25 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + pathHelp: service(), + beforeModel() { + return this.pathHelp.getNewModel('kmip/role', this.secretMountPath.currentPath); + }, + model() { + const params = this.paramsFor(this.routeName); + return this.store.queryRecord('kmip/role', { + backend: this.secretMountPath.currentPath, + scope: params.scope_name, + id: params.role_name, + }); + }, + + setupController(controller) { + this._super(...arguments); + let { scope_name: scope, role_name: role } = this.paramsFor(this.routeName); + controller.setProperties({ role, scope }); + }, +}); diff --git a/ui/lib/kmip/addon/routes/scope/roles.js b/ui/lib/kmip/addon/routes/scope/roles.js new file mode 100644 index 0000000000..17e9f16bae --- /dev/null +++ b/ui/lib/kmip/addon/routes/scope/roles.js @@ -0,0 +1,37 @@ +import Route from '@ember/routing/route'; +import ListRoute from 'core/mixins/list-route'; +import { inject as service } from '@ember/service'; + +export default Route.extend(ListRoute, { + store: service(), + secretMountPath: service(), + pathHelp: service(), + scope() { + return this.paramsFor('scope').scope_name; + }, + beforeModel() { + return this.pathHelp.getNewModel('kmip/role', this.secretMountPath.currentPath); + }, + model(params) { + return this.store + .lazyPaginatedQuery('kmip/role', { + backend: this.secretMountPath.currentPath, + scope: this.scope(), + responsePath: 'data.keys', + page: params.page, + pageFilter: params.pageFilter, + }) + .catch(err => { + if (err.httpStatus === 404) { + return []; + } else { + throw err; + } + }); + }, + + setupController(controller) { + this._super(...arguments); + controller.set('scope', this.scope()); + }, +}); diff --git a/ui/lib/kmip/addon/routes/scope/roles/create.js b/ui/lib/kmip/addon/routes/scope/roles/create.js new file mode 100644 index 0000000000..cf12111c18 --- /dev/null +++ b/ui/lib/kmip/addon/routes/scope/roles/create.js @@ -0,0 +1,25 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + pathHelp: service(), + scope() { + return this.paramsFor('scope').scope_name; + }, + beforeModel() { + return this.pathHelp.getNewModel('kmip/role', this.secretMountPath.currentPath); + }, + model() { + let model = this.store.createRecord('kmip/role', { + backend: this.secretMountPath.currentPath, + scope: this.scope(), + }); + return model; + }, + setupController(controller) { + this._super(...arguments); + controller.set('scope', this.scope()); + }, +}); diff --git a/ui/lib/kmip/addon/routes/scopes/create.js b/ui/lib/kmip/addon/routes/scopes/create.js new file mode 100644 index 0000000000..49e9576fe0 --- /dev/null +++ b/ui/lib/kmip/addon/routes/scopes/create.js @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default Route.extend({ + store: service(), + secretMountPath: service(), + model() { + let model = this.store.createRecord('kmip/scope', { + backend: this.secretMountPath.currentPath, + }); + return model; + }, +}); diff --git a/ui/lib/kmip/addon/routes/scopes/index.js b/ui/lib/kmip/addon/routes/scopes/index.js new file mode 100644 index 0000000000..16c5dbf6df --- /dev/null +++ b/ui/lib/kmip/addon/routes/scopes/index.js @@ -0,0 +1,38 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import ListRoute from 'core/mixins/list-route'; + +export default Route.extend(ListRoute, { + store: service(), + secretMountPath: service(), + model(params) { + return this.store + .lazyPaginatedQuery('kmip/scope', { + backend: this.secretMountPath.currentPath, + responsePath: 'data.keys', + page: params.page, + pageFilter: params.pageFilter, + }) + .catch(err => { + if (err.httpStatus === 404) { + return []; + } else { + throw err; + } + }); + }, + + actions: { + willTransition(transition) { + window.scrollTo(0, 0); + if (transition.targetName !== this.routeName) { + this.store.clearAllDatasets(); + } + return true; + }, + reload() { + this.store.clearAllDatasets(); + this.refresh(); + }, + }, +}); diff --git a/ui/lib/kmip/addon/templates/application.hbs b/ui/lib/kmip/addon/templates/application.hbs new file mode 100644 index 0000000000..e2147cab02 --- /dev/null +++ b/ui/lib/kmip/addon/templates/application.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/ui/lib/kmip/addon/templates/components/header-credentials.hbs b/ui/lib/kmip/addon/templates/components/header-credentials.hbs new file mode 100644 index 0000000000..daf73e9872 --- /dev/null +++ b/ui/lib/kmip/addon/templates/components/header-credentials.hbs @@ -0,0 +1,26 @@ + + + + + +

    + {{@role}} +

    +
    +
    +
    + +
    diff --git a/ui/lib/kmip/addon/templates/components/header-scope.hbs b/ui/lib/kmip/addon/templates/components/header-scope.hbs new file mode 100644 index 0000000000..15bf1c3898 --- /dev/null +++ b/ui/lib/kmip/addon/templates/components/header-scope.hbs @@ -0,0 +1,31 @@ + + + + + +

    + + {{this.secretMountPath.currentPath}} +

    +
    +
    +
    + +
    diff --git a/ui/lib/kmip/addon/templates/components/kmip-breadcrumb.hbs b/ui/lib/kmip/addon/templates/components/kmip-breadcrumb.hbs new file mode 100644 index 0000000000..f695d1cb86 --- /dev/null +++ b/ui/lib/kmip/addon/templates/components/kmip-breadcrumb.hbs @@ -0,0 +1,28 @@ + diff --git a/ui/lib/kmip/addon/templates/configuration.hbs b/ui/lib/kmip/addon/templates/configuration.hbs new file mode 100644 index 0000000000..acb82b8a01 --- /dev/null +++ b/ui/lib/kmip/addon/templates/configuration.hbs @@ -0,0 +1,22 @@ + + + + + Configure + + + +{{#if model}} + +{{else}} + + {{#link-to "configure"}} + Configure + {{/link-to}} + +{{/if}} diff --git a/ui/lib/kmip/addon/templates/configure.hbs b/ui/lib/kmip/addon/templates/configure.hbs new file mode 100644 index 0000000000..304e724309 --- /dev/null +++ b/ui/lib/kmip/addon/templates/configure.hbs @@ -0,0 +1,20 @@ + + + + + +

    + + Configure KMIP secrets engine +

    +
    +
    + diff --git a/ui/lib/kmip/addon/templates/credentials/generate.hbs b/ui/lib/kmip/addon/templates/credentials/generate.hbs new file mode 100644 index 0000000000..eb5fa49b1b --- /dev/null +++ b/ui/lib/kmip/addon/templates/credentials/generate.hbs @@ -0,0 +1,17 @@ + + + + + +

    + Generate credentials +

    +
    +
    + diff --git a/ui/lib/kmip/addon/templates/credentials/index.hbs b/ui/lib/kmip/addon/templates/credentials/index.hbs new file mode 100644 index 0000000000..fa6d278aac --- /dev/null +++ b/ui/lib/kmip/addon/templates/credentials/index.hbs @@ -0,0 +1,71 @@ + + + {{#if model.meta.total}} + + + {{#if filterFocused}} + {{#if filterMatchesKey}} +

    + ENTER to go to {{this.filter}} roles +

    + {{/if}} + {{#if firstPartialMatch}} +

    + TAB to complete {{firstPartialMatch.id}} +

    + {{/if}} + {{/if}} +
    + {{/if}} + + + Generate credentials + + +
    + + {{#if list.empty}} + + {{#link-to "credentials.generate"}} + Generate credentials + {{/link-to}} + + {{else if list.item}} + + + {{list.item.id}} + + +
  • + {{#link-to "credentials.show" this.scope this.role list.item.id class="is-block"}} + View credentials + {{/link-to}} +
  • +
    +
    + {{else}} + + + There are no credentials that match {{this.filter}}, press ENTER to add one. + + + {{/if}} +
    diff --git a/ui/lib/kmip/addon/templates/credentials/show.hbs b/ui/lib/kmip/addon/templates/credentials/show.hbs new file mode 100644 index 0000000000..a723cbb1ce --- /dev/null +++ b/ui/lib/kmip/addon/templates/credentials/show.hbs @@ -0,0 +1,67 @@ + + + + + +

    + Credentials +

    +
    +
    + + + + Back to role + + + Copy certificate + + + + +
    + + +
    + + +
    +
    + + +
    + {{#each model.caChain as |chain|}} + {{chain}} + {{/each}} +
    +
    +
    diff --git a/ui/lib/kmip/addon/templates/role.hbs b/ui/lib/kmip/addon/templates/role.hbs new file mode 100644 index 0000000000..5ef8e053f8 --- /dev/null +++ b/ui/lib/kmip/addon/templates/role.hbs @@ -0,0 +1,13 @@ + + + + + Edit role + + + +
    + +
    diff --git a/ui/lib/kmip/addon/templates/role/edit.hbs b/ui/lib/kmip/addon/templates/role/edit.hbs new file mode 100644 index 0000000000..32dcdae378 --- /dev/null +++ b/ui/lib/kmip/addon/templates/role/edit.hbs @@ -0,0 +1,13 @@ + + + + + +

    + Edit role +

    +
    +
    + diff --git a/ui/lib/kmip/addon/templates/scope/roles.hbs b/ui/lib/kmip/addon/templates/scope/roles.hbs new file mode 100644 index 0000000000..70f2cdab0e --- /dev/null +++ b/ui/lib/kmip/addon/templates/scope/roles.hbs @@ -0,0 +1,86 @@ + + + + + +

    + {{this.scope}} +

    +
    +
    + + {{#if model.meta.total}} + + + {{#if filterFocused}} + {{#if filterMatchesKey}} +

    + ENTER to go to {{this.filter}} roles +

    + {{/if}} + {{#if firstPartialMatch}} +

    + TAB to complete {{firstPartialMatch.id}} +

    + {{/if}} + {{/if}} +
    + {{/if}} + + + Create role + + +
    + + {{#if list.empty}} + + {{#link-to "scope.roles.create"}} + Create a role + {{/link-to}} + + {{else if list.item}} + + + {{list.item.id}} + + +
  • + {{#link-to "credentials" this.scope list.item.id class="is-block"}} + View credentials + {{/link-to}} +
  • +
  • + {{#link-to "role" this.scope list.item.id class="is-block"}} + View role + {{/link-to}} +
  • +
    +
    + {{else}} + + + There are no roles that match {{this.filter}}, press ENTER to add one. + + + {{/if}} +
    diff --git a/ui/lib/kmip/addon/templates/scope/roles/create.hbs b/ui/lib/kmip/addon/templates/scope/roles/create.hbs new file mode 100644 index 0000000000..6eedad1704 --- /dev/null +++ b/ui/lib/kmip/addon/templates/scope/roles/create.hbs @@ -0,0 +1,16 @@ + + + + + +

    + Create a role +

    +
    +
    + diff --git a/ui/lib/kmip/addon/templates/scopes.hbs b/ui/lib/kmip/addon/templates/scopes.hbs new file mode 100644 index 0000000000..c24cd68950 --- /dev/null +++ b/ui/lib/kmip/addon/templates/scopes.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/ui/lib/kmip/addon/templates/scopes/create.hbs b/ui/lib/kmip/addon/templates/scopes/create.hbs new file mode 100644 index 0000000000..8eca015ba6 --- /dev/null +++ b/ui/lib/kmip/addon/templates/scopes/create.hbs @@ -0,0 +1,12 @@ + + +

    + Create a scope +

    +
    +
    + diff --git a/ui/lib/kmip/addon/templates/scopes/index.hbs b/ui/lib/kmip/addon/templates/scopes/index.hbs new file mode 100644 index 0000000000..03736fb5e8 --- /dev/null +++ b/ui/lib/kmip/addon/templates/scopes/index.hbs @@ -0,0 +1,71 @@ + + + {{#if model.meta.total}} + + + {{#if filterFocused}} + {{#if filterMatchesKey}} +

    + ENTER to go to {{this.filter}} +

    + {{/if}} + {{#if firstPartialMatch}} +

    + TAB to complete {{firstPartialMatch.id}} +

    + {{/if}} + {{/if}} +
    + {{/if}} + + + Create scope + + +
    + + {{#if list.empty}} + + {{#link-to "scopes.create"}} + Create a scope + {{/link-to}} + + {{else if list.item}} + + + {{list.item.id}} + + +
  • + {{#link-to "scope" list.item.id class="is-block"}} + View scope + {{/link-to}} +
  • +
    +
    + {{else}} + + + There are no scopes that match {{this.filter}}, press ENTER to add one. + + + {{/if}} +
    diff --git a/ui/lib/kmip/config/environment.js b/ui/lib/kmip/config/environment.js new file mode 100644 index 0000000000..74131a35c9 --- /dev/null +++ b/ui/lib/kmip/config/environment.js @@ -0,0 +1,11 @@ +/* eslint-env node */ +'use strict'; + +module.exports = function(environment) { + let ENV = { + modulePrefix: 'kmip', + environment, + }; + + return ENV; +}; diff --git a/ui/lib/kmip/index.js b/ui/lib/kmip/index.js new file mode 100644 index 0000000000..f1884bddb9 --- /dev/null +++ b/ui/lib/kmip/index.js @@ -0,0 +1,17 @@ +/* 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: 'kmip', + + lazyLoading: { + enabled: true, + }, + + isDevelopingAddon() { + return true; + }, +}); diff --git a/ui/lib/kmip/package.json b/ui/lib/kmip/package.json new file mode 100644 index 0000000000..d3732ae651 --- /dev/null +++ b/ui/lib/kmip/package.json @@ -0,0 +1,16 @@ +{ + "name": "kmip", + "keywords": [ + "ember-addon", + "ember-engine" + ], + "dependencies": { + "ember-cli-htmlbars": "*", + "ember-cli-babel": "*" + }, + "ember-addon": { + "paths": [ + "../core" + ] + } +} diff --git a/ui/package.json b/ui/package.json index f01966574c..8fceb8b579 100644 --- a/ui/package.json +++ b/ui/package.json @@ -106,6 +106,7 @@ "ember-radio-button": "^1.1.1", "ember-resolver": "^5.0.1", "ember-responsive": "^3.0.0-beta.3", + "ember-router-helpers": "^0.2.0", "ember-sinon": "^1.0.1", "ember-source": "~3.4.0", "ember-svg-jar": "^1.2.2", @@ -155,7 +156,8 @@ "paths": [ "lib/core", "lib/css", - "lib/replication" + "lib/replication", + "lib/kmip" ] }, "dependencies": {} diff --git a/ui/public/eco/kmip.svg b/ui/public/eco/kmip.svg new file mode 100644 index 0000000000..e95f1b6171 --- /dev/null +++ b/ui/public/eco/kmip.svg @@ -0,0 +1 @@ + diff --git a/ui/tests/integration/components/http-requests-bar-chart-test.js b/ui/tests/integration/components/http-requests-bar-chart-test.js index bc6bb65592..fdf815570b 100644 --- a/ui/tests/integration/components/http-requests-bar-chart-test.js +++ b/ui/tests/integration/components/http-requests-bar-chart-test.js @@ -32,8 +32,6 @@ module('Integration | Component | http-requests-bar-chart', function(hooks) { test('it formats the ticks', async function(assert) { await render(hbs``); - debugger; - assert.equal( this.element.querySelector('.x-axis>.tick').textContent, 'Apr 2019', diff --git a/ui/tests/unit/utils/common-prefix-test.js b/ui/tests/unit/utils/common-prefix-test.js index 5b437330c5..d37dfc3085 100644 --- a/ui/tests/unit/utils/common-prefix-test.js +++ b/ui/tests/unit/utils/common-prefix-test.js @@ -1,4 +1,4 @@ -import commonPrefix from 'vault/utils/common-prefix'; +import commonPrefix from 'core/utils/common-prefix'; import { module, test } from 'qunit'; module('Unit | Util | common prefix', function() { diff --git a/ui/tests/unit/utils/openapi-to-attrs-test.js b/ui/tests/unit/utils/openapi-to-attrs-test.js index ee52120b6c..6c3bd82ed0 100644 --- a/ui/tests/unit/utils/openapi-to-attrs-test.js +++ b/ui/tests/unit/utils/openapi-to-attrs-test.js @@ -3,7 +3,7 @@ import { module, test } from 'qunit'; import DS from 'ember-data'; const { attr } = DS; -module('Unit | Util | OpenAPI Data Utilities', function () { +module('Unit | Util | OpenAPI Data Utilities', function() { const OPENAPI_RESPONSE_PROPS = { ttl: { type: 'string', @@ -51,7 +51,6 @@ module('Unit | Util | OpenAPI Data Utilities', function () { ttl: { helpText: 'this is a TTL!', editType: 'ttl', - type: 'string', label: 'TTL', fieldGroup: 'default', }, @@ -121,7 +120,6 @@ module('Unit | Util | OpenAPI Data Utilities', function () { }), ttl: attr('string', { editType: 'ttl', - type: 'string', label: 'TTL', helpText: 'this is a TTL!', }), @@ -146,21 +144,21 @@ module('Unit | Util | OpenAPI Data Utilities', function () { const NEW_FIELDS = ['one', 'two', 'three']; - test('it creates objects from OpenAPI schema props', function (assert) { + test('it creates objects from OpenAPI schema props', function(assert) { const generatedProps = expandOpenApiProps(OPENAPI_RESPONSE_PROPS); for (let propName in EXPANDED_PROPS) { assert.deepEqual(EXPANDED_PROPS[propName], generatedProps[propName], `correctly expands ${propName}`); } }); - test('it combines OpenAPI props with existing model attrs', function (assert) { + test('it combines OpenAPI props with existing model attrs', function(assert) { const combined = combineAttributes(EXISTING_MODEL_ATTRS, EXPANDED_PROPS); for (let propName in EXISTING_MODEL_ATTRS) { assert.deepEqual(COMBINED_ATTRS[propName], combined[propName]); } }); - test('it adds new fields from OpenAPI to fieldGroups except for exclusions', function (assert) { + test('it adds new fields from OpenAPI to fieldGroups except for exclusions', function(assert) { let modelFieldGroups = [ { default: ['name', 'awesomePeople'] }, { @@ -183,7 +181,7 @@ module('Unit | Util | OpenAPI Data Utilities', function () { ); } }); - test('it adds all new fields from OpenAPI to fieldGroups when excludedFields is empty', function (assert) { + test('it adds all new fields from OpenAPI to fieldGroups when excludedFields is empty', function(assert) { let modelFieldGroups = [ { default: ['name', 'awesomePeople'] }, { @@ -206,7 +204,7 @@ module('Unit | Util | OpenAPI Data Utilities', function () { ); } }); - test('it keeps fields the same when there are no brand new fields from OpenAPI', function (assert) { + test('it keeps fields the same when there are no brand new fields from OpenAPI', function(assert) { let modelFieldGroups = [ { default: ['name', 'awesomePeople', 'two', 'one', 'three'] }, { diff --git a/ui/yarn.lock b/ui/yarn.lock index 282979f134..5d8dad074c 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -8141,6 +8141,22 @@ ember-router-generator@^1.2.3: dependencies: recast "^0.11.3" +ember-router-helpers@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ember-router-helpers/-/ember-router-helpers-0.2.0.tgz#9e19d0cfe0cc99af315dae2ce8747b1abef1ff75" + integrity sha512-t2eGSwfiK+Ec2nZcPo3bR8NvmK9CxISupqrHEgMkeBpgtBX0sjoUzAewqz1nu3TTrcpkkrgxNiFQvkIpK6Zz+g== + dependencies: + ember-cli-babel "^6.6.0" + ember-cli-htmlbars "^2.0.2" + ember-router-service-polyfill "^1.0.1" + +ember-router-service-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ember-router-service-polyfill/-/ember-router-service-polyfill-1.0.3.tgz#506d7fbd7179951410b7ffe8db171540601d88b1" + integrity sha1-UG1/vXF5lRQQt//o2xcVQGAdiLE= + dependencies: + ember-cli-babel "^6.8.2" + ember-runtime-enumerable-includes-polyfill@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ember-runtime-enumerable-includes-polyfill/-/ember-runtime-enumerable-includes-polyfill-2.1.0.tgz#dc6d4a028471e4acc350dfd2a149874fb20913f5"