diff --git a/ui/core/app/components/form/role/grants/index.hbs b/ui/core/app/components/form/role/grants/index.hbs
new file mode 100644
index 000000000..ec731c2fe
--- /dev/null
+++ b/ui/core/app/components/form/role/grants/index.hbs
@@ -0,0 +1,45 @@
+
+
+ {{!--
+ {{#if @model.errors.grants}}
+
+ {{#each @model.errors.grants as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+ --}}
+
+
+ {{#each @model.grants as |grant|}}
+
+
+
+ {{t "form.label.grant"}}
+
+
+
+
+
+
+ {{/each}}
+
+
+
+
+
diff --git a/ui/core/app/router.js b/ui/core/app/router.js
index a4f121c19..bba93d1ad 100644
--- a/ui/core/app/router.js
+++ b/ui/core/app/router.js
@@ -21,7 +21,9 @@ Router.map(function () {
this.route('new');
});
this.route('roles', function () {
- this.route('role', { path: ':role_id' }, function () {});
+ this.route('role', { path: ':role_id' }, function () {
+ this.route('grants');
+ });
this.route('new');
});
this.route('projects', function() {
diff --git a/ui/core/app/routes/scopes/scope/roles/role/grants.js b/ui/core/app/routes/scopes/scope/roles/role/grants.js
new file mode 100644
index 000000000..098de9656
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/roles/role/grants.js
@@ -0,0 +1,31 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+
+export default class ScopesScopeRolesRoleGrantsRoute extends Route {
+
+ // =services
+
+ @service intl;
+ @service notify;
+
+ // =methods
+
+ /**
+ * Save an role in current scope.
+ * @param {RoleModel} role
+ */
+ @action
+ async save(role) {
+ try {
+ await role.saveGrants();
+ this.notify.success(
+ this.intl.t('notify.save-success')
+ );
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+
+}
diff --git a/ui/core/app/templates/scopes/scope/roles/role.hbs b/ui/core/app/templates/scopes/scope/roles/role.hbs
index d66e83390..552717330 100644
--- a/ui/core/app/templates/scopes/scope/roles/role.hbs
+++ b/ui/core/app/templates/scopes/scope/roles/role.hbs
@@ -1,3 +1,28 @@
{{title @model.displayName}}
-{{outlet}}
+
+
+
+
+
+
+
+ {{t "resources.role"}}
+
+
+
+
+
+ {{t 'titles.settings'}}
+
+
+ {{t 'titles.grants'}}
+
+
+
+
+
+ {{outlet}}
+
+
+
diff --git a/ui/core/app/templates/scopes/scope/roles/role/grants.hbs b/ui/core/app/templates/scopes/scope/roles/role/grants.hbs
new file mode 100644
index 000000000..81827f673
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/roles/role/grants.hbs
@@ -0,0 +1,7 @@
+{{title (t 'titles.grants')}}
+
+
diff --git a/ui/core/app/templates/scopes/scope/roles/role/index.hbs b/ui/core/app/templates/scopes/scope/roles/role/index.hbs
index 1b35c1929..bbb671abe 100644
--- a/ui/core/app/templates/scopes/scope/roles/role/index.hbs
+++ b/ui/core/app/templates/scopes/scope/roles/role/index.hbs
@@ -1,27 +1,13 @@
-
-
-
-
-
-
-
- {{t "resources.role"}}
-
-
-
-
-
-
- {{t 'actions.delete'}}
-
-
-
-
+
+
+
+ {{t 'actions.delete'}}
+
diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js
index 5d54a9320..73342c6b0 100644
--- a/ui/core/mirage/config.js
+++ b/ui/core/mirage/config.js
@@ -64,6 +64,13 @@ export default function() {
this.get('/scopes/:scope_id/roles/:id');
this.patch('/scopes/:scope_id/roles/:id');
this.del('/scopes/:scope_id/roles/:id');
+ this.post('/scopes/:scope_id/roles/:id', function ({ roles }, request) {
+ const attrs = this.normalizedRequestAttrs();
+ const id = request.params.id.split(':')[0];
+ const role = roles.find(id);
+ attrs.id = id;
+ return role.update(attrs);
+ });
// IAM: Groups
this.get('/scopes/:scope_id/groups');
diff --git a/ui/core/mirage/factories/role.js b/ui/core/mirage/factories/role.js
index abcb8de67..e597c3433 100644
--- a/ui/core/mirage/factories/role.js
+++ b/ui/core/mirage/factories/role.js
@@ -1,3 +1,11 @@
import factory from '../generated/factories/role';
-export default factory.extend();
+export default factory.extend({
+
+ // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
+ grants: [
+ 'id=*;action=*',
+ 'id=*;type=host-catalog;actions=create,read'
+ ],
+
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/role/grants-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/role/grants-test.js
new file mode 100644
index 000000000..87b0c03c7
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/roles/role/grants-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/roles/role/grants', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope/roles/role/grants');
+ assert.ok(route);
+ });
+});