Skip to content

Commit 199c56f

Browse files
authored
feat: Add launch functionality in targets list view (#3035)
1 parent 43f03ae commit 199c56f

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
@@ -24,6 +24,7 @@ import {
2424
STATUS_SESSION_ACTIVE,
2525
STATUS_SESSION_TERMINATED,
2626
} from 'api/models/session';
27+
import { TYPE_TARGET_RDP } from 'api/models/target';
2728

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

0 commit comments

Comments
 (0)