Icu 7490 UI add new address field to target (#1579)
* feat: 🎸 Add network address field to targets ✅ Closes: ICU-7490
This commit is contained in:
1 parent
bfc275e0f6
commit
0cecb0d856
11 files changed
+340
-33
No files matched your search
@@ -40,3 +40,4 @@ fullscreen: Fullscreen
|
|||||||
clear-all-filters: Clear all filters
|
clear-all-filters: Clear all filters
|
||||||
learn-more: Learn more
|
learn-more: Learn more
|
||||||
refresh: Refresh
|
refresh: Refresh
|
||||||
|
remove-resources: Remove resources and save
|
||||||
@@ -304,6 +304,7 @@ target:
|
|||||||
add-host-sources: Add Host Sources
|
add-host-sources: Add Host Sources
|
||||||
add-brokered-credential-sources: Add Brokered Credentials
|
add-brokered-credential-sources: Add Brokered Credentials
|
||||||
add-injected-application-credential-sources: Add Injected Application Credentials
|
add-injected-application-credential-sources: Add Injected Application Credentials
|
||||||
|
remove-address: Remove address and save
|
||||||
types:
|
types:
|
||||||
tcp: Generic TCP
|
tcp: Generic TCP
|
||||||
ssh: SSH
|
ssh: SSH
|
||||||
@@ -314,6 +315,13 @@ target:
|
|||||||
type:
|
type:
|
||||||
label: Type
|
label: Type
|
||||||
help: Target type is the protocol with which end users should connect to this target. Choose Generic TCP for broad support of common protocols including RDP, K8s, many databases, and more.
|
help: Target type is the protocol with which end users should connect to this target. Choose Generic TCP for broad support of common protocols including RDP, K8s, many databases, and more.
|
||||||
|
target-address:
|
||||||
|
label: Target Address
|
||||||
|
help: Must be a valid IP address or DNS name. We recommend leaving this blank and using host catalogs and host sets instead if you want to use this target on multiple hosts.
|
||||||
|
questions:
|
||||||
|
delete-host-sources:
|
||||||
|
title: Remove associated host sources?
|
||||||
|
message: You have {numHostSources, plural, one {# host source} other {# host sources}} associated with this target. Adding an address will remove these host sources when you save your changes.
|
||||||
host-source:
|
host-source:
|
||||||
title: Host Source
|
title: Host Source
|
||||||
title_plural: Host Sources
|
title_plural: Host Sources
|
||||||
@@ -327,6 +335,10 @@ target:
|
|||||||
add:
|
add:
|
||||||
title: Add Host Sources
|
title: Add Host Sources
|
||||||
description: Select host sources to assign to this target.
|
description: Select host sources to assign to this target.
|
||||||
|
questions:
|
||||||
|
delete-address:
|
||||||
|
title: Remove target address?
|
||||||
|
message: This target has an assigned address. Adding a host source will remove the assigned address from the target when you save your changes.
|
||||||
brokered-credential-source:
|
brokered-credential-source:
|
||||||
title: Brokered Credential
|
title: Brokered Credential
|
||||||
title_plural: Brokered Credentials
|
title_plural: Brokered Credentials
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{#if this.hasAvailableHostSets}}
|
{{#if this.hasAvailableHostSets}}
|
||||||
<Rose::Form
|
<Rose::Form
|
||||||
class='full-width'
|
class='full-width'
|
||||||
@onSubmit={{fn this.submit @submit}}
|
@onSubmit={{this.submit}}
|
||||||
@cancel={{@cancel}}
|
@cancel={{@cancel}}
|
||||||
@disabled={{@model.isSaving}}
|
@disabled={{@model.isSaving}}
|
||||||
as |form|
|
as |form|
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { computed, action } from '@ember/object';
|
import { computed, action } from '@ember/object';
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { loading } from 'ember-loading';
|
||||||
|
import { notifyError } from 'core/decorators/notify';
|
||||||
|
|
||||||
export default class FormTargetAddHostSetsComponent extends Component {
|
export default class FormTargetAddHostSetsComponent extends Component {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service confirm;
|
||||||
|
@service intl;
|
||||||
|
|
||||||
// =properties
|
// =properties
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +57,32 @@ export default class FormTargetAddHostSetsComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
submit(fn) {
|
@loading
|
||||||
fn(this.selectedHostSetIDs);
|
@notifyError(({ message }) => message)
|
||||||
|
async submit() {
|
||||||
|
const target = this.args.model;
|
||||||
|
|
||||||
|
if (target.address && this.selectedHostSetIDs.length) {
|
||||||
|
try {
|
||||||
|
await this.confirm.confirm(
|
||||||
|
this.intl.t(
|
||||||
|
'resources.target.host-source.questions.delete-address.message'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
'resources.target.host-source.questions.delete-address.title',
|
||||||
|
confirm: 'resources.target.actions.remove-address',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// if the user denies, do nothing and return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.address = null;
|
||||||
|
await target.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.args.submit(this.selectedHostSetIDs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<Rose::Form
|
<Rose::Form
|
||||||
@onSubmit={{@submit}}
|
@onSubmit={{this.submit}}
|
||||||
@cancel={{@cancel}}
|
@cancel={{@cancel}}
|
||||||
@disabled={{@model.isSaving}}
|
@disabled={{@model.isSaving}}
|
||||||
@showEditToggle={{if @model.isNew false true}}
|
@showEditToggle={{if @model.isNew false true}}
|
||||||
@@ -81,6 +81,57 @@
|
|||||||
</InfoField>
|
</InfoField>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if (feature-flag 'target-network-address')}}
|
||||||
|
<Hds::Form::TextInput::Field
|
||||||
|
@isRequired={{false}}
|
||||||
|
@isOptional={{true}}
|
||||||
|
@value={{@model.address}}
|
||||||
|
@isInvalid={{@model.errors.address}}
|
||||||
|
@type='text'
|
||||||
|
name='address'
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
{{on 'input' (set-from-event @model 'address')}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>{{t 'resources.target.form.target-address.label'}}</F.Label>
|
||||||
|
<F.HelperText>{{t
|
||||||
|
'resources.target.form.target-address.help'
|
||||||
|
}}</F.HelperText>
|
||||||
|
{{#if @model.errors.address}}
|
||||||
|
<F.Error as |E|>
|
||||||
|
{{#each @model.errors.address as |error|}}
|
||||||
|
<E.Message>{{error.message}}</E.Message>
|
||||||
|
{{/each}}
|
||||||
|
</F.Error>
|
||||||
|
{{/if}}
|
||||||
|
</Hds::Form::TextInput::Field>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<Hds::Form::TextInput::Field
|
||||||
|
@isRequired={{@model.isTCP}}
|
||||||
|
@isOptional={{not @model.isTCP}}
|
||||||
|
@value={{@model.default_port}}
|
||||||
|
@isInvalid={{@model.errors.default_port}}
|
||||||
|
@type='text'
|
||||||
|
name='default_port'
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
placeholder={{@defaultPort}}
|
||||||
|
{{on 'input' (set-from-event @model 'default_port')}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label data-test-default-port-label>{{t
|
||||||
|
'form.default_port.label'
|
||||||
|
}}</F.Label>
|
||||||
|
<F.HelperText>{{t 'form.default_port.help'}}</F.HelperText>
|
||||||
|
{{#if @model.errors.default_port}}
|
||||||
|
<F.Error as |E|>
|
||||||
|
{{#each @model.errors.default_port as |error|}}
|
||||||
|
<E.Message>{{error.message}}</E.Message>
|
||||||
|
{{/each}}
|
||||||
|
</F.Error>
|
||||||
|
{{/if}}
|
||||||
|
</Hds::Form::TextInput::Field>
|
||||||
|
|
||||||
<Hds::Form::TextInput::Field
|
<Hds::Form::TextInput::Field
|
||||||
@isRequired={{false}}
|
@isRequired={{false}}
|
||||||
@isOptional={{true}}
|
@isOptional={{true}}
|
||||||
@@ -125,31 +176,6 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</Hds::Form::TextInput::Field>
|
</Hds::Form::TextInput::Field>
|
||||||
|
|
||||||
<Hds::Form::TextInput::Field
|
|
||||||
@isRequired={{@model.isTCP}}
|
|
||||||
@isOptional={{not @model.isTCP}}
|
|
||||||
@value={{@model.default_port}}
|
|
||||||
@isInvalid={{@model.errors.default_port}}
|
|
||||||
@type='text'
|
|
||||||
name='default_port'
|
|
||||||
disabled={{form.disabled}}
|
|
||||||
placeholder={{@defaultPort}}
|
|
||||||
{{on 'input' (set-from-event @model 'default_port')}}
|
|
||||||
as |F|
|
|
||||||
>
|
|
||||||
<F.Label data-test-default-port-label>{{t
|
|
||||||
'form.default_port.label'
|
|
||||||
}}</F.Label>
|
|
||||||
<F.HelperText>{{t 'form.default_port.help'}}</F.HelperText>
|
|
||||||
{{#if @model.errors.default_port}}
|
|
||||||
<F.Error as |E|>
|
|
||||||
{{#each @model.errors.default_port as |error|}}
|
|
||||||
<E.Message>{{error.message}}</E.Message>
|
|
||||||
{{/each}}
|
|
||||||
</F.Error>
|
|
||||||
{{/if}}
|
|
||||||
</Hds::Form::TextInput::Field>
|
|
||||||
|
|
||||||
{{#if (feature-flag 'target-worker-filters-v2')}}
|
{{#if (feature-flag 'target-worker-filters-v2')}}
|
||||||
<Hds::Form::Fieldset
|
<Hds::Form::Fieldset
|
||||||
class='target-workers'
|
class='target-workers'
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { TYPES_TARGET } from 'api/models/target';
|
import { TYPES_TARGET } from 'api/models/target';
|
||||||
|
import { loading } from 'ember-loading';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { notifyError } from 'core/decorators/notify';
|
||||||
|
|
||||||
// NOTE: this is all a temporary solution till we have a resource type helper.
|
// NOTE: this is all a temporary solution till we have a resource type helper.
|
||||||
const types = [...TYPES_TARGET].reverse();
|
const types = [...TYPES_TARGET].reverse();
|
||||||
@@ -9,6 +13,11 @@ const icons = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class FormTargetComponent extends Component {
|
export default class FormTargetComponent extends Component {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service confirm;
|
||||||
|
@service intl;
|
||||||
|
|
||||||
// =properties
|
// =properties
|
||||||
/**
|
/**
|
||||||
* maps resource type with icon
|
* maps resource type with icon
|
||||||
@@ -37,4 +46,39 @@ export default class FormTargetComponent extends Component {
|
|||||||
get showDeprecationMessage() {
|
get showDeprecationMessage() {
|
||||||
return !this.args.model.isNew && this.args.model.worker_filter;
|
return !this.args.model.isNew && this.args.model.worker_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@loading
|
||||||
|
@notifyError(({ message }) => message)
|
||||||
|
async submit() {
|
||||||
|
const target = this.args.model;
|
||||||
|
const numHostSources = target.host_sources?.length;
|
||||||
|
const address = target.address;
|
||||||
|
if (address && numHostSources) {
|
||||||
|
try {
|
||||||
|
await this.confirm.confirm(
|
||||||
|
this.intl.t(
|
||||||
|
'resources.target.questions.delete-host-sources.message',
|
||||||
|
{ numHostSources }
|
||||||
|
),
|
||||||
|
{
|
||||||
|
title: 'resources.target.questions.delete-host-sources.title',
|
||||||
|
confirm: 'actions.remove-resources',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// if the user denies, do nothing and return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await target.removeHostSources(
|
||||||
|
target.host_sources.map((hs) => hs.host_source_id)
|
||||||
|
);
|
||||||
|
// After saving the host sources, the model gets reset to an empty address,
|
||||||
|
// so we need to update the address with the previous value before saving
|
||||||
|
target.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.args.submit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ export default class ScopesScopeTargetsRoute extends Route {
|
|||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
@loading
|
@loading
|
||||||
@notifyError(({ message }) => message)
|
@notifyError(({ message }) => message, { catch: true })
|
||||||
@notifySuccess(({ isNew }) =>
|
@notifySuccess(({ isNew }) =>
|
||||||
isNew ? 'notifications.create-success' : 'notifications.save-success'
|
isNew ? 'notifications.create-success' : 'notifications.save-success'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -132,7 +132,11 @@
|
|||||||
</dialog.body>
|
</dialog.body>
|
||||||
<dialog.footer>
|
<dialog.footer>
|
||||||
<Rose::Button @style='primary' {{on 'click' accept}}>
|
<Rose::Button @style='primary' {{on 'click' accept}}>
|
||||||
{{t 'actions.ok'}}
|
{{if
|
||||||
|
confirmation.options.confirm
|
||||||
|
(t confirmation.options.confirm)
|
||||||
|
(t 'actions.ok')
|
||||||
|
}}
|
||||||
</Rose::Button>
|
</Rose::Button>
|
||||||
<Rose::Button @style='secondary' {{on 'click' deny}}>
|
<Rose::Button @style='secondary' {{on 'click' deny}}>
|
||||||
{{t 'actions.cancel'}}
|
{{t 'actions.cancel'}}
|
||||||
|
|||||||
@@ -136,13 +136,13 @@ module('Acceptance | targets | create', function (hooks) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('defualt port is not marked required for SSH targets', async function (assert) {
|
test('default port is not marked required for SSH targets', async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
await visit(urls.newTarget);
|
await visit(urls.newTarget);
|
||||||
assert.dom('[data-test-default-port-label]').includesText('Optional');
|
assert.dom('[data-test-default-port-label]').includesText('Optional');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('defualt port is marked required for TCP targets', async function (assert) {
|
test('default port is marked required for TCP targets', async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
await visit(urls.newTarget);
|
await visit(urls.newTarget);
|
||||||
await click('[value="tcp"]');
|
await click('[value="tcp"]');
|
||||||
@@ -285,4 +285,31 @@ module('Acceptance | targets | create', function (hooks) {
|
|||||||
assert.dom('[role="alert"] div').hasText('The request was invalid.');
|
assert.dom('[role="alert"] div').hasText('The request was invalid.');
|
||||||
assert.dom('.hds-form-error__message').hasText('Name is required.');
|
assert.dom('.hds-form-error__message').hasText('Name is required.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can save address', async function (assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
const targetCount = getTargetCount();
|
||||||
|
await visit(urls.targets);
|
||||||
|
|
||||||
|
await click(`[href="${urls.newTarget}"]`);
|
||||||
|
await fillIn('[name="name"]', 'random string');
|
||||||
|
await fillIn('[name="address"]', '0.0.0.0');
|
||||||
|
await click('[type="submit"]');
|
||||||
|
|
||||||
|
assert.strictEqual(getTargetCount(), targetCount + 1);
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.all().models[getTargetCount() - 1].address,
|
||||||
|
'0.0.0.0'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('address field does not exist when target network address feature is disabled', async function (assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
featuresService.disable('target-network-address');
|
||||||
|
await visit(urls.targets);
|
||||||
|
|
||||||
|
await click(`[href="${urls.newTarget}"]`);
|
||||||
|
|
||||||
|
assert.dom('[name="address"]').doesNotExist();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -252,4 +252,79 @@ module('Acceptance | targets | host-sources', function (hooks) {
|
|||||||
assert.strictEqual(getTargetHostSetCount(), targetHostSetCount);
|
assert.strictEqual(getTargetHostSetCount(), targetHostSetCount);
|
||||||
assert.dom('[role="alert"] div').hasText('The request was invalid.');
|
assert.dom('[role="alert"] div').hasText('The request was invalid.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('saving host source with address brings up confirmation modal and removes address', async function (assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
const confirmService = this.owner.lookup('service:confirm');
|
||||||
|
confirmService.enabled = true;
|
||||||
|
const target = this.server.create('target', {
|
||||||
|
scope: instances.scopes.project,
|
||||||
|
address: '0.0.0.0',
|
||||||
|
});
|
||||||
|
const targetHostSetCount = this.server.schema.targets.find(target.id)
|
||||||
|
.hostSets.models.length;
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).address,
|
||||||
|
'0.0.0.0'
|
||||||
|
);
|
||||||
|
|
||||||
|
const targetUrl = `${urls.targets}/${target.id}`;
|
||||||
|
await visit(targetUrl);
|
||||||
|
await click(`[href="${targetUrl}/host-sources"]`);
|
||||||
|
await click('.rose-layout-page-actions a', 'Click add host set');
|
||||||
|
|
||||||
|
await click('tbody label');
|
||||||
|
await click('form [type="submit"]');
|
||||||
|
|
||||||
|
assert.dom('.rose-dialog').isVisible();
|
||||||
|
await click('.rose-dialog-footer .rose-button-primary', 'Remove resources');
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).address,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).hostSets.models.length,
|
||||||
|
targetHostSetCount + 1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('saving host source with address brings up confirmation modal and can cancel', async function (assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
const confirmService = this.owner.lookup('service:confirm');
|
||||||
|
confirmService.enabled = true;
|
||||||
|
const target = this.server.create('target', {
|
||||||
|
scope: instances.scopes.project,
|
||||||
|
address: '0.0.0.0',
|
||||||
|
});
|
||||||
|
const targetHostSetCount = this.server.schema.targets.find(target.id)
|
||||||
|
.hostSets.models.length;
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).address,
|
||||||
|
'0.0.0.0'
|
||||||
|
);
|
||||||
|
|
||||||
|
const targetUrl = `${urls.targets}/${target.id}`;
|
||||||
|
await visit(targetUrl);
|
||||||
|
await click(`[href="${targetUrl}/host-sources"]`);
|
||||||
|
await click('.rose-layout-page-actions a', 'Click add host set');
|
||||||
|
|
||||||
|
await click('tbody label');
|
||||||
|
await click('form [type="submit"]');
|
||||||
|
|
||||||
|
assert.dom('.rose-dialog').isVisible();
|
||||||
|
await click(
|
||||||
|
'.rose-dialog-footer .rose-button-secondary',
|
||||||
|
'Remove resources'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).address,
|
||||||
|
'0.0.0.0'
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).hostSets.models.length,
|
||||||
|
targetHostSetCount
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -199,4 +199,89 @@ module('Acceptance | targets | update', function (hooks) {
|
|||||||
|
|
||||||
assert.dom('form [type="button"]').doesNotExist();
|
assert.dom('form [type="button"]').doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('saving address with existing host sources brings up confirmation modal and removes host sources', async function (assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
const confirmService = this.owner.lookup('service:confirm');
|
||||||
|
confirmService.enabled = true;
|
||||||
|
this.server.createList(
|
||||||
|
'host-catalog',
|
||||||
|
8,
|
||||||
|
{ scope: instances.scopes.project },
|
||||||
|
'withChildren'
|
||||||
|
);
|
||||||
|
this.server.createList(
|
||||||
|
'credential-store',
|
||||||
|
3,
|
||||||
|
{ scope: instances.scopes.project },
|
||||||
|
'withAssociations'
|
||||||
|
);
|
||||||
|
const target = this.server.create(
|
||||||
|
'target',
|
||||||
|
{ scope: instances.scopes.project },
|
||||||
|
'withAssociations'
|
||||||
|
);
|
||||||
|
assert.true(this.server.schema.targets.find(target.id).hostSets.length > 0);
|
||||||
|
|
||||||
|
const url = `${urls.targets}/${target.id}`;
|
||||||
|
await visit(urls.targets);
|
||||||
|
await click(`[href="${url}"]`);
|
||||||
|
|
||||||
|
await click('form [type="button"]', 'Activate edit mode');
|
||||||
|
await fillIn('[name="address"]', '0.0.0.0');
|
||||||
|
await click('[type="submit"]');
|
||||||
|
|
||||||
|
assert.dom('.rose-dialog').isVisible();
|
||||||
|
await click('.rose-dialog-footer .rose-button-primary', 'Remove resources');
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).address,
|
||||||
|
'0.0.0.0'
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).hostSets.length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('saving address with existing host sources brings up confirmation modal and can cancel', async function (assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
const confirmService = this.owner.lookup('service:confirm');
|
||||||
|
confirmService.enabled = true;
|
||||||
|
this.server.createList(
|
||||||
|
'host-catalog',
|
||||||
|
8,
|
||||||
|
{ scope: instances.scopes.project },
|
||||||
|
'withChildren'
|
||||||
|
);
|
||||||
|
this.server.createList(
|
||||||
|
'credential-store',
|
||||||
|
3,
|
||||||
|
{ scope: instances.scopes.project },
|
||||||
|
'withAssociations'
|
||||||
|
);
|
||||||
|
const target = this.server.create(
|
||||||
|
'target',
|
||||||
|
{ scope: instances.scopes.project },
|
||||||
|
'withAssociations'
|
||||||
|
);
|
||||||
|
assert.true(this.server.schema.targets.find(target.id).hostSets.length > 0);
|
||||||
|
|
||||||
|
const url = `${urls.targets}/${target.id}`;
|
||||||
|
await visit(urls.targets);
|
||||||
|
await click(`[href="${url}"]`);
|
||||||
|
|
||||||
|
await click('form [type="button"]', 'Activate edit mode');
|
||||||
|
await fillIn('[name="address"]', '0.0.0.0');
|
||||||
|
await click('[type="submit"]');
|
||||||
|
|
||||||
|
assert.dom('.rose-dialog').isVisible();
|
||||||
|
await click('.rose-dialog-footer .rose-button-secondary', 'Cancel');
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
this.server.schema.targets.find(target.id).address,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
assert.true(this.server.schema.targets.find(target.id).hostSets.length > 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user