diff --git a/ui/core/app/components/form/role/add-principals/index.hbs b/ui/core/app/components/form/role/add-principals/index.hbs
new file mode 100644
index 000000000..d15f49bc3
--- /dev/null
+++ b/ui/core/app/components/form/role/add-principals/index.hbs
@@ -0,0 +1,66 @@
+{{#if this.hasAvailablePrincipals}}
+
+
+
+
+
+
+
+ {{t 'form.label.id'}}
+ {{t 'form.label.name'}}
+ {{t 'form.label.type'}}
+
+
+
+ {{#each @groups as |group|}}
+
+
+
+
+ {{group.name}}
+
+ {{t "resources.group"}}
+
+
+ {{/each}}
+ {{#each @users as |user|}}
+
+
+
+
+ {{user.name}}
+
+ {{t "resources.user"}}
+
+
+ {{/each}}
+
+
+
+
+{{/if}}
+
+{{#unless this.hasAvailablePrincipals}}
+
+
+
+ {{t "descriptions.no-principals"}}
+
+
+
+{{/unless}}
diff --git a/ui/core/app/components/form/role/add-principals/index.js b/ui/core/app/components/form/role/add-principals/index.js
new file mode 100644
index 000000000..34090a495
--- /dev/null
+++ b/ui/core/app/components/form/role/add-principals/index.js
@@ -0,0 +1,38 @@
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { A } from '@ember/array';
+
+export default class FormRoleAddPrincipalsComponent extends Component {
+
+ // =properties
+
+ /**
+ * Array of selected principal IDs.
+ * @type {EmberArray}
+ */
+ selectedPrincipalIDs = A();
+
+ /**
+ * True if there are users or groups not yet added to this role.
+ * TODO: this must be computed on users, groups, and role principals.
+ * @type {boolean}
+ */
+ hasAvailablePrincipals = true;
+
+ // =actions
+
+ @action
+ togglePrincipal(principal) {
+ if (!this.selectedPrincipalIDs.includes(principal.id)) {
+ this.selectedPrincipalIDs.addObject(principal.id);
+ } else {
+ this.selectedPrincipalIDs.removeObject(principal.id);
+ }
+ }
+
+ @action
+ submit(fn) {
+ fn(this.selectedPrincipalIDs);
+ }
+
+}
diff --git a/ui/core/app/routes/scopes/scope/roles/role/add-principals.js b/ui/core/app/routes/scopes/scope/roles/role/add-principals.js
index 1c4d8bbb8..b4854a825 100644
--- a/ui/core/app/routes/scopes/scope/roles/role/add-principals.js
+++ b/ui/core/app/routes/scopes/scope/roles/role/add-principals.js
@@ -1,9 +1,37 @@
import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+import { hash } from 'rsvp';
export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route {
+ // =services
+
+ @service intl;
+ @service notify;
+
// =methods
+ /**
+ * Emtpy out any previously loaded users and groups
+ * (collectively, principals).
+ */
+ beforeModel() {
+ this.store.unloadAll('user');
+ this.store.unloadAll('group');
+ }
+
+ model() {
+ const role = this.modelFor('scopes.scope.roles.role');
+ const { scopeID } = role;
+ const options = { adapterOptions: { scopeID } };
+ return hash({
+ role,
+ users: this.store.findAll('user', options),
+ groups: this.store.findAll('group', options)
+ });
+ }
+
/**
* Empties the actions and navigation outlets and renders a custom header.
* @override
@@ -26,4 +54,30 @@ export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route {
outlet: 'navigation'
});
}
+
+ /**
+ * Saves principal IDs into the role via the API.
+ * @param {RoleModel} role
+ * @param {[string]} principalIDs
+ */
+ @action
+ async addPrincipals(role, principalIDs) {
+ try {
+ await role.addPrincipals(principalIDs);
+ this.replaceWith('scopes.scope.roles.role.principals');
+ this.notify.success(this.intl.t('notify.save-success'));
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+
+ /**
+ * Redirect to role principals as if nothing ever happened.
+ */
+ @action
+ cancel() {
+ this.replaceWith('scopes.scope.roles.role.principals');
+ }
+
}
diff --git a/ui/core/app/templates/scopes/scope/roles/role/add-principals.hbs b/ui/core/app/templates/scopes/scope/roles/role/add-principals.hbs
index cf8fdc119..5110dbec0 100644
--- a/ui/core/app/templates/scopes/scope/roles/role/add-principals.hbs
+++ b/ui/core/app/templates/scopes/scope/roles/role/add-principals.hbs
@@ -1,3 +1,9 @@
{{title (t "actions.add-principals")}}
-{{outlet}}
+
diff --git a/ui/core/mirage/generated/factories/role.js b/ui/core/mirage/generated/factories/role.js
index 6265c7116..13e83aad3 100644
--- a/ui/core/mirage/generated/factories/role.js
+++ b/ui/core/mirage/generated/factories/role.js
@@ -12,5 +12,6 @@ export default Factory.extend({
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
+ version: () => random.number(),
});
diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js
index 5fc89b42e..07f55d080 100644
--- a/ui/core/tests/acceptance/roles-test.js
+++ b/ui/core/tests/acceptance/roles-test.js
@@ -237,4 +237,20 @@ module('Acceptance | roles', function (hooks) {
test('new grants can be added and saved or canceled', async function (assert) {
assert.expect(0);
});
+
+ test('viewing role principals', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('can delete a principal', async function(assert) {
+ assert.expect(0);
+ });
+
+ test('select and save principals to add', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('select and cancel principals to add', async function (assert) {
+ assert.expect(0);
+ });
});
diff --git a/ui/core/tests/integration/components/form/role/add-principals-test.js b/ui/core/tests/integration/components/form/role/add-principals-test.js
new file mode 100644
index 000000000..1e155a103
--- /dev/null
+++ b/ui/core/tests/integration/components/form/role/add-principals-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+// import { render } from '@ember/test-helpers';
+// import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | form/role/add-principals', function(hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function(assert) {
+ assert.expect(0);
+ });
+});