feat: Add launch functionality in targets list view (#3035)

This commit is contained in:
Dharini committed 2025-11-19 14:22:06 -08:00
1 parent 6554af1164
commit 27136eaa71
4 files changed
+153 -9

No files matched your search

@@ -68,3 +68,4 @@ show-errors: Show Errors
hide-errors: Hide Errors
edit-worker-filter: Edit Worker Filter
add-worker-filter: Add Worker Filter
open: Open
@@ -26,6 +26,7 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
@service store;
@service can;
@service intl;
@service rdp;
// =attributes
@@ -244,6 +245,8 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
'scopes.scope.projects.sessions.session',
session_id,
);
return session;
}
/**
@@ -311,4 +314,23 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
async refresh() {
await this.currentRoute.refreshAll();
}
/**
* Quick connect method used to call main connect method and
* then launch RDP client
* @param {TargetModel} target
*/
@action
async quickConnectAndLaunchRdp(target) {
try {
const session = await this.connect(target);
// Launch RDP client
await this.rdp.launchRdpClient(session.id);
} catch (error) {
this.confirm
.confirm(error.message, { isConnectError: true })
// Retry
.then(() => this.quickConnectAndLaunchRdp(target));
}
}
}
@@ -152,14 +152,25 @@
<div {{style display='inline-block'}}>
{{#if (can 'connect target' B.data)}}
{{#if (can 'read target' B.data)}}
<Hds::Button
data-test-targets-connect-button={{B.data.id}}
@text={{t 'resources.session.actions.connect'}}
@color='secondary'
@route='scopes.scope.projects.targets.target'
@model={{B.data.id}}
@query={{hash isConnecting=true}}
/>
{{#if (and B.data.isRDP this.rdp.isPreferredRdpClientSet)}}
<Hds::Button
data-test-targets-open-button={{B.data.id}}
@text={{t 'actions.open'}}
@icon='external-link'
@iconPosition='trailing'
@color='secondary'
{{on 'click' (fn this.quickConnectAndLaunchRdp B.data)}}
/>
{{else}}
<Hds::Button
data-test-targets-connect-button={{B.data.id}}
@text={{t 'resources.session.actions.connect'}}
@color='secondary'
@route='scopes.scope.projects.targets.target'
@model={{B.data.id}}
@query={{hash isConnecting=true}}
/>
{{/if}}
{{else}}
<Hds::Button
data-test-targets-connect-button={{B.data.id}}
@@ -22,6 +22,7 @@ import {
STATUS_SESSION_ACTIVE,
STATUS_SESSION_TERMINATED,
} from 'api/models/session';
import { TYPE_TARGET_RDP } from 'api/models/target';
module('Acceptance | projects | targets | index', function (hooks) {
setupApplicationTest(hooks);
@@ -43,7 +44,9 @@ module('Acceptance | projects | targets | index', function (hooks) {
'[data-test-targets-sessions-flyout] .hds-flyout__title';
const SESSIONS_FLYOUT_CLOSE_BUTTON =
'[data-test-targets-sessions-flyout] .hds-flyout__dismiss';
const TARGET_OPEN_BUTTON = (id) => `[data-test-targets-open-button="${id}"]`;
const TARGET_CONNECT_BUTTON = (id) =>
`[data-test-targets-connect-button="${id}"]`;
const instances = {
scopes: {
global: null,
@@ -709,4 +712,111 @@ module('Acceptance | projects | targets | index', function (hooks) {
.dom(activeSessionFlyoutButtonSelector(instances.session.targetId))
.doesNotExist();
});
test('shows `Open` button for RDP target with preferred client', async function (assert) {
let rdpService = this.owner.lookup('service:rdp');
rdpService.preferredRdpClient = 'windows-app';
instances.target.update({
type: TYPE_TARGET_RDP,
});
await visit(urls.targets);
assert.dom(TARGET_OPEN_BUTTON(instances.target.id)).exists();
assert.dom(TARGET_OPEN_BUTTON(instances.target.id)).hasText('Open');
assert.dom('[data-test-icon=external-link]').exists();
});
test('shows `Connect` button for RDP target with no preferred client', async function (assert) {
let rdpService = this.owner.lookup('service:rdp');
rdpService.preferredRdpClient = 'none';
instances.target.update({
type: TYPE_TARGET_RDP,
});
await visit(urls.targets);
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).exists();
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).hasText('Connect');
assert.dom('[data-test-icon=external-link]').doesNotExist();
});
test('shows `Connect` button for non-RDP target', async function (assert) {
await visit(urls.targets);
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).exists();
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).hasText('Connect');
});
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';
instances.target.update({ type: TYPE_TARGET_RDP });
this.ipcStub.withArgs('connect').returns({
session_id: instances.session.id,
address: 'a_123',
port: 'p_123',
protocol: 'rdp',
});
this.ipcStub.withArgs('launchRdpClient').resolves();
// visit targets page
await visit(urls.targets);
assert.dom(TARGET_OPEN_BUTTON(instances.target.id)).exists();
await click(TARGET_OPEN_BUTTON(instances.target.id));
assert.ok(this.ipcStub.calledWith('launchRdpClient', instances.session.id));
});
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';
instances.target.update({ type: TYPE_TARGET_RDP });
this.ipcStub.withArgs('connect').returns({
session_id: instances.session.id,
address: 'a_123',
port: 'p_123',
protocol: 'rdp',
});
// visit targets page
await visit(urls.targets);
assert.dom(TARGET_CONNECT_BUTTON(instances.target.id)).exists();
await click(TARGET_CONNECT_BUTTON(instances.target.id));
assert.ok(this.ipcStub.calledWith('connect'));
});
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';
instances.target.update({ type: TYPE_TARGET_RDP });
this.ipcStub.withArgs('cliExists').returns(true);
// target quick connection is a success but launching RDP client fails
this.ipcStub.withArgs('connect').returns({
session_id: instances.session.id,
address: 'a_123',
port: 'p_123',
protocol: 'rdp',
});
this.ipcStub.withArgs('launchRdpClient').rejects();
const confirmService = this.owner.lookup('service:confirm');
confirmService.enabled = true;
await visit(urls.targets);
await click(`[data-test-targets-open-button="${instances.target.id}"]`);
// Assert that the confirm modal appears
assert.dom(HDS_DIALOG_MODAL).isVisible();
});
});