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
670 additions
and
194 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,44 +142,47 @@ | ... | @@ -144,44 +142,47 @@ |
| 144 | }); | 142 | }); |
| 145 | 143 | ||
| 146 | this.el.classed('enhanced', true); | 144 | this.el.classed('enhanced', true); |
| 147 | - point = this.locatePortLabel(); | ||
| 148 | - angular.extend(point, { | ||
| 149 | - id: 'topo-port-tgt', | ||
| 150 | - num: this.get('portB') | ||
| 151 | - }); | ||
| 152 | - data.push(point); | ||
| 153 | 145 | ||
| 154 | - if (this.get('portA')) { | 146 | + if (showPort()) { |
| 155 | - point = this.locatePortLabel(1); | 147 | + point = this.locatePortLabel(); |
| 156 | angular.extend(point, { | 148 | angular.extend(point, { |
| 157 | - id: 'topo-port-src', | 149 | + id: 'topo-port-tgt', |
| 158 | - num: this.get('portA') | 150 | + num: this.get('portB') |
| 159 | }); | 151 | }); |
| 160 | data.push(point); | 152 | data.push(point); |
| 161 | - } | ||
| 162 | 153 | ||
| 163 | - var entering = d3.select('#topo-portLabels') | 154 | + if (this.get('portA')) { |
| 164 | - .selectAll('.portLabel') | 155 | + point = this.locatePortLabel(1); |
| 165 | - .data(data).enter().append('g') | 156 | + angular.extend(point, { |
| 166 | - .classed('portLabel', true) | 157 | + id: 'topo-port-src', |
| 167 | - .attr('id', function (d) { return d.id; }); | 158 | + num: this.get('portA') |
| 159 | + }); | ||
| 160 | + data.push(point); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + var entering = d3.select('#topo-portLabels') | ||
| 164 | + .selectAll('.portLabel') | ||
| 165 | + .data(data).enter().append('g') | ||
| 166 | + .classed('portLabel', true) | ||
| 167 | + .attr('id', function (d) { return d.id; }); | ||
| 168 | 168 | ||
| 169 | - entering.each(function (d) { | 169 | + entering.each(function (d) { |
| 170 | - var el = d3.select(this), | 170 | + var el = d3.select(this), |
| 171 | - rect = el.append('rect'), | 171 | + rect = el.append('rect'), |
| 172 | - text = el.append('text').text(d.num); | 172 | + text = el.append('text').text(d.num); |
| 173 | 173 | ||
| 174 | - var rectSize = rectAroundText(el); | 174 | + var rectSize = rectAroundText(el); |
| 175 | 175 | ||
| 176 | - rect.attr(rectSize) | 176 | + rect.attr(rectSize) |
| 177 | - .attr('rx', 2) | 177 | + .attr('rx', 2) |
| 178 | - .attr('ry', 2); | 178 | + .attr('ry', 2); |
| 179 | 179 | ||
| 180 | - text.attr('dy', linkLabelOffset) | 180 | + text.attr('dy', linkLabelOffset) |
| 181 | - .attr('text-anchor', 'middle'); | 181 | + .attr('text-anchor', 'middle'); |
| 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 | - this.p.show(); | 32 | + if (options.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