From 0e878ef378106f1bed11e1d8ca412c223d51bbe8 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 12 Dec 2019 11:38:34 +0000 Subject: [PATCH 01/14] ui: Add in the things we use for the animations --- ui-v2/app/helpers/tween-to.js | 9 + ui-v2/app/services/ticker.js | 35 +++ ui-v2/app/utils/ticker/index.js | 209 ++++++++++++++++++ .../integration/helpers/tween-to-test.js | 17 ++ ui-v2/tests/unit/services/ticker-test.js | 12 + ui-v2/tests/unit/utils/ticker/index-test.js | 10 + 6 files changed, 292 insertions(+) create mode 100644 ui-v2/app/helpers/tween-to.js create mode 100644 ui-v2/app/services/ticker.js create mode 100644 ui-v2/app/utils/ticker/index.js create mode 100644 ui-v2/tests/integration/helpers/tween-to-test.js create mode 100644 ui-v2/tests/unit/services/ticker-test.js create mode 100644 ui-v2/tests/unit/utils/ticker/index-test.js diff --git a/ui-v2/app/helpers/tween-to.js b/ui-v2/app/helpers/tween-to.js new file mode 100644 index 000000000000..37c441f751ed --- /dev/null +++ b/ui-v2/app/helpers/tween-to.js @@ -0,0 +1,9 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; + +export default Helper.extend({ + ticker: service('ticker'), + compute: function([props, id], hash) { + return this.ticker.tweenTo(props, id); + }, +}); diff --git a/ui-v2/app/services/ticker.js b/ui-v2/app/services/ticker.js new file mode 100644 index 000000000000..c2cb59282dca --- /dev/null +++ b/ui-v2/app/services/ticker.js @@ -0,0 +1,35 @@ +import Service from '@ember/service'; +import { Tween } from 'consul-ui/utils/ticker'; + +let map; +export default Service.extend({ + init: function() { + this._super(...arguments); + this.reset(); + }, + tweenTo: function(props, obj = '', frames, method) { + // TODO: Right now we only support string id's + // but potentially look at allowing passing of other objects + // especially DOM elements + const id = obj; + if (!map.has(id)) { + map.set(id, props); + return props; + } else { + obj = map.get(id); + if (obj instanceof Tween) { + obj = obj.stop().getTarget(); + } + map.set(id, Tween.to(obj, props, frames, method)); + return obj; + } + }, + // TODO: We'll try and use obj later for ticker bookkeeping + destroy: function(obj) { + this.reset(); + return Tween.destroy(); + }, + reset: function() { + map = new Map(); + }, +}); diff --git a/ui-v2/app/utils/ticker/index.js b/ui-v2/app/utils/ticker/index.js new file mode 100644 index 000000000000..688ec2cae3ad --- /dev/null +++ b/ui-v2/app/utils/ticker/index.js @@ -0,0 +1,209 @@ +import EventTarget from 'consul-ui/utils/dom/event-target/rsvp'; +import { set } from '@ember/object'; +const IntervalTickerGroup = class extends EventTarget { + constructor(rate = 1000 / 60) { + super(); + this.setRate(rate); + } + tick() { + this.dispatchEvent({ type: 'tick', target: this }); + } + setRate(rate) { + clearInterval(this._interval); + this._interval = setInterval(() => this.tick(), rate); + } + destroy() { + clearInterval(this._interval); + } +}; +export const Ticker = class extends EventTarget { + static destroy() { + if (typeof Ticker.defaultTickerGroup !== 'undefined') { + Ticker.defaultTickerGroup.destroy(); + delete Ticker.defaultTickerGroup; + } + } + constructor(obj) { + super(); + this.setTickable(obj); + } + tick() { + this._tickable.tick(); + } + setTickable(tickable) { + this._tickable = tickable; + // this.addEventListener(this._tickable); + if (typeof this._tickable.getTicker === 'undefined') { + this._tickable.getTicker = () => this; + } + this.tick = this._tickable.tick.bind(this._tickable); + } + getTickable() { + return this._tickable; + } + isAlive() { + return this._isAlive; + } + start() { + this._isAlive = true; + this.getTickerGroup().addEventListener('tick', this.tick); + this.dispatchEvent({ type: 'start', target: this }); + } + stop() { + this._isAlive = false; + this.getTickerGroup().removeEventListener('tick', this.tick); + this.dispatchEvent({ type: 'stop', target: this }); + } + activeCount() { + return this.getTickerGroup().activeCount(); + } + setTickerGroup(group) { + this._group = group; + } + getTickerGroup() { + if (typeof this._group === 'undefined') { + if (typeof Ticker.defaultTickerGroup === 'undefined') { + Ticker.defaultTickerGroup = new TickerGroup(); + } + this._group = Ticker.defaultTickerGroup; + } + return this._group; + } +}; +const TimelineAbstract = class { + constructor() { + this._currentframe = 1; + this.setIncrement(1); + } + isAtStart() { + return this._currentframe <= 1; + } + isAtEnd() { + return this._currentframe >= this._totalframes; + } + addEventListener() { + return this.getTicker().addEventListener(...arguments); + } + removeEventListener() { + return this.getTicker().removeEventListener(...arguments); + } + stop() { + return this.gotoAndStop(this._currentframe); + } + play() { + return this.gotoAndPlay(this._currentframe); + } + start() { + return this.gotoAndPlay(this._currentframe); + } + gotoAndStop(frame) { + this._currentframe = frame; + const ticker = this.getTicker(); + if (ticker.isAlive()) { + ticker.stop(); + } + return this; + } + gotoAndPlay(frame) { + this._currentframe = frame; + const ticker = this.getTicker(); + if (!ticker.isAlive()) { + ticker.start(); + } + return this; + } + getTicker() { + if (typeof this._ticker === 'undefined') { + this._ticker = new Ticker(this); + } + return this._ticker; + } + setFrames(frames) { + this._totalframes = frames; + return this; + } + setIncrement(inc) { + this._increment = inc; + return this; + } +}; +const Cubic = { + easeOut: function(t, b, c, d) { + t /= d; + t--; + return c * (t * t * t + 1) + b; + }, +}; +const TickerGroup = IntervalTickerGroup; +export const Tween = class extends TimelineAbstract { + static destroy() { + Ticker.destroy(); + } + static to(start, finish, frames, method) { + Object.keys(finish).forEach(function(key) { + finish[key] -= start[key]; + }); + return new Tween(start, finish, frames, method).play(); + } + constructor(obj, props, frames = 12, method = Cubic.easeOut) { + super(); + this.setMethod(method); + this.setProps(props); + this.setTarget(obj); + this.setFrames(frames); + this.tick = this.forwards; + } + _process() { + Object.keys(this._props).forEach(key => { + const num = this._method( + this._currentframe, + this._initialstate[key], + this._props[key], + this._totalframes + ); + // this._target[key] = num; + set(this._target, key, num); + }); + } + forwards() { + if (this._currentframe <= this._totalframes) { + this._process(); + this._currentframe += this._increment; + } else { + this._currentframe = this._totalframes; + this.getTicker().stop(); + } + } + backwards() { + this._currentframe -= this._increment; + if (this._currentframe >= 0) { + this._process(); + } else { + this.run = this.forwards; + this._currentframe = 1; + this.getTicker().stop(); + } + } + gotoAndPlay() { + if (typeof this._initialstate === 'undefined') { + this._initialstate = {}; + Object.keys(this._props).forEach(key => { + this._initialstate[key] = this._target[key]; + }); + } + return super.gotoAndPlay(...arguments); + } + setTarget(target) { + this._target = target; + } + getTarget(target) { + return this._target; + } + setProps(props) { + this._props = props; + return this; + } + setMethod(method) { + this._method = method; + } +}; diff --git a/ui-v2/tests/integration/helpers/tween-to-test.js b/ui-v2/tests/integration/helpers/tween-to-test.js new file mode 100644 index 000000000000..9500be7fadce --- /dev/null +++ b/ui-v2/tests/integration/helpers/tween-to-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Helper | tween-to', function(hooks) { + setupRenderingTest(hooks); + + // Replace this with your real tests. + test('it renders', async function(assert) { + this.set('inputValue', '1234'); + + await render(hbs`{{tween-to inputValue}}`); + + assert.equal(this.element.textContent.trim(), '1234'); + }); +}); diff --git a/ui-v2/tests/unit/services/ticker-test.js b/ui-v2/tests/unit/services/ticker-test.js new file mode 100644 index 000000000000..c3c07a0f8aa1 --- /dev/null +++ b/ui-v2/tests/unit/services/ticker-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Service | Ticker', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let service = this.owner.lookup('service:ticker'); + assert.ok(service); + }); +}); diff --git a/ui-v2/tests/unit/utils/ticker/index-test.js b/ui-v2/tests/unit/utils/ticker/index-test.js new file mode 100644 index 000000000000..04a18ef2a970 --- /dev/null +++ b/ui-v2/tests/unit/utils/ticker/index-test.js @@ -0,0 +1,10 @@ +import tickerIndex from 'consul-ui/utils/ticker/index'; +import { module, test } from 'qunit'; + +module('Unit | Utility | ticker/index', function() { + // Replace this with your real tests. + test('it works', function(assert) { + let result = tickerIndex(); + assert.ok(result); + }); +}); From 61a9480c752039800ac7be40544650ea422b4f7a Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 12 Dec 2019 11:39:50 +0000 Subject: [PATCH 02/14] ui: Add a few more CSS tweaks (inc vars to use) --- ui-v2/app/helpers/css-var.js | 2 ++ ui-v2/app/styles/components/discovery-chain/skin.scss | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui-v2/app/helpers/css-var.js b/ui-v2/app/helpers/css-var.js index 0d30af30a77a..480abba38f0a 100644 --- a/ui-v2/app/helpers/css-var.js +++ b/ui-v2/app/helpers/css-var.js @@ -1,7 +1,9 @@ import { helper } from '@ember/component/helper'; // TODO: Look at ember-inline-svg const cssVars = { + '--decor-border-100': '1px solid', '--decor-border-200': '2px solid', + '--decor-radius-300': '3px', '--white': '#FFF', '--gray-500': '#6f7682', '--kubernetes-color-svg': `url('data:image/svg+xml;charset=UTF-8,')`, diff --git a/ui-v2/app/styles/components/discovery-chain/skin.scss b/ui-v2/app/styles/components/discovery-chain/skin.scss index 656e2cd6ef46..f6f71ac5b4e3 100644 --- a/ui-v2/app/styles/components/discovery-chain/skin.scss +++ b/ui-v2/app/styles/components/discovery-chain/skin.scss @@ -106,7 +106,7 @@ border: 2px solid $gray-400; } %discovery-chain circle { - stroke-width: 1; + stroke-width: 2; stroke: $gray-400; fill: $white; } From 7b1bc5f0fc76b801c1a3cce18c34eb79ea5a50f3 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 12 Dec 2019 11:40:16 +0000 Subject: [PATCH 03/14] ui: Fix up tab selection, add animations, redirect workaround 1. Use IntersectionObserver so we know when the tab is visible, otherwise the dom-position helper won't work as the dom elements don't have any display. 2. Add some base work for animations and use them a little 3. Try to detect if a resolver is a redirect. Right now this works for datacenters and namespaces, but it can't work for services and subsets - we are awaiting backend support for doing this properly. --- ui-v2/app/components/discovery-chain.js | 144 +++++++---- ui-v2/app/controllers/dc/services/show.js | 6 +- ui-v2/app/helpers/dom-position.js | 9 +- ui-v2/app/services/dom.js | 22 ++ .../templates/components/discovery-chain.hbs | 235 ++++++++++-------- .../templates/components/resolver-card.hbs | 12 + 6 files changed, 262 insertions(+), 166 deletions(-) diff --git a/ui-v2/app/components/discovery-chain.js b/ui-v2/app/components/discovery-chain.js index f91a3982c790..ff4c82137911 100644 --- a/ui-v2/app/components/discovery-chain.js +++ b/ui-v2/app/components/discovery-chain.js @@ -3,6 +3,30 @@ import { inject as service } from '@ember/service'; import { set, get, computed } from '@ember/object'; import { next } from '@ember/runloop'; +const getType = function(nodes = {}, type) { + return Object.values(nodes).filter(item => item.Type === type); +}; + +const targetsToFailover = function(targets, a) { + let type; + const Targets = targets.map(function(b) { + // FIXME: this isn't going to work past namespace for services + // with dots in the name + const [aRev, bRev] = [a, b].map(item => item.split('.').reverse()); + const types = ['Datacenter', 'Namespace', 'Service', 'Subset']; + return bRev.find(function(item, i) { + const res = item !== aRev[i]; + if (res) { + type = types[i]; + } + return res; + }); + }); + return { + Type: type, + Targets: Targets, + }; +}; const getNodeResolvers = function(nodes = {}) { const failovers = getFailovers(nodes); const resolvers = {}; @@ -15,7 +39,7 @@ const getNodeResolvers = function(nodes = {}) { return resolvers; }; -const getTargetResolvers = function(targets = [], nodes = {}) { +const getTargetResolvers = function(dc, nspace = 'default', targets = [], nodes = {}) { const resolvers = {}; Object.values(targets).forEach(item => { let node = nodes[item.ID]; @@ -26,6 +50,7 @@ const getTargetResolvers = function(targets = [], nodes = {}) { Name: item.Service, Subsets: [], Failover: null, + Redirect: null, }; } const resolver = resolvers[item.Service]; @@ -43,6 +68,11 @@ const getTargetResolvers = function(targets = [], nodes = {}) { // FIXME: Figure out if we can get rid of this /* eslint ember/no-side-effects: "warn" */ set(failoverable, 'Failover', targetsToFailover(node.Resolver.Failover.Targets, item.ID)); + } else { + const res = targetsToFailover([node.Resolver.Target], `service.${nspace}.${dc}`); + if (res.Type === 'Datacenter' || res.Type === 'Namespace') { + set(failoverable, 'Redirect', true); + } } } }); @@ -59,41 +89,49 @@ const getFailovers = function(nodes = {}) { }); return failovers; }; -const getType = function(nodes = {}, type) { - return Object.values(nodes).filter(item => item.Type === type); -}; - -const targetsToFailover = function(targets, a) { - let type; - const Targets = targets.map(function(b) { - // FIXME: this isn't going to work past namespace for services - // with dots in the name - const [aRev, bRev] = [a, b].map(item => item.split('.').reverse()); - const types = ['Datacenter', 'Namespace', 'Service', 'Subset']; - return bRev.find(function(item, i) { - const res = item !== aRev[i]; - if (res) { - type = types[i]; - } - return res; - }); - }); - return { - Type: type, - Targets: Targets, - }; -}; export default Component.extend({ dom: service('dom'), + ticker: service('ticker'), dataStructs: service('data-structs'), classNames: ['discovery-chain'], classNameBindings: ['active'], + isDisplayed: false, selectedId: '', x: 0, y: 0, tooltip: '', activeTooltip: false, + init: function() { + this._super(...arguments); + this._listeners = this.dom.listeners(); + this._viewportlistener = this.dom.listeners(); + }, + didInsertElement: function() { + this._super(...arguments); + this._viewportlistener.add( + this.dom.isInViewport(this.element, bool => { + set(this, 'isDisplayed', bool); + if (this.isDisplayed) { + this.addPathListeners(); + } else { + this.ticker.destroy(this); + } + }) + ); + }, + didReceiveAttrs: function() { + this._super(...arguments); + if (this.element) { + this.addPathListeners(); + } + }, + willDestroyElement: function() { + this._super(...arguments); + this._listeners.remove(); + this._viewportlistener.remove(); + this.ticker.destroy(this); + }, splitters: computed('chain.Nodes', function() { return getType(get(this, 'chain.Nodes'), 'splitter').map(function(item) { set(item, 'ID', `splitter:${item.Name}`); @@ -104,23 +142,48 @@ export default Component.extend({ // Right now there should only ever be one 'Router'. return getType(get(this, 'chain.Nodes'), 'router'); }), - routes: computed('routers', function() { - return get(this, 'routers').reduce(function(prev, item) { + routes: computed('chain', 'routers', function() { + const routes = get(this, 'routers').reduce(function(prev, item) { return prev.concat( item.Routes.map(function(route, i) { return { ...route, - ID: `route:${item.Name}-${i}`, + ID: `route:${item.Name}-${JSON.stringify(route.Definition.Match.HTTP)}`, }; }) ); }, []); + if (routes.length === 0) { + let nextNode = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`; + const splitterID = `splitter:${this.chain.ServiceName}`; + if (typeof this.chain.Nodes[splitterID] !== 'undefined') { + nextNode = splitterID; + } + routes.push({ + ID: `route:${this.chain.ServiceName}`, + Name: this.chain.ServiceName, + Definition: { + Match: { + HTTP: { + PathPrefix: '/', + }, + }, + }, + NextNode: nextNode, + }); + } + return routes; }), nodeResolvers: computed('chain.Nodes', function() { return getNodeResolvers(get(this, 'chain.Nodes')); }), resolvers: computed('nodeResolvers.[]', 'chain.Targets', function() { - return getTargetResolvers(get(this, 'chain.Targets'), this.nodeResolvers); + return getTargetResolvers( + this.chain.Datacenter, + this.chain.Namespace, + get(this, 'chain.Targets'), + this.nodeResolvers + ); }), graph: computed('chain.Nodes', function() { const graph = this.dataStructs.graph(); @@ -133,7 +196,10 @@ export default Component.extend({ break; case 'router': item.Routes.forEach(function(route, i) { - graph.addLink(`route:${item.Name}-${i}`, route.NextNode); + graph.addLink( + `route:${item.Name}-${JSON.stringify(route.Definition.Match.HTTP)}`, + route.NextNode + ); }); break; } @@ -168,27 +234,16 @@ export default Component.extend({ edges: edges.map(item => `#${CSS.escape(item)}`), }; }), - width: computed('chain.{Nodes,Targets}', function() { + width: computed('isDisplayed', 'chain.{Nodes,Targets}', function() { return this.element.offsetWidth; }), - height: computed('chain.{Nodes,Targets}', function() { + height: computed('isDisplayed', 'chain.{Nodes,Targets}', function() { return this.element.offsetHeight; }), - didInsertElement: function() { - this.addPathListeners(); - }, - didReceiveAttrs: function() { - if (this.element) { - this.addPathListeners(); - } - }, // TODO(octane): ember has trouble adding mouse events to svg elements whilst giving // the developer access to the mouse event therefore we just use JS to add our events // revisit this post Octane addPathListeners: function() { - if (typeof this._listeners === 'undefined') { - this._listeners = this.dom.listeners(); - } // FIXME: Figure out if we can remove this next next(() => { this._listeners.remove(); @@ -206,9 +261,6 @@ export default Component.extend({ // the tooltip would stay if there was no change to the // set(this, 'activeTooltip', false); }, - willDestroyElement: function() { - this._listeners.remove(); - }, actions: { showSplit: function(e) { this.setProperties({ diff --git a/ui-v2/app/controllers/dc/services/show.js b/ui-v2/app/controllers/dc/services/show.js index 5ddf2940d62c..86684593dd22 100644 --- a/ui-v2/app/controllers/dc/services/show.js +++ b/ui-v2/app/controllers/dc/services/show.js @@ -20,11 +20,7 @@ export default Controller.extend(WithEventSource, WithSearching, { // as this is a variable used purely for view level things, if the view was different we might not // need this variable - // set(this, 'selectedTab', 'instances'); - // FIXME: Just to make it easier to build for the moment - // We'll also need to use a similar or the same approach as our - // didAppear thing see components/code-editor.js plus others - set(this, 'selectedTab', 'routing'); + set(this, 'selectedTab', 'instances'); }, item: listen('item').catch(function(e) { if (e.target.readyState === 1) { diff --git a/ui-v2/app/helpers/dom-position.js b/ui-v2/app/helpers/dom-position.js index f8198230015b..dfe69c4c735f 100644 --- a/ui-v2/app/helpers/dom-position.js +++ b/ui-v2/app/helpers/dom-position.js @@ -3,15 +3,15 @@ import { inject as service } from '@ember/service'; export default Helper.extend({ dom: service('dom'), - compute: function([selector], hash) { + compute: function([selector, id], hash) { const $el = this.dom.element(selector); - let $refs = [$el.offsetParent, $el]; - // TODO: helper probably need to accept a `reference=` option + const $refs = [$el.offsetParent, $el]; + // TODO: helper probably needs to accept a `reference=` option // with a selector to use as reference/root if (selector.startsWith('#resolver:')) { $refs.unshift($refs[0].offsetParent); } - const pos = $refs.reduce( + return $refs.reduce( function(prev, item) { prev.x += item.offsetLeft; prev.y += item.offsetTop; @@ -24,6 +24,5 @@ export default Helper.extend({ width: $el.offsetWidth, } ); - return pos; }, }); diff --git a/ui-v2/app/services/dom.js b/ui-v2/app/services/dom.js index 20a52b05b43d..7ac037ca1344 100644 --- a/ui-v2/app/services/dom.js +++ b/ui-v2/app/services/dom.js @@ -19,12 +19,14 @@ import clickFirstAnchorFactory from 'consul-ui/utils/dom/click-first-anchor'; // use $_ for components const $$ = qsaFactory(); let $_; +let inViewportCallbacks; const clickFirstAnchor = clickFirstAnchorFactory(closest); export default Service.extend({ doc: document, win: window, init: function() { this._super(...arguments); + inViewportCallbacks = new WeakMap(); $_ = getComponentFactory(getOwner(this)); }, document: function() { @@ -83,4 +85,24 @@ export default Service.extend({ return item != null; }); }, + isInViewport: function($el, cb, threshold = 0) { + inViewportCallbacks.set($el, cb); + const observer = new IntersectionObserver( + (entries, observer) => { + entries.map(item => { + const cb = inViewportCallbacks.get(item.target); + if (typeof cb === 'function') { + cb(item.isIntersecting); + } + }); + }, + { + rootMargin: '0px', + threshold: threshold, + } + ); + observer.observe($el); // eslint-disable-line ember/no-observers + // observer.unobserve($el); + return () => observer.disconnect(); // eslint-disable-line ember/no-observers + }, }); diff --git a/ui-v2/app/templates/components/discovery-chain.hbs b/ui-v2/app/templates/components/discovery-chain.hbs index bd8c686bb98c..9a8f4f359772 100644 --- a/ui-v2/app/templates/components/discovery-chain.hbs +++ b/ui-v2/app/templates/components/discovery-chain.hbs @@ -1,126 +1,141 @@ - -
-
-

- {{chain.ServiceName}} Router - - Use routers to intercept traffic using L7 criteria such as path prefixes or http headers. - -

-
-
- {{#each routes as |item|}} - {{route-card item=item onclick=(action 'click')}} - {{/each}} + background-color: {{css-var '--white'}}; + border: {{css-var '--decor-border-100'}}; + border-radius: {{css-var '--decor-radius-300'}}; + border-color: {{css-var '--gray-500'}}; + box-shadow: 0 8px 10px 0 rgba(0, 0, 0, 0.1); + } + {{/if}} + {{#if selected.edges }} + {{selected.edges}} { + opacity: 1; + } + {{/if}} + +
+
+

+ {{chain.ServiceName}} Router + + Use routers to intercept traffic using L7 criteria such as path prefixes or http headers. + +

+
+
+ {{#each routes as |item|}} + {{route-card item=item onclick=(action 'click')}} + {{/each}} +
-
-
-
-

- Splitters - - Splitters are configured to split incoming requests across different services or subsets of a single service. - -

-
-
- {{#each (sort-by 'Name' splitters) as |item|}} - {{splitter-card item=item onclick=(action 'click')}} - {{/each}} +
+
+

+ Splitters + + Splitters are configured to split incoming requests across different services or subsets of a single service. + +

+
+
+ {{#each (sort-by 'Name' splitters) as |item|}} + {{splitter-card item=item onclick=(action 'click')}} + {{/each}} +
-
-
-
-

- Resolvers - - Resolvers are used to define which instances of a service should satisfy discovery requests. - -

-
-
- {{#each (sort-by 'Name' resolvers) as |item|}} - {{resolver-card item=item onclick=(action 'click')}} - {{/each}} +
+
+

+ Resolvers + + Resolvers are used to define which instances of a service should satisfy discovery requests. + +

+
+
+ {{#each (sort-by 'Name' resolvers) as |item|}} + {{resolver-card item=item onclick=(action 'click')}} + {{/each}} +
-
- - {{#each routes as |item|}} - {{#let (dom-position (concat '#' item.ID)) as |src|}} - {{#let (dom-position (concat '#' item.NextNode)) as |dest|}} + + {{#each routes as |item|}} + {{#let (dom-position (concat '#' item.ID)) as |src|}} + {{#let (dom-position (concat '#' item.NextNode)) as |destRect|}} + {{#let (tween-to (hash + x=destRect.x + y=(add destRect.y (div destRect.height 2)) + ) (concat item.ID)) as |dest|}} + ' item.NextNode}} + d={{ + svg-curve (hash + x=dest.x + y=dest.y + ) src=(hash + x=(add src.x src.width) + y=(add src.y (div src.height 2)) + )}} /> + {{/let}} + {{/let}} + {{/let}} + {{/each}} + {{#each splitters as |splitter|}} + {{#let (dom-position (concat '#' splitter.ID)) as |src|}} + {{#each splitter.Splits as |item index|}} + {{#let (dom-position (concat '#' item.NextNode)) as |destRect|}} + {{#let (tween-to (hash + x=destRect.x + y=(add destRect.y (div destRect.height 2)) + ) (concat splitter.ID '-' index)) as |dest|}} ' item.NextNode}} + id={{concat 'splitter:' splitter.Name '>' item.NextNode}} + class="split" + data-percentage={{item.Weight}} d={{ svg-curve (hash x=dest.x - y=(add dest.y (div dest.height 2)) + y=dest.y ) src=(hash x=(add src.x src.width) y=(add src.y (div src.height 2)) - )}} /> + )}} /> + {{/let}} + {{/let}} + {{/each}} {{/let}} - {{/let}} - {{/each}} - {{#each splitters as |splitter|}} - {{#let (dom-position (concat '#' splitter.ID)) as |src|}} - {{#each splitter.Splits as |item|}} + {{/each}} + + + {{#each routes as |item|}} + {{#if (starts-with 'resolver:' item.NextNode) }} {{#let (dom-position (concat '#' item.NextNode)) as |dest|}} - ' item.NextNode}} - class="split" - data-percentage={{item.Weight}} - d={{ - svg-curve (hash - x=dest.x - y=(add dest.y (div dest.height 2)) - ) src=(hash - x=(add src.x src.width) - y=(add src.y (div src.height 2)) - )}} /> + {{/let}} - {{/each}} - {{/let}} - {{/each}} - - - {{#each routes as |item|}} - {{#if (starts-with 'resolver:' item.NextNode) }} - {{#let (dom-position (concat '#' item.NextNode)) as |dest|}} - - {{/let}} - {{/if}} - {{/each}} - {{#each splitters as |item|}} - {{#each item.Splits as |item|}} + {{/if}} + {{/each}} + {{#each splitters as |item|}} + {{#each item.Splits as |item|}} + {{#let (dom-position (concat '#' item.NextNode)) as |dest|}} + + {{/let}} + {{/each}} + {{/each}} + + + {{#each routes as |item|}} + {{#if (starts-with 'splitter:' item.NextNode) }} {{#let (dom-position (concat '#' item.NextNode)) as |dest|}} - + {{/let}} - {{/each}} - {{/each}} - - - {{#each routes as |item|}} - {{#if (starts-with 'splitter:' item.NextNode) }} - {{#let (dom-position (concat '#' item.NextNode)) as |dest|}} - - {{/let}} - {{/if}} - {{/each}} - -
- {{round tooltip decimals=2}}% -
+ {{/if}} + {{/each}} + +
+ {{round tooltip decimals=2}}% +
+{{/if}} diff --git a/ui-v2/app/templates/components/resolver-card.hbs b/ui-v2/app/templates/components/resolver-card.hbs index 1bbcbb7824ec..836665a64e49 100644 --- a/ui-v2/app/templates/components/resolver-card.hbs +++ b/ui-v2/app/templates/components/resolver-card.hbs @@ -16,6 +16,18 @@ {{/if}} + {{#if item.Redirect}} +
+
Redirect
+
+
    +
  1. + {{item.ID}} +
  2. +
+
+
+ {{/if}} {{#if (gt item.Subsets.length 0)}} From 5ab6c4771861945b4a3b571ef624d1f1b2b8f331 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 12 Dec 2019 12:16:45 +0000 Subject: [PATCH 04/14] ui: Remove the nodes argument we aren't using it yet --- ui-v2/app/services/data-structs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-v2/app/services/data-structs.js b/ui-v2/app/services/data-structs.js index 5c946526ea3d..7fa27f15237f 100644 --- a/ui-v2/app/services/data-structs.js +++ b/ui-v2/app/services/data-structs.js @@ -3,7 +3,7 @@ import Service from '@ember/service'; import createGraph from 'ngraph.graph'; export default Service.extend({ - graph: function(nodes) { + graph: function() { return createGraph(); }, }); From a32129cc6fd5049a125a9eef56d2f9d3c81bec5c Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 12 Dec 2019 12:24:23 +0000 Subject: [PATCH 05/14] Add further comment to explain width calculation --- ui-v2/app/styles/components/discovery-chain/layout.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui-v2/app/styles/components/discovery-chain/layout.scss b/ui-v2/app/styles/components/discovery-chain/layout.scss index f8538a1af7c2..36b9a61087d5 100644 --- a/ui-v2/app/styles/components/discovery-chain/layout.scss +++ b/ui-v2/app/styles/components/discovery-chain/layout.scss @@ -155,6 +155,8 @@ %discovery-chain .resolver-inlets { @extend %discovery-chain-joints; } +/* there are 3 columns, or %chain-groups the calculation here */ +/* depends on the width of those */ %discovery-chain .splitter-inlets { left: 50%; /* keep the extra calculations here for doc purposes */ From f7c39e240995aec211f23f58d5abf6807e76c583 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 12 Dec 2019 12:50:41 +0000 Subject: [PATCH 06/14] Add a comment pointing out there is a little CSS in the template --- ui-v2/app/styles/components/discovery-chain/skin.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui-v2/app/styles/components/discovery-chain/skin.scss b/ui-v2/app/styles/components/discovery-chain/skin.scss index f6f71ac5b4e3..b3bf19f22ca7 100644 --- a/ui-v2/app/styles/components/discovery-chain/skin.scss +++ b/ui-v2/app/styles/components/discovery-chain/skin.scss @@ -1,3 +1,8 @@ +/* CSS active states are partly added at the top of */ +/* components/templates/discovery-chain.hbs for reasons */ +/* the styling there almost 100% uses our CSS vars */ +/* defined in our CSS files, but be sure to */ +/* take a look in the discovery-chain.hbs */ %discovery-chain .tooltip, %chain-group > header span { @extend %with-tooltip; From b787b5bfca2bb849dac439d5c9707d5948863838 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Fri, 13 Dec 2019 09:26:14 +0000 Subject: [PATCH 07/14] getType > getNodesByType - Makes more obvious that it returns an array --- ui-v2/app/components/discovery-chain.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-v2/app/components/discovery-chain.js b/ui-v2/app/components/discovery-chain.js index ff4c82137911..cbdeaec844b2 100644 --- a/ui-v2/app/components/discovery-chain.js +++ b/ui-v2/app/components/discovery-chain.js @@ -3,7 +3,7 @@ import { inject as service } from '@ember/service'; import { set, get, computed } from '@ember/object'; import { next } from '@ember/runloop'; -const getType = function(nodes = {}, type) { +const getNodesByType = function(nodes = {}, type) { return Object.values(nodes).filter(item => item.Type === type); }; @@ -133,14 +133,14 @@ export default Component.extend({ this.ticker.destroy(this); }, splitters: computed('chain.Nodes', function() { - return getType(get(this, 'chain.Nodes'), 'splitter').map(function(item) { + return getNodesType(get(this, 'chain.Nodes'), 'splitter').map(function(item) { set(item, 'ID', `splitter:${item.Name}`); return item; }); }), routers: computed('chain.Nodes', function() { // Right now there should only ever be one 'Router'. - return getType(get(this, 'chain.Nodes'), 'router'); + return getNodesType(get(this, 'chain.Nodes'), 'router'); }), routes: computed('chain', 'routers', function() { const routes = get(this, 'routers').reduce(function(prev, item) { From 91b74e004ddb0e7cbe8cb9e963f83b358d048058 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Fri, 13 Dec 2019 09:31:06 +0000 Subject: [PATCH 08/14] :laughing: actually change the method names --- ui-v2/app/components/discovery-chain.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-v2/app/components/discovery-chain.js b/ui-v2/app/components/discovery-chain.js index cbdeaec844b2..968b2c99bcd2 100644 --- a/ui-v2/app/components/discovery-chain.js +++ b/ui-v2/app/components/discovery-chain.js @@ -133,14 +133,14 @@ export default Component.extend({ this.ticker.destroy(this); }, splitters: computed('chain.Nodes', function() { - return getNodesType(get(this, 'chain.Nodes'), 'splitter').map(function(item) { + return getNodesByType(get(this, 'chain.Nodes'), 'splitter').map(function(item) { set(item, 'ID', `splitter:${item.Name}`); return item; }); }), routers: computed('chain.Nodes', function() { // Right now there should only ever be one 'Router'. - return getNodesType(get(this, 'chain.Nodes'), 'router'); + return getNodesByType(get(this, 'chain.Nodes'), 'router'); }), routes: computed('chain', 'routers', function() { const routes = get(this, 'routers').reduce(function(prev, item) { From 2dc4d1f5ae249f401de6d522f16b681fb2cda5f6 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Fri, 13 Dec 2019 09:44:18 +0000 Subject: [PATCH 09/14] Fix up Redirects in a temporary way --- ui-v2/app/components/discovery-chain.js | 5 ++-- .../templates/components/resolver-card.hbs | 25 +++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/ui-v2/app/components/discovery-chain.js b/ui-v2/app/components/discovery-chain.js index 968b2c99bcd2..04cf5e97ac87 100644 --- a/ui-v2/app/components/discovery-chain.js +++ b/ui-v2/app/components/discovery-chain.js @@ -48,7 +48,7 @@ const getTargetResolvers = function(dc, nspace = 'default', targets = [], nodes resolvers[item.Service] = { ID: item.ID, Name: item.Service, - Subsets: [], + Children: [], Failover: null, Redirect: null, }; @@ -62,7 +62,7 @@ const getTargetResolvers = function(dc, nspace = 'default', targets = [], nodes const temp = item.ID.split('.'); temp.shift(); resolver.ID = temp.join('.'); - resolver.Subsets.push(item); + resolver.Children.push(item); } if (typeof node.Resolver.Failover !== 'undefined') { // FIXME: Figure out if we can get rid of this @@ -71,6 +71,7 @@ const getTargetResolvers = function(dc, nspace = 'default', targets = [], nodes } else { const res = targetsToFailover([node.Resolver.Target], `service.${nspace}.${dc}`); if (res.Type === 'Datacenter' || res.Type === 'Namespace') { + resolver.Children.push(item); set(failoverable, 'Redirect', true); } } diff --git a/ui-v2/app/templates/components/resolver-card.hbs b/ui-v2/app/templates/components/resolver-card.hbs index 836665a64e49..57de3f8e4c4e 100644 --- a/ui-v2/app/templates/components/resolver-card.hbs +++ b/ui-v2/app/templates/components/resolver-card.hbs @@ -30,20 +30,29 @@ {{/if}} -{{#if (gt item.Subsets.length 0)}} +{{#if (gt item.Children.length 0)}}