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:
parent
0e396cf4fe
commit
96c1e547f0
|
@ -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'],
|
||||
};
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
import IdentitySerializer from './_base';
|
||||
export default IdentitySerializer.extend();
|
|
@ -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' },
|
||||
},
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import IdentitySerializer from './_base';
|
||||
export default IdentitySerializer.extend();
|
|
@ -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' },
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)}}
|
||||
|
|
|
@ -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}}
|
|
@ -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")}}
|
||||
|
|
|
@ -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)}}
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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`
|
||||
);
|
||||
|
|
|
@ -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]'),
|
||||
|
|
|
@ -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]'),
|
||||
|
|
Loading…
Reference in New Issue