diff --git a/ui/core/app/router.js b/ui/core/app/router.js
index 0e7e5f7f7..accba55a0 100644
--- a/ui/core/app/router.js
+++ b/ui/core/app/router.js
@@ -21,6 +21,10 @@ Router.map(function () {
this.route('group', { path: ':group_id' }, function () {});
this.route('new');
});
+ this.route('roles', function () {
+ this.route('role', { path: ':role_id' }, function () {});
+ this.route('new');
+ });
});
});
diff --git a/ui/core/app/routes/scopes/scope/roles.js b/ui/core/app/routes/scopes/scope/roles.js
new file mode 100644
index 000000000..fd451c719
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/roles.js
@@ -0,0 +1,72 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+
+export default class ScopesScopeRolesRoute extends Route {
+ // =services
+
+ @service intl;
+ @service notify;
+
+ // =methods
+
+ /**
+ * Load all roles under current scope.
+ * @return {Promise{[RoleModel]}}
+ */
+ async model() {
+ return this.store.findAll('role', this.scopeAdapterOptions());
+ }
+
+ // =actions
+ /**
+ * Rollback changes on an role.
+ * @param {RoleModel} role
+ */
+ @action
+ cancel(role) {
+ const { isNew } = role;
+ role.rollbackAttributes();
+ if (isNew) this.transitionTo('scopes.scope.roles');
+ }
+
+ /**
+ * Save an role in current scope.
+ * @param {RoleModel} role
+ */
+ @action
+ async save(role) {
+ try {
+ await role.save(this.scopeAdapterOptions());
+ this.refresh();
+ this.notify.success(this.intl.t('notify.save-success'));
+ this.transitionTo('scopes.scope.roles.role', role);
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+
+ /**
+ * Delete role in current scope and redirect to index.
+ * @param {RoleModel} role
+ */
+ @action
+ async delete(role) {
+ try {
+ await role.destroyRecord(this.scopeAdapterOptions());
+ this.refresh();
+ this.notify.success(this.intl.t('notify.role.delete-success'));
+ this.transitionTo('scopes.scope.roles');
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+
+ scopeAdapterOptions() {
+ const { id: scope_id } = this.modelFor('scopes.scope');
+ const adapterOptions = { scopeID: scope_id };
+ return { adapterOptions };
+ }
+}
diff --git a/ui/core/app/routes/scopes/scope/roles/index.js b/ui/core/app/routes/scopes/scope/roles/index.js
new file mode 100644
index 000000000..f2a8d7af1
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/roles/index.js
@@ -0,0 +1,3 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeRolesIndexRoute extends Route {}
diff --git a/ui/core/app/routes/scopes/scope/roles/new.js b/ui/core/app/routes/scopes/scope/roles/new.js
new file mode 100644
index 000000000..f39f6e6c2
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/roles/new.js
@@ -0,0 +1,13 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeRolesNewRoute extends Route {
+ // =methods
+
+ /**
+ * Creates a new unsaved role.
+ * @return {RoleModel}
+ */
+ model() {
+ return this.store.createRecord('role');
+ }
+}
diff --git a/ui/core/app/routes/scopes/scope/roles/role.js b/ui/core/app/routes/scopes/scope/roles/role.js
new file mode 100644
index 000000000..781bb4e47
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/roles/role.js
@@ -0,0 +1,17 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeRolesRoleRoute extends Route {
+ // =methods
+
+ /**
+ * Load a role in current scope.
+ * @param {object} params
+ * @param {string} params.role_id
+ * @return {RoleModel}
+ */
+ async model({ role_id }) {
+ const { id: scope_id } = this.modelFor('scopes.scope');
+ const adapterOptions = { scopeID: scope_id };
+ return this.store.findRecord('role', role_id, { adapterOptions });
+ }
+}
diff --git a/ui/core/app/routes/scopes/scope/roles/role/index.js b/ui/core/app/routes/scopes/scope/roles/role/index.js
new file mode 100644
index 000000000..359e9c9f5
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/roles/role/index.js
@@ -0,0 +1,3 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeRolesRoleIndexRoute extends Route {}
diff --git a/ui/core/app/routes/scopes/scope/users.js b/ui/core/app/routes/scopes/scope/users.js
index bf0ba95c4..8f4f65905 100644
--- a/ui/core/app/routes/scopes/scope/users.js
+++ b/ui/core/app/routes/scopes/scope/users.js
@@ -58,7 +58,7 @@ export default class ScopesScopeUsersRoute extends Route {
await user.destroyRecord(this.scopeAdapterOptions());
this.refresh();
this.transitionTo('scopes.scope.users');
- this.notify.success(this.intl.t('notify.delete-success'));
+ this.notify.success(this.intl.t('notify.user.delete-success'));
} catch (error) {
// TODO: replace with translated strings
this.notify.error(error.message, { closeAfter: null });
diff --git a/ui/core/app/templates/scopes/scope.hbs b/ui/core/app/templates/scopes/scope.hbs
index f3199821b..55e35d7e0 100644
--- a/ui/core/app/templates/scopes/scope.hbs
+++ b/ui/core/app/templates/scopes/scope.hbs
@@ -15,6 +15,9 @@
{{t "resources.groups"}}
+
+ {{t "resources.roles"}}
+
{{outlet "sidebar"}}
diff --git a/ui/core/app/templates/scopes/scope/roles.hbs b/ui/core/app/templates/scopes/scope/roles.hbs
new file mode 100644
index 000000000..6d511b7f7
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/roles.hbs
@@ -0,0 +1,3 @@
+{{title (t "resources.roles")}}
+
+{{outlet}}
diff --git a/ui/core/app/templates/scopes/scope/roles/index.hbs b/ui/core/app/templates/scopes/scope/roles/index.hbs
new file mode 100644
index 000000000..e0e28cf9a
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/roles/index.hbs
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+ {{t "resources.roles"}}
+
+
+
+ {{#if @model}}
+ {{t 'titles.new'}}
+ {{/if}}
+
+
+
+
+ {{#if @model}}
+
+
+
+
+ {{t 'form.label.name'}}
+ {{t 'form.label.description'}}
+
+
+
+ {{#each @model as |role|}}
+
+
+
+ {{role.displayName}}
+
+
+ {{role.description}}
+
+ {{/each}}
+
+
+
+ {{else}}
+
+
+
+
+ {{t 'descriptions.roles-welcome'}}
+
+
+
+ {{t 'titles.new'}}
+
+
+
+
+ {{/if}}
+
+
+
+
diff --git a/ui/core/app/templates/scopes/scope/roles/new.hbs b/ui/core/app/templates/scopes/scope/roles/new.hbs
new file mode 100644
index 000000000..efddd8fb9
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/roles/new.hbs
@@ -0,0 +1,19 @@
+{{title (t 'titles.new')}}
+
+
+
+
+
+
+
+ {{t "titles.new"}}
+
+
+
+
+
+
diff --git a/ui/core/app/templates/scopes/scope/roles/role.hbs b/ui/core/app/templates/scopes/scope/roles/role.hbs
new file mode 100644
index 000000000..d66e83390
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/roles/role.hbs
@@ -0,0 +1,3 @@
+{{title @model.displayName}}
+
+{{outlet}}
diff --git a/ui/core/app/templates/scopes/scope/roles/role/index.hbs b/ui/core/app/templates/scopes/scope/roles/role/index.hbs
new file mode 100644
index 000000000..1b35c1929
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/roles/role/index.hbs
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ {{t "resources.role"}}
+
+
+
+
+
+
+ {{t 'actions.delete'}}
+
+
+
+
diff --git a/ui/core/tests/unit/routes/scopes/scope/roles-test.js b/ui/core/tests/unit/routes/scopes/scope/roles-test.js
new file mode 100644
index 000000000..f2aa633ee
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/roles-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/roles', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/roles');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/index-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/index-test.js
new file mode 100644
index 000000000..aad8dd94c
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/roles/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/roles/index', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/roles/index');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/new-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/new-test.js
new file mode 100644
index 000000000..1ddf02ba6
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/roles/new-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/roles/new', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/roles/new');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/role-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/role-test.js
new file mode 100644
index 000000000..41e292c3c
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/roles/role-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/roles/role', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/roles/role');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/role/index-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/role/index-test.js
new file mode 100644
index 000000000..1a4aecbf1
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/roles/role/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/roles/role/index', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/roles/role/index');
+ assert.ok(route);
+ });
+});