feat: 🎸 create Host Modal for connecting to specific modal (#1898)

✅ Closes: ICU-10432
This commit is contained in:
Cameron Perera authored and Zhihe Li committed 2023-09-22 18:58:14 -04:00
1 parent 02ce3341bd
commit c5cffd5137
9 files changed
+285 -3

No files matched your search

+9
View File
@@ -5,6 +5,7 @@
import GeneratedHostModel from '../generated/models/host';
import { attr } from '@ember-data/model';
import { pluginTypes } from './host-catalog';
export default class HostModel extends GeneratedHostModel {
// =attributes
@@ -58,6 +59,14 @@ export default class HostModel extends GeneratedHostModel {
return this.type === 'plugin';
}
/**
* True if the host catalog is an unknown type.
* @type {boolean}
*/
get isUnknown() {
return this.isPlugin && !pluginTypes.includes(this.plugin?.name);
}
/**
* True if the host plugin type is AWS.
* @type {boolean}
+22
View File
@@ -48,6 +48,28 @@ module('Unit | Model | host', function (hooks) {
assert.false(modelStatic.isPlugin);
});
test('it has isUnknown and returns the expected values', async function (assert) {
assert.expect(4);
const store = this.owner.lookup('service:store');
const modelAws = store.createRecord('host', {
type: 'plugin',
plugin: { name: 'aws' },
});
const modelAzure = store.createRecord('host', {
type: 'plugin',
plugin: { name: 'azure' },
});
const modelRandom = store.createRecord('host', {
type: 'plugin',
plugin: { name: 'random' },
});
assert.strictEqual(typeof modelRandom.isUnknown, 'boolean');
assert.true(modelRandom.isUnknown);
assert.false(modelAws.isUnknown);
assert.false(modelAzure.isUnknown);
});
test('it has isAWS and returns the expected values', async function (assert) {
assert.expect(3);
const store = this.owner.lookup('service:store');
@@ -295,6 +295,8 @@ session:
actions:
connect: Connect
end: End session
host: Choose a Host
quick-connect: Quick Connect
credential:
title: Credential
title_plural: Credentials
@@ -0,0 +1,73 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
{{#if @showModal}}
<Hds::Modal data-test-host-modal @onClose={{@toggle}} as |M|>
<M.Header @tagline={{@target.displayName}}>
{{t 'resources.session.actions.host'}}
</M.Header>
<M.Body>
<ul class='host-list-container'>
{{#each @hosts as |host|}}
<li class='host-container'>
<button
data-test-host-connect
type='button'
{{on 'click' (fn @connect @target host)}}
>
<div class='host-info-container'>
<Hds::IconTile
@color='boundary'
@icon='network-alt'
@size='small'
/>
<div class='host-info'>
<p
class='hds-typography-body-200 hds-font-weight-semibold hds-foreground-primary'
>
{{host.displayName}}
</p>
{{#if host.isStatic}}
<p class='hds-typography-body-100 hds-foreground-faint'>
{{host.address}}
</p>
{{/if}}
{{#if host.isPlugin}}
{{#if host.isUnknown}}
<Hds::Badge
@text={{t 'resources.host-catalog.types.unknown'}}
/>
{{else}}
<Hds::Badge
@text={{t 'resources.host-catalog.types.plugin'}}
@icon={{host.plugin.name}}
/>
{{/if}}
{{/if}}
{{#if host.description}}
<p class='hds-typography-body-100 hds-foreground-faint'>
{{host.description}}
</p>
{{/if}}
</div>
</div>
<FlightIcon class='host-arrow' @name='arrow-right' />
</button>
</li>
{{/each}}
</ul>
</M.Body>
<M.Footer>
<Hds::Button
data-test-host-quick-connect
type='button'
@icon='entry-point'
@iconPosition='trailing'
@text={{t 'resources.session.actions.quick-connect'}}
{{on 'click' (fn @connect @target)}}
/>
</M.Footer>
</Hds::Modal>
{{/if}}
@@ -4,11 +4,20 @@
*/
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class ScopesScopeProjectsTargetsTargetController extends Controller {
// =attributes
queryParams = [{ isConnecting: { type: 'boolean' } }];
isConnecting = false;
@tracked isConnecting = false;
// =methods
@action
toggle() {
this.isConnecting = !this.isConnecting;
}
}
@@ -71,9 +71,11 @@ export default class ScopesScopeProjectsTargetsTargetRoute extends Route {
*/
@action
@loading
async preConnect(model) {
async preConnect(model, toggleModal) {
if (model.target.address || model.hosts.length === 1) {
await this.connect(model.target);
} else {
toggleModal();
}
}
+32
View File
@@ -653,6 +653,38 @@ $chevron-down: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231
}
}
// Host Modal
.host-list-container {
border: 1px solid var(--token-color-border-primary);
padding: 0.25rem;
border-radius: 6px;
}
.host-container {
button {
background: unset;
border: unset;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0.5rem;
.host-info-container {
display: flex;
gap: 0.75rem;
.host-info {
text-align: left;
}
}
}
.host-arrow {
color: var(--token-color-foreground-faint);
}
}
#terminal-container {
.terminal.xterm .xterm-viewport {
border-radius: 8px;
@@ -20,12 +20,19 @@
@text={{t 'resources.session.actions.connect'}}
@icon='entry-point'
@iconPosition='trailing'
{{on 'click' (route-action 'preConnect' @model)}}
{{on 'click' (route-action 'preConnect' @model this.toggle)}}
/>
</page.actions>
<page.body class='details-screen-body'>
<Rose::Layout::BodyContent as |bc|>
<Target::HostModal
@showModal={{this.isConnecting}}
@toggle={{this.toggle}}
@connect={{route-action 'connect'}}
@hosts={{@model.hosts}}
@target={{@model.target}}
/>
<bc.Body class='target-details-body'>
<Hds::ApplicationState as |A|>
<A.Header
@@ -0,0 +1,126 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
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';
module('Integration | Component | target/host-modal', function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks);
let store;
let target;
let hostA;
let hostB;
let connect;
let toggle;
hooks.beforeEach(function () {
connect = () => {
this.set('called', true);
};
toggle = () => {
this.set('called', true);
};
store = this.owner.lookup('service:store');
target = store.createRecord('target', {
name: 'User',
});
hostA = store.createRecord('host', {
name: 'Host A',
compositeType: 'static',
address: '188e:68a9:b342:c05c:2595:2f46:499c:759f',
});
hostB = store.createRecord('host', {
name: 'Host B',
compositeType: 'static',
description: 'Description of host B',
address: '188e:68a9:b342:c05c:2595:499c:759f:2f46',
});
});
test('it renders', async function (assert) {
assert.expect(1);
this.set('connect', connect);
this.set('toggle', toggle);
this.set('hosts', [hostA, hostB]);
this.set('target', target);
await render(hbs`<Target::HostModal
@showModal={{true}}
@toggle={{this.toggle}}
@connect={{this.connect}}
@hosts={{this.hosts}}
@target={{this.target}} />`);
assert.dom('.hds-modal__tagline').hasText(target.displayName);
});
test('it closes model when dismissed', async function (assert) {
assert.expect(2);
const toggleModal = () => {
this.set('showModal', false);
};
this.set('connect', connect);
this.set('toggle', toggleModal);
this.set('showModal', true);
this.set('hosts', [hostA, hostB]);
this.set('target', target);
await render(hbs`<Target::HostModal
@showModal={{this.showModal}}
@toggle={{this.toggle}}
@connect={{this.connect}}
@hosts={{this.hosts}}
@target={{this.target}} />`);
assert.dom('.hds-modal__tagline').hasText(target.displayName);
await click('.hds-dismiss-button');
assert.dom('.hds-modal__tagline').doesNotExist();
});
test('it calls connect when Quick Connect button clicked', async function (assert) {
assert.expect(1);
this.set('connect', connect);
this.set('toggle', toggle);
this.set('hosts', [hostA, hostB]);
this.set('target', target);
await render(hbs`<Target::HostModal
@showModal={{true}}
@toggle={{this.toggle}}
@connect={{this.connect}}
@hosts={{this.hosts}}
@target={{this.target}} />`);
await click('[data-test-host-quick-connect]');
assert.true(this.called);
});
test('it calls connect when host item is clicked', async function (assert) {
assert.expect(1);
this.set('connect', connect);
this.set('toggle', toggle);
this.set('hosts', [hostA, hostB]);
this.set('target', target);
await render(hbs`<Target::HostModal
@showModal={{true}}
@toggle={{this.toggle}}
@connect={{this.connect}}
@hosts={{this.hosts}}
@target={{this.target}} />`);
await click('[data-test-host-connect]');
assert.true(this.called);
});
});