Merge pull request #375 from hashicorp/add-scope-header
Add header to desktop
This commit is contained in:
72 files changed
+3681
-5203
No files matched your search
@@ -27,6 +27,8 @@ Add this addon to an Ember application's `devDependencies` as:
|
||||
|
||||
See monorepo README for installation instructions.
|
||||
|
||||
When manually installing addon, ensure `ember-data-fragments@5.0.0-beta.*` is installed to use api models with data fragments.
|
||||
|
||||
## Yarn Commands
|
||||
|
||||
List of available project commands. `yarn run <command-name>`
|
||||
|
||||
@@ -73,6 +73,14 @@ To build a new component, start by generating it on the command line with
|
||||
`component-name.stories.mdx`. Use the Storybook UI during component
|
||||
development (see Yarn commands).
|
||||
|
||||
## Using Components
|
||||
|
||||
A few of the components have additional ember package dependencies that need to be installed before usage.
|
||||
|
||||
| Component | Dependency |
|
||||
| --------- | ---------- |
|
||||
| Rose::Dropdown | ember-modifier |
|
||||
|
||||
## Running tests
|
||||
|
||||
* `yarn test` – Runs the test suite on the current Ember version
|
||||
|
||||
@@ -6,4 +6,10 @@ export default class Router extends EmberRouter {
|
||||
rootURL = config.rootURL;
|
||||
}
|
||||
|
||||
Router.map(function () {});
|
||||
Router.map(function () {
|
||||
this.route('scopes', function() {
|
||||
this.route('scope', { path: ':scope_id' }, function() {
|
||||
this.route('targets');
|
||||
});
|
||||
});
|
||||
});
|
||||
Whitespace-only changes.
@@ -2,7 +2,6 @@ import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class IndexRoute extends Route {
|
||||
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
@@ -19,4 +18,10 @@ export default class IndexRoute extends Route {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to scopes route for further processing.
|
||||
*/
|
||||
redirect() {
|
||||
this.transitionTo('scopes');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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() {
|
||||
// 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([]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { A } from '@ember/array';
|
||||
|
||||
export default class ScopesScopeRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service scope;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Attempt to load the specified scope 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 the scope fails to load, we still proceed using a temporary scope object
|
||||
* consisting of only the specified ID.
|
||||
* @param {object} params
|
||||
* @param {string} params.scope_id
|
||||
* @return {Promise{ScopeModel}}
|
||||
*/
|
||||
model({ scope_id: id }) {
|
||||
// Since only global and org scopes are authenticatable, we can infer type
|
||||
// from ID because global has a fixed ID.
|
||||
const type = id === 'global' ? 'global' : 'org';
|
||||
return this.store.findRecord('scope', id).catch(() => {
|
||||
console.error('caught error');
|
||||
const maybeExistingScope = this.store.peekRecord('scope', id);
|
||||
|
||||
const scopeOptions = { id, type };
|
||||
return (
|
||||
maybeExistingScope || this.store.createRecord('scope', scopeOptions)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load all scopes within the current scope context. Always attempt to load
|
||||
* org scopes. Only attempt to load project scopes if the current scope is
|
||||
* a project. These are used for scope navigation.
|
||||
*/
|
||||
async afterModel(model) {
|
||||
// Load all orgs
|
||||
let orgs;
|
||||
orgs = await this.store.query('scope', { scope_id: 'global' })
|
||||
.catch(() => A([]));
|
||||
// Then pull out the "selected" scopes, if relevant
|
||||
let selectedOrg;
|
||||
if (model.isGlobal || model.isOrg) selectedOrg = model;
|
||||
|
||||
let projects;
|
||||
if(model.isGlobal) {
|
||||
projects = orgs.map((id) => this.store.query('scope', { scope_id: id }));
|
||||
}
|
||||
if(model.isOrg) {
|
||||
projects = this.store.query('scope', { scope_id: model.id });
|
||||
}
|
||||
|
||||
// Update the scope service with the current scope(s);
|
||||
this.scope.org = selectedOrg;
|
||||
this.scope.projects = await projects;
|
||||
this.scopes = { orgs, selectedOrg };
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the scopes hash to the controller context (see `afterModel`).
|
||||
* @param {Controller} controller
|
||||
*/
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
const scopes = this.scopes;
|
||||
controller.setProperties({ scopes });
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the scope-specific sidebar template.
|
||||
* @override
|
||||
* @param {object} controller
|
||||
* @param {object} model
|
||||
*/
|
||||
renderTemplate() {
|
||||
super.renderTemplate(...arguments);
|
||||
this.render('scopes/scope/-header-nav', {
|
||||
into: 'application',
|
||||
outlet: 'header-nav',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ScopesScopeIndexRoute extends Route {
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Redirects to scopes route for further processing.
|
||||
*/
|
||||
redirect() {
|
||||
this.transitionTo('scopes.scope.targets');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { all } from 'rsvp';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeTargetsRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service scope;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Loads all targets under current scope.
|
||||
* @return {Promise{[TargetModel]}}
|
||||
*/
|
||||
async model() {
|
||||
let projectTargets = await all(this.scope.projects.map(({ id: scope_id }) =>
|
||||
this.store.query('target', { scope_id })
|
||||
))
|
||||
|
||||
return projectTargets.map((target) => target.toArray()).flat();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Service from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
* This simple non-functional service is used to retain references to
|
||||
* the currently active scope or scopes.
|
||||
*/
|
||||
export default class ScopeService extends Service {
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* @type {ScopeModel}
|
||||
*/
|
||||
@tracked org;
|
||||
|
||||
/**
|
||||
* @type {[ScopeModel]}
|
||||
*/
|
||||
@tracked projects;
|
||||
}
|
||||
@@ -5,6 +5,10 @@
|
||||
<layout.header>
|
||||
<Rose::Header as |header|>
|
||||
<header.brand @logo="logo-app" @text={{app-name}} />
|
||||
|
||||
<header.nav>
|
||||
{{outlet "header-nav"}}
|
||||
</header.nav>
|
||||
</Rose::Header>
|
||||
</layout.header>
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
{{outlet}}
|
||||
{{@model.message}}
|
||||
@@ -0,0 +1 @@
|
||||
{{outlet}}
|
||||
@@ -0,0 +1,26 @@
|
||||
{{outlet}}
|
||||
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'scope.title'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model as |scope|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<Rose::LinkButton
|
||||
@route="scopes.scope"
|
||||
@model={{scope.id}}
|
||||
>
|
||||
{{scope.name}}
|
||||
</Rose::LinkButton>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
{{scope.id}}
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
@@ -0,0 +1 @@
|
||||
{{outlet}}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Rose::Dropdown
|
||||
@text={{this.scopes.selectedOrg.displayName}}
|
||||
@icon={{if this.scopes.selectedOrg.isGlobal "app-icons/global" "app-icons/org"}} as |dropdown|
|
||||
>
|
||||
{{!-- {{#if this.session.data.authenticated.isGlobal}} --}}
|
||||
<dropdown.link @route="scopes.scope" @model="global">
|
||||
{{t "scope.global.title"}}
|
||||
</dropdown.link>
|
||||
<dropdown.separator />
|
||||
{{!-- {{/if}} --}}
|
||||
{{#each this.scopes.orgs as |scope|}}
|
||||
<dropdown.link @route="scopes.scope" @model={{scope.id}}>
|
||||
{{scope.displayName}}
|
||||
</dropdown.link>
|
||||
{{/each}}
|
||||
</Rose::Dropdown>
|
||||
@@ -0,0 +1 @@
|
||||
{{outlet}}
|
||||
@@ -0,0 +1,27 @@
|
||||
{{outlet}}
|
||||
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t "name.label"}}</row.headerCell>
|
||||
<row.headerCell>{{t "type.label"}}</row.headerCell>
|
||||
<row.headerCell>{{t "id.label"}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model as |target|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
{{target.name}}
|
||||
<p>{{target.description}}</p>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
{{target.type}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
{{target.id}}
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
@@ -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 ? '' : process.env.EMBER_CLI_ELECTRON ? '' : '/',
|
||||
locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : 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
|
||||
|
||||
@@ -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.',
|
||||
// }]
|
||||
// }));
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import factory from '../generated/factories/account';
|
||||
|
||||
export default factory.extend();
|
||||
@@ -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] });
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -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 });
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import factory from '../generated/factories/host-set';
|
||||
|
||||
export default factory.extend({
|
||||
id: (i) => `host-set-id-${i}`
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import factory from '../generated/factories/host';
|
||||
|
||||
export default factory.extend({
|
||||
id: (i) => `host-id-${i}`
|
||||
});
|
||||
@@ -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 }
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -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});
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import factory from '../generated/factories/user';
|
||||
|
||||
export default factory.extend({
|
||||
id: (i) => `user-${i}`
|
||||
});
|
||||
@@ -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() }),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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() } },
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Model, belongsTo } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
|
||||
scope: belongsTo(),
|
||||
authMethod: belongsTo()
|
||||
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Model, belongsTo } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
|
||||
scope: belongsTo()
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
|
||||
scope: belongsTo(),
|
||||
hostSets: hasMany()
|
||||
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
|
||||
scope: belongsTo(),
|
||||
target: belongsTo(),
|
||||
hostCatalog: belongsTo(),
|
||||
hosts: hasMany()
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Model, belongsTo } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
|
||||
scope: belongsTo(),
|
||||
hostCatalog: belongsTo()
|
||||
|
||||
});
|
||||
@@ -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(),
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
|
||||
scope: belongsTo(),
|
||||
hostSets: hasMany()
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
|
||||
scope: belongsTo(),
|
||||
accounts: hasMany('account')
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export default function(server) {
|
||||
// Scopes
|
||||
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');
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'auth-method'
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'host-catalog',
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'host'
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'scope'
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -47,19 +47,22 @@
|
||||
"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": "5.0.0-beta.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",
|
||||
"ember-fetch": "^8.0.1",
|
||||
"ember-fetch": "^8.0.2",
|
||||
"ember-inline-svg": "^1.0.0",
|
||||
"ember-intl": "^5.5.0",
|
||||
"ember-load-initializers": "^2.1.1",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
"ember-modifier": "^2.1.1",
|
||||
"ember-page-title": "^6.0.3",
|
||||
"ember-qunit": "^4.6.0",
|
||||
"ember-resolver": "^8.0.0",
|
||||
@@ -69,6 +72,7 @@
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-plugin-ember": "^8.9.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"faker": "^5.1.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.1.2",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.97503 7.25H10.0232C9.83952 5.33678 9.13836 3.52559 7.99998 2.00183C6.85742 3.53084 6.15589 5.3466 5.97503 7.25ZM10.025 8.75C9.84413 10.6534 9.14259 12.4692 8.00003 13.9982C6.86166 12.4744 6.16049 10.6632 5.97679 8.75H10.025ZM11.5309 8.75H14.4572C14.1478 11.4433 12.1921 13.6359 9.62187 14.296C10.707 12.6405 11.3662 10.7347 11.5309 8.75ZM14.4572 7.25H11.5293C11.3619 5.25743 10.7034 3.35494 9.62165 1.70392C12.192 2.36401 14.1478 4.55661 14.4572 7.25ZM4.46913 7.25H1.5428C1.85224 4.55668 3.80789 2.36413 6.37814 1.70397C5.29298 3.35946 4.63378 5.2653 4.46913 7.25ZM4.47076 8.75H1.5428C1.85224 11.4434 3.808 13.636 6.37836 14.2961C5.29662 12.6451 4.63811 10.7426 4.47076 8.75ZM0 8C0 3.58172 3.58172 0 8 0L8.00195 3.05531e-06C12.4193 0.00105872 16 3.58237 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 990 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.15625 12.1875H5V16H0V0H12V5.125H15.5V16H12H10.5H7.15625V12.1875ZM10.5 14.5H8.65625V10.6875H3.5V14.5H1.5V1.5H10.5V5.125V14.5ZM12 14.5H14V6.625H12V14.5ZM3 3H4.88235V5H3V3ZM4.88235 7H3V9H4.88235V7ZM7 3H8.88235V5H7V3ZM8.88235 7H7V9H8.88235V7Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | scopes', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:scopes');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | scopes/scope', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:scopes/scope');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | scopes/scope/index', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:scopes/scope/index');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | scopes/scope/targets', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:scopes/scope/targets');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Service | scope', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let service = this.owner.lookup('service:scope');
|
||||
assert.ok(service);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
id:
|
||||
label: ID
|
||||
name:
|
||||
label: Name
|
||||
help: Name for identification purposes
|
||||
description:
|
||||
label: Description
|
||||
help: Description for identification purposes
|
||||
type:
|
||||
label: Type
|
||||
help: The type of the resource, to help differentiate schemas
|
||||
@@ -0,0 +1,8 @@
|
||||
scope:
|
||||
title: Scope
|
||||
title_plural: Scopes
|
||||
global:
|
||||
title: Global
|
||||
target:
|
||||
title: Target
|
||||
title_plural: Targets
|
||||
Vendored
Whitespace-only changes.
Reference in new issue
Block a user