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