Simon Hunt

GUI -- Implemented Instance Panel.

- handling addInstance event.

Change-Id: Ic98a3291bd37ecf1155dbe1696167d0635a31972
...@@ -75,7 +75,8 @@ ...@@ -75,7 +75,8 @@
75 append: appendPanel, 75 append: appendPanel,
76 width: panelWidth, 76 width: panelWidth,
77 height: panelHeight, 77 height: panelHeight,
78 - isVisible: panelIsVisible 78 + isVisible: panelIsVisible,
79 + el: panelEl
79 }; 80 };
80 81
81 p.el = panelLayer.append('div') 82 p.el = panelLayer.append('div')
...@@ -136,6 +137,10 @@ ...@@ -136,6 +137,10 @@
136 return p.on; 137 return p.on;
137 } 138 }
138 139
140 + function panelEl() {
141 + return p.el;
142 + }
143 +
139 return api; 144 return api;
140 } 145 }
141 146
......
...@@ -218,8 +218,7 @@ ...@@ -218,8 +218,7 @@
218 if (xns) { 218 if (xns) {
219 atr.transform = sus.translate(trans); 219 atr.transform = sus.translate(trans);
220 } 220 }
221 - elem.append('use').attr(atr).classed('overlay', ovr); 221 + return elem.append('use').attr(atr).classed('overlay', ovr);
222 -
223 } 222 }
224 223
225 // ---------------------------------------------------------------------- 224 // ----------------------------------------------------------------------
......
...@@ -143,11 +143,16 @@ ...@@ -143,11 +143,16 @@
143 return 'translate(' + x + ',' + y + ')'; 143 return 'translate(' + x + ',' + y + ')';
144 } 144 }
145 145
146 + function stripPx(s) {
147 + return s.replace(/px$/,'');
148 + }
149 +
146 return { 150 return {
147 createDragBehavior: createDragBehavior, 151 createDragBehavior: createDragBehavior,
148 loadGlow: loadGlow, 152 loadGlow: loadGlow,
149 cat7: cat7, 153 cat7: cat7,
150 - translate: translate 154 + translate: translate,
155 + stripPx: stripPx
151 }; 156 };
152 }]); 157 }]);
153 }()); 158 }());
......
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
78 <script src="view/topo/topoEvent.js"></script> 78 <script src="view/topo/topoEvent.js"></script>
79 <script src="view/topo/topoForce.js"></script> 79 <script src="view/topo/topoForce.js"></script>
80 <script src="view/topo/topoPanel.js"></script> 80 <script src="view/topo/topoPanel.js"></script>
81 + <script src="view/topo/topoInst.js"></script>
81 <script src="view/device/device.js"></script> 82 <script src="view/device/device.js"></script>
82 <!-- TODO: inject javascript refs server-side --> 83 <!-- TODO: inject javascript refs server-side -->
83 84
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
43 } 43 }
44 44
45 45
46 -/* --- Topo Panels --- */ 46 +/* --- Topo Summary Panel --- */
47 47
48 #topo-p-summary { 48 #topo-p-summary {
49 /* Base css from panel.css */ 49 /* Base css from panel.css */
...@@ -107,3 +107,78 @@ ...@@ -107,3 +107,78 @@
107 background-color: #888; 107 background-color: #888;
108 color: #888; 108 color: #888;
109 } 109 }
110 +
111 +
112 +/* --- Topo Detail Panel --- */
113 +
114 +/* TODO: add CSS rules */
115 +
116 +
117 +/* --- Topo Instance Panel --- */
118 +
119 +#topo-p-instance {
120 + height: 100px;
121 +}
122 +
123 +#topo-p-instance div.onosInst {
124 + display: inline-block;
125 + width: 170px;
126 + height: 85px;
127 + cursor: pointer;
128 +}
129 +
130 +#topo-p-instance svg rect {
131 + fill: #ccc;
132 + stroke: #aaa;
133 + stroke-width: 3.5;
134 +}
135 +#topo-p-instance .online svg rect {
136 + opacity: 1;
137 + fill: #9cf;
138 + stroke: #555;
139 +}
140 +
141 +#topo-p-instance svg .glyph {
142 + fill: #888;
143 + fill-rule: evenodd;
144 +}
145 +#topo-p-instance .online svg .glyph {
146 + fill: #000;
147 +}
148 +
149 +#topo-p-instance svg .badgeIcon {
150 + fill: #777;
151 + fill-rule: evenodd;
152 +}
153 +
154 +#topo-p-instance .online svg .badgeIcon {
155 + fill: #fff;
156 +}
157 +
158 +#topo-p-instance svg text {
159 + text-anchor: middle;
160 + fill: #777;
161 +}
162 +#topo-p-instance .online svg text {
163 + fill: #eee;
164 +}
165 +
166 +#topo-p-instance svg text.instTitle {
167 + font-size: 11pt;
168 + font-weight: bold;
169 +}
170 +#topo-p-instance svg text.instLabel {
171 + font-size: 9pt;
172 + font-style: italic;
173 +}
174 +
175 +#topo-p-instance .onosInst.mastership {
176 + opacity: 0.3;
177 +}
178 +#topo-p-instance .onosInst.mastership.affinity {
179 + opacity: 1.0;
180 +}
181 +#topo-p-instance .onosInst.mastership.affinity svg rect {
182 + /* TODO: add blue glow */
183 + /*filter: url(#blue-glow);*/
184 +}
......
...@@ -143,12 +143,13 @@ ...@@ -143,12 +143,13 @@
143 143
144 .controller('OvTopoCtrl', [ 144 .controller('OvTopoCtrl', [
145 '$scope', '$log', '$location', '$timeout', 145 '$scope', '$log', '$location', '$timeout',
146 - 'FnService', 'MastService', 146 + 'FnService', 'MastService', 'KeyService', 'ZoomService',
147 - 'KeyService', 'ZoomService', 'GlyphService', 'MapService', 147 + 'GlyphService', 'MapService',
148 'TopoEventService', 'TopoForceService', 'TopoPanelService', 148 'TopoEventService', 'TopoForceService', 'TopoPanelService',
149 + 'TopoInstService',
149 150
150 function ($scope, _$log_, $loc, $timeout, _fs_, mast, 151 function ($scope, _$log_, $loc, $timeout, _fs_, mast,
151 - _ks_, _zs_, _gs_, _ms_, tes, _tfs_, tps) { 152 + _ks_, _zs_, _gs_, _ms_, tes, _tfs_, tps, tis) {
152 var self = this; 153 var self = this;
153 $log = _$log_; 154 $log = _$log_;
154 fs = _fs_; 155 fs = _fs_;
...@@ -167,6 +168,7 @@ ...@@ -167,6 +168,7 @@
167 $log.log('OvTopoCtrl is saying Buh-Bye!'); 168 $log.log('OvTopoCtrl is saying Buh-Bye!');
168 tes.closeSock(); 169 tes.closeSock();
169 tps.destroyPanels(); 170 tps.destroyPanels();
171 + tis.destroyInst();
170 }); 172 });
171 173
172 // svg layer and initialization of components 174 // svg layer and initialization of components
...@@ -181,6 +183,7 @@ ...@@ -181,6 +183,7 @@
181 setUpMap(); 183 setUpMap();
182 setUpForce(); 184 setUpForce();
183 185
186 + tis.initInst();
184 tps.initPanels(); 187 tps.initPanels();
185 tes.openSock(); 188 tes.openSock();
186 189
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, wss, wes, tps; 26 + var $log, wss, wes, tps, tis;
27 27
28 // internal state 28 // internal state
29 var wsock; 29 var wsock;
...@@ -47,7 +47,8 @@ ...@@ -47,7 +47,8 @@
47 } 47 }
48 48
49 function addInstance(ev) { 49 function addInstance(ev) {
50 - $log.debug(' *** We got an ADD INSTANCE event: ', ev); 50 + $log.debug(' **** Add Instance **** ', ev.payload);
51 + tis.addInstance(ev.payload);
51 } 52 }
52 53
53 // ========================== 54 // ==========================
...@@ -87,13 +88,14 @@ ...@@ -87,13 +88,14 @@
87 angular.module('ovTopo') 88 angular.module('ovTopo')
88 .factory('TopoEventService', 89 .factory('TopoEventService',
89 ['$log', '$location', 'WebSocketService', 'WsEventService', 90 ['$log', '$location', 'WebSocketService', 'WsEventService',
90 - 'TopoPanelService', 91 + 'TopoPanelService', 'TopoInstService',
91 92
92 - function (_$log_, $loc, _wss_, _wes_, _tps_) { 93 + function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_) {
93 $log = _$log_; 94 $log = _$log_;
94 wss = _wss_; 95 wss = _wss_;
95 wes = _wes_; 96 wes = _wes_;
96 tps = _tps_; 97 tps = _tps_;
98 + tis = _tis_;
97 99
98 function bindDispatcher(TopoDomElementsPassedHere) { 100 function bindDispatcher(TopoDomElementsPassedHere) {
99 // TODO: store refs to topo DOM elements... 101 // TODO: store refs to topo DOM elements...
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/*
18 + ONOS GUI -- Topology Instances Module.
19 + Defines modeling of ONOS instances.
20 + */
21 +
22 +(function () {
23 + 'use strict';
24 +
25 + // injected refs
26 + var $log, ps, sus, gs;
27 +
28 + // configuration
29 + var instCfg = {
30 + rectPad: 8,
31 + nodeOx: 9,
32 + nodeOy: 9,
33 + nodeDim: 40,
34 + birdOx: 19,
35 + birdOy: 21,
36 + birdDim: 21,
37 + uiDy: 45,
38 + titleDy: 30,
39 + textYOff: 20,
40 + textYSpc: 15
41 + },
42 + showLogicErrors = true,
43 + idIns = 'topo-p-instance',
44 + instOpts = {
45 + edge: 'left',
46 + width: 20
47 + };
48 +
49 + // internal state
50 + var onosInstances,
51 + onosOrder,
52 + oiShowMaster,
53 + oiBox;
54 +
55 +
56 + // ==========================
57 + // *** ADD INSTANCE ***
58 +
59 + function addInstance(data) {
60 + var id = data.id;
61 +
62 + if (onosInstances[id]) {
63 + updateInstance(data);
64 + return;
65 + }
66 + onosInstances[id] = data;
67 + onosOrder.push(data);
68 + updateInstances();
69 + }
70 +
71 + function updateInstance(data) {
72 + var id = data.id,
73 + d = onosInstances[id];
74 + if (d) {
75 + angular.extend(d, data);
76 + updateInstances();
77 + } else {
78 + logicError('updateInstance: lookup fail: ID = "' + id + '"');
79 + }
80 + }
81 +
82 + function computeDim(self) {
83 + var css = window.getComputedStyle(self);
84 + return {
85 + w: sus.stripPx(css.width),
86 + h: sus.stripPx(css.height)
87 + };
88 + }
89 +
90 + function clickInst(d) {
91 + var el = d3.select(this),
92 + aff = el.classed('affinity');
93 + if (!aff) {
94 + setAffinity(el, d);
95 + } else {
96 + cancelAffinity();
97 + }
98 + }
99 +
100 + function setAffinity(el, d) {
101 + d3.selectAll('.onosInst')
102 + .classed('mastership', true)
103 + .classed('affinity', false);
104 + el.classed('affinity', true);
105 +
106 + // TODO: suppress the layers and highlight only specific nodes...
107 + //suppressLayers(true);
108 + //node.each(function (n) {
109 + // if (n.master === d.id) {
110 + // n.el.classed('suppressed', false);
111 + // }
112 + //});
113 + oiShowMaster = true;
114 + }
115 +
116 + function cancelAffinity() {
117 + d3.selectAll('.onosInst')
118 + .classed('mastership affinity', false);
119 +
120 + // TODO: restore layer state
121 + //restoreLayerState();
122 + oiShowMaster = false;
123 + }
124 +
125 + function instRectAttr(dim) {
126 + var pad = instCfg.rectPad;
127 + return {
128 + x: pad,
129 + y: pad,
130 + width: dim.w - pad*2,
131 + height: dim.h - pad*2,
132 + rx: 6
133 + };
134 + }
135 +
136 + function viewBox(dim) {
137 + return '0 0 ' + dim.w + ' ' + dim.h;
138 + }
139 +
140 + function attachUiBadge(svg) {
141 + gs.addGlyph(svg, 'uiAttached', 30, true, [12, instCfg.uiDy])
142 + .classed('badgeIcon uiBadge', true);
143 + }
144 +
145 + function instColor(id, online) {
146 + // TODO: fix this..
147 + //return cat7.get(id, !online, network.view.getTheme());
148 + return 'blue';
149 + }
150 +
151 + // ==============================
152 +
153 + function updateInstances() {
154 + var onoses = oiBox.el().selectAll('.onosInst')
155 + .data(onosOrder, function (d) { return d.id; }),
156 + instDim = {w:0,h:0},
157 + c = instCfg;
158 +
159 + function nSw(n) {
160 + return '# Switches: ' + n;
161 + }
162 +
163 + // operate on existing onos instances if necessary
164 + onoses.each(function (d) {
165 + var el = d3.select(this),
166 + svg = el.select('svg');
167 + instDim = computeDim(this);
168 +
169 + // update online state
170 + el.classed('online', d.online);
171 +
172 + // update ui-attached state
173 + svg.select('use.uiBadge').remove();
174 + if (d.uiAttached) {
175 + attachUiBadge(svg);
176 + }
177 +
178 + function updAttr(id, value) {
179 + svg.select('text.instLabel.'+id).text(value);
180 + }
181 +
182 + updAttr('ip', d.ip);
183 + updAttr('ns', nSw(d.switches));
184 + });
185 +
186 +
187 + // operate on new onos instances
188 + var entering = onoses.enter()
189 + .append('div')
190 + .attr('class', 'onosInst')
191 + .classed('online', function (d) { return d.online; })
192 + .on('click', clickInst);
193 +
194 + entering.each(function (d) {
195 + var el = d3.select(this),
196 + rectAttr,
197 + svg;
198 + instDim = computeDim(this);
199 + rectAttr = instRectAttr(instDim);
200 +
201 + svg = el.append('svg').attr({
202 + width: instDim.w,
203 + height: instDim.h,
204 + viewBox: viewBox(instDim)
205 + });
206 +
207 + svg.append('rect').attr(rectAttr);
208 +
209 + gs.addGlyph(svg, 'bird', 28, true, [14, 14])
210 + .classed('badgeIcon', true);
211 +
212 + if (d.uiAttached) {
213 + attachUiBadge(svg);
214 + }
215 +
216 + var left = c.nodeOx + c.nodeDim,
217 + len = rectAttr.width - left,
218 + hlen = len / 2,
219 + midline = hlen + left;
220 +
221 + // title
222 + svg.append('text')
223 + .attr({
224 + class: 'instTitle',
225 + x: midline,
226 + y: c.titleDy
227 + })
228 + .text(d.id);
229 +
230 + // a couple of attributes
231 + var ty = c.titleDy + c.textYOff;
232 +
233 + function addAttr(id, label) {
234 + svg.append('text').attr({
235 + class: 'instLabel ' + id,
236 + x: midline,
237 + y: ty
238 + }).text(label);
239 + ty += c.textYSpc;
240 + }
241 +
242 + addAttr('ip', d.ip);
243 + addAttr('ns', nSw(d.switches));
244 + });
245 +
246 + // operate on existing + new onoses here
247 + // set the affinity colors...
248 + onoses.each(function (d) {
249 + var el = d3.select(this),
250 + rect = el.select('svg').select('rect'),
251 + col = instColor(d.id, d.online);
252 + rect.style('fill', col);
253 + });
254 +
255 + // adjust the panel size appropriately...
256 + oiBox.width(instDim.w * onosOrder.length);
257 + oiBox.height(instDim.h);
258 +
259 + // remove any outgoing instances
260 + onoses.exit().remove();
261 + }
262 +
263 +
264 + // ==========================
265 +
266 + function logicError(msg) {
267 + if (showLogicErrors) {
268 + $log.warn('TopoInstService: ' + msg);
269 + }
270 + }
271 +
272 + function initInst() {
273 + oiBox = ps.createPanel(idIns, instOpts);
274 + oiBox.show();
275 +
276 + onosInstances = {};
277 + onosOrder = [];
278 + oiShowMaster = false;
279 + }
280 +
281 + function destroyInst() {
282 + ps.destroyPanel(idIns);
283 + oiBox = null;
284 + }
285 +
286 + // ==========================
287 +
288 + angular.module('ovTopo')
289 + .factory('TopoInstService',
290 + ['$log', 'PanelService', 'SvgUtilService', 'GlyphService',
291 +
292 + function (_$log_, _ps_, _sus_, _gs_) {
293 + $log = _$log_;
294 + ps = _ps_;
295 + sus = _sus_;
296 + gs = _gs_;
297 +
298 + return {
299 + initInst: initInst,
300 + destroyInst: destroyInst,
301 + addInstance: addInstance
302 + };
303 + }]);
304 +}());
...@@ -28,43 +28,20 @@ ...@@ -28,43 +28,20 @@
28 // constants 28 // constants
29 var idSum = 'topo-p-summary', 29 var idSum = 'topo-p-summary',
30 idDet = 'topo-p-detail', 30 idDet = 'topo-p-detail',
31 - idIns = 'topo-p-instance',
32 panelOpts = { 31 panelOpts = {
33 width: 260 32 width: 260
34 }; 33 };
35 34
36 - // internal state 35 + // panels
37 - var settings;
38 -
39 -
40 - // SVG elements;
41 - var fooPane;
42 -
43 - // D3 selections;
44 var summaryPanel, 36 var summaryPanel,
45 - detailPanel, 37 + detailPanel;
46 - instancePanel;
47 -
48 - // default settings for force layout
49 - var defaultSettings = {
50 - foo: 2
51 - };
52 -
53 38
54 // ========================== 39 // ==========================
40 + // *** SHOW SUMMARY ***
55 41
56 - function addSep(tbody) { 42 + function showSummary(data) {
57 - tbody.append('tr').append('td').attr('colspan', 2).append('hr'); 43 + populateSummary(data);
58 - } 44 + showSummaryPanel();
59 -
60 - function addProp(tbody, label, value) {
61 - var tr = tbody.append('tr');
62 -
63 - function addCell(cls, txt) {
64 - tr.append('td').attr('class', cls).text(txt);
65 - }
66 - addCell('label', label + ' :');
67 - addCell('value', value);
68 } 45 }
69 46
70 function populateSummary(data) { 47 function populateSummary(data) {
...@@ -89,9 +66,36 @@ ...@@ -89,9 +66,36 @@
89 }); 66 });
90 } 67 }
91 68
69 + function addSep(tbody) {
70 + tbody.append('tr').append('td').attr('colspan', 2).append('hr');
71 + }
72 +
73 + function addProp(tbody, label, value) {
74 + var tr = tbody.append('tr');
75 +
76 + function addCell(cls, txt) {
77 + tr.append('td').attr('class', cls).text(txt);
78 + }
79 + addCell('label', label + ' :');
80 + addCell('value', value);
81 + }
82 +
92 function showSummaryPanel() { 83 function showSummaryPanel() {
93 summaryPanel.show(); 84 summaryPanel.show();
85 + // TODO: augment, once we have the details pane also
86 + }
87 +
88 + // ==========================
89 +
90 + function initPanels() {
91 + summaryPanel = ps.createPanel(idSum, panelOpts);
92 + detailPanel = ps.createPanel(idDet, panelOpts);
93 + }
94 94
95 + function destroyPanels() {
96 + ps.destroyPanel(idSum);
97 + ps.destroyPanel(idDet);
98 + summaryPanel = detailPanel = null;
95 } 99 }
96 100
97 // ========================== 101 // ==========================
...@@ -105,22 +109,6 @@ ...@@ -105,22 +109,6 @@
105 ps = _ps_; 109 ps = _ps_;
106 gs = _gs_; 110 gs = _gs_;
107 111
108 - function initPanels() {
109 - summaryPanel = ps.createPanel(idSum, panelOpts);
110 - // TODO: set up detail and instance panels..
111 - }
112 -
113 - function destroyPanels() {
114 - ps.destroyPanel(idSum);
115 - summaryPanel = null;
116 - // TODO: destroy detail and instance panels..
117 - }
118 -
119 - function showSummary(payload) {
120 - populateSummary(payload);
121 - showSummaryPanel();
122 - }
123 -
124 return { 112 return {
125 initPanels: initPanels, 113 initPanels: initPanels,
126 destroyPanels: destroyPanels, 114 destroyPanels: destroyPanels,
......
...@@ -84,6 +84,13 @@ describe('factory: fw/layer/panel.js', function () { ...@@ -84,6 +84,13 @@ describe('factory: fw/layer/panel.js', function () {
84 expect(el.style('width')).toEqual('200px'); 84 expect(el.style('width')).toEqual('200px');
85 }); 85 });
86 86
87 + it('should provide an api of panel functions', function () {
88 + var p = ps.createPanel('foo');
89 + expect(fs.areFunctions(p, [
90 + 'show', 'hide', 'empty', 'append', 'width', 'height', 'isVisible', 'el'
91 + ])).toBeTruthy();
92 + });
93 +
87 it('should complain when a duplicate ID is used', function () { 94 it('should complain when a duplicate ID is used', function () {
88 spyOn($log, 'warn'); 95 spyOn($log, 'warn');
89 var p = ps.createPanel('foo'); 96 var p = ps.createPanel('foo');
......
...@@ -252,7 +252,7 @@ describe('factory: fw/svg/glyph.js', function() { ...@@ -252,7 +252,7 @@ describe('factory: fw/svg/glyph.js', function() {
252 252
253 it('should add a glyph with default size', function () { 253 it('should add a glyph with default size', function () {
254 gs.init(); 254 gs.init();
255 - gs.addGlyph(svg, 'crown'); 255 + var retval = gs.addGlyph(svg, 'crown');
256 var what = svg.selectAll('use'); 256 var what = svg.selectAll('use');
257 expect(what.size()).toEqual(1); 257 expect(what.size()).toEqual(1);
258 expect(what.attr('width')).toEqual('40'); 258 expect(what.attr('width')).toEqual('40');
...@@ -260,6 +260,10 @@ describe('factory: fw/svg/glyph.js', function() { ...@@ -260,6 +260,10 @@ describe('factory: fw/svg/glyph.js', function() {
260 expect(what.attr('xlink:href')).toEqual('#crown'); 260 expect(what.attr('xlink:href')).toEqual('#crown');
261 expect(what.classed('glyph')).toBeTruthy(); 261 expect(what.classed('glyph')).toBeTruthy();
262 expect(what.classed('overlay')).toBeFalsy(); 262 expect(what.classed('overlay')).toBeFalsy();
263 +
264 + // check a couple on retval, which should be the same thing..
265 + expect(retval.attr('xlink:href')).toEqual('#crown');
266 + expect(retval.classed('glyph')).toBeTruthy();
263 }); 267 });
264 268
265 it('should add a glyph with given size', function () { 269 it('should add a glyph with given size', function () {
......
...@@ -43,7 +43,13 @@ describe('factory: fw/svg/svgUtil.js', function() { ...@@ -43,7 +43,13 @@ describe('factory: fw/svg/svgUtil.js', function() {
43 ])).toBeTruthy(); 43 ])).toBeTruthy();
44 }); 44 });
45 45
46 - // TODO: add unit tests for drag behavior etc. 46 +
47 + // TODO: add unit tests for drag behavior
48 + // TODO: add unit tests for loadGlow
49 + // TODO: add unit tests for cat7
50 +
51 +
52 + // === translate()
47 53
48 it('should translate from two args', function () { 54 it('should translate from two args', function () {
49 expect(sus.translate(1,2)).toEqual('translate(1,2)'); 55 expect(sus.translate(1,2)).toEqual('translate(1,2)');
...@@ -53,4 +59,14 @@ describe('factory: fw/svg/svgUtil.js', function() { ...@@ -53,4 +59,14 @@ describe('factory: fw/svg/svgUtil.js', function() {
53 expect(sus.translate([3,4])).toEqual('translate(3,4)'); 59 expect(sus.translate([3,4])).toEqual('translate(3,4)');
54 }); 60 });
55 61
62 +
63 + // === stripPx()
64 +
65 + it('should not affect a number', function () {
66 + expect(sus.stripPx('4')).toEqual('4');
67 + });
68 +
69 + it('should remove trailing px', function () {
70 + expect(sus.stripPx('4px')).toEqual('4');
71 + });
56 }); 72 });
......
1 +{
2 + "event": "addInstance",
3 + "payload": {
4 + "id": "local",
5 + "ip": "127.0.0.1",
6 + "online": true,
7 + "uiAttached": true,
8 + "switches": 25,
9 + "labels": [
10 + "local",
11 + "127.0.0.1"
12 + ]
13 + }
14 +}