Skip to content

Commit e5d7f85

Browse files
committed
feat: Add launch functionality in targets list view (#3035)
1 parent b9ab34d commit e5d7f85

File tree

4 files changed

+153
-9
lines changed

4 files changed

+153
-9
lines changed

addons/core/translations/actions/en-us.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ show-errors: Show Errors
6868
hide-errors: Hide Errors
6969
edit-worker-filter: Edit Worker Filter
7070
add-worker-filter: Add Worker Filter
71+
open: Open

ui/desktop/app/controllers/scopes/scope/projects/targets/index.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
2626
@service store;
2727
@service can;
2828
@service intl;
29+
@service rdp;
2930

3031
// =attributes
3132

@@ -244,6 +245,8 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
244245
'scopes.scope.projects.sessions.session',
245246
session_id,
246247
);
248+
249+
return session;
247250
}
248251

249252
/**
@@ -311,4 +314,23 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
311314
async refresh() {
312315
await this.currentRoute.refreshAll();
313316
}
317+
318+
/**
319+
* Quick connect method used to call main connect method and
320+
* then launch RDP client
321+
* @param {TargetModel} target
322+
*/
323+
@action
324+
async quickConnectAndLaunchRdp(target) {
325+
try {
326+
const session = await this.connect(target);
327+
// Launch RDP client
328+
await this.rdp.launchRdpClient(session.id);
329+
} catch (error) {
330+
this.confirm
331+
.confirm(error.message, { isConnectError: true })
332+
// Retry
333+
.then(() => this.quickConnectAndLaunchRdp(target));
334+
}
335+
}
314336
}

ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,25 @@
152152
<div {{style display='inline-block'}}>
153153
{{#if (can 'connect target' B.data)}}
154154
{{#if (can 'read target' B.data)}}
155-
<Hds::Button
156-
data-test-targets-connect-button={{B.data.id}}
157-
@text={{t 'resources.session.actions.connect'}}
158-
@color='secondary'
159-
@route='scopes.scope.projects.targets.target'
160-
@model={{B.data.id}}
161-
@query={{hash isConnecting=true}}
162-
/>
155+
{{#if (and B.data.isRDP this.rdp.isPreferredRdpClientSet)}}
156+
<Hds::Button
157+
data-test-targets-open-button={{B.data.id}}
158+
@text={{t 'actions.open'}}
159+
@icon='external-link'
160+
@iconPosition='trailing'
161+
@color='secondary'
162+
{{on 'click' (fn this.quickConnectAndLaunchRdp B.data)}}
163+
/>
164+
{{else}}
165+
<Hds::Button
166+
data-test-targets-connect-button={{B.data.id}}
167+
@text={{t 'resources.session.actions.connect'}}
168+
@color='secondary'
169+
@route='scopes.scope.projects.targets.target'
170+
@model={{B.data.id}}
171+
@query={{hash isConnecting=true}}
172+
/>
173+
{{/if}}
163174
{{else}}
164175
<Hds::Button
165176
data-test-targets-connect-button={{B.data.id}}

ui/desktop/tests/acceptance/projects/targets/index-test.js

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
STATUS_SESSION_ACTIVE,
2323
STATUS_SESSION_TERMINATED,
2424
} from 'api/models/session';
25+
import { TYPE_TARGET_RDP } from 'api/models/target';
2526

2627
module('Acceptance | projects | targets | index', function (hooks) {
2728
setupApplicationTest(hooks);
@@ -43,7 +44,9 @@ module('Acceptance | projects | targets | index', function (hooks) {
4344
'[data-test-targets-sessions-flyout] .hds-flyout__title';
4445
const SESSIONS_FLYOUT_CLOSE_BUTTON =
4546
'[data-test-targets-sessions-flyout] .hds-flyout__dismiss';
46-
47+
const TARGET_OPEN_BUTTON = (id) => `[data-test-targets-open-button="${id}"]`;
48+
const TARGET_CONNECT_BUTTON = (id) =>
49+
`[data-test-targets-connect-button="${id}"]`;
4750
const instances = {
4851
scopes: {
4952
global: null,
@@ -700,4 +703,111 @@ module('Acceptance | projects | targets | index', function (hooks) {
700703
.dom(activeSessionFlyoutButtonSelector(instances.session.targetId))
701704
.doesNotExist();
702705
});
706+
707+
test('shows `Open` button for RDP target with preferred client', async function (assert) {
708+
let rdpService = this.owner.lookup('service:rdp');
709+
rdpService.preferredRdpClient = 'windows-app';
710+
instances.target.update({
711+
type: TYPE_TARGET_RDP,
712+
});
713+
await visit(urls.targets);
714+
715+
assert.dom(TARGET_OPEN_BUTTON(instances.target.id)).exists();
716+
assert.dom(TARGET_OPEN_BUTTON(instances.target.id)).hasText('Open');
717+
assert.dom('[data-test-icon=external-link]').exists();
718+
});
719+
720+
test('shows `Connect` button for RDP target with no preferred client', async function (assert) {
721+
let rdpService = this.owner.lookup('service:rdp');
722+
rdpService.preferredRdpClient = 'none';
723+
instances.target.update({
724+
type: TYPE_TARGET_RDP,
725+
});
726+
await visit(urls.targets);
727+
728+
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).exists();
729+
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).hasText('Connect');
730+
assert.dom('[data-test-icon=external-link]').doesNotExist();
731+
});
732+
733+
test('shows `Connect` button for non-RDP target', async function (assert) {
734+
await visit(urls.targets);
735+
736+
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).exists();
737+
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).hasText('Connect');
738+
});
739+
740+
test('clicking `Open` button for RDP target calls launchRdpClient IPC', async function (assert) {
741+
this.ipcStub.withArgs('cliExists').returns(true);
742+
743+
const rdpService = this.owner.lookup('service:rdp');
744+
rdpService.preferredRdpClient = 'windows-app';
745+
instances.target.update({ type: TYPE_TARGET_RDP });
746+
747+
this.ipcStub.withArgs('connect').returns({
748+
session_id: instances.session.id,
749+
address: 'a_123',
750+
port: 'p_123',
751+
protocol: 'rdp',
752+
});
753+
this.ipcStub.withArgs('launchRdpClient').resolves();
754+
755+
// visit targets page
756+
await visit(urls.targets);
757+
758+
assert.dom(TARGET_OPEN_BUTTON(instances.target.id)).exists();
759+
760+
await click(TARGET_OPEN_BUTTON(instances.target.id));
761+
762+
assert.ok(this.ipcStub.calledWith('launchRdpClient', instances.session.id));
763+
});
764+
765+
test('clicking `Connect` button for RDP target without preferred client calls connect IPC', async function (assert) {
766+
this.ipcStub.withArgs('cliExists').returns(true);
767+
768+
const rdpService = this.owner.lookup('service:rdp');
769+
rdpService.preferredRdpClient = 'none';
770+
instances.target.update({ type: TYPE_TARGET_RDP });
771+
772+
this.ipcStub.withArgs('connect').returns({
773+
session_id: instances.session.id,
774+
address: 'a_123',
775+
port: 'p_123',
776+
protocol: 'rdp',
777+
});
778+
779+
// visit targets page
780+
await visit(urls.targets);
781+
782+
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).exists();
783+
784+
await click(TARGET_CONNECT_BUTTON(instances.target.id));
785+
786+
assert.ok(this.ipcStub.calledWith('connect'));
787+
});
788+
789+
test('shows confirm modal when connection error occurs on launching rdp client', async function (assert) {
790+
let rdpService = this.owner.lookup('service:rdp');
791+
rdpService.preferredRdpClient = 'windows-app';
792+
instances.target.update({ type: TYPE_TARGET_RDP });
793+
794+
this.ipcStub.withArgs('cliExists').returns(true);
795+
// target quick connection is a success but launching RDP client fails
796+
this.ipcStub.withArgs('connect').returns({
797+
session_id: instances.session.id,
798+
address: 'a_123',
799+
port: 'p_123',
800+
protocol: 'rdp',
801+
});
802+
this.ipcStub.withArgs('launchRdpClient').rejects();
803+
804+
const confirmService = this.owner.lookup('service:confirm');
805+
confirmService.enabled = true;
806+
807+
await visit(urls.targets);
808+
await click(`[data-test-targets-open-button="${instances.target.id}"]`);
809+
810+
// Assert that the confirm modal appears
811+
assert.dom(HDS_DIALOG_MODAL).isVisible();
812+
});
703813
});

0 commit comments

Comments
 (0)