From 9e482d643484e5a14bdd599543e1b1a19406303e Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 10:16:40 -0400 Subject: [PATCH 1/7] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20initial=20ses?= =?UTF-8?q?sion=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/session.js | 30 ++++++++++++++++++++ addons/api/addon/models/session.js | 3 ++ addons/api/app/models/session.js | 1 + addons/api/tests/unit/models/session-test.js | 13 +++++++++ 4 files changed, 47 insertions(+) create mode 100644 addons/api/addon/generated/models/session.js create mode 100644 addons/api/addon/models/session.js create mode 100644 addons/api/app/models/session.js create mode 100644 addons/api/tests/unit/models/session-test.js diff --git a/addons/api/addon/generated/models/session.js b/addons/api/addon/generated/models/session.js new file mode 100644 index 000000000..c4dd740c9 --- /dev/null +++ b/addons/api/addon/generated/models/session.js @@ -0,0 +1,30 @@ +import BaseModel from '../../models/base'; +import { attr } from '@ember-data/model'; + +export default class GeneratedSessionModel extends BaseModel { + + // =attributes + + @attr('string') user_id; + @attr('string') target_id; + @attr('string') host_id; + @attr('string') host_set_id; + + @attr('string', { + description: 'The name of the current status of this session.', + }) + status; + + @attr('date', { + description: 'The time this resource was created\nOutput only.', + readOnly: true, + }) + created_time; + + @attr('date', { + description: 'The time this resource was last updated\nOutput only.', + readOnly: true, + }) + updated_time; + +} diff --git a/addons/api/addon/models/session.js b/addons/api/addon/models/session.js new file mode 100644 index 000000000..a624fb6b0 --- /dev/null +++ b/addons/api/addon/models/session.js @@ -0,0 +1,3 @@ +import Model from '@ember-data/model'; + +export default class SessionModel extends Model { } diff --git a/addons/api/app/models/session.js b/addons/api/app/models/session.js new file mode 100644 index 000000000..3106229d6 --- /dev/null +++ b/addons/api/app/models/session.js @@ -0,0 +1 @@ +export { default } from 'api/models/session'; diff --git a/addons/api/tests/unit/models/session-test.js b/addons/api/tests/unit/models/session-test.js new file mode 100644 index 000000000..2d373a2f6 --- /dev/null +++ b/addons/api/tests/unit/models/session-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | session', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('session', {}); + assert.ok(model); + }); +}); From 7a17ff3bfa3aa685d9ce4d97d180ce7156784663 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 10:59:07 -0400 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20support=20for?= =?UTF-8?q?=20custom=20cancel=20session=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/session.js | 5 +++ addons/api/addon/models/session.js | 28 +++++++++++++++-- addons/api/addon/serializers/session.js | 31 +++++++++++++++++++ addons/api/app/serializers/session.js | 1 + .../tests/unit/serializers/session-test.js | 31 +++++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 addons/api/addon/serializers/session.js create mode 100644 addons/api/app/serializers/session.js create mode 100644 addons/api/tests/unit/serializers/session-test.js diff --git a/addons/api/addon/generated/models/session.js b/addons/api/addon/generated/models/session.js index c4dd740c9..ee751a988 100644 --- a/addons/api/addon/generated/models/session.js +++ b/addons/api/addon/generated/models/session.js @@ -27,4 +27,9 @@ export default class GeneratedSessionModel extends BaseModel { }) updated_time; + @attr('number', { + description: 'Current version number of this resource.', + }) + version; + } diff --git a/addons/api/addon/models/session.js b/addons/api/addon/models/session.js index a624fb6b0..b37f08fc2 100644 --- a/addons/api/addon/models/session.js +++ b/addons/api/addon/models/session.js @@ -1,3 +1,27 @@ -import Model from '@ember-data/model'; +import GeneratedSessionModel from '../generated/models/session'; -export default class SessionModel extends Model { } +export default class SessionModel extends GeneratedSessionModel { + + // =methods + + /** + * Cancels the session via the `cancel` method. + * See serializer and adapter for more information. + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + cancelSession(options = { adapterOptions: {} }) { + const defaultAdapterOptions = { + method: 'cancel', + }; + return this.save({ + ...options, + adapterOptions: { + ...defaultAdapterOptions, + ...options.adapterOptions, + }, + }); + } + +} diff --git a/addons/api/addon/serializers/session.js b/addons/api/addon/serializers/session.js new file mode 100644 index 000000000..d8d5ea7f8 --- /dev/null +++ b/addons/api/addon/serializers/session.js @@ -0,0 +1,31 @@ +import ApplicationSerializer from './application'; + +export default class SessionSerializer extends ApplicationSerializer { + // =methods + + /** + * If `adapterOptions.method` is `cancel`, the serialization should + * include the version field only. + * @override + * @param {Snapshot} snapshot + * @return {object} + */ + serialize(snapshot) { + let serialized = super.serialize(...arguments); + if (snapshot?.adapterOptions?.method === 'cancel') { + serialized = this.serializeForCancel(snapshot); + } + return serialized; + } + + /** + * Returns a payload containing only version + * @param {Snapshot} snapshot + * @return {object} + */ + serializeForCancel(snapshot) { + return { + version: snapshot.attr('version'), + }; + } +} diff --git a/addons/api/app/serializers/session.js b/addons/api/app/serializers/session.js new file mode 100644 index 000000000..407fe5c23 --- /dev/null +++ b/addons/api/app/serializers/session.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/session'; diff --git a/addons/api/tests/unit/serializers/session-test.js b/addons/api/tests/unit/serializers/session-test.js new file mode 100644 index 000000000..fde7c215f --- /dev/null +++ b/addons/api/tests/unit/serializers/session-test.js @@ -0,0 +1,31 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | session', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let serializer = store.serializerFor('session'); + + assert.ok(serializer); + }); + + test('it serializes only version when `adapterOptions.method` is `cancel`', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('session'); + const record = store.createRecord('session', { + version: 1, + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = { + method: 'cancel', + }; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + version: 1, + }); + }); +}); From 4229658815795ed6b840a4e5c75159a598593be4 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 12:54:31 -0400 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20sessions=20lis?= =?UTF-8?q?t=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rose/addon/styles/rose/_typography.scss | 4 ++ ui/core/app/formats.js | 9 +++ ui/core/app/helpers/format-date-iso-human.js | 7 +++ ui/core/app/helpers/format-date-iso.js | 5 ++ ui/core/app/router.js | 1 + .../scopes/scope/projects/project/sessions.js | 25 +++++++++ .../scope/projects/project/sessions/index.js | 4 ++ .../scope/projects/project/_sidebar.hbs | 3 + .../scope/projects/project/sessions.hbs | 3 + .../scope/projects/project/sessions/index.hbs | 56 +++++++++++++++++++ ui/core/mirage/config.js | 7 +++ ui/core/mirage/factories/session.js | 35 ++++++++++++ ui/core/mirage/generated/factories/session.js | 14 +++++ ui/core/mirage/models/session.js | 11 ++++ ui/core/mirage/scenarios/default.js | 1 + ui/core/mirage/serializers/session.js | 22 ++++++++ .../helpers/format-date-iso-human-test.js | 17 ++++++ .../helpers/format-date-iso-test.js | 17 ++++++ .../scope/projects/project/sessions-test.js | 11 ++++ .../projects/project/sessions/index-test.js | 11 ++++ ui/core/translations/en-us.yaml | 5 ++ 21 files changed, 268 insertions(+) create mode 100644 ui/core/app/helpers/format-date-iso-human.js create mode 100644 ui/core/app/helpers/format-date-iso.js create mode 100644 ui/core/app/routes/scopes/scope/projects/project/sessions.js create mode 100644 ui/core/app/routes/scopes/scope/projects/project/sessions/index.js create mode 100644 ui/core/app/templates/scopes/scope/projects/project/sessions.hbs create mode 100644 ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs create mode 100644 ui/core/mirage/factories/session.js create mode 100644 ui/core/mirage/generated/factories/session.js create mode 100644 ui/core/mirage/models/session.js create mode 100644 ui/core/mirage/serializers/session.js create mode 100644 ui/core/tests/integration/helpers/format-date-iso-human-test.js create mode 100644 ui/core/tests/integration/helpers/format-date-iso-test.js create mode 100644 ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js create mode 100644 ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js diff --git a/addons/rose/addon/styles/rose/_typography.scss b/addons/rose/addon/styles/rose/_typography.scss index b44914ce8..c5fda86e4 100644 --- a/addons/rose/addon/styles/rose/_typography.scss +++ b/addons/rose/addon/styles/rose/_typography.scss @@ -54,3 +54,7 @@ p { a { color: var(--action); } + +time { + font-variant-numeric: tabular-nums; +} diff --git a/ui/core/app/formats.js b/ui/core/app/formats.js index f5d1f1502..9ea07ca26 100644 --- a/ui/core/app/formats.js +++ b/ui/core/app/formats.js @@ -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', diff --git a/ui/core/app/helpers/format-date-iso-human.js b/ui/core/app/helpers/format-date-iso-human.js new file mode 100644 index 000000000..2744a4eeb --- /dev/null +++ b/ui/core/app/helpers/format-date-iso-human.js @@ -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/, ''); +}); diff --git a/ui/core/app/helpers/format-date-iso.js b/ui/core/app/helpers/format-date-iso.js new file mode 100644 index 000000000..52cd9687d --- /dev/null +++ b/ui/core/app/helpers/format-date-iso.js @@ -0,0 +1,5 @@ +import { helper } from '@ember/component/helper'; + +export default helper(function formatDateIso(params/*, hash*/) { + return params[0].toISOString(); +}); diff --git a/ui/core/app/router.js b/ui/core/app/router.js index 204ced768..0894289f9 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -52,6 +52,7 @@ Router.map(function () { }); this.route('new'); }); + this.route('sessions', function() {}); }); this.route('new'); }); diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions.js b/ui/core/app/routes/scopes/scope/projects/project/sessions.js new file mode 100644 index 000000000..ed7c699f0 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions.js @@ -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), + })) + ); + } + +} diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions/index.js b/ui/core/app/routes/scopes/scope/projects/project/sessions/index.js new file mode 100644 index 000000000..a8ae56692 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions/index.js @@ -0,0 +1,4 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectSessionsIndexRoute extends Route { +} diff --git a/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs b/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs index ae4f79e0b..e29af0451 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs @@ -1,4 +1,7 @@ + + {{t "resources.sessions"}} + {{t "resources.targets"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions.hbs new file mode 100644 index 000000000..37fea125e --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions.hbs @@ -0,0 +1,3 @@ +{{title (t "resources.sessions")}} + +{{outlet}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs new file mode 100644 index 000000000..abbbbdfac --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs @@ -0,0 +1,56 @@ + + + + + + + +

{{t "resources.sessions"}}

+
+ + + + {{#if @model}} + + + + + {{t "form.label.id"}} + {{t "resources.user"}} + {{t "resources.target"}} + {{t "form.label.started"}} + + + + {{#each @model as |sessionAggregate|}} + + + {{sessionAggregate.session.id}} + + {{sessionAggregate.user.displayName}} + {{sessionAggregate.target.displayName}} + + + + + {{/each}} + + + + {{else}} + + + + + {{t "descriptions.no-sessions"}} + + + + + {{/if}} + + + +
diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index e7044298f..844211c61 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -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'); } diff --git a/ui/core/mirage/factories/session.js b/ui/core/mirage/factories/session.js new file mode 100644 index 000000000..9e5e31913 --- /dev/null +++ b/ui/core/mirage/factories/session.js @@ -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 }); + } + } + } + }) + +}); diff --git a/ui/core/mirage/generated/factories/session.js b/ui/core/mirage/generated/factories/session.js new file mode 100644 index 000000000..25aca3759 --- /dev/null +++ b/ui/core/mirage/generated/factories/session.js @@ -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(), + +}); diff --git a/ui/core/mirage/models/session.js b/ui/core/mirage/models/session.js new file mode 100644 index 000000000..91a4da594 --- /dev/null +++ b/ui/core/mirage/models/session.js @@ -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(), + +}); diff --git a/ui/core/mirage/scenarios/default.js b/ui/core/mirage/scenarios/default.js index 9492f92e3..5d1a85fe6 100644 --- a/ui/core/mirage/scenarios/default.js +++ b/ui/core/mirage/scenarios/default.js @@ -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'); }); } diff --git a/ui/core/mirage/serializers/session.js b/ui/core/mirage/serializers/session.js new file mode 100644 index 000000000..3e14a23e6 --- /dev/null +++ b/ui/core/mirage/serializers/session.js @@ -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; + } + +}); diff --git a/ui/core/tests/integration/helpers/format-date-iso-human-test.js b/ui/core/tests/integration/helpers/format-date-iso-human-test.js new file mode 100644 index 000000000..94eec92fa --- /dev/null +++ b/ui/core/tests/integration/helpers/format-date-iso-human-test.js @@ -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'); + }); +}); diff --git a/ui/core/tests/integration/helpers/format-date-iso-test.js b/ui/core/tests/integration/helpers/format-date-iso-test.js new file mode 100644 index 000000000..2954d3890 --- /dev/null +++ b/ui/core/tests/integration/helpers/format-date-iso-test.js @@ -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'); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js new file mode 100644 index 000000000..70769438e --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions-test.js @@ -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); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js new file mode 100644 index 000000000..a774bf223 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/sessions/index-test.js @@ -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); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index e458c2b50..2d13fa33e 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -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 From 2aa330fd3612b4a84b3e5ccc93a80fe6a848dc87 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 15:38:32 -0400 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20cancel=20sessi?= =?UTF-8?q?on=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../styles/rose/components/table/_table.scss | 4 ++++ .../scopes/scope/projects/project/sessions.js | 22 +++++++++++++++++++ .../scopes/scope/groups/group/members.hbs | 2 +- .../host-catalog/host-sets/host-set/hosts.hbs | 2 +- .../scope/projects/project/sessions/index.hbs | 6 +++++ .../project/targets/target/host-sets.hbs | 2 +- .../scopes/scope/roles/role/principals.hbs | 2 +- ui/core/mirage/config.js | 14 ++++++++++++ ui/core/translations/en-us.yaml | 2 ++ 9 files changed, 52 insertions(+), 4 deletions(-) diff --git a/addons/rose/addon/styles/rose/components/table/_table.scss b/addons/rose/addon/styles/rose/components/table/_table.scss index ba63d37aa..956ab5faa 100644 --- a/addons/rose/addon/styles/rose/components/table/_table.scss +++ b/addons/rose/addon/styles/rose/components/table/_table.scss @@ -34,4 +34,8 @@ padding: sizing.rems(l) sizing.rems(s); } } + + [class*="rose-button"] { + margin-bottom: 0; + } } diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions.js b/ui/core/app/routes/scopes/scope/projects/project/sessions.js index ed7c699f0..48bbb5cc0 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/sessions.js +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions.js @@ -1,8 +1,15 @@ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; import { all, hash } from 'rsvp'; export default class ScopesScopeProjectsProjectSessionsRoute extends Route { + // =services + + @service intl; + @service notify; + // =methods /** @@ -22,4 +29,19 @@ export default class ScopesScopeProjectsProjectSessionsRoute extends Route { ); } + /** + * Cancels the specified session and notifies user of success or error. + * @param {SessionModel} + */ + @action + async cancelSession(session) { + try { + await session.cancelSession(); + this.notify.success(this.intl.t('notify.session-canceled')); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } + } diff --git a/ui/core/app/templates/scopes/scope/groups/group/members.hbs b/ui/core/app/templates/scopes/scope/groups/group/members.hbs index fb562164b..24a6e13d9 100644 --- a/ui/core/app/templates/scopes/scope/groups/group/members.hbs +++ b/ui/core/app/templates/scopes/scope/groups/group/members.hbs @@ -7,7 +7,7 @@ {{t 'form.label.name'}} {{t 'form.label.id'}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs index 437140df4..b0ef4a625 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs @@ -6,7 +6,7 @@ {{t "form.label.name"}} {{t "form.label.type"}} {{t "form.label.id"}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs index abbbbdfac..83e480e5e 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs @@ -19,6 +19,7 @@ {{t "resources.user"}} {{t "resources.target"}} {{t "form.label.started"}} + {{t "titles.actions"}} @@ -34,6 +35,11 @@ {{format-date-iso-human sessionAggregate.session.created_time}} + + + {{t "actions.cancel"}} + + {{/each}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs index 40f12df7f..583a8df55 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/host-sets.hbs @@ -8,7 +8,7 @@ {{t "form.label.name"}} {{t "form.label.type"}} {{t "form.label.id"}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/app/templates/scopes/scope/roles/role/principals.hbs b/ui/core/app/templates/scopes/scope/roles/role/principals.hbs index b364b136f..160c91c7b 100644 --- a/ui/core/app/templates/scopes/scope/roles/role/principals.hbs +++ b/ui/core/app/templates/scopes/scope/roles/role/principals.hbs @@ -8,7 +8,7 @@ {{t 'form.label.name'}} {{t 'form.label.type'}} {{t 'form.label.id'}} - {{t "form.label.actions"}} + {{t "titles.actions"}} diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 844211c61..fa23ee0e4 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -299,4 +299,18 @@ export default function() { return sessions.where(session => session.scopeId === scope_id); }); this.get('/sessions/:id'); + this.post('/sessions/:idMethod', function ({ sessions }, { params: { idMethod } }) { + const attrs = this.normalizedRequestAttrs(); + const id = idMethod.split(':')[0]; + const method = idMethod.split(':')[1]; + const session = sessions.find(id); + const updatedAttrs = { + version: attrs.version + }; + if (method === 'cancel') { + updatedAttrs.status = 'canceled'; + } + return session.update(updatedAttrs); + }); + } diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 2d13fa33e..7fb4748de 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -117,6 +117,7 @@ titles: account: Account accounts-welcome: Welcome to Accounts no-sessions: No Active Sessions + actions: Actions 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. @@ -180,3 +181,4 @@ notify: create-success: Created succesfully. save-success: Saved succesfully. delete-success: Deleted successfully. + session-canceled: Session canceled. From 5fb1b1465887ed0de1f2acf90a483c7b6b348f6a Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 15:59:42 -0400 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20show=20session=20sta?= =?UTF-8?q?tus=20in=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scopes/scope/projects/project/sessions/index.hbs | 4 ++++ ui/core/mirage/config.js | 2 +- ui/core/mirage/factories/session.js | 7 +++++++ ui/core/translations/en-us.yaml | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs index 83e480e5e..6fe82d12b 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/sessions/index.hbs @@ -19,6 +19,7 @@ {{t "resources.user"}} {{t "resources.target"}} {{t "form.label.started"}} + {{t "form.label.status"}} {{t "titles.actions"}} @@ -35,6 +36,9 @@ {{format-date-iso-human sessionAggregate.session.created_time}} + + {{sessionAggregate.session.status}} + {{t "actions.cancel"}} diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index fa23ee0e4..87bf05300 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -308,7 +308,7 @@ export default function() { version: attrs.version }; if (method === 'cancel') { - updatedAttrs.status = 'canceled'; + updatedAttrs.status = 'canceling'; } return session.update(updatedAttrs); }); diff --git a/ui/core/mirage/factories/session.js b/ui/core/mirage/factories/session.js index 9e5e31913..efe1bd63a 100644 --- a/ui/core/mirage/factories/session.js +++ b/ui/core/mirage/factories/session.js @@ -1,9 +1,16 @@ import factory from '../generated/factories/target'; import { trait } from 'ember-cli-mirage'; +import { random } from 'faker'; export default factory.extend({ id: (i) => `session-id-${i}`, + status: () => random.arrayElement([ + 'pending', + 'active', + 'canceling', + 'terminated' + ]), /** * Sessions generated with this trait will automatically form diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 7fb4748de..a37c9bfe3 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -163,6 +163,7 @@ form: current_password: Current Password new_password: New Password started: Started + status: Status errors: titles: unauthenticated-error: You are not signed in From b8257fe6dc4e4cc05046fca714b4d3e5802d42eb Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 18 Sep 2020 16:03:52 -0400 Subject: [PATCH 6/7] chore: status is read-only --- addons/api/addon/generated/models/session.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/api/addon/generated/models/session.js b/addons/api/addon/generated/models/session.js index ee751a988..a33426e4c 100644 --- a/addons/api/addon/generated/models/session.js +++ b/addons/api/addon/generated/models/session.js @@ -12,6 +12,7 @@ export default class GeneratedSessionModel extends BaseModel { @attr('string', { description: 'The name of the current status of this session.', + readOnly: true, }) status; From 711818216c42ad2bfdb8d59f54c3a3e0a55e3919 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 19 Sep 2020 09:36:48 -0400 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20session=20poll?= =?UTF-8?q?ing=20to=20display=20changes=20to=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scopes/scope/projects/project/sessions.js | 38 +++++++++++++++++++ ui/core/mirage/config.js | 17 ++++++++- ui/core/mirage/factories/session.js | 16 +++++--- ui/core/package.json | 1 + yarn.lock | 9 +++++ 5 files changed, 73 insertions(+), 8 deletions(-) diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions.js b/ui/core/app/routes/scopes/scope/projects/project/sessions.js index 48bbb5cc0..b41d76d7a 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/sessions.js +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions.js @@ -2,6 +2,9 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { all, hash } from 'rsvp'; +import { task, timeout } from 'ember-concurrency'; + +const POLL_TIMEOUT_SECONDS = 2.5; export default class ScopesScopeProjectsProjectSessionsRoute extends Route { @@ -10,6 +13,25 @@ export default class ScopesScopeProjectsProjectSessionsRoute extends Route { @service intl; @service notify; + // =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 /** @@ -29,6 +51,22 @@ export default class ScopesScopeProjectsProjectSessionsRoute extends Route { ); } + /** + * 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(); + } + + // =actions + /** * Cancels the specified session and notifies user of success or error. * @param {SessionModel} diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 87bf05300..6803aac3d 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -1,5 +1,6 @@ import config from '../config/environment'; import { authHandler, deauthHandler } from './route-handlers/auth'; +import { pickRandomStatusString } from './factories/session'; //import { Response } from 'miragejs'; export default function() { @@ -295,10 +296,22 @@ export default function() { // session + // To simulate changes to `session.status` that may occur in the backend, + // we quietly randomize the value of the field on GET. this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) { - return sessions.where(session => session.scopeId === scope_id); + sessions.where(session => session.scopeId === scope_id) + .models + .forEach(session => session.update({ + status: pickRandomStatusString() + })); + return sessions.where(session => session.scopeId === scope_id) + }); + this.get('/sessions/:id', function ({ sessions }, { params: { id } }) { + const session = sessions.find(id); + return session.update({ + status: pickRandomStatusString() + }); }); - this.get('/sessions/:id'); this.post('/sessions/:idMethod', function ({ sessions }, { params: { idMethod } }) { const attrs = this.normalizedRequestAttrs(); const id = idMethod.split(':')[0]; diff --git a/ui/core/mirage/factories/session.js b/ui/core/mirage/factories/session.js index efe1bd63a..0100a4b75 100644 --- a/ui/core/mirage/factories/session.js +++ b/ui/core/mirage/factories/session.js @@ -2,15 +2,19 @@ import factory from '../generated/factories/target'; import { trait } from 'ember-cli-mirage'; import { random } from 'faker'; +const statusStrings = [ + 'pending', + 'active', + 'canceling', + 'terminated' +]; + +export const pickRandomStatusString = () => random.arrayElement(statusStrings); + export default factory.extend({ id: (i) => `session-id-${i}`, - status: () => random.arrayElement([ - 'pending', - 'active', - 'canceling', - 'terminated' - ]), + status: pickRandomStatusString, /** * Sessions generated with this trait will automatically form diff --git a/ui/core/package.json b/ui/core/package.json index 258f13a98..212ce3416 100644 --- a/ui/core/package.json +++ b/ui/core/package.json @@ -54,6 +54,7 @@ "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.3", "ember-cli-uglify": "^3.0.0", + "ember-concurrency": "^1.3.0", "ember-data": "~3.16.0", "ember-data-model-fragments": "5.0.0-beta.0", "ember-exam": "^5.0.1", diff --git a/yarn.lock b/yarn.lock index 5304e5ace..658b62c2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8923,6 +8923,15 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-co ember-cli-version-checker "^2.1.1" semver "^5.4.1" +ember-concurrency@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-1.3.0.tgz#66f90fb792687470bcee1172adc0ebf33f5e8b9c" + integrity sha512-DwGlfWFpYyAkTwsedlEtK4t1DznJSculAW6Vq5S1C0shVPc5b6tTpHB2FFYisannSYkm+wpm1f1Pd40qiNPtOQ== + dependencies: + ember-cli-babel "^7.7.3" + ember-compatibility-helpers "^1.2.0" + ember-maybe-import-regenerator "^0.1.6" + ember-cookies@^0.5.0, ember-cookies@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/ember-cookies/-/ember-cookies-0.5.2.tgz#06e33463f2f83254fefaf224cc944dec3fb9d3ba"