feat: 🎸 Add RDP client launch on session details page (#3037)
* feat: 🎸 preferred clients section on settings page * refactor: 💡 moved rdp calls to app route * test: 💍 fixed failings tests * refactor: 💡 addressed comments * fix: 🐛 fixed failing tests * refactor: 💡 add mock rdp service calls to tests * feat: 🎸 adds open button to session details page * fix: 🐛 translation string * refactor: 💡 address feedback * refactor: 💡 address comment * fix: 🐛 test failure
This commit is contained in:
1 parent
e55ea0b629
commit
fb0020c395
4 files changed
+174
-28
No files matched your search
@@ -4,7 +4,43 @@
|
||||
*/
|
||||
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
import { TYPE_TARGET_RDP } from 'api/models/target';
|
||||
import { service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeProjectsSessionsSessionController extends Controller {
|
||||
@controller('scopes/scope/projects/sessions/index') sessions;
|
||||
|
||||
// =services
|
||||
|
||||
@service rdp;
|
||||
@service confirm;
|
||||
|
||||
// =getters
|
||||
|
||||
/**
|
||||
* Whether to show the "Open" button for launching the RDP client.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get showOpenButton() {
|
||||
return (
|
||||
this.model.target?.type === TYPE_TARGET_RDP &&
|
||||
this.rdp.isPreferredRdpClientSet &&
|
||||
this.model.id
|
||||
);
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
@action
|
||||
async launchRdpClient() {
|
||||
try {
|
||||
await this.rdp.launchRdpClient(this.model.id);
|
||||
} catch (error) {
|
||||
this.confirm
|
||||
.confirm(error.message, { isConnectError: true })
|
||||
// Retry
|
||||
.then(() => this.launchRdpClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,27 @@
|
||||
@icon='loading'
|
||||
/>
|
||||
</PH.Badges>
|
||||
{{#if (can 'cancel session' @model)}}
|
||||
{{#if (or (can 'cancel session' @model) this.showOpenButton)}}
|
||||
<PH.Actions>
|
||||
<Hds::Button
|
||||
data-test-session-detail-cancel-button
|
||||
@text={{t 'resources.session.actions.end'}}
|
||||
@icon='x'
|
||||
@color='secondary'
|
||||
{{on 'click' (fn this.sessions.cancelSession @model)}}
|
||||
/>
|
||||
{{#if (can 'cancel session' @model)}}
|
||||
<Hds::Button
|
||||
data-test-session-detail-cancel-button
|
||||
@text={{t 'resources.session.actions.end'}}
|
||||
@icon='x'
|
||||
@color='secondary'
|
||||
{{on 'click' (fn this.sessions.cancelSession @model)}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if this.showOpenButton}}
|
||||
<Hds::Button
|
||||
data-test-session-detail-open-button
|
||||
@text={{t 'actions.open'}}
|
||||
@icon='external-link'
|
||||
@iconPosition='trailing'
|
||||
@color='primary'
|
||||
{{on 'click' this.launchRdpClient}}
|
||||
/>
|
||||
{{/if}}
|
||||
</PH.Actions>
|
||||
{{/if}}
|
||||
</Hds::PageHeader>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { STATUS_SESSION_ACTIVE } from 'api/models/session';
|
||||
import setupStubs from 'api/test-support/handlers/cache-daemon-search';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import sinon from 'sinon';
|
||||
import { TYPE_TARGET_RDP } from 'api/models/target';
|
||||
|
||||
module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -24,6 +25,8 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
const TOAST_DO_NOT_SHOW_AGAIN_BUTTON =
|
||||
'[data-test-toast-notification] button';
|
||||
const TOAST_DISMISS_BUTTON = '[aria-label="Dismiss"]';
|
||||
const RDP_OPEN_BUTTON = '[data-test-session-detail-open-button]';
|
||||
const CANCEL_SESSION_BUTTON = '[data-test-session-detail-cancel-button]';
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
@@ -36,6 +39,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
user: null,
|
||||
session: null,
|
||||
rdpSession: null,
|
||||
};
|
||||
|
||||
const urls = {
|
||||
@@ -139,8 +143,8 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(false);
|
||||
|
||||
// mock RDP service calls
|
||||
let rdpService = this.owner.lookup('service:rdp');
|
||||
sinon.stub(rdpService, 'initialize').resolves();
|
||||
this.rdpService = this.owner.lookup('service:rdp');
|
||||
sinon.stub(this.rdpService, 'initialize').resolves();
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
@@ -486,7 +490,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
|
||||
await visit(urls.session);
|
||||
|
||||
assert.dom('[data-test-session-detail-cancel-button]').isVisible();
|
||||
assert.dom(CANCEL_SESSION_BUTTON).isVisible();
|
||||
});
|
||||
|
||||
test('cannot cancel a session without cancel permissions', async function (assert) {
|
||||
@@ -504,7 +508,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
|
||||
await visit(urls.session);
|
||||
|
||||
assert.dom('[data-test-session-detail-cancel-button]').isNotVisible();
|
||||
assert.dom(CANCEL_SESSION_BUTTON).isNotVisible();
|
||||
});
|
||||
|
||||
test('cancelling a session shows success alert', async function (assert) {
|
||||
@@ -521,7 +525,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
|
||||
await visit(urls.session);
|
||||
|
||||
await click('[data-test-session-detail-cancel-button]');
|
||||
await click(CANCEL_SESSION_BUTTON);
|
||||
|
||||
assert
|
||||
.dom('[data-test-toast-notification].hds-alert--color-success')
|
||||
@@ -542,7 +546,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
|
||||
await visit(urls.session);
|
||||
|
||||
await click('[data-test-session-detail-cancel-button]');
|
||||
await click(CANCEL_SESSION_BUTTON);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.targets);
|
||||
});
|
||||
@@ -562,7 +566,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
|
||||
await visit(urls.session);
|
||||
|
||||
await click('[data-test-session-detail-cancel-button]');
|
||||
await click(CANCEL_SESSION_BUTTON);
|
||||
|
||||
assert
|
||||
.dom('[data-test-toast-notification].hds-alert--color-critical')
|
||||
@@ -584,10 +588,109 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
|
||||
await visit(urls.session);
|
||||
|
||||
await click('[data-test-session-detail-cancel-button]');
|
||||
await click(CANCEL_SESSION_BUTTON);
|
||||
|
||||
assert
|
||||
.dom('[data-test-toast-notification].hds-alert--color-critical')
|
||||
.isVisible();
|
||||
});
|
||||
|
||||
test('visiting an RDP session should display "open" button when preferred client is set', async function (assert) {
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = 'windows-app';
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
|
||||
await visit(urls.rdpTarget);
|
||||
await click(TARGET_CONNECT_BUTTON);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.rdpSession);
|
||||
assert.dom(RDP_OPEN_BUTTON).isVisible();
|
||||
|
||||
await click(RDP_OPEN_BUTTON);
|
||||
|
||||
assert.ok(
|
||||
this.ipcStub.calledWith('launchRdpClient', instances.rdpSession.id),
|
||||
);
|
||||
});
|
||||
|
||||
test('visiting an RDP session should not display "open" button when preferred client is set to none', async function (assert) {
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = 'none';
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
|
||||
await visit(urls.rdpTarget);
|
||||
await click(TARGET_CONNECT_BUTTON);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.rdpSession);
|
||||
assert.dom(RDP_OPEN_BUTTON).doesNotExist();
|
||||
});
|
||||
|
||||
test('it shows confirm modal when connection error occurs on launching rdp client', async function (assert) {
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = 'windows-app';
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
this.ipcStub.withArgs('launchRdpClient', instances.rdpSession.id).rejects();
|
||||
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
|
||||
await visit(urls.rdpTarget);
|
||||
await click(TARGET_CONNECT_BUTTON);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.rdpSession);
|
||||
assert.dom(RDP_OPEN_BUTTON).isVisible();
|
||||
|
||||
await click(RDP_OPEN_BUTTON);
|
||||
|
||||
assert.dom('.hds-modal').isVisible();
|
||||
});
|
||||
|
||||
test('it displays open button without cancel session permission', async function (assert) {
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.rdpService.preferredRdpClient = 'windows-app';
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
instances.rdpSession.update({ authorized_actions: [] });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
|
||||
await visit(urls.rdpTarget);
|
||||
await click(TARGET_CONNECT_BUTTON);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.rdpSession);
|
||||
assert.dom(RDP_OPEN_BUTTON).isVisible();
|
||||
assert.dom(CANCEL_SESSION_BUTTON).isNotVisible();
|
||||
});
|
||||
});
|
||||
@@ -165,8 +165,8 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
|
||||
|
||||
// mock RDP service calls
|
||||
let rdpService = this.owner.lookup('service:rdp');
|
||||
sinon.stub(rdpService, 'initialize').resolves();
|
||||
this.rdpService = this.owner.lookup('service:rdp');
|
||||
sinon.stub(this.rdpService, 'initialize').resolves();
|
||||
});
|
||||
|
||||
test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) {
|
||||
@@ -719,8 +719,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
});
|
||||
|
||||
test('shows `Open` button for RDP target with preferred client', async function (assert) {
|
||||
let rdpService = this.owner.lookup('service:rdp');
|
||||
rdpService.preferredRdpClient = 'windows-app';
|
||||
this.rdpService.preferredRdpClient = 'windows-app';
|
||||
instances.target.update({
|
||||
type: TYPE_TARGET_RDP,
|
||||
});
|
||||
@@ -732,8 +731,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
});
|
||||
|
||||
test('shows `Connect` button for RDP target with no preferred client', async function (assert) {
|
||||
let rdpService = this.owner.lookup('service:rdp');
|
||||
rdpService.preferredRdpClient = 'none';
|
||||
this.rdpService.preferredRdpClient = 'none';
|
||||
instances.target.update({
|
||||
type: TYPE_TARGET_RDP,
|
||||
});
|
||||
@@ -754,8 +752,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
test('clicking `Open` button for RDP target calls launchRdpClient IPC', async function (assert) {
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
const rdpService = this.owner.lookup('service:rdp');
|
||||
rdpService.preferredRdpClient = 'windows-app';
|
||||
this.rdpService.preferredRdpClient = 'windows-app';
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
@@ -779,8 +776,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
test('clicking `Connect` button for RDP target without preferred client calls connect IPC', async function (assert) {
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
const rdpService = this.owner.lookup('service:rdp');
|
||||
rdpService.preferredRdpClient = 'none';
|
||||
this.rdpService.preferredRdpClient = 'none';
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
@@ -801,8 +797,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
});
|
||||
|
||||
test('shows confirm modal when connection error occurs on launching rdp client', async function (assert) {
|
||||
let rdpService = this.owner.lookup('service:rdp');
|
||||
rdpService.preferredRdpClient = 'windows-app';
|
||||
this.rdpService.preferredRdpClient = 'windows-app';
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
Reference in new issue
Block a user