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..37d12c6b7
--- /dev/null
+++ b/ui/core/app/components/form/host-set/add-hosts/index.js
@@ -0,0 +1,58 @@
+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 currently not assigned to current host set.
+ * @type {[HostModel]}
+ */
+ @computed('args.{model,hosts}')
+ get filteredHosts() {
+ const currentHostIDs = this.args.model.host_ids.map(host => host.value);
+ return this.args.hosts.filter(({ id }) => !currentHostIDs.includes(id));
+ }
+
+ // =actions
+
+ /**
+ * Toggle host selection
+ * @param {HostModel} host
+ */
+ @action
+ toggleHost(host) {
+ if (!this.selectedHostIDs.includes(host.id)) {
+ this.selectedHostIDs.addObject(host.id);
+ } else {
+ this.selectedHostIDs.removeObject(host.id);
+ }
+ }
+
+ /**
+ * Callback submit with selected host ids
+ * @param {requestCallback} fn
+ */
+ @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.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set.js
index 6d2b4abd1..4c98b45f2 100644
--- a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set.js
+++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/host-sets/host-set.js
@@ -13,6 +13,7 @@ export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogHostSetsHo
const { id: scopeID } = this.modelFor('scopes.scope.projects.project');
const hostCatalogID = this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog').id;
return this.store.findRecord('host-set', host_set_id, {
+ reload: true,
adapterOptions: { scopeID, hostCatalogID }
});
}
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..b7ef1afd2
--- /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,107 @@
+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 current host set and all hosts under the current host catalog.
+ * @return {Promise{HostSetModel,[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;
+ return hash({
+ hostSet: this.modelFor('scopes.scope.projects.project.host-catalogs.host-catalog.host-sets.host-set'),
+ 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..f349d0c72 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,7 +1,7 @@
import Route from '@ember/routing/route';
-import { inject as service } from '@ember/service';
+import { hash, all } from 'rsvp';
import { action } from '@ember/object';
-import { hash } from 'rsvp';
+import { inject as service } from '@ember/service';
export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogHostSetsHostSetHostsRoute extends Route {
@@ -13,58 +13,44 @@ export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogHostSetsHo
// =methods
/**
- * Loads all hosts under the current host catalog.
- * @return {Promise{[HostModel]}}
+ * Empty out any previously loaded hosts.
+ */
+ beforeModel() {
+ this.store.unloadAll('host');
+ }
+
+ /**
+ * Loads all hosts under the current host set.
+ * @return {Promise{HostSetModel,[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');
- this.store.unloadAll('host');
return hash({
hostSet,
- hosts: this.store.findAll('host', { adapterOptions: { scopeID, hostCatalogID } })
+ hosts: all(hostSet.host_ids.map(host =>
+ this.store.findRecord('host', host.value, { adapterOptions: { scopeID, hostCatalogID } })
+ ))
});
}
- // =actions
-
/**
- * Toggles the presence of the passed host ID in the passed host set's
- * `host_ids` field.
+ * Remove a host from the current host set and redirect to hosts index.
* @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 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/mirage/config.js b/ui/core/mirage/config.js
index 11c78a555..a95797a20 100644
--- a/ui/core/mirage/config.js
+++ b/ui/core/mirage/config.js
@@ -183,9 +183,30 @@ export default function() {
this.post('/scopes/:scope_id/host-catalogs/:hostCatalogId/host-sets/:idMethod', function ({ hostSets }, { params: { idMethod } }) {
const attrs = this.normalizedRequestAttrs();
const id = idMethod.split(':')[0];
+ const method = idMethod.split(':')[1];
const hostSet = hostSets.find(id);
- attrs.id = id;
- return hostSet.update(attrs);
+ const updatedAttrs = {
+ version: attrs.version,
+ hostIds: hostSet.hostIds
+ }
+
+ // If adding hosts, push them into the array
+ if(method === 'add-hosts') {
+ attrs.hostIds.forEach(id => {
+ if(!updatedAttrs.hostIds.includes(id)) {
+ updatedAttrs.hostIds.push(id);
+ }
+ })
+ }
+
+ // If deleting hosts, filter them out of the array
+ if (method === 'remove-hosts') {
+ updatedAttrs.hostIds = updatedAttrs.hostIds.filter(id => {
+ return !attrs.hostIds.includes(id);
+ });
+ }
+
+ return hostSet.update(updatedAttrs);
});
// target
diff --git a/ui/core/tests/integration/components/form/host-set/add-hosts-test.js b/ui/core/tests/integration/components/form/host-set/add-hosts-test.js
new file mode 100644
index 000000000..827fa036d
--- /dev/null
+++ b/ui/core/tests/integration/components/form/host-set/add-hosts-test.js
@@ -0,0 +1,12 @@
+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', function(hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function(assert) {
+ assert.expect(0);
+ });
+});
\ No newline at end of file
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