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 { task } from 'ember-concurrency';
|
||||
import { dropTask } from 'ember-concurrency';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default Controller.extend({
|
||||
flashMessages: service(),
|
||||
export default class VaultClusterAccessMethodsController extends Controller {
|
||||
@service flashMessages;
|
||||
|
||||
queryParams: {
|
||||
page: 'page',
|
||||
pageFilter: 'pageFilter',
|
||||
},
|
||||
@tracked authMethodOptions = [];
|
||||
@tracked selectedAuthType = null;
|
||||
@tracked selectedAuthName = null;
|
||||
|
||||
page: 1,
|
||||
pageFilter: null,
|
||||
filter: null,
|
||||
queryParams = ['page, pageFilter'];
|
||||
|
||||
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;
|
||||
try {
|
||||
yield method.destroyRecord();
|
||||
|
@ -29,5 +81,5 @@ export default Controller.extend({
|
|||
`There was an error disabling Auth Method at ${path}: ${err.errors.join(' ')}.`
|
||||
);
|
||||
}
|
||||
}).drop(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,19 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Route.extend({
|
||||
store: service(),
|
||||
export default class VaultClusterAccessMethodsRoute extends Route {
|
||||
@service store;
|
||||
|
||||
queryParams: {
|
||||
queryParams = {
|
||||
page: {
|
||||
refreshModel: true,
|
||||
},
|
||||
pageFilter: {
|
||||
refreshModel: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
model() {
|
||||
return this.store.findAll('auth-method');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,33 @@
|
|||
</PageHeader>
|
||||
|
||||
<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>
|
||||
<ToolbarLink @route="vault.cluster.settings.auth.enable" @type="add" data-test-auth-enable>
|
||||
Enable new method
|
||||
|
@ -14,7 +41,7 @@
|
|||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
{{#each (sort-by "path" this.model) as |method|}}
|
||||
{{#each (sort-by "path" this.authMethodList) as |method|}}
|
||||
<LinkedBlock
|
||||
@params={{array "vault.cluster.access.method" method.id}}
|
||||
class="list-item-row"
|
||||
|
|
|
@ -4,22 +4,71 @@
|
|||
*/
|
||||
|
||||
import { currentRouteName } from '@ember/test-helpers';
|
||||
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
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 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);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.uid = uuidv4();
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
test('it navigates', async function (assert) {
|
||||
test('it navigates to auth method', async function (assert) {
|
||||
await page.visit();
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.access.methods', 'navigates to the correct route');
|
||||
assert.ok(page.methodsLink.isActive, 'the first link is active');
|
||||
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 { create } from 'ember-cli-page-object';
|
||||
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 { 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 ss from 'vault/tests/pages/components/search-select';
|
||||
|
||||
const consoleComponent = create(consoleClass);
|
||||
const searchSelect = create(ss);
|
||||
|
||||
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.`);
|
||||
|
||||
// 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) {
|
||||
|
@ -109,7 +110,7 @@ module('Acceptance | secret-engine list view', function (hooks) {
|
|||
assert.ok(rowsAgain.length > 1, 'filter has been removed');
|
||||
|
||||
// cleanup
|
||||
await runCommands([`delete sys/mounts/${enginePath1}`]);
|
||||
await runCommands([`delete sys/mounts/${enginePath2}`]);
|
||||
await consoleComponent.runCommands([`delete sys/mounts/${enginePath1}`]);
|
||||
await consoleComponent.runCommands([`delete sys/mounts/${enginePath2}`]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue