Topo2: Reset node position and unpin
Refactored NodeModel Added class to the surrounding rect for selected class Renamed Panels to avoid conflict with classic topo Topo2: Details Panel for single device selection Topo2: Added Equalize Masters keyboard shortcut Topo2: Toggle Link Port highlighting Topo2: Node Labels was returning empty string if friendly name was null Topo2: Reset map zoom and panning Change-Id: I0a949b2f8205e1abcfcac5aaec65c18d76e77cff
Showing
20 changed files
with
641 additions
and
165 deletions
... | @@ -66,7 +66,6 @@ | ... | @@ -66,7 +66,6 @@ |
66 | } | 66 | } |
67 | 67 | ||
68 | function updatePrefs(data) { | 68 | function updatePrefs(data) { |
69 | - $log.info('User properties updated'); | ||
70 | cache = data; | 69 | cache = data; |
71 | listeners.forEach(function (lsnr) { lsnr(); }); | 70 | listeners.forEach(function (lsnr) { lsnr(); }); |
72 | } | 71 | } | ... | ... |
... | @@ -3,7 +3,8 @@ module.exports = { | ... | @@ -3,7 +3,8 @@ module.exports = { |
3 | "installedESLint": true, | 3 | "installedESLint": true, |
4 | "globals": { | 4 | "globals": { |
5 | "angular": true, | 5 | "angular": true, |
6 | - "d3": true | 6 | + "d3": true, |
7 | + "_": true | ||
7 | }, | 8 | }, |
8 | "rules": { | 9 | "rules": { |
9 | "brace-style": 0, | 10 | "brace-style": 0, | ... | ... |
... | @@ -99,3 +99,29 @@ | ... | @@ -99,3 +99,29 @@ |
99 | cursor: pointer; | 99 | cursor: pointer; |
100 | fill-rule: evenodd; | 100 | fill-rule: evenodd; |
101 | } | 101 | } |
102 | + | ||
103 | +/* --- Topo Summary Panel --- */ | ||
104 | + | ||
105 | +#topo2-p-summary { | ||
106 | + padding: 16px; | ||
107 | +} | ||
108 | + | ||
109 | + | ||
110 | +/* --- Topo Detail Panel --- */ | ||
111 | + | ||
112 | +#topo2-p-detail { | ||
113 | + padding: 16px; | ||
114 | + top: 370px; | ||
115 | +} | ||
116 | +html[data-platform='iPad'] #topo2-p-detail { | ||
117 | + top: 386px; | ||
118 | +} | ||
119 | + | ||
120 | +#topo2-p-detail .actionBtns .actionBtn { | ||
121 | + display: inline-block; | ||
122 | +} | ||
123 | +#topo2-p-detail .actionBtns .actionBtn svg { | ||
124 | + width: 28px; | ||
125 | + height: 28px; | ||
126 | +} | ||
127 | + | ... | ... |
... | @@ -24,7 +24,7 @@ | ... | @@ -24,7 +24,7 @@ |
24 | 'use strict'; | 24 | 'use strict'; |
25 | 25 | ||
26 | // references to injected services | 26 | // references to injected services |
27 | - var $scope, $log, fs, mast, ks, zs, | 27 | + var $scope, $log, fs, mast, ks, |
28 | gs, sus, ps, t2es, t2fs, t2is, t2bcs, t2kcs, t2ms, t2mcs, t2zs; | 28 | gs, sus, ps, t2es, t2fs, t2is, t2bcs, t2kcs, t2ms, t2mcs, t2zs; |
29 | 29 | ||
30 | // DOM elements | 30 | // DOM elements |
... | @@ -81,19 +81,20 @@ | ... | @@ -81,19 +81,20 @@ |
81 | angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote']) | 81 | angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote']) |
82 | .controller('OvTopo2Ctrl', | 82 | .controller('OvTopo2Ctrl', |
83 | ['$scope', '$log', '$location', | 83 | ['$scope', '$log', '$location', |
84 | - 'FnService', 'MastService', 'KeyService', 'ZoomService', | 84 | + 'FnService', 'MastService', 'KeyService', |
85 | 'GlyphService', 'MapService', 'SvgUtilService', 'FlashService', | 85 | 'GlyphService', 'MapService', 'SvgUtilService', 'FlashService', |
86 | 'WebSocketService', 'PrefsService', 'ThemeService', | 86 | 'WebSocketService', 'PrefsService', 'ThemeService', |
87 | 'Topo2EventService', 'Topo2ForceService', 'Topo2InstanceService', | 87 | 'Topo2EventService', 'Topo2ForceService', 'Topo2InstanceService', |
88 | 'Topo2BreadcrumbService', 'Topo2KeyCommandService', 'Topo2MapService', | 88 | 'Topo2BreadcrumbService', 'Topo2KeyCommandService', 'Topo2MapService', |
89 | - 'Topo2MapConfigService', 'Topo2SummaryPanelService', 'Topo2ZoomService', | 89 | + 'Topo2MapConfigService', 'Topo2ZoomService', |
90 | + 'Topo2SummaryPanelService', 'Topo2DeviceDetailsPanel', | ||
90 | 91 | ||
91 | function (_$scope_, _$log_, _$loc_, | 92 | function (_$scope_, _$log_, _$loc_, |
92 | - _fs_, _mast_, _ks_, _zs_, | 93 | + _fs_, _mast_, _ks_, |
93 | _gs_, _ms_, _sus_, _flash_, | 94 | _gs_, _ms_, _sus_, _flash_, |
94 | _wss_, _ps_, _th_, | 95 | _wss_, _ps_, _th_, |
95 | _t2es_, _t2fs_, _t2is_, _t2bcs_, _t2kcs_, _t2ms_, _t2mcs_, | 96 | _t2es_, _t2fs_, _t2is_, _t2bcs_, _t2kcs_, _t2ms_, _t2mcs_, |
96 | - summaryPanel, _t2zs_ | 97 | + _t2zs_, summaryPanel, detailsPanel |
97 | ) { | 98 | ) { |
98 | 99 | ||
99 | var params = _$loc_.search(), | 100 | var params = _$loc_.search(), |
... | @@ -115,7 +116,6 @@ | ... | @@ -115,7 +116,6 @@ |
115 | fs = _fs_; | 116 | fs = _fs_; |
116 | mast = _mast_; | 117 | mast = _mast_; |
117 | ks = _ks_; | 118 | ks = _ks_; |
118 | - zs = _zs_; | ||
119 | 119 | ||
120 | gs = _gs_; | 120 | gs = _gs_; |
121 | sus = _sus_; | 121 | sus = _sus_; |
... | @@ -156,6 +156,8 @@ | ... | @@ -156,6 +156,8 @@ |
156 | ks.unbindKeys(); | 156 | ks.unbindKeys(); |
157 | t2fs.destroy(); | 157 | t2fs.destroy(); |
158 | t2is.destroy(); | 158 | t2is.destroy(); |
159 | + summaryPanel.destroy(); | ||
160 | + detailsPanel.destroy(); | ||
159 | }); | 161 | }); |
160 | 162 | ||
161 | // svg layer and initialization of components | 163 | // svg layer and initialization of components |
... | @@ -176,7 +178,7 @@ | ... | @@ -176,7 +178,7 @@ |
176 | t2es.bindHandlers(); | 178 | t2es.bindHandlers(); |
177 | 179 | ||
178 | // Add the map SVG Group | 180 | // Add the map SVG Group |
179 | - t2ms.init(zoomLayer).then( | 181 | + t2ms.init(zoomLayer, zoomer).then( |
180 | function (proj) { | 182 | function (proj) { |
181 | var z = ps.getPrefs('topo_zoom', { tx: 0, ty: 0, sc: 1 }); | 183 | var z = ps.getPrefs('topo_zoom', { tx: 0, ty: 0, sc: 1 }); |
182 | zoomer.panZoom([z.tx, z.ty], z.sc); | 184 | zoomer.panZoom([z.tx, z.ty], z.sc); |
... | @@ -219,6 +221,7 @@ | ... | @@ -219,6 +221,7 @@ |
219 | // $log.debug('registered overlays...', tov.list()); | 221 | // $log.debug('registered overlays...', tov.list()); |
220 | 222 | ||
221 | summaryPanel.init(); | 223 | summaryPanel.init(); |
224 | + detailsPanel.init(); | ||
222 | 225 | ||
223 | $log.log('OvTopo2Ctrl has been created'); | 226 | $log.log('OvTopo2Ctrl has been created'); |
224 | }]); | 227 | }]); | ... | ... |
... | @@ -34,10 +34,10 @@ | ... | @@ -34,10 +34,10 @@ |
34 | var DeviceCollection = Collection.extend({ | 34 | var DeviceCollection = Collection.extend({ |
35 | model: Model, | 35 | model: Model, |
36 | comparator: function (a, b) { | 36 | comparator: function (a, b) { |
37 | - var order = region.get('layerOrder'), | 37 | + // var order = region.get('layerOrder'), |
38 | - aLayer = a.get('layer'), | 38 | + // aLayer = a.get('id'), |
39 | - bLayer = b.get('layer'); | 39 | + // bLayer = b.get('layer'); |
40 | - return order.indexOf(aLayer - order.indexOf(bLayer)); | 40 | + // return order.indexOf(aLayer - order.indexOf(bLayer)); |
41 | } | 41 | } |
42 | }); | 42 | }); |
43 | 43 | ||
... | @@ -54,11 +54,10 @@ | ... | @@ -54,11 +54,10 @@ |
54 | return deviceCollection; | 54 | return deviceCollection; |
55 | } | 55 | } |
56 | 56 | ||
57 | - | ||
58 | angular.module('ovTopo2') | 57 | angular.module('ovTopo2') |
59 | .factory('Topo2DeviceService', | 58 | .factory('Topo2DeviceService', |
60 | - ['Topo2Collection', 'Topo2NodeModel', | 59 | + ['Topo2Collection', 'Topo2NodeModel', 'Topo2DeviceDetailsPanel', |
61 | - function (_c_, _nm_) { | 60 | + function (_c_, _nm_, detailsPanel) { |
62 | 61 | ||
63 | Collection = _c_; | 62 | Collection = _c_; |
64 | 63 | ||
... | @@ -67,11 +66,27 @@ | ... | @@ -67,11 +66,27 @@ |
67 | this.super = this.constructor.__super__; | 66 | this.super = this.constructor.__super__; |
68 | this.super.initialize.apply(this, arguments); | 67 | this.super.initialize.apply(this, arguments); |
69 | }, | 68 | }, |
69 | + events: { | ||
70 | + 'click': 'onClick' | ||
71 | + }, | ||
70 | nodeType: 'device', | 72 | nodeType: 'device', |
71 | icon: function () { | 73 | icon: function () { |
72 | var type = this.get('type'); | 74 | var type = this.get('type'); |
73 | return remappedDeviceTypes[type] || type || 'unknown'; | 75 | return remappedDeviceTypes[type] || type || 'unknown'; |
74 | }, | 76 | }, |
77 | + onClick: function () { | ||
78 | + | ||
79 | + if (this.get('selected')) { | ||
80 | + this.set('selected', false); | ||
81 | + detailsPanel.hide(); | ||
82 | + } else { | ||
83 | + this.set('selected', true); | ||
84 | + detailsPanel.updateDetails(this.get('id'), this.get('nodeType')); | ||
85 | + detailsPanel.show(); | ||
86 | + } | ||
87 | + | ||
88 | + this.el.attr('class', this.svgClassName()); | ||
89 | + }, | ||
75 | onExit: function () { | 90 | onExit: function () { |
76 | var node = this.el; | 91 | var node = this.el; |
77 | node.select('use') | 92 | node.select('use') | ... | ... |
1 | +/* | ||
2 | + * Copyright 2016-present 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 View Module. | ||
19 | + Module that displays the details panel for selected nodes | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + // Injected Services | ||
26 | + var Panel, gs, wss, flash, bs, fs, ns; | ||
27 | + | ||
28 | + // Internal State | ||
29 | + var detailsPanel; | ||
30 | + | ||
31 | + // configuration | ||
32 | + var id = 'topo2-p-detail', | ||
33 | + className = 'topo-p', | ||
34 | + panelOpts = { | ||
35 | + width: 260 // summary and detail panel width | ||
36 | + }, | ||
37 | + handlerMap = { | ||
38 | + 'showDetails': showDetails | ||
39 | + }; | ||
40 | + | ||
41 | + var coreButtons = { | ||
42 | + showDeviceView: { | ||
43 | + gid: 'switch', | ||
44 | + tt: 'Show Device View', | ||
45 | + path: 'device' | ||
46 | + }, | ||
47 | + showFlowView: { | ||
48 | + gid: 'flowTable', | ||
49 | + tt: 'Show Flow View for this Device', | ||
50 | + path: 'flow' | ||
51 | + }, | ||
52 | + showPortView: { | ||
53 | + gid: 'portTable', | ||
54 | + tt: 'Show Port View for this Device', | ||
55 | + path: 'port' | ||
56 | + }, | ||
57 | + showGroupView: { | ||
58 | + gid: 'groupTable', | ||
59 | + tt: 'Show Group View for this Device', | ||
60 | + path: 'group' | ||
61 | + }, | ||
62 | + showMeterView: { | ||
63 | + gid: 'meterTable', | ||
64 | + tt: 'Show Meter View for this Device', | ||
65 | + path: 'meter' | ||
66 | + } | ||
67 | + }; | ||
68 | + | ||
69 | + function init() { | ||
70 | + | ||
71 | + bindHandlers(); | ||
72 | + | ||
73 | + var options = angular.extend({}, panelOpts, { | ||
74 | + class: className | ||
75 | + }); | ||
76 | + | ||
77 | + detailsPanel = new Panel(id, options); | ||
78 | + detailsPanel.p.classed(className, true); | ||
79 | + } | ||
80 | + | ||
81 | + function addProp(tbody, label, value) { | ||
82 | + var tr = tbody.append('tr'), | ||
83 | + lab; | ||
84 | + if (typeof label === 'string') { | ||
85 | + lab = label.replace(/_/g, ' '); | ||
86 | + } else { | ||
87 | + lab = label; | ||
88 | + } | ||
89 | + | ||
90 | + function addCell(cls, txt) { | ||
91 | + tr.append('td').attr('class', cls).html(txt); | ||
92 | + } | ||
93 | + addCell('label', lab + ' :'); | ||
94 | + addCell('value', value); | ||
95 | + } | ||
96 | + | ||
97 | + function addSep(tbody) { | ||
98 | + tbody.append('tr').append('td').attr('colspan', 2).append('hr'); | ||
99 | + } | ||
100 | + | ||
101 | + function listProps(tbody, data) { | ||
102 | + data.propOrder.forEach(function (p) { | ||
103 | + if (p === '-') { | ||
104 | + addSep(tbody); | ||
105 | + } else { | ||
106 | + addProp(tbody, p, data.props[p]); | ||
107 | + } | ||
108 | + }); | ||
109 | + } | ||
110 | + | ||
111 | + function addBtnFooter() { | ||
112 | + detailsPanel.appendToFooter('hr'); | ||
113 | + detailsPanel.appendToFooter('div').classed('actionBtns', true); | ||
114 | + } | ||
115 | + | ||
116 | + function addAction(o) { | ||
117 | + var btnDiv = d3.select('#' + id) | ||
118 | + .select('.actionBtns') | ||
119 | + .append('div') | ||
120 | + .classed('actionBtn', true); | ||
121 | + bs.button(btnDiv, id + '-' + o.id, o.gid, o.cb, o.tt); | ||
122 | + } | ||
123 | + | ||
124 | + function installButtons(buttons, data, devId) { | ||
125 | + buttons.forEach(function (id) { | ||
126 | + var btn = coreButtons[id], | ||
127 | + gid = btn && btn.gid, | ||
128 | + tt = btn && btn.tt, | ||
129 | + path = btn && btn.path; | ||
130 | + | ||
131 | + if (btn) { | ||
132 | + addAction({ | ||
133 | + id: 'core-' + id, | ||
134 | + gid: gid, | ||
135 | + tt: tt, | ||
136 | + cb: function () { ns.navTo(path, { devId: devId }); } | ||
137 | + }); | ||
138 | + } | ||
139 | + // else if (btn = _getButtonDef(id, data)) { | ||
140 | + // addAction(btn); | ||
141 | + // } | ||
142 | + }); | ||
143 | + } | ||
144 | + | ||
145 | + function renderSingle(data) { | ||
146 | + | ||
147 | + detailsPanel.emptyRegions(); | ||
148 | + | ||
149 | + var svg = detailsPanel.appendToHeader('div') | ||
150 | + .classed('icon clickable', true) | ||
151 | + .append('svg'), | ||
152 | + title = detailsPanel.appendToHeader('h2') | ||
153 | + .classed('clickable', true), | ||
154 | + table = detailsPanel.appendToBody('table'), | ||
155 | + tbody = table.append('tbody'), | ||
156 | + navFn; | ||
157 | + | ||
158 | + gs.addGlyph(svg, (data.type || 'unknown'), 26); | ||
159 | + title.text(data.title); | ||
160 | + | ||
161 | + // // only add navigation when displaying a device | ||
162 | + // if (isDevice[data.type]) { | ||
163 | + // navFn = function () { | ||
164 | + // ns.navTo(devPath, { devId: data.id }); | ||
165 | + // }; | ||
166 | + // | ||
167 | + // svg.on('click', navFn); | ||
168 | + // title.on('click', navFn); | ||
169 | + // } | ||
170 | + | ||
171 | + listProps(tbody, data); | ||
172 | + addBtnFooter(); | ||
173 | + } | ||
174 | + | ||
175 | + | ||
176 | + function bindHandlers() { | ||
177 | + wss.bindHandlers(handlerMap); | ||
178 | + } | ||
179 | + | ||
180 | + function updateDetails(id, nodeType) { | ||
181 | + wss.sendEvent('requestDetails', { | ||
182 | + id: id, | ||
183 | + class: nodeType | ||
184 | + }); | ||
185 | + } | ||
186 | + | ||
187 | + function showDetails(data) { | ||
188 | + var buttons = fs.isA(data.buttons) || []; | ||
189 | + renderSingle(data); | ||
190 | + installButtons(buttons, data, data.id); | ||
191 | + } | ||
192 | + | ||
193 | + function toggle() { | ||
194 | + var on = detailsPanel.p.toggle(), | ||
195 | + verb = on ? 'Show' : 'Hide'; | ||
196 | + flash.flash(verb + ' Summary Panel'); | ||
197 | + } | ||
198 | + | ||
199 | + function show() { | ||
200 | + detailsPanel.p.show(); | ||
201 | + } | ||
202 | + | ||
203 | + function hide() { | ||
204 | + detailsPanel.p.hide(); | ||
205 | + } | ||
206 | + | ||
207 | + function destroy() { | ||
208 | + wss.unbindHandlers(handlerMap); | ||
209 | + detailsPanel.destroy(); | ||
210 | + } | ||
211 | + | ||
212 | + angular.module('ovTopo2') | ||
213 | + .factory('Topo2DeviceDetailsPanel', | ||
214 | + ['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', | ||
215 | + 'ButtonService', 'FnService', 'NavService', | ||
216 | + function (_ps_, _gs_, _wss_, _flash_, _bs_, _fs_, _ns_) { | ||
217 | + | ||
218 | + Panel = _ps_; | ||
219 | + gs = _gs_; | ||
220 | + wss = _wss_; | ||
221 | + flash = _flash_; | ||
222 | + bs = _bs_; | ||
223 | + fs = _fs_; | ||
224 | + ns = _ns_; | ||
225 | + | ||
226 | + return { | ||
227 | + init: init, | ||
228 | + updateDetails: updateDetails, | ||
229 | + | ||
230 | + toggle: toggle, | ||
231 | + show: show, | ||
232 | + hide: hide, | ||
233 | + destroy: destroy | ||
234 | + }; | ||
235 | + } | ||
236 | + ]); | ||
237 | +})(); |
... | @@ -190,18 +190,49 @@ | ... | @@ -190,18 +190,49 @@ |
190 | 190 | ||
191 | // ========================== Main Service Definition | 191 | // ========================== Main Service Definition |
192 | 192 | ||
193 | + function update(elements) { | ||
194 | + angular.forEach(elements, function (el) { | ||
195 | + el.update(); | ||
196 | + }); | ||
197 | + } | ||
198 | + | ||
193 | function updateNodes() { | 199 | function updateNodes() { |
194 | - var allNodes = t2rs.regionNodes(); | 200 | + update(t2rs.regionNodes()); |
195 | - angular.forEach(allNodes, function (node) { | 201 | + } |
196 | - node.update(); | 202 | + |
203 | + function updateLinks() { | ||
204 | + update(t2rs.regionLinks()); | ||
205 | + } | ||
206 | + | ||
207 | + function resetAllLocations() { | ||
208 | + var nodes = t2rs.regionNodes(); | ||
209 | + | ||
210 | + angular.forEach(nodes, function (node) { | ||
211 | + node.resetPosition(); | ||
212 | + }); | ||
213 | + | ||
214 | + t2ls.update(); | ||
215 | + t2ls.tick(); | ||
216 | + } | ||
217 | + | ||
218 | + function unpin() { | ||
219 | + var hovered = t2rs.filterRegionNodes(function (model) { | ||
220 | + return model.get('hovered'); | ||
221 | + }); | ||
222 | + | ||
223 | + angular.forEach(hovered, function (model) { | ||
224 | + model.fixed = false; | ||
225 | + model.el.classed('fixed', false); | ||
197 | }); | 226 | }); |
198 | } | 227 | } |
199 | 228 | ||
200 | angular.module('ovTopo2') | 229 | angular.module('ovTopo2') |
201 | .factory('Topo2ForceService', | 230 | .factory('Topo2ForceService', |
202 | - ['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService', | 231 | + ['$log', 'WebSocketService', 'Topo2InstanceService', |
203 | - 'Topo2LayoutService', 'Topo2ViewService', 'Topo2BreadcrumbService', 'Topo2ZoomService', | 232 | + 'Topo2RegionService', 'Topo2LayoutService', 'Topo2ViewService', |
204 | - function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_, _t2vs_, _t2bcs_, zoomService) { | 233 | + 'Topo2BreadcrumbService', 'Topo2ZoomService', |
234 | + function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_, | ||
235 | + _t2vs_, _t2bcs_, zoomService) { | ||
205 | 236 | ||
206 | $log = _$log_; | 237 | $log = _$log_; |
207 | wss = _wss_; | 238 | wss = _wss_; |
... | @@ -238,7 +269,10 @@ | ... | @@ -238,7 +269,10 @@ |
238 | showMastership: showMastership, | 269 | showMastership: showMastership, |
239 | topo2PeerRegions: topo2PeerRegions, | 270 | topo2PeerRegions: topo2PeerRegions, |
240 | 271 | ||
241 | - updateNodes: updateNodes | 272 | + updateNodes: updateNodes, |
273 | + updateLinks: updateLinks, | ||
274 | + resetAllLocations: resetAllLocations, | ||
275 | + unpin: unpin | ||
242 | }; | 276 | }; |
243 | }]); | 277 | }]); |
244 | })(); | 278 | })(); | ... | ... |
... | @@ -17,9 +17,9 @@ | ... | @@ -17,9 +17,9 @@ |
17 | (function () { | 17 | (function () { |
18 | 18 | ||
19 | // Injected Services | 19 | // Injected Services |
20 | - var ks, t2ps, t2ms, ps, t2is, t2sp; | 20 | + var ks, flash, wss, t2ps, t2ms, ps, t2is, t2sp, t2vs; |
21 | 21 | ||
22 | - var topo2ForceService; | 22 | + var t2fs; |
23 | 23 | ||
24 | // Commmands | 24 | // Commmands |
25 | var actionMap = { | 25 | var actionMap = { |
... | @@ -27,11 +27,16 @@ | ... | @@ -27,11 +27,16 @@ |
27 | G: [openMapSelection, 'Select background geo map'], | 27 | G: [openMapSelection, 'Select background geo map'], |
28 | B: [toggleMap, 'Toggle background geo map'], | 28 | B: [toggleMap, 'Toggle background geo map'], |
29 | I: [toggleInstancePanel, 'Toggle ONOS Instance Panel'], | 29 | I: [toggleInstancePanel, 'Toggle ONOS Instance Panel'], |
30 | - O: [toggleSummary, 'Toggle the Summary Panel'] | 30 | + O: [toggleSummary, 'Toggle the Summary Panel'], |
31 | + R: [resetZoom, 'Reset pan / zoom'], | ||
32 | + P: [togglePorts, 'Toggle Port Highlighting'], | ||
33 | + E: [equalizeMasters, 'Equalize mastership roles'], | ||
34 | + X: [resetAllNodeLocations, 'Reset Node Location'], | ||
35 | + U: [unpinNode, 'Unpin node (mouse over)'] | ||
31 | }; | 36 | }; |
32 | 37 | ||
33 | - function init(t2fs) { | 38 | + function init(_t2fs_) { |
34 | - topo2ForceService = t2fs; | 39 | + t2fs = _t2fs_; |
35 | bindCommands(); | 40 | bindCommands(); |
36 | } | 41 | } |
37 | 42 | ||
... | @@ -58,7 +63,7 @@ | ... | @@ -58,7 +63,7 @@ |
58 | function cycleDeviceLabels() { | 63 | function cycleDeviceLabels() { |
59 | var deviceLabelIndex = t2ps.get('dlbls') + 1; | 64 | var deviceLabelIndex = t2ps.get('dlbls') + 1; |
60 | t2ps.set('dlbls', deviceLabelIndex % 3); | 65 | t2ps.set('dlbls', deviceLabelIndex % 3); |
61 | - topo2ForceService.updateNodes(); | 66 | + t2fs.updateNodes(); |
62 | } | 67 | } |
63 | 68 | ||
64 | function openMapSelection() { | 69 | function openMapSelection() { |
... | @@ -77,17 +82,47 @@ | ... | @@ -77,17 +82,47 @@ |
77 | t2sp.toggle(); | 82 | t2sp.toggle(); |
78 | } | 83 | } |
79 | 84 | ||
85 | + function resetZoom() { | ||
86 | + t2ms.resetZoom(); | ||
87 | + flash.flash('Pan and zoom reset'); | ||
88 | + } | ||
89 | + | ||
90 | + function togglePorts(x) { | ||
91 | + updatePrefsState('porthl', t2vs.togglePortHighlights(x)); | ||
92 | + t2fs.updateLinks(); | ||
93 | + } | ||
94 | + | ||
95 | + function equalizeMasters() { | ||
96 | + wss.sendEvent('equalizeMasters'); | ||
97 | + flash.flash('Equalizing master roles'); | ||
98 | + } | ||
99 | + | ||
100 | + function resetAllNodeLocations() { | ||
101 | + t2fs.resetAllLocations(); | ||
102 | + flash.flash('Reset node locations'); | ||
103 | + } | ||
104 | + | ||
105 | + function unpinNode() { | ||
106 | + t2fs.unpin(); | ||
107 | + flash.flash('Unpin node'); | ||
108 | + } | ||
109 | + | ||
80 | angular.module('ovTopo2') | 110 | angular.module('ovTopo2') |
81 | .factory('Topo2KeyCommandService', | 111 | .factory('Topo2KeyCommandService', |
82 | - ['KeyService', 'Topo2PrefsService', 'Topo2MapService', 'PrefsService', | 112 | + ['KeyService', 'FlashService', 'WebSocketService', 'Topo2PrefsService', |
83 | - 'Topo2InstanceService', 'Topo2SummaryPanelService', | 113 | + 'Topo2MapService', 'PrefsService', 'Topo2InstanceService', |
84 | - function (_ks_, _t2ps_, _t2ms_, _ps_, _t2is_, _t2sp_) { | 114 | + 'Topo2SummaryPanelService', 'Topo2ViewService', |
115 | + function (_ks_, _flash_, _wss_, _t2ps_, _t2ms_, _ps_, _t2is_, _t2sp_, _t2vs_) { | ||
116 | + | ||
117 | + ks = _ks_; | ||
118 | + flash = _flash_; | ||
119 | + wss = _wss_; | ||
85 | t2ps = _t2ps_; | 120 | t2ps = _t2ps_; |
86 | t2ms = _t2ms_; | 121 | t2ms = _t2ms_; |
87 | t2is = _t2is_; | 122 | t2is = _t2is_; |
88 | ps = _ps_; | 123 | ps = _ps_; |
89 | - ks = _ks_; | ||
90 | t2sp = _t2sp_; | 124 | t2sp = _t2sp_; |
125 | + t2vs = _t2vs_; | ||
91 | 126 | ||
92 | return { | 127 | return { |
93 | init: init, | 128 | init: init, | ... | ... |
... | @@ -438,6 +438,7 @@ | ... | @@ -438,6 +438,7 @@ |
438 | init: init, | 438 | init: init, |
439 | createForceLayout: createForceLayout, | 439 | createForceLayout: createForceLayout, |
440 | update: update, | 440 | update: update, |
441 | + tick: tick, | ||
441 | start: start, | 442 | start: start, |
442 | 443 | ||
443 | setDimensions: setDimensions | 444 | setDimensions: setDimensions | ... | ... |
... | @@ -22,8 +22,7 @@ | ... | @@ -22,8 +22,7 @@ |
22 | (function () { | 22 | (function () { |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | - var $log; | 25 | + var $log, Collection, Model, ts, sus, t2zs, t2vs; |
26 | - var Collection, Model, ts, sus, t2zs; | ||
27 | 26 | ||
28 | var linkLabelOffset = '0.35em'; | 27 | var linkLabelOffset = '0.35em'; |
29 | 28 | ||
... | @@ -59,7 +58,6 @@ | ... | @@ -59,7 +58,6 @@ |
59 | var attrs = angular.extend({}, linkPoints, { | 58 | var attrs = angular.extend({}, linkPoints, { |
60 | key: this.get('id'), | 59 | key: this.get('id'), |
61 | class: 'link', | 60 | class: 'link', |
62 | - weight: 1, | ||
63 | srcPort: this.get('srcPort'), | 61 | srcPort: this.get('srcPort'), |
64 | tgtPort: this.get('dstPort'), | 62 | tgtPort: this.get('dstPort'), |
65 | position: { | 63 | position: { |
... | @@ -144,6 +142,8 @@ | ... | @@ -144,6 +142,8 @@ |
144 | }); | 142 | }); |
145 | 143 | ||
146 | this.el.classed('enhanced', true); | 144 | this.el.classed('enhanced', true); |
145 | + | ||
146 | + if (showPort()) { | ||
147 | point = this.locatePortLabel(); | 147 | point = this.locatePortLabel(); |
148 | angular.extend(point, { | 148 | angular.extend(point, { |
149 | id: 'topo-port-tgt', | 149 | id: 'topo-port-tgt', |
... | @@ -182,6 +182,7 @@ | ... | @@ -182,6 +182,7 @@ |
182 | 182 | ||
183 | el.attr('transform', sus.translate(d.x, d.y)); | 183 | el.attr('transform', sus.translate(d.x, d.y)); |
184 | }); | 184 | }); |
185 | + } | ||
185 | }, | 186 | }, |
186 | unenhance: function () { | 187 | unenhance: function () { |
187 | this.el.classed('enhanced', false); | 188 | this.el.classed('enhanced', false); |
... | @@ -248,6 +249,11 @@ | ... | @@ -248,6 +249,11 @@ |
248 | setScale: function () { | 249 | setScale: function () { |
249 | var width = linkScale(widthRatio / t2zs.scale()); | 250 | var width = linkScale(widthRatio / t2zs.scale()); |
250 | this.el.style('stroke-width', width + 'px'); | 251 | this.el.style('stroke-width', width + 'px'); |
252 | + }, | ||
253 | + update: function () { | ||
254 | + if (this.el.classed('enhanced')) { | ||
255 | + this.enhance(); | ||
256 | + } | ||
251 | } | 257 | } |
252 | }); | 258 | }); |
253 | 259 | ||
... | @@ -258,17 +264,23 @@ | ... | @@ -258,17 +264,23 @@ |
258 | return new LinkCollection(data); | 264 | return new LinkCollection(data); |
259 | } | 265 | } |
260 | 266 | ||
267 | + function showPort() { | ||
268 | + return t2vs.getPortHighlighting(); | ||
269 | + } | ||
270 | + | ||
261 | angular.module('ovTopo2') | 271 | angular.module('ovTopo2') |
262 | .factory('Topo2LinkService', | 272 | .factory('Topo2LinkService', |
263 | ['$log', 'Topo2Collection', 'Topo2Model', | 273 | ['$log', 'Topo2Collection', 'Topo2Model', |
264 | 'ThemeService', 'SvgUtilService', 'Topo2ZoomService', | 274 | 'ThemeService', 'SvgUtilService', 'Topo2ZoomService', |
265 | - | 275 | + 'Topo2ViewService', |
266 | - function (_$log_, _Collection_, _Model_, _ts_, _sus_, _t2zs_) { | 276 | + function (_$log_, _Collection_, _Model_, _ts_, _sus_, |
277 | + _t2zs_, _t2vs_) { | ||
267 | 278 | ||
268 | $log = _$log_; | 279 | $log = _$log_; |
269 | ts = _ts_; | 280 | ts = _ts_; |
270 | sus = _sus_; | 281 | sus = _sus_; |
271 | t2zs = _t2zs_; | 282 | t2zs = _t2zs_; |
283 | + t2vs = _t2vs_; | ||
272 | Collection = _Collection_; | 284 | Collection = _Collection_; |
273 | Model = _Model_; | 285 | Model = _Model_; |
274 | 286 | ... | ... |
... | @@ -29,13 +29,15 @@ | ... | @@ -29,13 +29,15 @@ |
29 | var MapSelectionDialog; | 29 | var MapSelectionDialog; |
30 | 30 | ||
31 | // internal state | 31 | // internal state |
32 | - var mapG; | 32 | + var mapG, zoomLayer, zoomer; |
33 | 33 | ||
34 | - function init(zoomLayer) { | 34 | + function init(_zoomLayer_, _zoomer_) { |
35 | - return setUpMap(zoomLayer); | 35 | + zoomLayer = _zoomLayer_; |
36 | + zoomer = _zoomer_; | ||
37 | + return setUpMap(); | ||
36 | } | 38 | } |
37 | 39 | ||
38 | - function setUpMap(zoomLayer) { | 40 | + function setUpMap() { |
39 | var prefs = currentMap(), | 41 | var prefs = currentMap(), |
40 | mapId = prefs.mapid, | 42 | mapId = prefs.mapid, |
41 | mapFilePath = prefs.mapfilepath, | 43 | mapFilePath = prefs.mapfilepath, |
... | @@ -124,6 +126,10 @@ | ... | @@ -124,6 +126,10 @@ |
124 | }).open(); | 126 | }).open(); |
125 | } | 127 | } |
126 | 128 | ||
129 | + function resetZoom() { | ||
130 | + zoomer.reset(); | ||
131 | + } | ||
132 | + | ||
127 | angular.module('ovTopo2') | 133 | angular.module('ovTopo2') |
128 | .factory('Topo2MapService', | 134 | .factory('Topo2MapService', |
129 | ['$location', 'PrefsService', 'MapService', | 135 | ['$location', 'PrefsService', 'MapService', |
... | @@ -140,7 +146,9 @@ | ... | @@ -140,7 +146,9 @@ |
140 | return { | 146 | return { |
141 | init: init, | 147 | init: init, |
142 | openMapSelection: openMapSelection, | 148 | openMapSelection: openMapSelection, |
143 | - toggle: toggle | 149 | + toggle: toggle, |
150 | + | ||
151 | + resetZoom: resetZoom | ||
144 | }; | 152 | }; |
145 | } | 153 | } |
146 | ]); | 154 | ]); | ... | ... |
... | @@ -15,18 +15,14 @@ | ... | @@ -15,18 +15,14 @@ |
15 | */ | 15 | */ |
16 | 16 | ||
17 | /* | 17 | /* |
18 | - ONOS GUI -- Topology Layout Module. | 18 | + ONOS GUI -- Topology Node Module. |
19 | - Module that contains the d3.force.layout logic | 19 | + Module that contains model for nodes within the topology |
20 | */ | 20 | */ |
21 | 21 | ||
22 | (function () { | 22 | (function () { |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | - var randomService, ps, sus, is, ts, t2mcs; | 25 | + var ps, sus, is, ts, t2mcs, t2nps, fn; |
26 | - var fn; | ||
27 | - | ||
28 | - // Internal state; | ||
29 | - var nearDist = 15; | ||
30 | 26 | ||
31 | var devIconDim = 36, | 27 | var devIconDim = 36, |
32 | devIconDimMin = 20, | 28 | devIconDimMin = 20, |
... | @@ -56,133 +52,56 @@ | ... | @@ -56,133 +52,56 @@ |
56 | dColTheme[ts.theme()][otag]; | 52 | dColTheme[ts.theme()][otag]; |
57 | } | 53 | } |
58 | 54 | ||
59 | - function positionNode(node, forUpdate) { | ||
60 | - var meta = node.get('metaUi'), | ||
61 | - x = meta && meta.x, | ||
62 | - y = meta && meta.y, | ||
63 | - dim = [800, 600], | ||
64 | - xy; | ||
65 | - | ||
66 | - // If the device contains explicit LONG/LAT data, use that to position | ||
67 | - if (setLongLat(node)) { | ||
68 | - // Indicate we want to update cached meta data... | ||
69 | - return true; | ||
70 | - } | ||
71 | - | ||
72 | - // else if we have [x,y] cached in meta data, use that... | ||
73 | - if (x !== undefined && y !== undefined) { | ||
74 | - node.fixed = true; | ||
75 | - node.px = node.x = x; | ||
76 | - node.py = node.y = y; | ||
77 | - return; | ||
78 | - } | ||
79 | - | ||
80 | - // if this is a node update (not a node add).. skip randomizer | ||
81 | - if (forUpdate) { | ||
82 | - return; | ||
83 | - } | ||
84 | - | ||
85 | - // Note: Placing incoming unpinned nodes at exactly the same point | ||
86 | - // (center of the view) causes them to explode outwards when | ||
87 | - // the force layout kicks in. So, we spread them out a bit | ||
88 | - // initially, to provide a more serene layout convergence. | ||
89 | - // Additionally, if the node is a host, we place it near | ||
90 | - // the device it is connected to. | ||
91 | - | ||
92 | - function rand() { | ||
93 | - return { | ||
94 | - x: randomService.randDim(dim[0]), | ||
95 | - y: randomService.randDim(dim[1]) | ||
96 | - }; | ||
97 | - } | ||
98 | - | ||
99 | - function near(node) { | ||
100 | - return { | ||
101 | - x: node.x + nearDist + randomService.spread(nearDist), | ||
102 | - y: node.y + nearDist + randomService.spread(nearDist) | ||
103 | - }; | ||
104 | - } | ||
105 | - | ||
106 | - function getDevice(cp) { | ||
107 | - return rand(); | ||
108 | - } | ||
109 | - | ||
110 | - xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); | ||
111 | - | ||
112 | - if (node.class === 'sub-region') { | ||
113 | - xy = rand(); | ||
114 | - node.x = node.px = xy.x; | ||
115 | - node.y = node.py = xy.y; | ||
116 | - } | ||
117 | - angular.extend(node, xy); | ||
118 | - } | ||
119 | - | ||
120 | - function setLongLat(el) { | ||
121 | - var loc = el.get('location'), | ||
122 | - coord; | ||
123 | - | ||
124 | - if (loc && loc.type === 'lnglat') { | ||
125 | - | ||
126 | - if (loc.lat === 0 && loc.lng === 0) { | ||
127 | - return false; | ||
128 | - } | ||
129 | - | ||
130 | - coord = coordFromLngLat(loc); | ||
131 | - el.fixed = true; | ||
132 | - el.x = el.px = coord[0]; | ||
133 | - el.y = el.py = coord[1]; | ||
134 | - | ||
135 | - return true; | ||
136 | - } | ||
137 | - } | ||
138 | - | ||
139 | - function coordFromLngLat(loc) { | ||
140 | - var p = t2mcs.projection(); | ||
141 | - return p ? p([loc.lng, loc.lat]) : [0, 0]; | ||
142 | - } | ||
143 | - | ||
144 | angular.module('ovTopo2') | 55 | angular.module('ovTopo2') |
145 | .factory('Topo2NodeModel', | 56 | .factory('Topo2NodeModel', |
146 | - ['Topo2Model', 'FnService', 'RandomService', 'Topo2PrefsService', | 57 | + ['Topo2Model', 'FnService', 'Topo2PrefsService', |
147 | 'SvgUtilService', 'IconService', 'ThemeService', | 58 | 'SvgUtilService', 'IconService', 'ThemeService', |
148 | - 'Topo2MapConfigService', 'Topo2ZoomService', | 59 | + 'Topo2MapConfigService', 'Topo2ZoomService', 'Topo2NodePositionService', |
149 | - function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_, | 60 | + function (Model, _fn_, _ps_, _sus_, _is_, _ts_, |
150 | - _t2mcs_, zoomService) { | 61 | + _t2mcs_, zoomService, _t2nps_) { |
151 | 62 | ||
152 | - randomService = _RandomService_; | ||
153 | ts = _ts_; | 63 | ts = _ts_; |
154 | fn = _fn_; | 64 | fn = _fn_; |
155 | ps = _ps_; | 65 | ps = _ps_; |
156 | sus = _sus_; | 66 | sus = _sus_; |
157 | is = _is_; | 67 | is = _is_; |
158 | t2mcs = _t2mcs_; | 68 | t2mcs = _t2mcs_; |
69 | + t2nps = _t2nps_; | ||
159 | 70 | ||
160 | return Model.extend({ | 71 | return Model.extend({ |
161 | initialize: function () { | 72 | initialize: function () { |
162 | - this.set('class', this.nodeType); | ||
163 | - this.set('svgClass', this.svgClassName()); | ||
164 | this.node = this.createNode(); | 73 | this.node = this.createNode(); |
74 | + this._events = { | ||
75 | + 'mouseover': 'mouseoverHandler', | ||
76 | + 'mouseout': 'mouseoutHandler' | ||
77 | + }; | ||
165 | }, | 78 | }, |
166 | createNode: function () { | 79 | createNode: function () { |
167 | - this.set('class', this.nodeType); | ||
168 | this.set('svgClass', this.svgClassName()); | 80 | this.set('svgClass', this.svgClassName()); |
169 | - positionNode(this); | 81 | + t2nps.positionNode(this); |
170 | return this; | 82 | return this; |
171 | }, | 83 | }, |
172 | setUpEvents: function () { | 84 | setUpEvents: function () { |
173 | - var _this = this; | 85 | + var _this = this, |
174 | - angular.forEach(this.events, function (handler, key) { | 86 | + events = angular.extend({}, this._events, this.events); |
87 | + angular.forEach(events, function (handler, key) { | ||
175 | _this.el.on(key, _this[handler].bind(_this)); | 88 | _this.el.on(key, _this[handler].bind(_this)); |
176 | }); | 89 | }); |
177 | }, | 90 | }, |
91 | + mouseoverHandler: function () { | ||
92 | + this.set('hovered', true); | ||
93 | + }, | ||
94 | + mouseoutHandler: function () { | ||
95 | + this.set('hovered', false); | ||
96 | + }, | ||
178 | icon: function () { | 97 | icon: function () { |
179 | return 'unknown'; | 98 | return 'unknown'; |
180 | }, | 99 | }, |
181 | label: function () { | 100 | label: function () { |
182 | var props = this.get('props'), | 101 | var props = this.get('props'), |
183 | id = this.get('id'), | 102 | id = this.get('id'), |
184 | - friendlyName = props ? props.name : id, | 103 | + friendlyName = props && props.name ? props.name : id, |
185 | - labels = ['', friendlyName, id], | 104 | + labels = ['', friendlyName || id, id], |
186 | nli = ps.get('dlbls'), | 105 | nli = ps.get('dlbls'), |
187 | idx = (nli < labels.length) ? nli : 0; | 106 | idx = (nli < labels.length) ? nli : 0; |
188 | 107 | ||
... | @@ -197,7 +116,8 @@ | ... | @@ -197,7 +116,8 @@ |
197 | return box.width + labelPad * 2; | 116 | return box.width + labelPad * 2; |
198 | }, | 117 | }, |
199 | addLabelElements: function (label) { | 118 | addLabelElements: function (label) { |
200 | - var rect = this.el.append('rect'); | 119 | + var rect = this.el.append('rect') |
120 | + .attr('class', 'node-container'); | ||
201 | var glythRect = this.el.append('rect') | 121 | var glythRect = this.el.append('rect') |
202 | .attr('y', -halfDevIcon) | 122 | .attr('y', -halfDevIcon) |
203 | .attr('x', -halfDevIcon) | 123 | .attr('x', -halfDevIcon) |
... | @@ -243,7 +163,8 @@ | ... | @@ -243,7 +163,8 @@ |
243 | this.nodeType, | 163 | this.nodeType, |
244 | this.get('type'), | 164 | this.get('type'), |
245 | { | 165 | { |
246 | - online: this.get('online') | 166 | + online: this.get('online'), |
167 | + selected: this.get('selected') | ||
247 | } | 168 | } |
248 | ); | 169 | ); |
249 | }, | 170 | }, |
... | @@ -251,6 +172,9 @@ | ... | @@ -251,6 +172,9 @@ |
251 | var p = t2mcs.projection(); | 172 | var p = t2mcs.projection(); |
252 | return p ? p.invert(coord) : [0, 0]; | 173 | return p ? p.invert(coord) : [0, 0]; |
253 | }, | 174 | }, |
175 | + resetPosition: function () { | ||
176 | + t2nps.setLongLat(this); | ||
177 | + }, | ||
254 | update: function () { | 178 | update: function () { |
255 | this.updateLabel(); | 179 | this.updateLabel(); |
256 | }, | 180 | }, |
... | @@ -281,8 +205,8 @@ | ... | @@ -281,8 +205,8 @@ |
281 | multipler = devIconDimMax / (dim * zoomService.scale()); | 205 | multipler = devIconDimMax / (dim * zoomService.scale()); |
282 | } | 206 | } |
283 | 207 | ||
284 | - | 208 | + this.el.selectAll('*') |
285 | - this.el.selectAll('*').style('transform', 'scale(' + multipler + ')'); | 209 | + .style('transform', 'scale(' + multipler + ')'); |
286 | }, | 210 | }, |
287 | render: function () { | 211 | render: function () { |
288 | var node = this.el, | 212 | var node = this.el, |
... | @@ -293,7 +217,8 @@ | ... | @@ -293,7 +217,8 @@ |
293 | // Label | 217 | // Label |
294 | var labelElements = this.addLabelElements(label); | 218 | var labelElements = this.addLabelElements(label); |
295 | labelWidth = label ? this.computeLabelWidth(node) : 0; | 219 | labelWidth = label ? this.computeLabelWidth(node) : 0; |
296 | - labelElements.rect.attr(this.labelBox(devIconDim, labelWidth)); | 220 | + labelElements.rect |
221 | + .attr(this.labelBox(devIconDim, labelWidth)); | ||
297 | 222 | ||
298 | // Icon | 223 | // Icon |
299 | glyph = is.addDeviceIcon(node, glyphId, devIconDim); | 224 | glyph = is.addDeviceIcon(node, glyphId, devIconDim); | ... | ... |
1 | +/* | ||
2 | + * Copyright 2016-present 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 Node Position Module. | ||
19 | + Module that helps position nodes in the topology | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + // Injected vars | ||
26 | + var rs, t2mcs; | ||
27 | + | ||
28 | + // Internal state; | ||
29 | + var nearDist = 15; | ||
30 | + | ||
31 | + function positionNode(node, forUpdate) { | ||
32 | + var meta = node.get('metaUi'), | ||
33 | + x = meta && meta.x, | ||
34 | + y = meta && meta.y, | ||
35 | + dim = [800, 600], | ||
36 | + xy; | ||
37 | + | ||
38 | + // If the device contains explicit LONG/LAT data, use that to position | ||
39 | + if (setLongLat(node)) { | ||
40 | + // Indicate we want to update cached meta data... | ||
41 | + return true; | ||
42 | + } | ||
43 | + | ||
44 | + // else if we have [x,y] cached in meta data, use that... | ||
45 | + if (x !== undefined && y !== undefined) { | ||
46 | + node.fixed = true; | ||
47 | + node.px = node.x = x; | ||
48 | + node.py = node.y = y; | ||
49 | + return; | ||
50 | + } | ||
51 | + | ||
52 | + // if this is a node update (not a node add).. skip randomizer | ||
53 | + if (forUpdate) { | ||
54 | + return; | ||
55 | + } | ||
56 | + | ||
57 | + // Note: Placing incoming unpinned nodes at exactly the same point | ||
58 | + // (center of the view) causes them to explode outwards when | ||
59 | + // the force layout kicks in. So, we spread them out a bit | ||
60 | + // initially, to provide a more serene layout convergence. | ||
61 | + // Additionally, if the node is a host, we place it near | ||
62 | + // the device it is connected to. | ||
63 | + | ||
64 | + function rand() { | ||
65 | + return { | ||
66 | + x: rs.randDim(dim[0]), | ||
67 | + y: rs.randDim(dim[1]) | ||
68 | + }; | ||
69 | + } | ||
70 | + | ||
71 | + function near(node) { | ||
72 | + return { | ||
73 | + x: node.x + nearDist + rs.spread(nearDist), | ||
74 | + y: node.y + nearDist + rs.spread(nearDist) | ||
75 | + }; | ||
76 | + } | ||
77 | + | ||
78 | + function getDevice(cp) { | ||
79 | + return rand(); | ||
80 | + } | ||
81 | + | ||
82 | + xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); | ||
83 | + | ||
84 | + if (node.class === 'sub-region') { | ||
85 | + xy = rand(); | ||
86 | + node.x = node.px = xy.x; | ||
87 | + node.y = node.py = xy.y; | ||
88 | + } | ||
89 | + angular.extend(node, xy); | ||
90 | + } | ||
91 | + | ||
92 | + function setLongLat(el) { | ||
93 | + var loc = el.get('location'), | ||
94 | + coord; | ||
95 | + | ||
96 | + if (loc && loc.type === 'lnglat') { | ||
97 | + | ||
98 | + if (loc.lat === 0 && loc.lng === 0) { | ||
99 | + return false; | ||
100 | + } | ||
101 | + | ||
102 | + coord = coordFromLngLat(loc); | ||
103 | + el.fixed = true; | ||
104 | + el.x = el.px = coord[0]; | ||
105 | + el.y = el.py = coord[1]; | ||
106 | + | ||
107 | + return true; | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + function coordFromLngLat(loc) { | ||
112 | + var p = t2mcs.projection(); | ||
113 | + return p ? p([loc.lng, loc.lat]) : [0, 0]; | ||
114 | + } | ||
115 | + | ||
116 | + angular.module('ovTopo2') | ||
117 | + .factory('Topo2NodePositionService', | ||
118 | + ['RandomService', | ||
119 | + function (_rs_, _t2mcs_) { | ||
120 | + | ||
121 | + rs = _rs_; | ||
122 | + t2mcs = _t2mcs_; | ||
123 | + | ||
124 | + return { | ||
125 | + positionNode: positionNode, | ||
126 | + setLongLat: setLongLat | ||
127 | + }; | ||
128 | + } | ||
129 | + ]); | ||
130 | +})(); |
... | @@ -29,7 +29,9 @@ | ... | @@ -29,7 +29,9 @@ |
29 | this.p = ps.createPanel(this.id, options); | 29 | this.p = ps.createPanel(this.id, options); |
30 | this.setup(); | 30 | this.setup(); |
31 | 31 | ||
32 | + if (options.show) { | ||
32 | this.p.show(); | 33 | this.p.show(); |
34 | + } | ||
33 | }; | 35 | }; |
34 | 36 | ||
35 | Panel.prototype = { | 37 | Panel.prototype = { |
... | @@ -59,8 +61,8 @@ | ... | @@ -59,8 +61,8 @@ |
59 | this.body.selectAll("*").remove(); | 61 | this.body.selectAll("*").remove(); |
60 | this.footer.selectAll("*").remove(); | 62 | this.footer.selectAll("*").remove(); |
61 | }, | 63 | }, |
62 | - destory: function () { | 64 | + destroy: function () { |
63 | - ps.destroy(this.id); | 65 | + ps.destroyPanel(this.id); |
64 | } | 66 | } |
65 | }; | 67 | }; |
66 | 68 | ... | ... |
... | @@ -80,6 +80,11 @@ | ... | @@ -80,6 +80,11 @@ |
80 | return []; | 80 | return []; |
81 | } | 81 | } |
82 | 82 | ||
83 | + function filterRegionNodes(predicate) { | ||
84 | + var nodes = regionNodes(); | ||
85 | + return _.filter(nodes, predicate); | ||
86 | + } | ||
87 | + | ||
83 | function regionLinks() { | 88 | function regionLinks() { |
84 | return (region) ? region.get('links').models : []; | 89 | return (region) ? region.get('links').models : []; |
85 | } | 90 | } |
... | @@ -105,6 +110,7 @@ | ... | @@ -105,6 +110,7 @@ |
105 | addRegion: addRegion, | 110 | addRegion: addRegion, |
106 | regionNodes: regionNodes, | 111 | regionNodes: regionNodes, |
107 | regionLinks: regionLinks, | 112 | regionLinks: regionLinks, |
113 | + filterRegionNodes: filterRegionNodes, | ||
108 | 114 | ||
109 | getSubRegions: t2sr.getSubRegions | 115 | getSubRegions: t2sr.getSubRegions |
110 | }; | 116 | }; | ... | ... |
... | @@ -29,8 +29,12 @@ | ... | @@ -29,8 +29,12 @@ |
29 | var summaryPanel, summaryData; | 29 | var summaryPanel, summaryData; |
30 | 30 | ||
31 | // configuration | 31 | // configuration |
32 | - var id = 'topo-p-summary', | 32 | + var id = 'topo2-p-summary', |
33 | className = 'topo-p', | 33 | className = 'topo-p', |
34 | + panelOpts = { | ||
35 | + show: true, | ||
36 | + width: 260 // summary and detail panel width | ||
37 | + }, | ||
34 | handlerMap = { | 38 | handlerMap = { |
35 | showSummary: handleSummaryData | 39 | showSummary: handleSummaryData |
36 | }; | 40 | }; |
... | @@ -40,10 +44,12 @@ | ... | @@ -40,10 +44,12 @@ |
40 | bindHandlers(); | 44 | bindHandlers(); |
41 | wss.sendEvent('requestSummary'); | 45 | wss.sendEvent('requestSummary'); |
42 | 46 | ||
43 | - summaryPanel = new Panel(id, { | 47 | + var options = angular.extend({}, panelOpts, { |
44 | class: className | 48 | class: className |
45 | }); | 49 | }); |
46 | 50 | ||
51 | + summaryPanel = new Panel(id, options); | ||
52 | + | ||
47 | summaryPanel.p.classed(className, true); | 53 | summaryPanel.p.classed(className, true); |
48 | } | 54 | } |
49 | 55 | ||
... | @@ -107,6 +113,11 @@ | ... | @@ -107,6 +113,11 @@ |
107 | flash.flash(verb + ' Summary Panel'); | 113 | flash.flash(verb + ' Summary Panel'); |
108 | } | 114 | } |
109 | 115 | ||
116 | + function destroy() { | ||
117 | + wss.unbindHandlers(handlerMap); | ||
118 | + summaryPanel.destroy(); | ||
119 | + } | ||
120 | + | ||
110 | angular.module('ovTopo2') | 121 | angular.module('ovTopo2') |
111 | .factory('Topo2SummaryPanelService', | 122 | .factory('Topo2SummaryPanelService', |
112 | ['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', | 123 | ['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', |
... | @@ -120,7 +131,8 @@ | ... | @@ -120,7 +131,8 @@ |
120 | return { | 131 | return { |
121 | init: init, | 132 | init: init, |
122 | 133 | ||
123 | - toggle: toggle | 134 | + toggle: toggle, |
135 | + destroy: destroy | ||
124 | }; | 136 | }; |
125 | } | 137 | } |
126 | ]); | 138 | ]); | ... | ... |
... | @@ -15,14 +15,21 @@ | ... | @@ -15,14 +15,21 @@ |
15 | */ | 15 | */ |
16 | 16 | ||
17 | /* | 17 | /* |
18 | - ONOS GUI -- Topology Layout Module. | 18 | + ONOS GUI -- Topology View Module. |
19 | - Module that contains the d3.force.layout logic | 19 | + Module that contains the topology view variables |
20 | */ | 20 | */ |
21 | 21 | ||
22 | (function () { | 22 | (function () { |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | - var dimensions; | 25 | + // Injected Services |
26 | + var flash; | ||
27 | + | ||
28 | + // Internal State | ||
29 | + var dimensions, | ||
30 | + viewOptions = { | ||
31 | + linkPortHighlighting: true | ||
32 | + }; | ||
26 | 33 | ||
27 | function newDim(_dimensions) { | 34 | function newDim(_dimensions) { |
28 | dimensions = _dimensions; | 35 | dimensions = _dimensions; |
... | @@ -32,13 +39,33 @@ | ... | @@ -32,13 +39,33 @@ |
32 | return dimensions; | 39 | return dimensions; |
33 | } | 40 | } |
34 | 41 | ||
42 | + function togglePortHighlights(x) { | ||
43 | + var kev = (x === 'keyev'), | ||
44 | + on = kev ? !viewOptions.linkPortHighlighting : Boolean(x), | ||
45 | + what = on ? 'Enable' : 'Disable'; | ||
46 | + | ||
47 | + viewOptions.linkPortHighlighting = on; | ||
48 | + flash.flash(what + ' port highlighting'); | ||
49 | + return on; | ||
50 | + } | ||
51 | + | ||
52 | + function getPortHighlighting() { | ||
53 | + return viewOptions.linkPortHighlighting; | ||
54 | + } | ||
55 | + | ||
35 | angular.module('ovTopo2') | 56 | angular.module('ovTopo2') |
36 | .factory('Topo2ViewService', | 57 | .factory('Topo2ViewService', |
37 | - [ | 58 | + ['FlashService', |
38 | - function () { | 59 | + function (_flash_) { |
60 | + | ||
61 | + flash = _flash_; | ||
62 | + | ||
39 | return { | 63 | return { |
40 | newDim: newDim, | 64 | newDim: newDim, |
41 | - getDimensions: getDimensions | 65 | + getDimensions: getDimensions, |
66 | + | ||
67 | + togglePortHighlights: togglePortHighlights, | ||
68 | + getPortHighlighting: getPortHighlighting | ||
42 | }; | 69 | }; |
43 | } | 70 | } |
44 | ] | 71 | ] | ... | ... |
... | @@ -40,6 +40,7 @@ | ... | @@ -40,6 +40,7 @@ |
40 | 40 | ||
41 | <script src="tp/Chart.min.js"></script> | 41 | <script src="tp/Chart.min.js"></script> |
42 | <script src="tp/angular-chart.min.js"></script> | 42 | <script src="tp/angular-chart.min.js"></script> |
43 | + <script src="tp/lodash.min.js"></script> | ||
43 | 44 | ||
44 | <!-- {INJECTED-USER-START} --> | 45 | <!-- {INJECTED-USER-START} --> |
45 | <!-- {INJECTED-USER-END} --> | 46 | <!-- {INJECTED-USER-END} --> |
... | @@ -132,6 +133,7 @@ | ... | @@ -132,6 +133,7 @@ |
132 | <script src="app/view/topo2/topo2D3.js"></script> | 133 | <script src="app/view/topo2/topo2D3.js"></script> |
133 | <script src="app/view/topo2/topo2Dialog.js"></script> | 134 | <script src="app/view/topo2/topo2Dialog.js"></script> |
134 | <script src="app/view/topo2/topo2Device.js"></script> | 135 | <script src="app/view/topo2/topo2Device.js"></script> |
136 | + <script src="app/view/topo2/topo2DeviceDetailsPanel.js"></script> | ||
135 | <script src="app/view/topo2/topo2Event.js"></script> | 137 | <script src="app/view/topo2/topo2Event.js"></script> |
136 | <script src="app/view/topo2/topo2Force.js"></script> | 138 | <script src="app/view/topo2/topo2Force.js"></script> |
137 | <script src="app/view/topo2/topo2Host.js"></script> | 139 | <script src="app/view/topo2/topo2Host.js"></script> |
... | @@ -145,6 +147,7 @@ | ... | @@ -145,6 +147,7 @@ |
145 | <script src="app/view/topo2/topo2MapDialog.js"></script> | 147 | <script src="app/view/topo2/topo2MapDialog.js"></script> |
146 | <script src="app/view/topo2/topo2Model.js"></script> | 148 | <script src="app/view/topo2/topo2Model.js"></script> |
147 | <script src="app/view/topo2/topo2NodeModel.js"></script> | 149 | <script src="app/view/topo2/topo2NodeModel.js"></script> |
150 | + <script src="app/view/topo2/topo2NodePosition.js"></script> | ||
148 | <script src="app/view/topo2/topo2Panel.js"></script> | 151 | <script src="app/view/topo2/topo2Panel.js"></script> |
149 | <script src="app/view/topo2/topo2Prefs.js"></script> | 152 | <script src="app/view/topo2/topo2Prefs.js"></script> |
150 | <script src="app/view/topo2/topo2Region.js"></script> | 153 | <script src="app/view/topo2/topo2Region.js"></script> | ... | ... |
web/gui/src/main/webapp/tp/lodash.min.js
0 → 100644
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment