From 4a179ac3e3f0b8080e3be75fd48a13c9e4c22ccd Mon Sep 17 00:00:00 2001 From: Priya Patel Date: Thu, 3 Jul 2025 11:57:55 -0500 Subject: [PATCH] =?UTF-8?q?chore:=20=F0=9F=A4=96=20rdp=20desktop=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/core/translations/resources/en-us.yaml | 1 + .../app/components/session/details/index.hbs | 116 +++++++++++ .../components/session/proxy-url/index.hbs | 59 +++--- .../app/components/session/proxy-url/index.js | 77 +++++++- .../app/components/session/tabs/index.hbs | 160 ++++------------ ui/desktop/app/styles/app.scss | 8 - .../scope/projects/targets/target/index.hbs | 12 +- .../integration/components/proxy-url-test.js | 181 ++++++++++++------ .../components/session/tabs-test.js | 86 +++++++++ 9 files changed, 452 insertions(+), 248 deletions(-) create mode 100644 ui/desktop/app/components/session/details/index.hbs create mode 100644 ui/desktop/tests/integration/components/session/tabs-test.js diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 7cb365157..597784975 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -369,6 +369,7 @@ session: proxy-url: address-port: Address & port ssh: SSH + rdp: RDP shell: Shell global: title: Global diff --git a/ui/desktop/app/components/session/details/index.hbs b/ui/desktop/app/components/session/details/index.hbs new file mode 100644 index 000000000..8e89058bb --- /dev/null +++ b/ui/desktop/app/components/session/details/index.hbs @@ -0,0 +1,116 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + {{#if @model.credentials}} + + {{else}} + + + + + {{/if}} + + + {{#if (or @model.host @model.target)}} + +
+ {{#if @model.host}} +
+ {{t 'resources.host.details'}} +
+ + {{! Host name }} + {{#if @model.host.name}} + {{@model.host.name}} + {{/if}} + {{! Host description }} + {{#if @model.host.description}} + {{@model.host.description}} + {{/if}} + {{! Host address }} + {{#if @model.host.address}} + {{@model.host.address}} + {{/if}} + {{! Host ID }} + + + + + {{/if}} + + {{#if @model.target}} +
+ {{t 'resources.target.details'}} +
+ + {{! Target name }} + {{@model.target.name}} + {{! Project name }} + {{@model.target.project.displayName}} + {{! Target description }} + {{#if @model.target.description}} + {{@model.target.description}} + {{/if}} + {{! Target type (SSH/RDP/TCP) }} + + {{t (concat 'resources.target.types.' @model.target.type)}} + + {{! Target address }} + {{#if @model.target.address}} + {{@model.target.address}} + {{/if}} + {{! Target ID}} + + + + + {{/if}} +
+
+ {{/if}} +
\ No newline at end of file diff --git a/ui/desktop/app/components/session/proxy-url/index.hbs b/ui/desktop/app/components/session/proxy-url/index.hbs index e3cc015e4..945e66fcd 100644 --- a/ui/desktop/app/components/session/proxy-url/index.hbs +++ b/ui/desktop/app/components/session/proxy-url/index.hbs @@ -3,40 +3,31 @@ SPDX-License-Identifier: BUSL-1.1 }} -
+
- - {{t 'resources.session.proxy-url.address-port'}} - {{t 'resources.session.proxy-url.ssh'}} + + {{#each this.dropdownOptions as |optionId|}} + + {{t (concat 'resources.session.proxy-url.' optionId)}} + + {{/each}} - {{#if this.isAddressAndPort}} - - - {{/if}} - {{#unless this.isAddressAndPort}} - - {{/unless}} + + {{#let this.copySnippetData as |snippet|}} + {{#if snippet}} + + {{#if snippet.port}} + + {{/if}} + {{/if}} + {{/let}}
\ No newline at end of file diff --git a/ui/desktop/app/components/session/proxy-url/index.js b/ui/desktop/app/components/session/proxy-url/index.js index b2cc084c8..5c2c302b0 100644 --- a/ui/desktop/app/components/session/proxy-url/index.js +++ b/ui/desktop/app/components/session/proxy-url/index.js @@ -7,28 +7,85 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { service } from '@ember/service'; +import { + TYPE_TARGET_RDP, + TYPE_TARGET_SSH, + TYPE_TARGET_TCP, +} from 'api/models/target'; + +const ADDRESS_PORT = 'address-port'; export default class SessionProxyUrlComponent extends Component { + // =services + @service intl; - @tracked selectedValue = this.args.isSSHTarget - ? this.intl.t('resources.session.proxy-url.ssh') - : this.intl.t('resources.session.proxy-url.address-port'); + // =attributes - get isAddressAndPort() { + @tracked selectedValue = null; + + /** + * Returns the current value to be displayed in the dropdown. + * If a value is selected, it returns that value. + * Otherwise, it defaults to 'address-port' for TCP targets or the targetType for others. + */ + get currentValue() { return ( - this.selectedValue === - this.intl.t('resources.session.proxy-url.address-port') + this.selectedValue ?? + (this.args.targetType === TYPE_TARGET_TCP + ? ADDRESS_PORT + : this.args.targetType) ); } - get sshCommand() { - return `ssh ${this.args.proxyAddress} -p ${this.args.proxyPort}`; + /** + * Returns the copy snippet data based on the current selected value. + */ + get copySnippetData() { + const { proxyAddress, proxyPort } = this.args; + switch (this.currentValue) { + case ADDRESS_PORT: + return { + address: proxyAddress, + port: proxyPort, + }; + case TYPE_TARGET_SSH: + return { + address: `ssh ${proxyAddress} -p ${proxyPort}`, + }; + case TYPE_TARGET_RDP: + // TODO: This is a just a placeholder. Update this based on the design and product requirements. + return { + address: `rdp ${proxyAddress} -p ${proxyPort}`, + }; + default: + return null; + } } + /** Returns the dropdown options based on the target type. + */ + get dropdownOptions() { + let options = [ADDRESS_PORT]; + + options.push( + this.args.targetType === TYPE_TARGET_RDP + ? TYPE_TARGET_RDP + : TYPE_TARGET_SSH, + ); + + return options; + } + + // =actions + + /** Updates the selected value and closes the dropdown. + * @param {string} optionId - The selected option. + * @param {function} closeCallback - The callback function to close the dropdown. + */ @action - updateContent(value, closeCallback) { - this.selectedValue = value; + handleSelect(optionId, closeCallback) { + this.selectedValue = optionId; closeCallback(); } } diff --git a/ui/desktop/app/components/session/tabs/index.hbs b/ui/desktop/app/components/session/tabs/index.hbs index b63774e6f..09cd3ab63 100644 --- a/ui/desktop/app/components/session/tabs/index.hbs +++ b/ui/desktop/app/components/session/tabs/index.hbs @@ -3,133 +3,37 @@ SPDX-License-Identifier: BUSL-1.1 }} - - {{t 'titles.details'}} - - {{t 'resources.session.shell'}} - - - {{#if (and @model.proxy_address @model.proxy_port)}} - - {{/if}} - - - {{#if @model.credentials}} - - {{else}} - - - - - {{/if}} - +{{#if @model.target.isRDP}} + {{! RDP: Show content directly without tabs }} + {{#if (and @model.proxy_address @model.proxy_port)}} + + {{/if}} - {{#if (or @model.host @model.target)}} - -
- {{#if @model.host}} -
- {{t 'resources.host.details'}} -
- - {{! Host name }} - {{#if @model.host.name}} - - {{@model.host.name}} - - {{/if}} - {{! Host description }} - {{#if @model.host.description}} - - {{@model.host.description}} - - {{/if}} - {{! Host address }} - {{#if @model.host.address}} - - {{@model.host.address}} - - {{/if}} - {{! Host ID }} - - - - - {{/if}} - {{#if @model.target}} -
- {{t 'resources.target.details'}} -
- - {{! Target name }} - - {{@model.target.name}} - - {{! Project name }} - - {{@model.target.project.displayName}} - - {{! Target description }} - {{#if @model.target.description}} - - {{@model.target.description}} - - {{/if}} - {{! Target type }} - - {{#if @model.target.isSSH}} - {{t 'resources.target.types.ssh'}} - {{else}} - {{t 'resources.target.types.tcp'}} - {{/if}} - - {{! Target address }} - {{#if @model.target.address}} - - {{@model.target.address}} - - {{/if}} - {{! Target ID }} - - - - - {{/if}} -
-
+ +{{else}} + {{! Non-RDP: Show tabbed interface }} + + {{t 'titles.details'}} + + {{t 'resources.session.shell'}} + + + {{#if (and @model.proxy_address @model.proxy_port)}} + {{/if}} -
-
- -
-
-
\ No newline at end of file + + + + +
+
+ +{{/if}} \ No newline at end of file diff --git a/ui/desktop/app/styles/app.scss b/ui/desktop/app/styles/app.scss index 42ef864c2..5196fd8c1 100644 --- a/ui/desktop/app/styles/app.scss +++ b/ui/desktop/app/styles/app.scss @@ -300,14 +300,6 @@ .hds-copy-snippet { width: auto; } - - .copy-address { - max-width: 70%; - } - - .copy-ssh-command { - max-width: 90%; - } } // Credentials Panel diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/target/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/target/index.hbs index b16433293..238f38ed6 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/target/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target/index.hbs @@ -134,12 +134,12 @@ {{/if}} {{! Target type }} - - {{#if @model.target.isSSH}} - {{t 'resources.target.types.ssh'}} - {{else}} - {{t 'resources.target.types.tcp'}} - {{/if}} + + {{t (concat 'resources.target.types.' @model.target.type)}} {{! Target address }} {{#if @model.target.address}} diff --git a/ui/desktop/tests/integration/components/proxy-url-test.js b/ui/desktop/tests/integration/components/proxy-url-test.js index 0a1e7e9e6..251d23121 100644 --- a/ui/desktop/tests/integration/components/proxy-url-test.js +++ b/ui/desktop/tests/integration/components/proxy-url-test.js @@ -8,90 +8,147 @@ import { setupRenderingTest } from 'desktop/tests/helpers'; import { render, click } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupIntl } from 'ember-intl/test-support'; +import { + TYPE_TARGET_SSH, + TYPE_TARGET_RDP, + TYPE_TARGET_TCP, +} from 'api/models/target'; module('Integration | Component | session/proxy-url', function (hooks) { setupRenderingTest(hooks); setupIntl(hooks, 'en-us'); - test('it renders', async function (assert) { - assert.expect(2); - this.set('proxyAddress', 'http://localhost'); - this.set('proxyPort', '1234'); + test.each( + 'shows the correct command based on the target type', + [ + { + targetType: TYPE_TARGET_SSH, + expectedCommand: (address, port) => `ssh ${address} -p ${port}`, + expectedSnippetCount: 1, + }, + { + targetType: TYPE_TARGET_RDP, + expectedCommand: (address, port) => `rdp ${address} -p ${port}`, + expectedSnippetCount: 1, + }, + { + targetType: TYPE_TARGET_TCP, + expectedCommand: (address, port) => `${address}:${port}`, + expectedSnippetCount: 2, // TCP has two copy snippets- one for address and one for port + }, + ], + async function ( + assert, + { targetType, expectedCommand, expectedSnippetCount }, + ) { + this.set('proxyAddress', 'http://localhost'); + this.set('proxyPort', '1234'); + this.set('targetType', targetType); - await render( - hbs` + await render( + hbs` `, - ); + ); + assert.dom('.hds-copy-snippet').exists({ count: expectedSnippetCount }); - assert.dom('.copy-address').hasText(this.proxyAddress); - assert.dom('.copy-port').hasText(this.proxyPort); - }); + // Assert the content of the copy snippets + if (targetType === TYPE_TARGET_TCP) { + assert + .dom('.hds-copy-snippet:nth-of-type(1)') + .hasText(this.proxyAddress); + assert.dom('.hds-copy-snippet:nth-of-type(2)').hasText(this.proxyPort); + } else { + assert + .dom('.hds-copy-snippet') + .hasText(expectedCommand(this.proxyAddress, this.proxyPort)); + } + }, + ); - test('it switches to SSH command on dropdown click', async function (assert) { - assert.expect(2); - this.set('proxyAddress', 'http://localhost'); - this.set('proxyPort', '1234'); + test.each( + 'displays correct dropdown options based on target type', + [ + { + targetType: TYPE_TARGET_SSH, + visibleOptions: [ + '[data-test="ssh-option"]', + '[data-test="address-port-option"]', + ], + hiddenOptions: ['[data-test="rdp-option"]'], + }, + { + targetType: TYPE_TARGET_RDP, + visibleOptions: [ + '[data-test="rdp-option"]', + '[data-test="address-port-option"]', + ], + hiddenOptions: ['[data-test="ssh-option"]'], + }, + { + targetType: TYPE_TARGET_TCP, + visibleOptions: [ + '[data-test="address-port-option"]', + '[data-test="ssh-option"]', + ], + hiddenOptions: ['[data-test="rdp-option"]'], + }, + ], + async function (assert, { targetType, visibleOptions, hiddenOptions }) { + this.set('proxyAddress', 'http://localhost'); + this.set('proxyPort', '1234'); + this.set('targetType', targetType); - await render( - hbs` + await render( + hbs` `, + ); + + await click('.proxy-url-container .hds-dropdown-toggle-button'); + + // Assert visible options + for (const option of visibleOptions) { + assert.dom(option).isVisible(); + } + + // Assert hidden options + for (const option of hiddenOptions) { + assert.dom(option).doesNotExist(); + } + }, + ); + + test('updates command based on dropdown selection', async function (assert) { + this.set('proxyAddress', 'http://localhost'); + this.set('proxyPort', '1234'); + this.set('targetType', TYPE_TARGET_SSH); + + await render( + hbs` + + `, ); + + // select address-port dropdown option await click('.proxy-url-container .hds-dropdown-toggle-button'); - await click('[data-test-ssh-option]'); + await click('[data-test="address-port-option"]'); - assert.dom('.copy-ssh-command').isVisible(); assert - .dom('.copy-ssh-command') - .hasText(`ssh ${this.proxyAddress} -p ${this.proxyPort}`); - }); - - test('shows address & port info when target type is TCP', async function (assert) { - assert.expect(2); - this.set('proxyAddress', 'http://localhost'); - this.set('proxyPort', '1234'); - - await render( - hbs` - - `, - ); - - assert.dom('.copy-address').hasText(this.proxyAddress); - assert.dom('.copy-port').hasText(this.proxyPort); - }); - - test('shows ssh command when target type is SSH', async function (assert) { - assert.expect(2); - this.set('proxyAddress', 'http://localhost'); - this.set('proxyPort', '1234'); - - await render( - hbs` - - `, - ); - - assert.dom('.copy-ssh-command').isVisible(); - assert - .dom('.copy-ssh-command') - .hasText(`ssh ${this.proxyAddress} -p ${this.proxyPort}`); + .dom('.hds-copy-snippet:nth-of-type(1)') + .hasText(`${this.proxyAddress}`); + assert.dom('.hds-copy-snippet:nth-of-type(2)').hasText(`${this.proxyPort}`); }); }); diff --git a/ui/desktop/tests/integration/components/session/tabs-test.js b/ui/desktop/tests/integration/components/session/tabs-test.js new file mode 100644 index 000000000..ef0bc205e --- /dev/null +++ b/ui/desktop/tests/integration/components/session/tabs-test.js @@ -0,0 +1,86 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { + TYPE_TARGET_SSH, + TYPE_TARGET_RDP, + TYPE_TARGET_TCP, +} from 'api/models/target'; +import { setupIntl } from 'ember-intl/test-support'; + +module('Integration | Component | session/tabs', function (hooks) { + setupRenderingTest(hooks); + setupIntl(hooks, 'en-us'); + + hooks.beforeEach(function () { + this.store = this.owner.lookup('service:store'); + }); + + test.each( + 'it shows correct content for different target types', + [ + { + targetType: TYPE_TARGET_TCP, + targetId: 'tcp-target-1', + defaultPort: 9000, + proxyPort: 9000, + shouldShowTabs: true, + }, + { + targetType: TYPE_TARGET_SSH, + targetId: 'ssh-target-4', + defaultPort: 22, + proxyPort: 9000, + shouldShowTabs: true, + }, + { + targetType: TYPE_TARGET_RDP, + targetId: 'rdp-target-2', + defaultPort: 3389, + proxyPort: 3389, + shouldShowTabs: false, + }, + ], + async function ( + assert, + { targetType, targetId, defaultPort, proxyPort, shouldShowTabs }, + ) { + this.store.createRecord('target', { + id: targetId, + type: targetType, + address: 'example.com', + default_port: defaultPort, + scope: { + scope_id: 'p_123', + type: 'project', + }, + }); + + const session = this.store.createRecord('session', { + id: `session-${targetId}`, + target_id: targetId, + proxy_address: '127.0.0.1', + proxy_port: proxyPort, + }); + + this.set('model', session); + + await render(hbs``); + + if (shouldShowTabs) { + assert.dom('.hds-tabs').exists(); + } else { + assert.dom('.hds-tabs').doesNotExist(); + } + + assert.dom('[data-test-proxy-url]').exists(); + assert.dom('[data-test-session-details]').exists(); + }, + ); +});