diff --git a/ui/desktop/app/routes/scopes.js b/ui/desktop/app/routes/scopes.js index e2d6dcd0f..4f53d4955 100644 --- a/ui/desktop/app/routes/scopes.js +++ b/ui/desktop/app/routes/scopes.js @@ -1,8 +1,21 @@ import Route from '@ember/routing/route'; +import { A } from '@ember/array'; export default class ScopesRoute extends Route { + // =methods + /** + * Attempt to load all scopes from the API. This is allowed + * to fail, since in some cases the user may not have permission to read a + * scope directly, but may have permission to read resources under it. + * If scopes fails to load, we still proceed using an empty array. + * @param {object} params + * @param {string} params.scope_id + * @return {Promise{[ScopeModel]}} + */ model() { - // return this.store.findAll('scope'); + // NOTE: In the absence of a `scope_id` query parameter, this endpoint is + // expected to default to the global scope, thus returning org scopes. + return this.store.findAll('scope').catch(() => A([])); } } diff --git a/ui/desktop/app/templates/scopes.hbs b/ui/desktop/app/templates/scopes.hbs index 66ec3011a..257dd84b7 100644 --- a/ui/desktop/app/templates/scopes.hbs +++ b/ui/desktop/app/templates/scopes.hbs @@ -1,3 +1,4 @@ I'm in scopes +{{@model}} {{outlet}} \ No newline at end of file diff --git a/ui/desktop/config/environment.js b/ui/desktop/config/environment.js index e358c0cac..2de008f1c 100644 --- a/ui/desktop/config/environment.js +++ b/ui/desktop/config/environment.js @@ -7,8 +7,8 @@ module.exports = function (environment) { let ENV = { modulePrefix: 'desktop', environment, - rootURL: process.env.EMBER_CLI_ELECTRON ? '' : '/', - locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : 'auto', + rootURL: process.env.EMBER_CLI_ELECTRON ? '' : process.env.EMBER_CLI_ELECTRON ? '' : '/', + locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : process.env.EMBER_CLI_ELECTRON ? 'hash' : 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build diff --git a/ui/desktop/mirage/config.js b/ui/desktop/mirage/config.js new file mode 100644 index 000000000..475ff24eb --- /dev/null +++ b/ui/desktop/mirage/config.js @@ -0,0 +1,149 @@ +import Ember from 'ember'; +import config from '../config/environment'; +import { authHandler, deauthHandler } from './route-handlers/auth'; +import { pickRandomStatusString } from './factories/session'; +import { Response } from 'miragejs'; + +export default function() { + + // make this `http://localhost:8080`, for example, if your API is on a different server + // this.urlPrefix = ''; + // make this `/api`, for example, if your API is namespaced + this.namespace = config.api.namespace; + + // Allow any configured API host to passthrough, which is useful in + // development for testing against a locally running backend. + if (config.api.host) this.passthrough(`${config.api.host}/**`); + this.passthrough(); + + // Scope resources + + this.get('/scopes', ({ scopes }, { queryParams: { scope_id } }) => { + // Default parent scope is global + if (!scope_id) scope_id = 'global'; + const results = scopes.where(scope => { + return scope.scope ? scope.scope.id === scope_id : false + }); + return results; + }); + this.get('/scopes/:id'); + + // Auth & IAM resources + + this.get('/auth-methods', ({ authMethods }, { queryParams: { scope_id: scopeId } }) => { + return authMethods.where({ scopeId }); + }); + this.get('/auth-methods/:id'); + + // Auth Method Accounts + this.get('/accounts', ({ accounts }, { queryParams: { auth_method_id: authMethodId } }) => { + return accounts.where({ authMethodId }); + }); + this.get('/accounts/:id'); + this.post('/accounts/:idMethod', function ({ accounts }, { params: { idMethod } }) { + const attrs = this.normalizedRequestAttrs(); + const id = idMethod.split(':')[0]; + const method = idMethod.split(':')[1]; + const account = accounts.find(id); + const updatedAttrs = { + version: attrs.version, + attributes: account.attributes + }; + // Set new password + if (method === 'change-password') { + updatedAttrs.attributes.password = attrs.new_password; + } + return account.update(updatedAttrs); + }); + + // Authenticate/deauthenticate routes + this.post('/auth-methods/:id_method', authHandler); + this.post('/scopes/:id_method', deauthHandler); + + // Other resources + // host-catalog + + this.get('/host-catalogs', ({ hostCatalogs }, { queryParams: { scope_id: scopeId } }) => { + return hostCatalogs.where({ scopeId }); + }); + this.get('/host-catalogs/:id'); + + // host + + this.get('/hosts', function ({ hosts }, { queryParams: { host_catalog_id: hostCatalogId } }) { + return hosts.where({ hostCatalogId }); + }); + this.get('/hosts/:id'); + + // host-set + + this.get('/host-sets', function ({ hostSets }, { queryParams: { host_catalog_id: hostCatalogId } }) { + return hostSets.where({ hostCatalogId }); + }); + this.get('/host-sets/:id'); + + // target + + this.get('/targets', function ({ targets }, { queryParams: { scope_id } }) { + return targets.where(target => target.scopeId === scope_id); + }); + this.get('/targets/:id'); + + // session + + this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) { + // To simulate changes to `session.status` that may occur in the backend, + // we quietly randomize the value of the field on GET. + // But only if not in testing mode. + // In tests, we need deterministic statuses. + if (!Ember.testing) { + sessions.where(session => session.scopeId === scope_id) + .models + .forEach(session => session.update({ + status: pickRandomStatusString() + })); + } + return sessions.where(session => session.scopeId === scope_id) + }); + this.get('/sessions/:id', function ({ sessions }, { params: { id } }) { + const session = sessions.find(id); + return session.update({ + status: pickRandomStatusString() + }); + }); + this.post('/sessions/:idMethod', function ({ sessions }, { params: { idMethod } }) { + const attrs = this.normalizedRequestAttrs(); + const id = idMethod.split(':')[0]; + const method = idMethod.split(':')[1]; + const session = sessions.find(id); + const updatedAttrs = { + version: attrs.version + }; + if (method === 'cancel') { + updatedAttrs.status = 'canceling'; + } + return session.update(updatedAttrs); + }); + + // auth-tokens + + this.get('/auth-tokens/:id', function () { + // Set to 401 or 404 to simulate token invalidation, which will + // cause the session to fail restoration and logout the user. + return new Response(200); + }); + + /* Uncomment the following line and the Response import above + * Then change the response code to simulate error responses. + * this.get('/scopes', () => new Response(505)); + * + * Update error payload to simulate specific error responses. + */ + // this.get('/host-catalogs', () => new Response(404)); + // this.get('/scopes/:id', () => new Response(505, {}, { + // errors: [{ + // status: 505, + // message: 'HTTP version not supported.', + // }] + // })); +} diff --git a/ui/desktop/mirage/factories/account.js b/ui/desktop/mirage/factories/account.js new file mode 100644 index 000000000..5908d0a21 --- /dev/null +++ b/ui/desktop/mirage/factories/account.js @@ -0,0 +1,3 @@ +import factory from '../generated/factories/account'; + +export default factory.extend(); diff --git a/ui/desktop/mirage/factories/auth-method.js b/ui/desktop/mirage/factories/auth-method.js new file mode 100644 index 000000000..61d29726b --- /dev/null +++ b/ui/desktop/mirage/factories/auth-method.js @@ -0,0 +1,20 @@ +import factory from '../generated/factories/auth-method'; +import { trait } from 'ember-cli-mirage'; + +export default factory.extend({ + id: (i) => `auth-method-id-${i}`, + + /** + * Adds accounts (with associated users) to auth method. + */ + withAccountsAndUsers: trait({ + afterCreate(authMethod, server) { + const { scope } = authMethod; + server.createList('user', 5, { scope }).map(user => { + const { id } = server.create('account', { scope, authMethod }); + user.update({ accountIds: [id] }); + }); + } + }) + +}); diff --git a/ui/desktop/mirage/factories/host-catalog.js b/ui/desktop/mirage/factories/host-catalog.js new file mode 100644 index 000000000..1eaed0433 --- /dev/null +++ b/ui/desktop/mirage/factories/host-catalog.js @@ -0,0 +1,14 @@ +import factory from '../generated/factories/host-catalog'; +import { trait } from 'ember-cli-mirage'; + +export default factory.extend({ + + withChildren: trait({ + afterCreate(hostCatalog, server) { + const { scope } = hostCatalog; + const hosts = server.createList('host', 10, { scope, hostCatalog }); + server.createList('host-set', 3, { scope, hostCatalog, hosts }); + } + }) + +}); diff --git a/ui/desktop/mirage/factories/host-set.js b/ui/desktop/mirage/factories/host-set.js new file mode 100644 index 000000000..ad9ae1fa1 --- /dev/null +++ b/ui/desktop/mirage/factories/host-set.js @@ -0,0 +1,5 @@ +import factory from '../generated/factories/host-set'; + +export default factory.extend({ + id: (i) => `host-set-id-${i}` +}); diff --git a/ui/desktop/mirage/factories/host.js b/ui/desktop/mirage/factories/host.js new file mode 100644 index 000000000..f322cb4d4 --- /dev/null +++ b/ui/desktop/mirage/factories/host.js @@ -0,0 +1,5 @@ +import factory from '../generated/factories/host'; + +export default factory.extend({ + id: (i) => `host-id-${i}` +}); diff --git a/ui/desktop/mirage/factories/scope.js b/ui/desktop/mirage/factories/scope.js new file mode 100644 index 000000000..a14dbc719 --- /dev/null +++ b/ui/desktop/mirage/factories/scope.js @@ -0,0 +1,35 @@ +import factory from '../generated/factories/scope'; +import { trait } from 'ember-cli-mirage'; + +export default factory.extend({ + + type: 'global', + + /** + * Generates realistic-ish IDs while still being deterministic. + */ + id(i) { + let id = 'tv6fv3yz8p'; + if (i % 3) id = 'xk1i4dk50v'; + if (i % 2) id = 's9n819zgko'; + return `s_${id}${i}`; + }, + + withChildren: trait({ + afterCreate(record, server) { + if (record.type === 'global') { + server.createList('scope', 1, { + type: 'org', + scope: { id: record.id, type: record.type } + }, 'withChildren'); + } + if (record.type === 'org') { + server.createList('scope', 3, { + type: 'project', + scope: { id: record.id, type: record.type } + }); + } + } + }) + +}); diff --git a/ui/desktop/mirage/factories/session.js b/ui/desktop/mirage/factories/session.js new file mode 100644 index 000000000..0100a4b75 --- /dev/null +++ b/ui/desktop/mirage/factories/session.js @@ -0,0 +1,46 @@ +import factory from '../generated/factories/target'; +import { trait } from 'ember-cli-mirage'; +import { random } from 'faker'; + +const statusStrings = [ + 'pending', + 'active', + 'canceling', + 'terminated' +]; + +export const pickRandomStatusString = () => random.arrayElement(statusStrings); + +export default factory.extend({ + + id: (i) => `session-id-${i}`, + status: pickRandomStatusString, + + /** + * Sessions generated with this trait will automatically form + * associations to user, target, host set, and host (if available) within + * the current scope. + */ + withAssociations: trait({ + afterCreate(record, server) { + const scopeId = record.scopeId; + if (record.scope.type === 'project') { + const orgScopeId = record.scope?.scope.id; + const user = + server.schema.users.where({ scopeId: orgScopeId }).models[0]; + if (user) record.update({ user }); + } + const target = server.schema.targets.where({ scopeId }).models[0]; + if (target) { + record.update({ target }); + const hostSet = target.hostSets.models[0]; + if (hostSet) { + record.update({ hostSet }); + const host = hostSet.hosts.models[0]; + if (host) record.update({ host }); + } + } + } + }) + +}); diff --git a/ui/desktop/mirage/factories/target.js b/ui/desktop/mirage/factories/target.js new file mode 100644 index 000000000..4f34cc459 --- /dev/null +++ b/ui/desktop/mirage/factories/target.js @@ -0,0 +1,46 @@ +import factory from '../generated/factories/target'; +import { trait } from 'ember-cli-mirage'; +import { random } from 'faker'; + +const randomBoolean = (chance = 0.5) => Math.random() < chance; +const hostSetChance = 0.3; + +export default factory.extend({ + + /** + * -1 means "unlimited" and we want to generate these on occassion. + */ + session_connection_limit: () => random.arrayElement([-1, random.number()]), + + /** + * Generates attributes fields by type. + */ + afterCreate(target) { + if (target.type === 'tcp') { + target.update({ + attributes: { + default_port: random.number() + } + }); + } + }, + + /** + * Randomly selects existing host sets to assign to target. + */ + withRandomHostSets: trait({ + afterCreate(target, server) { + let randomlySelectedHostSets; + randomlySelectedHostSets = server.schema.hostSets + // BLERG: fun fact, for no reason at all, the element passed + // into a where function is not a full model instance, as you might + // expect at this level of abstraction, but appears to be a serialized + // representation of the model instance. It's very confusing since + // the result set of `where` _is a collection of model instances_. + .where(hostSet => hostSet.scopeId === target.scope.id).models + .filter(() => randomBoolean(hostSetChance)); + target.update({hostSets: randomlySelectedHostSets}); + } + }) + +}); diff --git a/ui/desktop/mirage/factories/user.js b/ui/desktop/mirage/factories/user.js new file mode 100644 index 000000000..42bccd33d --- /dev/null +++ b/ui/desktop/mirage/factories/user.js @@ -0,0 +1,5 @@ +import factory from '../generated/factories/user'; + +export default factory.extend({ + id: (i) => `user-${i}` +}); diff --git a/ui/desktop/mirage/generated/factories/account.js b/ui/desktop/mirage/generated/factories/account.js new file mode 100644 index 000000000..a66184e1f --- /dev/null +++ b/ui/desktop/mirage/generated/factories/account.js @@ -0,0 +1,17 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedAccountFactory + */ +export default Factory.extend({ + + type: 'password', + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + version: () => random.number(), + attributes: () => ({ login_name: random.words() }), + +}); diff --git a/ui/desktop/mirage/generated/factories/auth-method.js b/ui/desktop/mirage/generated/factories/auth-method.js new file mode 100644 index 000000000..d76245946 --- /dev/null +++ b/ui/desktop/mirage/generated/factories/auth-method.js @@ -0,0 +1,16 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedAuthMethodModelFactory + */ +export default Factory.extend({ + + type: 'password', + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + +}); diff --git a/ui/desktop/mirage/generated/factories/host-catalog.js b/ui/desktop/mirage/generated/factories/host-catalog.js new file mode 100644 index 000000000..daf2d356f --- /dev/null +++ b/ui/desktop/mirage/generated/factories/host-catalog.js @@ -0,0 +1,16 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedHostCatalogModelFactory + */ +export default Factory.extend({ + + type: 'static', + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + +}); diff --git a/ui/desktop/mirage/generated/factories/host-set.js b/ui/desktop/mirage/generated/factories/host-set.js new file mode 100644 index 000000000..95636bda0 --- /dev/null +++ b/ui/desktop/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/desktop/mirage/generated/factories/host.js b/ui/desktop/mirage/generated/factories/host.js new file mode 100644 index 000000000..e6fccf602 --- /dev/null +++ b/ui/desktop/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(), + attributes: () => { return { address: internet.ipv6() } }, + +}); diff --git a/ui/desktop/mirage/generated/factories/org.js b/ui/desktop/mirage/generated/factories/org.js new file mode 100644 index 000000000..81ee23629 --- /dev/null +++ b/ui/desktop/mirage/generated/factories/org.js @@ -0,0 +1,15 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedOrgModelFactory + * + */ +export default Factory.extend({ + + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + +}); diff --git a/ui/desktop/mirage/generated/factories/project.js b/ui/desktop/mirage/generated/factories/project.js new file mode 100644 index 000000000..69054217d --- /dev/null +++ b/ui/desktop/mirage/generated/factories/project.js @@ -0,0 +1,16 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedProjectModelFactory + * Project contains all fields related to a Project resource + */ +export default Factory.extend({ + + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + +}); diff --git a/ui/desktop/mirage/generated/factories/scope.js b/ui/desktop/mirage/generated/factories/scope.js new file mode 100644 index 000000000..cc574d3cd --- /dev/null +++ b/ui/desktop/mirage/generated/factories/scope.js @@ -0,0 +1,15 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedScopeModelFactory + */ +export default Factory.extend({ + + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + +}); diff --git a/ui/desktop/mirage/generated/factories/session.js b/ui/desktop/mirage/generated/factories/session.js new file mode 100644 index 000000000..25aca3759 --- /dev/null +++ b/ui/desktop/mirage/generated/factories/session.js @@ -0,0 +1,14 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedSessionModelFactory + */ +export default Factory.extend({ + + type: 'tcp', + created_time: () => date.recent(), + updated_time: () => date.recent(), + version: () => random.number(), + +}); diff --git a/ui/desktop/mirage/generated/factories/target.js b/ui/desktop/mirage/generated/factories/target.js new file mode 100644 index 000000000..5ce21adeb --- /dev/null +++ b/ui/desktop/mirage/generated/factories/target.js @@ -0,0 +1,19 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedTargetModelFactory + */ +export default Factory.extend({ + + type: 'tcp', + name: () => random.words(), + session_max_seconds: () => random.number(), + session_connection_limit: () => random.number(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + version: () => random.number(), + +}); diff --git a/ui/desktop/mirage/generated/factories/user.js b/ui/desktop/mirage/generated/factories/user.js new file mode 100644 index 000000000..3934bc7e9 --- /dev/null +++ b/ui/desktop/mirage/generated/factories/user.js @@ -0,0 +1,16 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedUserModelFactory + * User contains all fields related to a User resource + */ +export default Factory.extend({ + + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + +}); diff --git a/ui/desktop/mirage/models/account.js b/ui/desktop/mirage/models/account.js new file mode 100644 index 000000000..e6d3e303c --- /dev/null +++ b/ui/desktop/mirage/models/account.js @@ -0,0 +1,8 @@ +import { Model, belongsTo } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + authMethod: belongsTo() + +}); diff --git a/ui/desktop/mirage/models/auth-method.js b/ui/desktop/mirage/models/auth-method.js new file mode 100644 index 000000000..08b1f5ad3 --- /dev/null +++ b/ui/desktop/mirage/models/auth-method.js @@ -0,0 +1,7 @@ +import { Model, belongsTo } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo() + +}); diff --git a/ui/desktop/mirage/models/host-catalog.js b/ui/desktop/mirage/models/host-catalog.js new file mode 100644 index 000000000..8c2757e71 --- /dev/null +++ b/ui/desktop/mirage/models/host-catalog.js @@ -0,0 +1,8 @@ +import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + hostSets: hasMany() + +}); diff --git a/ui/desktop/mirage/models/host-set.js b/ui/desktop/mirage/models/host-set.js new file mode 100644 index 000000000..5a579e119 --- /dev/null +++ b/ui/desktop/mirage/models/host-set.js @@ -0,0 +1,10 @@ +import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + target: belongsTo(), + hostCatalog: belongsTo(), + hosts: hasMany() + +}); diff --git a/ui/desktop/mirage/models/host.js b/ui/desktop/mirage/models/host.js new file mode 100644 index 000000000..b9a941a4f --- /dev/null +++ b/ui/desktop/mirage/models/host.js @@ -0,0 +1,8 @@ +import { Model, belongsTo } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + hostCatalog: belongsTo() + +}); diff --git a/ui/desktop/mirage/models/session.js b/ui/desktop/mirage/models/session.js new file mode 100644 index 000000000..91a4da594 --- /dev/null +++ b/ui/desktop/mirage/models/session.js @@ -0,0 +1,11 @@ +import { Model, belongsTo } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + user: belongsTo(), + target: belongsTo(), + host: belongsTo(), + hostSet: belongsTo(), + +}); diff --git a/ui/desktop/mirage/models/target.js b/ui/desktop/mirage/models/target.js new file mode 100644 index 000000000..8c2757e71 --- /dev/null +++ b/ui/desktop/mirage/models/target.js @@ -0,0 +1,8 @@ +import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + hostSets: hasMany() + +}); diff --git a/ui/desktop/mirage/models/user.js b/ui/desktop/mirage/models/user.js new file mode 100644 index 000000000..45cf14952 --- /dev/null +++ b/ui/desktop/mirage/models/user.js @@ -0,0 +1,8 @@ +import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; + +export default Model.extend({ + + scope: belongsTo(), + accounts: hasMany('account') + +}); diff --git a/ui/desktop/mirage/route-handlers/auth.js b/ui/desktop/mirage/route-handlers/auth.js new file mode 100644 index 000000000..96813a7d5 --- /dev/null +++ b/ui/desktop/mirage/route-handlers/auth.js @@ -0,0 +1,53 @@ +// import config from '../../config/environment'; +// import { v1 } from 'ember-uuid'; +import { Response } from 'miragejs'; + +// /** +// * Simulates a cookie-based response to authentication requests. Any user/pass +// * values are allowed *except* when `username` equals `error`, in which case a +// * simulated error response is returned. +// * @param {object} schema +// * @param {object} request +// * @return {Response} +// */ +// export default function authenticateHandler(schema, request) { +// const payload = JSON.parse(request.requestBody); +// if (payload.credentials.login_name === 'error') { +// return new Response(400); +// } else { +// const cookieName = config.auth.passwordCookieName; +// const cookieValue = v1(); +// const cookiePath = config.rootURL; +// document.cookie = `${cookieName}=${cookieValue};path=${cookiePath}`; +// return new Response(200); +// } +// } + +export function authHandler({ scopes, authMethods }, request) { + const payload = JSON.parse(request.requestBody); + if (payload.credentials.login_name === 'error') { + return new Response(400); + } else { + const id = request.params.id_method.split(':')[0]; + const authMethod = authMethods.find(id); + const scope = scopes.find(authMethod.scopeId); + const scopeAttrs = + this.serialize(scopes.find(scope.id)); + return new Response(200, {}, { + scope: scopeAttrs, + id: 'token123', + token: 'thetokenstring', + account_id: '1', + user_id: 'user123', + auth_method_id: 'authmethod123', + created_time: '', + updated_time: '', + last_used_time: '', + expiration_time: '' + }); + } +} + +export function deauthHandler() { + return new Response(200); +} diff --git a/ui/desktop/mirage/scenarios/default.js b/ui/desktop/mirage/scenarios/default.js new file mode 100644 index 000000000..7d8e3a5bb --- /dev/null +++ b/ui/desktop/mirage/scenarios/default.js @@ -0,0 +1,27 @@ +export default function(server) { + + // Scope resources + + // creates a global scope + const globalScope = server.create('scope', { + id: 'global', + type: 'global', + name: 'Global' + }); + const orgScope = server.createList('scope', 3, { + type: 'org', + scope: { id: globalScope.id, type: globalScope.type } + }, 'withChildren')[0]; + + // Auth + server.createList('auth-method', 1, { scope: globalScope }, 'withAccountsAndUsers'); + server.createList('auth-method', 3, { scope: orgScope }, 'withAccountsAndUsers'); + + // Other resources + server.schema.scopes.where({type: 'project'}).models.forEach(scope => { + server.createList('host-catalog', 2, { scope }, 'withChildren'); + server.createList('target', 2, { scope }, 'withRandomHostSets'); + server.createList('session', 4, { scope }, 'withAssociations'); + }); + +} diff --git a/ui/desktop/mirage/serializers/account.js b/ui/desktop/mirage/serializers/account.js new file mode 100644 index 000000000..85d38851b --- /dev/null +++ b/ui/desktop/mirage/serializers/account.js @@ -0,0 +1,12 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'account', + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + json.auth_method_id = model.authMethodId; + delete json.attributes?.password; + return json; + } +}); diff --git a/ui/desktop/mirage/serializers/application.js b/ui/desktop/mirage/serializers/application.js new file mode 100644 index 000000000..7bfbc7cd7 --- /dev/null +++ b/ui/desktop/mirage/serializers/application.js @@ -0,0 +1,84 @@ +import { RestSerializer } from 'ember-cli-mirage'; +import { underscore } from '@ember/string'; +import { isArray } from '@ember/array'; + +/** + * Manages serialization/normalization of data into and out of the mock API. + */ +export default RestSerializer.extend({ + + // =attributes + + /** + * False to disable automatically rooting payloads under a key. + * This is handled by the `serialize()` method, see below. + * @type {boolean} + */ + root: false, + + /** + * @type {boolean} + */ + embed: true, + + // =methods + + /** + * Generates an underscored key for the attribute. + * @param {string} attr + * @return {string} + */ + keyForAttribute(attr) { + return underscore(attr); + }, + + keyForRelationshipId(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_id`; + }, + + /** + * Serialize items into a root key `items`. + * @return {object} + */ + serialize() { + let json = RestSerializer.prototype.serialize.apply(this, arguments); + // If array, root it under a standard `items` key + if (isArray(json)) json = {items: json}; + return json; + }, + + /** + * Serialize scope into the payload, if set. + * @param {object} model + * @return {object} + */ + _hashForModel(model) { + const json = RestSerializer.prototype._hashForModel.apply(this, arguments); + if (model.scope) { + json.scope = { + id: model.scope.id, + type: model.scope.type + }; + } + return json; + }, + + /** + * Adds a root to the payload, since `root: false` doesn't appear to affect + * the normalize method. + * @param {object} json + * @return {object} + */ + normalize(json) { + let newJSON = json; + if (this.modelName) { + newJSON = {}; + newJSON[this.modelName] = json; + } + const value = RestSerializer.prototype.normalize.apply(this, [newJSON]); + return value; + } + +}); diff --git a/ui/desktop/mirage/serializers/auth-method.js b/ui/desktop/mirage/serializers/auth-method.js new file mode 100644 index 000000000..cabe9ee7b --- /dev/null +++ b/ui/desktop/mirage/serializers/auth-method.js @@ -0,0 +1,5 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'auth-method' +}); diff --git a/ui/desktop/mirage/serializers/host-catalog.js b/ui/desktop/mirage/serializers/host-catalog.js new file mode 100644 index 000000000..bab3f7aa0 --- /dev/null +++ b/ui/desktop/mirage/serializers/host-catalog.js @@ -0,0 +1,5 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'host-catalog', +}); diff --git a/ui/desktop/mirage/serializers/host-set.js b/ui/desktop/mirage/serializers/host-set.js new file mode 100644 index 000000000..7acf251b3 --- /dev/null +++ b/ui/desktop/mirage/serializers/host-set.js @@ -0,0 +1,22 @@ +import ApplicationSerializer from './application'; +import { underscore } from '@ember/string'; + +export default ApplicationSerializer.extend({ + modelName: 'host-set', + + keyForRelationshipIds(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_ids`; + }, + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + json.host_catalog_id = model.hostCatalogId; + if (model.hostIds?.length) { + json.host_ids = model.hostIds; + } + return json; + } + +}); diff --git a/ui/desktop/mirage/serializers/host.js b/ui/desktop/mirage/serializers/host.js new file mode 100644 index 000000000..ccd3994ca --- /dev/null +++ b/ui/desktop/mirage/serializers/host.js @@ -0,0 +1,5 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'host' +}); diff --git a/ui/desktop/mirage/serializers/scope.js b/ui/desktop/mirage/serializers/scope.js new file mode 100644 index 000000000..bfb33f398 --- /dev/null +++ b/ui/desktop/mirage/serializers/scope.js @@ -0,0 +1,5 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'scope' +}); diff --git a/ui/desktop/mirage/serializers/session.js b/ui/desktop/mirage/serializers/session.js new file mode 100644 index 000000000..3e14a23e6 --- /dev/null +++ b/ui/desktop/mirage/serializers/session.js @@ -0,0 +1,22 @@ +import ApplicationSerializer from './application'; +import { underscore } from '@ember/string'; + +export default ApplicationSerializer.extend({ + modelName: 'session', + + keyForRelationshipId(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_id`; + }, + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + json.user_id = model.userId; + json.target_id = model.targetId; + json.host_set_id = model.hostSetId; + json.host_id = model.hostId; + return json; + } + +}); diff --git a/ui/desktop/mirage/serializers/target.js b/ui/desktop/mirage/serializers/target.js new file mode 100644 index 000000000..2d2a0d974 --- /dev/null +++ b/ui/desktop/mirage/serializers/target.js @@ -0,0 +1,26 @@ +import ApplicationSerializer from './application'; +import { underscore } from '@ember/string'; + +export default ApplicationSerializer.extend({ + modelName: 'target', + + keyForRelationshipIds(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_ids`; + }, + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + const { hostSets } = this.schema; + if (model.hostSetIds?.length) { + json.host_sets = model.hostSetIds.map(host_set_id => { + const hostSet = hostSets.find(host_set_id); + const host_catalog_id = hostSet?.hostCatalog?.id; + return { id: host_set_id, host_catalog_id }; + }); + } + return json; + } + +}); diff --git a/ui/desktop/mirage/serializers/user.js b/ui/desktop/mirage/serializers/user.js new file mode 100644 index 000000000..0ef9298f5 --- /dev/null +++ b/ui/desktop/mirage/serializers/user.js @@ -0,0 +1,18 @@ +import ApplicationSerializer from './application'; +import { underscore } from '@ember/string'; + +export default ApplicationSerializer.extend({ + modelName: 'user', + + keyForRelationshipIds(relationshipName) { + return `${this._container.inflector.singularize( + underscore(relationshipName) + )}_ids`; + }, + + _hashForModel(model) { + const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments); + if (model.accountIds.length) json.account_ids = model.accountIds; + return json; + } +}); diff --git a/ui/desktop/package.json b/ui/desktop/package.json index ff1d2e0d9..fe4414ee7 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -47,11 +47,13 @@ "ember-cli-dependency-checker": "^3.2.0", "ember-cli-htmlbars": "^5.2.0", "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-mirage": "^1.1.8", "ember-cli-sass": "^10.0.1", "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^2.0.2", "ember-cli-uglify": "^3.0.0", "ember-data": "~3.20.0", + "ember-data-model-fragments": "^4.0.0", "ember-electron": "https://github.com/adopted-ember-addons/ember-electron#v3.0.0-beta.4", "ember-exam": "^6.0.1", "ember-export-application-global": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index e4567440a..1d5213a95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8229,7 +8229,7 @@ ember-cli-babel@7.21.0: rimraf "^3.0.1" semver "^5.5.0" -ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.2: +ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.2: version "6.18.0" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.18.0.tgz#3f6435fd275172edeff2b634ee7b29ce74318957" integrity sha512-7ceC8joNYxY2wES16iIBlbPSxwKDBhYwC8drU3ZEvuPDMwVv1KzxCNu1fvxyFEBWhwaRNTUxSCsEVoTd9nosGA== @@ -8396,7 +8396,7 @@ ember-cli-lodash-subset@^2.0.1: resolved "https://registry.yarnpkg.com/ember-cli-lodash-subset/-/ember-cli-lodash-subset-2.0.1.tgz#20cb68a790fe0fde2488ddfd8efbb7df6fe766f2" integrity sha1-IMtop5D+D94kiN39jvu332/nZvI= -ember-cli-mirage@^1.1.6, ember-cli-mirage@^1.1.7: +ember-cli-mirage@^1.1.6, ember-cli-mirage@^1.1.7, ember-cli-mirage@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/ember-cli-mirage/-/ember-cli-mirage-1.1.8.tgz#b3874a0f2eb83d8f341416d3a691ff82735c629c" integrity sha512-i0/CNwdyza+o+0RW92i/dEhAqTcyoAJ9jGKKiOLK74RwDG0Bgt5w/CdqKfIFBf/lIGPXHPveneWsyckeSiVr4A== @@ -8775,6 +8775,19 @@ ember-data-model-fragments@5.0.0-beta.0: git-repo-info "^2.0.0" npm-git-info "^1.0.3" +ember-data-model-fragments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ember-data-model-fragments/-/ember-data-model-fragments-4.0.0.tgz#8570d8b6bef12e8a3518279af01478ef8818a44f" + integrity sha512-DcRRQAWrrfRQBW8+v6takQdS2hNJ9rKWU/uHjnPY6+Vg/y6TxyPU8mo0PF2z0DYAeXwoIQ05iYTJT0sqFPEo+w== + dependencies: + broccoli-file-creator "^2.1.1" + broccoli-merge-trees "^3.0.0" + calculate-cache-key-for-tree "^1.1.0" + ember-cli-babel "^6.8.0" + ember-copy "1.0.0" + git-repo-info "^2.0.0" + npm-git-info "^1.0.3" + ember-data@^3.16.0: version "3.22.1" resolved "https://registry.yarnpkg.com/ember-data/-/ember-data-3.22.1.tgz#5b075fc8a2c3f18dac2305037d34b88666631b1a"