Merge pull request #423 from hashicorp/init-sessions-view

Add sessions sidebar in desktop
This commit is contained in:
Randall Morey authored and GitHub committed 2021-01-29 16:08:41 -05:00
commit 69d2ca6240
25 files changed
+934 -752

No files matched your search

+10 -3
View File
@@ -325,14 +325,21 @@ export default function() {
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) {
// To simulate changes to `session.status` that may occur in the backend,
// we quietly randomize the value of the field on GET.
// To populate sessions for logged in user,
// update alternate sessions to auth user.
// But only if not in testing mode.
// In tests, we need deterministic statuses.
if (!Ember.testing) {
sessions.where(session => session.scopeId === scope_id)
.models
.forEach(session => session.update({
status: pickRandomStatusString()
}));
.forEach(session => {
session.update({
status: pickRandomStatusString()
});
if(session.id.split('-').pop() % 2) session.update({
userId: 'authenticateduser'
});
});
}
return sessions.where(session => session.scopeId === scope_id)
});
+2 -3
View File
@@ -1,4 +1,4 @@
import factory from '../generated/factories/target';
import factory from '../generated/factories/session';
import { trait } from 'ember-cli-mirage';
import { random } from 'faker';
@@ -26,8 +26,7 @@ export default factory.extend({
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];
const user = server.schema.users.where({ scopeId: orgScopeId }).models[0];
if (user) record.update({ user });
}
const target = server.schema.targets.where({ scopeId }).models[0];
+1 -1
View File
@@ -38,7 +38,7 @@ export function authHandler({ scopes, authMethods }, request) {
id: 'token123',
token: 'thetokenstring',
account_id: '1',
user_id: 'user123',
user_id: 'authenticateduser',
auth_method_id: 'authmethod123',
created_time: '',
updated_time: '',
+2 -1
View File
@@ -13,6 +13,8 @@ export default function(server) {
scope: { id: globalScope.id, type: globalScope.type }
}, 'withChildren')[0];
server.create('user', { id: 'authenticateduser' });
// Auth
server.createList('auth-method', 1, { scope: globalScope }, 'withAccountsAndUsers');
server.createList('auth-method', 3, { scope: orgScope }, 'withAccountsAndUsers');
@@ -30,5 +32,4 @@ export default function(server) {
server.createList('target', 2, { scope }, 'withRandomHostSets');
server.createList('session', 4, { scope }, 'withAssociations');
});
}
@@ -44,6 +44,7 @@ module('Unit | Authenticator | password', function (hooks) {
const authMethod = { id: 'paum_1234' };
this.server.create('scope', { id: 'global', type: 'global' });
this.server.create('auth-method', { id: authMethod.id, scopeId: 'global' });
this.server.create('user', { scopeId: 'global' });
await authenticator.authenticate(creds, null, { scope, authMethod });
assert.equal(
applicationAdapter.headers.Authorization,
+1 -1
View File
@@ -91,7 +91,7 @@ export default class ScopesScopeRoute extends Route {
}
/**
* Renders the scope-specific sidebar template.
* Renders the scope-specific header template.
* @override
* @param {object} controller
* @param {object} model
@@ -67,6 +67,7 @@ module('Acceptance | authentication', function (hooks) {
globalAuthMethod = this.server.create('auth-method', {
scope: globalScope,
});
this.server.create('user', { scope: globalScope });
authMethod = this.server.create('auth-method', { scope: orgScope });
orgScopeID = orgScope.id;
globalAuthMethodID = globalAuthMethod.id;
+1
View File
@@ -14,6 +14,7 @@ Router.map(function () {
});
this.route('projects', function () {
this.route('targets', function () {});
this.route('sessions', function() {});
});
});
});
@@ -0,0 +1,102 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { all, hash } from 'rsvp';
import { task, timeout } from 'ember-concurrency';
import { A } from '@ember/array';
import config from '../../../../config/environment';
const POLL_TIMEOUT_SECONDS = config.sessionPollingTimeoutSeconds;
export default class ScopesScopeProjectsSessionsRoute extends Route {
// =services
@service session;
// =attributes
/**
* A simple Ember Concurrency-based polling task that refreshes the route
* every POLL_TIMEOUT_SECONDS seconds. This is necessary to display changes
* to session `status` that may occur.
*
* NOTE: tasks are sort of attributes and sort of methods, but they are not
* language-level constructs. Thus we annotate this task as if it
* is an attribute.
* @type {Task}
*/
@task(function * () {
while(true) {
yield timeout(POLL_TIMEOUT_SECONDS * 1000);
yield this.refresh();
}
}).drop() poller;
// =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 => sessions.filter(
session => 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;
}
/**
* When this route is activated (entered), begin polling for changes.
*/
activate() {
this.poller.perform();
}
/**
* When this route is deactivated (exited), stop polling for changes.
*/
deactivate() {
this.poller.cancelAll();
}
}
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
}
-4
View File
@@ -105,10 +105,6 @@
padding-left: sizing.rems(l) * 3;
position: relative;
.rose-header-brand {
display: none;
}
.logotype {
display: none;
}
+1 -5
View File
@@ -1,5 +1 @@
{{page-title (t 'resources.scope.title_plural')}}
<main>
{{outlet}}
</main>
{{outlet}}
+1 -36
View File
@@ -1,36 +1 @@
<Rose::Layout::Page as |page|>
<page.header>
<h2>
{{t "resources.scope.title_plural"}}
</h2>
</page.header>
<page.body>
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t 'resources.scope.title'}}</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
{{#each @model as |scope|}}
<body.row as |row|>
<row.cell>
<Rose::LinkButton
@route="scopes.scope"
@model={{scope.id}}
>
{{scope.name}}
</Rose::LinkButton>
</row.cell>
<row.cell>
{{scope.id}}
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
</page.body>
</Rose::Layout::Page>
{{outlet}}
+1 -1
View File
@@ -1 +1 @@
{{outlet}}
{{outlet}}
@@ -1 +1,25 @@
{{outlet}}
<Rose::Layout::Sidebar as |sidebarLayout|>
<sidebarLayout.sidebar>
<Rose::Nav::Sidebar as |nav|>
<nav.link @route="scopes.scope.projects.targets">
<Rose::Icon
@name="app-icons/target"
@size="medium" />
{{t "resources.target.title_plural"}}
</nav.link>
<nav.link @route="scopes.scope.projects.sessions">
<Rose::Icon
@name="app-icons/action-session"
@size="medium" />
{{t "resources.session.title_plural"}}
</nav.link>
</Rose::Nav::Sidebar>
</sidebarLayout.sidebar>
<sidebarLayout.body>
<main>
{{outlet}}
</main>
</sidebarLayout.body>
</Rose::Layout::Sidebar>
@@ -0,0 +1 @@
{{outlet}}
@@ -0,0 +1,58 @@
<Rose::Layout::Page as |page|>
<page.header>
<h2>{{t "resources.session.title_plural"}}</h2>
</page.header>
<page.body>
{{#if @model}}
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t "form.id.label"}}</row.headerCell>
<row.headerCell>{{t "resources.target.title"}}</row.headerCell>
<row.headerCell>{{t "form.started.label"}}</row.headerCell>
<row.headerCell>{{t "form.status.label"}}</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
{{#each @model as |sessionAggregate|}}
<body.row as |row|>
<row.headerCell class="nowrap">
<Rose::Icon
class={{concat "session-status-" sessionAggregate.session.status}}
@name="app-icons/action-session"
@size="medium" />
<code>{{sessionAggregate.session.id}}</code>
</row.headerCell>
<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>
<row.cell>
<Rose::Badge>{{sessionAggregate.session.status}}</Rose::Badge>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{else}}
<Rose::Layout::Centered>
<Rose::Message @title={{t "resources.session.messages.none.title"}} as |message|>
<message.description>
{{t "resources.session.messages.none.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/if}}
</page.body>
</Rose::Layout::Page>
+1
View File
@@ -42,6 +42,7 @@ module.exports = function (environment) {
appName: APP_NAME,
notifyTimeout: 4000,
sessionPollingTimeoutSeconds: 2.5,
};
if (environment === 'development') {
+1
View File
@@ -54,6 +54,7 @@
"ember-cli-sri": "^2.1.1",
"ember-cli-template-lint": "^2.0.2",
"ember-cli-uglify": "^3.0.0",
"ember-concurrency": "^1.3.0",
"ember-electron": "https://github.com/adopted-ember-addons/ember-electron#v3.0.0-beta.4",
"ember-exam": "^6.0.1",
"ember-export-application-global": "^2.0.1",
-3
View File
@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.97503 7.25H10.0232C9.83952 5.33678 9.13836 3.52559 7.99998 2.00183C6.85742 3.53084 6.15589 5.3466 5.97503 7.25ZM10.025 8.75C9.84413 10.6534 9.14259 12.4692 8.00003 13.9982C6.86166 12.4744 6.16049 10.6632 5.97679 8.75H10.025ZM11.5309 8.75H14.4572C14.1478 11.4433 12.1921 13.6359 9.62187 14.296C10.707 12.6405 11.3662 10.7347 11.5309 8.75ZM14.4572 7.25H11.5293C11.3619 5.25743 10.7034 3.35494 9.62165 1.70392C12.192 2.36401 14.1478 4.55661 14.4572 7.25ZM4.46913 7.25H1.5428C1.85224 4.55668 3.80789 2.36413 6.37814 1.70397C5.29298 3.35946 4.63378 5.2653 4.46913 7.25ZM4.47076 8.75H1.5428C1.85224 11.4434 3.808 13.636 6.37836 14.2961C5.29662 12.6451 4.63811 10.7426 4.47076 8.75ZM0 8C0 3.58172 3.58172 0 8 0L8.00195 3.05531e-06C12.4193 0.00105872 16 3.58237 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 990 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.15625 12.1875H5V16H0V0H12V5.125H15.5V16H12H10.5H7.15625V12.1875ZM10.5 14.5H8.65625V10.6875H3.5V14.5H1.5V1.5H10.5V5.125V14.5ZM12 14.5H14V6.625H12V14.5ZM3 3H4.88235V5H3V3ZM4.88235 7H3V9H4.88235V7ZM7 3H8.88235V5H7V3ZM8.88235 7H7V9H8.88235V7Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 417 B

-71
View File
@@ -1,71 +0,0 @@
<svg
width="30"
height="30"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 67 67">
<g class="logomark">
<polygon
fill="#f04e54"
points="9.89 64.1 9.89 59.28 19.9 59.28 19.9 56.3 15.05 56.3 15.05 51.45 37.97 51.45 27.32 33.01 37.97 14.57 14.47 14.57 14.47 40.15 1.82 40.15 1.82 1.92 45.34 1.92 52.61 14.5 41.92 33.01 52.83 51.9 45.78 64.1 9.89 64.1">
</polygon>
<rect
fill="#f04e54"
x="6.98"
y="51.45"
width="4.85"
height="4.85">
</rect>
<rect
fill="#f04e54"
x="1.82"
y="59.25"
width="4.85"
height="4.85">
</rect>
</g>
<g class="logotype">
<path
fill="#ffffff"
d="M104.58,32.17c3.08-1.33,4.48-3.21,4.48-6.54V21.45c0-5.44-3.09-8.59-10.72-8.59H82.5v40.3H99.44c7.32,0,11-3.57,11-9.26V39.61C110.45,35.67,108.27,33,104.58,32.17Zm-14.46-13h7.32c2.78,0,4,1,4,3.27V26c0,2.36-1.08,3.76-4.05,3.76H90.12Zm12.71,23.78c0,3-1.46,3.94-4.91,3.94h-7.8v-11h8.47c3,0,4.24,1.33,4.24,4Z">
</path>
<path
fill="#ffffff"
d="M126.48,23.09c-10.1,0-12.82,5.56-12.82,11.62v7.44c0,6.05,2.72,11.61,12.82,11.61s12.83-5.56,12.83-11.61V34.71C139.31,28.65,136.58,23.09,126.48,23.09Zm5.45,19.3c0,3.33-1.51,5.08-5.45,5.08S121,45.72,121,42.39V34.47c0-3.34,1.51-5.09,5.44-5.09s5.45,1.75,5.45,5.09Z">
</path>
<path
fill="#ffffff"
d="M161.46,23.69V44.15c-2.67,1.39-5.88,2.48-7.69,2.48s-2.36-.79-2.36-2.37V23.69H144V45.05c0,5.27,1.75,8.71,6.65,8.71a29.59,29.59,0,0,0,11.8-3.08l.73,2.48h5.63V23.69Z">
</path>
<path
fill="#ffffff"
d="M193.34,23.09a29.59,29.59,0,0,0-11.8,3.08l-.72-2.48h-5.63V53.16h7.38V32.71c2.66-1.39,5.87-2.48,7.68-2.48s2.36.79,2.36,2.36V53.16H200V31.8C200,26.54,198.24,23.09,193.34,23.09Z">
</path>
<path
fill="#ffffff"
d="M222.44,11.66V23.94a41,41,0,0,0-7.87-.85c-6.83,0-9.73,3.87-9.73,10.41v10c0,6.72,3.14,10.22,9.07,10.22a15.48,15.48,0,0,0,9.32-3.08l.72,2.48h5.87V10.63Zm0,33.15a10.43,10.43,0,0,1-6.59,2.66c-2.73,0-3.63-1.33-3.63-3.75V33.32c0-2.67,1-3.94,3.69-3.94a33,33,0,0,1,6.53.79Z">
</path>
<path
fill="#ffffff"
d="M245.74,23.09a38.76,38.76,0,0,0-10.11,1.39l.91,5.63a40.74,40.74,0,0,1,8.17-.91c4.71,0,5.62,1.15,5.62,4.42V36.7h-7.07c-6,0-8.6,2.3-8.6,8.29,0,5.09,2.3,8.77,7.69,8.77a16.06,16.06,0,0,0,8.77-2.6l.54,2h6.06V33.74C257.72,26.35,255,23.09,245.74,23.09Zm4.59,23.29a11.57,11.57,0,0,1-5.56,1.51c-2.3,0-3-.9-3-3,0-2.24.67-2.9,3.09-2.9h5.44Z">
</path>
<path
fill="#ffffff"
d="M277.92,23.09A33,33,0,0,0,270.18,27l-.48-3.27h-6.24V53.16h7.39v-19a56,56,0,0,1,7.8-4.29Z">
</path>
<path
fill="#ffffff"
d="M300.61,23.69l-6.77,22.39-6.72-22.39h-7.56l9.32,29.47h2.59l-3.68,11.92h7l3.93-11.92,9.44-29.47Z">
</path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

@@ -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);
});
});
@@ -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);
});
});
+698 -619
View File
File diff suppressed because it is too large. Load diff