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
This commit is contained in:
Matthew Irish 2018-05-29 21:56:15 -05:00 committed by GitHub
parent 0e396cf4fe
commit 96c1e547f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 128 additions and 30 deletions

View File

@ -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'],
};

View File

@ -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',

View File

@ -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);
});
},
});

View File

@ -0,0 +1,2 @@
import IdentitySerializer from './_base';
export default IdentitySerializer.extend();

View File

@ -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' },
},

View File

@ -0,0 +1,2 @@
import IdentitySerializer from './_base';
export default IdentitySerializer.extend();

View File

@ -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' },
},

View File

@ -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;

View File

@ -13,9 +13,14 @@
glyph='role'
size=14
class="has-text-grey-light"
}}{{item.name}}</a>
<span class="tag">{{item.mountType}}</span>
<code class="has-text-grey is-size-8">{{item.mountAccessor}}</code>
}}<span class="has-text-weight-semibold">{{item.name}}</span></a>
<div class="has-text-grey">
{{item.id}}
</div>
<span class="tag"> {{item.mountType}} </span>
<span class="has-text-grey is-size-8">
{{item.mountAccessor}}
</span>
</div>
<div class="column has-text-right">
{{identity/popup-alias params=(array item)}}

View File

@ -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"
}}
<div class="columns is-mobile">
<div class="column is-10">
<a href={{href-to "vault.cluster.access.identity.show" "groups" gid "details" }}
class="is-block has-text-black has-text-weight-semibold"
>{{i-con
glyph='folder'
size=14
class="has-text-grey-light"
}}{{gid}}</a>
</div>
<div class="column has-text-right">
</div>
</div>
{{/linked-block}}
{{/each}}
{{else}}
<div class="box is-bottomless has-background-white-bis">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey">
This group has no parent groups.
</p>
</div>
</div>
</div>
</div>
{{/if}}

View File

@ -21,7 +21,14 @@
glyph="role"
size=14
class="has-text-grey-light"
}}<span class="has-text-weight-semibold">{{item.id}}</span></a>
}}<span class="has-text-weight-semibold">{{item.name}}</span></a>
<div class="has-text-grey">
{{item.id}}
</div>
<span class="tag"> {{item.mountType}} </span>
<span class="has-text-grey is-size-8">
{{item.mountAccessor}}
</span>
</div>
<div class="column has-text-right">
{{identity/popup-alias params=(array item) onSuccess=(action "onDelete")}}

View File

@ -9,7 +9,7 @@
data-test-identity-row=true
}}
<div class="columns is-mobile">
<div class="column is-10">
<div class="column is-7-tablet is-10-mobile">
<a href={{href-to
"vault.cluster.access.identity.show"
item.id
@ -21,7 +21,18 @@
glyph="role"
size=14
class="has-text-grey-light"
}}<span class="has-text-weight-semibold">{{item.id}}</span></a>
}}<span class="has-text-weight-semibold">{{item.name}}</span></a>
<div class="has-text-grey is-size-8">
{{item.id}}
</div>
</div>
<div class="column is-3 is-hidden-mobile">
{{#if (eq item.identityType "entity")}}
{{#if item.aliases.length}}
{{pluralize item.aliases.length "alias"}}
{{/if}}
{{else}}
{{/if}}
</div>
<div class="column has-text-right">
{{#popup-menu name="identity-item" onOpen=(action "reloadRecord" item)}}

View File

@ -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"}}
<a href={{href-to "vault.cluster.access.identity.show" (pluralize model.identityType) model.id tab }}>
{{capitalize tab}}
{{capitalize (humanize tab)}}
</a>
{{/link-to}}
{{/each}}

View File

@ -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();

View File

@ -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`
);

View File

@ -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]'),

View File

@ -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]'),