diff --git a/ui/app/components/page-header-level-left.js b/ui/app/components/page-header-level-left.js
new file mode 100644
index 0000000000..e3ac4fb5c0
--- /dev/null
+++ b/ui/app/components/page-header-level-left.js
@@ -0,0 +1,5 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+ tagName: '',
+});
diff --git a/ui/app/components/page-header-level-right.js b/ui/app/components/page-header-level-right.js
new file mode 100644
index 0000000000..e3ac4fb5c0
--- /dev/null
+++ b/ui/app/components/page-header-level-right.js
@@ -0,0 +1,5 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+ tagName: '',
+});
diff --git a/ui/app/components/page-header-top.js b/ui/app/components/page-header-top.js
new file mode 100644
index 0000000000..e3ac4fb5c0
--- /dev/null
+++ b/ui/app/components/page-header-top.js
@@ -0,0 +1,5 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+ tagName: '',
+});
diff --git a/ui/app/components/page-header.js b/ui/app/components/page-header.js
new file mode 100644
index 0000000000..0c8a391eed
--- /dev/null
+++ b/ui/app/components/page-header.js
@@ -0,0 +1,6 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+ tagName: '',
+ hasLevel: true,
+});
diff --git a/ui/app/components/secret-list-header.js b/ui/app/components/secret-list-header.js
new file mode 100644
index 0000000000..63aeac4439
--- /dev/null
+++ b/ui/app/components/secret-list-header.js
@@ -0,0 +1,14 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+ tagName: '',
+
+ // api
+ isCertTab: false,
+ isConfigure: false,
+ baseKey: null,
+ backendCrumb: null,
+ model: null,
+
+
+});
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 2c51a88cb1..569a281148 100644
--- a/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js
+++ b/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js
@@ -30,7 +30,6 @@ export default Ember.Controller.extend({
description: null,
default_lease_ttl: null,
max_lease_ttl: null,
- force_no_cache: null,
showConfig: false,
local: false,
sealWrap: false,
@@ -50,7 +49,6 @@ export default Ember.Controller.extend({
description: null,
default_lease_ttl: null,
max_lease_ttl: null,
- force_no_cache: null,
local: false,
showConfig: false,
sealWrap: false,
@@ -82,7 +80,6 @@ export default Ember.Controller.extend({
selectedType: type,
description,
default_lease_ttl,
- force_no_cache,
local,
max_lease_ttl,
sealWrap,
@@ -92,7 +89,6 @@ export default Ember.Controller.extend({
'selectedType',
'description',
'default_lease_ttl',
- 'force_no_cache',
'local',
'max_lease_ttl',
'sealWrap',
@@ -112,9 +108,8 @@ export default Ember.Controller.extend({
if (this.get('showConfig')) {
attrs.config = {
- default_lease_ttl,
- max_lease_ttl,
- force_no_cache,
+ defaultLeaseTtl: default_lease_ttl,
+ maxLeaseTtl: max_lease_ttl,
};
}
diff --git a/ui/app/models/mount-options.js b/ui/app/models/mount-options.js
index d40c18f9b0..9870456f68 100644
--- a/ui/app/models/mount-options.js
+++ b/ui/app/models/mount-options.js
@@ -2,5 +2,7 @@ import attr from 'ember-data/attr';
import Fragment from 'ember-data-model-fragments/fragment';
export default Fragment.extend({
- version: attr('number'),
+ version: attr('number', {
+ label: 'Version',
+ }),
});
diff --git a/ui/app/models/secret-engine.js b/ui/app/models/secret-engine.js
index 3e0a57858d..a015f1b33f 100644
--- a/ui/app/models/secret-engine.js
+++ b/ui/app/models/secret-engine.js
@@ -3,6 +3,8 @@ import DS from 'ember-data';
import { queryRecord } from 'ember-computed-query';
import { fragment } from 'ember-data-model-fragments/attributes';
+import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
+
const { attr } = DS;
const { computed } = Ember;
@@ -16,11 +18,26 @@ export default DS.Model.extend({
name: attr('string'),
type: attr('string'),
description: attr('string'),
- config: attr('object'),
- options: fragment('mount-options'),
+ config: fragment('mount-config', { defaultValue: {} }),
+ options: fragment('mount-options', { defaultValue: {} }),
local: attr('boolean'),
sealWrap: attr('boolean'),
+ formFields: [
+ 'type',
+ 'path',
+ 'description',
+ 'accessor',
+ 'local',
+ 'sealWrap',
+ 'config.{defaultLeaseTtl,maxLeaseTtl}',
+ 'options.{version}',
+ ],
+
+ attrs: computed('formFields', function() {
+ return expandAttributeMeta(this, this.get('formFields'));
+ }),
+
shouldIncludeInList: computed('type', function() {
return !LIST_EXCLUDED_BACKENDS.includes(this.get('type'));
}),
diff --git a/ui/app/router.js b/ui/app/router.js
index ecb289b575..c4ddb49d71 100644
--- a/ui/app/router.js
+++ b/ui/app/router.js
@@ -68,6 +68,7 @@ Router.map(function() {
this.route('backends', { path: '/' });
this.route('backend', { path: '/:backend' }, function() {
this.route('index', { path: '/' });
+ this.route('configuration');
// because globs / params can't be empty,
// we have to special-case ids of '' with thier own routes
this.route('list-root', { path: '/list/' });
diff --git a/ui/app/routes/vault/cluster/secrets/backend/configuration.js b/ui/app/routes/vault/cluster/secrets/backend/configuration.js
new file mode 100644
index 0000000000..e18705600d
--- /dev/null
+++ b/ui/app/routes/vault/cluster/secrets/backend/configuration.js
@@ -0,0 +1,7 @@
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+ model() {
+ return this.modelFor('vault.cluster.secrets.backend');
+ },
+});
diff --git a/ui/app/styles/components/popup-menu.scss b/ui/app/styles/components/popup-menu.scss
index 8e2406737c..5155b12370 100644
--- a/ui/app/styles/components/popup-menu.scss
+++ b/ui/app/styles/components/popup-menu.scss
@@ -20,8 +20,7 @@
}
}
.popup-menu-trigger {
- width: 3rem;
- height: 2rem;
+ min-width: auto;
}
.popup-menu-trigger.is-active {
&,
diff --git a/ui/app/styles/core/buttons.scss b/ui/app/styles/core/buttons.scss
index ef23caf786..5f961c3b67 100644
--- a/ui/app/styles/core/buttons.scss
+++ b/ui/app/styles/core/buttons.scss
@@ -146,7 +146,6 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
}
}
- &.is-more-icon,
&.tool-tip-trigger {
color: $black;
min-width: auto;
diff --git a/ui/app/templates/application.hbs b/ui/app/templates/application.hbs
index fb6884ce52..c3d67ecfe2 100644
--- a/ui/app/templates/application.hbs
+++ b/ui/app/templates/application.hbs
@@ -40,7 +40,7 @@
{{/if}}
-
+
Policies
diff --git a/ui/app/templates/components/key-value-header.hbs b/ui/app/templates/components/key-value-header.hbs
index 272f4296de..ed57f90661 100644
--- a/ui/app/templates/components/key-value-header.hbs
+++ b/ui/app/templates/components/key-value-header.hbs
@@ -1,4 +1,5 @@
+ {{yield}}
{{#each secretPath as |path index|}}
-
/
diff --git a/ui/app/templates/components/page-header-level-left.hbs b/ui/app/templates/components/page-header-level-left.hbs
new file mode 100644
index 0000000000..889d9eeadc
--- /dev/null
+++ b/ui/app/templates/components/page-header-level-left.hbs
@@ -0,0 +1 @@
+{{yield}}
diff --git a/ui/app/templates/components/page-header-level-right.hbs b/ui/app/templates/components/page-header-level-right.hbs
new file mode 100644
index 0000000000..889d9eeadc
--- /dev/null
+++ b/ui/app/templates/components/page-header-level-right.hbs
@@ -0,0 +1 @@
+{{yield}}
diff --git a/ui/app/templates/components/page-header.hbs b/ui/app/templates/components/page-header.hbs
new file mode 100644
index 0000000000..956ab8b24e
--- /dev/null
+++ b/ui/app/templates/components/page-header.hbs
@@ -0,0 +1,15 @@
+
diff --git a/ui/app/templates/components/popup-menu.hbs b/ui/app/templates/components/popup-menu.hbs
index 12f07223e2..5be8ed129a 100644
--- a/ui/app/templates/components/popup-menu.hbs
+++ b/ui/app/templates/components/popup-menu.hbs
@@ -1,5 +1,5 @@
{{#basic-dropdown class="popup-menu" horizontalPosition="auto-right" verticalPosition="below" onOpen=onOpen as |d|}}
- {{#d.trigger tagName="button" class=(concat "popup-menu-trigger button is-transparent " (if d.isOpen "is-active")) data-test-popup-menu-trigger=true}}
+ {{#d.trigger tagName="button" class=(concat "popup-menu-trigger button is-ghost " (if d.isOpen "is-active")) data-test-popup-menu-trigger=true}}
{{i-con
glyph="more"
class="has-text-black auto-width"
diff --git a/ui/app/templates/components/secret-list-header.hbs b/ui/app/templates/components/secret-list-header.hbs
new file mode 100644
index 0000000000..84cbc6b659
--- /dev/null
+++ b/ui/app/templates/components/secret-list-header.hbs
@@ -0,0 +1,108 @@
+{{#with (options-for-backend model.type) as |options|}}
+ {{#page-header as |p|}}
+ {{#p.top}}
+ {{#key-value-header
+ baseKey=baseKey
+ path="vault.cluster.secrets.backend.list"
+ root=backendCrumb
+ }}
+
-
+ /
+
+ secrets
+
+
+ {{/key-value-header}}
+ {{/p.top}}
+ {{#p.levelLeft}}
+
+ {{model.id}}
+
+ {{or options.displayName (capitalize model.type)}}
+
+ {{#if (eq model.options.version 2)}}
+
+ Version 2
+
+ {{/if}}
+
+ {{/p.levelLeft}}
+ {{#p.levelRight}}
+ {{#unless (or isCertTab isConfigure)}}
+
+ {{#secret-link
+ mode="create"
+ secret=(or baseKey.id '')
+ queryParams=(query-params initialKey='')
+ class="button has-icon-right is-ghost is-compact"
+ data-test-secret-create=true
+ }}
+ {{options.create}}
+ {{i-con glyph="chevron-right" size=11}}
+ {{/secret-link}}
+
+ {{/unless}}
+ {{#if (or (eq model.type "aws") (eq model.type "ssh") (eq model.type "pki"))}}
+
+ {{/if}}
+ {{/p.levelRight}}
+ {{/page-header}}
+ {{#if options.tabs}}
+
+
+
+ {{else}}
+ {{!-- if there are no tabs in the options, we'll hardcode them here --}}
+
+
+
+ {{/if}}
+{{/with}}
diff --git a/ui/app/templates/vault/cluster/secrets/backend/configuration.hbs b/ui/app/templates/vault/cluster/secrets/backend/configuration.hbs
new file mode 100644
index 0000000000..af8864a1bd
--- /dev/null
+++ b/ui/app/templates/vault/cluster/secrets/backend/configuration.hbs
@@ -0,0 +1,19 @@
+{{secret-list-header model=model isConfigure=true backendCrumb=(hash label=model.id text=model.id path="vault.cluster.secrets.backend.list-root" model=model.id)}}
+
+
+ {{#each model.attrs as |attr|}}
+ {{#if (eq attr.type "object")}}
+ {{info-table-row
+ alwaysRender=true
+ label=(or attr.options.label (to-label attr.name))
+ value=(stringify (get model attr.name))
+ }}
+ {{else}}
+ {{info-table-row
+ alwaysRender=(not-eq attr.name 'options.version')
+ label=(or attr.options.label (to-label attr.name))
+ value=(get model attr.name)
+ }}
+ {{/if}}
+ {{/each}}
+
diff --git a/ui/app/templates/vault/cluster/secrets/backend/list.hbs b/ui/app/templates/vault/cluster/secrets/backend/list.hbs
index b1389c4ee7..1e12a73aeb 100644
--- a/ui/app/templates/vault/cluster/secrets/backend/list.hbs
+++ b/ui/app/templates/vault/cluster/secrets/backend/list.hbs
@@ -1,79 +1,6 @@
+{{secret-list-header isCertTab=(eq tab "certs") model=backendModel baseKey=baseKey backendCrumb=backendCrumb}}
+
{{#with (options-for-backend backendType tab) as |options|}}
-
- {{#if options.tabs}}
-
-
-
- {{/if}}
@@ -90,17 +17,15 @@
mode=(if (eq tab 'certs') 'secrets-cert' 'secrets')
}}
{{#if filterFocused}}
-
-
{{#if filterMatchesKey}}
{{#unless filterIsFolder}}
-
+
Enter to view {{filter}}
{{/unless}}
{{/if}}
{{#if firstPartialMatch}}
-
+
Tab to autocomplete
{{/if}}
diff --git a/ui/app/templates/vault/cluster/secrets/backends.hbs b/ui/app/templates/vault/cluster/secrets/backends.hbs
index 8a7206d66c..78c7a1ce88 100644
--- a/ui/app/templates/vault/cluster/secrets/backends.hbs
+++ b/ui/app/templates/vault/cluster/secrets/backends.hbs
@@ -18,11 +18,8 @@
{{#linked-block
"vault.cluster.secrets.backend.list-root"
backend.id
- class=(concat
- 'box is-sideless is-marginless has-pointer '
- (if (get this (concat backend.accessor '-open')) 'has-background-white-bis')
- )
- data-test-secret-backend-link=backend.id
+ class="box is-sideless is-marginless has-pointer"
+ data-test-secret-backend-row=backend.id
}}
@@ -53,25 +50,35 @@
-
+ {{#popup-menu name="engine-menu"}}
+
+ {{/popup-menu}}
- {{#if (get this (concat backend.accessor '-open'))}}
- {{partial "partials/backend-details"}}
- {{/if}}
{{/linked-block}}
{{/each}}
{{#each unsupportedBackends as |backend|}}
-
-
+
{{i-con glyph="folder" size=14 class="has-text-grey-light"}} {{backend.path}}
@@ -90,14 +97,19 @@
-
+ {{#popup-menu name="engine-menu"}}
+
+ {{/popup-menu}}
- {{#if (get this (concat backend.accessor '-open'))}}
- {{partial "partials/backend-details"}}
- {{/if}}
{{/each}}
diff --git a/ui/app/utils/field-to-attrs.js b/ui/app/utils/field-to-attrs.js
index 723b769577..812d71d2da 100644
--- a/ui/app/utils/field-to-attrs.js
+++ b/ui/app/utils/field-to-attrs.js
@@ -42,11 +42,12 @@ export const expandAttributeMeta = function(modelClass, attributeNames, namePref
let attributeMap = map || new Map();
modelClass.eachAttribute((name, meta) => {
let fieldName = namePrefix ? namePrefix + name : name;
- if (meta.isFragment) {
+ let maybeFragment = Ember.get(modelClass, fieldName);
+ if (meta.isFragment && maybeFragment) {
// pass the fragment and all fields that start with
// the fragment name down to get extracted from the Fragment
expandAttributeMeta(
- Ember.get(modelClass, fieldName),
+ maybeFragment,
fields.filter(f => f.startsWith(fieldName)),
fieldName + '.',
attributeMap
@@ -60,13 +61,15 @@ export const expandAttributeMeta = function(modelClass, attributeNames, namePref
// so we'll replace each key in `fields` with the expanded meta
fields = fields.map(field => {
let meta = attributeMap.get(field);
- const { type, options } = meta;
+ if (meta) {
+ var { type, options } = meta;
+ }
return {
// using field name here because it is the full path,
// name on the attribute meta will be relative to the fragment it's on
name: field,
- type,
- options,
+ type: type,
+ options: options,
};
});
return fields;
diff --git a/ui/tests/acceptance/settings-test.js b/ui/tests/acceptance/settings-test.js
index 9f0345f722..fba94b0c82 100644
--- a/ui/tests/acceptance/settings-test.js
+++ b/ui/tests/acceptance/settings-test.js
@@ -1,5 +1,6 @@
import { test } from 'qunit';
import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance';
+import backendListPage from 'vault/tests/pages/secrets/backends';
moduleForAcceptance('Acceptance | settings', {
beforeEach() {
@@ -36,14 +37,19 @@ test('settings', function(assert) {
find('[data-test-flash-message]').text().trim(),
`Successfully mounted '${type}' at '${path}'!`
);
+ let row = backendListPage.rows().findByPath(path);
+ row.menu();
+ });
+
+ andThen(() => {
+ backendListPage.configLink();
});
- //show mount details
- click(`[data-test-secret-backend-row="${path}"] [data-test-secret-backend-detail]`);
andThen(() => {
assert.ok(
- find('[data-test-secret-backend-details="default-ttl"]').text().match(/100/),
- 'displays the input ttl'
+ currentURL(),
+ '/vault/secrets/${path}/configuration',
+ 'navigates to the config page'
);
});
});
diff --git a/ui/tests/acceptance/settings/mount-secret-backend-test.js b/ui/tests/acceptance/settings/mount-secret-backend-test.js
index fd9e85a8c3..2b761bf7c1 100644
--- a/ui/tests/acceptance/settings/mount-secret-backend-test.js
+++ b/ui/tests/acceptance/settings/mount-secret-backend-test.js
@@ -1,7 +1,7 @@
import { test } from 'qunit';
import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance';
import page from 'vault/tests/pages/settings/mount-secret-backend';
-import listPage from 'vault/tests/pages/secrets/backends';
+import configPage from 'vault/tests/pages/secrets/backend/configuration';
moduleForAcceptance('Acceptance | settings/mount-secret-backend', {
beforeEach() {
@@ -30,13 +30,9 @@ test('it sets the ttl corrects when mounting', function(assert) {
.maxTTLUnit('h')
.submit();
- listPage.visit();
+ configPage.visit({backend: path});
andThen(() => {
- listPage.links().findByPath(path).toggleDetails();
- });
- andThen(() => {
- const details = listPage.links().findByPath(path);
- assert.equal(details.defaultTTL, defaultTTLSeconds, 'shows the proper TTL');
- assert.equal(details.maxTTL, maxTTLSeconds, 'shows the proper max TTL');
+ assert.equal(configPage.defaultTTL, defaultTTLSeconds, 'shows the proper TTL');
+ assert.equal(configPage.maxTTL, maxTTLSeconds, 'shows the proper max TTL');
});
});
diff --git a/ui/tests/pages/secrets/backend/configuration.js b/ui/tests/pages/secrets/backend/configuration.js
new file mode 100644
index 0000000000..be630bc0e0
--- /dev/null
+++ b/ui/tests/pages/secrets/backend/configuration.js
@@ -0,0 +1,7 @@
+import { create, visitable, text } from 'ember-cli-page-object';
+
+export default create({
+ visit: visitable('/vault/secrets/:backend/configuration'),
+ defaultTTL: text('[data-test-row-value="Default Lease TTL"]'),
+ maxTTL: text('[data-test-row-value="Max Lease TTL"]'),
+});
diff --git a/ui/tests/pages/secrets/backends.js b/ui/tests/pages/secrets/backends.js
index 9e3ac18997..824539b4c7 100644
--- a/ui/tests/pages/secrets/backends.js
+++ b/ui/tests/pages/secrets/backends.js
@@ -1,17 +1,16 @@
-import { create, visitable, collection, text, clickable } from 'ember-cli-page-object';
+import { create, visitable, collection, clickable, text } from 'ember-cli-page-object';
export default create({
visit: visitable('/vault/secrets'),
- links: collection({
- itemScope: '[data-test-secret-backend-link]',
+ rows: collection({
+ itemScope: '[data-test-secret-backend-row]',
item: {
path: text('[data-test-secret-path]'),
- toggleDetails: clickable('[data-test-secret-backend-detail]'),
- defaultTTL: text('[data-test-secret-backend-details="default-ttl"]'),
- maxTTL: text('[data-test-secret-backend-details="max-ttl"]'),
+ menu: clickable('[data-test-popup-menu-trigger]'),
},
findByPath(path) {
return this.toArray().findBy('path', path + '/');
},
}),
+ configLink: clickable('[data-test-engine-config]'),
});