From 96c1e547f027ac6560c5aff652638f1325e13702 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 29 May 2018 21:56:15 -0500 Subject: [PATCH] UI identity lists (#4655) * add new key_info to the list models for identity endpoints * add details to group and show pages * add parent groups to group tabs * render alias the same everywhere * space tab subnav more like the designs * fix tests * pull tabs in and remove padding --- ui/app/helpers/tabs-for-identity-show.js | 6 +-- ui/app/models/identity/group.js | 10 +++++ ui/app/serializers/identity/_base.js | 27 ++++++++++++++ ui/app/serializers/identity/entity-alias.js | 2 + ui/app/serializers/identity/entity.js | 4 +- ui/app/serializers/identity/group-alias.js | 2 + ui/app/serializers/identity/group.js | 4 +- ui/app/styles/components/sub-nav.scss | 6 ++- .../components/identity/item-aliases.hbs | 11 ++++-- .../identity/item-parent-groups.hbs | 37 +++++++++++++++++++ .../cluster/access/identity/aliases/index.hbs | 9 ++++- .../vault/cluster/access/identity/index.hbs | 15 +++++++- .../vault/cluster/access/identity/show.hbs | 2 +- .../access/identity/_shared-alias-tests.js | 4 +- .../access/identity/_shared-tests.js | 15 ++------ .../pages/access/identity/aliases/index.js | 2 +- ui/tests/pages/access/identity/index.js | 2 +- 17 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 ui/app/serializers/identity/_base.js create mode 100644 ui/app/serializers/identity/entity-alias.js create mode 100644 ui/app/serializers/identity/group-alias.js create mode 100644 ui/app/templates/components/identity/item-parent-groups.hbs diff --git a/ui/app/helpers/tabs-for-identity-show.js b/ui/app/helpers/tabs-for-identity-show.js index a1c808ca1..ced7696cf 100644 --- a/ui/app/helpers/tabs-for-identity-show.js +++ b/ui/app/helpers/tabs-for-identity-show.js @@ -4,9 +4,9 @@ export const TABS = { entity: ['details', 'aliases', 'policies', 'groups', 'metadata'], 'entity-alias': ['details', 'metadata'], //group will be used in the model hook of the route - group: ['details', 'aliases', 'policies', 'members', 'metadata'], - 'group-internal': ['details', 'policies', 'members', 'metadata'], - 'group-external': ['details', 'aliases', 'policies', 'members', 'metadata'], + group: ['details', 'aliases', 'policies', 'members', 'parent-groups', 'metadata'], + 'group-internal': ['details', 'policies', 'members', 'parent-groups', 'metadata'], + 'group-external': ['details', 'aliases', 'policies', 'members', 'parent-groups', 'metadata'], 'group-alias': ['details'], }; diff --git a/ui/app/models/identity/group.js b/ui/app/models/identity/group.js index 47483dd52..d8f3bb275 100644 --- a/ui/app/models/identity/group.js +++ b/ui/app/models/identity/group.js @@ -26,6 +26,12 @@ export default IdentityModel.extend({ lastUpdateTime: attr('string', { readOnly: true, }), + numMemberEntities: attr('number', { + readOnly: true + }), + numParentGroups: attr('number', { + readOnly: true + }), metadata: attr('object', { editType: 'kv', }), @@ -36,6 +42,10 @@ export default IdentityModel.extend({ label: 'Member Group IDs', editType: 'stringArray', }), + parentGroupIds: attr({ + label: 'Parent Group IDs', + editType: 'stringArray', + }), memberEntityIds: attr({ label: 'Member Entity IDs', editType: 'stringArray', diff --git a/ui/app/serializers/identity/_base.js b/ui/app/serializers/identity/_base.js new file mode 100644 index 000000000..217d13331 --- /dev/null +++ b/ui/app/serializers/identity/_base.js @@ -0,0 +1,27 @@ +import ApplicationSerializer from '../application'; +import Ember from 'ember'; + +export default ApplicationSerializer.extend({ + normalizeItems(payload) { + if (payload.data.keys && Array.isArray(payload.data.keys)) { + return payload.data.keys; + } + Ember.assign(payload, payload.data); + delete payload.data; + return payload; + }, + + extractLazyPaginatedData(payload) { + let list; + list = payload.data.keys.map(key => { + let model = payload.data.key_info[key]; + model.id = key; + return model; + }); + delete payload.data.key_info; + return list.sort((a,b) => { + return a.name.localeCompare(b.name); + }); + }, + +}); diff --git a/ui/app/serializers/identity/entity-alias.js b/ui/app/serializers/identity/entity-alias.js new file mode 100644 index 000000000..c7246dba8 --- /dev/null +++ b/ui/app/serializers/identity/entity-alias.js @@ -0,0 +1,2 @@ +import IdentitySerializer from './_base'; +export default IdentitySerializer.extend(); diff --git a/ui/app/serializers/identity/entity.js b/ui/app/serializers/identity/entity.js index 03aee2693..4ff22baac 100644 --- a/ui/app/serializers/identity/entity.js +++ b/ui/app/serializers/identity/entity.js @@ -1,7 +1,7 @@ import DS from 'ember-data'; -import ApplicationSerializer from '../application'; +import IdentitySerializer from './_base'; -export default ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, { +export default IdentitySerializer.extend(DS.EmbeddedRecordsMixin, { attrs: { aliases: { embedded: 'always' }, }, diff --git a/ui/app/serializers/identity/group-alias.js b/ui/app/serializers/identity/group-alias.js new file mode 100644 index 000000000..c7246dba8 --- /dev/null +++ b/ui/app/serializers/identity/group-alias.js @@ -0,0 +1,2 @@ +import IdentitySerializer from './_base'; +export default IdentitySerializer.extend(); diff --git a/ui/app/serializers/identity/group.js b/ui/app/serializers/identity/group.js index c2df2fdf8..37cb633f2 100644 --- a/ui/app/serializers/identity/group.js +++ b/ui/app/serializers/identity/group.js @@ -1,7 +1,7 @@ import DS from 'ember-data'; -import ApplicationSerializer from '../application'; +import IdentitySerializer from './_base'; -export default ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, { +export default IdentitySerializer.extend(DS.EmbeddedRecordsMixin, { attrs: { alias: { embedded: 'always' }, }, diff --git a/ui/app/styles/components/sub-nav.scss b/ui/app/styles/components/sub-nav.scss index 9c336fa77..630ac3398 100644 --- a/ui/app/styles/components/sub-nav.scss +++ b/ui/app/styles/components/sub-nav.scss @@ -14,13 +14,17 @@ border-color: $blue; color: $blue; } + &:first-child a { + margin-left: $size-5; + } } a { color: $grey-dark; font-weight: $font-weight-semibold; text-decoration: none; - padding: $size-6 $size-8 $size-8; + padding: $size-6 0; + margin: 0 $size-4; border-bottom: 2px solid transparent; transition: border-color $speed; diff --git a/ui/app/templates/components/identity/item-aliases.hbs b/ui/app/templates/components/identity/item-aliases.hbs index afe3f7cdd..c2aa2c68d 100644 --- a/ui/app/templates/components/identity/item-aliases.hbs +++ b/ui/app/templates/components/identity/item-aliases.hbs @@ -13,9 +13,14 @@ glyph='role' size=14 class="has-text-grey-light" - }}{{item.name}} - {{item.mountType}} - {{item.mountAccessor}} + }}{{item.name}} +
+ {{item.id}} +
+ {{item.mountType}} + + {{item.mountAccessor}} +
{{identity/popup-alias params=(array item)}} diff --git a/ui/app/templates/components/identity/item-parent-groups.hbs b/ui/app/templates/components/identity/item-parent-groups.hbs new file mode 100644 index 000000000..c94e854b1 --- /dev/null +++ b/ui/app/templates/components/identity/item-parent-groups.hbs @@ -0,0 +1,37 @@ +{{#if model.parentGroupIds.length}} + {{#each model.parentGroupIds as |gid|}} + {{#linked-block + "vault.cluster.access.identity.show" + "groups" + gid + details + class="box is-sideless is-marginless" + }} + + {{/linked-block}} + {{/each}} +{{else}} +
+
+
+
+

+ This group has no parent groups. +

+
+
+
+
+{{/if}} diff --git a/ui/app/templates/vault/cluster/access/identity/aliases/index.hbs b/ui/app/templates/vault/cluster/access/identity/aliases/index.hbs index f75ba0d43..176195ce3 100644 --- a/ui/app/templates/vault/cluster/access/identity/aliases/index.hbs +++ b/ui/app/templates/vault/cluster/access/identity/aliases/index.hbs @@ -21,7 +21,14 @@ glyph="role" size=14 class="has-text-grey-light" - }}{{item.id}} + }}{{item.name}} +
+ {{item.id}} +
+ {{item.mountType}} + + {{item.mountAccessor}} +
{{identity/popup-alias params=(array item) onSuccess=(action "onDelete")}} diff --git a/ui/app/templates/vault/cluster/access/identity/index.hbs b/ui/app/templates/vault/cluster/access/identity/index.hbs index ff751dabd..eeefdde49 100644 --- a/ui/app/templates/vault/cluster/access/identity/index.hbs +++ b/ui/app/templates/vault/cluster/access/identity/index.hbs @@ -9,7 +9,7 @@ data-test-identity-row=true }}
-
+
{{item.id}} + }}{{item.name}} +
+ {{item.id}} +
+
+
+ {{#if (eq item.identityType "entity")}} + {{#if item.aliases.length}} + {{pluralize item.aliases.length "alias"}} + {{/if}} + {{else}} + {{/if}}
{{#popup-menu name="identity-item" onOpen=(action "reloadRecord" item)}} diff --git a/ui/app/templates/vault/cluster/access/identity/show.hbs b/ui/app/templates/vault/cluster/access/identity/show.hbs index ee8385c7e..e16ccdb62 100644 --- a/ui/app/templates/vault/cluster/access/identity/show.hbs +++ b/ui/app/templates/vault/cluster/access/identity/show.hbs @@ -35,7 +35,7 @@ {{#each (tabs-for-identity-show model.identityType model.type) as |tab|}} {{#link-to "vault.cluster.access.identity.show" model.id tab tagName="li"}} - {{capitalize tab}} + {{capitalize (humanize tab)}} {{/link-to}} {{/each}} diff --git a/ui/tests/acceptance/access/identity/_shared-alias-tests.js b/ui/tests/acceptance/access/identity/_shared-alias-tests.js index 7f4440bc7..3c592e484 100644 --- a/ui/tests/acceptance/access/identity/_shared-alias-tests.js +++ b/ui/tests/acceptance/access/identity/_shared-alias-tests.js @@ -38,11 +38,11 @@ export const testAliasCRUD = (name, itemType, assert) => { aliasIndexPage.visit({ item_type: itemType }); andThen(() => { assert.equal( - aliasIndexPage.items.filterBy('id', aliasID).length, + aliasIndexPage.items.filterBy('name', name).length, 1, `${itemType}: lists the entity in the entity list` ); - aliasIndexPage.items.filterBy('id', aliasID)[0].menu(); + aliasIndexPage.items.filterBy('name', name)[0].menu(); }); aliasIndexPage.delete().confirmDelete(); diff --git a/ui/tests/acceptance/access/identity/_shared-tests.js b/ui/tests/acceptance/access/identity/_shared-tests.js index b7fe3de59..e1a8371ef 100644 --- a/ui/tests/acceptance/access/identity/_shared-tests.js +++ b/ui/tests/acceptance/access/identity/_shared-tests.js @@ -3,12 +3,9 @@ import showPage from 'vault/tests/pages/access/identity/show'; import indexPage from 'vault/tests/pages/access/identity/index'; export const testCRUD = (name, itemType, assert) => { - let id; page.visit({ item_type: itemType }); page.editForm.name(name).submit(); andThen(() => { - let idRow = showPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0]; - id = idRow.rowValue; assert.equal( currentRouteName(), 'vault.cluster.access.identity.show', @@ -26,16 +23,16 @@ export const testCRUD = (name, itemType, assert) => { indexPage.visit({ item_type: itemType }); andThen(() => { assert.equal( - indexPage.items.filterBy('id', id).length, + indexPage.items.filterBy('name', name).length, 1, `${itemType}: lists the entity in the entity list` ); - indexPage.items.filterBy('id', id)[0].menu(); + indexPage.items.filterBy('name', name)[0].menu(); }); indexPage.delete().confirmDelete(); andThen(() => { - assert.equal(indexPage.items.filterBy('id', id).length, 0, `${itemType}: the row is deleted`); + assert.equal(indexPage.items.filterBy('name', name).length, 0, `${itemType}: the row is deleted`); indexPage.flashMessage.latestMessage.startsWith( 'Successfully deleted', `${itemType}: shows flash message` @@ -44,12 +41,8 @@ export const testCRUD = (name, itemType, assert) => { }; export const testDeleteFromForm = (name, itemType, assert) => { - let id; page.visit({ item_type: itemType }); page.editForm.name(name).submit(); - andThen(() => { - id = showPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0].rowValue; - }); showPage.edit(); andThen(() => { assert.equal( @@ -66,7 +59,7 @@ export const testDeleteFromForm = (name, itemType, assert) => { `${itemType}: navigates to list page on delete` ); assert.equal( - indexPage.items.filterBy('id', id).length, + indexPage.items.filterBy('name', name).length, 0, `${itemType}: the row does not show in the list` ); diff --git a/ui/tests/pages/access/identity/aliases/index.js b/ui/tests/pages/access/identity/aliases/index.js index 297dbeab8..9a67c169e 100644 --- a/ui/tests/pages/access/identity/aliases/index.js +++ b/ui/tests/pages/access/identity/aliases/index.js @@ -6,7 +6,7 @@ export default create({ flashMessage, items: collection('[data-test-identity-row]', { menu: clickable('[data-test-popup-menu-trigger]'), - id: text('[data-test-identity-link]'), + name: text('[data-test-identity-link]'), }), delete: clickable('[data-test-item-delete] [data-test-confirm-action-trigger]'), confirmDelete: clickable('[data-test-item-delete] [data-test-confirm-button]'), diff --git a/ui/tests/pages/access/identity/index.js b/ui/tests/pages/access/identity/index.js index 4a79ea39f..7a24086b9 100644 --- a/ui/tests/pages/access/identity/index.js +++ b/ui/tests/pages/access/identity/index.js @@ -6,7 +6,7 @@ export default create({ flashMessage, items: collection('[data-test-identity-row]', { menu: clickable('[data-test-popup-menu-trigger]'), - id: text('[data-test-identity-link]'), + name: text('[data-test-identity-link]'), }), delete: clickable('[data-test-item-delete] [data-test-confirm-action-trigger]'), confirmDelete: clickable('[data-test-item-delete] [data-test-confirm-button]'),