diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml index 51178af71..5bf9accd6 100644 --- a/addons/core/translations/actions/en-us.yaml +++ b/addons/core/translations/actions/en-us.yaml @@ -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 diff --git a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js index e3470fa85..763425588 100644 --- a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js +++ b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js @@ -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)); + } + } } diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs index 6092a0c71..d51d832b6 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs @@ -152,14 +152,25 @@
{{#if (can 'connect target' B.data)}} {{#if (can 'read target' B.data)}} - + {{#if (and B.data.isRDP this.rdp.isPreferredRdpClientSet)}} + + {{else}} + + {{/if}} {{else}} `[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(); + }); });