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.Component.extend(ThrottledResize, { classNames: ['stack-graph'], graphZoom: null, graphInner: null, graphOuter: null, graphRender: null, graph: null, stackId: null, didInsertElement: function() { this._super(); this.bootstrap(); }, bootstrap: function() { var elem = $('
').appendTo('BODY'); this.set('graphElem', elem[0]); this.set('stackId', this.get('model.stack.id')); if (this.get('model.stack.services.length')) { Ember.run.later(this,'initGraph',100); } else { this.sendAction('setNoServices', true); } }, click: function(evt) { if ($(evt.target).hasClass('icon-x')) { this.styleSvg(); } }, onResize: function() { $('#stack-svg').css('top', $('MAIN').position().top + 55 + 'px'); $('#stack-svg').css('bottom', $('FOOTER').outerHeight() + 'px'); if ( this.get('graph') ) { this.renderGraph(); } }, initGraph: function() { var outer = d3.select("#stack-svg svg"); // SVG must be lowercase! This is case sensitive in not-Chrome 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(); $(outer[0]).on('click', (event) => { if ( this.isDestroyed || this.isDestroying ) { return; } var fo = $(event.target).closest('foreignObject'); if ( fo ) { if ( this.get('currentFO') ) { this.get('currentFO').find('>div').removeClass('highlighted'); this.set('prevFO', this.get('currentFO')); this.set('currentFO', fo); fo.find('>div').addClass('highlighted'); } else { this.set('currentFO', fo); fo.find('>div').addClass('highlighted'); } var serviceId = $('span[data-service]', fo).data('service'); if ( serviceId ) { this.showService(serviceId); return; } } this.showService(null); this.setProperties({ currentFO: null, prevFO: null, }); }); }, styleSvg: function(height='100%') { $('#stack-svg svg').css('height', height); }, showService: function() { let svgHeight = $('#stack-svg').height() - 0; // svg minus the height of info service-addtl-info.scss this.styleSvg(svgHeight); }, crosslinkServices: function() { // Add services that are cross-linked from another stack var out = []; this.get('model.stack.services').forEach((service) => { var externals = (service.get('consumedServicesWithNames')||[]).filter((linked) => { return linked.get('service.stackId') !== this.get('model.stack.id'); }).map((linked) => { return linked.get('service'); }); out.pushObjects(externals); }); return out; }.property('model.stack.services.@each.consumedServicesWithNames'), updateGraph: function() { var g = this.get('graph'); var services = this.get('model.stack.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 instances = service.get('instances.length')||'No'; var color = service.get('stateColor').replace('text-',''); var isCrossLink = service.get('stackId') !== this.get('model.stack.id'); var stackName = ''; if ( isCrossLink ) { stackName = service.get('displayStack') + '/'; } var html = `