diff --git a/addons/api/addon/adapters/role.js b/addons/api/addon/adapters/role.js
new file mode 100644
index 000000000..c9b0c13b0
--- /dev/null
+++ b/addons/api/addon/adapters/role.js
@@ -0,0 +1,14 @@
+import ApplicationAdapter from './application';
+import ScopeAdapterMixin from '../mixins/scope-adapter';
+
+export default class RoleAdapter extends ApplicationAdapter.extend(
+ ScopeAdapterMixin
+) {
+ // =attributes
+
+ /**
+ * Role is scoped to org only, for now.
+ * @type {boolean}
+ */
+ includeProject = false;
+}
diff --git a/addons/api/app/adapters/role.js b/addons/api/app/adapters/role.js
new file mode 100644
index 000000000..f12325923
--- /dev/null
+++ b/addons/api/app/adapters/role.js
@@ -0,0 +1 @@
+export { default } from 'api/adapters/role';
diff --git a/addons/api/tests/unit/adapters/role-test.js b/addons/api/tests/unit/adapters/role-test.js
new file mode 100644
index 000000000..adb7b1abd
--- /dev/null
+++ b/addons/api/tests/unit/adapters/role-test.js
@@ -0,0 +1,16 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import config from 'ember-get-config';
+
+module('Unit | Adapter | role', function(hooks) {
+ setupTest(hooks);
+
+ test('it generates URL prefixes without project scope', function (assert) {
+ assert.expect(1);
+ const service = this.owner.lookup('service:scope');
+ service.org = { id: 1 };
+ service.project = { id: 2 };
+ const adapter = this.owner.lookup('adapter:role');
+ assert.equal(adapter.urlPrefix(), `${config.api.namespace}/orgs/1`);
+ });
+});
diff --git a/ui/core/app/components/form/role/index.hbs b/ui/core/app/components/form/role/index.hbs
new file mode 100644
index 000000000..9db695baa
--- /dev/null
+++ b/ui/core/app/components/form/role/index.hbs
@@ -0,0 +1,49 @@
+
+
+
+ {{#if @model.errors.name}}
+
+ {{#each @model.errors.name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+
+ {{#if @model.errors.description}}
+
+ {{#each @model.errors.description as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+
+
+
diff --git a/ui/core/app/controllers/orgs/org/roles.js b/ui/core/app/controllers/orgs/org/roles.js
new file mode 100644
index 000000000..7f62b0e1a
--- /dev/null
+++ b/ui/core/app/controllers/orgs/org/roles.js
@@ -0,0 +1,18 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class OrgsOrgRolesController extends Controller {
+ // =services
+
+ @service intl;
+
+ // =attributes
+
+ /**
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.intl.t('resources.roles');
+ }
+
+}
diff --git a/ui/core/app/controllers/orgs/org/roles/new.js b/ui/core/app/controllers/orgs/org/roles/new.js
new file mode 100644
index 000000000..38a098eaa
--- /dev/null
+++ b/ui/core/app/controllers/orgs/org/roles/new.js
@@ -0,0 +1,18 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class OrgsOrgRolesNewController extends Controller {
+ // =services
+
+ @service intl;
+
+ // =attributes
+
+ /**
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.intl.t('titles.new');
+ }
+
+}
diff --git a/ui/core/app/controllers/orgs/org/roles/role.js b/ui/core/app/controllers/orgs/org/roles/role.js
new file mode 100644
index 000000000..7a5564ac0
--- /dev/null
+++ b/ui/core/app/controllers/orgs/org/roles/role.js
@@ -0,0 +1,18 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class OrgsOrgRolesRoleController extends Controller {
+ // =services
+
+ @service intl;
+
+ // =attributes
+
+ /**
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.intl.t('resources.role');
+ }
+
+}
diff --git a/ui/core/app/router.js b/ui/core/app/router.js
index 76af92941..b9360c541 100644
--- a/ui/core/app/router.js
+++ b/ui/core/app/router.js
@@ -18,6 +18,10 @@ Router.map(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/orgs/org/roles.js b/ui/core/app/routes/orgs/org/roles.js
new file mode 100644
index 000000000..9c804bf0b
--- /dev/null
+++ b/ui/core/app/routes/orgs/org/roles.js
@@ -0,0 +1,32 @@
+import Route from '@ember/routing/route';
+
+export default class OrgsOrgRolesRoute extends Route {
+ // =methods
+
+ /**
+ * Returns all roles from store.
+ * @return {Promise[RoleModel]}
+ */
+ model() {
+ return this.store.findAll('role').catch(() => {
+ // FIX ME: API response of "{}" generates following error:
+ // Error while processing route: orgs.org.roles.index
+ // Error: Assertion Failed: You made a 'findAll' request for 'role' records, but the adapter's response did not have any data
+ });
+ }
+
+ /**
+ * Renders the roles-specific sidebar template.
+ * @override
+ * @param {object} controller
+ * @param {object} model
+ */
+ renderTemplate(controller, model) {
+ this.render('orgs/org/roles/_sidebar', {
+ into: 'application',
+ outlet: 'sidebar',
+ model: model
+ });
+ super.renderTemplate(...arguments);
+ }
+}
diff --git a/ui/core/app/routes/orgs/org/roles/index.js b/ui/core/app/routes/orgs/org/roles/index.js
new file mode 100644
index 000000000..89b868927
--- /dev/null
+++ b/ui/core/app/routes/orgs/org/roles/index.js
@@ -0,0 +1,3 @@
+import Route from '@ember/routing/route';
+
+export default class OrgsOrgRolesIndexRoute extends Route { }
diff --git a/ui/core/app/routes/orgs/org/roles/new.js b/ui/core/app/routes/orgs/org/roles/new.js
new file mode 100644
index 000000000..cad253804
--- /dev/null
+++ b/ui/core/app/routes/orgs/org/roles/new.js
@@ -0,0 +1,50 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+
+export default class OrgsOrgRolesNewRoute extends Route {
+ // =services
+
+ @service intl;
+ @service notify;
+
+ // =methods
+
+ /**
+ * Returns a new unsaved role.
+ * @return {Promise{RoleModel}}
+ */
+ model() {
+ return this.store.createRecord('role');
+ }
+
+ // =actions
+
+ /**
+ * Rollback changes on role by destroying unsaved instance
+ * and redirect to role index.
+ * @param {RoleModel} role
+ */
+ @action
+ cancel(role) {
+ role.rollbackAttributes();
+ this.transitionTo('orgs.org.roles');
+ }
+
+ /**
+ * Handle saving a role.
+ * @param {RoleModel} role
+ * @param {Event} e
+ */
+ @action
+ async save(role) {
+ try {
+ await role.save();
+ this.transitionTo('orgs.org.roles.role', role);
+ this.notify.success(this.intl.t('notify.role.create-success'));
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+}
diff --git a/ui/core/app/routes/orgs/org/roles/role.js b/ui/core/app/routes/orgs/org/roles/role.js
new file mode 100644
index 000000000..1820f2ba1
--- /dev/null
+++ b/ui/core/app/routes/orgs/org/roles/role.js
@@ -0,0 +1,64 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+
+export default class OrgsOrgRolesRoleRoute extends Route {
+ // =services
+
+ @service intl;
+ @service notify;
+
+ // =methods
+
+ /**
+ * Return a role with id
+ * @param {object} params
+ * @return {Promise{RoleModel}}
+ */
+ model({ role_id: id }) {
+ return this.store.findRecord('role', id);
+ }
+
+ // =actions
+
+ /**
+ * Rollback changes on role.
+ * @param {RoleModel} role
+ */
+ @action
+ cancel(role) {
+ role.rollbackAttributes();
+ }
+
+ /**
+ * Handle saving a role.
+ * @param {RoleModel} role
+ * @param {Event} e
+ */
+ @action
+ async save(role) {
+ try {
+ await role.save();
+ this.notify.success(this.intl.t('notify.save-success'));
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+
+ /**
+ * Delete role and redirect to roles index.
+ * @param {RoleModel} role
+ */
+ @action
+ async delete(role) {
+ try {
+ await role.destroyRecord();
+ this.transitionTo('orgs.org.roles');
+ this.notify.success(this.intl.t('notify.role.delete-success'));
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+}
diff --git a/ui/core/app/templates/orgs/org/roles.hbs b/ui/core/app/templates/orgs/org/roles.hbs
new file mode 100644
index 000000000..6d511b7f7
--- /dev/null
+++ b/ui/core/app/templates/orgs/org/roles.hbs
@@ -0,0 +1,3 @@
+{{title (t "resources.roles")}}
+
+{{outlet}}
diff --git a/ui/core/app/templates/orgs/org/roles/_sidebar.hbs b/ui/core/app/templates/orgs/org/roles/_sidebar.hbs
new file mode 100644
index 000000000..584ba2ccc
--- /dev/null
+++ b/ui/core/app/templates/orgs/org/roles/_sidebar.hbs
@@ -0,0 +1,18 @@
+
+
+
+ {{t "resources.projects"}}
+
+
+
+
+
+
+ {{t "resources.roles"}}
+
+
+
diff --git a/ui/core/app/templates/orgs/org/roles/index.hbs b/ui/core/app/templates/orgs/org/roles/index.hbs
new file mode 100644
index 000000000..498aecac5
--- /dev/null
+++ b/ui/core/app/templates/orgs/org/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.name}}
+
+
+ {{role.description}}
+
+ {{/each}}
+
+
+
+ {{else}}
+
+
+
+
+ {{t 'descriptions.roles-welcome'}}
+
+
+
+ {{t 'titles.new'}}
+
+
+
+
+ {{/if}}
+
+
+
+
diff --git a/ui/core/app/templates/orgs/org/roles/new.hbs b/ui/core/app/templates/orgs/org/roles/new.hbs
new file mode 100644
index 000000000..911310f26
--- /dev/null
+++ b/ui/core/app/templates/orgs/org/roles/new.hbs
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ {{t "titles.new"}}
+
+
+
+
+
+
diff --git a/ui/core/app/templates/orgs/org/roles/role.hbs b/ui/core/app/templates/orgs/org/roles/role.hbs
new file mode 100644
index 000000000..2b05f838d
--- /dev/null
+++ b/ui/core/app/templates/orgs/org/roles/role.hbs
@@ -0,0 +1,3 @@
+{{title @model.name}}
+
+{{outlet}}
diff --git a/ui/core/app/templates/orgs/org/roles/role/index.hbs b/ui/core/app/templates/orgs/org/roles/role/index.hbs
new file mode 100644
index 000000000..1b35c1929
--- /dev/null
+++ b/ui/core/app/templates/orgs/org/roles/role/index.hbs
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ {{t "resources.role"}}
+
+
+
+
+
+
+ {{t 'actions.delete'}}
+
+
+
+
diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js
index 247fc6f36..8a3e0f49c 100644
--- a/ui/core/mirage/config.js
+++ b/ui/core/mirage/config.js
@@ -40,6 +40,13 @@ export default function() {
this.patch('/orgs/:org_id/auth-methods/:id');
this.del('/orgs/:org_id/auth-methods/:id');
+ // role
+ this.get('/orgs/:org_id/roles');
+ this.post('/orgs/:org_id/roles');
+ this.get('/orgs/:org_id/roles/:id');
+ this.patch('/orgs/:org_id/roles/:id');
+ this.del('/orgs/:org_id/roles/:id');
+
// Other resources
// host-catalog
diff --git a/ui/core/mirage/factories/role.js b/ui/core/mirage/factories/role.js
new file mode 100644
index 000000000..abcb8de67
--- /dev/null
+++ b/ui/core/mirage/factories/role.js
@@ -0,0 +1,3 @@
+import factory from '../generated/factories/role';
+
+export default factory.extend();
diff --git a/ui/core/mirage/generated/factories/role.js b/ui/core/mirage/generated/factories/role.js
new file mode 100644
index 000000000..6265c7116
--- /dev/null
+++ b/ui/core/mirage/generated/factories/role.js
@@ -0,0 +1,16 @@
+import { Factory } from 'ember-cli-mirage';
+import { random, date } from 'faker';
+
+/**
+ * GeneratedRoleModelFactory
+ * Role contains all field related to a Role resource
+ */
+export default Factory.extend({
+
+ name: () => random.words(),
+ description: () => random.words(),
+ created_time: () => date.recent(),
+ updated_time: () => date.recent(),
+ disabled: () => random.boolean(),
+
+});
diff --git a/ui/core/mirage/scenarios/default.js b/ui/core/mirage/scenarios/default.js
index b8accbf8b..ef06c6ee4 100644
--- a/ui/core/mirage/scenarios/default.js
+++ b/ui/core/mirage/scenarios/default.js
@@ -12,6 +12,10 @@ export default function(server) {
// same items for request at all scopes.
server.createList('auth-method', 3);
+ // IAM
+ // Roles
+ server.createList('role', 5);
+
// Other resources
server.createList('host-catalog', 6);
diff --git a/ui/core/mirage/serializers/role.js b/ui/core/mirage/serializers/role.js
new file mode 100644
index 000000000..c0db8a130
--- /dev/null
+++ b/ui/core/mirage/serializers/role.js
@@ -0,0 +1,5 @@
+import ApplicationSerializer from './application';
+
+export default ApplicationSerializer.extend({
+ modelName: 'role'
+});
diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js
new file mode 100644
index 000000000..976ba47b7
--- /dev/null
+++ b/ui/core/tests/acceptance/roles-test.js
@@ -0,0 +1,191 @@
+import { module, test } from 'qunit';
+import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers';
+import { setupApplicationTest } from 'ember-qunit';
+import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
+import a11yAudit from 'ember-a11y-testing/test-support/audit';
+import { Response } from 'miragejs';
+
+module('Acceptance | roles', function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(function () {
+ this.server.create('org');
+ });
+
+ test('visiting roles', async function (assert) {
+ assert.expect(1);
+ await visit('/orgs/1/roles');
+ await a11yAudit();
+ assert.equal(currentURL(), '/orgs/1/roles');
+ });
+
+ test('can create new role', async function (assert) {
+ assert.expect(4);
+ assert.equal(this.server.db.roles.length, 0);
+ await visit('/orgs/1/roles/new');
+ await fillIn('[name="name"]', 'role name');
+ await click('[type="submit"]');
+ assert.equal(currentURL(), '/orgs/1/roles/1');
+ assert.equal(this.server.db.roles.length, 1);
+ assert.equal(this.server.db.roles[0].name, 'role name');
+ });
+
+ test('can cancel new role creation', async function (assert) {
+ assert.expect(3);
+ assert.equal(this.server.db.roles.length, 0);
+ await visit('/orgs/1/roles/new');
+ await fillIn('[name="name"]', 'role name');
+ await click('.rose-form-actions [type="button"]');
+ assert.equal(currentURL(), '/orgs/1/roles');
+ assert.equal(this.server.db.roles.length, 0);
+ });
+
+ test('saving a new role with invalid fields displays error messages', async function (assert) {
+ assert.expect(2);
+ this.server.post('/orgs/:org_id/roles', () => {
+ return new Response(
+ 400,
+ {},
+ {
+ status: 400,
+ code: 'invalid_argument',
+ message: 'The request was invalid.',
+ details: {
+ fields: [
+ {
+ name: 'name',
+ message: 'Name is required.',
+ },
+ ],
+ },
+ }
+ );
+ });
+ await visit('/orgs/1/roles/new');
+ await fillIn('[name="name"]', 'role name');
+ await click('[type="submit"]');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'The request was invalid.',
+ 'Displays primary error message.'
+ );
+ assert.ok(
+ find('.rose-form-error-message').textContent.trim(),
+ 'Name is required.',
+ 'Displays field-level errors.'
+ );
+ });
+
+ test('can save changes to an existing role', async function (assert) {
+ assert.expect(2);
+ this.server.createList('role', 1, {name: 'Admin role'});
+ await visit('/orgs/1/roles/1');
+ await fillIn('[name="name"]', 'Updated admin role');
+ await click('.rose-form-actions [type="submit"]');
+ assert.equal(currentURL(), '/orgs/1/roles/1');
+ assert.equal(this.server.db.roles[0].name, 'Updated admin role');
+ });
+
+ test('can cancel changes to an existing role', async function (assert) {
+ assert.expect(1);
+ this.server.createList('role', 1, {name: 'Admin role'});
+ await visit('/orgs/1/roles/1');
+ await fillIn('[name="name"]', 'Updated admin role');
+ await click('.rose-form-actions [type="button"]');
+ assert.equal(find('[name="name"]').value, 'Admin role');
+ });
+
+ test('can delete a role', async function(assert) {
+ assert.expect(2);
+ this.server.createList('role', 1);
+ assert.equal(this.server.db.roles.length, 1);
+ await visit('/orgs/1/roles/1');
+ await click('.rose-button-warning');
+ assert.equal(this.server.db.roles.length, 0);
+ });
+
+ test('saving an existing role with invalid fields displays error messages', async function (assert) {
+ assert.expect(2);
+ this.server.createList('role', 1);
+ this.server.patch('/orgs/:org_id/roles/:id', () => {
+ return new Response(
+ 400,
+ {},
+ {
+ status: 400,
+ code: 'invalid_argument',
+ message: 'The request was invalid.',
+ details: {
+ fields: [
+ {
+ name: 'name',
+ message: 'Name is required.',
+ },
+ ],
+ },
+ }
+ );
+ });
+ await visit('/orgs/1/roles/1');
+ await fillIn('[name="name"]', 'random string');
+ await click('[type="submit"]');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'The request was invalid.',
+ 'Displays primary error message.'
+ );
+ assert.ok(
+ find('.rose-form-error-message').textContent.trim(),
+ 'Name is required.',
+ 'Displays field-level errors.'
+ );
+ });
+
+ test('errors are displayed when save project fails', async function (assert) {
+ assert.expect(1);
+ this.server.createList('role', 1);
+ this.server.patch('/orgs/:org_id/roles/:id', () => {
+ return new Response(
+ 490,
+ {},
+ {
+ status: 490,
+ code: 'error',
+ message: 'Oops.',
+ }
+ );
+ });
+ await visit('/orgs/1/roles/1');
+ await fillIn('[name="name"]', 'Role name');
+ await click('[type="submit"]');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'Oops.',
+ 'Displays primary error message.'
+ );
+ });
+
+ test('errors are displayed when delete project fails', async function (assert) {
+ assert.expect(1);
+ this.server.createList('role', 1);
+ this.server.del('/orgs/:org_id/roles/:id', () => {
+ return new Response(
+ 490,
+ {},
+ {
+ status: 490,
+ code: 'error',
+ message: 'Oops.',
+ }
+ );
+ });
+ await visit('/orgs/1/roles/1');
+ await click('.rose-button-warning');
+ assert.ok(
+ find('[role="alert"]').textContent.trim(),
+ 'Oops.',
+ 'Displays primary error message.'
+ );
+ });
+});
diff --git a/ui/core/tests/unit/controllers/orgs/org/roles-test.js b/ui/core/tests/unit/controllers/orgs/org/roles-test.js
new file mode 100644
index 000000000..816459cd3
--- /dev/null
+++ b/ui/core/tests/unit/controllers/orgs/org/roles-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | orgs/org/roles', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let controller = this.owner.lookup('controller:orgs/org/roles');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/core/tests/unit/controllers/orgs/org/roles/new-test.js b/ui/core/tests/unit/controllers/orgs/org/roles/new-test.js
new file mode 100644
index 000000000..8d948ae06
--- /dev/null
+++ b/ui/core/tests/unit/controllers/orgs/org/roles/new-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | orgs/org/roles/new', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let controller = this.owner.lookup('controller:orgs/org/roles/new');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/core/tests/unit/controllers/orgs/org/roles/role-test.js b/ui/core/tests/unit/controllers/orgs/org/roles/role-test.js
new file mode 100644
index 000000000..cd56c941f
--- /dev/null
+++ b/ui/core/tests/unit/controllers/orgs/org/roles/role-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | orgs/org/roles/role', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let controller = this.owner.lookup('controller:orgs/org/roles/role');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/core/tests/unit/routes/orgs/org/roles-test.js b/ui/core/tests/unit/routes/orgs/org/roles-test.js
new file mode 100644
index 000000000..38bc3acb2
--- /dev/null
+++ b/ui/core/tests/unit/routes/orgs/org/roles-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | orgs/org/roles', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:orgs/org/roles');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/orgs/org/roles/index-test.js b/ui/core/tests/unit/routes/orgs/org/roles/index-test.js
new file mode 100644
index 000000000..12f247adb
--- /dev/null
+++ b/ui/core/tests/unit/routes/orgs/org/roles/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | orgs/org/roles/index', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:orgs/org/roles/index');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/orgs/org/roles/new-test.js b/ui/core/tests/unit/routes/orgs/org/roles/new-test.js
new file mode 100644
index 000000000..d613eb34a
--- /dev/null
+++ b/ui/core/tests/unit/routes/orgs/org/roles/new-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | orgs/org/roles/new', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:orgs/org/roles/new');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/orgs/org/roles/role-test.js b/ui/core/tests/unit/routes/orgs/org/roles/role-test.js
new file mode 100644
index 000000000..149fd657e
--- /dev/null
+++ b/ui/core/tests/unit/routes/orgs/org/roles/role-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | orgs/org/roles/role', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:orgs/org/roles/role');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml
index 29ba0c47f..d8151b25b 100644
--- a/ui/core/translations/en-us.yaml
+++ b/ui/core/translations/en-us.yaml
@@ -5,22 +5,28 @@ actions:
save: Save
help: Need help?
resources:
+ role: Role
+ roles: Roles
projects: Projects
project: Project
host-catalog: Host Catalog
host-catalogs: Host Catalogs
titles:
+ iam: IAM
new: New
+ general: General
overview: Overview
settings: Settings
empty-set: No Items Yet
projects-welcome: Welcome to Projects
host-catalogs-welcome: Welcome to Host Catalogs
+ roles-welcome: Welcome to Roles
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.
projects-welcome: Projects allow you to create and manage host catalogs. To start, create a project.
host-catalogs-welcome: Host catalogs allow you to configure to collect and create hosts and host sets. To start, create a host catalog.
+ roles-welcome: Roles are collections of capabilities that can be granted to user and/or groups.
states:
info: Info
success: Success
@@ -44,6 +50,11 @@ errors:
notfound-error: We could not find the requested resource. You can ask your administrator or try again later.
server-error: We ran into a problem and could not continue. You can ask your administrator or try again later.
unknown-error: Please contact your administrator or try again later.
+notify:
+ save-success: Save succeeded.
+ role:
+ create-success: Role created succesfully.
+ delete-success: Deleted role successfully.
dev:
default-org: Default Org
menu-item: Menu item