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/components/form/auth-method/index.hbs b/ui/core/app/components/form/auth-method/index.hbs
new file mode 100644
index 000000000..7c1bc5162
--- /dev/null
+++ b/ui/core/app/components/form/auth-method/index.hbs
@@ -0,0 +1,53 @@
+
+
+
+
+ {{#if @model.errors.name}}
+
+ {{#each @model.errors.name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+
+ {{#if @model.errors.description}}
+
+ {{#each @model.errors.description as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+
+
\ No newline at end of file
diff --git a/ui/core/app/controllers/scopes/scope/auth-methods.js b/ui/core/app/controllers/scopes/scope/auth-methods.js
new file mode 100644
index 000000000..ab152cf62
--- /dev/null
+++ b/ui/core/app/controllers/scopes/scope/auth-methods.js
@@ -0,0 +1,18 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class ScopesScopeAuthMethodsController extends Controller {
+ // =services
+
+ @service intl;
+
+ // =attributes
+
+ /**
+ * Translated auth-methods breadcrumb
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.intl.t('resources.auth-methods');
+ }
+}
diff --git a/ui/core/app/controllers/scopes/scope/auth-methods/auth-method.js b/ui/core/app/controllers/scopes/scope/auth-methods/auth-method.js
new file mode 100644
index 000000000..1005f0216
--- /dev/null
+++ b/ui/core/app/controllers/scopes/scope/auth-methods/auth-method.js
@@ -0,0 +1,13 @@
+import Controller from '@ember/controller';
+
+export default class ScopesScopeAuthMethodsAuthMethodController extends Controller {
+ // =attributes
+
+ /**
+ * An auth-method breadcrumb
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.model.displayName;
+ }
+}
diff --git a/ui/core/app/controllers/scopes/scope/auth-methods/new.js b/ui/core/app/controllers/scopes/scope/auth-methods/new.js
new file mode 100644
index 000000000..ba550f456
--- /dev/null
+++ b/ui/core/app/controllers/scopes/scope/auth-methods/new.js
@@ -0,0 +1,18 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class ScopesScopeAuthMethodsNewController extends Controller {
+ // =services
+
+ @service intl;
+
+ // =attributes
+
+ /**
+ * Translated new auth-method breadcrumb
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.intl.t('titles.new');
+ }
+}
diff --git a/ui/core/app/router.js b/ui/core/app/router.js
index 4521a8b75..95356d6c0 100644
--- a/ui/core/app/router.js
+++ b/ui/core/app/router.js
@@ -46,8 +46,20 @@ Router.map(function () {
this.route('new');
});
- this.route('auth-methods', function() {});
-
+ this.route('projects', function() {
+ this.route('project', { path: ':project_id' }, function() {
+ this.route('host-catalogs', function() {
+ this.route('new');
+ this.route('host-catalog', { path: ':host_catalog_id' }, function() {});
+ });
+ });
+ this.route('new');
+ });
+ this.route('auth-methods', function() {
+ this.route('auth-method', { path: ':auth_method_id' }, function() {});
+ this.route('new');
+ });
+
});
});
});
diff --git a/ui/core/app/routes/scopes/scope/auth-methods.js b/ui/core/app/routes/scopes/scope/auth-methods.js
index 112c40248..ffad801fa 100644
--- a/ui/core/app/routes/scopes/scope/auth-methods.js
+++ b/ui/core/app/routes/scopes/scope/auth-methods.js
@@ -1,6 +1,13 @@
import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
export default class ScopesScopeAuthMethodsRoute extends Route {
+ // =services
+
+ @service intl;
+ @service notify;
+
// =methods
/**
@@ -11,4 +18,36 @@ export default class ScopesScopeAuthMethodsRoute extends Route {
const { id: scopeID} = this.modelFor('scopes.scope');
return this.store.findAll('auth-method', { adapterOptions: { scopeID } });
}
+
+ // =actions
+ /**
+ * Rollback changes to an auth-method.
+ * @param {AuthMethodModel} authMethod
+ */
+ @action
+ cancel(authMethod) {
+ const { isNew } = authMethod;
+ authMethod.rollbackAttributes();
+ if (isNew) this.transitionTo('scopes.scope.auth-methods');
+ }
+
+ /**
+ * Save an auth-method in current scope.
+ * @param {AuthMethodModel} authMethod
+ */
+ @action
+ async save(authMethod) {
+ const { isNew } = authMethod;
+ try {
+ await authMethod.save();
+ this.refresh();
+ this.notify.success(
+ this.intl.t(isNew ? 'notify.create-success' : 'notify.save-success')
+ );
+ this.transitionTo('scopes.scope.auth-methods.auth-method', authMethod);
+ } catch (error) {
+ //TODO: replace with translated strings
+ this.notify.error(error.message, { closeAfter: null });
+ }
+ }
}
diff --git a/ui/core/app/routes/scopes/scope/auth-methods/auth-method.js b/ui/core/app/routes/scopes/scope/auth-methods/auth-method.js
new file mode 100644
index 000000000..5cc752eac
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/auth-methods/auth-method.js
@@ -0,0 +1,16 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route {
+ // =methods
+
+ /**
+ * Load a single auth-method in current scope.
+ * @param {object} params
+ * @param {string} params.auth_method_id
+ * @return {authMethodModel}
+ */
+ async model({ auth_method_id}) {
+ const { id: scopeID } = this.modelFor('scopes.scope');
+ return this.store.findRecord('auth-method', auth_method_id, { adapterOptions: { scopeID }});
+ }
+}
diff --git a/ui/core/app/routes/scopes/scope/auth-methods/auth-method/index.js b/ui/core/app/routes/scopes/scope/auth-methods/auth-method/index.js
new file mode 100644
index 000000000..5cef508e0
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/auth-methods/auth-method/index.js
@@ -0,0 +1,4 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeAuthMethodsAuthMethodIndexRoute extends Route {
+}
diff --git a/ui/core/app/routes/scopes/scope/auth-methods/new.js b/ui/core/app/routes/scopes/scope/auth-methods/new.js
new file mode 100644
index 000000000..ceeb6a5d7
--- /dev/null
+++ b/ui/core/app/routes/scopes/scope/auth-methods/new.js
@@ -0,0 +1,14 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeAuthMethodsNewRoute extends Route {
+ // =methods
+
+ /**
+ * Create a new unsaved auth-method.
+ * @return {AuthMethodModel}
+ */
+ model() {
+ const scopeModel = this.modelFor('scopes.scope');
+ return this.store.createRecord('auth-method', { type: 'password', scopeModel });
+ }
+}
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/scopes/scope/auth-methods/auth-method.hbs b/ui/core/app/templates/scopes/scope/auth-methods/auth-method.hbs
new file mode 100644
index 000000000..c24cd6895
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/auth-methods/auth-method.hbs
@@ -0,0 +1 @@
+{{outlet}}
diff --git a/ui/core/app/templates/scopes/scope/auth-methods/auth-method/index.hbs b/ui/core/app/templates/scopes/scope/auth-methods/auth-method/index.hbs
new file mode 100644
index 000000000..c08c3fd4a
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/auth-methods/auth-method/index.hbs
@@ -0,0 +1,28 @@
+
+
+ {{t "resources.auth-method"}}
+
+ {{t (concat 'auth.method-types.' @model.type)}}
+
+
+
+
+
+
+
+
+
+
+ {{t 'titles.details'}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/core/app/templates/scopes/scope/auth-methods/index.hbs b/ui/core/app/templates/scopes/scope/auth-methods/index.hbs
index 2d5456c58..a5bc4f485 100644
--- a/ui/core/app/templates/scopes/scope/auth-methods/index.hbs
+++ b/ui/core/app/templates/scopes/scope/auth-methods/index.hbs
@@ -3,6 +3,18 @@
{{t "resources.auth-methods"}}
+
+
+
+
+
+ {{#if @model}}
+
+ {{t "titles.new"}}
+
+ {{/if}}
+
+
{{#if @model}}
@@ -19,12 +31,17 @@
{{#each @model as |authMethod|}}
- {{authMethod.name}}
- {{authMethod.description}}
+
+ {{authMethod.name}}
+
+ {{authMethod.description}}
- {{authMethod.type}}
+ {{t (concat 'auth.method-types.' authMethod.type)}}
{{authMethod.id}}
@@ -40,6 +57,10 @@
{{t 'descriptions.auth-methods-welcome'}}
+
+
+ {{t 'titles.new'}}
+
diff --git a/ui/core/app/templates/scopes/scope/auth-methods/new.hbs b/ui/core/app/templates/scopes/scope/auth-methods/new.hbs
new file mode 100644
index 000000000..44c4f35d6
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/auth-methods/new.hbs
@@ -0,0 +1,19 @@
+{{title (t "titles.new")}}
+
+
+
+ {{t "titles.new"}}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js
index 73342c6b0..4a949b05f 100644
--- a/ui/core/mirage/config.js
+++ b/ui/core/mirage/config.js
@@ -88,6 +88,22 @@ export default function() {
this.patch('/scopes/:scope_id/host-catalogs/:id');
this.del('/scopes/:scope_id/host-catalogs/:id');
+ // host
+
+ this.get('/scopes/:scope_id/host-catalogs/:host_catalog_id/hosts');
+ this.post('/scopes/:scope_id/host-catalogs/:host_catalog_id/hosts');
+ this.get('/scopes/:scope_id/host-catalogs/:host_catalog_id/hosts/:id');
+ this.patch('/scopes/:scope_id/host-catalogs/:host_catalog_id/hosts/:id');
+ this.del('/scopes/:scope_id/host-catalogs/:host_catalog_id/hosts/:id');
+
+ // host-set
+
+ this.get('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets');
+ this.post('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets');
+ this.get('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets/:id');
+ this.patch('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets/:id');
+ this.del('/scopes/:scope_id/host-catalogs/:host_catalog_id/host-sets/:id');
+
// Uncomment the following line and the Response import above
// Then change the response code to simulate error responses.
// this.get('/scopes/:scope_id/projects', () => 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 e6bfe856c..5a87be352 100644
--- a/ui/core/mirage/scenarios/default.js
+++ b/ui/core/mirage/scenarios/default.js
@@ -30,9 +30,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/tests/acceptance/auth-methods-test.js b/ui/core/tests/acceptance/auth-methods-test.js
new file mode 100644
index 000000000..5b816f253
--- /dev/null
+++ b/ui/core/tests/acceptance/auth-methods-test.js
@@ -0,0 +1,94 @@
+import { module, test } from 'qunit';
+import { visit, currentURL } from '@ember/test-helpers';
+import { setupApplicationTest } from 'ember-qunit';
+import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
+import a11yAudit from 'ember-a11y-testing/test-support/audit';
+import {
+ authenticateSession,
+ // These are left here intentionally for future reference.
+ //currentSession,
+ //invalidateSession,
+} from 'ember-simple-auth/test-support';
+
+module('Acceptance | auth methods', function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ const instances = {
+ scopes: {
+ global: null,
+ org: null
+ },
+ authMethod: null
+ };
+ const urls = {
+ orgScope: null,
+ authMethods: null
+ };
+
+ hooks.beforeEach(function () {
+ // Setup Mirage mock resources for this test
+ instances.scopes.global = this.server.create('scope', { id: 'global' });
+ instances.scopes.org = this.server.create('scope', { type: 'org',
+ scope: { id: instances.scopes.global.id, type: instances.scopes.global.type }
+ });
+ instances.authMethod = this.server.create('auth-method', {
+ scope: {
+ id: instances.scopes.org.id,
+ type: instances.scopes.org.type
+ }
+ });
+
+ // Generate route URLs for resources
+ urls.orgScope = `/scopes/${instances.scopes.org.id}`;
+ urls.authMethods = `${urls.orgScope}/auth-methods`;
+ });
+
+ test('visiting auth methods', async function (assert) {
+ assert.expect(1);
+ authenticateSession();
+ await visit(urls.authMethods);
+ await a11yAudit();
+ assert.equal(currentURL(), urls.authMethods);
+ });
+
+ test('can navigate to an auth method form', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('can update an auth method and save changes', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('can update an auth method and cancel changes', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('can create new auth method', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('can cancel new auth method creation', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('saving a new auth method with invalid fields displays error messages', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('errors are displayed when save on auth method fails', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('errors are displayed when delete on an auth method fails', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('saving a new auth method with invalid fields displays error messages', async function (assert) {
+ assert.expect(0);
+ });
+
+ test('saving an existing auth method with invalid fields displays error messages', async function (assert) {
+ assert.expect(0);
+ });
+});
diff --git a/ui/core/tests/integration/components/form/auth-method-test.js b/ui/core/tests/integration/components/form/auth-method-test.js
new file mode 100644
index 000000000..267e516b5
--- /dev/null
+++ b/ui/core/tests/integration/components/form/auth-method-test.js
@@ -0,0 +1,23 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | form/auth-method', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ this.submit = () => {};
+ this.cancel = () => {};
+ this.model = { type: 'password' };
+
+ await render(hbs`
+ `);
+
+ assert.ok(find('form'));
+ });
+});
diff --git a/ui/core/tests/unit/controllers/scopes/scope/auth-methods-test.js b/ui/core/tests/unit/controllers/scopes/scope/auth-methods-test.js
new file mode 100644
index 000000000..20cbdbe00
--- /dev/null
+++ b/ui/core/tests/unit/controllers/scopes/scope/auth-methods-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | scopes/scope/auth-methods', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let controller = this.owner.lookup('controller:scopes/scope/auth-methods');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/core/tests/unit/controllers/scopes/scope/auth-methods/auth-method-test.js b/ui/core/tests/unit/controllers/scopes/scope/auth-methods/auth-method-test.js
new file mode 100644
index 000000000..ad19d3e67
--- /dev/null
+++ b/ui/core/tests/unit/controllers/scopes/scope/auth-methods/auth-method-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | scopes/scope/auth-methods/auth-method', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let controller = this.owner.lookup('controller:scopes/scope/auth-methods/auth-method');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/core/tests/unit/controllers/scopes/scope/auth-methods/new-test.js b/ui/core/tests/unit/controllers/scopes/scope/auth-methods/new-test.js
new file mode 100644
index 000000000..2d5306d6a
--- /dev/null
+++ b/ui/core/tests/unit/controllers/scopes/scope/auth-methods/new-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | scopes/scope/auth-methods/new', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let controller = this.owner.lookup('controller:scopes/scope/auth-methods/new');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/auth-methods/auth-method-test.js b/ui/core/tests/unit/routes/scopes/scope/auth-methods/auth-method-test.js
new file mode 100644
index 000000000..36b2e2f2d
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/auth-methods/auth-method-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/auth-methods/auth-method', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope/auth-methods/auth-method');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/auth-methods/auth-method/index-test.js b/ui/core/tests/unit/routes/scopes/scope/auth-methods/auth-method/index-test.js
new file mode 100644
index 000000000..5a51858df
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/auth-methods/auth-method/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/auth-methods/auth-method/index', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope/auth-methods/auth-method/index');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/tests/unit/routes/scopes/scope/auth-methods/new-test.js b/ui/core/tests/unit/routes/scopes/scope/auth-methods/new-test.js
new file mode 100644
index 000000000..6eea2751c
--- /dev/null
+++ b/ui/core/tests/unit/routes/scopes/scope/auth-methods/new-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/auth-methods/new', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope/auth-methods/new');
+ assert.ok(route);
+ });
+});
diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml
index 0758222ee..7f207f658 100644
--- a/ui/core/translations/en-us.yaml
+++ b/ui/core/translations/en-us.yaml
@@ -33,6 +33,7 @@ resources:
project: Project
host-catalog: Host Catalog
host-catalogs: Host Catalogs
+ auth-method: Auth Method
auth-methods: Auth Methods
titles:
iam: IAM