Merge pull request #200 from hashicorp/feature-sessions-ui

feat: 🎸 add sessions list view
This commit is contained in:
Susmitha Girumala authored and GitHub committed 2020-09-20 17:56:49 -04:00
commit 76a983fdda
21 files changed
+268

No files matched your search

@@ -54,3 +54,7 @@ p {
a {
color: var(--action);
}
time {
font-variant-numeric: tabular-nums;
}
+9
View File
@@ -7,6 +7,15 @@ export default {
},
},
date: {
default: {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
},
hhmmss: {
hour: 'numeric',
minute: 'numeric',
@@ -0,0 +1,7 @@
import { helper } from '@ember/component/helper';
export default helper(function formatDateIsoHuman(params/*, hash*/) {
return params[0].toISOString()
.replace('T', ' ')
.replace(/\.\d*Z/, '');
});
+5
View File
@@ -0,0 +1,5 @@
import { helper } from '@ember/component/helper';
export default helper(function formatDateIso(params/*, hash*/) {
return params[0].toISOString();
});
+1
View File
@@ -52,6 +52,7 @@ Router.map(function () {
});
this.route('new');
});
this.route('sessions', function() {});
});
this.route('new');
});
@@ -0,0 +1,25 @@
import Route from '@ember/routing/route';
import { all, hash } from 'rsvp';
export default class ScopesScopeProjectsProjectSessionsRoute extends Route {
// =methods
/**
* Loads all sessions under the current scope and encapsulates them into
* an array of objects containing their associated users and targets.
* @return {Promise{[{session: SessionModel, user: UserModel, target: TargetModel}]}}
*/
async model() {
const { id: scope_id } = this.modelFor('scopes.scope.projects.project');
const sessions = await this.store.query('session', { scope_id });
return all(
sessions.map(session => hash({
session,
user: this.store.findRecord('user', session.user_id),
target: this.store.findRecord('target', session.target_id),
}))
);
}
}
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default class ScopesScopeProjectsProjectSessionsIndexRoute extends Route {
}
@@ -1,4 +1,7 @@
<Rose::Nav::Sidebar @title={{t "titles.resources"}} as |nav|>
<nav.link @route="scopes.scope.projects.project.sessions">
{{t "resources.sessions"}}
</nav.link>
<nav.link @route="scopes.scope.projects.project.targets">
{{t "resources.targets"}}
</nav.link>
@@ -0,0 +1,3 @@
{{title (t "resources.sessions")}}
{{outlet}}
@@ -0,0 +1,56 @@
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<BreadCrumbs />
</page.breadcrumbs>
<page.header>
<h2>{{t "resources.sessions"}}</h2>
</page.header>
<page.body>
{{#if @model}}
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t "form.label.id"}}</row.headerCell>
<row.headerCell>{{t "resources.user"}}</row.headerCell>
<row.headerCell>{{t "resources.target"}}</row.headerCell>
<row.headerCell>{{t "form.label.started"}}</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
{{#each @model as |sessionAggregate|}}
<body.row as |row|>
<row.headerCell>
<code>{{sessionAggregate.session.id}}</code>
</row.headerCell>
<row.cell>{{sessionAggregate.user.displayName}}</row.cell>
<row.cell>{{sessionAggregate.target.displayName}}</row.cell>
<row.cell>
<time datetime={{format-date-iso sessionAggregate.session.created_time}}>
{{format-date-iso-human sessionAggregate.session.created_time}}
</time>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{else}}
<Rose::Layout::Centered>
<Rose::Message @title={{t "titles.no-sessions"}} as |message|>
<message.description>
{{t "descriptions.no-sessions"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/if}}
</page.body>
</Rose::Layout::Page>
+7
View File
@@ -292,4 +292,11 @@ export default function() {
}
return target.update(updatedAttrs);
});
// session
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) {
return sessions.where(session => session.scopeId === scope_id);
});
this.get('/sessions/:id');
}
+35
View File
@@ -0,0 +1,35 @@
import factory from '../generated/factories/target';
import { trait } from 'ember-cli-mirage';
export default factory.extend({
id: (i) => `session-id-${i}`,
/**
* Sessions generated with this trait will automatically form
* associations to user, target, host set, and host (if available) within
* the current scope.
*/
withAssociations: trait({
afterCreate(record, server) {
const scopeId = record.scopeId;
if (record.scope.type === 'project') {
const orgScopeId = record.scope?.scope.id;
const user =
server.schema.users.where({ scopeId: orgScopeId }).models[0];
if (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 });
}
}
}
})
});
@@ -0,0 +1,14 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedSessionModelFactory
*/
export default Factory.extend({
type: 'tcp',
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
});
+11
View File
@@ -0,0 +1,11 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
user: belongsTo(),
target: belongsTo(),
host: belongsTo(),
hostSet: belongsTo(),
});
+1
View File
@@ -26,6 +26,7 @@ export default function(server) {
server.schema.scopes.where({type: 'project'}).models.forEach(scope => {
server.createList('host-catalog', 2, { scope }, 'withChildren');
server.createList('target', 2, { scope }, 'withRandomHostSets');
server.createList('session', 4, { scope }, 'withAssociations');
});
}
+22
View File
@@ -0,0 +1,22 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'session',
keyForRelationshipId(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_id`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
json.user_id = model.userId;
json.target_id = model.targetId;
json.host_set_id = model.hostSetId;
json.host_id = model.hostId;
return json;
}
});
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | format-date-iso-human', function(hooks) {
setupRenderingTest(hooks);
// Replace this with your real tests.
test('it renders', async function(assert) {
this.set('inputValue', new Date('2020-01-01T00:00:00.999Z'));
await render(hbs`{{format-date-iso-human inputValue}}`);
assert.equal(this.element.textContent.trim(), '2020-01-01 00:00:00');
});
});
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | format-date-iso', function(hooks) {
setupRenderingTest(hooks);
// Replace this with your real tests.
test('it renders', async function(assert) {
this.set('inputValue', new Date('2020-01-01T00:00:00.999Z'));
await render(hbs`{{format-date-iso inputValue}}`);
assert.equal(this.element.textContent.trim(), '2020-01-01T00:00:00.999Z');
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | scopes/scope/projects/project/sessions', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:scopes/scope/projects/project/sessions');
assert.ok(route);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | scopes/scope/projects/project/sessions/index', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:scopes/scope/projects/project/sessions/index');
assert.ok(route);
});
});
+5
View File
@@ -77,6 +77,8 @@ resources:
targets: Targets
accounts: Accounts
account: Account
sessions: Sessions
session: Session
titles:
iam: IAM
new: New
@@ -114,6 +116,7 @@ titles:
accounts: Accounts
account: Account
accounts-welcome: Welcome to Accounts
no-sessions: No Active Sessions
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.
@@ -136,6 +139,7 @@ descriptions:
no-available-users: No users are available to add to this group.
account-set-password: Set a password for the account.
accounts-welcome: No accounts are available in this auth method.
no-sessions: There are no active sessions at this time.
states:
info: Info
success: Success
@@ -157,6 +161,7 @@ form:
default_port: Default Port
current_password: Current Password
new_password: New Password
started: Started
errors:
titles:
unauthenticated-error: You are not signed in