Filter Auth methods by name or type (#20747)
* glimmerize controller * search selects added and working * add test and cleanup disable * small fix on name filtering * add changelog * Add comment about individualized names * Update methods.js remove spaces
This commit is contained in:
parent
325c0dd1ac
commit
4180f56d73
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
ui: Add filtering by auth type and auth name to the Authentication Method list view.
|
||||||
|
```
|
|
@ -4,22 +4,74 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { task } from 'ember-concurrency';
|
import { dropTask } from 'ember-concurrency';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default class VaultClusterAccessMethodsController extends Controller {
|
||||||
flashMessages: service(),
|
@service flashMessages;
|
||||||
|
|
||||||
queryParams: {
|
@tracked authMethodOptions = [];
|
||||||
page: 'page',
|
@tracked selectedAuthType = null;
|
||||||
pageFilter: 'pageFilter',
|
@tracked selectedAuthName = null;
|
||||||
},
|
|
||||||
|
|
||||||
page: 1,
|
queryParams = ['page, pageFilter'];
|
||||||
pageFilter: null,
|
|
||||||
filter: null,
|
|
||||||
|
|
||||||
disableMethod: task(function* (method) {
|
page = 1;
|
||||||
|
pageFilter = null;
|
||||||
|
filter = null;
|
||||||
|
|
||||||
|
get authMethodList() {
|
||||||
|
// return an options list to filter by engine type, ex: 'kv'
|
||||||
|
if (this.selectedAuthType) {
|
||||||
|
// check first if the user has also filtered by name.
|
||||||
|
// names are individualized across type so you can't have the same name for an aws auth method and userpass.
|
||||||
|
// this means it's fine to filter by first type and then name or just name.
|
||||||
|
if (this.selectedAuthName) {
|
||||||
|
return this.model.filter((method) => this.selectedAuthName === method.id);
|
||||||
|
}
|
||||||
|
// otherwise filter by auth type
|
||||||
|
return this.model.filter((method) => this.selectedAuthType === method.type);
|
||||||
|
}
|
||||||
|
// return an options list to filter by auth name, ex: 'my-userpass'
|
||||||
|
if (this.selectedAuthName) {
|
||||||
|
return this.model.filter((method) => this.selectedAuthName === method.id);
|
||||||
|
}
|
||||||
|
// no filters, return full sorted list.
|
||||||
|
return this.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
get authMethodArrayByType() {
|
||||||
|
const arrayOfAllAuthTypes = this.authMethodList.map((modelObject) => modelObject.type);
|
||||||
|
// filter out repeated auth types (e.g. [userpass, userpass] => [userpass])
|
||||||
|
const arrayOfUniqueAuthTypes = [...new Set(arrayOfAllAuthTypes)];
|
||||||
|
|
||||||
|
return arrayOfUniqueAuthTypes.map((authType) => ({
|
||||||
|
name: authType,
|
||||||
|
id: authType,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
get authMethodArrayByName() {
|
||||||
|
return this.authMethodList.map((modelObject) => ({
|
||||||
|
name: modelObject.id,
|
||||||
|
id: modelObject.id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
filterAuthType([type]) {
|
||||||
|
this.selectedAuthType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
filterAuthName([name]) {
|
||||||
|
this.selectedAuthName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@dropTask
|
||||||
|
*disableMethod(method) {
|
||||||
const { type, path } = method;
|
const { type, path } = method;
|
||||||
try {
|
try {
|
||||||
yield method.destroyRecord();
|
yield method.destroyRecord();
|
||||||
|
@ -29,5 +81,5 @@ export default Controller.extend({
|
||||||
`There was an error disabling Auth Method at ${path}: ${err.errors.join(' ')}.`
|
`There was an error disabling Auth Method at ${path}: ${err.errors.join(' ')}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}).drop(),
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -6,19 +6,19 @@
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default Route.extend({
|
export default class VaultClusterAccessMethodsRoute extends Route {
|
||||||
store: service(),
|
@service store;
|
||||||
|
|
||||||
queryParams: {
|
queryParams = {
|
||||||
page: {
|
page: {
|
||||||
refreshModel: true,
|
refreshModel: true,
|
||||||
},
|
},
|
||||||
pageFilter: {
|
pageFilter: {
|
||||||
refreshModel: true,
|
refreshModel: true,
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return this.store.findAll('auth-method');
|
return this.store.findAll('auth-method');
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -7,6 +7,33 @@
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
<ToolbarFilters>
|
||||||
|
<SearchSelect
|
||||||
|
@id="filter-by-auth-type"
|
||||||
|
@options={{this.authMethodArrayByType}}
|
||||||
|
@selectLimit="1"
|
||||||
|
@disallowNewItems={{true}}
|
||||||
|
@fallbackComponent="input-search"
|
||||||
|
@onChange={{this.filterAuthType}}
|
||||||
|
@placeholder={{"Filter by auth type"}}
|
||||||
|
@displayInherit={{true}}
|
||||||
|
@inputValue={{if this.selectedAuthType (array this.selectedAuthType)}}
|
||||||
|
@disabled={{if this.selectedAuthName true false}}
|
||||||
|
class="is-marginless"
|
||||||
|
/>
|
||||||
|
<SearchSelect
|
||||||
|
@id="filter-by-auth-name"
|
||||||
|
@options={{this.authMethodArrayByName}}
|
||||||
|
@selectLimit="1"
|
||||||
|
@disallowNewItems={{true}}
|
||||||
|
@fallbackComponent="input-search"
|
||||||
|
@onChange={{this.filterAuthName}}
|
||||||
|
@placeholder={{"Filter by auth name"}}
|
||||||
|
@displayInherit={{true}}
|
||||||
|
@inputValue={{if this.selectedAuthName (array this.selectedAuthName)}}
|
||||||
|
class="is-marginless has-left-padding-s"
|
||||||
|
/>
|
||||||
|
</ToolbarFilters>
|
||||||
<ToolbarActions>
|
<ToolbarActions>
|
||||||
<ToolbarLink @route="vault.cluster.settings.auth.enable" @type="add" data-test-auth-enable>
|
<ToolbarLink @route="vault.cluster.settings.auth.enable" @type="add" data-test-auth-enable>
|
||||||
Enable new method
|
Enable new method
|
||||||
|
@ -14,7 +41,7 @@
|
||||||
</ToolbarActions>
|
</ToolbarActions>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
{{#each (sort-by "path" this.model) as |method|}}
|
{{#each (sort-by "path" this.authMethodList) as |method|}}
|
||||||
<LinkedBlock
|
<LinkedBlock
|
||||||
@params={{array "vault.cluster.access.method" method.id}}
|
@params={{array "vault.cluster.access.method" method.id}}
|
||||||
class="list-item-row"
|
class="list-item-row"
|
||||||
|
|
|
@ -4,22 +4,71 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { currentRouteName } from '@ember/test-helpers';
|
import { currentRouteName } from '@ember/test-helpers';
|
||||||
|
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupApplicationTest } from 'ember-qunit';
|
import { setupApplicationTest } from 'ember-qunit';
|
||||||
|
import { create } from 'ember-cli-page-object';
|
||||||
import page from 'vault/tests/pages/access/methods';
|
import page from 'vault/tests/pages/access/methods';
|
||||||
|
import authEnable from 'vault/tests/pages/settings/auth/enable';
|
||||||
import authPage from 'vault/tests/pages/auth';
|
import authPage from 'vault/tests/pages/auth';
|
||||||
|
import ss from 'vault/tests/pages/components/search-select';
|
||||||
|
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
||||||
|
|
||||||
module('Acceptance | /access/', function (hooks) {
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
const consoleComponent = create(consoleClass);
|
||||||
|
const searchSelect = create(ss);
|
||||||
|
|
||||||
|
module('Acceptance | auth-methods list view', function (hooks) {
|
||||||
setupApplicationTest(hooks);
|
setupApplicationTest(hooks);
|
||||||
|
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
|
this.uid = uuidv4();
|
||||||
return authPage.login();
|
return authPage.login();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it navigates', async function (assert) {
|
test('it navigates to auth method', async function (assert) {
|
||||||
await page.visit();
|
await page.visit();
|
||||||
assert.strictEqual(currentRouteName(), 'vault.cluster.access.methods', 'navigates to the correct route');
|
assert.strictEqual(currentRouteName(), 'vault.cluster.access.methods', 'navigates to the correct route');
|
||||||
assert.ok(page.methodsLink.isActive, 'the first link is active');
|
assert.ok(page.methodsLink.isActive, 'the first link is active');
|
||||||
assert.strictEqual(page.methodsLink.text, 'Authentication methods');
|
assert.strictEqual(page.methodsLink.text, 'Authentication methods');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it filters by name and auth type', async function (assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
const authPath1 = `userpass-1-${this.uid}`;
|
||||||
|
const authPath2 = `userpass-2-${this.uid}`;
|
||||||
|
const type = 'userpass';
|
||||||
|
await authEnable.visit();
|
||||||
|
await authEnable.enable(type, authPath1);
|
||||||
|
await authEnable.visit();
|
||||||
|
await authEnable.enable(type, authPath2);
|
||||||
|
await page.visit();
|
||||||
|
// filter by auth type
|
||||||
|
|
||||||
|
await clickTrigger('#filter-by-auth-type');
|
||||||
|
await searchSelect.options.objectAt(0).click();
|
||||||
|
|
||||||
|
const rows = document.querySelectorAll('[data-test-auth-backend-link]');
|
||||||
|
const rowsUserpass = Array.from(rows).filter((row) => row.innerText.includes('userpass'));
|
||||||
|
|
||||||
|
assert.strictEqual(rows.length, rowsUserpass.length, 'all rows returned are userpass');
|
||||||
|
|
||||||
|
// filter by name
|
||||||
|
await clickTrigger('#filter-by-auth-name');
|
||||||
|
const firstItemToSelect = searchSelect.options.objectAt(0).text;
|
||||||
|
await searchSelect.options.objectAt(0).click();
|
||||||
|
const singleRow = document.querySelectorAll('[data-test-auth-backend-link]');
|
||||||
|
|
||||||
|
assert.strictEqual(singleRow.length, 1, 'returns only one row');
|
||||||
|
assert.dom(singleRow[0]).includesText(firstItemToSelect, 'shows the filtered by auth name');
|
||||||
|
// clear filter by engine name
|
||||||
|
await searchSelect.deleteButtons.objectAt(1).click();
|
||||||
|
const rowsAgain = document.querySelectorAll('[data-test-auth-backend-link]');
|
||||||
|
assert.ok(rowsAgain.length > 1, 'filter has been removed');
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
await consoleComponent.runCommands([`delete sys/auth/${authPath1}`]);
|
||||||
|
await consoleComponent.runCommands([`delete sys/auth/${authPath2}`]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { currentRouteName, settled } from '@ember/test-helpers';
|
||||||
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
||||||
import { create } from 'ember-cli-page-object';
|
import { create } from 'ember-cli-page-object';
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands';
|
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
||||||
import { setupApplicationTest } from 'ember-qunit';
|
import { setupApplicationTest } from 'ember-qunit';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import backendsPage from 'vault/tests/pages/secrets/backends';
|
||||||
import authPage from 'vault/tests/pages/auth';
|
import authPage from 'vault/tests/pages/auth';
|
||||||
import ss from 'vault/tests/pages/components/search-select';
|
import ss from 'vault/tests/pages/components/search-select';
|
||||||
|
|
||||||
|
const consoleComponent = create(consoleClass);
|
||||||
const searchSelect = create(ss);
|
const searchSelect = create(ss);
|
||||||
|
|
||||||
module('Acceptance | secret-engine list view', function (hooks) {
|
module('Acceptance | secret-engine list view', function (hooks) {
|
||||||
|
@ -76,7 +77,7 @@ module('Acceptance | secret-engine list view', function (hooks) {
|
||||||
assert.dom(rowSupported[0]).hasClass('linked-block', `linked-block class is added to supported engines.`);
|
assert.dom(rowSupported[0]).hasClass('linked-block', `linked-block class is added to supported engines.`);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
await runCommands([`delete sys/mounts/${enginePath}`]);
|
await consoleComponent.runCommands([`delete sys/mounts/${enginePath}`]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it filters by name and engine type', async function (assert) {
|
test('it filters by name and engine type', async function (assert) {
|
||||||
|
@ -109,7 +110,7 @@ module('Acceptance | secret-engine list view', function (hooks) {
|
||||||
assert.ok(rowsAgain.length > 1, 'filter has been removed');
|
assert.ok(rowsAgain.length > 1, 'filter has been removed');
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
await runCommands([`delete sys/mounts/${enginePath1}`]);
|
await consoleComponent.runCommands([`delete sys/mounts/${enginePath1}`]);
|
||||||
await runCommands([`delete sys/mounts/${enginePath2}`]);
|
await consoleComponent.runCommands([`delete sys/mounts/${enginePath2}`]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue