diff --git a/addons/api/addon/generated/models/host-set.js b/addons/api/addon/generated/models/host-set.js index 6ce3eb882..d1af3fe32 100644 --- a/addons/api/addon/generated/models/host-set.js +++ b/addons/api/addon/generated/models/host-set.js @@ -13,6 +13,11 @@ export default class GeneratedHostSetModel extends BaseModel { readOnly: true }) type; + @attr('string', { + description: 'The owning host catalog ID.', + readOnly: true + }) host_catalog_id; + @attr('string', { description: 'Optional name for identification purposes' }) name; diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index e30e86236..9b6611bb1 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -1,5 +1,6 @@ import GeneratedHostSetModel from '../generated/models/host-set'; import { fragmentArray } from 'ember-data-model-fragments/attributes'; +//import { computed } from '@ember/object'; export default class HostSetModel extends GeneratedHostSetModel { @@ -11,6 +12,18 @@ export default class HostSetModel extends GeneratedHostSetModel { */ @fragmentArray('fragment-string', {readOnly: true}) host_ids; + // TODO this is here in case we want it in the future, + // especially for the target / add host sets workflow + /** + * Returns the host catalog instance associated with this host set if + * it is already loaded into the store. + * @type {?HostCatalogModel} + */ + // @computed('host_catalog_id') + // get hostCatalog() { + // return this.store.peekRecord('host-catalog', this.host_catalog_id); + // } + // =methods /** diff --git a/addons/rose/addon/templates/components/rose/dropdown/link.hbs b/addons/rose/addon/templates/components/rose/dropdown/link.hbs index 6b3bd413a..0213454f6 100644 --- a/addons/rose/addon/templates/components/rose/dropdown/link.hbs +++ b/addons/rose/addon/templates/components/rose/dropdown/link.hbs @@ -1,8 +1,18 @@ - - {{yield}} - \ No newline at end of file +{{#if @model}} + + {{yield}} + +{{else}} + + {{yield}} + +{{/if}} diff --git a/ui/core/app/components/form/target/add-host-sets/index.hbs b/ui/core/app/components/form/target/add-host-sets/index.hbs new file mode 100644 index 000000000..5ace0a855 --- /dev/null +++ b/ui/core/app/components/form/target/add-host-sets/index.hbs @@ -0,0 +1,44 @@ + + + + + + + + {{t 'form.label.id'}} + {{t 'form.label.name'}} + {{t 'form.label.type'}} + {{t 'resources.host-catalog'}} + + + + {{#each this.filteredHostSets as |hostSet|}} + + + + + {{hostSet.name}} + + {{hostSet.type}} + + + {{hostSet.host_catalog_id}} + + + {{/each}} + + + + diff --git a/ui/core/app/components/form/target/add-host-sets/index.js b/ui/core/app/components/form/target/add-host-sets/index.js new file mode 100644 index 000000000..aadca547e --- /dev/null +++ b/ui/core/app/components/form/target/add-host-sets/index.js @@ -0,0 +1,44 @@ +import Component from '@glimmer/component'; +import { computed, action } from '@ember/object'; +import { A } from '@ember/array'; + +export default class FormTargetAddHostSetsComponent extends Component { + + // =properties + + /** + * Array of selected host set IDs. + * @type {EmberArray} + */ + selectedHostSetIDs = A(); + + /** + * Host sets not already added to the target. + * @type {[HostSetModel]} + */ + @computed('args.{hostSets.[],model.host_sets.[]}') + get filteredHostSets() { + // Get IDs for host sets already added to the current target + const alreadyAddedHostSetIDs = this.args.model.host_sets + .map(({ host_set_id }) => host_set_id); + const notAddedHostSets = this.args.hostSets + .filter(({ id }) => !alreadyAddedHostSetIDs.includes(id)); + return notAddedHostSets; + } + + // =actions + + @action + toggleHostSet(hostSet) { + if (!this.selectedHostSetIDs.includes(hostSet.id)) { + this.selectedHostSetIDs.addObject(hostSet.id); + } else { + this.selectedHostSetIDs.removeObject(hostSet.id); + } + } + + @action + submit(fn) { + fn(this.selectedHostSetIDs); + } +} diff --git a/ui/core/app/controllers/scopes/scope/projects/project/targets/target/add-host-sets.js b/ui/core/app/controllers/scopes/scope/projects/project/targets/target/add-host-sets.js new file mode 100644 index 000000000..81698e45f --- /dev/null +++ b/ui/core/app/controllers/scopes/scope/projects/project/targets/target/add-host-sets.js @@ -0,0 +1,19 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeProjectsProjectTargetsTargetAddHostSetsController extends Controller { + + // =services + + @service intl; + + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.intl.t('actions.add-host-sets'); + } + +} diff --git a/ui/core/app/router.js b/ui/core/app/router.js index e5577e83a..effcb4523 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -40,6 +40,7 @@ Router.map(function () { this.route('host-sets', function() { this.route('host-set', { path: ':host_set_id'}, function() {}); }); + this.route('add-host-sets'); }); this.route('new'); }); diff --git a/ui/core/app/routes/scopes/scope/projects/project/targets/target.js b/ui/core/app/routes/scopes/scope/projects/project/targets/target.js index 8b5cf77ab..c1d1093f8 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/targets/target.js +++ b/ui/core/app/routes/scopes/scope/projects/project/targets/target.js @@ -15,4 +15,28 @@ export default class ScopesScopeProjectsProjectTargetsTargetRoute extends Route return this.store.findRecord('target', target_id, { adapterOptions: { scopeID } }); } + /** + * Renders the target-specific templates for header, navigation, + * and actions page sections. + * @override + */ + renderTemplate() { + super.renderTemplate(...arguments); + + this.render('scopes/scope/projects/project/targets/target/-header', { + into: 'scopes/scope/projects/project/targets/target', + outlet: 'header', + }); + + this.render('scopes/scope/projects/project/targets/target/-navigation', { + into: 'scopes/scope/projects/project/targets/target', + outlet: 'navigation', + }); + + this.render('scopes/scope/projects/project/targets/target/-actions', { + into: 'scopes/scope/projects/project/targets/target', + outlet: 'actions', + }); + } + } diff --git a/ui/core/app/routes/scopes/scope/projects/project/targets/target/add-host-sets.js b/ui/core/app/routes/scopes/scope/projects/project/targets/target/add-host-sets.js new file mode 100644 index 000000000..f58b7ebb7 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/targets/target/add-host-sets.js @@ -0,0 +1,89 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { all } from 'rsvp'; + +export default class ScopesScopeProjectsProjectTargetsTargetAddHostSetsRoute extends Route { + + // =services + + @service intl; + @service notify; + + // =methods + + /** + * Emtpy out any previously loaded host catalogs and host sets. + */ + beforeModel() { + this.store.unloadAll('host-catalog'); + this.store.unloadAll('host-set'); + } + + /** + * Returns the current target, all host catalogs, and all host sets. + * @return {{target: TargetModel, hostCatalogs: [HostCatalogModel], hostSets: [HostSetModel]}} + */ + async model() { + const target = + this.modelFor('scopes.scope.projects.project.targets.target'); + const { scopeID } = target; + const hostCatalogs = + await this.store.findAll('host-catalog', { adapterOptions: { scopeID } }); + await all(hostCatalogs.map(({ id: hostCatalogID }) => + this.store + .findAll('host-set', { adapterOptions: { scopeID, hostCatalogID } }) + )); + const hostSets = this.store.peekAll('host-set'); + return { + target, + hostCatalogs, + hostSets + }; + } + + /** + * Renders the add-host-sets-specific header template. + * @override + */ + renderTemplate() { + super.renderTemplate(...arguments); + + this.render('scopes/scope/projects/project/targets/target/add-host-sets/-header', { + into: 'scopes/scope/projects/project/targets/target', + outlet: 'header', + }); + + this.render('-empty', { + into: 'scopes/scope/projects/project/targets/target', + outlet: 'navigation', + }); + + this.render('-empty', { + into: 'scopes/scope/projects/project/targets/target', + outlet: 'actions', + }); + } + + // =actions + + @action + async save(target, hostSetIDs) { + try { + await target.addHostSets(hostSetIDs); + this.replaceWith('scopes.scope.projects.project.targets.target.host-sets'); + this.notify.success(this.intl.t('notify.save-success')); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } + + /** + * Redirect to target host sets as if nothing ever happened. + */ + @action + cancel() { + this.replaceWith('scopes.scope.projects.project.targets.target.host-sets'); + } +} diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs index 50b97cd47..2a878ff51 100644 --- a/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target.hbs @@ -1,3 +1,5 @@ +{{title @model.displayName}} + @@ -5,30 +7,15 @@ -

{{t "resources.target"}}

+ {{outlet "header"}}
- - - {{t 'titles.details'}} - - - {{t 'titles.host-sets'}} - - + {{outlet "navigation"}} - - - {{t 'actions.delete-target'}} - - + {{outlet "actions"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/-actions.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-actions.hbs new file mode 100644 index 000000000..a4c8ed5fa --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-actions.hbs @@ -0,0 +1,12 @@ + + + {{t 'actions.add-host-sets'}} + + + {{t 'actions.delete-target'}} + + diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/-header.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-header.hbs new file mode 100644 index 000000000..721607f47 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-header.hbs @@ -0,0 +1 @@ +

{{t "resources.target"}}

diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/-navigation.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-navigation.hbs new file mode 100644 index 000000000..b486f4ca8 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/-navigation.hbs @@ -0,0 +1,8 @@ + + + {{t 'titles.details'}} + + + {{t 'titles.host-sets'}} + + diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/add-host-sets.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/add-host-sets.hbs new file mode 100644 index 000000000..206de2d68 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/add-host-sets.hbs @@ -0,0 +1,21 @@ +{{title (t 'actions.add-host-sets')}} + +{{#if @model.hostSets}} + +{{/if}} + +{{#unless @model.hostSets}} + + + + {{t "descriptions.no-host-sets-for-target"}} + + + +{{/unless}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/targets/target/add-host-sets/-header.hbs b/ui/core/app/templates/scopes/scope/projects/project/targets/target/add-host-sets/-header.hbs new file mode 100644 index 000000000..f29bc39c7 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/targets/target/add-host-sets/-header.hbs @@ -0,0 +1 @@ +

{{t "actions.add-host-sets"}}

diff --git a/ui/core/mirage/factories/host-set.js b/ui/core/mirage/factories/host-set.js index 2f12361c6..ad9ae1fa1 100644 --- a/ui/core/mirage/factories/host-set.js +++ b/ui/core/mirage/factories/host-set.js @@ -1,3 +1,5 @@ import factory from '../generated/factories/host-set'; -export default factory.extend(); +export default factory.extend({ + id: (i) => `host-set-id-${i}` +}); diff --git a/ui/core/mirage/serializers/host-set.js b/ui/core/mirage/serializers/host-set.js index c4640cc93..bd680326e 100644 --- a/ui/core/mirage/serializers/host-set.js +++ b/ui/core/mirage/serializers/host-set.js @@ -13,6 +13,7 @@ export default ApplicationSerializer.extend({ _hashForModel(model) { const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); json.host_ids = model.hostIds; + json.host_catalog_id = model.hostCatalogId; return json; } diff --git a/ui/core/tests/acceptance/targets-test.js b/ui/core/tests/acceptance/targets-test.js index 4d6ccb0ad..734090704 100644 --- a/ui/core/tests/acceptance/targets-test.js +++ b/ui/core/tests/acceptance/targets-test.js @@ -66,4 +66,12 @@ module('Acceptance | targets', function (hooks) { test('can delete a host sets', async function(assert) { assert.expect(0); }); + + test('select and save host sets to add', async function (assert) { + assert.expect(0); + }); + + test('select and cancel host sets to add', async function (assert) { + assert.expect(0); + }); }); diff --git a/ui/core/tests/integration/components/form/target/add-host-sets-test.js b/ui/core/tests/integration/components/form/target/add-host-sets-test.js new file mode 100644 index 000000000..6013607fe --- /dev/null +++ b/ui/core/tests/integration/components/form/target/add-host-sets-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/target/add-host-sets', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + assert.expect(0); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/projects/project/targets/target/add-host-sets-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects/project/targets/target/add-host-sets-test.js new file mode 100644 index 000000000..5d58f8870 --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/projects/project/targets/target/add-host-sets-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/projects/project/targets/target/add-host-sets', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:scopes/scope/projects/project/targets/target/add-host-sets'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/targets/target/add-host-sets-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/targets/target/add-host-sets-test.js new file mode 100644 index 000000000..520a4e41f --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/targets/target/add-host-sets-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/targets/target/add-host-sets', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/targets/target/add-host-sets'); + assert.ok(route); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 8b630279f..4563e59eb 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -21,6 +21,7 @@ actions: delete-target: Delete target add-host-set: Create Host Set add-host: Create Host + add-host-sets: Add Host Sets auth: titles: no-scopes: No Scopes Available @@ -89,6 +90,7 @@ titles: targets: Targets global: Global no-host-set-hosts: No Hosts Available + no-host-sets-for-target: No Host Sets Available descriptions: host-catalogs: Overview of collections of hosts and host sets. empty-set: There are no items to display yet. You may be able to add items or try back later. @@ -103,6 +105,7 @@ descriptions: targets-welcome: Targets provide Users the means to connect to and interact networked services. A Target contains a collection of Hosts and a collection of Credentials. target-host-sets-welcome: Add Host Sets to this target to provide access. no-host-set-hosts: Before associating hosts with this host set, create hosts within the host catalog. + no-host-sets-for-target: No host sets are available to add to this target. states: info: Info success: Success