ui: DataSource Decorator (#9746)

We use a `<DataSource @src={{url}} />` component throughout our UI for when we want to load data from within our components. The URL specified as the `@src` is used to map/lookup what is used in to retrieve data, for example we mostly use our repository methods wrapped with our Promise backed `EventSource` implementation, but DataSource URLs can also be mapped to EventTarget backed `EventSource`s and native `EventSource`s or `WebSockets` if we ever need to use those (for example these are options for potential streaming support with the Consul backend).

The URL to function/method mapping previous to this PR used a very naive humongous `switch` statement which was a temporary 'this is fine for the moment' solution, although we'd always wanted to replace with something more manageable.

Here we add `wayfarer` as a dependency - a very small (1kb), very fast, radix trie based router, and use that to perform the URL to function/method mapping.

This essentially turns every `DataSource` into a very small SPA - change its URL and the view of data changes. When the data itself changes, either the yielded view of data changes or the `onchange` event is fired with the changed data, making the externally sourced view of data completely reactive.

```javascript
// use the new decorator a service somewhere to annotate/decorate
// a method with the URL that can be used to access this method
@dataSource('/:ns/:dc/services')
async findAllByDatacenter(params) {
  // get the data
}

// can use with JS in a route somewhere
async model() {
  return this.data.source(uri => uri`/${nspace}/${dc}/services`)
}
```

```hbs
{{!-- or just straight in a template using the component --}}
<DataSource @src="/default/dc1/services" @onchange="" />
```

This also uses a new `container` Service to automatically execute/import certain services yet not execute them. This new service also provides a lookup that supports both standard ember DI lookup plus Class based lookup or these specific services. Lastly we also provide another debug function called DataSourceRoutes() which can be called from console which gives you a list of URLs and their mappings.
This commit is contained in:
John Cowen 2021-02-23 08:56:42 +00:00 committed by GitHub
parent fa77e9e1f7
commit 8b12d0d09d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 582 additions and 530 deletions

View File

@ -4,20 +4,20 @@ import { inject as service } from '@ember/service';
export default class PermissionAdapter extends Adapter {
@service('env') env;
requestForAuthorize(request, { dc, ns, permissions = [], index }) {
requestForAuthorize(request, { dc, ns, resources = [], index }) {
// the authorize endpoint is slightly different to all others in that it
// ignores an ns parameter, but accepts a Namespace property on each
// resource. Here we hide this different from the rest of the app as
// currently we never need to ask for permissions/resources for mutiple
// currently we never need to ask for permissions/resources for multiple
// different namespaces in one call so here we use the ns param and add
// this to the resources instead of passing through on the queryParameter
if (this.env.var('CONSUL_NSPACES_ENABLED')) {
permissions = permissions.map(item => ({ ...item, Namespace: ns }));
resources = resources.map(item => ({ ...item, Namespace: ns }));
}
return request`
POST /v1/internal/acl/authorize?${{ dc, index }}
${permissions}
${resources}
`;
}

View File

@ -0,0 +1,52 @@
import { runInDebug } from '@ember/debug';
import wayfarer from 'wayfarer';
const router = wayfarer();
const routes = {};
export default path => (target, propertyKey, desc) => {
runInDebug(() => {
routes[path] = { cls: target, method: propertyKey };
});
router.on(path, function(params, owner) {
const container = owner.lookup('service:container');
const instance = container.get(target);
return configuration => desc.value.apply(instance, [params, configuration]);
});
return desc;
};
export const match = path => {
return router.match(path);
};
runInDebug(() => {
window.DataSourceRoutes = () => {
// debug time only way to access the application and be able to lookup
// services, don't use ConsulUi global elsewhere!
const container = window.ConsulUi.__container__.lookup('service:container');
const win = window.open('', '_blank');
win.document.write(`
<body>
<pre>
${Object.entries(routes)
.map(([key, value]) => {
let cls = container
.keyForClass(value.cls)
.split('/')
.pop();
cls = cls
.split('-')
.map(item => `${item[0].toUpperCase()}${item.substr(1)}`)
.join('');
return `${key}
${cls}Repository.${value.method}(params)
`;
})
.join('')}
</pre>
</body>
`);
win.focus();
return;
};
});

View File

@ -0,0 +1,32 @@
import { runInDebug } from '@ember/debug';
export default {
name: 'container',
initialize(application) {
const container = application.lookup('service:container');
// find all the services and add their classes to the container so we can
// look instances up by class afterwards as we then resolve the
// registration for each of these further down this means that any top
// level code for these services is executed, this is most useful for
// making sure any annotation type decorators are executed.
// For now we only want repositories, so only look for those for the moment
let repositories = container
.get('container-debug-adapter:main')
.catalogEntriesByType('service')
.filter(item => item.startsWith('repository/'));
// during testing we get -test files in here, filter those out but only in debug envs
runInDebug(() => (repositories = repositories.filter(item => !item.endsWith('-test'))));
// 'service' service is not returned by catalogEntriesByType, possibly
// related to pods and the service being called 'service':
// https://github.com/ember-cli/ember-resolver/blob/c07287af17766bfd3acf390f867fea17686f77d2/addon/resolvers/classic/container-debug-adapter.js#L80
// so push it on the end
repositories.push('repository/service');
//
repositories.forEach(item => {
const key = `service:${item}`;
container.set(key, container.resolveRegistration(key));
});
},
};

View File

@ -8,11 +8,11 @@ export default Mixin.create(WithBlockingActions, {
actions: {
use: function(item) {
return this.repo
.findBySlug(
get(item, 'AccessorID'),
this.modelFor('dc').dc.Name,
this.modelFor('nspace').nspace.substr(1)
)
.findBySlug({
ns: this.modelFor('nspace').nspace.substr(1),
dc: this.modelFor('dc').dc.Name,
id: get(item, 'AccessorID'),
})
.then(item => {
return this.settings.persist({
token: {

View File

@ -43,7 +43,10 @@ export default class DcRoute extends Route {
app.nspaces.length > 1 ? findActiveNspace(app.nspaces, nspace) : app.nspaces.firstObject;
// When disabled nspaces is [], so nspace is undefined
const permissions = await this.permissionsRepo.findAll(params.dc, get(nspace || {}, 'Name'));
const permissions = await this.permissionsRepo.findAll({
dc: params.dc,
nspace: get(nspace || {}, 'Name'),
});
return {
dc,
nspace,
@ -79,7 +82,10 @@ export default class DcRoute extends Route {
const controller = this.controllerFor('application');
Promise.all([
this.nspacesRepo.findAll(),
this.permissionsRepo.findAll(get(controller, 'dc.Name'), get(controller, 'nspace.Name')),
this.permissionsRepo.findAll({
dc: get(controller, 'dc.Name'),
nspace: get(controller, 'nspace.Name'),
}),
]).then(([nspaces, permissions]) => {
if (typeof controller !== 'undefined') {
controller.setProperties({

View File

@ -22,10 +22,10 @@ export default class IndexRoute extends Route {
model(params) {
return hash({
...this.repo.status({
items: this.repo.findAllByDatacenter(
this.modelFor('dc').dc.Name,
this.modelFor('nspace').nspace.substr(1)
),
items: this.repo.findAllByDatacenter({
dc: this.modelFor('dc').dc.Name,
ns: this.modelFor('nspace').nspace.substr(1),
}),
}),
searchProperties: this.queryParams.searchproperty.empty[0],
});

View File

@ -13,7 +13,10 @@ export default class EditRoute extends Route.extend(WithAclActions) {
model(params) {
return hash({
item: this.repo.findBySlug(params.id, this.modelFor('dc').dc.Name),
item: this.repo.findBySlug({
dc: this.modelFor('dc').dc.Name,
id: params.id,
}),
types: ['management', 'client'],
});
}

View File

@ -30,7 +30,7 @@ export default class IndexRoute extends Route.extend(WithAclActions) {
}
async model(params) {
const _items = this.repo.findAllByDatacenter(this.modelFor('dc').dc.Name);
const _items = this.repo.findAllByDatacenter({ dc: this.modelFor('dc').dc.Name });
const _token = this.settings.findBySlug('token');
return {
items: await _items,

View File

@ -21,15 +21,21 @@ export default class EditRoute extends SingleRoute.extend(WithPolicyActions) {
...model,
...{
routeName: this.routeName,
items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc, nspace).catch(function(e) {
switch (get(e, 'errors.firstObject.status')) {
case '403':
case '401':
// do nothing the SingleRoute will have caught it already
return;
}
throw e;
}),
items: tokenRepo
.findByPolicy({
ns: nspace,
dc: dc,
id: get(model.item, 'ID'),
})
.catch(function(e) {
switch (get(e, 'errors.firstObject.status')) {
case '403':
case '401':
// do nothing the SingleRoute will have caught it already
return;
}
throw e;
}),
},
});
});

View File

@ -26,10 +26,10 @@ export default class IndexRoute extends Route.extend(WithPolicyActions) {
model(params) {
return hash({
...this.repo.status({
items: this.repo.findAllByDatacenter(
this.modelFor('dc').dc.Name,
this.modelFor('nspace').nspace.substr(1)
),
items: this.repo.findAllByDatacenter({
ns: this.modelFor('nspace').nspace.substr(1),
dc: this.modelFor('dc').dc.Name,
}),
}),
searchProperties: this.queryParams.searchproperty.empty[0],
});

View File

@ -20,15 +20,21 @@ export default class EditRoute extends SingleRoute.extend(WithRoleActions) {
return hash({
...model,
...{
items: tokenRepo.findByRole(get(model.item, 'ID'), dc, nspace).catch(function(e) {
switch (get(e, 'errors.firstObject.status')) {
case '403':
case '401':
// do nothing the SingleRoute will have caught it already
return;
}
throw e;
}),
items: tokenRepo
.findByRole({
ns: nspace,
dc: dc,
id: get(model.item, 'ID'),
})
.catch(function(e) {
switch (get(e, 'errors.firstObject.status')) {
case '403':
case '401':
// do nothing the SingleRoute will have caught it already
return;
}
throw e;
}),
},
});
});

View File

@ -22,10 +22,10 @@ export default class IndexRoute extends Route.extend(WithRoleActions) {
model(params) {
return hash({
...this.repo.status({
items: this.repo.findAllByDatacenter(
this.modelFor('dc').dc.Name,
this.modelFor('nspace').nspace.substr(1)
),
items: this.repo.findAllByDatacenter({
ns: this.modelFor('nspace').nspace.substr(1),
dc: this.modelFor('dc').dc.Name,
}),
}),
searchProperties: this.queryParams.searchproperty.empty[0],
});

View File

@ -35,10 +35,10 @@ export default class IndexRoute extends Route.extend(WithTokenActions) {
model(params) {
return hash({
...this.repo.status({
items: this.repo.findAllByDatacenter(
this.modelFor('dc').dc.Name,
this.modelFor('nspace').nspace.substr(1)
),
items: this.repo.findAllByDatacenter({
ns: this.modelFor('nspace').nspace.substr(1),
dc: this.modelFor('dc').dc.Name,
}),
}),
nspace: this.modelFor('nspace').nspace.substr(1),
token: this.settings.findBySlug('token'),

View File

@ -11,7 +11,11 @@ export default class EditRoute extends Route {
let item;
if (typeof intention_id !== 'undefined') {
item = await this.repo.findBySlug(intention_id, dc, nspace);
item = await this.repo.findBySlug({
ns: nspace,
dc: dc,
id: intention_id,
});
} else {
const defaultNspace = this.env.var('CONSUL_NSPACES_ENABLED') ? '*' : 'default';
item = await this.repo.create({

View File

@ -24,14 +24,26 @@ export default class EditRoute extends Route {
nspace: nspace || 'default',
parent:
typeof key !== 'undefined'
? this.repo.findBySlug(ascend(key, 1) || '/', dc, nspace)
: this.repo.findBySlug('/', dc, nspace),
? this.repo.findBySlug({
ns: nspace,
dc: dc,
id: ascend(key, 1) || '/',
})
: this.repo.findBySlug({
ns: nspace,
dc: dc,
id: '/',
}),
item: create
? this.repo.create({
Datacenter: dc,
Namespace: nspace,
})
: this.repo.findBySlug(key, dc, nspace),
: this.repo.findBySlug({
ns: nspace,
dc: dc,
id: key,
}),
session: null,
}).then(model => {
// TODO: Consider loading this after initial page load
@ -41,7 +53,11 @@ export default class EditRoute extends Route {
return hash({
...model,
...{
session: this.sessionRepo.findByKey(session, dc, nspace),
session: this.sessionRepo.findByKey({
ns: nspace,
dc: dc,
id: session,
}),
},
});
}

View File

@ -31,12 +31,30 @@ export default class IndexRoute extends Route {
const dc = this.modelFor('dc').dc.Name;
const nspace = this.modelFor('nspace').nspace.substr(1);
return hash({
parent: this.repo.findBySlug(key, dc, nspace),
parent: this.repo.findBySlug({
ns: nspace,
dc: dc,
id: key,
}),
}).then(model => {
return hash({
...model,
...{
items: this.repo.findAllBySlug(get(model.parent, 'Key'), dc, nspace),
items: this.repo
.findAllBySlug({
ns: nspace,
dc: dc,
id: get(model.parent, 'Key'),
})
.catch(e => {
const status = get(e, 'errors.firstObject.status');
switch (status) {
case '403':
return this.transitionTo('dc.acls.tokens');
default:
return this.transitionTo('dc.kv.index');
}
}),
},
});
});

View File

@ -28,7 +28,7 @@ export default class EditRoute extends Route.extend(WithNspaceActions) {
},
})
)
: repo.findBySlug(params.name),
: repo.findBySlug({ id: params.name }),
});
}

View File

@ -27,7 +27,11 @@ export default Route.extend({
Namespace: nspace,
})
)
: repo.findBySlug(params.id, dc, nspace),
: repo.findBySlug({
ns: nspace,
dc: dc,
id: params.id,
}),
}),
});
},

View File

@ -0,0 +1,36 @@
import Service from '@ember/service';
export default class ContainerService extends Service {
constructor(owner) {
super(...arguments);
this._owner = owner;
this._wm = new WeakMap();
}
set(key, value) {
this._wm.set(value, key);
}
// vaguely private, used publicly for debugging purposes
keyForClass(cls) {
return this._wm.get(cls);
}
get(key) {
if (typeof key !== 'string') {
key = this.keyForClass(key);
}
return this.lookup(key);
}
lookup(key) {
return this._owner.lookup(key);
}
resolveRegistration(key) {
// ember resolveRegistration returns an ember flavoured class extending
// from the actual class, access the actual class from the
// prototype/parent which is what decorators pass through as target
return this._owner.resolveRegistration(key).prototype;
}
}

View File

@ -1,95 +1,28 @@
import Service, { inject as service } from '@ember/service';
import { get } from '@ember/object';
import { getOwner } from '@ember/application';
import { match } from 'consul-ui/decorators/data-source';
import { singularize } from 'ember-inflector';
export default class HttpService extends Service {
@service('repository/dc')
datacenters;
@service('repository/dc') datacenters;
@service('repository/node') leader;
@service('repository/service') gateways;
@service('repository/service-instance') 'proxy-service-instance';
@service('repository/proxy') 'proxy-instance';
@service('repository/nspace') namespaces;
@service('repository/metrics') metrics;
@service('repository/oidc-provider') oidc;
@service('repository/node')
nodes;
@service('repository/node')
node;
@service('repository/node')
leader;
@service('repository/service')
gateways;
@service('repository/service')
services;
@service('repository/service')
service;
@service('repository/service-instance')
'service-instance';
@service('repository/service-instance')
'proxy-service-instance';
@service('repository/service-instance')
'service-instances';
@service('repository/proxy')
proxies;
@service('repository/proxy')
'proxy-instance';
@service('repository/discovery-chain')
'discovery-chain';
@service('repository/topology')
topology;
@service('repository/coordinate')
coordinates;
@service('repository/session')
sessions;
@service('repository/nspace')
namespaces;
@service('repository/intention')
intentions;
@service('repository/intention')
intention;
@service('repository/kv')
kv;
@service('repository/token')
token;
@service('repository/policy')
policies;
@service('repository/policy')
policy;
@service('repository/role')
roles;
@service('repository/oidc-provider')
oidc;
@service('repository/metrics')
metrics;
@service('data-source/protocols/http/blocking')
type;
@service('data-source/protocols/http/blocking') type;
source(src, configuration) {
// TODO: Consider adding/requiring 'action': nspace, dc, model, action, ...rest
const [, nspace, dc, model, ...rest] = src.split('/').map(decodeURIComponent);
// nspaces can be filled, blank or *
// so we might get urls like //dc/services
let find;
const repo = this[model];
const [, , , model] = src.split('/');
const owner = getOwner(this);
const route = match(src);
const find = route.cb(route.params, owner);
const repo = this[model] || owner.lookup(`service:repository/${singularize(model)}`);
configuration.createEvent = function(result = {}, configuration) {
const event = {
type: 'message',
@ -101,141 +34,6 @@ export default class HttpService extends Service {
}
return event;
};
let method, slug, more, protocol;
switch (model) {
case 'metrics':
[method, slug, ...more] = rest;
switch (method) {
case 'summary-for-service':
[protocol, ...more] = more;
find = configuration =>
repo.findServiceSummary(protocol, slug, dc, nspace, configuration);
break;
case 'upstream-summary-for-service':
find = configuration => repo.findUpstreamSummary(slug, dc, nspace, configuration);
break;
case 'downstream-summary-for-service':
find = configuration => repo.findDownstreamSummary(slug, dc, nspace, configuration);
break;
}
break;
case 'datacenters':
case 'namespaces':
find = configuration => repo.findAll(configuration);
break;
case 'services':
case 'nodes':
case 'roles':
case 'policies':
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
break;
case 'leader':
find = configuration => repo.findLeader(dc, configuration);
break;
case 'intentions':
[method, ...slug] = rest;
switch (method) {
case 'for-service':
find = configuration => repo.findByService(slug, dc, nspace, configuration);
break;
default:
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
break;
}
break;
case 'service-instances':
[method, ...slug] = rest;
switch (method) {
case 'for-service':
find = configuration => repo.findByService(slug, dc, nspace, configuration);
break;
}
break;
case 'coordinates':
[method, ...slug] = rest;
switch (method) {
case 'for-node':
find = configuration => repo.findAllByNode(slug, dc, configuration);
break;
}
break;
case 'proxies':
[method, ...slug] = rest;
switch (method) {
case 'for-service':
find = configuration => repo.findAllBySlug(slug, dc, nspace, configuration);
break;
}
break;
case 'gateways':
[method, ...slug] = rest;
switch (method) {
case 'for-service':
find = configuration => repo.findGatewayBySlug(slug, dc, nspace, configuration);
break;
}
break;
case 'sessions':
[method, ...slug] = rest;
switch (method) {
case 'for-node':
find = configuration => repo.findByNode(slug, dc, nspace, configuration);
break;
}
break;
case 'token':
find = configuration => repo.self(rest[1], dc);
break;
case 'discovery-chain':
case 'node':
find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration);
break;
case 'service-instance':
// id, node, service
find = configuration =>
repo.findBySlug(rest[0], rest[1], rest[2], dc, nspace, configuration);
break;
case 'proxy-service-instance':
// id, node, service
find = configuration =>
repo.findProxyBySlug(rest[0], rest[1], rest[2], dc, nspace, configuration);
break;
case 'proxy-instance':
// id, node, service
find = configuration =>
repo.findInstanceBySlug(rest[0], rest[1], rest[2], dc, nspace, configuration);
break;
case 'topology':
// id, service kind
find = configuration => repo.findBySlug(rest[0], rest[1], dc, nspace, configuration);
break;
case 'policy':
case 'kv':
case 'intention':
slug = rest[0];
if (slug) {
find = configuration => repo.findBySlug(slug, dc, nspace, configuration);
} else {
find = configuration =>
Promise.resolve(repo.create({ Datacenter: dc, Namespace: nspace }));
}
break;
case 'oidc':
[method, ...slug] = rest;
switch (method) {
case 'providers':
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
break;
case 'provider':
find = configuration => repo.findBySlug(slug[0], dc, nspace);
break;
case 'authorize':
find = configuration =>
repo.authorize(slug[0], slug[1], slug[2], dc, nspace, configuration);
break;
}
break;
}
return this.type.source(find, configuration);
}
}

View File

@ -23,43 +23,33 @@ export default class RepositoryService extends Service {
}
/**
* Creates a set of permissions base don a slug, loads in the access
* permissions for themand checks/validates
* Creates a set of permissions based on an id/slug, loads in the access
* permissions for them and checks/validates
*/
async authorizeBySlug(cb, access, slug, dc, nspace) {
return this.validatePermissions(
cb,
await this.permissions.findBySlug(slug, this.getModelName(), dc, nspace),
access,
dc,
nspace
);
async authorizeBySlug(cb, access, params) {
params.resources = await this.permissions.findBySlug(params, this.getModelName());
return this.validatePermissions(cb, access, params);
}
/**
* Loads in the access permissions and checks/validates them for a set of
* permissions
*/
async authorizeByPermissions(cb, permissions, access, dc, nspace) {
return this.validatePermissions(
cb,
await this.permissions.authorize(permissions, dc, nspace),
access,
dc,
nspace
);
async authorizeByPermissions(cb, access, params) {
params.resources = await this.permissions.authorize(params);
return this.validatePermissions(cb, access, params);
}
/**
* Checks already loaded permissions for certain access before calling cb to
* return the thing you wanted to check the permissions on
*/
async validatePermissions(cb, permissions, access, dc, nspace) {
async validatePermissions(cb, access, params) {
// inspect the permissions for this segment/slug remotely, if we have zero
// permissions fire a fake 403 so we don't even request the model/resource
if (permissions.length > 0) {
const permission = permissions.find(item => item.Access === access);
if (permission && permission.Allow === false) {
if (params.resources.length > 0) {
const resource = params.resources.find(item => item.Access === access);
if (resource && resource.Allow === false) {
// TODO: Here we temporarily make a hybrid HTTPError/ember-data HTTP error
// we should eventually use HTTPError's everywhere
const e = new HTTPError(403);
@ -71,7 +61,7 @@ export default class RepositoryService extends Service {
// add the `Resource` information to the record/model so we can inspect
// them in other places like templates etc
if (get(item, 'Resources')) {
set(item, 'Resources', permissions);
set(item, 'Resources', params.resources);
}
return item;
}
@ -102,34 +92,29 @@ export default class RepositoryService extends Service {
return this.store.peekRecord(this.getModelName(), id);
}
findAllByDatacenter(dc, nspace, configuration = {}) {
const query = {
dc: dc,
ns: nspace,
};
findAllByDatacenter(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query);
return this.store.query(this.getModelName(), params);
}
async findBySlug(slug, dc, nspace, configuration = {}) {
const query = {
dc: dc,
ns: nspace,
id: slug,
};
async findBySlug(params, configuration = {}) {
if (params.id === '') {
return this.create({
Datacenter: params.dc,
Namespace: params.ns,
});
}
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.authorizeBySlug(
() => this.store.queryRecord(this.getModelName(), query),
() => this.store.queryRecord(this.getModelName(), params),
ACCESS_READ,
slug,
dc,
nspace
params
);
}

View File

@ -2,6 +2,7 @@ import RepositoryService from 'consul-ui/services/repository';
import statusFactory from 'consul-ui/utils/acls-status';
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/auth-method';
import dataSource from 'consul-ui/decorators/data-source';
const isValidServerError = isValidServerErrorFactory();
const status = statusFactory(isValidServerError, Promise);
@ -20,6 +21,16 @@ export default class AuthMethodService extends RepositoryService {
return SLUG_KEY;
}
@dataSource('/:ns/:dc/auth-methods')
async findAllByDatacenter() {
return super.findAllByDatacenter(...arguments);
}
@dataSource('/:ns/:dc/auth-method/:id')
async findBySlug() {
return super.findBySlug(...arguments);
}
status(obj) {
return status(obj);
}

View File

@ -4,6 +4,7 @@ import RepositoryService from 'consul-ui/services/repository';
import tomographyFactory from 'consul-ui/utils/tomography';
import distance from 'consul-ui/utils/distance';
const tomography = tomographyFactory(distance);
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'coordinate';
export default class CoordinateService extends RepositoryService {
@ -13,25 +14,21 @@ export default class CoordinateService extends RepositoryService {
// Coordinates don't need nspaces so we have a custom method here
// that doesn't accept nspaces
findAllByDatacenter(dc, configuration = {}) {
const query = {
dc: dc,
};
@dataSource('/:ns/:dc/coordinates')
findAllByDatacenter(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query);
return this.store.query(this.getModelName(), params);
}
findAllByNode(node, dc, configuration) {
return this.findAllByDatacenter(dc, configuration).then(function(coordinates) {
@dataSource('/:ns/:dc/coordinates/for-node/:id')
findAllByNode(params, configuration) {
return this.findAllByDatacenter(params, configuration).then(function(coordinates) {
let results = {};
if (get(coordinates, 'length') > 1) {
results = tomography(
node,
coordinates
);
results = tomography(params.id, coordinates);
}
results.meta = get(coordinates, 'meta');
return results;

View File

@ -2,6 +2,7 @@ import { inject as service } from '@ember/service';
import RepositoryService from 'consul-ui/services/repository';
import { get } from '@ember/object';
import Error from '@ember/error';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'dc';
export default class DcService extends RepositoryService {
@ -12,7 +13,8 @@ export default class DcService extends RepositoryService {
return modelName;
}
async findAll() {
@dataSource('/:ns/:dc/datacenters')
async findAll(params, configuration = {}) {
return this.store.query(this.getModelName(), {});
}

View File

@ -1,6 +1,7 @@
import { inject as service } from '@ember/service';
import { get, set } from '@ember/object';
import RepositoryService from 'consul-ui/services/repository';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'discovery-chain';
const ERROR_MESH_DISABLED = 'Connect must be enabled in order to use this endpoint';
@ -12,8 +13,9 @@ export default class DiscoveryChainService extends RepositoryService {
return modelName;
}
findBySlug(slug, dc, nspace, configuration = {}) {
const datacenter = this.dcs.peekOne(dc);
@dataSource('/:ns/:dc/discovery-chain/:id')
findBySlug(params, configuration = {}) {
const datacenter = this.dcs.peekOne(params.dc);
if (datacenter !== null && !get(datacenter, 'MeshEnabled')) {
return Promise.resolve();
}

View File

@ -1,6 +1,7 @@
import { set, get } from '@ember/object';
import RepositoryService from 'consul-ui/services/repository';
import { PRIMARY_KEY } from 'consul-ui/models/intention';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'intention';
export default class IntentionRepository extends RepositoryService {
@ -34,13 +35,13 @@ export default class IntentionRepository extends RepositoryService {
// legacy intentions are strange that in order to read/write you need access
// to either/or the destination or source
async authorizeBySlug(cb, access, slug, dc, nspace) {
const [, source, , destination] = slug.split(':');
async authorizeBySlug(cb, access, params) {
const [, source, , destination] = params.id.split(':');
const ability = this.permissions.abilityFor(this.getModelName());
const permissions = ability
params.resources = ability
.generateForSegment(source)
.concat(ability.generateForSegment(destination));
return this.authorizeByPermissions(cb, permissions, access, dc, nspace);
return this.authorizeByPermissions(cb, access, params);
}
async persist(obj) {
@ -56,11 +57,22 @@ export default class IntentionRepository extends RepositoryService {
return res;
}
async findByService(slug, dc, nspace, configuration = {}) {
@dataSource('/:ns/:dc/intentions')
async findAllByDatacenter() {
return super.findAllByDatacenter(...arguments);
}
@dataSource('/:ns/:dc/intention/:id')
async findBySlug() {
return super.findBySlug(...arguments);
}
@dataSource('/:ns/:dc/intentions/for-service/:id')
async findByService(params, configuration = {}) {
const query = {
dc,
nspace,
filter: `SourceName == "${slug}" or DestinationName == "${slug}" or SourceName == "*" or DestinationName == "*"`,
dc: params.dc,
nspace: params.nspace,
filter: `SourceName == "${params.id}" or DestinationName == "${params.id}" or SourceName == "*" or DestinationName == "*"`,
};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;

View File

@ -3,6 +3,7 @@ import isFolder from 'consul-ui/utils/isFolder';
import { get } from '@ember/object';
import { PRIMARY_KEY } from 'consul-ui/models/kv';
import { ACCESS_LIST } from 'consul-ui/abilities/base';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'kv';
export default class KvService extends RepositoryService {
@ -15,71 +16,65 @@ export default class KvService extends RepositoryService {
}
// this one gives you the full object so key,values and meta
async findBySlug(slug, dc, nspace, configuration = {}) {
if (isFolder(slug)) {
@dataSource('/:ns/:dc/kv/*id')
async findBySlug(params, configuration = {}) {
if (isFolder(params.id)) {
// we only use findBySlug for a folder when we are looking to create a
// parent for a key for retriveing something Model shaped. Therefore we
// only use existing records or a fake record with the correct Key,
// which means we don't need to inpsect permissions as its an already
// which means we don't need to inspect permissions as its an already
// existing KV or a fake one
// TODO: This very much shouldn't be here,
// needs to eventually use ember-datas generateId thing
// in the meantime at least our fingerprinter
const id = JSON.stringify([nspace, dc, slug]);
let item = this.store.peekRecord(this.getModelName(), id);
const uid = JSON.stringify([params.ns, params.dc, params.id]);
let item = this.store.peekRecord(this.getModelName(), uid);
if (!item) {
item = this.create({
Key: slug,
Datacenter: dc,
Namespace: nspace,
Key: params.id,
Datacenter: params.dc,
Namespace: params.ns,
});
}
return item;
} else {
return super.findBySlug(slug, dc, nspace, configuration);
return super.findBySlug(...arguments);
}
}
// this one only gives you keys
// https://www.consul.io/api/kv.html
findAllBySlug(key, dc, nspace, configuration = {}) {
if (key === '/') {
key = '';
findAllBySlug(params, configuration = {}) {
if (params.id === '/') {
params.id = '';
}
return this.authorizeBySlug(
async () => {
const query = {
id: key,
dc: dc,
ns: nspace,
separator: '/',
};
params.separator = '/';
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
params.index = configuration.cursor;
}
let items;
try {
items = await this.store.query(this.getModelName(), query);
items = await this.store.query(this.getModelName(), params);
} catch (e) {
if (get(e, 'errors.firstObject.status') === '404') {
// TODO: This very much shouldn't be here,
// needs to eventually use ember-datas generateId thing
// in the meantime at least our fingerprinter
const id = JSON.stringify([dc, key]);
const record = this.store.peekRecord(this.getModelName(), id);
const uid = JSON.stringify([params.ns, params.dc, params.id]);
const record = this.store.peekRecord(this.getModelName(), uid);
if (record) {
record.unloadRecord();
}
}
throw e;
}
return items.filter(item => key !== get(item, 'Key'));
return items.filter(item => params.id !== get(item, 'Key'));
},
ACCESS_LIST,
key,
dc,
nspace
params
);
}
}

View File

@ -1,9 +1,9 @@
import { inject as service } from '@ember/service';
import RepositoryService from 'consul-ui/services/repository';
import dataSource from 'consul-ui/decorators/data-source';
// CONSUL_METRICS_POLL_INTERVAL controls how long between each poll to the
// metrics provider
export default class MetricsService extends RepositoryService {
@service('ui-config') config;
@service('env') env;
@ -11,6 +11,10 @@ export default class MetricsService extends RepositoryService {
error = null;
getModelName() {
return 'metrics';
}
init() {
super.init(...arguments);
const config = this.config.get();
@ -34,13 +38,26 @@ export default class MetricsService extends RepositoryService {
}
}
findServiceSummary(protocol, slug, dc, nspace, configuration = {}) {
@dataSource('/:ns/:dc/metrics/summary-for-service/:slug/:protocol')
findServiceSummary(params, configuration = {}) {
if (this.error) {
return Promise.reject(this.error);
}
const promises = [
this.provider.serviceRecentSummarySeries(slug, dc, nspace, protocol, {}),
this.provider.serviceRecentSummaryStats(slug, dc, nspace, protocol, {}),
this.provider.serviceRecentSummarySeries(
params.slug,
params.dc,
params.ns,
params.protocol,
{}
),
this.provider.serviceRecentSummaryStats(
params.slug,
params.dc,
params.ns,
params.protocol,
{}
),
];
return Promise.all(promises).then(results => {
return {
@ -53,27 +70,33 @@ export default class MetricsService extends RepositoryService {
});
}
findUpstreamSummary(slug, dc, nspace, configuration = {}) {
@dataSource('/:ns/:dc/metrics/upstream-summary-for-service/:slug/:protocol')
findUpstreamSummary(params, configuration = {}) {
if (this.error) {
return Promise.reject(this.error);
}
return this.provider.upstreamRecentSummaryStats(slug, dc, nspace, {}).then(result => {
result.meta = {
interval: this.env.var('CONSUL_METRICS_POLL_INTERVAL') || 10000,
};
return result;
});
return this.provider
.upstreamRecentSummaryStats(params.slug, params.dc, params.ns, {})
.then(result => {
result.meta = {
interval: this.env.var('CONSUL_METRICS_POLL_INTERVAL') || 10000,
};
return result;
});
}
findDownstreamSummary(slug, dc, nspace, configuration = {}) {
@dataSource('/:ns/:dc/metrics/downstream-summary-for-service/:slug/:protocol')
findDownstreamSummary(params, configuration = {}) {
if (this.error) {
return Promise.reject(this.error);
}
return this.provider.downstreamRecentSummaryStats(slug, dc, nspace, {}).then(result => {
result.meta = {
interval: this.env.var('CONSUL_METRICS_POLL_INTERVAL') || 10000,
};
return result;
});
return this.provider
.downstreamRecentSummaryStats(params.slug, params.dc, params.ns, {})
.then(result => {
result.meta = {
interval: this.env.var('CONSUL_METRICS_POLL_INTERVAL') || 10000,
};
return result;
});
}
}

View File

@ -1,4 +1,5 @@
import RepositoryService from 'consul-ui/services/repository';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'node';
export default class NodeService extends RepositoryService {
@ -6,13 +7,21 @@ export default class NodeService extends RepositoryService {
return modelName;
}
findLeader(dc, configuration = {}) {
const query = {
dc: dc,
};
@dataSource('/:ns/:dc/nodes')
async findAllByDatacenter() {
return super.findAllByDatacenter(...arguments);
}
@dataSource('/:ns/:dc/node/:id')
async findBySlug() {
return super.findBySlug(...arguments);
}
@dataSource('/:ns/:dc/leader')
findLeader(params, configuration = {}) {
if (typeof configuration.refresh !== 'undefined') {
query.uri = configuration.uri;
params.uri = configuration.uri;
}
return this.store.queryLeader(this.getModelName(), query);
return this.store.queryLeader(this.getModelName(), params);
}
}

View File

@ -1,5 +1,6 @@
import RepositoryService from 'consul-ui/services/repository';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/nspace';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'nspace';
export default class NspaceService extends RepositoryService {
@ -37,7 +38,13 @@ export default class NspaceService extends RepositoryService {
return res;
}
findAll(configuration = {}) {
@dataSource('/:ns/:dc/namespace/:id')
async findBySlug() {
return super.findBySlug(...arguments);
}
@dataSource('/:ns/:dc/namespaces')
findAll(params, configuration = {}) {
const query = {};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;

View File

@ -24,7 +24,7 @@ export default class EnabledService extends RepositoryService {
return modelName;
}
findAll(configuration = {}) {
findAll(params, configuration = {}) {
const query = {};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;

View File

@ -2,6 +2,7 @@ import { inject as service } from '@ember/service';
import RepositoryService from 'consul-ui/services/repository';
import { getOwner } from '@ember/application';
import { set } from '@ember/object';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'oidc-provider';
const OAUTH_PROVIDER_NAME = 'oidc-with-url';
@ -18,15 +19,19 @@ export default class OidcProviderService extends RepositoryService {
return modelName;
}
authorize(id, code, state, dc, nspace, configuration = {}) {
const query = {
id: id,
code: code,
state: state,
dc: dc,
ns: nspace,
};
return this.store.authorize(this.getModelName(), query);
@dataSource('/:ns/:dc/oidc/providers')
async findAllByDatacenter() {
return super.findAllByDatacenter(...arguments);
}
@dataSource('/:ns/:dc/oidc/provider/:id')
async findBySlug() {
return super.findBySlug(...arguments);
}
@dataSource('/:ns/:dc/oidc/authorize/:id/:code/:state')
authorize(params, configuration = {}) {
return this.store.authorize(this.getModelName(), params);
}
logout(id, code, state, dc, nspace, configuration = {}) {

View File

@ -97,31 +97,27 @@ export default class PermissionService extends RepositoryService {
* If ACLs are disabled, then you have access to everything, hence we check
* that here and only make the request if ACLs are enabled
*/
async authorize(resources, dc, nspace) {
async authorize(params) {
if (!this.env.var('CONSUL_ACLS_ENABLED')) {
return resources.map(item => {
return params.resources.map(item => {
return {
...item,
Allow: true,
};
});
} else {
let permissions = [];
let resources = [];
try {
permissions = await this.store.authorize('permission', {
dc: dc,
ns: nspace,
permissions: resources,
});
resources = await this.store.authorize('permission', params);
} catch (e) {
runInDebug(() => console.error(e));
// passthrough
}
return permissions;
return resources;
}
}
async findBySlug(segment, model, dc, nspace) {
async findBySlug(params, model) {
let ability;
try {
ability = this._can.abilityFor(model);
@ -129,21 +125,23 @@ export default class PermissionService extends RepositoryService {
return [];
}
const resources = ability.generateForSegment(segment.toString());
const resources = ability.generateForSegment(params.id.toString());
// if we get no resources for a segment it means that this
// ability/permission isn't segmentable
if (resources.length === 0) {
return [];
}
return this.authorize(resources, dc, nspace);
params.resources = resources;
return this.authorize(params);
}
async findByPermissions(resources, dc, nspace) {
return this.authorize(resources, dc, nspace);
async findByPermissions(params) {
return this.authorize(params);
}
async findAll(dc, nspace) {
this.permissions = await this.findByPermissions(REQUIRED_PERMISSIONS, dc, nspace);
async findAll(params) {
params.resources = REQUIRED_PERMISSIONS;
this.permissions = await this.findByPermissions(params);
return this.permissions;
}
}

View File

@ -3,6 +3,7 @@ import { get } from '@ember/object';
import statusFactory from 'consul-ui/utils/acls-status';
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/policy';
import dataSource from 'consul-ui/decorators/data-source';
const isValidServerError = isValidServerErrorFactory();
const status = statusFactory(isValidServerError, Promise);
@ -21,6 +22,16 @@ export default class PolicyService extends RepositoryService {
return SLUG_KEY;
}
@dataSource('/:ns/:dc/policies')
async findAllByDatacenter() {
return super.findAllByDatacenter(...arguments);
}
@dataSource('/:ns/:dc/policy/:id')
async findBySlug() {
return super.findBySlug(...arguments);
}
status(obj) {
return status(obj);
}

View File

@ -1,6 +1,8 @@
import RepositoryService from 'consul-ui/services/repository';
import { PRIMARY_KEY } from 'consul-ui/models/proxy';
import { get, set } from '@ember/object';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'proxy';
export default class ProxyService extends RepositoryService {
getModelName() {
@ -11,17 +13,13 @@ export default class ProxyService extends RepositoryService {
return PRIMARY_KEY;
}
findAllBySlug(slug, dc, nspace, configuration = {}) {
const query = {
id: slug,
ns: nspace,
dc: dc,
};
@dataSource('/:ns/:dc/proxies/for-service/:id')
findAllBySlug(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query).then(items => {
return this.store.query(this.getModelName(), params).then(items => {
items.forEach(item => {
// swap out the id for the services id
// so we can then assign the proxy to it if it exists
@ -37,17 +35,18 @@ export default class ProxyService extends RepositoryService {
});
}
findInstanceBySlug(id, node, slug, dc, nspace, configuration) {
return this.findAllBySlug(slug, dc, nspace, configuration).then(function(items) {
@dataSource('/:ns/:dc/proxy-instance/:serviceId/:node/:id')
findInstanceBySlug(params, nspace, configuration) {
return this.findAllBySlug(params, configuration).then(function(items) {
let res = {};
if (get(items, 'length') > 0) {
let instance = items
.filterBy('ServiceProxy.DestinationServiceID', id)
.findBy('NodeName', node);
.filterBy('ServiceProxy.DestinationServiceID', params.serviceId)
.findBy('NodeName', params.node);
if (instance) {
res = instance;
} else {
instance = items.findBy('ServiceProxy.DestinationServiceName', slug);
instance = items.findBy('ServiceProxy.DestinationServiceName', params.id);
if (instance) {
res = instance;
}

View File

@ -2,6 +2,7 @@ import RepositoryService from 'consul-ui/services/repository';
import statusFactory from 'consul-ui/utils/acls-status';
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/role';
import dataSource from 'consul-ui/decorators/data-source';
const isValidServerError = isValidServerErrorFactory();
const status = statusFactory(isValidServerError, Promise);
@ -20,6 +21,16 @@ export default class RoleService extends RepositoryService {
return SLUG_KEY;
}
@dataSource('/:ns/:dc/roles')
async findAllByDatacenter() {
return super.findAllByDatacenter(...arguments);
}
@dataSource('/:ns/:dc/role/:id')
async findBySlug() {
return super.findBySlug(...arguments);
}
status(obj) {
return status(obj);
}

View File

@ -2,6 +2,7 @@ import RepositoryService from 'consul-ui/services/repository';
import { inject as service } from '@ember/service';
import { set } from '@ember/object';
import { ACCESS_READ } from 'consul-ui/abilities/base';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'service-instance';
export default class ServiceInstanceService extends RepositoryService {
@ -10,47 +11,34 @@ export default class ServiceInstanceService extends RepositoryService {
return modelName;
}
async findByService(slug, dc, nspace, configuration = {}) {
const query = {
dc: dc,
ns: nspace,
id: slug,
};
@dataSource('/:ns/:dc/service-instances/for-service/:id')
async findByService(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.authorizeBySlug(
async () => this.store.query(this.getModelName(), query),
async () => this.store.query(this.getModelName(), params),
ACCESS_READ,
slug,
dc,
nspace
params
);
}
async findBySlug(serviceId, node, service, dc, nspace, configuration = {}) {
const query = {
dc: dc,
ns: nspace,
serviceId: serviceId,
node: node,
id: service,
};
@dataSource('/:ns/:dc/service-instance/:serviceId/:node/:id')
async findBySlug(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.authorizeBySlug(
async () => this.store.queryRecord(this.getModelName(), query),
async () => this.store.queryRecord(this.getModelName(), params),
ACCESS_READ,
service,
dc,
nspace
params
);
}
async findProxyBySlug(serviceId, node, service, dc, nspace, configuration = {}) {
@dataSource('/:ns/:dc/proxy-service-instance/:serviceId/:node/:id')
async findProxyBySlug(params, configuration = {}) {
const instance = await this.findBySlug(...arguments);
let proxy = this.store.peekRecord('proxy', instance.uid);
// Currently, we call the proxy endpoint before this endpoint

View File

@ -1,20 +1,23 @@
import RepositoryService from 'consul-ui/services/repository';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'service';
export default class _RepositoryService extends RepositoryService {
export default class ServiceService extends RepositoryService {
getModelName() {
return modelName;
}
findGatewayBySlug(slug, dc, nspace, configuration = {}) {
const query = {
dc: dc,
ns: nspace,
gateway: slug,
};
@dataSource('/:ns/:dc/services')
async findAllByDatacenter() {
return super.findAllByDatacenter(...arguments);
}
@dataSource('/:ns/:dc/gateways/for-service/:gateway')
findGatewayBySlug(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query);
return this.store.query(this.getModelName(), params);
}
}

View File

@ -1,5 +1,6 @@
import { inject as service } from '@ember/service';
import RepositoryService from 'consul-ui/services/repository';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'session';
export default class SessionService extends RepositoryService {
@ -10,21 +11,18 @@ export default class SessionService extends RepositoryService {
return modelName;
}
findByNode(node, dc, nspace, configuration = {}) {
const query = {
id: node,
dc: dc,
ns: nspace,
};
@dataSource('/:ns/:dc/sessions/for-node/:id')
findByNode(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query);
return this.store.query(this.getModelName(), params);
}
// TODO: Why Key? Probably should be findBySlug like the others
findByKey(slug, dc, nspace) {
@dataSource('/:ns/:dc/sessions/for-key/:id')
findByKey(params, configuration = {}) {
return this.findBySlug(...arguments);
}
}

View File

@ -3,6 +3,7 @@ import { get } from '@ember/object';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/token';
import statusFactory from 'consul-ui/utils/acls-status';
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
import dataSource from 'consul-ui/decorators/data-source';
const isValidServerError = isValidServerErrorFactory();
const status = statusFactory(isValidServerError, Promise);
@ -25,11 +26,13 @@ export default class TokenService extends RepositoryService {
return status(obj);
}
self(secret, dc) {
@dataSource('/:ns/:dc/token/self/:secret')
self(params) {
// TODO: Does this need ns passing through?
return this.store
.self(this.getModelName(), {
secret: secret,
dc: dc,
secret: params.secret,
dc: params.dc,
})
.catch(e => {
// If we get this 500 RPC error, it means we are a legacy ACL cluster
@ -37,7 +40,7 @@ export default class TokenService extends RepositoryService {
if (isValidServerError(e)) {
return {
AccessorID: null,
SecretID: secret,
SecretID: params.secret,
};
}
return Promise.reject(e);
@ -48,19 +51,19 @@ export default class TokenService extends RepositoryService {
return this.store.clone(this.getModelName(), get(item, PRIMARY_KEY));
}
findByPolicy(id, dc, nspace) {
findByPolicy(params) {
return this.store.query(this.getModelName(), {
policy: id,
dc: dc,
ns: nspace,
policy: params.id,
dc: params.dc,
ns: params.ns,
});
}
findByRole(id, dc, nspace) {
findByRole(params) {
return this.store.query(this.getModelName(), {
role: id,
dc: dc,
ns: nspace,
role: params.id,
dc: params.dc,
ns: params.ns,
});
}
}

View File

@ -1,6 +1,7 @@
import { inject as service } from '@ember/service';
import RepositoryService from 'consul-ui/services/repository';
import { get, set } from '@ember/object';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'topology';
const ERROR_MESH_DISABLED = 'Connect must be enabled in order to use this endpoint';
@ -13,24 +14,19 @@ export default class TopologyService extends RepositoryService {
return modelName;
}
findBySlug(slug, kind, dc, nspace, configuration = {}) {
const datacenter = this.dcs.peekOne(dc);
@dataSource('/:ns/:dc/topology/:id/:kind')
findBySlug(params, configuration = {}) {
const datacenter = this.dcs.peekOne(params.dc);
if (datacenter !== null && !get(datacenter, 'MeshEnabled')) {
return Promise.resolve();
}
const query = {
dc: dc,
ns: nspace,
id: slug,
kind: kind,
};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.store.queryRecord(this.getModelName(), query).catch(e => {
return this.store.queryRecord(this.getModelName(), params).catch(e => {
const code = get(e, 'errors.firstObject.status');
const body = get(e, 'errors.firstObject.detail').trim();
const body = (get(e, 'errors.firstObject.detail') || '').trim();
switch (code) {
case '500':
if (datacenter !== null && body.endsWith(ERROR_MESH_DISABLED)) {

View File

@ -160,7 +160,8 @@
"tape": "^5.0.1",
"text-encoding": "^0.7.0",
"tippy.js": "^6.2.7",
"torii": "^0.10.1"
"torii": "^0.10.1",
"wayfarer": "^7.0.1"
},
"resolutions": {
"ember-basic-dropdown": "^3.0.10"

View File

@ -19,7 +19,7 @@ test('findByDatacenter returns the correct data for list endpoint', function(ass
});
},
function performTest(service) {
return service.findAllByDatacenter(dc);
return service.findAllByDatacenter({ dc });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -47,7 +47,7 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
return stub(`/v1/acl/info/${id}?dc=${dc}`);
},
function performTest(service) {
return service.findBySlug(id, dc);
return service.findBySlug({ id, dc });
},
function performAssertion(actual, expected) {
assert.deepEqual(

View File

@ -25,7 +25,10 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findAllByDatacenter(dc, nspace || undefinedNspace);
return service.findAllByDatacenter({
dc: dc,
nspace: nspace || undefinedNspace,
});
},
function performAssertion(actual, expected) {
assert.deepEqual(

View File

@ -24,7 +24,7 @@ test('findAllByDatacenter returns the correct data for list endpoint', function(
});
},
function performTest(service) {
return service.findAllByDatacenter(dc);
return service.findAllByDatacenter({ dc });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -51,11 +51,11 @@ test('findAllByNode calls findAllByDatacenter with the correct arguments', funct
cursor: 1,
};
const service = this.subject();
service.findAllByDatacenter = function(dc, configuration) {
service.findAllByDatacenter = function(params, configuration) {
assert.equal(arguments.length, 2, 'Expected to be called with the correct number of arguments');
assert.equal(dc, datacenter);
assert.equal(params.dc, datacenter);
assert.deepEqual(configuration, conf);
return Promise.resolve([]);
};
return service.findAllByNode('node-name', datacenter, conf);
return service.findAllByNode({ node: 'node-name', dc: datacenter }, conf);
});

View File

@ -18,7 +18,7 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
});
},
function performTest(service) {
return service.findBySlug(id, dc);
return service.findBySlug({ id, dc });
},
function performAssertion(actual, expected) {
const result = expected(function(payload) {

View File

@ -23,7 +23,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findAllBySlug(id, dc, nspace || undefinedNspace);
return service.findAllBySlug({ id, dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -51,7 +51,7 @@ const undefinedNspace = 'default';
return stub(`/v1/kv/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`);
},
function(service) {
return service.findBySlug(id, dc, nspace || undefinedNspace);
return service.findBySlug({ id, dc, ns: nspace || undefinedNspace });
},
function(actual, expected) {
assert.deepEqual(

View File

@ -25,7 +25,7 @@ test('findByDatacenter returns the correct data for list endpoint', function(ass
});
},
function performTest(service) {
return service.findAllByDatacenter(dc);
return service.findAllByDatacenter({ dc });
},
function performAssertion(actual, expected) {
actual.forEach(item => {
@ -44,7 +44,7 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
return stub(`/v1/internal/ui/node/${id}?dc=${dc}`);
},
function(service) {
return service.findBySlug(id, dc);
return service.findBySlug({ id, dc });
},
function(actual, expected) {
assert.equal(actual.uid, `["${nspace}","${dc}","${actual.ID}"]`);

View File

@ -29,7 +29,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findAllByDatacenter(dc, nspace || undefinedNspace);
return service.findAllByDatacenter({ dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -59,7 +59,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findBySlug(id, dc, nspace || undefinedNspace);
return service.findBySlug({ id, dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(

View File

@ -30,7 +30,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findAllByDatacenter(dc, nspace || undefinedNspace);
return service.findAllByDatacenter({ dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -61,7 +61,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findBySlug(id, dc, nspace || undefinedNspace);
return service.findBySlug({ id, dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(

View File

@ -33,7 +33,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findGatewayBySlug(gateway, dc, nspace || undefinedNspace, conf);
return service.findGatewayBySlug({ gateway, dc, ns: nspace || undefinedNspace }, conf);
},
function performAssertion(actual, expected) {
const result = expected(function(payload) {

View File

@ -29,7 +29,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findByNode(id, dc, nspace || undefinedNspace);
return service.findByNode({ id, dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -59,7 +59,7 @@ const undefinedNspace = 'default';
);
},
function(service) {
return service.findByKey(id, dc, nspace || undefinedNspace);
return service.findByKey({ id, dc, ns: nspace || undefinedNspace });
},
function(actual, expected) {
assert.deepEqual(

View File

@ -26,7 +26,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findAllByDatacenter(dc, nspace || undefinedNspace);
return service.findAllByDatacenter({ dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -57,7 +57,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findBySlug(id, dc, nspace || undefinedNspace);
return service.findBySlug({ id, dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -99,7 +99,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findByPolicy(policy, dc, nspace || undefinedNspace);
return service.findByPolicy({ id: policy, dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(
@ -136,7 +136,7 @@ const undefinedNspace = 'default';
);
},
function performTest(service) {
return service.findByRole(role, dc, nspace || undefinedNspace);
return service.findByRole({ id: role, dc, ns: nspace || undefinedNspace });
},
function performAssertion(actual, expected) {
assert.deepEqual(

View File

@ -19,7 +19,7 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
});
},
function performTest(service) {
return service.findBySlug(id, kind, dc);
return service.findBySlug({ id, kind, dc });
},
function performAssertion(actual, expected) {
const result = expected(function(payload) {

View File

@ -13365,6 +13365,11 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoassert@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-1.1.0.tgz#4f3152e09540fde28c76f44b19bbcd1d5a42478d"
integrity sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -17877,6 +17882,13 @@ watchpack@^1.5.0, watchpack@^1.7.4:
chokidar "^3.4.1"
watchpack-chokidar2 "^2.0.0"
wayfarer@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/wayfarer/-/wayfarer-7.0.1.tgz#17a64d351d49f9d3d6c508155867df7658184ce3"
integrity sha512-yf+kAlOYnJRjLxflLy+1+xEclb6222EAVvAjSY+Yz2qAIDrXeN5wLl/G302Mwv3E0KMg1HT/WDGsvSymX0U7Rw==
dependencies:
nanoassert "^1.1.0"
wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"