diff --git a/ui/core/app/components/form/host-set/add-hosts/index.hbs b/ui/core/app/components/form/host-set/add-hosts/index.hbs
new file mode 100644
index 000000000..b6428b1c1
--- /dev/null
+++ b/ui/core/app/components/form/host-set/add-hosts/index.hbs
@@ -0,0 +1,56 @@
+{{#if this.hasAvailableHosts}}
+
+
+
+
+
+
+
+ {{t "form.label.name"}}
+ {{t "form.label.type"}}
+ {{t "form.label.id"}}
+
+
+
+ {{#each this.filteredHosts as |host|}}
+
+
+
+
+ {{host.name}}
+
+ {{host.type}}
+
+
+ {{/each}}
+
+
+
+
+{{/if}}
+
+{{#unless this.hasAvailableHosts}}
+
+
+
+ {{t "descriptions.no-host-set-hosts"}}
+
+
+
+ {{t "actions.back"}}
+
+
+
+{{/unless}}
diff --git a/ui/core/app/components/form/host-set/add-hosts/index.js b/ui/core/app/components/form/host-set/add-hosts/index.js
new file mode 100644
index 000000000..03915cdee
--- /dev/null
+++ b/ui/core/app/components/form/host-set/add-hosts/index.js
@@ -0,0 +1,50 @@
+import Component from '@glimmer/component';
+import { computed, action } from '@ember/object';
+import { A } from '@ember/array';
+
+export default class FormHostSetAddHostsIndexComponent extends Component {
+
+ // =properties
+
+ /**
+ * Array of selected host IDs.
+ * @type {EmberArray}
+ */
+ selectedHostIDs = A();
+
+ /**
+ * Checks for unassigned hosts.
+ * @param {[HostModel]} filteredHosts
+ * @type {boolean}
+ */
+ @computed('filteredHosts')
+ get hasAvailableHosts() {
+ return this.filteredHosts.length > 0;
+ }
+
+ /**
+ * Hosts not currently assigned to current host set.
+ * @type {[HostModel]}
+ */
+ @computed('args.{model,hosts}')
+ get filteredHosts() {
+ const currentHostIDs = this.args.hosts.map(host => host.id);
+ return this.args.hosts.filter(({ id }) => !currentHostIDs.includes(id));
+ }
+
+ // =actions
+
+ @action
+ toggleHost(host) {
+ if (!this.selectedHostIDs.includes(host.id)) {
+ this.selectedHostIDs.addObject(host.id);
+ } else {
+ this.selectedHostIDs.removeObject(host.id);
+ }
+ }
+
+ @action
+ submit(fn) {
+ fn(this.selectedHostIDs);
+ }
+}
diff --git a/ui/core/app/router.js b/ui/core/app/router.js
index c55b8124b..70de27a3c 100644
--- a/ui/core/app/router.js
+++ b/ui/core/app/router.js
@@ -26,6 +26,7 @@ Router.map(function () {
this.route('host-sets', function() {
this.route('host-set', { path: ':host_set_id' }, function() {
this.route('hosts');
+ this.route('add-hosts');
});
this.route('new');
});
diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts.js
new file mode 100644
index 000000000..0884616dc
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts.js
@@ -0,0 +1,108 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+import { hash } from 'rsvp';
+
+export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogHostSetsHostSetAddHostsRoute extends Route {
+
+ // =services
+
+ @service intl;
+ @service notify;
+
+ // =methods
+
+ /**
+ * Empty out any previously loaded hosts.
+ */
+ beforeModel() {
+ this.store.unloadAll('host');
+ }
+
+ /**
+ * Loads all hosts under the current host catalog.
+ * @return {Promise{[HostModel]}}
+ */
+ async model() {
+ const scopeID = this.modelFor('scopes.scope.projects.project').id;
+ const hostCatalogID = this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog').id;
+ const hostSet = this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog.host-sets.host-set');
+ return hash({
+ hostSet,
+ hosts: this.store.findAll('host', { adapterOptions: { scopeID, hostCatalogID } })
+ });
+ }
+
+ /**
+ * Renders the add-hosts-specific header template.
+ * Empties the actions and navigation outlets and renders a custom empty header.
+ * @override
+ */
+ renderTemplate() {
+ super.renderTemplate(...arguments);
+
+ this.render('scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts/-header', {
+ into: 'scopes/scope/projects/project/host-catalogs/host-catalog',
+ outlet: 'header',
+ });
+
+ this.render('-empty', {
+ into: 'scopes/scope/projects/project/host-catalogs/host-catalog',
+ outlet: 'navigation',
+ });
+
+ this.render('-empty', {
+ into: 'scopes/scope/projects/project/host-catalogs/host-catalog',
+ outlet: 'actions',
+ });
+ }
+
+ // =actions
+
+ /**
+ * Toggles the presence of the passed host ID in the passed host set's
+ * `host_ids` field.
+ * @param {HostSetModel} hostSet
+ * @param {HostModel} host
+ */
+ @action
+ toggleHost(hostSet, host) {
+ const isHostSelected = Boolean(hostSet.host_ids.findBy('value', host.id));
+ const fragment = hostSet.host_ids.findBy('value', host.id);
+ if (isHostSelected) {
+ // if host is already selected, filter it out
+ hostSet.host_ids.removeObject(fragment);
+ } else {
+ // if host is not selected, add it
+ hostSet.host_ids.addObject({ value: host.id });
+ }
+ }
+
+ /**
+ * Saves host IDs on the host set.
+ * @param {HostSetModel} hostSet
+ */
+ @action
+ async addHosts(hostSet, hostIDs) {
+ try {
+ const scopeID = this.modelFor('scopes.scope.projects.project').id;
+ const hostCatalogID = this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog').id;
+ await hostSet.addHosts(hostIDs, { adapterOptions : { scopeID, hostCatalogID } });
+ this.notify.success(
+ this.intl.t('notify.save-success')
+ );
+ this.replaceWith('scopes.scope.projects.project.host-catalogs.host-catalog.host-sets.host-set.hosts');
+ } catch (error) {
+ // TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
+
+ /**
+ * Redirect to hosts as if nothing ever happened.
+ */
+ @action
+ cancel() {
+ this.replaceWith('scopes.scope.projects.project.host-catalogs.host-catalog.host-sets.host-set.hosts');
+ }
+}
diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.js
index b7bc79f0e..c5bbfb2d9 100644
--- a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.js
+++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.js
@@ -1,17 +1,19 @@
import Route from '@ember/routing/route';
-import { inject as service } from '@ember/service';
-import { action } from '@ember/object';
import { hash } from 'rsvp';
+import { action } from '@ember/object';
export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogHostSetsHostSetHostsRoute extends Route {
- // =services
-
- @service intl;
- @service notify;
-
// =methods
+ /**
+ * Empty out any previously loaded hosts.
+ */
+ beforeModel() {
+ this.store.unloadAll('host');
+ }
+
+
/**
* Loads all hosts under the current host catalog.
* @return {Promise{[HostModel]}}
@@ -20,51 +22,27 @@ export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogHostSetsHo
const scopeID = this.modelFor('scopes.scope.projects.project').id;
const hostCatalogID = this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog').id;
const hostSet = this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog.host-sets.host-set');
- this.store.unloadAll('host');
return hash({
hostSet,
hosts: this.store.findAll('host', { adapterOptions: { scopeID, hostCatalogID } })
});
}
- // =actions
-
/**
- * Toggles the presence of the passed host ID in the passed host set's
- * `host_ids` field.
- * @param {HostSetModel} hostSet
+ * Remove a host from the current hostset and redirect to hosts index.
* @param {HostModel} host
*/
@action
- toggleHost(hostSet, host) {
- const isHostSelected = Boolean(hostSet.host_ids.findBy('value', host.id));
- const fragment = hostSet.host_ids.findBy('value', host.id);
- if (isHostSelected) {
- // if host is already selected, filter it out
- hostSet.host_ids.removeObject(fragment);
- } else {
- // if host is not selected, add it
- hostSet.host_ids.addObject({ value: host.id });
- }
- }
-
- /**
- * Saves host IDs on the host set.
- * @param {HostSetModel} hostSet
- */
- @action
- async save(hostSet) {
+ async removeHost(hostSet, host) {
try {
+ const scopeID = this.modelFor('scopes.scope.projects.project').id;
const hostCatalogID = this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog').id;
- const options = { adapterOptions : { hostCatalogID } };
- await hostSet.saveHostIDs(options);
- this.notify.success(
- this.intl.t('notify.save-success')
- );
+ await hostSet.removeHost(host.id, { adapterOptions : { scopeID, hostCatalogID } });
+ this.refresh();
+ this.notify.success(this.intl.t('notify.delete-success'));
} catch (error) {
// TODO: replace with translated strings
this.notify.error(error.message, { closeAfter: null });
}
}
-
}
diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-actions.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-actions.hbs
index b795fe639..ec81ba648 100644
--- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-actions.hbs
+++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/-actions.hbs
@@ -1,4 +1,7 @@
+
+ {{t 'actions.add-hosts'}}
+
--}}
+
+
diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts/-header.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts/-header.hbs
new file mode 100644
index 000000000..eab1e4623
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts/-header.hbs
@@ -0,0 +1 @@
+{{t "actions.add-hosts"}}
diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs
index cc1964bf8..437140df4 100644
--- a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs
+++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/hosts.hbs
@@ -1,11 +1,53 @@
{{#if @model.hosts}}
-
+
+
+
+
+ {{t "form.label.name"}}
+ {{t "form.label.type"}}
+ {{t "form.label.id"}}
+ {{t "form.label.actions"}}
+
+
+
+ {{#each @model.hosts as |host|}}
+
+
+
+ {{host.displayName}}
+
+ {{host.description}}
+
+
+ {{host.type}}
+
+
+ {{host.id}}
+
+
+
+
+ {{t "actions.remove"}}
+
+
+
+
+ {{/each}}
+
+
+
{{/if}}
{{#unless @model.hosts}}
diff --git a/ui/core/tests/integration/components/form/host-set/add-hosts/index-test.js b/ui/core/tests/integration/components/form/host-set/add-hosts/index-test.js
new file mode 100644
index 000000000..807b20d1e
--- /dev/null
+++ b/ui/core/tests/integration/components/form/host-set/add-hosts/index-test.js
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | form/host-set/add-hosts/index', function(hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.set('myAction', function(val) { ... });
+
+ await render(hbs``);
+
+ assert.equal(this.element.textContent.trim(), '');
+
+ // Template block usage:
+ await render(hbs`
+
+ template block text
+
+ `);
+
+ assert.equal(this.element.textContent.trim(), 'template block text');
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts-test.js
new file mode 100644
index 000000000..22e869203
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set/add-hosts');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml
index 12e5dd2a5..08e371e4b 100644
--- a/ui/core/translations/en-us.yaml
+++ b/ui/core/translations/en-us.yaml
@@ -26,6 +26,7 @@ actions:
add-host-sets: Add Host Sets
add-principals: Add Principals
add-members: Add Members
+ add-hosts: Add Hosts
auth:
titles:
no-scopes: No Scopes Available