-
+
{{#if filterFocused}}
{{#if filterMatchesKey}}
@@ -33,6 +33,7 @@
Create scope
diff --git a/ui/scripts/gen-story-md.js b/ui/scripts/gen-story-md.js
index 6cdc5b3207..70cdfc66e6 100644
--- a/ui/scripts/gen-story-md.js
+++ b/ui/scripts/gen-story-md.js
@@ -1,5 +1,11 @@
#!/usr/bin/env node
/* eslint-disable */
+// run this script via yarn in the ui directory:
+// yarn gen-story-md some-component
+//
+// or if the story is for a component in an in-repo-addon or an engine:
+// yarn gen-story-md some-component name-of-engine
+
const fs = require('fs');
const jsdoc2md = require('jsdoc-to-markdown');
var args = process.argv.slice(2);
@@ -8,7 +14,8 @@ const addonOrEngine = args[1];
const inputFile = addonOrEngine
? `lib/${addonOrEngine}/addon/components/${name}.js`
: `app/components/${name}.js`;
-const outputFile = `stories/${name}.md`;
+const outputFile = addonOrEngine ? `lib/${addonOrEngine}/stories/${name}.md` : `stories/${name}.md`;
+
const component = name
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
diff --git a/ui/tests/acceptance/enterprise-kmip-test.js b/ui/tests/acceptance/enterprise-kmip-test.js
new file mode 100644
index 0000000000..cb617f1d80
--- /dev/null
+++ b/ui/tests/acceptance/enterprise-kmip-test.js
@@ -0,0 +1,233 @@
+import { currentURL, currentRouteName } from '@ember/test-helpers';
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { create } from 'ember-cli-page-object';
+
+import consoleClass from 'vault/tests/pages/components/console/ui-panel';
+import authPage from 'vault/tests/pages/auth';
+import scopesPage from 'vault/tests/pages/secrets/backend/kmip/scopes';
+import rolesPage from 'vault/tests/pages/secrets/backend/kmip/roles';
+import credentialsPage from 'vault/tests/pages/secrets/backend/kmip/credentials';
+import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
+
+const uiConsole = create(consoleClass);
+
+const mount = async (shouldConfig = true) => {
+ let path = `kmip-${Date.now()}`;
+ let commands = shouldConfig
+ ? [`write sys/mounts/${path} type=kmip`, `write ${path}/config -force`]
+ : [`write sys/mounts/${path} type=kmip`];
+ await uiConsole.runCommands(commands);
+ return path;
+};
+
+const createScope = async () => {
+ let path = await mount();
+ let scope = `scope-${Date.now()}`;
+ await uiConsole.runCommands([`write ${path}/scope/${scope} -force`]);
+ return { path, scope };
+};
+
+const createRole = async () => {
+ let { path, scope } = await createScope();
+ let role = `role-${Date.now()}`;
+ await uiConsole.runCommands([`write ${path}/scope/${scope}/role/${role} operation_all=true`]);
+ return { path, scope, role };
+};
+
+const generateCreds = async () => {
+ let { path, scope, role } = await createRole();
+ await uiConsole.runCommands([
+ `write ${path}/scope/${scope}/role/${role}/credential/generate format=pem
+ -field=serial_number`,
+ ]);
+ let serial = uiConsole.lastLogOutput;
+ return { path, scope, role, serial };
+};
+
+module('Acceptance | Enterprise | KMIP secrets', function(hooks) {
+ setupApplicationTest(hooks);
+
+ hooks.beforeEach(function() {
+ return authPage.login();
+ });
+
+ test('it enables KMIP secrets engine', async function(assert) {
+ let path = `kmip-${Date.now()}`;
+ await mountSecrets.enable('kmip', path);
+
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes`,
+ 'mounts and redirects to the kmip scopes page'
+ );
+ assert.ok(scopesPage.isEmpty, 'renders empty state');
+ });
+
+ test('it can configure a KMIP secrets engine', async function(assert) {
+ let path = await mount(false);
+ await scopesPage.visit({ backend: path });
+ await scopesPage.configurationLink();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/configuration`,
+ 'configuration navigates to the config page'
+ );
+ assert.ok(scopesPage.isEmpty, 'config page renders empty state');
+
+ await scopesPage.configureLink();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/configure`,
+ 'configuration navigates to the configure page'
+ );
+ await scopesPage.submit();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/configuration`,
+ 'redirects to configuration page after saving config'
+ );
+ assert.notOk(scopesPage.isEmpty, 'configuration page no longer renders empty state');
+ });
+
+ test('it can create a scope', async function(assert) {
+ let path = await mount(this);
+ await scopesPage.visit({ backend: path });
+ await scopesPage.createLink();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/create`,
+ 'navigates to the kmip scope create page'
+ );
+
+ // create scope
+ await scopesPage.scopeName('foo');
+ await scopesPage.submit();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes`,
+ 'navigates to the kmip scopes page after create'
+ );
+ assert.equal(scopesPage.listItemLinks.length, 1, 'renders a single scope');
+ });
+
+ test('it can delete a scope from the list', async function(assert) {
+ let { path } = await createScope(this);
+ await scopesPage.visit({ backend: path });
+ // delete the scope
+ await scopesPage.listItemLinks.objectAt(0).menuToggle();
+ await scopesPage.delete();
+ await scopesPage.confirmDelete();
+ assert.equal(scopesPage.listItemLinks.length, 0, 'no scopes');
+ assert.ok(scopesPage.isEmpty, 'renders the empty state');
+ });
+
+ test('it can create a role', async function(assert) {
+ let { path, scope } = await createScope(this);
+ let role = `role-${Date.now()}`;
+ await rolesPage.visit({ backend: path, scope });
+ assert.ok(rolesPage.isEmpty, 'renders the empty role page');
+ await rolesPage.create();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/${scope}/roles/create`,
+ 'links to the role create form'
+ );
+
+ await rolesPage.roleName(role);
+ await rolesPage.submit();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/${scope}/roles`,
+ 'redirects to roles list'
+ );
+
+ assert.equal(rolesPage.listItemLinks.length, 1, 'renders a single role');
+ });
+
+ test('it can delete a role from the list', async function(assert) {
+ let { path, scope } = await createRole();
+ await rolesPage.visit({ backend: path, scope });
+ // delete the role
+ await rolesPage.listItemLinks.objectAt(0).menuToggle();
+ await rolesPage.delete();
+ await rolesPage.confirmDelete();
+ assert.equal(rolesPage.listItemLinks.length, 0, 'renders no roles');
+ assert.ok(rolesPage.isEmpty, 'renders empty');
+ });
+
+ test('it can delete a role from the detail page', async function(assert) {
+ let { path, scope, role } = await createRole(this);
+ await rolesPage.visitDetail({ backend: path, scope, role });
+ await rolesPage.detailEditLink();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/edit`,
+ 'navigates to role edit'
+ );
+ await rolesPage.cancelLink();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}`,
+ 'cancel navigates to role show'
+ );
+ await rolesPage
+ .detailDelete()
+ .delete()
+ .confirmDelete();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/${scope}/roles`,
+ 'redirects to the roles list'
+ );
+ assert.ok(rolesPage.isEmpty, 'renders an empty roles page');
+ });
+
+ test('it can create a credential', async function(assert) {
+ let { path, scope, role } = await createRole();
+ await credentialsPage.visit({ backend: path, scope, role });
+ assert.ok(credentialsPage.isEmpty, 'renders empty creds page');
+ await credentialsPage.generateCredentialsLink();
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials/generate`,
+ 'navigates to generate credentials'
+ );
+ await credentialsPage.submit();
+ assert.equal(
+ currentRouteName(),
+ 'vault.cluster.secrets.backend.kmip.credentials.show',
+ 'generate redirects to the show page'
+ );
+ await credentialsPage.backToRoleLink();
+
+ assert.equal(credentialsPage.listItemLinks.length, 1, 'renders a single credential');
+ });
+
+ test('it can revoke a credential from the list', async function(assert) {
+ let { path, scope, role } = await generateCreds();
+ await credentialsPage.visit({ backend: path, scope, role });
+ // revoke the credentials
+ await credentialsPage.listItemLinks.objectAt(0).menuToggle();
+ await credentialsPage.delete();
+ await credentialsPage.confirmDelete();
+ assert.equal(credentialsPage.listItemLinks.length, 0, 'renders no credentials');
+ assert.ok(credentialsPage.isEmpty, 'renders empty');
+ });
+
+ test('it can revoke from the credentials show page', async function(assert) {
+ let { path, scope, role, serial } = await generateCreds();
+ await credentialsPage.visitDetail({ backend: path, scope, role, serial });
+ await credentialsPage
+ .detailRevoke()
+ .delete()
+ .confirmDelete();
+
+ assert.equal(
+ currentURL(),
+ `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials`,
+ 'redirects to the credentials list'
+ );
+ assert.ok(credentialsPage.isEmpty, 'renders an empty credentials page');
+ });
+});
diff --git a/ui/tests/pages/components/list-view.js b/ui/tests/pages/components/list-view.js
new file mode 100644
index 0000000000..90ef015f43
--- /dev/null
+++ b/ui/tests/pages/components/list-view.js
@@ -0,0 +1,23 @@
+import { text, isPresent, collection, clickable } from 'ember-cli-page-object';
+
+export default {
+ isEmpty: isPresent('[data-test-component="empty-state"]'),
+ listItemLinks: collection('[data-test-list-item-link]', {
+ text: text(),
+ click: clickable(),
+ menuToggle: clickable('[data-test-popup-menu-trigger]'),
+ }),
+ listItems: collection('[data-test-list-item]', {
+ text: text(),
+ menuToggle: clickable('[data-test-popup-menu-trigger]'),
+ }),
+ menuItems: collection('.ember-basic-dropdown-content li', {
+ testContainer: '#ember-testing',
+ }),
+ delete: clickable('[data-test-confirm-action-trigger]', {
+ testContainer: '#ember-testing',
+ }),
+ confirmDelete: clickable('[data-test-confirm-button]', {
+ testContainer: '#ember-testing',
+ }),
+};
diff --git a/ui/tests/pages/secrets/backend/kmip/credentials.js b/ui/tests/pages/secrets/backend/kmip/credentials.js
new file mode 100644
index 0000000000..fbb3a6cbd0
--- /dev/null
+++ b/ui/tests/pages/secrets/backend/kmip/credentials.js
@@ -0,0 +1,15 @@
+import { create, clickable, visitable } from 'ember-cli-page-object';
+import ListView from 'vault/tests/pages/components/list-view';
+
+export default create({
+ ...ListView,
+ visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials'),
+ visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials/:serial'),
+ create: clickable('[data-test-role-create]'),
+ credentialsLink: clickable('[data-test-kmip-link-credentials]'),
+ generateCredentialsLink: clickable('[data-test-kmip-link-generate-credentials]'),
+ roleDetailsLink: clickable('[data-test-kmip-link-role-details]'),
+ backToRoleLink: clickable('[data-test-kmip-link-back-to-role]'),
+ detailRevoke: clickable('[data-test-popup-menu-trigger]'),
+ submit: clickable('[data-test-edit-form-submit]'),
+});
diff --git a/ui/tests/pages/secrets/backend/kmip/roles.js b/ui/tests/pages/secrets/backend/kmip/roles.js
new file mode 100644
index 0000000000..aa96a1295c
--- /dev/null
+++ b/ui/tests/pages/secrets/backend/kmip/roles.js
@@ -0,0 +1,14 @@
+import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
+import ListView from 'vault/tests/pages/components/list-view';
+
+export default create({
+ ...ListView,
+ visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles'),
+ visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role'),
+ create: clickable('[data-test-role-create]'),
+ roleName: fillable('[data-test-input="name"]'),
+ submit: clickable('[data-test-edit-form-submit]'),
+ detailDelete: clickable('[data-test-popup-menu-trigger]'),
+ detailEditLink: clickable('[data-test-kmip-link-edit-role]'),
+ cancelLink: clickable('[data-test-edit-form-cancel]'),
+});
diff --git a/ui/tests/pages/secrets/backend/kmip/scopes.js b/ui/tests/pages/secrets/backend/kmip/scopes.js
new file mode 100644
index 0000000000..20506780c9
--- /dev/null
+++ b/ui/tests/pages/secrets/backend/kmip/scopes.js
@@ -0,0 +1,14 @@
+import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
+import ListView from 'vault/tests/pages/components/list-view';
+
+export default create({
+ ...ListView,
+ visit: visitable('/vault/secrets/:backend/kmip/scopes'),
+ visitCreate: visitable('/vault/secrets/:backend/kmip/scopes/create'),
+ createLink: clickable('[data-test-scope-create]'),
+ scopeName: fillable('[data-test-input="name"]'),
+ submit: clickable('[data-test-edit-form-submit]'),
+ configurationLink: clickable('[data-test-kmip-link-config]'),
+ configureLink: clickable('[data-test-kmip-link-configure]'),
+ scopesLink: clickable('[data-test-kmip-link-scopes]'),
+});