diff --git a/addons/api/mirage/factories/auth-method.js b/addons/api/mirage/factories/auth-method.js
index 61d29726b..1d0b29df8 100644
--- a/addons/api/mirage/factories/auth-method.js
+++ b/addons/api/mirage/factories/auth-method.js
@@ -10,7 +10,10 @@ export default factory.extend({
withAccountsAndUsers: trait({
afterCreate(authMethod, server) {
const { scope } = authMethod;
- server.createList('user', 5, { scope }).map(user => {
+ const users = server.createList('user', 5, { scope });
+ // Create authorized user: user123
+ users.push(server.create('user', { scope, id: 'user123' }));
+ users.map(user => {
const { id } = server.create('account', { scope, authMethod });
user.update({ accountIds: [id] });
});
diff --git a/addons/api/mirage/factories/session.js b/addons/api/mirage/factories/session.js
index 0100a4b75..1292d3e10 100644
--- a/addons/api/mirage/factories/session.js
+++ b/addons/api/mirage/factories/session.js
@@ -41,6 +41,29 @@ export default factory.extend({
}
}
}
+ }),
+
+ /**
+ * Sessions generated with this trait will automatically form
+ * associations to target, host set, and host (if available) within
+ * the current scope and for specified user.
+ */
+ withUserAssociations: trait({
+ afterCreate(record, server) {
+ const scopeId = record.scopeId;
+ const user = record.user;
+ record.update({ user });
+ const target = server.schema.targets.where({ scopeId }).models[0];
+ if (target) {
+ record.update({ target });
+ const hostSet = target.hostSets.models[0];
+ if (hostSet) {
+ record.update({ hostSet });
+ const host = hostSet.hosts.models[0];
+ if (host) record.update({ host });
+ }
+ }
+ }
})
});
diff --git a/addons/api/mirage/scenarios/default.js b/addons/api/mirage/scenarios/default.js
index cc821e6dd..9961b7010 100644
--- a/addons/api/mirage/scenarios/default.js
+++ b/addons/api/mirage/scenarios/default.js
@@ -29,6 +29,9 @@ export default function(server) {
server.createList('host-catalog', 2, { scope }, 'withChildren');
server.createList('target', 2, { scope }, 'withRandomHostSets');
server.createList('session', 4, { scope }, 'withAssociations');
+ // Create sessions for all users
+ server.schema.users.where({ scopeId: scope.scope.id }).models.forEach(user =>
+ server.createList('session', 4, { scope, user }, 'withUserAssociations')
+ );
});
-
}
diff --git a/ui/desktop/app/router.js b/ui/desktop/app/router.js
index 9480e547d..bcf4938e5 100644
--- a/ui/desktop/app/router.js
+++ b/ui/desktop/app/router.js
@@ -14,6 +14,7 @@ Router.map(function () {
});
this.route('projects', function () {
this.route('targets', function () {});
+ this.route('sessions', function() {});
});
});
});
diff --git a/ui/desktop/app/routes/scopes/scope/projects/sessions.js b/ui/desktop/app/routes/scopes/scope/projects/sessions.js
new file mode 100644
index 000000000..7430c7764
--- /dev/null
+++ b/ui/desktop/app/routes/scopes/scope/projects/sessions.js
@@ -0,0 +1,66 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { all, hash } from 'rsvp';
+import { A } from '@ember/array';
+
+export default class ScopesScopeProjectsSessionsRoute extends Route {
+
+ // =services
+
+ @service session;
+
+ // =methods
+
+ /**
+ * If arriving here unauthenticated, redirect to index for further processing.
+ */
+ beforeModel() {
+ if (!this.session.isAuthenticated) this.transitionTo('index');
+ }
+
+ /**
+ * Loads all sessions under the current scope and encapsulates them into
+ * an array of objects filtering to current user
+ * @return {Promise{[{session: SessionModel, target: TargetModel}]}}
+ */
+ async model() {
+ const projects = this.modelFor('scopes.scope.projects');
+ const userId = this.session.data.authenticated.user_id;
+ let projectSessions = await all(
+ projects.map(({ id: scope_id }) =>
+ this.store.query('session', { scope_id })
+ )
+ );
+
+ // Filter sessions to current user
+ let sessions = projectSessions.map(sessions => {
+ return sessions.filter(session => {
+ console.log(session.user_id, userId)
+ return session.user_id === userId
+ });
+ }).flat();
+
+ const sessionAggregates = await all(
+ sessions.map(session => hash({
+ session,
+ target: session.target_id
+ ? (
+ this.store.peekRecord('target', session.target_id) ||
+ this.store.findRecord('target', session.target_id)
+ )
+ : null,
+ }))
+ );
+
+ // Sort sessions by time created...
+ let sortedSessionAggregates =
+ A(sessionAggregates).sortBy('session.created_time').reverse();
+ // Then move active sessions to the top...
+ sortedSessionAggregates = [
+ ...sortedSessionAggregates.filter((aggregate) => aggregate.session.status === 'active'),
+ ...sortedSessionAggregates.filter((aggregate) => aggregate.session.status !== 'active'),
+ ];
+
+ return sortedSessionAggregates;
+ }
+}
diff --git a/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js b/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js
new file mode 100644
index 000000000..85b153379
--- /dev/null
+++ b/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js
@@ -0,0 +1,4 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
+}
diff --git a/ui/desktop/app/templates/scopes/scope.hbs b/ui/desktop/app/templates/scopes/scope.hbs
index 763b790a7..6a8cd0d89 100644
--- a/ui/desktop/app/templates/scopes/scope.hbs
+++ b/ui/desktop/app/templates/scopes/scope.hbs
@@ -7,6 +7,13 @@
@size="medium" />
{{t "resources.target.title_plural"}}
+
+
+
+ {{t "resources.session.title_plural"}}
+
diff --git a/ui/desktop/app/templates/scopes/scope/projects/sessions.hbs b/ui/desktop/app/templates/scopes/scope/projects/sessions.hbs
new file mode 100644
index 000000000..e2147cab0
--- /dev/null
+++ b/ui/desktop/app/templates/scopes/scope/projects/sessions.hbs
@@ -0,0 +1 @@
+{{outlet}}
\ No newline at end of file
diff --git a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs
new file mode 100644
index 000000000..d0847dbb3
--- /dev/null
+++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs
@@ -0,0 +1,58 @@
+
+
+
+ {{t "resources.session.title_plural"}}
+
+
+
+
+ {{#if @model}}
+
+
+
+
+ {{t "form.id.label"}}
+ {{t "resources.target.title"}}
+ {{t "form.started.label"}}
+ {{t "form.status.label"}}
+
+
+
+ {{#each @model as |sessionAggregate|}}
+
+
+
+ {{sessionAggregate.session.id}}
+
+ {{sessionAggregate.target.displayName}}
+
+
+
+
+ {{sessionAggregate.session.status}}
+
+
+ {{/each}}
+
+
+
+ {{else}}
+
+
+
+
+ {{t "resources.session.messages.none.description"}}
+
+
+
+
+ {{/if}}
+
+
+
+
diff --git a/ui/desktop/tests/unit/routes/scopes/scope/projects/sessions-test.js b/ui/desktop/tests/unit/routes/scopes/scope/projects/sessions-test.js
new file mode 100644
index 000000000..358be9959
--- /dev/null
+++ b/ui/desktop/tests/unit/routes/scopes/scope/projects/sessions-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/projects/sessions', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope/projects/sessions');
+ assert.ok(route);
+ });
+});
diff --git a/ui/desktop/tests/unit/routes/scopes/scope/projects/sessions/index-test.js b/ui/desktop/tests/unit/routes/scopes/scope/projects/sessions/index-test.js
new file mode 100644
index 000000000..5c63b65eb
--- /dev/null
+++ b/ui/desktop/tests/unit/routes/scopes/scope/projects/sessions/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/projects/sessions/index', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope/projects/sessions/index');
+ assert.ok(route);
+ });
+});