diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index f12baf9a3..c0f94b306 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -551,6 +551,9 @@ target: connect-more-info: title: Connect for more info description: Additional information for this target will be provided here after your session is established. + cannot-connect: + title: Cannot connect to {targetDisplayName} + description: No host sources and no target address found. Please update target with address information to connect directly. Otherwise, add at least one host to connect using host sources. titles: new: New Target actions: diff --git a/ui/desktop/app/abilities/target.js b/ui/desktop/app/abilities/target.js new file mode 100644 index 000000000..40c420212 --- /dev/null +++ b/ui/desktop/app/abilities/target.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import TargetAbility from 'api/abilities/target'; + +export default class OverrideTargetAbility extends TargetAbility { + /** + * + * @type {boolean} + */ + get canInitiateConnection() { + return this.model.address || this.model.host_sources?.length; + } +} 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 441cfced8..94bcd80bb 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs @@ -15,34 +15,51 @@ - + {{#if (can 'connect target' @model.target)}} + + {{/if}} - + {{#if (can 'initiateConnection target' @model.target)}} + + {{/if}} - - + {{#if (can 'initiateConnection target' @model.target)}} + + + {{else}} + + + {{/if}} diff --git a/ui/desktop/tests/acceptance/targets-test.js b/ui/desktop/tests/acceptance/targets-test.js index 2ea8a5678..3cca4e681 100644 --- a/ui/desktop/tests/acceptance/targets-test.js +++ b/ui/desktop/tests/acceptance/targets-test.js @@ -4,7 +4,7 @@ */ import { module, test } from 'qunit'; -import { visit, currentURL } from '@ember/test-helpers'; +import { visit, click, currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; @@ -19,6 +19,9 @@ module('Acceptance | targets', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + const TARGET_CONNECT_BUTTON = '[data-test-target-detail-connect-button]'; + const APP_STATE_TITLE = '.hds-application-state__title'; + const instances = { scopes: { global: null, @@ -29,6 +32,7 @@ module('Acceptance | targets', function (hooks) { global: null, }, target: null, + emptyTarget: null, }; const stubs = { @@ -49,6 +53,8 @@ module('Acceptance | targets', function (hooks) { }, projects: null, targets: null, + target: null, + emptyTarget: null, }; const setDefaultClusterUrl = (test) => { @@ -85,9 +91,12 @@ module('Acceptance | targets', function (hooks) { ); instances.target = this.server.create( 'target', - { scope: instances.scopes.project }, + { scope: instances.scopes.project, address: '127.0.0.1' }, 'withAssociations' ); + instances.emptyTarget = this.server.create('target', { + scope: instances.scopes.project, + }); urls.scopes.global = `/scopes/${instances.scopes.global.id}`; urls.scopes.org = `/scopes/${instances.scopes.org.id}`; @@ -95,6 +104,8 @@ module('Acceptance | targets', function (hooks) { urls.authenticate.methods.global = `${urls.authenticate.global}/${instances.authMethods.global.id}`; urls.projects = `${urls.scopes.org}/projects`; urls.targets = `${urls.projects}/targets`; + urls.target = `${urls.targets}/${instances.target.id}`; + urls.emptyTarget = `${urls.targets}/${instances.emptyTarget.id}`; // Mock the postMessage interface used by IPC. this.owner.register('service:browser/window', WindowMockIPC); @@ -115,4 +126,24 @@ module('Acceptance | targets', function (hooks) { await visit(urls.targets); assert.strictEqual(currentURL(), urls.targets); }); + + test('cannot connect to a target without an address and no host sources', async function (assert) { + assert.expect(2); + await visit(urls.targets); + + await click(`[href="${urls.emptyTarget}"]`); + + assert.dom(TARGET_CONNECT_BUTTON).isDisabled(); + assert.dom(APP_STATE_TITLE).includesText('Cannot connect'); + }); + + test('can connect to a target with an address', async function (assert) { + assert.expect(2); + await visit(urls.targets); + + await click(`[href="${urls.target}"]`); + + assert.dom(TARGET_CONNECT_BUTTON).isEnabled(); + assert.dom(APP_STATE_TITLE).includesText('Connect for more info'); + }); }); diff --git a/ui/desktop/tests/unit/abilities/target-test.js b/ui/desktop/tests/unit/abilities/target-test.js new file mode 100644 index 000000000..2299590ab --- /dev/null +++ b/ui/desktop/tests/unit/abilities/target-test.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | target', function (hooks) { + setupTest(hooks); + + test('it reflects when a target can initiate connection or not', function (assert) { + assert.expect(5); + const service = this.owner.lookup('service:can'); + const model = { + address: '', + }; + assert.false(service.can('initiateConnection target', model)); + model.host_sources = []; + assert.false(service.can('initiateConnection target', model)); + model.address = 'localhost'; + assert.true(service.can('initiateConnection target', model)); + model.address = ''; + model.host_sources = ['host_123']; + assert.true(service.can('initiateConnection target', model)); + model.address = 'localhost'; + model.host_sources = ['host_123']; + assert.true(service.can('initiateConnection target', model)); + }); +});