diff --git a/ui/public/file-error.svg b/ui/public/file-error.svg
new file mode 100644
index 000000000..97f8e1310
--- /dev/null
+++ b/ui/public/file-error.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/public/file-success.svg b/ui/public/file-success.svg
new file mode 100644
index 000000000..779273d51
--- /dev/null
+++ b/ui/public/file-success.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/stories/search-select.stories.js b/ui/stories/search-select.stories.js
deleted file mode 100644
index e7e08da91..000000000
--- a/ui/stories/search-select.stories.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import hbs from 'htmlbars-inline-precompile';
-import { storiesOf } from '@storybook/ember';
-import notes from './search-select.md';
-import { withKnobs, text, select } from '@storybook/addon-knobs';
-
-const onChange = (value) => alert(`New value is "${value}"`);
-const models = ["identity/groups"];
-
-storiesOf('SearchSelect/', module)
- .addParameters({ options: { showPanel: true } })
- .addDecorator(withKnobs({ escapeHTML: false }))
- .add(`SearchSelect`, () => ({
- template: hbs`
-
Search Select
-
- `,
- context: {
- label: text("Label", "Group IDs"),
- helpText: text("Help Tooltip Text", "Group IDs to associate with this entity"),
- inputValue: [],
- models: models,
- onChange: onChange,
- staticOptions: [{ name: "my-group", id: "123dsafdsarf" }, { name: "my-other-group", id: "45ssadd435" }, { name: "example-1", id: "5678" }, { name: "group-2", id: "gro09283" }],
- },
- }),
- { notes }
- );
diff --git a/ui/tests/integration/components/mount-filter-config-list-test.js b/ui/tests/integration/components/mount-filter-config-list-test.js
deleted file mode 100644
index 2ce407054..000000000
--- a/ui/tests/integration/components/mount-filter-config-list-test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render, click, findAll, fillIn, blur } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-import engineResolverFor from 'ember-engines/test-support/engine-resolver-for';
-const resolver = engineResolverFor('replication');
-
-module('Integration | Component | mount filter config list', function(hooks) {
- setupRenderingTest(hooks, { resolver });
-
- test('it renders', async function(assert) {
- this.set('config', { mode: 'whitelist', paths: [] });
- this.set('mounts', [{ path: 'userpass/', type: 'userpass', accessor: 'userpass' }]);
- await render(hbs`{{mount-filter-config-list config=config mounts=mounts}}`);
-
- assert.equal(findAll('#filter-userpass').length, 1);
- });
-
- test('it sets config.paths', async function(assert) {
- this.set('config', { mode: 'whitelist', paths: [] });
- this.set('mounts', [{ path: 'userpass/', type: 'userpass', accessor: 'userpass' }]);
- await render(hbs`{{mount-filter-config-list config=config mounts=mounts}}`);
-
- await click('#filter-userpass');
- assert.ok(this.get('config.paths').includes('userpass/'), 'adds to paths');
-
- await click('#filter-userpass');
- assert.equal(this.get('config.paths').length, 0, 'removes from paths');
- });
-
- test('it sets config.mode', async function(assert) {
- this.set('config', { mode: 'whitelist', paths: [] });
- await render(hbs`{{mount-filter-config-list config=config}}`);
- await fillIn('#filter-mode', 'blacklist');
- await blur('#filter-mode');
- assert.equal(this.get('config.mode'), 'blacklist');
- });
-});
diff --git a/ui/tests/integration/components/path-filter-config-list-test.js b/ui/tests/integration/components/path-filter-config-list-test.js
new file mode 100644
index 000000000..db89e61d6
--- /dev/null
+++ b/ui/tests/integration/components/path-filter-config-list-test.js
@@ -0,0 +1,116 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, click } from '@ember/test-helpers';
+import { typeInSearch, clickTrigger } from 'ember-power-select/test-support/helpers';
+import hbs from 'htmlbars-inline-precompile';
+import engineResolverFor from 'ember-engines/test-support/engine-resolver-for';
+import Service from '@ember/service';
+import sinon from 'sinon';
+import { Promise } from 'rsvp';
+import { create } from 'ember-cli-page-object';
+import ss from 'vault/tests/pages/components/search-select';
+
+const searchSelect = create(ss);
+
+const resolver = engineResolverFor('replication');
+
+const MOUNTS_RESPONSE = {
+ data: {
+ secret: {},
+ auth: {
+ 'userpass/': { type: 'userpass', accessor: 'userpass' },
+ },
+ },
+};
+const NAMESPACE_MOUNTS_RESPONSE = {
+ data: {
+ secret: {
+ 'namespace-kv/': { type: 'kv', accessor: 'kv' },
+ },
+ auth: {},
+ },
+};
+
+module('Integration | Component | path filter config list', function(hooks) {
+ setupRenderingTest(hooks, { resolver });
+ hooks.beforeEach(function() {
+ let ajaxStub = sinon.stub().usingPromise(Promise);
+ ajaxStub.withArgs('/v1/sys/internal/ui/mounts', 'GET').resolves(MOUNTS_RESPONSE);
+ ajaxStub
+ .withArgs('/v1/sys/internal/ui/mounts', 'GET', { namespace: 'ns1' })
+ .resolves(NAMESPACE_MOUNTS_RESPONSE);
+ this.set('ajaxStub', ajaxStub);
+ const namespaceServiceStub = Service.extend({
+ init() {
+ this._super(...arguments);
+ this.set('accessibleNamespaces', ['ns1']);
+ },
+ });
+
+ const storeServiceStub = Service.extend({
+ adapterFor() {
+ return {
+ ajax: ajaxStub,
+ };
+ },
+ });
+ this.owner.register('service:namespace', namespaceServiceStub);
+ this.owner.register('service:store', storeServiceStub);
+ });
+
+ test('it renders', async function(assert) {
+ this.set('config', { mode: null, paths: [] });
+ await render(hbs`
`);
+
+ assert.dom('[data-test-component=path-filter-config]').exists();
+ });
+
+ test('it sets config.paths', async function(assert) {
+ this.set('config', { mode: 'allow', paths: [] });
+ this.set('paths', []);
+ await render(hbs`
`);
+
+ await clickTrigger();
+ await typeInSearch('auth');
+ await searchSelect.options.objectAt(1).click();
+ assert.ok(this.config.paths.includes('auth/userpass/'), 'adds to paths');
+
+ await clickTrigger();
+ await assert.equal(searchSelect.options.length, 1, 'has one option left');
+
+ await searchSelect.deleteButtons.objectAt(0).click();
+ assert.equal(this.config.paths.length, 0, 'removes from paths');
+ await clickTrigger();
+ await assert.equal(searchSelect.options.length, 2, 'has both options');
+ });
+
+ test('it sets config.mode', async function(assert) {
+ this.set('config', { mode: 'allow', paths: [] });
+ await render(hbs`
`);
+ await click('#deny');
+ assert.equal(this.config.mode, 'deny');
+ await click('#no-filtering');
+ assert.equal(this.config.mode, null);
+ });
+
+ test('it shows a warning when going from a mode to allow all', async function(assert) {
+ this.set('config', { mode: 'allow', paths: [] });
+ await render(hbs`
`);
+ await click('#no-filtering');
+ assert.dom('[data-test-remove-warning]').exists('shows removal warning');
+ });
+
+ test('it fetches mounts from a namespace when namespace name is entered', async function(assert) {
+ this.set('config', { mode: 'allow', paths: [] });
+ this.set('paths', []);
+ await render(hbs`
`);
+
+ await clickTrigger();
+ assert.equal(searchSelect.options.length, 2, 'shows userpass and namespace as an option');
+ // type the namespace to trigger an ajax request
+ await typeInSearch('ns1');
+ assert.equal(searchSelect.options.length, 2, 'has ns and ns mount in the list');
+ await searchSelect.options.objectAt(1).click();
+ assert.ok(this.config.paths.includes('ns1/namespace-kv/'), 'adds namespace mount to paths');
+ });
+});