feat: 🎸 add principals list tab
This commit is contained in:
16 files changed
+201
-4
No files matched your search
@@ -0,0 +1,20 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
export default class FragmentPrincipal extends Fragment {
|
||||
|
||||
// =attributes
|
||||
|
||||
@attr('string', {
|
||||
description: 'ID of the related scope'
|
||||
}) scope_id;
|
||||
|
||||
@attr('string', {
|
||||
description: 'ID of the related principal (user or group)'
|
||||
}) principal_id;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Type of the related principal (user or group)'
|
||||
}) type;
|
||||
|
||||
}
|
||||
@@ -5,6 +5,15 @@ export default class RoleModel extends GeneratedRoleModel {
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Principals are users and groups assigned to a role. They are represented
|
||||
* as references to User and Group instances. Since Ember Data relationships
|
||||
* are wanting, we do not model these as a polymorphic relationship as might
|
||||
* see obvious. Instead, the application layer is expected to load referenced
|
||||
* users and groups as needed.
|
||||
*/
|
||||
@fragmentArray('fragment-principal', {readOnly: true}) principals;
|
||||
|
||||
/**
|
||||
* Grants is read-only _most_ under normal circumstances. But grants can
|
||||
* be persisted via a dedicated call to `saveGrants()`.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import JSONSerializer from '@ember-data/serializer/json';
|
||||
|
||||
export default class FragmentPrincipalSerializer extends JSONSerializer {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Renames `id` to `principal_id` because fragments may not have IDs.
|
||||
* Otherwise normalizes as normal.
|
||||
* @param {ModelClass} typeClass
|
||||
* @param {object} resourceHash
|
||||
* @return {object}
|
||||
*/
|
||||
normalize(typeClass, { id: principal_id, ...obj }) {
|
||||
return super.normalize(typeClass, { principal_id, ...obj });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/fragment-principal';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/fragment-principal';
|
||||
@@ -0,0 +1,13 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | fragment principal', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('fragment-principal', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import FragmentPrincipal from 'api/models/fragment-principal';
|
||||
|
||||
module('Unit | Serializer | fragment principal', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it normalizes `id` into `principal_id`', function(assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('fragment-principal');
|
||||
const normalized = serializer.normalize(FragmentPrincipal, {
|
||||
scope_id: '1',
|
||||
principal_id: '2',
|
||||
type: 'user'
|
||||
});
|
||||
assert.deepEqual(normalized, {
|
||||
data: {
|
||||
attributes: {
|
||||
scope_id: '1',
|
||||
principal_id: '2',
|
||||
type: 'user'
|
||||
},
|
||||
id: null,
|
||||
relationships: {},
|
||||
type: 'fragment-principal'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,6 +23,7 @@ Router.map(function () {
|
||||
this.route('roles', function () {
|
||||
this.route('role', { path: ':role_id' }, function () {
|
||||
this.route('grants');
|
||||
this.route('principals');
|
||||
});
|
||||
this.route('new');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { all } from 'rsvp';
|
||||
|
||||
export default class ScopesScopeRolesRolePrincipalsRoute extends Route {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Returns users and groups associated with this role.
|
||||
* @param {object} params
|
||||
* @return {Promise{RoleModel}}
|
||||
*/
|
||||
model() {
|
||||
const adapterOptions = { scopeID: this.modelFor('scopes.scope').id };
|
||||
const role = this.modelFor('scopes.scope.roles.role');
|
||||
const users = role.principals
|
||||
.filterBy('type', 'user')
|
||||
.map(({ principal_id }) =>
|
||||
this.store.findRecord('user', principal_id, { adapterOptions })
|
||||
.then(model => ({
|
||||
type: 'user',
|
||||
model
|
||||
}))
|
||||
);
|
||||
const groups = role.principals
|
||||
.filterBy('type', 'group')
|
||||
.map(({ principal_id }) =>
|
||||
this.store.findRecord('group', principal_id, { adapterOptions })
|
||||
.then(model => ({
|
||||
type: 'group',
|
||||
model
|
||||
}))
|
||||
);
|
||||
return all(users.concat(groups));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
<nav.link @route='scopes.scope.roles.role.index'>
|
||||
{{t 'titles.settings'}}
|
||||
</nav.link>
|
||||
<nav.link @route='scopes.scope.roles.role.principals'>
|
||||
{{t 'titles.principals'}}
|
||||
</nav.link>
|
||||
<nav.link @route='scopes.scope.roles.role.grants'>
|
||||
{{t 'titles.grants'}}
|
||||
</nav.link>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{{title (t 'titles.principals')}}
|
||||
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.cell>{{t 'form.label.name'}}</row.cell>
|
||||
<row.cell>{{t 'form.label.type'}}</row.cell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model as |principal|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
{{principal.model.displayName}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
{{t (concat 'resources.' principal.type)}}
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
@@ -1,4 +1,5 @@
|
||||
import factory from '../generated/factories/role';
|
||||
import { trait } from 'ember-cli-mirage';
|
||||
|
||||
export default factory.extend({
|
||||
|
||||
@@ -8,4 +9,29 @@ export default factory.extend({
|
||||
'id=*;type=host-catalog;actions=create,read'
|
||||
],
|
||||
|
||||
/**
|
||||
* Create roles with associated "principals": related users and groups.
|
||||
*/
|
||||
withPrincipals: trait({
|
||||
afterCreate(role, server) {
|
||||
const users = server.createList('user', 2);
|
||||
const groups = server.createList('group', 2);
|
||||
const userPrincipalFragments = users.map(user => ({
|
||||
scope_id: role.scope.id,
|
||||
id: user.id,
|
||||
type: 'user'
|
||||
}));
|
||||
const groupPrincipalFragments = groups.map(group => ({
|
||||
scope_id: role.scope.id,
|
||||
id: group.id,
|
||||
type: 'group'
|
||||
}));
|
||||
const principals = [
|
||||
...userPrincipalFragments,
|
||||
...groupPrincipalFragments
|
||||
];
|
||||
role.update({ principals });
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -18,11 +18,10 @@ export default function(server) {
|
||||
|
||||
// User
|
||||
server.createList('user', 5, { scope });
|
||||
|
||||
// Role
|
||||
server.createList('role', 5, { scope });
|
||||
// Groups
|
||||
// Group
|
||||
server.createList('group', 5, { scope });
|
||||
// Role
|
||||
server.createList('role', 5, { scope }, 'withPrincipals');
|
||||
|
||||
// Other resources
|
||||
|
||||
|
||||
@@ -218,6 +218,10 @@ module('Acceptance | roles', function (hooks) {
|
||||
);
|
||||
});
|
||||
|
||||
test('principals may be viewed in principals tab', async function (assert) {
|
||||
assert.expect(0);
|
||||
});
|
||||
|
||||
test('grants can be updated and saved or canceled', async function (assert) {
|
||||
assert.expect(0);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | scopes/scope/roles/role/principals', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:scopes/scope/roles/role/principals');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -47,6 +47,7 @@ titles:
|
||||
groups-welcome: Welcome to Groups
|
||||
grants: Permissions
|
||||
new-grant: New Grant
|
||||
principals: Principals
|
||||
descriptions:
|
||||
host-catalogs: Overview of collections of hosts and host sets.
|
||||
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
||||
@@ -69,6 +70,7 @@ form:
|
||||
identification: Username
|
||||
password: Password
|
||||
grant: Grant
|
||||
principal: Principal
|
||||
errors:
|
||||
titles:
|
||||
unauthenticated-error: You are not signed in
|
||||
|
||||
Reference in new issue
Block a user