diff --git a/addons/api/addon/adapters/host-set.js b/addons/api/addon/adapters/host-set.js
new file mode 100644
index 000000000..fbbe11495
--- /dev/null
+++ b/addons/api/addon/adapters/host-set.js
@@ -0,0 +1,38 @@
+import ApplicationAdapter from './application';
+import { inject as service } from '@ember/service';
+
+export default class HostSetAdapter extends ApplicationAdapter {
+
+ // =services
+
+ @service store;
+
+ // =methods
+
+ /**
+ * Append a host catalog URL ahead of the host sets path if `hostCatalogID` is
+ * passed via adapter options. Otherwise, generate the prefix as normal.
+ *
+ * TODO: generalize the interface to resource-prefixed URLs by accepting
+ * a modelName and an ID, rather than looking for a known key.
+ *
+ * @override
+ * @param {string} path
+ * @param {string} parentURL
+ * @param {string} modelName
+ * @param {string} id
+ * @param {object} snapshot
+ * @return {string} urlPrefix
+ */
+ urlPrefix(path, parentURL, modelName, id, snapshot) {
+ const hostCatalogID = snapshot?.adapterOptions?.hostCatalogID;
+ if (hostCatalogID) {
+ const hostCatalogAdapter = this.store.adapterFor('host-catalog');
+ return hostCatalogAdapter
+ ._buildPrefixedURL('host-catalog', hostCatalogID, snapshot);
+ } else {
+ return super.urlPrefix(...arguments);
+ }
+ }
+
+}
diff --git a/addons/api/addon/adapters/host.js b/addons/api/addon/adapters/host.js
new file mode 100644
index 000000000..ae1747b06
--- /dev/null
+++ b/addons/api/addon/adapters/host.js
@@ -0,0 +1,38 @@
+import ApplicationAdapter from './application';
+import { inject as service } from '@ember/service';
+
+export default class HostAdapter extends ApplicationAdapter {
+
+ // =services
+
+ @service store;
+
+ // =methods
+
+ /**
+ * Append a host catalog URL ahead of the host path if `hostCatalogID` is
+ * passed via adapter options. Otherwise, generate the prefix as normal.
+ *
+ * TODO: generalize the interface to resource-prefixed URLs by accepting
+ * a modelName and an ID, rather than looking for a known key.
+ *
+ * @override
+ * @param {string} path
+ * @param {string} parentURL
+ * @param {string} modelName
+ * @param {string} id
+ * @param {object} snapshot
+ * @return {string} urlPrefix
+ */
+ urlPrefix(path, parentURL, modelName, id, snapshot) {
+ const hostCatalogID = snapshot?.adapterOptions?.hostCatalogID;
+ if (hostCatalogID) {
+ const hostCatalogAdapter = this.store.adapterFor('host-catalog');
+ return hostCatalogAdapter
+ ._buildPrefixedURL('host-catalog', hostCatalogID, snapshot);
+ } else {
+ return super.urlPrefix(...arguments);
+ }
+ }
+
+}
diff --git a/addons/api/addon/generated/models/host-set.js b/addons/api/addon/generated/models/host-set.js
new file mode 100644
index 000000000..5793cc4a5
--- /dev/null
+++ b/addons/api/addon/generated/models/host-set.js
@@ -0,0 +1,42 @@
+import BaseModel from '../../models/base';
+import { attr } from '@ember-data/model';
+
+/**
+ * HostSet is a collection of Hosts created and managed by a HostCatalog
+ */
+export default class GeneratedHostSetModel extends BaseModel {
+
+ // =attributes
+
+ @attr('string', {
+ description: 'The type of the resource, to help differentiate schemas'
+ }) type;
+
+ @attr('string', {
+ description: 'Optional name for identification purposes'
+ }) name;
+
+ @attr('string', {
+ description: 'Optional user-set description for identification purposes'
+ }) description;
+
+ @attr('date', {
+ description: 'The time this resource was created\nOutput only.',
+ readOnly: true
+ }) created_time;
+
+ @attr('date', {
+ description: 'The time this resource was last updated\nOutput only.',
+ readOnly: true
+ }) updated_time;
+
+ @attr('number', {
+ description: 'Current version number of this resource.',
+ })
+ version;
+
+ @attr('number', {
+ description: 'The total count of hosts in this host set\nOutput only.'
+ }) size;
+
+}
diff --git a/addons/api/addon/generated/models/host.js b/addons/api/addon/generated/models/host.js
new file mode 100644
index 000000000..0d9e0dd2d
--- /dev/null
+++ b/addons/api/addon/generated/models/host.js
@@ -0,0 +1,42 @@
+import BaseModel from '../../models/base';
+import { attr } from '@ember-data/model';
+
+/**
+ * Host contains all fields related to a Host resource
+ */
+export default class GeneratedHostModel extends BaseModel {
+
+ // =attributes
+
+ @attr('string', {
+ description: 'The type of the resource, to help differentiate schemas'
+ }) type;
+
+ @attr('string', {
+ description: 'Optional name for identification purposes'
+ }) name;
+
+ @attr('string', {
+ description: 'Optional user-set description for identification purposes'
+ }) description;
+
+ @attr('date', {
+ description: 'The time this resource was created\nOutput only.',
+ readOnly: true
+ }) created_time;
+
+ @attr('date', {
+ description: 'The time this resource was last updated\nOutput only.',
+ readOnly: true
+ }) updated_time;
+
+ @attr('number', {
+ description: 'Current version number of this resource.',
+ })
+ version;
+
+ @attr('string', {
+ description: 'The address (DNS or IP name) used to reach the host'
+ }) address;
+
+}
diff --git a/addons/api/addon/mixins/adapter-build-url.js b/addons/api/addon/mixins/adapter-build-url.js
index 264364d0d..cee278e05 100644
--- a/addons/api/addon/mixins/adapter-build-url.js
+++ b/addons/api/addon/mixins/adapter-build-url.js
@@ -46,6 +46,31 @@ export default Mixin.create({
return '';
},
+ /**
+ * Builds the (mostly full) URL with a prefix, but not the suffix.
+ * The `_buildURL` method adds a suffix.
+ * Do not call directly.
+ * @override
+ * @param {String} modelName
+ * @param {String} id
+ * @return {String} url
+ */
+ _buildPrefixedURL(modelName, id, snapshot) {
+ let path;
+ let url = [];
+ const host = get(this, 'host');
+ const prefix = this.urlPrefix(null, null, modelName, id, snapshot);
+ if (modelName) {
+ path = this.pathForType(modelName);
+ if (path) url.push(path);
+ }
+ if (id) url.push(encodeURIComponent(id));
+ if (prefix) url.unshift(prefix);
+ url = url.join('/');
+ if (!host && url && url.charAt(0) !== '/') url = '/' + url;
+ return `${url}`;
+ },
+
/**
* Overrides the default `_buildURL` with a nearly identical version.
* This method simply passes arguments into the `urlPrefix` method for
@@ -58,19 +83,8 @@ export default Mixin.create({
* @return {String} url
*/
_buildURL(modelName, id, snapshot) {
- let path;
- let url = [];
- const host = get(this, 'host');
- const prefix = this.urlPrefix(null, null, modelName, id, snapshot);
+ const url = this._buildPrefixedURL(modelName, id, snapshot);
const suffix = this.urlSuffix(modelName, id, snapshot);
- if (modelName) {
- path = this.pathForType(modelName);
- if (path) url.push(path);
- }
- if (id) url.push(encodeURIComponent(id));
- if (prefix) url.unshift(prefix);
- url = url.join('/');
- if (!host && url && url.charAt(0) !== '/') url = '/' + url;
return `${url}${suffix}`;
},
diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js
index a0162bfeb..894ee9cf1 100644
--- a/addons/api/addon/models/host-catalog.js
+++ b/addons/api/addon/models/host-catalog.js
@@ -1,3 +1,3 @@
import GeneratedHostCatalogModel from '../generated/models/host-catalog';
-export default class HostCatalogModel extends GeneratedHostCatalogModel { }
+export default class HostCatalogModel extends GeneratedHostCatalogModel {}
diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js
new file mode 100644
index 000000000..01a376f6b
--- /dev/null
+++ b/addons/api/addon/models/host-set.js
@@ -0,0 +1,3 @@
+import GeneratedHostSetModel from '../generated/models/host-set';
+
+export default class HostSetModel extends GeneratedHostSetModel {}
diff --git a/addons/api/addon/models/host.js b/addons/api/addon/models/host.js
new file mode 100644
index 000000000..451097080
--- /dev/null
+++ b/addons/api/addon/models/host.js
@@ -0,0 +1,3 @@
+import GeneratedHostModel from '../generated/models/host';
+
+export default class HostModel extends GeneratedHostModel {}
diff --git a/addons/api/app/adapters/host-set.js b/addons/api/app/adapters/host-set.js
new file mode 100644
index 000000000..958a53f64
--- /dev/null
+++ b/addons/api/app/adapters/host-set.js
@@ -0,0 +1 @@
+export { default } from 'api/adapters/host-set';
diff --git a/addons/api/app/adapters/host.js b/addons/api/app/adapters/host.js
new file mode 100644
index 000000000..46bc96d3c
--- /dev/null
+++ b/addons/api/app/adapters/host.js
@@ -0,0 +1 @@
+export { default } from 'api/adapters/host';
diff --git a/addons/api/app/models/host-set.js b/addons/api/app/models/host-set.js
new file mode 100644
index 000000000..6963bf5e3
--- /dev/null
+++ b/addons/api/app/models/host-set.js
@@ -0,0 +1 @@
+export { default } from 'api/models/host-set';
diff --git a/addons/api/app/models/host.js b/addons/api/app/models/host.js
new file mode 100644
index 000000000..ce1f67327
--- /dev/null
+++ b/addons/api/app/models/host.js
@@ -0,0 +1 @@
+export { default } from 'api/models/host';
diff --git a/addons/api/tests/unit/adapters/host-set-test.js b/addons/api/tests/unit/adapters/host-set-test.js
new file mode 100644
index 000000000..e47c7a7d6
--- /dev/null
+++ b/addons/api/tests/unit/adapters/host-set-test.js
@@ -0,0 +1,28 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Adapter | host set', function(hooks) {
+ setupTest(hooks);
+
+ test('it generates correct complete URLs', function (assert) {
+ assert.expect(2);
+ const scopeID = 'o_1';
+ const hostCatalogID = 'hc_098';
+ const method = 'my-custom-method';
+ const mockSnapshot = {
+ adapterOptions: {
+ scopeID,
+ method
+ }
+ };
+ const adapter = this.owner.lookup('adapter:host-set');
+ // Without hostCatalogID...
+ let url = adapter.buildURL('host-set', null, mockSnapshot, 'findAll');
+ assert.equal(url, '/v1/scopes/o_1/host-sets:my-custom-method');
+ // With hostCatalogID...
+ mockSnapshot.adapterOptions.hostCatalogID = hostCatalogID;
+ url = adapter.buildURL('host-set', null, mockSnapshot, 'findAll');
+ assert.equal(url, '/v1/scopes/o_1/host-catalogs/hc_098/host-sets:my-custom-method');
+ });
+
+});
diff --git a/addons/api/tests/unit/adapters/host-test.js b/addons/api/tests/unit/adapters/host-test.js
new file mode 100644
index 000000000..9e019284e
--- /dev/null
+++ b/addons/api/tests/unit/adapters/host-test.js
@@ -0,0 +1,28 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Adapter | host', function(hooks) {
+ setupTest(hooks);
+
+ test('it generates correct complete URLs', function (assert) {
+ assert.expect(2);
+ const scopeID = 'o_1';
+ const hostCatalogID = 'hc_098';
+ const method = 'my-custom-method';
+ const mockSnapshot = {
+ adapterOptions: {
+ scopeID,
+ method
+ }
+ };
+ const adapter = this.owner.lookup('adapter:host');
+ // Without hostCatalogID...
+ let url = adapter.buildURL('host', null, mockSnapshot, 'findAll');
+ assert.equal(url, '/v1/scopes/o_1/hosts:my-custom-method');
+ // With hostCatalogID...
+ mockSnapshot.adapterOptions.hostCatalogID = hostCatalogID;
+ url = adapter.buildURL('host', null, mockSnapshot, 'findAll');
+ assert.equal(url, '/v1/scopes/o_1/host-catalogs/hc_098/hosts:my-custom-method');
+ });
+
+});
diff --git a/addons/api/tests/unit/models/host-set-test.js b/addons/api/tests/unit/models/host-set-test.js
new file mode 100644
index 000000000..61e007f70
--- /dev/null
+++ b/addons/api/tests/unit/models/host-set-test.js
@@ -0,0 +1,13 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Model | host set', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let model = store.createRecord('host-set', {});
+ assert.ok(model);
+ });
+});
diff --git a/addons/api/tests/unit/models/host-test.js b/addons/api/tests/unit/models/host-test.js
new file mode 100644
index 000000000..d19d98fc3
--- /dev/null
+++ b/addons/api/tests/unit/models/host-test.js
@@ -0,0 +1,13 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Model | host', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let model = store.createRecord('host', {});
+ assert.ok(model);
+ });
+});
diff --git a/ui/core/app/index.html b/ui/core/app/index.html
index d23416610..21cb16f81 100644
--- a/ui/core/app/index.html
+++ b/ui/core/app/index.html
@@ -7,8 +7,6 @@
-
-
{{content-for "head"}}
diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js
index f54947470..d25608633 100644
--- a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js
+++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js
@@ -1,4 +1,20 @@
import Route from '@ember/routing/route';
export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogRoute extends Route {
+
+ // =methods
+
+ /**
+ * Load a host catalog.
+ * @param {object} params
+ * @param {string} params.host_catalog_id
+ * @return {HostCatalogModel}
+ */
+ async model({ host_catalog_id }) {
+ const { id: scopeID } = this.modelFor('scopes.scope');
+ return this.store.findRecord('host-catalog', host_catalog_id, {
+ adapterOptions: { scopeID },
+ });
+ }
+
}
diff --git a/ui/core/app/templates/application.hbs b/ui/core/app/templates/application.hbs
index 0f0b66871..15d146e9a 100644
--- a/ui/core/app/templates/application.hbs
+++ b/ui/core/app/templates/application.hbs
@@ -7,18 +7,6 @@
- {{!--
-
-
- {{t 'dev.menu-item'}}
- {{t 'dev.menu-item'}}
- {{t 'dev.button-menu-item'}}
-
- {{t 'dev.section'}}
- {{t 'dev.section'}}
-
- --}}
-
{{#if this.session.isAuthenticated}}
new Response(505));
diff --git a/ui/core/mirage/factories/host-set.js b/ui/core/mirage/factories/host-set.js
new file mode 100644
index 000000000..2f12361c6
--- /dev/null
+++ b/ui/core/mirage/factories/host-set.js
@@ -0,0 +1,3 @@
+import factory from '../generated/factories/host-set';
+
+export default factory.extend();
diff --git a/ui/core/mirage/factories/host.js b/ui/core/mirage/factories/host.js
new file mode 100644
index 000000000..eb463f1f9
--- /dev/null
+++ b/ui/core/mirage/factories/host.js
@@ -0,0 +1,3 @@
+import factory from '../generated/factories/host';
+
+export default factory.extend();
diff --git a/ui/core/mirage/factories/scope.js b/ui/core/mirage/factories/scope.js
index 703e80ad4..d60486d15 100644
--- a/ui/core/mirage/factories/scope.js
+++ b/ui/core/mirage/factories/scope.js
@@ -14,7 +14,7 @@ export default factory.extend({
}, 'withChildren');
}
if (record.type === 'org') {
- server.createList('scope', 5, {
+ server.createList('scope', 3, {
type: 'project',
scope: { id: record.id, type: record.type }
});
diff --git a/ui/core/mirage/generated/factories/host-set.js b/ui/core/mirage/generated/factories/host-set.js
new file mode 100644
index 000000000..95636bda0
--- /dev/null
+++ b/ui/core/mirage/generated/factories/host-set.js
@@ -0,0 +1,17 @@
+import { Factory } from 'ember-cli-mirage';
+import { random, date } from 'faker';
+
+/**
+ * GeneratedHostSetModelFactory
+ */
+export default Factory.extend({
+
+ type: 'static',
+ name: () => random.words(),
+ description: () => random.words(),
+ created_time: () => date.recent(),
+ updated_time: () => date.recent(),
+ version: () => random.number(),
+ size: () => random.number(),
+
+});
diff --git a/ui/core/mirage/generated/factories/host.js b/ui/core/mirage/generated/factories/host.js
new file mode 100644
index 000000000..2d6925e82
--- /dev/null
+++ b/ui/core/mirage/generated/factories/host.js
@@ -0,0 +1,17 @@
+import { Factory } from 'ember-cli-mirage';
+import { random, date, internet } from 'faker';
+
+/**
+ * GeneratedHostModelFactory
+ */
+export default Factory.extend({
+
+ type: 'static',
+ name: () => random.words(),
+ description: () => random.words(),
+ created_time: () => date.recent(),
+ updated_time: () => date.recent(),
+ version: () => random.number(),
+ address: () => internet.ipv6(),
+
+});
diff --git a/ui/core/mirage/scenarios/default.js b/ui/core/mirage/scenarios/default.js
index 6f8e29775..2e830c4ce 100644
--- a/ui/core/mirage/scenarios/default.js
+++ b/ui/core/mirage/scenarios/default.js
@@ -25,9 +25,13 @@ export default function(server) {
// Other resources
- server.db.scopes.where({type: 'project'}).forEach(projectScope => {
- const scope = { id: projectScope.id, type: projectScope.type };
+ server.db.scopes.where({type: 'project'}).forEach(({ id, type }) => {
+ const scope = { id, type };
server.createList('host-catalog', 2, { scope });
+ // Hosts and host sets are supposed to be associated to a host catalog.
+ // For simplicity of mocks, we don't actually associate them here.
+ server.createList('host-set', 3, { scope });
+ server.createList('host', 10, { scope });
});
}
diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml
index c5199e191..2fe1ad5f8 100644
--- a/ui/core/translations/en-us.yaml
+++ b/ui/core/translations/en-us.yaml
@@ -92,10 +92,3 @@ notify:
create-success: Created succesfully.
save-success: Saved succesfully.
delete-success: Deleted successfully.
-dev:
- default-org: Default Scope
- menu-item: Menu item
- button-menu-item: Button menu item
- section: Section
- title: Title
- item-name: Item Name