import Ember from 'ember'; import Util from 'ui/utils/util'; import ThrottledResize from 'ui/mixins/throttled-resize'; import { activeIcon } from 'ui/service/model'; 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 = $('
').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 = '' + '

'+ envName + Util.escapeHtml(service.get('displayName')) + '

' + '
' + instances + ' container' + (instances === 1 ? '' : 's') + '
' + '
' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '
'; 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(); } }, });