diff --git a/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js b/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js index d060a8ef9..adf1eb514 100644 --- a/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js +++ b/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js @@ -15,6 +15,7 @@ export default class ScopesScopeProjectsTargetsTargetController extends Controll @service store; @service confirm; + @service rdp; // =attributes @@ -48,4 +49,13 @@ export default class ScopesScopeProjectsTargetsTargetController extends Controll }); } } + + /** + * Launch method that calls parent quickConnectAndLaunchRdp method + * @param {TargetModel} target + */ + @action + async connectAndLaunchRdp(target) { + await this.targets.quickConnectAndLaunchRdp(target); + } } diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs index e151496f3..b9f5b3547 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs @@ -15,17 +15,37 @@ {{#if (can 'connect target' @model.target)}} - + {{#if (and @model.target.isRDP this.rdp.isPreferredRdpClientSet)}} + + + {{else}} + + {{/if}} {{/if}} diff --git a/ui/desktop/tests/acceptance/projects/targets/target-test.js b/ui/desktop/tests/acceptance/projects/targets/target-test.js index 5d4cac159..67c22164c 100644 --- a/ui/desktop/tests/acceptance/projects/targets/target-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/target-test.js @@ -17,6 +17,7 @@ import { setupApplicationTest } from 'desktop/tests/helpers'; import WindowMockIPC from '../../../helpers/window-mock-ipc'; import setupStubs from 'api/test-support/handlers/cache-daemon-search'; import { setRunOptions } from 'ember-a11y-testing/test-support'; +import { TYPE_TARGET_RDP } from 'api/models/target'; module('Acceptance | projects | targets | target', function (hooks) { setupApplicationTest(hooks); @@ -25,6 +26,7 @@ module('Acceptance | projects | targets | target', function (hooks) { const TARGET_RESOURCE_LINK = (id) => `[data-test-visit-target="${id}"]`; const TARGET_TABLE_CONNECT_BUTTON = (id) => `[data-test-targets-connect-button="${id}"]`; + const TARGET_OPEN_BUTTON = `[data-test-target-detail-open-button]`; const TARGET_CONNECT_BUTTON = '[data-test-target-detail-connect-button]'; const TARGET_HOST_SOURCE_CONNECT_BUTTON = (id) => `[data-test-target-connect-button=${id}]`; @@ -570,6 +572,7 @@ module('Acceptance | projects | targets | target', function (hooks) { assert.strictEqual(currentURL(), urls.targetWithOneHost); assert.dom('.aliases').exists(); }); + test('user can connect to a target without read permissions for host-set', async function (assert) { setRunOptions({ rules: { @@ -625,4 +628,99 @@ module('Acceptance | projects | targets | target', function (hooks) { assert.dom(APP_STATE_TITLE).hasText('Connected'); }); + + test('shows `Open` and `Connect` 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 }); + + this.stubCacheDaemonSearch(); + + await visit(urls.target); + + assert.dom(TARGET_OPEN_BUTTON).exists(); + assert.dom(TARGET_OPEN_BUTTON).hasText('Open'); + assert.dom(TARGET_CONNECT_BUTTON).exists(); + assert.dom(TARGET_CONNECT_BUTTON).hasText('Connect'); + }); + + test('shows "Connect" button for RDP target without preferred client', async function (assert) { + let rdpService = this.owner.lookup('service:rdp'); + rdpService.preferredRdpClient = null; + instances.target.update({ type: TYPE_TARGET_RDP }); + + this.stubCacheDaemonSearch(); + + await visit(urls.target); + + assert.dom(TARGET_CONNECT_BUTTON).exists(); + assert.dom(TARGET_CONNECT_BUTTON).hasText('Connect'); + }); + + test('clicking `open` button for RDP target triggers launchRdpClient', 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); + this.ipcStub.withArgs('connect').returns({ + session_id: instances.session.id, + address: 'a_123', + port: 'p_123', + protocol: 'rdp', + }); + this.stubCacheDaemonSearch(); + this.ipcStub.withArgs('launchRdpClient').resolves(); + + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + + await visit(urls.target); + + await click(TARGET_OPEN_BUTTON); + + assert.ok(this.ipcStub.calledWith('launchRdpClient', instances.session.id)); + }); + + test('shows `Connect` button for rdp target without preferred client', async function (assert) { + let rdpService = this.owner.lookup('service:rdp'); + rdpService.preferredRdpClient = null; + instances.target.update({ type: TYPE_TARGET_RDP }); + + this.stubCacheDaemonSearch(); + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ + session_id: instances.session.id, + address: 'a_123', + port: 'p_123', + protocol: 'rdp', + }); + await visit(urls.target); + + assert.dom(TARGET_CONNECT_BUTTON).exists(); + assert.dom(TARGET_CONNECT_BUTTON).hasText('Connect'); + assert.dom(TARGET_OPEN_BUTTON).doesNotExist(); + + await click(TARGET_CONNECT_BUTTON); + + assert.ok(this.ipcStub.calledWith('connect')); + assert.notOk(this.ipcStub.calledWith('launchRdpClient')); + }); + + test('shows confirm modal when quickConnectAndLaunchRdp fails', async function (assert) { + let rdpService = this.owner.lookup('service:rdp'); + rdpService.preferredRdpClient = 'windows-app'; + instances.target.update({ type: TYPE_TARGET_RDP }); + this.stubCacheDaemonSearch(); + + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + + await visit(urls.target); + + await click('[data-test-target-detail-open-button]'); + + // The modal should be visible because cliExists was not stubbed, and the connection failed + assert.dom(HDS_DIALOG_MODAL).isVisible(); + }); });