diff --git a/ui/core/app/router.js b/ui/core/app/router.js
index 1c3e3e78b..0e7e5f7f7 100644
--- a/ui/core/app/router.js
+++ b/ui/core/app/router.js
@@ -17,6 +17,10 @@ Router.map(function () {
this.route('user', { path: ':user_id' }, function () {});
this.route('new');
});
+ this.route('groups', function () {
+ this.route('group', { path: ':group_id' }, function () {});
+ this.route('new');
+ });
});
});
diff --git a/ui/core/app/routes/scopes/scope/groups.js b/ui/core/app/routes/scopes/scope/groups.js
new file mode 100644
index 000000000..0344e53f1
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/groups.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 ScopesScopeGroupsRoute extends Route {
+ // =services
+
+ @service intl;
+ @service notify;
+
+ // =methods
+
+ /**
+ * Load all groups under current scope
+ * @return {Promise[GroupModel]}
+ */
+ async model() {
+ return this.store.findAll('group', this.scopeAdapterOptions());
+ }
+
+ // =actions
+ /**
+ * Rollback changes on a group.
+ * @param {GroupModel} group
+ */
+ @action
+ cancel(group) {
+ const { isNew } = group;
+ group.rollbackAttributes();
+ if (isNew) this.transitionTo('scopes.scope.groups');
+ }
+
+ /**
+ * Save a group in current scope.
+ * @param {GroupModel} group
+ */
+ @action
+ async save(group) {
+ try {
+ await group.save(this.scopeAdapterOptions());
+ this.refresh();
+ this.notify.success(this.intl.t('notify.save-success'));
+ this.transitionTo('scopes.scope.groups.group', group);
+ } catch (error) {
+ //TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+
+ /**
+ * Delete a group in current scope and redirect to index
+ * @param {GroupModel} group
+ */
+ @action
+ async delete(group) {
+ try {
+ await group.destroyRecord(this.scopeAdapterOptions());
+ this.refresh();
+ this.transitionTo('scopes.scope.groups');
+ this.notify.success(this.intl.t('notify.group.delete-success'));
+ } 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/groups/group.js b/ui/core/app/routes/scopes/scope/groups/group.js
new file mode 100644
index 000000000..da536ed07
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/groups/group.js
@@ -0,0 +1,17 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeGroupsGroupRoute extends Route {
+ // =methods
+
+ /**
+ * Load a group in current scope.
+ * @param {object} params
+ * @param {string} params.group_id
+ * @return {groupModel}
+ */
+ async model({ group_id }) {
+ const { id: scope_id } = this.modelFor('scopes.scope');
+ const adapterOptions = { scopeID: scope_id };
+ return this.store.findRecord('group', group_id, { adapterOptions });
+ }
+}
diff --git a/ui/core/app/routes/scopes/scope/groups/group/index.js b/ui/core/app/routes/scopes/scope/groups/group/index.js
new file mode 100644
index 000000000..90f0fb3a1
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/groups/group/index.js
@@ -0,0 +1,3 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeGroupsGroupIndexRoute extends Route {}
diff --git a/ui/core/app/routes/scopes/scope/groups/index.js b/ui/core/app/routes/scopes/scope/groups/index.js
new file mode 100644
index 000000000..7d2a20fb0
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/groups/index.js
@@ -0,0 +1,3 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeGroupsIndexRoute extends Route {}
diff --git a/ui/core/app/routes/scopes/scope/groups/new.js b/ui/core/app/routes/scopes/scope/groups/new.js
new file mode 100644
index 000000000..e33233f5f
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/groups/new.js
@@ -0,0 +1,13 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeGroupsNewRoute extends Route {
+ // =methods
+
+ /**
+ * Creates a new unsaved group.
+ * @return {GroupModel}
+ */
+ model() {
+ return this.store.createRecord('group');
+ }
+}
diff --git a/ui/core/app/routes/scopes/scope/users.js b/ui/core/app/routes/scopes/scope/users.js
index 2720f56b1..bf0ba95c4 100644
--- a/ui/core/app/routes/scopes/scope/users.js
+++ b/ui/core/app/routes/scopes/scope/users.js
@@ -20,7 +20,7 @@ export default class ScopesScopeUsersRoute extends Route {
// =actions
/**
- * Rollback changes on user.
+ * Rollback changes on an user.
* @param {UserModel} user
*/
@action
@@ -31,7 +31,7 @@ export default class ScopesScopeUsersRoute extends Route {
}
/**
- * Save a user in current scope.
+ * Save an user in current scope.
* @param {UserModel} user
* @param {Event} e
*/
diff --git a/ui/core/app/templates/scopes/scope.hbs b/ui/core/app/templates/scopes/scope.hbs
index ee969738f..f3199821b 100644
--- a/ui/core/app/templates/scopes/scope.hbs
+++ b/ui/core/app/templates/scopes/scope.hbs
@@ -12,6 +12,9 @@
{{t "resources.users"}}
+
+ {{t "resources.groups"}}
+
{{outlet "sidebar"}}
diff --git a/ui/core/app/templates/scopes/scope/groups.hbs b/ui/core/app/templates/scopes/scope/groups.hbs
new file mode 100644
index 000000000..c53da7f4b
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/groups.hbs
@@ -0,0 +1,3 @@
+{{title (t "resources.groups")}}
+
+{{outlet}}
diff --git a/ui/core/app/templates/scopes/scope/groups/group.hbs b/ui/core/app/templates/scopes/scope/groups/group.hbs
new file mode 100644
index 000000000..d66e83390
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/groups/group.hbs
@@ -0,0 +1,3 @@
+{{title @model.displayName}}
+
+{{outlet}}
diff --git a/ui/core/app/templates/scopes/scope/groups/group/index.hbs b/ui/core/app/templates/scopes/scope/groups/group/index.hbs
new file mode 100644
index 000000000..ac8915f59
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/groups/group/index.hbs
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+ {{t "resources.group"}}
+
+
+
+
+
+ {{t 'titles.details'}}
+
+
+
+
+
+
+
+
+ {{t 'actions.delete'}}
+
+
+
+
diff --git a/ui/core/app/templates/scopes/scope/groups/index.hbs b/ui/core/app/templates/scopes/scope/groups/index.hbs
new file mode 100644
index 000000000..cddaa478b
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/groups/index.hbs
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+ {{t "resources.groups"}}
+
+
+
+ {{#if @model}}
+ {{t 'titles.new'}}
+ {{/if}}
+
+
+
+
+ {{#if @model}}
+
+
+
+
+ {{t 'form.label.name'}}
+ {{t 'form.label.description'}}
+
+
+
+ {{#each @model as |group|}}
+
+
+
+ {{group.name}}
+
+
+ {{group.description}}
+
+ {{/each}}
+
+
+
+ {{else}}
+
+
+
+
+ {{t 'descriptions.groups-welcome'}}
+
+
+
+ {{t 'titles.new'}}
+
+
+
+
+ {{/if}}
+
+
+
+
diff --git a/ui/core/app/templates/scopes/scope/groups/new.hbs b/ui/core/app/templates/scopes/scope/groups/new.hbs
new file mode 100644
index 000000000..b8f782b23
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/groups/new.hbs
@@ -0,0 +1,21 @@
+{{title (t 'titles.new')}}
+
+
+
+
+
+
+
+
+ {{t "titles.new"}}
+
+
+
+
+
+
+
diff --git a/ui/core/tests/unit/routes/scopes/scope/groups-test.js b/ui/core/tests/unit/routes/scopes/scope/groups-test.js
new file mode 100644
index 000000000..879a3925b
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/groups-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/groups', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/groups');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/groups/group-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/group-test.js
new file mode 100644
index 000000000..8f8cc6713
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/groups/group-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/groups/group', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/groups/group');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/groups/group/index-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/group/index-test.js
new file mode 100644
index 000000000..3981831ad
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/groups/group/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/groups/group/index', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/groups/group/index');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/groups/index-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/index-test.js
new file mode 100644
index 000000000..f50de2922
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/groups/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/groups/index', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/groups/index');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/groups/new-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/new-test.js
new file mode 100644
index 000000000..6e689bdd3
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/groups/new-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/groups/new', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/groups/new');
+ assert.ok(route);
+ });
+});