4c3fbebefd
* ui: Move components to the new nested structure * Move data-test attribute to the correct HTML element We don't currently rely on this, but was incorrectly placed on the input rather than the label tag * Fix up left over curly bracket components that were causing issues For some reason the combination of: 1. Old style curly bracket components 2. data-test-* attributes 3. Moving to the new component file structure Meant that our data-test-* selectors where no longer being rendered. Whilst this had no effect on the app, it meant our tests suite could no longer select DOM elements in order to assert various things. Moving the old style curly bracket components to the new style XML/Angle bracket format fixes the issue * Update ui-v2/app/templates/dc/nodes/-services.hbs Co-Authored-By: Greg Hoin <1416421+gregone@users.noreply.github.com> * Update ui-v2/app/templates/dc/nodes/-services.hbs Co-Authored-By: Greg Hoin <1416421+gregone@users.noreply.github.com> Co-authored-by: Greg Hoin <1416421+gregone@users.noreply.github.com>
197 lines
6.3 KiB
JavaScript
197 lines
6.3 KiB
JavaScript
import Component from '@ember/component';
|
|
import { inject as service } from '@ember/service';
|
|
import { set, get, computed } from '@ember/object';
|
|
|
|
import {
|
|
createRoute,
|
|
getSplitters,
|
|
getRoutes,
|
|
getResolvers,
|
|
} from 'consul-ui/utils/components/discovery-chain/index';
|
|
|
|
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 => {
|
|
if (get(this, 'isDisplayed') !== 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 getSplitters(get(this, 'chain.Nodes'));
|
|
}),
|
|
routes: computed('chain.Nodes', function() {
|
|
const routes = getRoutes(get(this, 'chain.Nodes'), this.dom.guid);
|
|
// if we have no routes with a PathPrefix of '/' or one with no definition at all
|
|
// then add our own 'default catch all'
|
|
if (
|
|
!routes.find(item => get(item, 'Definition.Match.HTTP.PathPrefix') === '/') &&
|
|
!routes.find(item => typeof item.Definition === 'undefined')
|
|
) {
|
|
let nextNode;
|
|
const resolverID = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
|
|
const splitterID = `splitter:${this.chain.ServiceName}.${this.chain.Namespace}`;
|
|
if (typeof this.chain.Nodes[resolverID] !== 'undefined') {
|
|
nextNode = resolverID;
|
|
} else if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
|
|
nextNode = splitterID;
|
|
}
|
|
if (typeof nextNode !== 'undefined') {
|
|
routes.push({
|
|
Default: true,
|
|
ID: `route:${this.chain.ServiceName}`,
|
|
Name: this.chain.ServiceName,
|
|
Definition: {
|
|
Match: {
|
|
HTTP: {
|
|
PathPrefix: '/',
|
|
},
|
|
},
|
|
},
|
|
NextNode: nextNode,
|
|
});
|
|
}
|
|
}
|
|
return routes;
|
|
}),
|
|
resolvers: computed('chain.{Nodes,Targets}', function() {
|
|
return getResolvers(
|
|
this.chain.Datacenter,
|
|
this.chain.Namespace,
|
|
get(this, 'chain.Targets'),
|
|
get(this, 'chain.Nodes')
|
|
);
|
|
}),
|
|
graph: computed('splitters', 'routes', function() {
|
|
const graph = this.dataStructs.graph();
|
|
const router = this.chain.ServiceName;
|
|
this.splitters.forEach(item => {
|
|
item.Splits.forEach(splitter => {
|
|
graph.addLink(item.ID, splitter.NextNode);
|
|
});
|
|
});
|
|
this.routes.forEach((route, i) => {
|
|
route = createRoute(route, router, this.dom.guid);
|
|
graph.addLink(route.ID, route.NextNode);
|
|
});
|
|
return graph;
|
|
}),
|
|
selected: computed('selectedId', 'graph', function() {
|
|
if (this.selectedId === '' || !this.dom.element(`#${this.selectedId}`)) {
|
|
return {};
|
|
}
|
|
const id = this.selectedId;
|
|
const type = id.split(':').shift();
|
|
const nodes = [id];
|
|
const edges = [];
|
|
this.graph.forEachLinkedNode(id, (linkedNode, link) => {
|
|
nodes.push(linkedNode.id);
|
|
edges.push(`${link.fromId}>${link.toId}`);
|
|
this.graph.forEachLinkedNode(linkedNode.id, (linkedNode, link) => {
|
|
const nodeType = linkedNode.id.split(':').shift();
|
|
if (type !== nodeType && type !== 'splitter' && nodeType !== 'splitter') {
|
|
nodes.push(linkedNode.id);
|
|
edges.push(`${link.fromId}>${link.toId}`);
|
|
}
|
|
});
|
|
});
|
|
return {
|
|
nodes: nodes.map(item => `#${CSS.escape(item)}`),
|
|
edges: edges.map(item => `#${CSS.escape(item)}`),
|
|
};
|
|
}),
|
|
width: computed('isDisplayed', 'chain.{Nodes,Targets}', function() {
|
|
return this.element.offsetWidth;
|
|
}),
|
|
height: computed('isDisplayed', 'chain.{Nodes,Targets}', function() {
|
|
return this.element.offsetHeight;
|
|
}),
|
|
// 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() {
|
|
this._listeners.remove();
|
|
this._listeners.add(this.dom.document(), {
|
|
click: e => {
|
|
// all route/splitter/resolver components currently
|
|
// have classes that end in '-card'
|
|
if (!this.dom.closest('[class$="-card"]', e.target)) {
|
|
set(this, 'active', false);
|
|
set(this, 'selectedId', '');
|
|
}
|
|
},
|
|
});
|
|
[...this.dom.elements('path.split', this.element)].forEach(item => {
|
|
this._listeners.add(item, {
|
|
mouseover: e => this.actions.showSplit.apply(this, [e]),
|
|
mouseout: e => this.actions.hideSplit.apply(this, [e]),
|
|
});
|
|
});
|
|
// TODO: currently don't think there is a way to listen
|
|
// for an element being removed inside a component, possibly
|
|
// using IntersectionObserver. It's a tiny detail, but we just always
|
|
// remove the tooltip on component update as its so tiny, ideal
|
|
// the tooltip would stay if there was no change to the <path>
|
|
// set(this, 'activeTooltip', false);
|
|
},
|
|
actions: {
|
|
showSplit: function(e) {
|
|
this.setProperties({
|
|
x: e.offsetX,
|
|
y: e.offsetY - 5,
|
|
tooltip: e.target.dataset.percentage,
|
|
activeTooltip: true,
|
|
});
|
|
},
|
|
hideSplit: function(e = null) {
|
|
set(this, 'activeTooltip', false);
|
|
},
|
|
click: function(e) {
|
|
const id = e.currentTarget.getAttribute('id');
|
|
if (id === this.selectedId) {
|
|
set(this, 'active', false);
|
|
set(this, 'selectedId', '');
|
|
} else {
|
|
set(this, 'active', true);
|
|
set(this, 'selectedId', id);
|
|
}
|
|
},
|
|
},
|
|
});
|