|
1 | 1 | import Ember from 'ember'; |
| 2 | +import Util from 'ui/utils/util'; |
2 | 3 | import ThrottledResize from 'ui/mixins/throttled-resize'; |
3 | 4 |
|
4 | 5 | export default Ember.View.extend(ThrottledResize,{ |
5 | | - cy: null, |
6 | | - cyElem: null, |
| 6 | + graphZoom: null, |
| 7 | + graphInner: null, |
| 8 | + graphOuter: null, |
| 9 | + graphRender: null, |
| 10 | + graph: null, |
| 11 | + |
| 12 | + didInsertElement: function() { |
| 13 | + this._super(); |
| 14 | + var elem = $('<div id="environment-graph"><svg style="width: 100%; height: 100%;"><g/></svg></div>').appendTo('BODY'); |
| 15 | + this.set('graphElem', elem[0]); |
| 16 | + |
| 17 | + Ember.run.later(this,'initGraph',100); |
| 18 | + }, |
7 | 19 |
|
8 | 20 | onResize: function() { |
9 | | - var cy = this.get('cy'); |
10 | | - if ( cy ) |
| 21 | + $('#environment-graph').css('top', $('MAIN').position().top + 55 + 'px'); |
| 22 | + if ( this.get('graph') ) |
11 | 23 | { |
12 | | - setTimeout(function() { |
13 | | - cy.fit(null, 20); |
14 | | - },100); |
| 24 | + this.renderGraph(); |
15 | 25 | } |
16 | 26 | }, |
17 | 27 |
|
18 | | - didInsertElement: function() { |
19 | | - this._super(); |
20 | | - var elem = $('<div id="environment-graph"></div>').appendTo('BODY'); |
21 | | - elem.css('top', $('MAIN').position().top + $('MAIN').height() + 'px'); |
22 | | - this.set('cyElem', elem[0]); |
| 28 | + initGraph: function() { |
| 29 | + var outer = d3.select("#environment-graph svg"); |
| 30 | + var inner = outer.select("g"); |
| 31 | + var zoom = d3.behavior.zoom().on("zoom", function() { |
| 32 | + inner.attr("transform", "translate(" + d3.event.translate + ")" + |
| 33 | + "scale(" + d3.event.scale + ")"); |
| 34 | + }); |
| 35 | + |
| 36 | + outer.call(zoom); |
| 37 | + |
| 38 | + var g = new dagreD3.graphlib.Graph().setGraph({ |
| 39 | + rankdir: "TB", |
| 40 | + nodesep: 50, |
| 41 | + ranksep: 50, |
| 42 | + marginx: 30, |
| 43 | + marginy: 30 |
| 44 | + }); |
| 45 | + |
| 46 | + var render = new dagreD3.render(); |
23 | 47 |
|
24 | | - Ember.run.later(this,'graph',250); |
| 48 | + this.setProperties({ |
| 49 | + graphZoom: zoom, |
| 50 | + graphOuter: outer, |
| 51 | + graphInner: inner, |
| 52 | + graphRender: render, |
| 53 | + graph: g |
| 54 | + }); |
| 55 | + |
| 56 | + this.updateGraph(); |
25 | 57 | }, |
26 | 58 |
|
27 | 59 | updateGraph: function() { |
28 | | - console.log('starting updateGraph'); |
29 | | - var cy = this.get('cy'); |
30 | | - cy.startBatch(); |
31 | | - |
| 60 | + var g = this.get('graph'); |
32 | 61 | var services = this.get('context.services'); |
33 | | - |
34 | | - var relayout = false; |
35 | | - var startNodes = cy.nodes().length; |
36 | | - var startEdges = cy.edges().length; |
37 | | - var expectedNodes = cy.collection(); |
38 | | - var expectedEdges = cy.collection(); |
39 | 62 | var unremovedServices = services.filter(function(service) { |
40 | 63 | return ['removed','purging','purged'].indexOf(service.get('state')) === -1; |
41 | 64 | }); |
42 | 65 |
|
| 66 | + var unexpectedNodes = g.nodes(); |
| 67 | + var unexpectedEdges = g.edges(); |
| 68 | + |
43 | 69 | unremovedServices.forEach(function(service) { |
44 | 70 | var serviceId = service.get('id'); |
45 | | - var node = cy.getElementById(serviceId)[0]; |
46 | | - if ( !node ) |
47 | | - { |
48 | | - relayout = true; |
49 | | - node = cy.add({ |
50 | | - group: 'nodes', |
51 | | - data: { |
52 | | - id: serviceId, |
53 | | - shape: 'ellipse', |
54 | | - }, |
55 | | - position: {x: 0, y: 0} |
56 | | - })[0]; |
57 | | - } |
58 | | - node.data({ |
59 | | - name: service.get('name'), |
60 | | - color: (service.get('state') === 'active' ? '#41c77d' : (service.get('state') === 'inactive' ? '#f00' : '#f3bd24')), |
| 71 | + var color = (service.get('state') === 'active' ? 'green' : (service.get('state') === 'inactive' ? 'red' : 'yellow')); |
| 72 | + var instances = service.get('instances.length')||'No'; |
| 73 | + |
| 74 | + var html = '<i class="icon ss-layergroup"></i>' + |
| 75 | + '<h4>'+ Util.escapeHtml(service.get('name')) + '</h4>' + |
| 76 | + '<h6 class="count"><b>' + instances + '</b> container' + (instances === 1 ? '' : 's') + '</h6>' + |
| 77 | + '<h6><span class="state '+ color +'">' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '</span></h6>'; |
| 78 | + |
| 79 | + g.setNode(serviceId, { |
| 80 | + labelType: "html", |
| 81 | + label: html, |
| 82 | + padding: 0, |
| 83 | + class: color, |
61 | 84 | }); |
62 | 85 |
|
63 | | - expectedNodes = expectedNodes.add(node); |
| 86 | + unexpectedNodes.removeObject(serviceId); |
| 87 | + }); |
64 | 88 |
|
65 | | - var edge; |
| 89 | + unremovedServices.forEach(function(service) { |
| 90 | + var serviceId = service.get('id'); |
66 | 91 | (service.get('consumedservices')||[]).map(function(target) { |
67 | 92 | var targetId = target.get('id'); |
68 | | - edge = cy.edges().filter(function(i, e) { |
69 | | - return e.source().id() === serviceId && e.target().id() === targetId; |
70 | | - })[0]; |
71 | | - |
72 | | - if ( !edge ) |
73 | | - { |
74 | | - relayout = true; |
75 | | - edge = cy.add({ |
76 | | - group: 'edges', |
77 | | - data: { |
78 | | - source: serviceId, |
79 | | - target: targetId, |
80 | | - }, |
81 | | - })[0]; |
82 | | - } |
83 | | - |
84 | | - edge.data({ |
85 | | - color: (target.get('state') === 'active' ? '#41c77d' : (target.get('state') === 'inactive' ? '#f00' : '#f3bd24')), |
| 93 | + var color = (target.get('state') === 'active' ? 'green' : (target.get('state') === 'inactive' ? 'red' : 'yellow')); |
| 94 | + |
| 95 | + g.setEdge(serviceId, targetId, { |
| 96 | + lineInterpolate: 'bundle', |
| 97 | + class: color, |
86 | 98 | }); |
87 | 99 |
|
88 | | - expectedEdges = expectedEdges.add(edge); |
| 100 | + var existing = unexpectedEdges.filter(function(edge) { |
| 101 | + return edge.v === serviceId && edge.w === targetId; |
| 102 | + }); |
| 103 | + unexpectedEdges.removeObjects(existing); |
89 | 104 | }); |
90 | 105 | }); |
91 | 106 |
|
92 | | - // Remove nodes & edges that shouldn't be there |
93 | | - cy.nodes().not(expectedNodes).remove(); |
94 | | - cy.edges().not(expectedEdges).remove(); |
95 | | - console.log('end batch'); |
96 | | - cy.endBatch(); |
97 | | - if ( relayout || startNodes !== cy.nodes().length || startEdges !== cy.edges().length ) |
98 | | - { |
99 | | - console.log('layout'); |
100 | | - cy.layout(); |
101 | | - console.log('end layout'); |
102 | | - } |
103 | | - console.log('done updateGraph'); |
104 | | - }, |
105 | | - |
106 | | - throttledUpdateGraph: function() { |
107 | | - Ember.run.throttle(this,'updateGraph',500); |
108 | | - }.observes('context.services.@each.{id,name,state,consumedServicesUpdated}'), |
| 107 | + // Remove nodes & edges that shouldn't be there anymore |
| 108 | + unexpectedNodes.forEach(function(node) { |
| 109 | + g.removeNode(node); |
| 110 | + }); |
109 | 111 |
|
110 | | - graph: function() { |
111 | | - var style = cytoscape.stylesheet() |
112 | | - .selector('node') |
113 | | - .css({ |
114 | | - 'shape': 'data(shape)', |
115 | | - 'content': 'data(name)', |
116 | | - 'text-valign': 'center', |
117 | | - 'text-outline-width': 2, |
118 | | - 'text-outline-color': 'data(color)', |
119 | | - 'border-width': 2, |
120 | | - 'border-color': '#f4f5f8', |
121 | | - 'background-color': 'data(color)', |
122 | | - 'font-family': '"Open Sans"', |
123 | | - 'font-weight': 300, |
124 | | - 'font-size': 13, |
125 | | - 'color': '#fff', |
126 | | - }) |
127 | | - .selector(':selected') |
128 | | - .css({ |
129 | | - 'border-width': 3, |
130 | | - 'border-color': '#333' |
131 | | - }) |
132 | | - .selector('edge') |
133 | | - .css({ |
134 | | - 'opacity': 0.8, |
135 | | - 'width': 'mapData(strength, 70, 100, 2, 6)', |
136 | | - 'target-arrow-shape': 'triangle-backcurve', |
137 | | - 'source-arrow-shape': 'none', |
138 | | - 'line-color': 'data(color)', |
139 | | - 'source-arrow-color': 'data(color)', |
140 | | - 'target-arrow-color': 'data(color)' |
141 | | - }) |
142 | | - .selector('edge.questionable') |
143 | | - .css({ |
144 | | - 'line-style': 'dotted', |
145 | | - 'target-arrow-shape': 'diamond' |
146 | | - }) |
147 | | - .selector('.faded') |
148 | | - .css({ |
149 | | - 'opacity': 0.25, |
150 | | - 'text-opacity': 0 |
151 | | - }); |
152 | | - // End: style |
153 | | - |
154 | | - var cy = cytoscape({ |
155 | | - container: this.get('cyElem'), |
156 | | - style: style, |
157 | | - userZoomingEnabled: false, |
158 | | - userPanningEnabled: false, |
159 | | - layout: { |
160 | | - name: 'dagre', |
161 | | - animate: false, |
162 | | - padding: 20, |
163 | | - edgeSep: 20, |
164 | | - }, |
| 112 | + unexpectedEdges.forEach(function(edge) { |
| 113 | + g.removeNode(edge.v, edge.w); |
165 | 114 | }); |
166 | 115 |
|
167 | | - this.set('cy', cy); |
168 | | - this.updateGraph(); |
| 116 | + this.renderGraph(); |
169 | 117 | }, |
170 | 118 |
|
| 119 | + renderGraph: function() { |
| 120 | + var zoom = this.get('graphZoom'); |
| 121 | + var render = this.get('graphRender'); |
| 122 | + var inner = this.get('graphInner'); |
| 123 | + var outer = this.get('graphOuter'); |
| 124 | + var g = this.get('graph'); |
| 125 | + |
| 126 | + inner.call(render, g); |
| 127 | + |
| 128 | + // Zoom and scale to fit |
| 129 | + var zoomScale = zoom.scale(); |
| 130 | + var graphWidth = g.graph().width; |
| 131 | + var graphHeight = g.graph().height; |
| 132 | + var width = $('#environment-graph').width(); |
| 133 | + var height = $('#environment-graph').height(); |
| 134 | + zoomScale = Math.min(2.0, Math.min(width / graphWidth, height / graphHeight)); |
| 135 | + var translate = [(width/2) - ((graphWidth*zoomScale)/2), (height/2) - ((graphHeight*zoomScale)/2)]; |
| 136 | + zoom.translate(translate); |
| 137 | + zoom.scale(zoomScale); |
| 138 | + zoom.event(outer); |
| 139 | + |
| 140 | + // Overflow the foreignObjects |
| 141 | + $(this.get('graphElem').getElementsByTagName('foreignObject')).css('overflow','visible'); |
| 142 | + }, |
| 143 | + |
| 144 | + throttledUpdateGraph: function() { |
| 145 | + Ember.run.throttle(this,'updateGraph',250); |
| 146 | + }.observes('context.services.@each.{id,name,state,consumedServicesUpdated}'), |
| 147 | + |
171 | 148 | willDestroyElement: function() { |
172 | 149 | this._super(); |
173 | | - var cy = this.get('cy'); |
174 | | - if ( cy ) |
175 | | - { |
176 | | - cy.destroy(); |
177 | | - } |
178 | | - |
179 | | - var elem = this.get('cyElem'); |
| 150 | + var elem = this.get('graphElem'); |
180 | 151 | if ( elem ) |
181 | 152 | { |
182 | 153 | $(elem).remove(); |
|
0 commit comments