ui/app/environment/graph/view.js

197 lines
6.1 KiB
JavaScript

import Ember from 'ember';
import Util from 'ui/utils/util';
import ThrottledResize from 'ui/mixins/throttled-resize';
import { activeIcon } from 'ui/models/service';
export default Ember.View.extend(ThrottledResize,{
classNames: ['environment-graph'],
graphZoom: null,
graphInner: null,
graphOuter: null,
graphRender: null,
graph: null,
didInsertElement: function() {
this._super();
var elem = $('<div id="environment-svg"><svg style="width: 100%; height: 100%;"><g/></svg></div>').appendTo('BODY');
this.set('graphElem', elem[0]);
Ember.run.later(this,'initGraph',100);
},
onResize: function() {
$('#environment-svg').css('top', $('MAIN').position().top + 55 + 'px');
if ( this.get('graph') )
{
this.renderGraph();
}
},
initGraph: function() {
var outer = d3.select("#environment-svg svg");
var inner = outer.select("g");
var zoom = d3.behavior.zoom().on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
});
outer.call(zoom);
var g = new dagreD3.graphlib.Graph().setGraph({
rankdir: "TB",
nodesep: 50,
ranksep: 50,
marginx: 30,
marginy: 30
});
var render = new dagreD3.render();
this.setProperties({
graphZoom: zoom,
graphOuter: outer,
graphInner: inner,
graphRender: render,
graph: g
});
this.updateGraph();
},
crosslinkServices: function() {
// Add services that are cross-linked from another environment
var out = [];
var unremovedServices = this.get('context.model.services').filter(function(service) {
return ['removed','purging','purged'].indexOf(service.get('state')) === -1;
});
unremovedServices.forEach((service) => {
var externals = (service.get('consumedServicesWithNames')||[]).filter((linked) => {
return linked.get('service.environmentId') !== this.get('context.model.id');
}).map((linked) => { return linked.get('service'); });
out.pushObjects(externals);
});
return out;
}.property('context.model.services.@each.consumedServicesUpdated'),
updateGraph: function() {
var g = this.get('graph');
var services = this.get('context.model.services');
var unremovedServices = services.filter(function(service) {
return ['removed','purging','purged'].indexOf(service.get('state')) === -1;
});
var unexpectedNodes = g.nodes();
var unexpectedEdges = g.edges();
var expectedServices = unremovedServices.slice();
expectedServices.pushObjects(this.get('crosslinkServices'));
expectedServices.forEach((service) => {
var serviceId = service.get('id');
var color = (service.get('state') === 'active' ? 'green' : (service.get('state') === 'inactive' ? 'red' : 'yellow'));
var instances = service.get('instances.length')||'No';
var isCrossLink = service.get('environmentId') !== this.get('context.model.id');
var envName = '';
if ( isCrossLink )
{
envName = service.get('displayEnvironment') + '/';
}
var html = '<i class="icon '+ activeIcon(service) +'"></i>' +
'<h4 class="clip">'+ envName + Util.escapeHtml(service.get('displayName')) + '</h4>' +
'<h6 class="count"><b>' + instances + '</b> container' + (instances === 1 ? '' : 's') + '</h6>' +
'<h6><span class="state '+ color +'">' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '</span></h6>';
g.setNode(serviceId, {
labelType: "html",
label: html,
padding: 0,
class: color + (isCrossLink ? ' crosslink' : ''),
});
unexpectedNodes.removeObject(serviceId);
});
unremovedServices.forEach(function(service) {
var serviceId = service.get('id');
(service.get('consumedServicesWithNames')||[]).map(function(map) {
var target = map.get('service');
var targetId = target.get('id');
var color = (target.get('state') === 'active' ? 'green' : (target.get('state') === 'inactive' ? 'red' : 'yellow'));
var edgeOpts = {
arrowhead: 'vee',
lineInterpolate: 'bundle',
class: color,
};
var mapName = map.get('name');
if ( mapName && mapName !== target.get('name') )
{
edgeOpts.label = mapName;
}
g.setEdge(serviceId, targetId, edgeOpts);
var existing = unexpectedEdges.filter(function(edge) {
return edge.v === serviceId && edge.w === targetId;
});
unexpectedEdges.removeObjects(existing);
});
});
// Remove nodes & edges that shouldn't be there anymore
unexpectedNodes.forEach(function(node) {
g.removeNode(node);
});
unexpectedEdges.forEach(function(edge) {
g.removeEdge(edge.v, edge.w);
});
this.renderGraph();
},
renderGraph: function() {
var zoom = this.get('graphZoom');
var render = this.get('graphRender');
var inner = this.get('graphInner');
var outer = this.get('graphOuter');
var g = this.get('graph');
inner.call(render, g);
// Zoom and scale to fit
var zoomScale = zoom.scale();
var graphWidth = g.graph().width;
var graphHeight = g.graph().height;
var width = $('#environment-svg').width();
var height = $('#environment-svg').height();
zoomScale = Math.min(2.0, Math.min(width / graphWidth, height / graphHeight));
var translate = [(width/2) - ((graphWidth*zoomScale)/2), (height/2) - ((graphHeight*zoomScale)/2)];
zoom.translate(translate);
zoom.scale(zoomScale);
zoom.event(outer);
// Overflow the foreignObjects
$(this.get('graphElem').getElementsByTagName('foreignObject')).css('overflow','visible');
},
throttledUpdateGraph: function() {
Ember.run.throttle(this,'updateGraph',250);
}.observes('context.model.services.@each.{id,name,state,consumedServicesUpdated}','crosslinkServices.@each.{id,name,state,displayEnvironment}'),
willDestroyElement: function() {
this._super();
var elem = this.get('graphElem');
if ( elem )
{
$(elem).remove();
}
},
});