From 8cb402eae7497df7f056ec9ebbfc9d1e2d86752f Mon Sep 17 00:00:00 2001 From: John Cowen Date: Fri, 17 Jul 2020 14:42:45 +0100 Subject: [PATCH] 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 and 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 data slot to be configurable * Allow the 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 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 != '' --- ui-v2/app/adapters/coordinate.js | 3 +- ui-v2/app/adapters/discovery-chain.js | 3 +- ui-v2/app/adapters/intention.js | 3 +- ui-v2/app/adapters/node.js | 6 +- ui-v2/app/adapters/nspace.js | 3 +- ui-v2/app/adapters/oidc-provider.js | 3 +- ui-v2/app/adapters/proxy.js | 3 +- ui-v2/app/adapters/service.js | 7 +- ui-v2/app/adapters/session.js | 3 +- ui-v2/app/components/data-loader/index.hbs | 35 +++--- ui-v2/app/components/data-loader/index.js | 2 +- ui-v2/app/components/data-source/index.js | 9 +- ui-v2/app/components/data-writer/index.hbs | 12 +- ui-v2/app/components/event-source/index.hbs | 3 + ui-v2/app/components/event-source/index.js | 81 ++++++++++--- ui-v2/app/controllers/dc/nodes/show.js | 30 ----- .../app/controllers/dc/nodes/show/services.js | 2 - ui-v2/app/helpers/uri.js | 9 ++ .../app/instance-initializers/event-source.js | 74 +----------- ui-v2/app/routes/dc/nodes/index.js | 4 +- ui-v2/app/routes/dc/nodes/show.js | 12 +- ui-v2/app/routes/dc/nspaces/index.js | 3 +- ui-v2/app/routes/dc/services/index.js | 11 +- ui-v2/app/routes/dc/services/instance.js | 32 ++++-- ui-v2/app/routes/dc/services/show.js | 18 +-- ui-v2/app/services/client/connections.js | 38 +++++-- ui-v2/app/services/client/http.js | 2 +- ui-v2/app/services/client/transports/xhr.js | 6 +- .../app/services/data-sink/protocols/http.js | 1 + .../services/data-source/protocols/http.js | 84 +++++++++++--- .../data-source/protocols/http/blocking.js | 2 +- .../data-source/protocols/local-storage.js | 1 + ui-v2/app/services/data-source/service.js | 78 +++++++++++-- ui-v2/app/services/encoder.js | 27 +++++ ui-v2/app/services/repository.js | 2 + ui-v2/app/services/repository/coordinate.js | 6 +- ui-v2/app/services/repository/intention.js | 1 + ui-v2/app/services/repository/nspace.js | 1 + .../app/services/repository/nspace/enabled.js | 1 + ui-v2/app/services/repository/proxy.js | 1 + ui-v2/app/services/repository/service.js | 1 + ui-v2/app/services/repository/session.js | 1 + ui-v2/app/templates/dc/nodes/show.hbs | 107 +++++++++++------- .../app/templates/dc/nodes/show/services.hbs | 4 +- .../app/templates/dc/nodes/show/sessions.hbs | 4 +- ui-v2/app/templates/dc/services/instance.hbs | 1 + ui-v2/app/templates/dc/services/show.hbs | 2 + ui-v2/app/utils/dom/event-source/callable.js | 1 + .../integration/adapters/coordinate-test.js | 4 +- .../adapters/discovery-chain-test.js | 4 +- .../integration/adapters/intention-test.js | 4 +- ui-v2/tests/integration/adapters/node-test.js | 8 +- .../tests/integration/adapters/nspace-test.js | 4 +- .../show-test.js => services/encoder-test.js} | 6 +- 54 files changed, 490 insertions(+), 283 deletions(-) create mode 100644 ui-v2/app/components/event-source/index.hbs delete mode 100644 ui-v2/app/controllers/dc/nodes/show.js create mode 100644 ui-v2/app/helpers/uri.js create mode 100644 ui-v2/app/services/encoder.js rename ui-v2/tests/unit/{controllers/dc/nodes/show-test.js => services/encoder-test.js} (54%) diff --git a/ui-v2/app/adapters/coordinate.js b/ui-v2/app/adapters/coordinate.js index a9cdb1398..7bdb2bb20 100644 --- a/ui-v2/app/adapters/coordinate.js +++ b/ui-v2/app/adapters/coordinate.js @@ -1,9 +1,10 @@ import Adapter from './application'; // TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, index }) { + requestForQuery: function(request, { dc, index, uri }) { return request` GET /v1/coordinate/nodes?${{ dc }} + X-Request-ID: ${uri} ${{ index }} `; diff --git a/ui-v2/app/adapters/discovery-chain.js b/ui-v2/app/adapters/discovery-chain.js index 1f1efa0a9..588fd4d67 100644 --- a/ui-v2/app/adapters/discovery-chain.js +++ b/ui-v2/app/adapters/discovery-chain.js @@ -2,12 +2,13 @@ import Adapter from './application'; // TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQueryRecord: function(request, { dc, ns, index, id }) { + requestForQueryRecord: function(request, { dc, ns, index, id, uri }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/discovery-chain/${id}?${{ dc }} + X-Request-ID: ${uri} ${{ ...this.formatNspace(ns), diff --git a/ui-v2/app/adapters/intention.js b/ui-v2/app/adapters/intention.js index 310a9f5c7..73fe3d25f 100644 --- a/ui-v2/app/adapters/intention.js +++ b/ui-v2/app/adapters/intention.js @@ -6,9 +6,10 @@ import { SLUG_KEY } from 'consul-ui/models/intention'; // TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, filter, index }) { + requestForQuery: function(request, { dc, filter, index, uri }) { return request` GET /v1/connect/intentions?${{ dc }} + X-Request-ID: ${uri} ${{ index, diff --git a/ui-v2/app/adapters/node.js b/ui-v2/app/adapters/node.js index 1dffcce4b..4a31dc7b6 100644 --- a/ui-v2/app/adapters/node.js +++ b/ui-v2/app/adapters/node.js @@ -1,19 +1,21 @@ import Adapter from './application'; // TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, index, id }) { + requestForQuery: function(request, { dc, index, id, uri }) { return request` GET /v1/internal/ui/nodes?${{ dc }} + X-Request-ID: ${uri} ${{ index }} `; }, - requestForQueryRecord: function(request, { dc, index, id }) { + requestForQueryRecord: function(request, { dc, index, id, uri }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/internal/ui/node/${id}?${{ dc }} + X-Request-ID: ${uri} ${{ index }} `; diff --git a/ui-v2/app/adapters/nspace.js b/ui-v2/app/adapters/nspace.js index be25fbd19..a16dce864 100644 --- a/ui-v2/app/adapters/nspace.js +++ b/ui-v2/app/adapters/nspace.js @@ -3,9 +3,10 @@ import { SLUG_KEY } from 'consul-ui/models/nspace'; // namespaces aren't categorized by datacenter, therefore no dc export default Adapter.extend({ - requestForQuery: function(request, { index }) { + requestForQuery: function(request, { index, uri }) { return request` GET /v1/namespaces + X-Request-ID: ${uri} ${{ index }} `; diff --git a/ui-v2/app/adapters/oidc-provider.js b/ui-v2/app/adapters/oidc-provider.js index 0cbeb73ab..55251b6c9 100644 --- a/ui-v2/app/adapters/oidc-provider.js +++ b/ui-v2/app/adapters/oidc-provider.js @@ -12,9 +12,10 @@ if (env('CONSUL_NSPACES_ENABLED')) { } export default Adapter.extend({ env: service('env'), - requestForQuery: function(request, { dc, ns, index }) { + requestForQuery: function(request, { dc, ns, index, uri }) { return request` GET /v1/internal/ui/oidc-auth-methods?${{ dc }} + X-Request-ID: ${uri} ${{ index, diff --git a/ui-v2/app/adapters/proxy.js b/ui-v2/app/adapters/proxy.js index 7cd999cbf..bae20b3af 100644 --- a/ui-v2/app/adapters/proxy.js +++ b/ui-v2/app/adapters/proxy.js @@ -1,12 +1,13 @@ import Adapter from './application'; // TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, ns, index, id }) { + requestForQuery: function(request, { dc, ns, index, id, uri }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/catalog/connect/${id}?${{ dc }} + X-Request-ID: ${uri} ${{ ...this.formatNspace(ns), diff --git a/ui-v2/app/adapters/service.js b/ui-v2/app/adapters/service.js index dd079aaaa..b9cced90b 100644 --- a/ui-v2/app/adapters/service.js +++ b/ui-v2/app/adapters/service.js @@ -1,10 +1,11 @@ import Adapter from './application'; // TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, ns, index, gateway }) { + requestForQuery: function(request, { dc, ns, index, gateway, uri }) { if (typeof gateway !== 'undefined') { return request` GET /v1/internal/ui/gateway-services-nodes/${gateway}?${{ dc }} + X-Request-ID: ${uri} ${{ ...this.formatNspace(ns), @@ -14,6 +15,7 @@ export default Adapter.extend({ } else { return request` GET /v1/internal/ui/services?${{ dc }} + X-Request-ID: ${uri} ${{ ...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') { throw new Error('You must specify an id'); } return request` GET /v1/health/service/${id}?${{ dc }} + X-Request-ID: ${uri} ${{ ...this.formatNspace(ns), diff --git a/ui-v2/app/adapters/session.js b/ui-v2/app/adapters/session.js index 869205124..12d171079 100644 --- a/ui-v2/app/adapters/session.js +++ b/ui-v2/app/adapters/session.js @@ -6,12 +6,13 @@ import { NSPACE_KEY } from 'consul-ui/models/nspace'; // TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, ns, index, id }) { + requestForQuery: function(request, { dc, ns, index, id, uri }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/session/node/${id}?${{ dc }} + X-Request-ID: ${uri} ${{ ...this.formatNspace(ns), diff --git a/ui-v2/app/components/data-loader/index.hbs b/ui-v2/app/components/data-loader/index.hbs index a96cb48cb..2c55d9ee2 100644 --- a/ui-v2/app/components/data-loader/index.hbs +++ b/ui-v2/app/components/data-loader/index.hbs @@ -9,23 +9,28 @@ {{#let (hash data=data error=error + dispatchError=(queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR")) ) as |api|}} -{{! if we didn't specify any data}} -{{#if (not items)}} - {{! try and load the data if we aren't in an error state}} - - {{! but only if we only asked for a single load and we are in loading state}} - {{#if (or (not once) (state-matches state "loading"))}} - - {{/if}} - -{{/if}} + {{#yield-slot name="data"}} + {{yield api}} + {{else}} + {{! if we didn't specify any data}} + {{#if (not items)}} + {{! try and load the data if we aren't in an error state}} + + {{! but only if we only asked for a single load and we are in loading state}} + {{#if (and src (or (not once) (state-matches state "loading")))}} + + {{/if}} + + {{/if}} + {{/yield-slot}} {{#yield-slot name="loading"}} diff --git a/ui-v2/app/components/data-loader/index.js b/ui-v2/app/components/data-loader/index.js index dfc1cf3b4..19967a12a 100644 --- a/ui-v2/app/components/data-loader/index.js +++ b/ui-v2/app/components/data-loader/index.js @@ -22,7 +22,7 @@ export default Component.extend(Slotted, { }, actions: { isLoaded: function() { - return typeof this.items !== 'undefined'; + return typeof this.items !== 'undefined' || typeof this.src === 'undefined'; }, change: function(data) { set(this, 'data', this.onchange(data)); diff --git a/ui-v2/app/components/data-source/index.js b/ui-v2/app/components/data-source/index.js index 8ee8a796b..e307d81e9 100644 --- a/ui-v2/app/components/data-source/index.js +++ b/ui-v2/app/components/data-source/index.js @@ -91,7 +91,10 @@ export default Component.extend({ ); const error = err => { try { - this.onerror(err); + 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); @@ -107,9 +110,7 @@ export default Component.extend({ } }, error: e => { - if (get(e, 'error.errors.firstObject.status') !== '429') { - error(e); - } + error(e); }, }); replace(this, '_remove', remove); diff --git a/ui-v2/app/components/data-writer/index.hbs b/ui-v2/app/components/data-writer/index.hbs index 757ff05da..2668aa21e 100644 --- a/ui-v2/app/components/data-writer/index.hbs +++ b/ui-v2/app/components/data-writer/index.hbs @@ -32,16 +32,16 @@ - - {{#yield-slot name="removed"}} - {{yield api}} - {{else}} + {{#yield-slot name="removed" params=(block-params (component 'notification' after=(queue (action dispatch "RESET") (action ondelete))))}} + {{yield api}} + {{else}} + - {{/yield-slot}} - + + {{/yield-slot}} diff --git a/ui-v2/app/components/event-source/index.hbs b/ui-v2/app/components/event-source/index.hbs new file mode 100644 index 000000000..c1cec5090 --- /dev/null +++ b/ui-v2/app/components/event-source/index.hbs @@ -0,0 +1,3 @@ +{{yield (hash + close=(action "close") +)}} diff --git a/ui-v2/app/components/event-source/index.js b/ui-v2/app/components/event-source/index.js index c0f1fc573..833829a11 100644 --- a/ui-v2/app/components/event-source/index.js +++ b/ui-v2/app/components/event-source/index.js @@ -1,10 +1,24 @@ import Component from '@ember/component'; 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({ tagName: '', dom: service('dom'), logger: service('logger'), + data: service('data-source/service'), closeOnDestroy: true, onerror: function(e) { this.logger.execute(e.error); @@ -14,25 +28,64 @@ export default Component.extend({ this._listeners = this.dom.listeners(); }, willDestroyElement: function() { - if (this.closeOnDestroy && typeof (this.src || {}).close === 'function') { - this.src.close(); - this.src.willDestroy(); + if (this.closeOnDestroy) { + this.actions.close.apply(this, []); } this._listeners.remove(); this._super(...arguments); }, didReceiveAttrs: function() { - this._listeners.remove(); - if (typeof (this.src || {}).addEventListener === 'function') { - this._listeners.add(this.src, { - error: e => { - try { - this.onerror(e); - } catch (err) { - this.logger.execute(e.error); - } - }, - }); + this._super(...arguments); + // only close and reopen if the uri changes + // otherwise this will fire whenever the proxies data changes + if (get(this, 'src.configuration.uri') !== get(this, 'source.configuration.uri')) { + this.actions.open.apply(this, []); } }, + 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(); + } + }, + }, }); diff --git a/ui-v2/app/controllers/dc/nodes/show.js b/ui-v2/app/controllers/dc/nodes/show.js deleted file mode 100644 index 98b8755b4..000000000 --- a/ui-v2/app/controllers/dc/nodes/show.js +++ /dev/null @@ -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(); - } - }); - } - } - }, - }, -}); diff --git a/ui-v2/app/controllers/dc/nodes/show/services.js b/ui-v2/app/controllers/dc/nodes/show/services.js index 005114add..c9199c3b4 100644 --- a/ui-v2/app/controllers/dc/nodes/show/services.js +++ b/ui-v2/app/controllers/dc/nodes/show/services.js @@ -1,9 +1,7 @@ import Controller from '@ember/controller'; -import { alias } from '@ember/object/computed'; import { get, computed } from '@ember/object'; export default Controller.extend({ - items: alias('item.Services'), queryParams: { search: { as: 'filter', diff --git a/ui-v2/app/helpers/uri.js b/ui-v2/app/helpers/uri.js new file mode 100644 index 000000000..fb1ae3747 --- /dev/null +++ b/ui-v2/app/helpers/uri.js @@ -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); + }, +}); diff --git a/ui-v2/app/instance-initializers/event-source.js b/ui-v2/app/instance-initializers/event-source.js index 322ffad09..dfbafd07a 100644 --- a/ui-v2/app/instance-initializers/event-source.js +++ b/ui-v2/app/instance-initializers/event-source.js @@ -4,20 +4,7 @@ export function initialize(container) { if (env('CONSUL_UI_DISABLE_REALTIME')) { 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( ['policy', 'role'].map(function(item) { // create repositories that return a promise resolving to an EventSource @@ -33,50 +20,6 @@ export function initialize(container) { }) ) .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', 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) { if (typeof definition.extend !== 'undefined') { // Create the class instances that we need @@ -111,9 +42,6 @@ export function initialize(container) { // but hardcode this for the moment if (typeof definition.route !== 'undefined') { 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 { container.inject(`service:${definition.service}`, name, `service:${servicePath}`); } diff --git a/ui-v2/app/routes/dc/nodes/index.js b/ui-v2/app/routes/dc/nodes/index.js index 3a988215f..783f1efa4 100644 --- a/ui-v2/app/routes/dc/nodes/index.js +++ b/ui-v2/app/routes/dc/nodes/index.js @@ -4,6 +4,7 @@ import { hash } from 'rsvp'; export default Route.extend({ repo: service('repository/node'), + data: service('data-source/service'), queryParams: { search: { as: 'filter', @@ -12,8 +13,9 @@ export default Route.extend({ }, model: function(params) { const dc = this.modelFor('dc').dc.Name; + const nspace = '*'; 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), }); }, diff --git a/ui-v2/app/routes/dc/nodes/show.js b/ui-v2/app/routes/dc/nodes/show.js index 97051653a..10aadb719 100644 --- a/ui-v2/app/routes/dc/nodes/show.js +++ b/ui-v2/app/routes/dc/nodes/show.js @@ -3,20 +3,20 @@ import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; export default Route.extend({ - repo: service('repository/node'), - sessionRepo: service('repository/session'), - coordinateRepo: service('repository/coordinate'), + data: service('data-source/service'), model: function(params) { const dc = this.modelFor('dc').dc.Name; const nspace = this.modelFor('nspace').nspace.substr(1); const name = params.name; 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 => { return hash({ ...model, - sessions: this.sessionRepo.findByNode(name, dc, nspace), - tomography: this.coordinateRepo.findAllByNode(name, dc), + tomography: this.data.source(uri => uri`/${nspace}/${dc}/coordinates/for-node/${name}`), + sessions: this.data.source(uri => uri`/${nspace}/${dc}/sessions/for-node/${name}`), }); }); }, diff --git a/ui-v2/app/routes/dc/nspaces/index.js b/ui-v2/app/routes/dc/nspaces/index.js index 353074695..e6df448e3 100644 --- a/ui-v2/app/routes/dc/nspaces/index.js +++ b/ui-v2/app/routes/dc/nspaces/index.js @@ -4,6 +4,7 @@ import { hash } from 'rsvp'; import WithNspaceActions from 'consul-ui/mixins/nspace/with-actions'; export default Route.extend(WithNspaceActions, { + data: service('data-source/service'), repo: service('repository/nspace'), queryParams: { search: { @@ -13,7 +14,7 @@ export default Route.extend(WithNspaceActions, { }, model: function(params) { return hash({ - items: this.repo.findAll(), + items: this.data.source(uri => uri`/*/*/namespaces`), isLoading: false, }); }, diff --git a/ui-v2/app/routes/dc/services/index.js b/ui-v2/app/routes/dc/services/index.js index ce622f699..905e2bf3a 100644 --- a/ui-v2/app/routes/dc/services/index.js +++ b/ui-v2/app/routes/dc/services/index.js @@ -3,7 +3,7 @@ import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; export default Route.extend({ - repo: service('repository/service'), + data: service('data-source/service'), queryParams: { search: { as: 'filter', @@ -29,12 +29,13 @@ export default Route.extend({ .trim(); } } + const nspace = this.modelFor('nspace').nspace.substr(1); + const dc = this.modelFor('dc').dc.Name; return hash({ + nspace: nspace, + dc: dc, terms: terms !== '' ? terms.split('\n') : [], - items: this.repo.findAllByDatacenter( - this.modelFor('dc').dc.Name, - this.modelFor('nspace').nspace.substr(1) - ), + items: this.data.source(uri => uri`/${nspace}/${dc}/services`), }); }, setupController: function(controller, model) { diff --git a/ui-v2/app/routes/dc/services/instance.js b/ui-v2/app/routes/dc/services/instance.js index f62bb98b6..01a52df67 100644 --- a/ui-v2/app/routes/dc/services/instance.js +++ b/ui-v2/app/routes/dc/services/instance.js @@ -4,38 +4,46 @@ import { hash } from 'rsvp'; import { get } from '@ember/object'; export default Route.extend({ - repo: service('repository/service'), - proxyRepo: service('repository/proxy'), + data: service('data-source/service'), model: function(params) { 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({ dc: dc, - nspace: nspace || 'default', - item: this.repo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace), + nspace: nspace, + item: this.data.source( + uri => uri`/${nspace}/${dc}/service-instance/${params.id}/${params.node}/${params.name}` + ), }).then(model => { // 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 // connect-proxy or vice versa so leave as is for now return hash({ + ...model, proxyMeta: // proxies and mesh-gateways can't have proxies themselves so don't even look ['connect-proxy', 'mesh-gateway'].includes(get(model.item, 'Kind')) ? null - : this.proxyRepo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace), - ...model, + : this.data.source( + uri => + uri`/${nspace}/${dc}/proxy-instance/${params.id}/${params.node}/${params.name}` + ), }).then(model => { if (typeof get(model, 'proxyMeta.ServiceID') === 'undefined') { return model; } - const proxyName = get(model, 'proxyMeta.ServiceName'); - const proxyID = get(model, 'proxyMeta.ServiceID'); - const proxyNode = get(model, 'proxyMeta.Node'); + const proxy = { + id: get(model, 'proxyMeta.ServiceID'), + node: get(model, 'proxyMeta.Node'), + name: get(model, 'proxyMeta.ServiceName'), + }; return hash({ + ...model, // Proxies have identical dc/nspace as their parent instance // No need to use Proxy's dc/nspace response - proxy: this.repo.findInstanceBySlug(proxyID, proxyNode, proxyName, dc, nspace), - ...model, + proxy: this.data.source( + uri => uri`/${nspace}/${dc}/service-instance/${proxy.id}/${proxy.node}/${proxy.name}` + ), }); }); }); diff --git a/ui-v2/app/routes/dc/services/show.js b/ui-v2/app/routes/dc/services/show.js index 1544209a0..9f6a39de9 100644 --- a/ui-v2/app/routes/dc/services/show.js +++ b/ui-v2/app/routes/dc/services/show.js @@ -4,9 +4,7 @@ import { hash } from 'rsvp'; import { get } from '@ember/object'; export default Route.extend({ - repo: service('repository/service'), - chainRepo: service('repository/discovery-chain'), - proxyRepo: service('repository/proxy'), + data: service('data-source/service'), settings: service('settings'), model: function(params, transition = {}) { const dc = this.modelFor('dc').dc.Name; @@ -14,8 +12,8 @@ export default Route.extend({ return hash({ slug: params.name, dc: dc, - nspace: nspace || 'default', - item: this.repo.findBySlug(params.name, dc, nspace), + nspace: nspace, + item: this.data.source(uri => uri`/${nspace}/${dc}/service/${params.name}`), urls: this.settings.findBySlug('urls'), proxies: [], }) @@ -25,16 +23,20 @@ export default Route.extend({ ) ? model : hash({ - chain: this.chainRepo.findBySlug(params.name, dc, nspace), - proxies: this.proxyRepo.findAllBySlug(params.name, dc, nspace), ...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 => { return ['ingress-gateway', 'terminating-gateway'].includes(get(model, 'item.Service.Kind')) ? hash({ - gatewayServices: this.repo.findGatewayBySlug(params.name, dc, nspace), ...model, + gatewayServices: this.data.source( + uri => uri`/${nspace}/${dc}/gateways/for-service/${params.name}` + ), }) : model; }); diff --git a/ui-v2/app/services/client/connections.js b/ui-v2/app/services/client/connections.js index f7d7dd0d5..e5ca38cd6 100644 --- a/ui-v2/app/services/client/connections.js +++ b/ui-v2/app/services/client/connections.js @@ -3,6 +3,8 @@ import Service, { inject as service } from '@ember/service'; export default Service.extend({ dom: service('dom'), env: service('env'), + data: service('data-source/service'), + sources: service('repository/type/event-source'), init: function() { this._super(...arguments); this._listeners = this.dom.listeners(); @@ -42,21 +44,41 @@ export default Service.extend({ } return Promise.resolve(e); }, - purge: function() { + purge: function(statusCode = 0) { [...this.connections].forEach(function(connection) { // Cancelled - connection.abort(0); + connection.abort(statusCode); }); this.connections = new Set(); }, acquire: function(request) { - this.connections.add(request); - if (this.connections.size > this.env.var('CONSUL_HTTP_MAX_CONNECTIONS')) { - const connection = this.connections.values().next().value; - this.connections.delete(connection); - // Too Many Requests - connection.abort(429); + if (this.connections.size >= this.env.var('CONSUL_HTTP_MAX_CONNECTIONS')) { + const closed = this.data.closed(); + let connection = [...this.connections].find(item => { + const id = item.headers()['x-request-id']; + if (id) { + 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) { this.connections.delete(request); diff --git a/ui-v2/app/services/client/http.js b/ui-v2/app/services/client/http.js index ae92ddade..e98ba07c5 100644 --- a/ui-v2/app/services/client/http.js +++ b/ui-v2/app/services/client/http.js @@ -68,7 +68,7 @@ const parseBody = function(strs, ...values) { return [body, ...values]; }; -const CLIENT_HEADERS = [CACHE_CONTROL]; +const CLIENT_HEADERS = [CACHE_CONTROL, 'X-Request-ID']; export default Service.extend({ dom: service('dom'), connections: service('client/connections'), diff --git a/ui-v2/app/services/client/transports/xhr.js b/ui-v2/app/services/client/transports/xhr.js index 595312356..0f3a6f3f8 100644 --- a/ui-v2/app/services/client/transports/xhr.js +++ b/ui-v2/app/services/client/transports/xhr.js @@ -12,7 +12,10 @@ export default Service.extend({ return xhr(options); }, 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 = { ...params, beforeSend: function(xhr) { @@ -51,6 +54,7 @@ export default Service.extend({ }; request.fetch = () => { this.xhr(options); + return request; }; return request; }, diff --git a/ui-v2/app/services/data-sink/protocols/http.js b/ui-v2/app/services/data-sink/protocols/http.js index cc655aca2..b6e39965f 100644 --- a/ui-v2/app/services/data-sink/protocols/http.js +++ b/ui-v2/app/services/data-sink/protocols/http.js @@ -4,6 +4,7 @@ import { setProperties } from '@ember/object'; export default Service.extend({ settings: service('settings'), intention: service('repository/intention'), + session: service('repository/session'), prepare: function(sink, data, instance) { return setProperties(instance, data); }, diff --git a/ui-v2/app/services/data-source/protocols/http.js b/ui-v2/app/services/data-source/protocols/http.js index 6d9e4d6c1..a4e231943 100644 --- a/ui-v2/app/services/data-source/protocols/http.js +++ b/ui-v2/app/services/data-source/protocols/http.js @@ -3,7 +3,17 @@ import { get } from '@ember/object'; export default Service.extend({ datacenters: service('repository/dc'), + nodes: service('repository/node'), + node: service('repository/node'), + gateways: 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'), intentions: service('repository/intention'), intention: service('repository/intention'), @@ -12,17 +22,15 @@ export default Service.extend({ policies: service('repository/policy'), policy: service('repository/policy'), roles: service('repository/role'), - oidc: service('repository/oidc-provider'), + type: service('data-source/protocols/http/blocking'), + source: function(src, configuration) { - // TODO: Consider adding/requiring nspace, dc, model, action, ...rest - const [, nspace, dc, model, ...rest] = src.split('/'); - // TODO: Consider throwing if we have an empty nspace or dc - // we are going to use '*' for 'all' when we need that - // 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' + // 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]; if (repo.shouldReconcile(src)) { @@ -41,32 +49,72 @@ export default Service.extend({ case 'namespaces': find = configuration => repo.findAll(configuration); break; - case 'token': - find = configuration => repo.self(rest[1], dc); - break; case 'services': + case 'nodes': case 'roles': case 'policies': find = configuration => repo.findAllByDatacenter(dc, nspace, configuration); break; - case 'policy': - find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration); - break; case 'intentions': [method, ...slug] = rest; switch (method) { case 'for-service': - // TODO: Are we going to need to encode/decode here...? - find = configuration => repo.findByService(slug.join('/'), dc, nspace, configuration); + find = configuration => repo.findByService(slug, dc, nspace, configuration); break; default: find = configuration => repo.findAllByDatacenter(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 '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': - // TODO: Are we going to need to encode/decode here...? - slug = rest.join('/'); + slug = rest[0]; if (slug) { find = configuration => repo.findBySlug(slug, dc, nspace, configuration); } else { diff --git a/ui-v2/app/services/data-source/protocols/http/blocking.js b/ui-v2/app/services/data-source/protocols/http/blocking.js index 9e363ea1d..df072d8cf 100644 --- a/ui-v2/app/services/data-source/protocols/http/blocking.js +++ b/ui-v2/app/services/data-source/protocols/http/blocking.js @@ -18,7 +18,7 @@ export default Service.extend({ return find(configuration) .then(maybeCall(close, ifNotBlocking(this.settings))) .then(function(res) { - if (typeof get(res, 'meta.cursor') === 'undefined') { + if (typeof get(res || {}, 'meta.cursor') === 'undefined') { close(); } return res; diff --git a/ui-v2/app/services/data-source/protocols/local-storage.js b/ui-v2/app/services/data-source/protocols/local-storage.js index 846d68459..805d94b4c 100644 --- a/ui-v2/app/services/data-source/protocols/local-storage.js +++ b/ui-v2/app/services/data-source/protocols/local-storage.js @@ -11,6 +11,7 @@ export default Service.extend({ }, { key: src, + uri: configuration.uri, } ); }, diff --git a/ui-v2/app/services/data-source/service.js b/ui-v2/app/services/data-source/service.js index 4c9f50bdc..1d56d295b 100644 --- a/ui-v2/app/services/data-source/service.js +++ b/ui-v2/app/services/data-source/service.js @@ -1,4 +1,5 @@ import Service, { inject as service } from '@ember/service'; +import { proxy } from 'consul-ui/utils/dom/event-source'; import MultiMap from 'mnemonist/multi-map'; @@ -10,9 +11,9 @@ let cache = null; let sources = null; // keeps a count of currently in use EventSources let usage = null; - export default Service.extend({ dom: service('dom'), + encoder: service('encoder'), consul: service('data-source/protocols/http'), settings: service('data-source/protocols/local-storage'), @@ -33,29 +34,68 @@ export default Service.extend({ }); cache = null; sources = null; + usage.clear(); 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) { + if (typeof uri !== 'string') { + return this.unwrap(uri, ref); + } let source; // Check the cache for an EventSource that is already being used // for this uri. If we don't have one, set one up. if (uri.indexOf('://') === -1) { uri = `consul://${uri}`; } + let [providerName, pathname] = uri.split('://'); + const provider = this[providerName]; if (!sources.has(uri)) { - let [providerName, pathname] = uri.split('://'); - const provider = this[providerName]; - let configuration = {}; if (cache.has(uri)) { configuration = cache.get(uri); } + configuration.uri = uri; source = provider.source(pathname, configuration); - this._listeners.add(source, { + const remove = this._listeners.add(source, { 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; - source.removeEventListener('close', close); const event = source.getCurrentEvent(); const cursor = source.configuration.cursor; // only cache data if we have any @@ -67,20 +107,25 @@ export default Service.extend({ } // the data is cached delete the EventSource if (!usage.has(source)) { + // A non-blocking query could close but still be on the page sources.delete(uri); } + remove(); }, }); sources.set(uri, source); } else { 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 // in the case of blocking queries being disabled // you may want to specifically force an open // if blocking queries are enabled then opening an already // open blocking query does nothing - if (!usage.has(source) || open) { + if (!usage.has(source) || source.readyState > 1 || open) { source.open(); } // set/increase the usage counter @@ -88,6 +133,8 @@ export default Service.extend({ return source; }, 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) { // decrease the usage counter 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) if (!usage.has(source)) { 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]); + }, }); diff --git a/ui-v2/app/services/encoder.js b/ui-v2/app/services/encoder.js new file mode 100644 index 000000000..84917777f --- /dev/null +++ b/ui-v2/app/services/encoder.js @@ -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); + }, +}); diff --git a/ui-v2/app/services/repository.js b/ui-v2/app/services/repository.js index 757cdc52b..9e3e13971 100644 --- a/ui-v2/app/services/repository.js +++ b/ui-v2/app/services/repository.js @@ -49,6 +49,7 @@ export default Service.extend({ }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), query); }, @@ -60,6 +61,7 @@ export default Service.extend({ }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.queryRecord(this.getModelName(), query); }, diff --git a/ui-v2/app/services/repository/coordinate.js b/ui-v2/app/services/repository/coordinate.js index 86f07e259..fa7623549 100644 --- a/ui-v2/app/services/repository/coordinate.js +++ b/ui-v2/app/services/repository/coordinate.js @@ -18,6 +18,7 @@ export default RepositoryService.extend({ }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), query); }, @@ -25,7 +26,10 @@ export default RepositoryService.extend({ return this.findAllByDatacenter(dc, configuration).then(function(coordinates) { let results = {}; 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'); return results; diff --git a/ui-v2/app/services/repository/intention.js b/ui-v2/app/services/repository/intention.js index b4f464c61..193ed9373 100644 --- a/ui-v2/app/services/repository/intention.js +++ b/ui-v2/app/services/repository/intention.js @@ -29,6 +29,7 @@ export default RepositoryService.extend({ }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), { ...query, diff --git a/ui-v2/app/services/repository/nspace.js b/ui-v2/app/services/repository/nspace.js index 9027a8dd9..5a6822cdf 100644 --- a/ui-v2/app/services/repository/nspace.js +++ b/ui-v2/app/services/repository/nspace.js @@ -16,6 +16,7 @@ export default RepositoryService.extend({ const query = {}; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), query); }, diff --git a/ui-v2/app/services/repository/nspace/enabled.js b/ui-v2/app/services/repository/nspace/enabled.js index 2d6fd2226..ec05fa1bf 100644 --- a/ui-v2/app/services/repository/nspace/enabled.js +++ b/ui-v2/app/services/repository/nspace/enabled.js @@ -21,6 +21,7 @@ export default RepositoryService.extend({ const query = {}; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), query); }, diff --git a/ui-v2/app/services/repository/proxy.js b/ui-v2/app/services/repository/proxy.js index 5c779a272..8a70e93d0 100644 --- a/ui-v2/app/services/repository/proxy.js +++ b/ui-v2/app/services/repository/proxy.js @@ -17,6 +17,7 @@ export default RepositoryService.extend({ }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), query); }, diff --git a/ui-v2/app/services/repository/service.js b/ui-v2/app/services/repository/service.js index dc4df18c2..a62a70a53 100644 --- a/ui-v2/app/services/repository/service.js +++ b/ui-v2/app/services/repository/service.js @@ -84,6 +84,7 @@ export default RepositoryService.extend({ }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), query); }, diff --git a/ui-v2/app/services/repository/session.js b/ui-v2/app/services/repository/session.js index acf11f14d..aa96584da 100644 --- a/ui-v2/app/services/repository/session.js +++ b/ui-v2/app/services/repository/session.js @@ -15,6 +15,7 @@ export default RepositoryService.extend({ }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; + query.uri = configuration.uri; } return this.store.query(this.getModelName(), query); }, diff --git a/ui-v2/app/templates/dc/nodes/show.hbs b/ui-v2/app/templates/dc/nodes/show.hbs index 4598999c8..3da01b62a 100644 --- a/ui-v2/app/templates/dc/nodes/show.hbs +++ b/ui-v2/app/templates/dc/nodes/show.hbs @@ -1,39 +1,70 @@ {{title item.Node}} - - - - - - {{!TODO: Move sessions to its own folder within nodes }} - {{partial 'dc/nodes/notifications'}} - - -
    -
  1. All Nodes
  2. -
-
- -

- {{ item.Node }} -

- -
- - - - - {{item.Address}} - - - {{outlet}} - -
+ + + + + + + + + + + + + + {{#if (eq api.error.status "404")}} + + + + {{else}} + + + + {{/if}} + + + + + + {{!TODO: Move sessions to its own folder within nodes }} + {{partial 'dc/nodes/notifications'}} + + +
    +
  1. All Nodes
  2. +
+
+ +

+ {{ item.Node }} +

+ +
+ + + + + {{item.Address}} + + + {{outlet}} + +
+ +
+
diff --git a/ui-v2/app/templates/dc/nodes/show/services.hbs b/ui-v2/app/templates/dc/nodes/show/services.hbs index f885ca293..178b12228 100644 --- a/ui-v2/app/templates/dc/nodes/show/services.hbs +++ b/ui-v2/app/templates/dc/nodes/show/services.hbs @@ -1,3 +1,4 @@ +{{#let item.Services as |items|}}
{{#if (gt items.length 0) }} @@ -23,4 +24,5 @@
-
\ No newline at end of file + +{{/let}} \ No newline at end of file diff --git a/ui-v2/app/templates/dc/nodes/show/sessions.hbs b/ui-v2/app/templates/dc/nodes/show/sessions.hbs index 8b3a50d43..6a39b5e6e 100644 --- a/ui-v2/app/templates/dc/nodes/show/sessions.hbs +++ b/ui-v2/app/templates/dc/nodes/show/sessions.hbs @@ -39,14 +39,14 @@ - +

{{message}}

- +
diff --git a/ui-v2/app/templates/dc/services/instance.hbs b/ui-v2/app/templates/dc/services/instance.hbs index cfe57a7ec..b3d2672cb 100644 --- a/ui-v2/app/templates/dc/services/instance.hbs +++ b/ui-v2/app/templates/dc/services/instance.hbs @@ -1,6 +1,7 @@ {{title item.ID}} + {{partial 'dc/services/notifications'}} diff --git a/ui-v2/app/templates/dc/services/show.hbs b/ui-v2/app/templates/dc/services/show.hbs index 737c32dbb..0519602f6 100644 --- a/ui-v2/app/templates/dc/services/show.hbs +++ b/ui-v2/app/templates/dc/services/show.hbs @@ -2,6 +2,8 @@ + + {{partial 'dc/services/notifications'}} diff --git a/ui-v2/app/utils/dom/event-source/callable.js b/ui-v2/app/utils/dom/event-source/callable.js index 86b1433cf..f8b232586 100644 --- a/ui-v2/app/utils/dom/event-source/callable.js +++ b/ui-v2/app/utils/dom/event-source/callable.js @@ -57,6 +57,7 @@ export default function( // 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 this.readyState = 2; // CLOSE + this.dispatchEvent({ type: 'close' }); }) .then(() => { // This only gets called when the promise chain completely finishes diff --git a/ui-v2/tests/integration/adapters/coordinate-test.js b/ui-v2/tests/integration/adapters/coordinate-test.js index c9a7e6c81..6989ae9c2 100644 --- a/ui-v2/tests/integration/adapters/coordinate-test.js +++ b/ui-v2/tests/integration/adapters/coordinate-test.js @@ -7,10 +7,10 @@ module('Integration | Adapter | coordinate', function(hooks) { const adapter = this.owner.lookup('adapter:coordinate'); const client = this.owner.lookup('service:client/http'); const expected = `GET /v1/coordinate/nodes?dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { + const actual = adapter.requestForQuery(client.requestParams.bind(client), { dc: dc, }); - assert.equal(actual, expected); + assert.equal(`${actual.method} ${actual.url}`, expected); }); test('requestForQuery returns the correct body', function(assert) { const adapter = this.owner.lookup('adapter:coordinate'); diff --git a/ui-v2/tests/integration/adapters/discovery-chain-test.js b/ui-v2/tests/integration/adapters/discovery-chain-test.js index 3ded7b1f2..3faac8c4f 100644 --- a/ui-v2/tests/integration/adapters/discovery-chain-test.js +++ b/ui-v2/tests/integration/adapters/discovery-chain-test.js @@ -11,11 +11,11 @@ module('Integration | Adapter | discovery-chain', function(hooks) { const adapter = this.owner.lookup('adapter:discovery-chain'); const client = this.owner.lookup('service:client/http'); 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, 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) { const adapter = this.owner.lookup('adapter:discovery-chain'); diff --git a/ui-v2/tests/integration/adapters/intention-test.js b/ui-v2/tests/integration/adapters/intention-test.js index aa0a05fd8..f6204453f 100644 --- a/ui-v2/tests/integration/adapters/intention-test.js +++ b/ui-v2/tests/integration/adapters/intention-test.js @@ -8,10 +8,10 @@ module('Integration | Adapter | intention', function(hooks) { const adapter = this.owner.lookup('adapter:intention'); const client = this.owner.lookup('service:client/http'); const expected = `GET /v1/connect/intentions?dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { + const actual = adapter.requestForQuery(client.requestParams.bind(client), { dc: dc, }); - assert.equal(actual, expected); + assert.equal(`${actual.method} ${actual.url}`, expected); }); test('requestForQueryRecord returns the correct url', function(assert) { const adapter = this.owner.lookup('adapter:intention'); diff --git a/ui-v2/tests/integration/adapters/node-test.js b/ui-v2/tests/integration/adapters/node-test.js index 5eb3b0cdc..3efca49ad 100644 --- a/ui-v2/tests/integration/adapters/node-test.js +++ b/ui-v2/tests/integration/adapters/node-test.js @@ -8,20 +8,20 @@ module('Integration | Adapter | node', function(hooks) { const adapter = this.owner.lookup('adapter:node'); const client = this.owner.lookup('service:client/http'); 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, }); - assert.equal(actual, expected); + assert.equal(`${actual.method} ${actual.url}`, expected); }); test('requestForQueryRecord returns the correct url', function(assert) { const adapter = this.owner.lookup('adapter:node'); const client = this.owner.lookup('service:client/http'); 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, 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) { const adapter = this.owner.lookup('adapter:node'); diff --git a/ui-v2/tests/integration/adapters/nspace-test.js b/ui-v2/tests/integration/adapters/nspace-test.js index b0333ec8a..ecfde700a 100644 --- a/ui-v2/tests/integration/adapters/nspace-test.js +++ b/ui-v2/tests/integration/adapters/nspace-test.js @@ -9,8 +9,8 @@ module('Integration | Adapter | nspace', function(hooks) { const adapter = this.owner.lookup('adapter:nspace'); const client = this.owner.lookup('service:client/http'); const expected = `GET /v1/namespaces`; - const actual = adapter.requestForQuery(client.url, {}); - assert.equal(actual, expected); + const actual = adapter.requestForQuery(client.requestParams.bind(client), {}); + assert.equal(`${actual.method} ${actual.url}`, expected); }); test('requestForQueryRecord returns the correct url/method', function(assert) { const adapter = this.owner.lookup('adapter:nspace'); diff --git a/ui-v2/tests/unit/controllers/dc/nodes/show-test.js b/ui-v2/tests/unit/services/encoder-test.js similarity index 54% rename from ui-v2/tests/unit/controllers/dc/nodes/show-test.js rename to ui-v2/tests/unit/services/encoder-test.js index be580fc41..9ba371371 100644 --- a/ui-v2/tests/unit/controllers/dc/nodes/show-test.js +++ b/ui-v2/tests/unit/services/encoder-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | dc/nodes/show', function(hooks) { +module('Unit | Service | encoder', function(hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function(assert) { - let controller = this.owner.lookup('controller:dc/nodes/show'); - assert.ok(controller); + let service = this.owner.lookup('service:encoder'); + assert.ok(service); }); });