ui: Move routes to use data-sources (#8321)
* Add uri identifiers to all data source things and make them the same 1. Add uri identitifer to data-source service 2. Make <EventSource /> and <DataSource /> as close as possible 3. Add extra `.closed` method to get a list of inactive/closed/closing data-sources from elsewhere * Make the connections cleanup the least worst connection when required * Pass the uri/request id through all the things * Better user erroring * Make event sources close on error * Allow <DataLoader /> data slot to be configurable * Allow the <DataWriter /> removed state to be configurable * Don't error if meta is undefined * Stitch together all the repositories into the data-source/sink * Use data.source over repositories * Add missing <EventSource /> components * Fix up the views/templates * Disable all the old route based blocking query things * We still need the repo for the mixin for the moment * Don't default to default, default != ''
This commit is contained in:
parent
0b6a098aca
commit
8cb402eae7
|
@ -1,9 +1,10 @@
|
||||||
import Adapter from './application';
|
import Adapter from './application';
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { dc, index }) {
|
requestForQuery: function(request, { dc, index, uri }) {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/coordinate/nodes?${{ dc }}
|
GET /v1/coordinate/nodes?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{ index }}
|
${{ index }}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -2,12 +2,13 @@ import Adapter from './application';
|
||||||
|
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
requestForQueryRecord: function(request, { dc, ns, index, id, uri }) {
|
||||||
if (typeof id === 'undefined') {
|
if (typeof id === 'undefined') {
|
||||||
throw new Error('You must specify an id');
|
throw new Error('You must specify an id');
|
||||||
}
|
}
|
||||||
return request`
|
return request`
|
||||||
GET /v1/discovery-chain/${id}?${{ dc }}
|
GET /v1/discovery-chain/${id}?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
...this.formatNspace(ns),
|
...this.formatNspace(ns),
|
||||||
|
|
|
@ -6,9 +6,10 @@ import { SLUG_KEY } from 'consul-ui/models/intention';
|
||||||
|
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { dc, filter, index }) {
|
requestForQuery: function(request, { dc, filter, index, uri }) {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/connect/intentions?${{ dc }}
|
GET /v1/connect/intentions?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
index,
|
index,
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import Adapter from './application';
|
import Adapter from './application';
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { dc, index, id }) {
|
requestForQuery: function(request, { dc, index, id, uri }) {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/internal/ui/nodes?${{ dc }}
|
GET /v1/internal/ui/nodes?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{ index }}
|
${{ index }}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
requestForQueryRecord: function(request, { dc, index, id, uri }) {
|
||||||
if (typeof id === 'undefined') {
|
if (typeof id === 'undefined') {
|
||||||
throw new Error('You must specify an id');
|
throw new Error('You must specify an id');
|
||||||
}
|
}
|
||||||
return request`
|
return request`
|
||||||
GET /v1/internal/ui/node/${id}?${{ dc }}
|
GET /v1/internal/ui/node/${id}?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{ index }}
|
${{ index }}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -3,9 +3,10 @@ import { SLUG_KEY } from 'consul-ui/models/nspace';
|
||||||
|
|
||||||
// namespaces aren't categorized by datacenter, therefore no dc
|
// namespaces aren't categorized by datacenter, therefore no dc
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { index }) {
|
requestForQuery: function(request, { index, uri }) {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/namespaces
|
GET /v1/namespaces
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{ index }}
|
${{ index }}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -12,9 +12,10 @@ if (env('CONSUL_NSPACES_ENABLED')) {
|
||||||
}
|
}
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
env: service('env'),
|
env: service('env'),
|
||||||
requestForQuery: function(request, { dc, ns, index }) {
|
requestForQuery: function(request, { dc, ns, index, uri }) {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/internal/ui/oidc-auth-methods?${{ dc }}
|
GET /v1/internal/ui/oidc-auth-methods?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
index,
|
index,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import Adapter from './application';
|
import Adapter from './application';
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { dc, ns, index, id }) {
|
requestForQuery: function(request, { dc, ns, index, id, uri }) {
|
||||||
if (typeof id === 'undefined') {
|
if (typeof id === 'undefined') {
|
||||||
throw new Error('You must specify an id');
|
throw new Error('You must specify an id');
|
||||||
}
|
}
|
||||||
return request`
|
return request`
|
||||||
GET /v1/catalog/connect/${id}?${{ dc }}
|
GET /v1/catalog/connect/${id}?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
...this.formatNspace(ns),
|
...this.formatNspace(ns),
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import Adapter from './application';
|
import Adapter from './application';
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { dc, ns, index, gateway }) {
|
requestForQuery: function(request, { dc, ns, index, gateway, uri }) {
|
||||||
if (typeof gateway !== 'undefined') {
|
if (typeof gateway !== 'undefined') {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/internal/ui/gateway-services-nodes/${gateway}?${{ dc }}
|
GET /v1/internal/ui/gateway-services-nodes/${gateway}?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
...this.formatNspace(ns),
|
...this.formatNspace(ns),
|
||||||
|
@ -14,6 +15,7 @@ export default Adapter.extend({
|
||||||
} else {
|
} else {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/internal/ui/services?${{ dc }}
|
GET /v1/internal/ui/services?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
...this.formatNspace(ns),
|
...this.formatNspace(ns),
|
||||||
|
@ -22,12 +24,13 @@ export default Adapter.extend({
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
requestForQueryRecord: function(request, { dc, ns, index, id, uri }) {
|
||||||
if (typeof id === 'undefined') {
|
if (typeof id === 'undefined') {
|
||||||
throw new Error('You must specify an id');
|
throw new Error('You must specify an id');
|
||||||
}
|
}
|
||||||
return request`
|
return request`
|
||||||
GET /v1/health/service/${id}?${{ dc }}
|
GET /v1/health/service/${id}?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
...this.formatNspace(ns),
|
...this.formatNspace(ns),
|
||||||
|
|
|
@ -6,12 +6,13 @@ import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||||
|
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { dc, ns, index, id }) {
|
requestForQuery: function(request, { dc, ns, index, id, uri }) {
|
||||||
if (typeof id === 'undefined') {
|
if (typeof id === 'undefined') {
|
||||||
throw new Error('You must specify an id');
|
throw new Error('You must specify an id');
|
||||||
}
|
}
|
||||||
return request`
|
return request`
|
||||||
GET /v1/session/node/${id}?${{ dc }}
|
GET /v1/session/node/${id}?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
|
||||||
${{
|
${{
|
||||||
...this.formatNspace(ns),
|
...this.formatNspace(ns),
|
||||||
|
|
|
@ -9,23 +9,28 @@
|
||||||
{{#let (hash
|
{{#let (hash
|
||||||
data=data
|
data=data
|
||||||
error=error
|
error=error
|
||||||
|
dispatchError=(queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR"))
|
||||||
) as |api|}}
|
) as |api|}}
|
||||||
|
|
||||||
{{! if we didn't specify any data}}
|
{{#yield-slot name="data"}}
|
||||||
{{#if (not items)}}
|
{{yield api}}
|
||||||
{{! try and load the data if we aren't in an error state}}
|
{{else}}
|
||||||
<State @notMatches={{array "error" "disconnected"}}>
|
{{! if we didn't specify any data}}
|
||||||
{{! but only if we only asked for a single load and we are in loading state}}
|
{{#if (not items)}}
|
||||||
{{#if (or (not once) (state-matches state "loading"))}}
|
{{! try and load the data if we aren't in an error state}}
|
||||||
<DataSource
|
<State @notMatches={{array "error" "disconnected"}}>
|
||||||
@open={{open}}
|
{{! but only if we only asked for a single load and we are in loading state}}
|
||||||
@src={{src}}
|
{{#if (and src (or (not once) (state-matches state "loading")))}}
|
||||||
@onchange={{queue (action "change" value="data") (action dispatch "SUCCESS")}}
|
<DataSource
|
||||||
@onerror={{queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR")}}
|
@open={{open}}
|
||||||
/>
|
@src={{src}}
|
||||||
{{/if}}
|
@onchange={{queue (action "change" value="data") (action dispatch "SUCCESS")}}
|
||||||
</State>
|
@onerror={{api.dispatchError}}
|
||||||
{{/if}}
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</State>
|
||||||
|
{{/if}}
|
||||||
|
{{/yield-slot}}
|
||||||
|
|
||||||
<State @matches="loading">
|
<State @matches="loading">
|
||||||
{{#yield-slot name="loading"}}
|
{{#yield-slot name="loading"}}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default Component.extend(Slotted, {
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
isLoaded: function() {
|
isLoaded: function() {
|
||||||
return typeof this.items !== 'undefined';
|
return typeof this.items !== 'undefined' || typeof this.src === 'undefined';
|
||||||
},
|
},
|
||||||
change: function(data) {
|
change: function(data) {
|
||||||
set(this, 'data', this.onchange(data));
|
set(this, 'data', this.onchange(data));
|
||||||
|
|
|
@ -91,7 +91,10 @@ export default Component.extend({
|
||||||
);
|
);
|
||||||
const error = err => {
|
const error = err => {
|
||||||
try {
|
try {
|
||||||
this.onerror(err);
|
const error = get(err, 'error.errors.firstObject');
|
||||||
|
if (get(error || {}, 'status') !== '429') {
|
||||||
|
this.onerror(err);
|
||||||
|
}
|
||||||
this.logger.execute(err);
|
this.logger.execute(err);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.execute(err);
|
this.logger.execute(err);
|
||||||
|
@ -107,9 +110,7 @@ export default Component.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: e => {
|
error: e => {
|
||||||
if (get(e, 'error.errors.firstObject.status') !== '429') {
|
error(e);
|
||||||
error(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
replace(this, '_remove', remove);
|
replace(this, '_remove', remove);
|
||||||
|
|
|
@ -32,16 +32,16 @@
|
||||||
</State>
|
</State>
|
||||||
|
|
||||||
<State @matches="removed">
|
<State @matches="removed">
|
||||||
<Notification @after={{queue (action dispatch "RESET") (action ondelete)}}>
|
{{#yield-slot name="removed" params=(block-params (component 'notification' after=(queue (action dispatch "RESET") (action ondelete))))}}
|
||||||
{{#yield-slot name="removed"}}
|
{{yield api}}
|
||||||
{{yield api}}
|
{{else}}
|
||||||
{{else}}
|
<Notification @after={{queue (action dispatch "RESET") (action ondelete)}}>
|
||||||
<p data-notification role="alert" class="success notification-delete">
|
<p data-notification role="alert" class="success notification-delete">
|
||||||
<strong>Success!</strong>
|
<strong>Success!</strong>
|
||||||
Your {{type}} has been deleted.
|
Your {{type}} has been deleted.
|
||||||
</p>
|
</p>
|
||||||
{{/yield-slot}}
|
</Notification>
|
||||||
</Notification>
|
{{/yield-slot}}
|
||||||
</State>
|
</State>
|
||||||
|
|
||||||
<State @matches="persisted">
|
<State @matches="persisted">
|
||||||
|
|
3
ui-v2/app/components/event-source/index.hbs
Normal file
3
ui-v2/app/components/event-source/index.hbs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{{yield (hash
|
||||||
|
close=(action "close")
|
||||||
|
)}}
|
|
@ -1,10 +1,24 @@
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
import { get, set } from '@ember/object';
|
||||||
|
|
||||||
|
const replace = function(
|
||||||
|
obj,
|
||||||
|
prop,
|
||||||
|
value,
|
||||||
|
destroy = (prev = null, value) => (typeof prev === 'function' ? prev() : null)
|
||||||
|
) {
|
||||||
|
const prev = obj[prop];
|
||||||
|
if (prev !== value) {
|
||||||
|
destroy(prev, value);
|
||||||
|
}
|
||||||
|
return set(obj, prop, value);
|
||||||
|
};
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
tagName: '',
|
tagName: '',
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
logger: service('logger'),
|
logger: service('logger'),
|
||||||
|
data: service('data-source/service'),
|
||||||
closeOnDestroy: true,
|
closeOnDestroy: true,
|
||||||
onerror: function(e) {
|
onerror: function(e) {
|
||||||
this.logger.execute(e.error);
|
this.logger.execute(e.error);
|
||||||
|
@ -14,25 +28,64 @@ export default Component.extend({
|
||||||
this._listeners = this.dom.listeners();
|
this._listeners = this.dom.listeners();
|
||||||
},
|
},
|
||||||
willDestroyElement: function() {
|
willDestroyElement: function() {
|
||||||
if (this.closeOnDestroy && typeof (this.src || {}).close === 'function') {
|
if (this.closeOnDestroy) {
|
||||||
this.src.close();
|
this.actions.close.apply(this, []);
|
||||||
this.src.willDestroy();
|
|
||||||
}
|
}
|
||||||
this._listeners.remove();
|
this._listeners.remove();
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
},
|
},
|
||||||
didReceiveAttrs: function() {
|
didReceiveAttrs: function() {
|
||||||
this._listeners.remove();
|
this._super(...arguments);
|
||||||
if (typeof (this.src || {}).addEventListener === 'function') {
|
// only close and reopen if the uri changes
|
||||||
this._listeners.add(this.src, {
|
// otherwise this will fire whenever the proxies data changes
|
||||||
error: e => {
|
if (get(this, 'src.configuration.uri') !== get(this, 'source.configuration.uri')) {
|
||||||
try {
|
this.actions.open.apply(this, []);
|
||||||
this.onerror(e);
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.execute(e.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
open: function() {
|
||||||
|
replace(this, 'source', this.data.open(this.src, this), (prev, source) => {
|
||||||
|
// Makes sure any previous source (if different) is ALWAYS closed
|
||||||
|
if (typeof prev !== 'undefined') {
|
||||||
|
this.data.close(prev, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
replace(this, 'proxy', this.src, (prev, proxy) => {
|
||||||
|
// Makes sure any previous proxy (if different) is ALWAYS closed
|
||||||
|
if (typeof prev !== 'undefined') {
|
||||||
|
prev.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const error = err => {
|
||||||
|
try {
|
||||||
|
const error = get(err, 'error.errors.firstObject');
|
||||||
|
if (get(error || {}, 'status') !== '429') {
|
||||||
|
this.onerror(err);
|
||||||
|
}
|
||||||
|
this.logger.execute(err);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.execute(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// set up the listeners (which auto cleanup on component destruction)
|
||||||
|
// we only need errors here as this only uses proxies which
|
||||||
|
// automatically update their data
|
||||||
|
const remove = this._listeners.add(this.source, {
|
||||||
|
error: e => {
|
||||||
|
error(e);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
replace(this, '_remove', remove);
|
||||||
|
},
|
||||||
|
close: function() {
|
||||||
|
if (typeof this.source !== 'undefined') {
|
||||||
|
this.data.close(this.source, this);
|
||||||
|
replace(this, '_remove', undefined);
|
||||||
|
set(this, 'source', undefined);
|
||||||
|
}
|
||||||
|
if (typeof this.proxy !== 'undefined') {
|
||||||
|
this.proxy.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import Controller from '@ember/controller';
|
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import { get } from '@ember/object';
|
|
||||||
import { alias } from '@ember/object/computed';
|
|
||||||
|
|
||||||
export default Controller.extend({
|
|
||||||
dom: service('dom'),
|
|
||||||
notify: service('flashMessages'),
|
|
||||||
items: alias('item.Services'),
|
|
||||||
actions: {
|
|
||||||
error: function(e) {
|
|
||||||
if (e.target.readyState === 1) {
|
|
||||||
// OPEN
|
|
||||||
if (get(e, 'error.errors.firstObject.status') === '404') {
|
|
||||||
this.notify.add({
|
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
|
||||||
type: 'warning',
|
|
||||||
action: 'update',
|
|
||||||
});
|
|
||||||
[e.target, this.tomography, this.sessions].forEach(function(item) {
|
|
||||||
if (item && typeof item.close === 'function') {
|
|
||||||
item.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,9 +1,7 @@
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { alias } from '@ember/object/computed';
|
|
||||||
import { get, computed } from '@ember/object';
|
import { get, computed } from '@ember/object';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
items: alias('item.Services'),
|
|
||||||
queryParams: {
|
queryParams: {
|
||||||
search: {
|
search: {
|
||||||
as: 'filter',
|
as: 'filter',
|
||||||
|
|
9
ui-v2/app/helpers/uri.js
Normal file
9
ui-v2/app/helpers/uri.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import Helper from '@ember/component/helper';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
export default Helper.extend({
|
||||||
|
encoder: service('encoder'),
|
||||||
|
compute(params, hash) {
|
||||||
|
return this.encoder.uriJoin(params);
|
||||||
|
},
|
||||||
|
});
|
|
@ -4,20 +4,7 @@ export function initialize(container) {
|
||||||
if (env('CONSUL_UI_DISABLE_REALTIME')) {
|
if (env('CONSUL_UI_DISABLE_REALTIME')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
['node', 'coordinate', 'session', 'service', 'proxy', 'discovery-chain', 'intention']
|
[]
|
||||||
.concat(env('CONSUL_NSPACES_ENABLED') ? ['nspace/enabled'] : [])
|
|
||||||
.map(function(item) {
|
|
||||||
// create repositories that return a promise resolving to an EventSource
|
|
||||||
return {
|
|
||||||
service: `repository/${item}/event-source`,
|
|
||||||
extend: 'repository/type/event-source',
|
|
||||||
// Inject our original respository that is used by this class
|
|
||||||
// within the callable of the EventSource
|
|
||||||
services: {
|
|
||||||
content: `repository/${item}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.concat(
|
.concat(
|
||||||
['policy', 'role'].map(function(item) {
|
['policy', 'role'].map(function(item) {
|
||||||
// create repositories that return a promise resolving to an EventSource
|
// create repositories that return a promise resolving to an EventSource
|
||||||
|
@ -33,50 +20,6 @@ export function initialize(container) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.concat([
|
.concat([
|
||||||
// These are the routes where we overwrite the 'default'
|
|
||||||
// repo service. Default repos are repos that return a promise resolving to
|
|
||||||
// an ember-data record or recordset
|
|
||||||
{
|
|
||||||
route: 'dc/nodes/index',
|
|
||||||
services: {
|
|
||||||
repo: 'repository/node/event-source',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: 'dc/nodes/show',
|
|
||||||
services: {
|
|
||||||
repo: 'repository/node/event-source',
|
|
||||||
coordinateRepo: 'repository/coordinate/event-source',
|
|
||||||
sessionRepo: 'repository/session/event-source',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: 'dc/services/index',
|
|
||||||
services: {
|
|
||||||
repo: 'repository/service/event-source',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: 'dc/services/show',
|
|
||||||
services: {
|
|
||||||
repo: 'repository/service/event-source',
|
|
||||||
chainRepo: 'repository/discovery-chain/event-source',
|
|
||||||
intentionRepo: 'repository/intention/event-source',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: 'dc/services/instance',
|
|
||||||
services: {
|
|
||||||
repo: 'repository/service/event-source',
|
|
||||||
proxyRepo: 'repository/proxy/event-source',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: 'dc/intentions/index',
|
|
||||||
services: {
|
|
||||||
repo: 'repository/intention/event-source',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
service: 'form',
|
service: 'form',
|
||||||
services: {
|
services: {
|
||||||
|
@ -85,18 +28,6 @@ export function initialize(container) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.concat(
|
|
||||||
env('CONSUL_NSPACES_ENABLED')
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
route: 'dc/nspaces/index',
|
|
||||||
services: {
|
|
||||||
repo: 'repository/nspace/enabled/event-source',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
)
|
|
||||||
.forEach(function(definition) {
|
.forEach(function(definition) {
|
||||||
if (typeof definition.extend !== 'undefined') {
|
if (typeof definition.extend !== 'undefined') {
|
||||||
// Create the class instances that we need
|
// Create the class instances that we need
|
||||||
|
@ -111,9 +42,6 @@ export function initialize(container) {
|
||||||
// but hardcode this for the moment
|
// but hardcode this for the moment
|
||||||
if (typeof definition.route !== 'undefined') {
|
if (typeof definition.route !== 'undefined') {
|
||||||
container.inject(`route:${definition.route}`, name, `service:${servicePath}`);
|
container.inject(`route:${definition.route}`, name, `service:${servicePath}`);
|
||||||
if (env('CONSUL_NSPACES_ENABLED') && definition.route.startsWith('dc/')) {
|
|
||||||
container.inject(`route:nspace/${definition.route}`, name, `service:${servicePath}`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
container.inject(`service:${definition.service}`, name, `service:${servicePath}`);
|
container.inject(`service:${definition.service}`, name, `service:${servicePath}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { hash } from 'rsvp';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
repo: service('repository/node'),
|
repo: service('repository/node'),
|
||||||
|
data: service('data-source/service'),
|
||||||
queryParams: {
|
queryParams: {
|
||||||
search: {
|
search: {
|
||||||
as: 'filter',
|
as: 'filter',
|
||||||
|
@ -12,8 +13,9 @@ export default Route.extend({
|
||||||
},
|
},
|
||||||
model: function(params) {
|
model: function(params) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
|
const nspace = '*';
|
||||||
return hash({
|
return hash({
|
||||||
items: this.repo.findAllByDatacenter(dc, this.modelFor('nspace').nspace.substr(1)),
|
items: this.data.source(uri => uri`/${nspace}/${dc}/nodes`),
|
||||||
leader: this.repo.findByLeader(dc),
|
leader: this.repo.findByLeader(dc),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,20 +3,20 @@ import { inject as service } from '@ember/service';
|
||||||
import { hash } from 'rsvp';
|
import { hash } from 'rsvp';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
repo: service('repository/node'),
|
data: service('data-source/service'),
|
||||||
sessionRepo: service('repository/session'),
|
|
||||||
coordinateRepo: service('repository/coordinate'),
|
|
||||||
model: function(params) {
|
model: function(params) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||||
const name = params.name;
|
const name = params.name;
|
||||||
return hash({
|
return hash({
|
||||||
item: this.repo.findBySlug(name, dc, nspace),
|
dc: dc,
|
||||||
|
nspace: nspace,
|
||||||
|
item: this.data.source(uri => uri`/${nspace}/${dc}/node/${name}`),
|
||||||
}).then(model => {
|
}).then(model => {
|
||||||
return hash({
|
return hash({
|
||||||
...model,
|
...model,
|
||||||
sessions: this.sessionRepo.findByNode(name, dc, nspace),
|
tomography: this.data.source(uri => uri`/${nspace}/${dc}/coordinates/for-node/${name}`),
|
||||||
tomography: this.coordinateRepo.findAllByNode(name, dc),
|
sessions: this.data.source(uri => uri`/${nspace}/${dc}/sessions/for-node/${name}`),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { hash } from 'rsvp';
|
||||||
|
|
||||||
import WithNspaceActions from 'consul-ui/mixins/nspace/with-actions';
|
import WithNspaceActions from 'consul-ui/mixins/nspace/with-actions';
|
||||||
export default Route.extend(WithNspaceActions, {
|
export default Route.extend(WithNspaceActions, {
|
||||||
|
data: service('data-source/service'),
|
||||||
repo: service('repository/nspace'),
|
repo: service('repository/nspace'),
|
||||||
queryParams: {
|
queryParams: {
|
||||||
search: {
|
search: {
|
||||||
|
@ -13,7 +14,7 @@ export default Route.extend(WithNspaceActions, {
|
||||||
},
|
},
|
||||||
model: function(params) {
|
model: function(params) {
|
||||||
return hash({
|
return hash({
|
||||||
items: this.repo.findAll(),
|
items: this.data.source(uri => uri`/*/*/namespaces`),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { inject as service } from '@ember/service';
|
||||||
import { hash } from 'rsvp';
|
import { hash } from 'rsvp';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
repo: service('repository/service'),
|
data: service('data-source/service'),
|
||||||
queryParams: {
|
queryParams: {
|
||||||
search: {
|
search: {
|
||||||
as: 'filter',
|
as: 'filter',
|
||||||
|
@ -29,12 +29,13 @@ export default Route.extend({
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||||
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
return hash({
|
return hash({
|
||||||
|
nspace: nspace,
|
||||||
|
dc: dc,
|
||||||
terms: terms !== '' ? terms.split('\n') : [],
|
terms: terms !== '' ? terms.split('\n') : [],
|
||||||
items: this.repo.findAllByDatacenter(
|
items: this.data.source(uri => uri`/${nspace}/${dc}/services`),
|
||||||
this.modelFor('dc').dc.Name,
|
|
||||||
this.modelFor('nspace').nspace.substr(1)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
|
|
|
@ -4,38 +4,46 @@ import { hash } from 'rsvp';
|
||||||
import { get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
repo: service('repository/service'),
|
data: service('data-source/service'),
|
||||||
proxyRepo: service('repository/proxy'),
|
|
||||||
model: function(params) {
|
model: function(params) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
const nspace = this.modelFor('nspace').nspace.substr(1) || 'default';
|
||||||
return hash({
|
return hash({
|
||||||
dc: dc,
|
dc: dc,
|
||||||
nspace: nspace || 'default',
|
nspace: nspace,
|
||||||
item: this.repo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace),
|
item: this.data.source(
|
||||||
|
uri => uri`/${nspace}/${dc}/service-instance/${params.id}/${params.node}/${params.name}`
|
||||||
|
),
|
||||||
}).then(model => {
|
}).then(model => {
|
||||||
// this will not be run in a blocking loop, but this is ok as
|
// this will not be run in a blocking loop, but this is ok as
|
||||||
// its highly unlikely that a service will suddenly change to being a
|
// its highly unlikely that a service will suddenly change to being a
|
||||||
// connect-proxy or vice versa so leave as is for now
|
// connect-proxy or vice versa so leave as is for now
|
||||||
return hash({
|
return hash({
|
||||||
|
...model,
|
||||||
proxyMeta:
|
proxyMeta:
|
||||||
// proxies and mesh-gateways can't have proxies themselves so don't even look
|
// proxies and mesh-gateways can't have proxies themselves so don't even look
|
||||||
['connect-proxy', 'mesh-gateway'].includes(get(model.item, 'Kind'))
|
['connect-proxy', 'mesh-gateway'].includes(get(model.item, 'Kind'))
|
||||||
? null
|
? null
|
||||||
: this.proxyRepo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace),
|
: this.data.source(
|
||||||
...model,
|
uri =>
|
||||||
|
uri`/${nspace}/${dc}/proxy-instance/${params.id}/${params.node}/${params.name}`
|
||||||
|
),
|
||||||
}).then(model => {
|
}).then(model => {
|
||||||
if (typeof get(model, 'proxyMeta.ServiceID') === 'undefined') {
|
if (typeof get(model, 'proxyMeta.ServiceID') === 'undefined') {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
const proxyName = get(model, 'proxyMeta.ServiceName');
|
const proxy = {
|
||||||
const proxyID = get(model, 'proxyMeta.ServiceID');
|
id: get(model, 'proxyMeta.ServiceID'),
|
||||||
const proxyNode = get(model, 'proxyMeta.Node');
|
node: get(model, 'proxyMeta.Node'),
|
||||||
|
name: get(model, 'proxyMeta.ServiceName'),
|
||||||
|
};
|
||||||
return hash({
|
return hash({
|
||||||
|
...model,
|
||||||
// Proxies have identical dc/nspace as their parent instance
|
// Proxies have identical dc/nspace as their parent instance
|
||||||
// No need to use Proxy's dc/nspace response
|
// No need to use Proxy's dc/nspace response
|
||||||
proxy: this.repo.findInstanceBySlug(proxyID, proxyNode, proxyName, dc, nspace),
|
proxy: this.data.source(
|
||||||
...model,
|
uri => uri`/${nspace}/${dc}/service-instance/${proxy.id}/${proxy.node}/${proxy.name}`
|
||||||
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,9 +4,7 @@ import { hash } from 'rsvp';
|
||||||
import { get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
repo: service('repository/service'),
|
data: service('data-source/service'),
|
||||||
chainRepo: service('repository/discovery-chain'),
|
|
||||||
proxyRepo: service('repository/proxy'),
|
|
||||||
settings: service('settings'),
|
settings: service('settings'),
|
||||||
model: function(params, transition = {}) {
|
model: function(params, transition = {}) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
|
@ -14,8 +12,8 @@ export default Route.extend({
|
||||||
return hash({
|
return hash({
|
||||||
slug: params.name,
|
slug: params.name,
|
||||||
dc: dc,
|
dc: dc,
|
||||||
nspace: nspace || 'default',
|
nspace: nspace,
|
||||||
item: this.repo.findBySlug(params.name, dc, nspace),
|
item: this.data.source(uri => uri`/${nspace}/${dc}/service/${params.name}`),
|
||||||
urls: this.settings.findBySlug('urls'),
|
urls: this.settings.findBySlug('urls'),
|
||||||
proxies: [],
|
proxies: [],
|
||||||
})
|
})
|
||||||
|
@ -25,16 +23,20 @@ export default Route.extend({
|
||||||
)
|
)
|
||||||
? model
|
? model
|
||||||
: hash({
|
: hash({
|
||||||
chain: this.chainRepo.findBySlug(params.name, dc, nspace),
|
|
||||||
proxies: this.proxyRepo.findAllBySlug(params.name, dc, nspace),
|
|
||||||
...model,
|
...model,
|
||||||
|
chain: this.data.source(uri => uri`/${nspace}/${dc}/discovery-chain/${params.name}`),
|
||||||
|
proxies: this.data.source(
|
||||||
|
uri => uri`/${nspace}/${dc}/proxies/for-service/${params.name}`
|
||||||
|
),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(model => {
|
.then(model => {
|
||||||
return ['ingress-gateway', 'terminating-gateway'].includes(get(model, 'item.Service.Kind'))
|
return ['ingress-gateway', 'terminating-gateway'].includes(get(model, 'item.Service.Kind'))
|
||||||
? hash({
|
? hash({
|
||||||
gatewayServices: this.repo.findGatewayBySlug(params.name, dc, nspace),
|
|
||||||
...model,
|
...model,
|
||||||
|
gatewayServices: this.data.source(
|
||||||
|
uri => uri`/${nspace}/${dc}/gateways/for-service/${params.name}`
|
||||||
|
),
|
||||||
})
|
})
|
||||||
: model;
|
: model;
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,8 @@ import Service, { inject as service } from '@ember/service';
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
env: service('env'),
|
env: service('env'),
|
||||||
|
data: service('data-source/service'),
|
||||||
|
sources: service('repository/type/event-source'),
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this._listeners = this.dom.listeners();
|
this._listeners = this.dom.listeners();
|
||||||
|
@ -42,21 +44,41 @@ export default Service.extend({
|
||||||
}
|
}
|
||||||
return Promise.resolve(e);
|
return Promise.resolve(e);
|
||||||
},
|
},
|
||||||
purge: function() {
|
purge: function(statusCode = 0) {
|
||||||
[...this.connections].forEach(function(connection) {
|
[...this.connections].forEach(function(connection) {
|
||||||
// Cancelled
|
// Cancelled
|
||||||
connection.abort(0);
|
connection.abort(statusCode);
|
||||||
});
|
});
|
||||||
this.connections = new Set();
|
this.connections = new Set();
|
||||||
},
|
},
|
||||||
acquire: function(request) {
|
acquire: function(request) {
|
||||||
this.connections.add(request);
|
if (this.connections.size >= this.env.var('CONSUL_HTTP_MAX_CONNECTIONS')) {
|
||||||
if (this.connections.size > this.env.var('CONSUL_HTTP_MAX_CONNECTIONS')) {
|
const closed = this.data.closed();
|
||||||
const connection = this.connections.values().next().value;
|
let connection = [...this.connections].find(item => {
|
||||||
this.connections.delete(connection);
|
const id = item.headers()['x-request-id'];
|
||||||
// Too Many Requests
|
if (id) {
|
||||||
connection.abort(429);
|
return closed.includes(item.headers()['x-request-id']);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (typeof connection === 'undefined') {
|
||||||
|
// all connections are being used on the page
|
||||||
|
// if the new one is a blocking query then cancel the oldest connection
|
||||||
|
if (request.headers()['content-type'] === 'text/event-stream') {
|
||||||
|
connection = this.connections.values().next().value;
|
||||||
|
}
|
||||||
|
// otherwise wait for a connection to become available
|
||||||
|
}
|
||||||
|
// cancel the connection
|
||||||
|
if (typeof connection !== 'undefined') {
|
||||||
|
// if its a shared blocking query cancel everything
|
||||||
|
// listening to it
|
||||||
|
this.release(connection);
|
||||||
|
// Too Many Requests
|
||||||
|
connection.abort(429);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this.connections.add(request);
|
||||||
},
|
},
|
||||||
release: function(request) {
|
release: function(request) {
|
||||||
this.connections.delete(request);
|
this.connections.delete(request);
|
||||||
|
|
|
@ -68,7 +68,7 @@ const parseBody = function(strs, ...values) {
|
||||||
return [body, ...values];
|
return [body, ...values];
|
||||||
};
|
};
|
||||||
|
|
||||||
const CLIENT_HEADERS = [CACHE_CONTROL];
|
const CLIENT_HEADERS = [CACHE_CONTROL, 'X-Request-ID'];
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
connections: service('client/connections'),
|
connections: service('client/connections'),
|
||||||
|
|
|
@ -12,7 +12,10 @@ export default Service.extend({
|
||||||
return xhr(options);
|
return xhr(options);
|
||||||
},
|
},
|
||||||
request: function(params) {
|
request: function(params) {
|
||||||
const request = new Request(params.method, params.url, { body: params.data || {} });
|
const request = new Request(params.method, params.url, {
|
||||||
|
['x-request-id']: params.clientHeaders['x-request-id'],
|
||||||
|
body: params.data || {},
|
||||||
|
});
|
||||||
const options = {
|
const options = {
|
||||||
...params,
|
...params,
|
||||||
beforeSend: function(xhr) {
|
beforeSend: function(xhr) {
|
||||||
|
@ -51,6 +54,7 @@ export default Service.extend({
|
||||||
};
|
};
|
||||||
request.fetch = () => {
|
request.fetch = () => {
|
||||||
this.xhr(options);
|
this.xhr(options);
|
||||||
|
return request;
|
||||||
};
|
};
|
||||||
return request;
|
return request;
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { setProperties } from '@ember/object';
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
settings: service('settings'),
|
settings: service('settings'),
|
||||||
intention: service('repository/intention'),
|
intention: service('repository/intention'),
|
||||||
|
session: service('repository/session'),
|
||||||
prepare: function(sink, data, instance) {
|
prepare: function(sink, data, instance) {
|
||||||
return setProperties(instance, data);
|
return setProperties(instance, data);
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,17 @@ import { get } from '@ember/object';
|
||||||
|
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
datacenters: service('repository/dc'),
|
datacenters: service('repository/dc'),
|
||||||
|
nodes: service('repository/node'),
|
||||||
|
node: service('repository/node'),
|
||||||
|
gateways: service('repository/service'),
|
||||||
services: service('repository/service'),
|
services: service('repository/service'),
|
||||||
|
service: service('repository/service'),
|
||||||
|
['service-instance']: service('repository/service'),
|
||||||
|
proxies: service('repository/proxy'),
|
||||||
|
['proxy-instance']: service('repository/proxy'),
|
||||||
|
['discovery-chain']: service('repository/discovery-chain'),
|
||||||
|
coordinates: service('repository/coordinate'),
|
||||||
|
sessions: service('repository/session'),
|
||||||
namespaces: service('repository/nspace'),
|
namespaces: service('repository/nspace'),
|
||||||
intentions: service('repository/intention'),
|
intentions: service('repository/intention'),
|
||||||
intention: service('repository/intention'),
|
intention: service('repository/intention'),
|
||||||
|
@ -12,17 +22,15 @@ export default Service.extend({
|
||||||
policies: service('repository/policy'),
|
policies: service('repository/policy'),
|
||||||
policy: service('repository/policy'),
|
policy: service('repository/policy'),
|
||||||
roles: service('repository/role'),
|
roles: service('repository/role'),
|
||||||
|
|
||||||
oidc: service('repository/oidc-provider'),
|
oidc: service('repository/oidc-provider'),
|
||||||
|
|
||||||
type: service('data-source/protocols/http/blocking'),
|
type: service('data-source/protocols/http/blocking'),
|
||||||
|
|
||||||
source: function(src, configuration) {
|
source: function(src, configuration) {
|
||||||
// TODO: Consider adding/requiring nspace, dc, model, action, ...rest
|
// TODO: Consider adding/requiring 'action': nspace, dc, model, action, ...rest
|
||||||
const [, nspace, dc, model, ...rest] = src.split('/');
|
const [, nspace, dc, model, ...rest] = src.split('/').map(decodeURIComponent);
|
||||||
// TODO: Consider throwing if we have an empty nspace or dc
|
// nspaces can be filled, blank or *
|
||||||
// we are going to use '*' for 'all' when we need that
|
// so we might get urls like //dc/services
|
||||||
// and an empty value is the same as 'default'
|
|
||||||
// reasoning for potentially doing it here is, uri's should
|
|
||||||
// always be complete, they should never have things like '///model'
|
|
||||||
let find;
|
let find;
|
||||||
const repo = this[model];
|
const repo = this[model];
|
||||||
if (repo.shouldReconcile(src)) {
|
if (repo.shouldReconcile(src)) {
|
||||||
|
@ -41,32 +49,72 @@ export default Service.extend({
|
||||||
case 'namespaces':
|
case 'namespaces':
|
||||||
find = configuration => repo.findAll(configuration);
|
find = configuration => repo.findAll(configuration);
|
||||||
break;
|
break;
|
||||||
case 'token':
|
|
||||||
find = configuration => repo.self(rest[1], dc);
|
|
||||||
break;
|
|
||||||
case 'services':
|
case 'services':
|
||||||
|
case 'nodes':
|
||||||
case 'roles':
|
case 'roles':
|
||||||
case 'policies':
|
case 'policies':
|
||||||
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
|
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
|
||||||
break;
|
break;
|
||||||
case 'policy':
|
|
||||||
find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration);
|
|
||||||
break;
|
|
||||||
case 'intentions':
|
case 'intentions':
|
||||||
[method, ...slug] = rest;
|
[method, ...slug] = rest;
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'for-service':
|
case 'for-service':
|
||||||
// TODO: Are we going to need to encode/decode here...?
|
find = configuration => repo.findByService(slug, dc, nspace, configuration);
|
||||||
find = configuration => repo.findByService(slug.join('/'), dc, nspace, configuration);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
|
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
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 'service':
|
||||||
|
case 'discovery-chain':
|
||||||
|
case 'node':
|
||||||
|
find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration);
|
||||||
|
break;
|
||||||
|
case 'service-instance':
|
||||||
|
case 'proxy-instance':
|
||||||
|
// id, node, service
|
||||||
|
find = configuration =>
|
||||||
|
repo.findInstanceBySlug(rest[0], rest[1], rest[2], dc, nspace, configuration);
|
||||||
|
break;
|
||||||
|
case 'policy':
|
||||||
case 'intention':
|
case 'intention':
|
||||||
// TODO: Are we going to need to encode/decode here...?
|
slug = rest[0];
|
||||||
slug = rest.join('/');
|
|
||||||
if (slug) {
|
if (slug) {
|
||||||
find = configuration => repo.findBySlug(slug, dc, nspace, configuration);
|
find = configuration => repo.findBySlug(slug, dc, nspace, configuration);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default Service.extend({
|
||||||
return find(configuration)
|
return find(configuration)
|
||||||
.then(maybeCall(close, ifNotBlocking(this.settings)))
|
.then(maybeCall(close, ifNotBlocking(this.settings)))
|
||||||
.then(function(res) {
|
.then(function(res) {
|
||||||
if (typeof get(res, 'meta.cursor') === 'undefined') {
|
if (typeof get(res || {}, 'meta.cursor') === 'undefined') {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default Service.extend({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: src,
|
key: src,
|
||||||
|
uri: configuration.uri,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Service, { inject as service } from '@ember/service';
|
import Service, { inject as service } from '@ember/service';
|
||||||
|
import { proxy } from 'consul-ui/utils/dom/event-source';
|
||||||
|
|
||||||
import MultiMap from 'mnemonist/multi-map';
|
import MultiMap from 'mnemonist/multi-map';
|
||||||
|
|
||||||
|
@ -10,9 +11,9 @@ let cache = null;
|
||||||
let sources = null;
|
let sources = null;
|
||||||
// keeps a count of currently in use EventSources
|
// keeps a count of currently in use EventSources
|
||||||
let usage = null;
|
let usage = null;
|
||||||
|
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
|
encoder: service('encoder'),
|
||||||
consul: service('data-source/protocols/http'),
|
consul: service('data-source/protocols/http'),
|
||||||
settings: service('data-source/protocols/local-storage'),
|
settings: service('data-source/protocols/local-storage'),
|
||||||
|
|
||||||
|
@ -33,29 +34,68 @@ export default Service.extend({
|
||||||
});
|
});
|
||||||
cache = null;
|
cache = null;
|
||||||
sources = null;
|
sources = null;
|
||||||
|
usage.clear();
|
||||||
usage = null;
|
usage = null;
|
||||||
},
|
},
|
||||||
|
source: function(cb, attrs) {
|
||||||
|
const src = cb(this.encoder.uriTag());
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const ref = {};
|
||||||
|
const source = this.open(src, ref, true);
|
||||||
|
source.configuration.ref = ref;
|
||||||
|
const remove = this._listeners.add(source, {
|
||||||
|
message: e => {
|
||||||
|
remove();
|
||||||
|
// the source only gets wrapped in the proxy
|
||||||
|
// after the first message
|
||||||
|
// but the proxy itself is resolve to the route
|
||||||
|
resolve(proxy(e.target, e.data));
|
||||||
|
},
|
||||||
|
error: e => {
|
||||||
|
remove();
|
||||||
|
this.close(source, ref);
|
||||||
|
reject(e.error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (typeof source.getCurrentEvent() !== 'undefined') {
|
||||||
|
source.dispatchEvent(source.getCurrentEvent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unwrap: function(src, ref) {
|
||||||
|
const source = src._source;
|
||||||
|
usage.set(source, ref);
|
||||||
|
usage.remove(source, source.configuration.ref);
|
||||||
|
delete source.configuration.ref;
|
||||||
|
return source;
|
||||||
|
},
|
||||||
open: function(uri, ref, open = false) {
|
open: function(uri, ref, open = false) {
|
||||||
|
if (typeof uri !== 'string') {
|
||||||
|
return this.unwrap(uri, ref);
|
||||||
|
}
|
||||||
let source;
|
let source;
|
||||||
// Check the cache for an EventSource that is already being used
|
// Check the cache for an EventSource that is already being used
|
||||||
// for this uri. If we don't have one, set one up.
|
// for this uri. If we don't have one, set one up.
|
||||||
if (uri.indexOf('://') === -1) {
|
if (uri.indexOf('://') === -1) {
|
||||||
uri = `consul://${uri}`;
|
uri = `consul://${uri}`;
|
||||||
}
|
}
|
||||||
|
let [providerName, pathname] = uri.split('://');
|
||||||
|
const provider = this[providerName];
|
||||||
if (!sources.has(uri)) {
|
if (!sources.has(uri)) {
|
||||||
let [providerName, pathname] = uri.split('://');
|
|
||||||
const provider = this[providerName];
|
|
||||||
|
|
||||||
let configuration = {};
|
let configuration = {};
|
||||||
if (cache.has(uri)) {
|
if (cache.has(uri)) {
|
||||||
configuration = cache.get(uri);
|
configuration = cache.get(uri);
|
||||||
}
|
}
|
||||||
|
configuration.uri = uri;
|
||||||
source = provider.source(pathname, configuration);
|
source = provider.source(pathname, configuration);
|
||||||
this._listeners.add(source, {
|
const remove = this._listeners.add(source, {
|
||||||
close: e => {
|
close: e => {
|
||||||
|
// a close could be fired either by:
|
||||||
|
// 1. A non-blocking query leaving the page
|
||||||
|
// 2. A non-blocking query responding
|
||||||
|
// 3. A blocking query responding when is in a closing state
|
||||||
|
// 3. A non-blocking query or a blocking query being cancelled
|
||||||
const source = e.target;
|
const source = e.target;
|
||||||
source.removeEventListener('close', close);
|
|
||||||
const event = source.getCurrentEvent();
|
const event = source.getCurrentEvent();
|
||||||
const cursor = source.configuration.cursor;
|
const cursor = source.configuration.cursor;
|
||||||
// only cache data if we have any
|
// only cache data if we have any
|
||||||
|
@ -67,20 +107,25 @@ export default Service.extend({
|
||||||
}
|
}
|
||||||
// the data is cached delete the EventSource
|
// the data is cached delete the EventSource
|
||||||
if (!usage.has(source)) {
|
if (!usage.has(source)) {
|
||||||
|
// A non-blocking query could close but still be on the page
|
||||||
sources.delete(uri);
|
sources.delete(uri);
|
||||||
}
|
}
|
||||||
|
remove();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sources.set(uri, source);
|
sources.set(uri, source);
|
||||||
} else {
|
} else {
|
||||||
source = sources.get(uri);
|
source = sources.get(uri);
|
||||||
|
// bump to the end of the list
|
||||||
|
sources.delete(uri);
|
||||||
|
sources.set(uri, source);
|
||||||
}
|
}
|
||||||
// only open if its not already being used
|
// only open if its not already being used
|
||||||
// in the case of blocking queries being disabled
|
// in the case of blocking queries being disabled
|
||||||
// you may want to specifically force an open
|
// you may want to specifically force an open
|
||||||
// if blocking queries are enabled then opening an already
|
// if blocking queries are enabled then opening an already
|
||||||
// open blocking query does nothing
|
// open blocking query does nothing
|
||||||
if (!usage.has(source) || open) {
|
if (!usage.has(source) || source.readyState > 1 || open) {
|
||||||
source.open();
|
source.open();
|
||||||
}
|
}
|
||||||
// set/increase the usage counter
|
// set/increase the usage counter
|
||||||
|
@ -88,6 +133,8 @@ export default Service.extend({
|
||||||
return source;
|
return source;
|
||||||
},
|
},
|
||||||
close: function(source, ref) {
|
close: function(source, ref) {
|
||||||
|
// this close is called when the source has either left the page
|
||||||
|
// or in the case of a proxied source, it errors
|
||||||
if (source) {
|
if (source) {
|
||||||
// decrease the usage counter
|
// decrease the usage counter
|
||||||
usage.remove(source, ref);
|
usage.remove(source, ref);
|
||||||
|
@ -95,7 +142,22 @@ export default Service.extend({
|
||||||
// close it (data caching is dealt with by the above 'close' event listener)
|
// close it (data caching is dealt with by the above 'close' event listener)
|
||||||
if (!usage.has(source)) {
|
if (!usage.has(source)) {
|
||||||
source.close();
|
source.close();
|
||||||
|
if (source.readyState === 2) {
|
||||||
|
// in the case that a non-blocking query is on the page
|
||||||
|
// and it has already responded and has therefore been cached
|
||||||
|
// but not removed itself from sources
|
||||||
|
// delete from sources
|
||||||
|
sources.delete(source.configuration.uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
closed: function() {
|
||||||
|
// anything that is closed or closing
|
||||||
|
return [...sources.entries()]
|
||||||
|
.filter(([key, item]) => {
|
||||||
|
return item.readyState > 1;
|
||||||
|
})
|
||||||
|
.map(item => item[0]);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
27
ui-v2/app/services/encoder.js
Normal file
27
ui-v2/app/services/encoder.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import Service from '@ember/service';
|
||||||
|
import atob from 'consul-ui/utils/atob';
|
||||||
|
import btoa from 'consul-ui/utils/btoa';
|
||||||
|
|
||||||
|
export default Service.extend({
|
||||||
|
uriComponent: encodeURIComponent,
|
||||||
|
atob: function() {
|
||||||
|
return atob(...arguments);
|
||||||
|
},
|
||||||
|
btoa: function() {
|
||||||
|
return btoa(...arguments);
|
||||||
|
},
|
||||||
|
uriJoin: function() {
|
||||||
|
return this.joiner(this.uriComponent, '/', '')(...arguments);
|
||||||
|
},
|
||||||
|
uriTag: function() {
|
||||||
|
return this.tag(this.uriJoin.bind(this));
|
||||||
|
},
|
||||||
|
joiner: (encoder, joiner = '', defaultValue = '') => (values, strs) =>
|
||||||
|
(strs || Array(values.length).fill(joiner)).reduce(
|
||||||
|
(prev, item, i) => `${prev}${item}${encoder(values[i] || defaultValue)}`,
|
||||||
|
''
|
||||||
|
),
|
||||||
|
tag: function(join) {
|
||||||
|
return (strs, ...values) => join(values, strs);
|
||||||
|
},
|
||||||
|
});
|
|
@ -49,6 +49,7 @@ export default Service.extend({
|
||||||
};
|
};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), query);
|
return this.store.query(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
@ -60,6 +61,7 @@ export default Service.extend({
|
||||||
};
|
};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.queryRecord(this.getModelName(), query);
|
return this.store.queryRecord(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default RepositoryService.extend({
|
||||||
};
|
};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), query);
|
return this.store.query(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
@ -25,7 +26,10 @@ export default RepositoryService.extend({
|
||||||
return this.findAllByDatacenter(dc, configuration).then(function(coordinates) {
|
return this.findAllByDatacenter(dc, configuration).then(function(coordinates) {
|
||||||
let results = {};
|
let results = {};
|
||||||
if (get(coordinates, 'length') > 1) {
|
if (get(coordinates, 'length') > 1) {
|
||||||
results = tomography(node, coordinates.map(item => get(item, 'data')));
|
results = tomography(
|
||||||
|
node,
|
||||||
|
coordinates.map(item => get(item, 'data'))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
results.meta = get(coordinates, 'meta');
|
results.meta = get(coordinates, 'meta');
|
||||||
return results;
|
return results;
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default RepositoryService.extend({
|
||||||
};
|
};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), {
|
return this.store.query(this.getModelName(), {
|
||||||
...query,
|
...query,
|
||||||
|
|
|
@ -16,6 +16,7 @@ export default RepositoryService.extend({
|
||||||
const query = {};
|
const query = {};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), query);
|
return this.store.query(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,6 +21,7 @@ export default RepositoryService.extend({
|
||||||
const query = {};
|
const query = {};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), query);
|
return this.store.query(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,7 @@ export default RepositoryService.extend({
|
||||||
};
|
};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), query);
|
return this.store.query(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
|
|
@ -84,6 +84,7 @@ export default RepositoryService.extend({
|
||||||
};
|
};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), query);
|
return this.store.query(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,6 +15,7 @@ export default RepositoryService.extend({
|
||||||
};
|
};
|
||||||
if (typeof configuration.cursor !== 'undefined') {
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
query.index = configuration.cursor;
|
query.index = configuration.cursor;
|
||||||
|
query.uri = configuration.uri;
|
||||||
}
|
}
|
||||||
return this.store.query(this.getModelName(), query);
|
return this.store.query(this.getModelName(), query);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,39 +1,70 @@
|
||||||
{{title item.Node}}
|
{{title item.Node}}
|
||||||
<EventSource @src={{item}} @onerror={{action "error"}} />
|
<DataLoader as |api|>
|
||||||
<EventSource @src={{sessions}} />
|
|
||||||
<EventSource @src={{tomography}} />
|
<BlockSlot @name="data">
|
||||||
<AppView @class="node show">
|
<EventSource @src={{sessions}} />
|
||||||
<BlockSlot @name="notification" as |status type|>
|
<EventSource @src={{tomography}} />
|
||||||
{{!TODO: Move sessions to its own folder within nodes }}
|
<EventSource @src={{item}} @onerror={{queue (action api.dispatchError)}} />
|
||||||
{{partial 'dc/nodes/notifications'}}
|
</BlockSlot>
|
||||||
</BlockSlot>
|
|
||||||
<BlockSlot @name="breadcrumbs">
|
<BlockSlot @name="error">
|
||||||
<ol>
|
<AppError @error={{api.error}} />
|
||||||
<li><a data-test-back href={{href-to 'dc.nodes'}}>All Nodes</a></li>
|
</BlockSlot>
|
||||||
</ol>
|
|
||||||
</BlockSlot>
|
<BlockSlot @name="disconnected" as |Notification|>
|
||||||
<BlockSlot @name="header">
|
{{#if (eq api.error.status "404")}}
|
||||||
<h1>
|
<Notification @sticky={{true}}>
|
||||||
{{ item.Node }}
|
<p data-notification role="alert" class="warning notification-update">
|
||||||
</h1>
|
<strong>Warning!</strong>
|
||||||
<label for="toolbar-toggle"></label>
|
This node no longer exists in the catalog.
|
||||||
</BlockSlot>
|
</p>
|
||||||
<BlockSlot @name="nav">
|
</Notification>
|
||||||
<TabNav @items={{
|
{{else}}
|
||||||
compact
|
<Notification @sticky={{true}}>
|
||||||
(array
|
<p data-notification role="alert" class="warning notification-update">
|
||||||
(hash label="Health Checks" href=(href-to "dc.nodes.show.healthchecks") selected=(is-href "dc.nodes.show.healthchecks"))
|
<strong>Warning!</strong>
|
||||||
(hash label="Service Instances" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
|
An error was returned whilst loading this data, refresh to try again.
|
||||||
(if tomography.distances (hash label="Round Trip Time" href=(href-to "dc.nodes.show.rtt") selected=(is-href "dc.nodes.show.rtt")) '')
|
</p>
|
||||||
(hash label="Lock Sessions" href=(href-to "dc.nodes.show.sessions") selected=(is-href "dc.nodes.show.sessions"))
|
</Notification>
|
||||||
(hash label="Metadata" href=(href-to "dc.nodes.show.metadata") selected=(is-href "dc.nodes.show.metadata"))
|
{{/if}}
|
||||||
)
|
</BlockSlot>
|
||||||
}}/>
|
|
||||||
</BlockSlot>
|
<BlockSlot @name="loaded">
|
||||||
<BlockSlot @name="actions">
|
<AppView @class="node show">
|
||||||
<CopyButton @value={{item.Address}} @name="Address">{{item.Address}}</CopyButton>
|
<BlockSlot @name="notification" as |status type|>
|
||||||
</BlockSlot>
|
{{!TODO: Move sessions to its own folder within nodes }}
|
||||||
<BlockSlot @name="content">
|
{{partial 'dc/nodes/notifications'}}
|
||||||
{{outlet}}
|
</BlockSlot>
|
||||||
</BlockSlot>
|
<BlockSlot @name="breadcrumbs">
|
||||||
</AppView>
|
<ol>
|
||||||
|
<li><a data-test-back href={{href-to 'dc.nodes'}}>All Nodes</a></li>
|
||||||
|
</ol>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="header">
|
||||||
|
<h1>
|
||||||
|
{{ item.Node }}
|
||||||
|
</h1>
|
||||||
|
<label for="toolbar-toggle"></label>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="nav">
|
||||||
|
<TabNav @items={{
|
||||||
|
compact
|
||||||
|
(array
|
||||||
|
(hash label="Health Checks" href=(href-to "dc.nodes.show.healthchecks") selected=(is-href "dc.nodes.show.healthchecks"))
|
||||||
|
(hash label="Service Instances" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
|
||||||
|
(if tomography.distances (hash label="Round Trip Time" href=(href-to "dc.nodes.show.rtt") selected=(is-href "dc.nodes.show.rtt")) '')
|
||||||
|
(hash label="Lock Sessions" href=(href-to "dc.nodes.show.sessions") selected=(is-href "dc.nodes.show.sessions"))
|
||||||
|
(hash label="Metadata" href=(href-to "dc.nodes.show.metadata") selected=(is-href "dc.nodes.show.metadata"))
|
||||||
|
)
|
||||||
|
}}/>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="actions">
|
||||||
|
<CopyButton @value={{item.Address}} @name="Address">{{item.Address}}</CopyButton>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="content">
|
||||||
|
{{outlet}}
|
||||||
|
</BlockSlot>
|
||||||
|
</AppView>
|
||||||
|
|
||||||
|
</BlockSlot>
|
||||||
|
</DataLoader>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{{#let item.Services as |items|}}
|
||||||
<div id="services" class="tab-section">
|
<div id="services" class="tab-section">
|
||||||
<div role="tabpanel">
|
<div role="tabpanel">
|
||||||
{{#if (gt items.length 0) }}
|
{{#if (gt items.length 0) }}
|
||||||
|
@ -24,3 +25,4 @@
|
||||||
</ChangeableSet>
|
</ChangeableSet>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{/let}}
|
|
@ -39,14 +39,14 @@
|
||||||
<td>
|
<td>
|
||||||
<ConfirmationDialog @message="Are you sure you want to invalidate this session?">
|
<ConfirmationDialog @message="Are you sure you want to invalidate this session?">
|
||||||
<BlockSlot @name="action" as |confirm|>
|
<BlockSlot @name="action" as |confirm|>
|
||||||
<button data-test-delete type="button" class="type-delete" {{action confirm 'invalidateSession' item}}>Invalidate</button>
|
<button data-test-delete type="button" class="type-delete" onclick={{queue (action confirm 'invalidateSession' item) (refresh-route)}}>Invalidate</button>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="dialog" as |execute cancel message|>
|
<BlockSlot @name="dialog" as |execute cancel message|>
|
||||||
<p>
|
<p>
|
||||||
{{message}}
|
{{message}}
|
||||||
</p>
|
</p>
|
||||||
<button type="button" class="type-delete" {{action execute}}>Confirm Invalidate</button>
|
<button type="button" class="type-delete" {{action execute}}>Confirm Invalidate</button>
|
||||||
<button type="button" class="type-cancel" {{ action cancel}}>Cancel</button>
|
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{{title item.ID}}
|
{{title item.ID}}
|
||||||
<EventSource @src={{item}} @onerror={{action "error"}} />
|
<EventSource @src={{item}} @onerror={{action "error"}} />
|
||||||
<EventSource @src={{proxy}} />
|
<EventSource @src={{proxy}} />
|
||||||
|
<EventSource @src={{proxyMeta}} />
|
||||||
<AppView @class="instance show">
|
<AppView @class="instance show">
|
||||||
<BlockSlot @name="notification" as |status type|>
|
<BlockSlot @name="notification" as |status type|>
|
||||||
{{partial 'dc/services/notifications'}}
|
{{partial 'dc/services/notifications'}}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
<EventSource @src={{item}} @onerror={{action "error"}} />
|
<EventSource @src={{item}} @onerror={{action "error"}} />
|
||||||
<EventSource @src={{chain}} />
|
<EventSource @src={{chain}} />
|
||||||
<EventSource @src={{intentions}} />
|
<EventSource @src={{intentions}} />
|
||||||
|
<EventSource @src={{proxies}} />
|
||||||
|
<EventSource @src={{gatewayServices}} />
|
||||||
<AppView @class="service show">
|
<AppView @class="service show">
|
||||||
<BlockSlot @name="notification" as |status type|>
|
<BlockSlot @name="notification" as |status type|>
|
||||||
{{partial 'dc/services/notifications'}}
|
{{partial 'dc/services/notifications'}}
|
||||||
|
|
|
@ -57,6 +57,7 @@ export default function(
|
||||||
// close after the dispatch so we can tell if it was an error whilst closed or not
|
// close after the dispatch so we can tell if it was an error whilst closed or not
|
||||||
// but make sure its before the promise tick
|
// but make sure its before the promise tick
|
||||||
this.readyState = 2; // CLOSE
|
this.readyState = 2; // CLOSE
|
||||||
|
this.dispatchEvent({ type: 'close' });
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// This only gets called when the promise chain completely finishes
|
// This only gets called when the promise chain completely finishes
|
||||||
|
|
|
@ -7,10 +7,10 @@ module('Integration | Adapter | coordinate', function(hooks) {
|
||||||
const adapter = this.owner.lookup('adapter:coordinate');
|
const adapter = this.owner.lookup('adapter:coordinate');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `GET /v1/coordinate/nodes?dc=${dc}`;
|
const expected = `GET /v1/coordinate/nodes?dc=${dc}`;
|
||||||
const actual = adapter.requestForQuery(client.url, {
|
const actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||||
dc: dc,
|
dc: dc,
|
||||||
});
|
});
|
||||||
assert.equal(actual, expected);
|
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||||
});
|
});
|
||||||
test('requestForQuery returns the correct body', function(assert) {
|
test('requestForQuery returns the correct body', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:coordinate');
|
const adapter = this.owner.lookup('adapter:coordinate');
|
||||||
|
|
|
@ -11,11 +11,11 @@ module('Integration | Adapter | discovery-chain', function(hooks) {
|
||||||
const adapter = this.owner.lookup('adapter:discovery-chain');
|
const adapter = this.owner.lookup('adapter:discovery-chain');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `GET /v1/discovery-chain/${id}?dc=${dc}`;
|
const expected = `GET /v1/discovery-chain/${id}?dc=${dc}`;
|
||||||
const actual = adapter.requestForQueryRecord(client.url, {
|
const actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||||
dc: dc,
|
dc: dc,
|
||||||
id: id,
|
id: id,
|
||||||
});
|
});
|
||||||
assert.equal(actual, expected);
|
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||||
});
|
});
|
||||||
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
|
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:discovery-chain');
|
const adapter = this.owner.lookup('adapter:discovery-chain');
|
||||||
|
|
|
@ -8,10 +8,10 @@ module('Integration | Adapter | intention', function(hooks) {
|
||||||
const adapter = this.owner.lookup('adapter:intention');
|
const adapter = this.owner.lookup('adapter:intention');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `GET /v1/connect/intentions?dc=${dc}`;
|
const expected = `GET /v1/connect/intentions?dc=${dc}`;
|
||||||
const actual = adapter.requestForQuery(client.url, {
|
const actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||||
dc: dc,
|
dc: dc,
|
||||||
});
|
});
|
||||||
assert.equal(actual, expected);
|
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||||
});
|
});
|
||||||
test('requestForQueryRecord returns the correct url', function(assert) {
|
test('requestForQueryRecord returns the correct url', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:intention');
|
const adapter = this.owner.lookup('adapter:intention');
|
||||||
|
|
|
@ -8,20 +8,20 @@ module('Integration | Adapter | node', function(hooks) {
|
||||||
const adapter = this.owner.lookup('adapter:node');
|
const adapter = this.owner.lookup('adapter:node');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `GET /v1/internal/ui/nodes?dc=${dc}`;
|
const expected = `GET /v1/internal/ui/nodes?dc=${dc}`;
|
||||||
const actual = adapter.requestForQuery(client.url, {
|
const actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||||
dc: dc,
|
dc: dc,
|
||||||
});
|
});
|
||||||
assert.equal(actual, expected);
|
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||||
});
|
});
|
||||||
test('requestForQueryRecord returns the correct url', function(assert) {
|
test('requestForQueryRecord returns the correct url', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:node');
|
const adapter = this.owner.lookup('adapter:node');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `GET /v1/internal/ui/node/${id}?dc=${dc}`;
|
const expected = `GET /v1/internal/ui/node/${id}?dc=${dc}`;
|
||||||
const actual = adapter.requestForQueryRecord(client.url, {
|
const actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||||
dc: dc,
|
dc: dc,
|
||||||
id: id,
|
id: id,
|
||||||
});
|
});
|
||||||
assert.equal(actual, expected);
|
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||||
});
|
});
|
||||||
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
|
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:node');
|
const adapter = this.owner.lookup('adapter:node');
|
||||||
|
|
|
@ -9,8 +9,8 @@ module('Integration | Adapter | nspace', function(hooks) {
|
||||||
const adapter = this.owner.lookup('adapter:nspace');
|
const adapter = this.owner.lookup('adapter:nspace');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `GET /v1/namespaces`;
|
const expected = `GET /v1/namespaces`;
|
||||||
const actual = adapter.requestForQuery(client.url, {});
|
const actual = adapter.requestForQuery(client.requestParams.bind(client), {});
|
||||||
assert.equal(actual, expected);
|
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||||
});
|
});
|
||||||
test('requestForQueryRecord returns the correct url/method', function(assert) {
|
test('requestForQueryRecord returns the correct url/method', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:nspace');
|
const adapter = this.owner.lookup('adapter:nspace');
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
import { setupTest } from 'ember-qunit';
|
||||||
|
|
||||||
module('Unit | Controller | dc/nodes/show', function(hooks) {
|
module('Unit | Service | encoder', function(hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
|
|
||||||
// Replace this with your real tests.
|
// Replace this with your real tests.
|
||||||
test('it exists', function(assert) {
|
test('it exists', function(assert) {
|
||||||
let controller = this.owner.lookup('controller:dc/nodes/show');
|
let service = this.owner.lookup('service:encoder');
|
||||||
assert.ok(controller);
|
assert.ok(service);
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
Reference in a new issue