GUI -- Added handling of hosts and links. (Still WIP).
Change-Id: I0ad3b16d47b264b6812f732f220230a2ae92de02
Showing
10 changed files
with
872 additions
and
101 deletions
| ... | @@ -20,7 +20,7 @@ | ... | @@ -20,7 +20,7 @@ |
| 20 | (function () { | 20 | (function () { |
| 21 | 'use strict'; | 21 | 'use strict'; |
| 22 | 22 | ||
| 23 | - var $log, fs, gs; | 23 | + var $log, fs, gs, sus; |
| 24 | 24 | ||
| 25 | var vboxSize = 50, | 25 | var vboxSize = 50, |
| 26 | cornerSize = vboxSize / 10, | 26 | cornerSize = vboxSize / 10, |
| ... | @@ -144,8 +144,21 @@ | ... | @@ -144,8 +144,21 @@ |
| 144 | return g; | 144 | return g; |
| 145 | } | 145 | } |
| 146 | 146 | ||
| 147 | - function addHostIcon(elem, glyphId) { | 147 | + function addHostIcon(elem, radius, glyphId) { |
| 148 | - // TODO: | 148 | + var dim = radius * 1.5, |
| 149 | + xlate = -dim / 2, | ||
| 150 | + g = elem.append('g') | ||
| 151 | + .attr('class', 'svgIcon hostIcon'); | ||
| 152 | + | ||
| 153 | + g.append('circle').attr('r', radius); | ||
| 154 | + | ||
| 155 | + g.append('use').attr({ | ||
| 156 | + 'xlink:href': '#' + glyphId, | ||
| 157 | + width: dim, | ||
| 158 | + height: dim, | ||
| 159 | + transform: sus.translate(xlate,xlate) | ||
| 160 | + }); | ||
| 161 | + return g; | ||
| 149 | } | 162 | } |
| 150 | 163 | ||
| 151 | 164 | ||
| ... | @@ -154,10 +167,13 @@ | ... | @@ -154,10 +167,13 @@ |
| 154 | 167 | ||
| 155 | angular.module('onosSvg') | 168 | angular.module('onosSvg') |
| 156 | .factory('IconService', ['$log', 'FnService', 'GlyphService', | 169 | .factory('IconService', ['$log', 'FnService', 'GlyphService', |
| 157 | - function (_$log_, _fs_, _gs_) { | 170 | + 'SvgUtilService', |
| 171 | + | ||
| 172 | + function (_$log_, _fs_, _gs_, _sus_) { | ||
| 158 | $log = _$log_; | 173 | $log = _$log_; |
| 159 | fs = _fs_; | 174 | fs = _fs_; |
| 160 | gs = _gs_; | 175 | gs = _gs_; |
| 176 | + sus = _sus_; | ||
| 161 | 177 | ||
| 162 | return { | 178 | return { |
| 163 | loadIcon: loadIcon, | 179 | loadIcon: loadIcon, | ... | ... |
| ... | @@ -325,19 +325,121 @@ | ... | @@ -325,19 +325,121 @@ |
| 325 | /* Host Nodes */ | 325 | /* Host Nodes */ |
| 326 | 326 | ||
| 327 | #ov-topo svg .node.host { | 327 | #ov-topo svg .node.host { |
| 328 | - stroke: #000; | ||
| 329 | } | 328 | } |
| 330 | 329 | ||
| 331 | #ov-topo svg .node.host text { | 330 | #ov-topo svg .node.host text { |
| 332 | - fill: #846; | ||
| 333 | stroke: none; | 331 | stroke: none; |
| 334 | font: 9pt sans-serif; | 332 | font: 9pt sans-serif; |
| 335 | } | 333 | } |
| 334 | +.light #ov-topo svg .node.host text { | ||
| 335 | + fill: #846; | ||
| 336 | +} | ||
| 337 | +.dark #ov-topo svg .node.host text { | ||
| 338 | + fill: #BB809D; | ||
| 339 | +} | ||
| 336 | 340 | ||
| 337 | -svg .node.host circle { | 341 | +.light svg .node.host circle { |
| 338 | stroke: #000; | 342 | stroke: #000; |
| 339 | fill: #edb; | 343 | fill: #edb; |
| 340 | } | 344 | } |
| 345 | +.dark svg .node.host circle { | ||
| 346 | + stroke: #eee; | ||
| 347 | + fill: #B2A180; | ||
| 348 | +} | ||
| 349 | + | ||
| 350 | +.light svg .node.host .svgIcon { | ||
| 351 | + fill: #444; | ||
| 352 | +} | ||
| 353 | +.dark svg .node.host .svgIcon { | ||
| 354 | + fill: #222; | ||
| 355 | +} | ||
| 356 | + | ||
| 357 | +/* --- Topo Links --- */ | ||
| 358 | + | ||
| 359 | +#ov-topo svg .link { | ||
| 360 | + opacity: .9; | ||
| 361 | +} | ||
| 362 | + | ||
| 363 | +#ov-topo svg .link.inactive { | ||
| 364 | + opacity: .5; | ||
| 365 | + stroke-dasharray: 8 4; | ||
| 366 | +} | ||
| 367 | + | ||
| 368 | +#ov-topo svg .link.secondary { | ||
| 369 | + stroke-width: 3px; | ||
| 370 | +} | ||
| 371 | +.light #ov-topo svg .link.secondary { | ||
| 372 | + stroke: rgba(0,153,51,0.5); | ||
| 373 | +} | ||
| 374 | +.dark #ov-topo svg .link.secondary { | ||
| 375 | + stroke: rgba(121,231,158,0.5); | ||
| 376 | +} | ||
| 377 | + | ||
| 378 | +#ov-topo svg .link.primary { | ||
| 379 | + stroke-width: 4px; | ||
| 380 | +} | ||
| 381 | +.light #ov-topo svg .link.primary { | ||
| 382 | + stroke: #ffA300; | ||
| 383 | +} | ||
| 384 | +.dark #ov-topo svg .link.primary { | ||
| 385 | + stroke: #D58E0F; | ||
| 386 | +} | ||
| 387 | + | ||
| 388 | +.light #ov-topo svg .link.animated { | ||
| 389 | + stroke: #ffA300; | ||
| 390 | +} | ||
| 391 | +.dark #ov-topo svg .link.animated { | ||
| 392 | + stroke: #D58E0F; | ||
| 393 | +} | ||
| 394 | + | ||
| 395 | +#ov-topo svg .link.secondary.optical { | ||
| 396 | + stroke-width: 4px; | ||
| 397 | +} | ||
| 398 | +.light #ov-topo svg .link.secondary.optical { | ||
| 399 | + stroke: rgba(128,64,255,0.5); | ||
| 400 | +} | ||
| 401 | +.dark #ov-topo svg .link.secondary.optical { | ||
| 402 | + stroke: rgba(164,139,215,0.5); | ||
| 403 | +} | ||
| 341 | 404 | ||
| 405 | +#ov-topo svg .link.primary.optical { | ||
| 406 | + stroke-width: 6px; | ||
| 407 | +} | ||
| 408 | +.light #ov-topo svg .link.primary.optical { | ||
| 409 | + stroke: #74f; | ||
| 410 | +} | ||
| 411 | +.dark #ov-topo svg .link.primary.optical { | ||
| 412 | + stroke: #7352CD; | ||
| 413 | +} | ||
| 414 | + | ||
| 415 | +#ov-topo svg .link.animated.optical { | ||
| 416 | + stroke-width: 10px; | ||
| 417 | +} | ||
| 418 | +.light #ov-topo svg .link.animated.optical { | ||
| 419 | + stroke: #74f; | ||
| 420 | +} | ||
| 421 | +.dark #ov-topo svg .link.animated.optical { | ||
| 422 | + stroke: #7352CD; | ||
| 423 | +} | ||
| 342 | 424 | ||
| 425 | +#ov-topo svg .linkLabel rect { | ||
| 426 | + stroke: none; | ||
| 427 | +} | ||
| 428 | +.light #ov-topo svg .linkLabel rect { | ||
| 429 | + fill: #eee; | ||
| 430 | +} | ||
| 431 | +.dark #ov-topo svg .linkLabel rect { | ||
| 432 | + fill: #eee; | ||
| 433 | +} | ||
| 343 | 434 | ||
| 435 | +#ov-topo svg .linkLabel text { | ||
| 436 | + text-anchor: middle; | ||
| 437 | + stroke-width: 0.1; | ||
| 438 | + font-size: 9pt; | ||
| 439 | +} | ||
| 440 | +.light #ov-topo svg .linkLabel text { | ||
| 441 | + stroke: #777; | ||
| 442 | +} | ||
| 443 | +.dark #ov-topo svg .linkLabel text { | ||
| 444 | + stroke: #777; | ||
| 445 | +} | ... | ... |
| ... | @@ -198,12 +198,6 @@ | ... | @@ -198,12 +198,6 @@ |
| 198 | return ms.loadMapInto(mapG, '*continental_us'); | 198 | return ms.loadMapInto(mapG, '*continental_us'); |
| 199 | } | 199 | } |
| 200 | 200 | ||
| 201 | - // --- Force Layout -------------------------------------------------- | ||
| 202 | - | ||
| 203 | - function setUpForce(xlink) { | ||
| 204 | - forceG = zoomLayer.append('g').attr('id', 'topo-force'); | ||
| 205 | - tfs.initForce(forceG, xlink, svg.attr('width'), svg.attr('height')); | ||
| 206 | - } | ||
| 207 | 201 | ||
| 208 | // --- Controller Definition ----------------------------------------- | 202 | // --- Controller Definition ----------------------------------------- |
| 209 | 203 | ||
| ... | @@ -219,8 +213,12 @@ | ... | @@ -219,8 +213,12 @@ |
| 219 | function ($scope, _$log_, $loc, $timeout, _fs_, mast, | 213 | function ($scope, _$log_, $loc, $timeout, _fs_, mast, |
| 220 | _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { | 214 | _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { |
| 221 | var self = this, | 215 | var self = this, |
| 222 | - xlink = { | 216 | + projection, |
| 223 | - showNoDevs: showNoDevs | 217 | + uplink = { |
| 218 | + // provides function calls back into this space | ||
| 219 | + showNoDevs: showNoDevs, | ||
| 220 | + projection: function () { return projection; }, | ||
| 221 | + sendEvent: tes.sendEvent | ||
| 224 | }; | 222 | }; |
| 225 | 223 | ||
| 226 | $log = _$log_; | 224 | $log = _$log_; |
| ... | @@ -255,9 +253,15 @@ | ... | @@ -255,9 +253,15 @@ |
| 255 | setUpDefs(); | 253 | setUpDefs(); |
| 256 | setUpZoom(); | 254 | setUpZoom(); |
| 257 | setUpNoDevs(); | 255 | setUpNoDevs(); |
| 258 | - xlink.projectionPromise = setUpMap(); | 256 | + setUpMap().then( |
| 259 | - setUpForce(xlink); | 257 | + function (proj) { |
| 260 | - | 258 | + projection = proj; |
| 259 | + $log.debug('** We installed the projection: ', proj); | ||
| 260 | + } | ||
| 261 | + ); | ||
| 262 | + | ||
| 263 | + forceG = zoomLayer.append('g').attr('id', 'topo-force'); | ||
| 264 | + tfs.initForce(forceG, uplink, svg.attr('width'), svg.attr('height')); | ||
| 261 | tis.initInst(); | 265 | tis.initInst(); |
| 262 | tps.initPanels(); | 266 | tps.initPanels(); |
| 263 | tes.openSock(); | 267 | tes.openSock(); | ... | ... |
| ... | @@ -34,9 +34,16 @@ | ... | @@ -34,9 +34,16 @@ |
| 34 | updateInstance: updateInstance, | 34 | updateInstance: updateInstance, |
| 35 | removeInstance: removeInstance, | 35 | removeInstance: removeInstance, |
| 36 | addDevice: addDevice, | 36 | addDevice: addDevice, |
| 37 | - updateDevice: updateDevice | 37 | + updateDevice: updateDevice, |
| 38 | - // TODO: implement remaining handlers.. | 38 | + removeDevice: removeDevice, |
| 39 | + addHost: addHost, | ||
| 40 | + updateHost: updateHost, | ||
| 41 | + removeHost: removeHost, | ||
| 42 | + addLink: addLink, | ||
| 43 | + updateLink: updateLink, | ||
| 44 | + removeLink: removeLink | ||
| 39 | 45 | ||
| 46 | + // TODO: implement remaining handlers.. | ||
| 40 | }; | 47 | }; |
| 41 | 48 | ||
| 42 | function unknownEvent(ev) { | 49 | function unknownEvent(ev) { |
| ... | @@ -45,6 +52,9 @@ | ... | @@ -45,6 +52,9 @@ |
| 45 | 52 | ||
| 46 | // === Event Handlers === | 53 | // === Event Handlers === |
| 47 | 54 | ||
| 55 | + // NOTE: --- once these are done, we will collapse them into | ||
| 56 | + // a more compact data structure... but for now, write in full.. | ||
| 57 | + | ||
| 48 | function showSummary(ev) { | 58 | function showSummary(ev) { |
| 49 | $log.debug(' **** Show Summary **** ', ev.payload); | 59 | $log.debug(' **** Show Summary **** ', ev.payload); |
| 50 | tps.showSummary(ev.payload); | 60 | tps.showSummary(ev.payload); |
| ... | @@ -75,6 +85,42 @@ | ... | @@ -75,6 +85,42 @@ |
| 75 | tfs.updateDevice(ev.payload); | 85 | tfs.updateDevice(ev.payload); |
| 76 | } | 86 | } |
| 77 | 87 | ||
| 88 | + function removeDevice(ev) { | ||
| 89 | + $log.debug(' **** Remove Device **** ', ev.payload); | ||
| 90 | + tfs.removeDevice(ev.payload); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + function addHost(ev) { | ||
| 94 | + $log.debug(' **** Add Host **** ', ev.payload); | ||
| 95 | + tfs.addHost(ev.payload); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + function updateHost(ev) { | ||
| 99 | + $log.debug(' **** Update Host **** ', ev.payload); | ||
| 100 | + tfs.updateHost(ev.payload); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + function removeHost(ev) { | ||
| 104 | + $log.debug(' **** Remove Host **** ', ev.payload); | ||
| 105 | + tfs.removeHost(ev.payload); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + function addLink(ev) { | ||
| 109 | + $log.debug(' **** Add Link **** ', ev.payload); | ||
| 110 | + tfs.addLink(ev.payload); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + function updateLink(ev) { | ||
| 114 | + $log.debug(' **** Update Link **** ', ev.payload); | ||
| 115 | + tfs.updateLink(ev.payload); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + function removeLink(ev) { | ||
| 119 | + $log.debug(' **** Remove Link **** ', ev.payload); | ||
| 120 | + tfs.removeLink(ev.payload); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + | ||
| 78 | // ========================== | 124 | // ========================== |
| 79 | 125 | ||
| 80 | var dispatcher = { | 126 | var dispatcher = { |
| ... | @@ -122,14 +168,9 @@ | ... | @@ -122,14 +168,9 @@ |
| 122 | tis = _tis_; | 168 | tis = _tis_; |
| 123 | tfs = _tfs_; | 169 | tfs = _tfs_; |
| 124 | 170 | ||
| 125 | - function bindDispatcher(TopoDomElementsPassedHere) { | ||
| 126 | - // TODO: store refs to topo DOM elements... | ||
| 127 | - | ||
| 128 | - return dispatcher; | ||
| 129 | - } | ||
| 130 | - | ||
| 131 | // TODO: handle "guiSuccessor" functionality (replace host) | 171 | // TODO: handle "guiSuccessor" functionality (replace host) |
| 132 | // TODO: implement retry on close functionality | 172 | // TODO: implement retry on close functionality |
| 173 | + | ||
| 133 | function openSock() { | 174 | function openSock() { |
| 134 | wsock = wss.createWebSocket('topology', { | 175 | wsock = wss.createWebSocket('topology', { |
| 135 | onOpen: onWsOpen, | 176 | onOpen: onWsOpen, |
| ... | @@ -151,9 +192,9 @@ | ... | @@ -151,9 +192,9 @@ |
| 151 | } | 192 | } |
| 152 | 193 | ||
| 153 | return { | 194 | return { |
| 154 | - bindDispatcher: bindDispatcher, | ||
| 155 | openSock: openSock, | 195 | openSock: openSock, |
| 156 | - closeSock: closeSock | 196 | + closeSock: closeSock, |
| 197 | + sendEvent: dispatcher.sendEvent | ||
| 157 | }; | 198 | }; |
| 158 | }]); | 199 | }]); |
| 159 | }()); | 200 | }()); | ... | ... |
| ... | @@ -23,7 +23,9 @@ | ... | @@ -23,7 +23,9 @@ |
| 23 | 'use strict'; | 23 | 'use strict'; |
| 24 | 24 | ||
| 25 | // injected refs | 25 | // injected refs |
| 26 | - var $log, sus, is, ts, tis, xlink; | 26 | + var $log, fs, sus, is, ts, tis, uplink; |
| 27 | + | ||
| 28 | + var icfg; | ||
| 27 | 29 | ||
| 28 | // configuration | 30 | // configuration |
| 29 | var labelConfig = { | 31 | var labelConfig = { |
| ... | @@ -44,6 +46,21 @@ | ... | @@ -44,6 +46,21 @@ |
| 44 | yoff: -18 | 46 | yoff: -18 |
| 45 | }; | 47 | }; |
| 46 | 48 | ||
| 49 | + var linkConfig = { | ||
| 50 | + light: { | ||
| 51 | + baseColor: '#666', | ||
| 52 | + inColor: '#66f', | ||
| 53 | + outColor: '#f00', | ||
| 54 | + }, | ||
| 55 | + dark: { | ||
| 56 | + baseColor: '#666', | ||
| 57 | + inColor: '#66f', | ||
| 58 | + outColor: '#f00', | ||
| 59 | + }, | ||
| 60 | + inWidth: 12, | ||
| 61 | + outWidth: 10 | ||
| 62 | + }; | ||
| 63 | + | ||
| 47 | // internal state | 64 | // internal state |
| 48 | var settings, // merged default settings and options | 65 | var settings, // merged default settings and options |
| 49 | force, // force layout object | 66 | force, // force layout object |
| ... | @@ -54,9 +71,11 @@ | ... | @@ -54,9 +71,11 @@ |
| 54 | lookup: {}, | 71 | lookup: {}, |
| 55 | revLinkToKey: {} | 72 | revLinkToKey: {} |
| 56 | }, | 73 | }, |
| 57 | - projection, // background map projection | 74 | + lu = network.lookup, // shorthand |
| 58 | deviceLabelIndex = 0, // for device label cycling | 75 | deviceLabelIndex = 0, // for device label cycling |
| 59 | - hostLabelIndex = 0; // for host label cycling | 76 | + hostLabelIndex = 0, // for host label cycling |
| 77 | + showHosts = 1, // whether hosts are displayed | ||
| 78 | + width, height; | ||
| 60 | 79 | ||
| 61 | // SVG elements; | 80 | // SVG elements; |
| 62 | var linkG, linkLabelG, nodeG; | 81 | var linkG, linkLabelG, nodeG; |
| ... | @@ -99,18 +118,18 @@ | ... | @@ -99,18 +118,18 @@ |
| 99 | var id = data.id, | 118 | var id = data.id, |
| 100 | d; | 119 | d; |
| 101 | 120 | ||
| 102 | - xlink.showNoDevs(false); | 121 | + uplink.showNoDevs(false); |
| 103 | 122 | ||
| 104 | // although this is an add device event, if we already have the | 123 | // although this is an add device event, if we already have the |
| 105 | // device, treat it as an update instead.. | 124 | // device, treat it as an update instead.. |
| 106 | - if (network.lookup[id]) { | 125 | + if (lu[id]) { |
| 107 | updateDevice(data); | 126 | updateDevice(data); |
| 108 | return; | 127 | return; |
| 109 | } | 128 | } |
| 110 | 129 | ||
| 111 | d = createDeviceNode(data); | 130 | d = createDeviceNode(data); |
| 112 | network.nodes.push(d); | 131 | network.nodes.push(d); |
| 113 | - network.lookup[id] = d; | 132 | + lu[id] = d; |
| 114 | 133 | ||
| 115 | $log.debug("Created new device.. ", d.id, d.x, d.y); | 134 | $log.debug("Created new device.. ", d.id, d.x, d.y); |
| 116 | 135 | ||
| ... | @@ -120,7 +139,7 @@ | ... | @@ -120,7 +139,7 @@ |
| 120 | 139 | ||
| 121 | function updateDevice(data) { | 140 | function updateDevice(data) { |
| 122 | var id = data.id, | 141 | var id = data.id, |
| 123 | - d = network.lookup[id], | 142 | + d = lu[id], |
| 124 | wasOnline; | 143 | wasOnline; |
| 125 | 144 | ||
| 126 | if (d) { | 145 | if (d) { |
| ... | @@ -141,26 +160,379 @@ | ... | @@ -141,26 +160,379 @@ |
| 141 | } | 160 | } |
| 142 | } | 161 | } |
| 143 | 162 | ||
| 163 | + function removeDevice(data) { | ||
| 164 | + var id = data.id, | ||
| 165 | + d = lu[id]; | ||
| 166 | + if (d) { | ||
| 167 | + removeDeviceElement(d); | ||
| 168 | + } else { | ||
| 169 | + // TODO: decide whether we want to capture logic errors | ||
| 170 | + //logicError('removeDevice lookup fail. ID = "' + id + '"'); | ||
| 171 | + } | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + function addHost(data) { | ||
| 175 | + var id = data.id, | ||
| 176 | + d, lnk; | ||
| 177 | + | ||
| 178 | + // although this is an add host event, if we already have the | ||
| 179 | + // host, treat it as an update instead.. | ||
| 180 | + if (lu[id]) { | ||
| 181 | + updateHost(data); | ||
| 182 | + return; | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + d = createHostNode(data); | ||
| 186 | + network.nodes.push(d); | ||
| 187 | + lu[id] = d; | ||
| 188 | + | ||
| 189 | + $log.debug("Created new host.. ", d.id, d.x, d.y); | ||
| 190 | + | ||
| 191 | + updateNodes(); | ||
| 192 | + | ||
| 193 | + lnk = createHostLink(data); | ||
| 194 | + if (lnk) { | ||
| 195 | + | ||
| 196 | + $log.debug("Created new host-link.. ", lnk.key); | ||
| 197 | + | ||
| 198 | + d.linkData = lnk; // cache ref on its host | ||
| 199 | + network.links.push(lnk); | ||
| 200 | + lu[d.ingress] = lnk; | ||
| 201 | + lu[d.egress] = lnk; | ||
| 202 | + updateLinks(); | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + fStart(); | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + function updateHost(data) { | ||
| 209 | + var id = data.id, | ||
| 210 | + d = lu[id]; | ||
| 211 | + if (d) { | ||
| 212 | + angular.extend(d, data); | ||
| 213 | + if (positionNode(d, true)) { | ||
| 214 | + sendUpdateMeta(d, true); | ||
| 215 | + } | ||
| 216 | + updateNodes(); | ||
| 217 | + } else { | ||
| 218 | + // TODO: decide whether we want to capture logic errors | ||
| 219 | + //logicError('updateHost lookup fail. ID = "' + id + '"'); | ||
| 220 | + } | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + function removeHost(data) { | ||
| 224 | + var id = data.id, | ||
| 225 | + d = lu[id]; | ||
| 226 | + if (d) { | ||
| 227 | + removeHostElement(d, true); | ||
| 228 | + } else { | ||
| 229 | + // may have already removed host, if attached to removed device | ||
| 230 | + //console.warn('removeHost lookup fail. ID = "' + id + '"'); | ||
| 231 | + } | ||
| 232 | + } | ||
| 233 | + | ||
| 234 | + function addLink(data) { | ||
| 235 | + var result = findLink(data, 'add'), | ||
| 236 | + bad = result.badLogic, | ||
| 237 | + d = result.ldata; | ||
| 238 | + | ||
| 239 | + if (bad) { | ||
| 240 | + //logicError(bad + ': ' + link.id); | ||
| 241 | + return; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + if (d) { | ||
| 245 | + // we already have a backing store link for src/dst nodes | ||
| 246 | + addLinkUpdate(d, data); | ||
| 247 | + return; | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + // no backing store link yet | ||
| 251 | + d = createLink(data); | ||
| 252 | + if (d) { | ||
| 253 | + network.links.push(d); | ||
| 254 | + lu[d.key] = d; | ||
| 255 | + updateLinks(); | ||
| 256 | + fStart(); | ||
| 257 | + } | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + function updateLink(data) { | ||
| 261 | + var result = findLink(data, 'update'), | ||
| 262 | + bad = result.badLogic; | ||
| 263 | + if (bad) { | ||
| 264 | + //logicError(bad + ': ' + link.id); | ||
| 265 | + return; | ||
| 266 | + } | ||
| 267 | + result.updateWith(link); | ||
| 268 | + } | ||
| 269 | + | ||
| 270 | + function removeLink(data) { | ||
| 271 | + var result = findLink(data, 'remove'), | ||
| 272 | + bad = result.badLogic; | ||
| 273 | + if (bad) { | ||
| 274 | + // may have already removed link, if attached to removed device | ||
| 275 | + //console.warn(bad + ': ' + link.id); | ||
| 276 | + return; | ||
| 277 | + } | ||
| 278 | + result.removeRawLink(); | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + // ======================== | ||
| 282 | + | ||
| 283 | + function addLinkUpdate(ldata, link) { | ||
| 284 | + // add link event, but we already have the reverse link installed | ||
| 285 | + ldata.fromTarget = link; | ||
| 286 | + network.revLinkToKey[link.id] = ldata.key; | ||
| 287 | + restyleLinkElement(ldata); | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + function createLink(link) { | ||
| 291 | + var lnk = linkEndPoints(link.src, link.dst); | ||
| 292 | + | ||
| 293 | + if (!lnk) { | ||
| 294 | + return null; | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + angular.extend(lnk, { | ||
| 298 | + key: link.id, | ||
| 299 | + class: 'link', | ||
| 300 | + fromSource: link, | ||
| 301 | + | ||
| 302 | + // functions to aggregate dual link state | ||
| 303 | + type: function () { | ||
| 304 | + var s = lnk.fromSource, | ||
| 305 | + t = lnk.fromTarget; | ||
| 306 | + return (s && s.type) || (t && t.type) || defaultLinkType; | ||
| 307 | + }, | ||
| 308 | + online: function () { | ||
| 309 | + var s = lnk.fromSource, | ||
| 310 | + t = lnk.fromTarget, | ||
| 311 | + both = lnk.source.online && lnk.target.online; | ||
| 312 | + return both && ((s && s.online) || (t && t.online)); | ||
| 313 | + }, | ||
| 314 | + linkWidth: function () { | ||
| 315 | + var s = lnk.fromSource, | ||
| 316 | + t = lnk.fromTarget, | ||
| 317 | + ws = (s && s.linkWidth) || 0, | ||
| 318 | + wt = (t && t.linkWidth) || 0; | ||
| 319 | + return Math.max(ws, wt); | ||
| 320 | + } | ||
| 321 | + }); | ||
| 322 | + return lnk; | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + | ||
| 326 | + function makeNodeKey(d, what) { | ||
| 327 | + var port = what + 'Port'; | ||
| 328 | + return d[what] + '/' + d[port]; | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + function makeLinkKey(d, flipped) { | ||
| 332 | + var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'), | ||
| 333 | + two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst'); | ||
| 334 | + return one + '-' + two; | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + var widthRatio = 1.4, | ||
| 338 | + linkScale = d3.scale.linear() | ||
| 339 | + .domain([1, 12]) | ||
| 340 | + .range([widthRatio, 12 * widthRatio]) | ||
| 341 | + .clamp(true); | ||
| 342 | + | ||
| 343 | + var allLinkTypes = 'direct indirect optical tunnel', | ||
| 344 | + defaultLinkType = 'direct'; | ||
| 345 | + | ||
| 346 | + function restyleLinkElement(ldata) { | ||
| 347 | + // this fn's job is to look at raw links and decide what svg classes | ||
| 348 | + // need to be applied to the line element in the DOM | ||
| 349 | + var th = ts.theme(), | ||
| 350 | + el = ldata.el, | ||
| 351 | + type = ldata.type(), | ||
| 352 | + lw = ldata.linkWidth(), | ||
| 353 | + online = ldata.online(); | ||
| 354 | + | ||
| 355 | + el.classed('link', true); | ||
| 356 | + el.classed('inactive', !online); | ||
| 357 | + el.classed(allLinkTypes, false); | ||
| 358 | + if (type) { | ||
| 359 | + el.classed(type, true); | ||
| 360 | + } | ||
| 361 | + el.transition() | ||
| 362 | + .duration(1000) | ||
| 363 | + .attr('stroke-width', linkScale(lw)) | ||
| 364 | + .attr('stroke', linkConfig[th].baseColor); | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + function findLink(linkData, op) { | ||
| 368 | + var key = makeLinkKey(linkData), | ||
| 369 | + keyrev = makeLinkKey(linkData, 1), | ||
| 370 | + link = lu[key], | ||
| 371 | + linkRev = lu[keyrev], | ||
| 372 | + result = {}, | ||
| 373 | + ldata = link || linkRev, | ||
| 374 | + rawLink; | ||
| 375 | + | ||
| 376 | + if (op === 'add') { | ||
| 377 | + if (link) { | ||
| 378 | + // trying to add a link that we already know about | ||
| 379 | + result.ldata = link; | ||
| 380 | + result.badLogic = 'addLink: link already added'; | ||
| 381 | + | ||
| 382 | + } else if (linkRev) { | ||
| 383 | + // we found the reverse of the link to be added | ||
| 384 | + result.ldata = linkRev; | ||
| 385 | + if (linkRev.fromTarget) { | ||
| 386 | + result.badLogic = 'addLink: link already added'; | ||
| 387 | + } | ||
| 388 | + } | ||
| 389 | + } else if (op === 'update') { | ||
| 390 | + if (!ldata) { | ||
| 391 | + result.badLogic = 'updateLink: link not found'; | ||
| 392 | + } else { | ||
| 393 | + rawLink = link ? ldata.fromSource : ldata.fromTarget; | ||
| 394 | + result.updateWith = function (data) { | ||
| 395 | + angular.extend(rawLink, data); | ||
| 396 | + restyleLinkElement(ldata); | ||
| 397 | + } | ||
| 398 | + } | ||
| 399 | + } else if (op === 'remove') { | ||
| 400 | + if (!ldata) { | ||
| 401 | + result.badLogic = 'removeLink: link not found'; | ||
| 402 | + } else { | ||
| 403 | + rawLink = link ? ldata.fromSource : ldata.fromTarget; | ||
| 404 | + | ||
| 405 | + if (!rawLink) { | ||
| 406 | + result.badLogic = 'removeLink: link not found'; | ||
| 407 | + | ||
| 408 | + } else { | ||
| 409 | + result.removeRawLink = function () { | ||
| 410 | + if (link) { | ||
| 411 | + // remove fromSource | ||
| 412 | + ldata.fromSource = null; | ||
| 413 | + if (ldata.fromTarget) { | ||
| 414 | + // promote target into source position | ||
| 415 | + ldata.fromSource = ldata.fromTarget; | ||
| 416 | + ldata.fromTarget = null; | ||
| 417 | + ldata.key = keyrev; | ||
| 418 | + delete network.lookup[key]; | ||
| 419 | + network.lookup[keyrev] = ldata; | ||
| 420 | + delete network.revLinkToKey[keyrev]; | ||
| 421 | + } | ||
| 422 | + } else { | ||
| 423 | + // remove fromTarget | ||
| 424 | + ldata.fromTarget = null; | ||
| 425 | + delete network.revLinkToKey[keyrev]; | ||
| 426 | + } | ||
| 427 | + if (ldata.fromSource) { | ||
| 428 | + restyleLinkElement(ldata); | ||
| 429 | + } else { | ||
| 430 | + removeLinkElement(ldata); | ||
| 431 | + } | ||
| 432 | + } | ||
| 433 | + } | ||
| 434 | + } | ||
| 435 | + } | ||
| 436 | + return result; | ||
| 437 | + } | ||
| 438 | + | ||
| 439 | + | ||
| 440 | + function findAttachedHosts(devId) { | ||
| 441 | + var hosts = []; | ||
| 442 | + network.nodes.forEach(function (d) { | ||
| 443 | + if (d.class === 'host' && d.cp.device === devId) { | ||
| 444 | + hosts.push(d); | ||
| 445 | + } | ||
| 446 | + }); | ||
| 447 | + return hosts; | ||
| 448 | + } | ||
| 449 | + | ||
| 450 | + function findAttachedLinks(devId) { | ||
| 451 | + var links = []; | ||
| 452 | + network.links.forEach(function (d) { | ||
| 453 | + if (d.source.id === devId || d.target.id === devId) { | ||
| 454 | + links.push(d); | ||
| 455 | + } | ||
| 456 | + }); | ||
| 457 | + return links; | ||
| 458 | + } | ||
| 459 | + | ||
| 460 | + function removeLinkElement(d) { | ||
| 461 | + var idx = fs.find(d.key, network.links, 'key'), | ||
| 462 | + removed; | ||
| 463 | + if (idx >=0) { | ||
| 464 | + // remove from links array | ||
| 465 | + removed = network.links.splice(idx, 1); | ||
| 466 | + // remove from lookup cache | ||
| 467 | + delete lu[removed[0].key]; | ||
| 468 | + updateLinks(); | ||
| 469 | + fResume(); | ||
| 470 | + } | ||
| 471 | + } | ||
| 472 | + | ||
| 473 | + function removeHostElement(d, upd) { | ||
| 474 | + // first, remove associated hostLink... | ||
| 475 | + removeLinkElement(d.linkData); | ||
| 476 | + | ||
| 477 | + // remove hostLink bindings | ||
| 478 | + delete lu[d.ingress]; | ||
| 479 | + delete lu[d.egress]; | ||
| 480 | + | ||
| 481 | + // remove from lookup cache | ||
| 482 | + delete lu[d.id]; | ||
| 483 | + // remove from nodes array | ||
| 484 | + var idx = fs.find(d.id, network.nodes); | ||
| 485 | + network.nodes.splice(idx, 1); | ||
| 486 | + | ||
| 487 | + // remove from SVG | ||
| 488 | + // NOTE: upd is false if we were called from removeDeviceElement() | ||
| 489 | + if (upd) { | ||
| 490 | + updateNodes(); | ||
| 491 | + fResume(); | ||
| 492 | + } | ||
| 493 | + } | ||
| 494 | + | ||
| 495 | + function removeDeviceElement(d) { | ||
| 496 | + var id = d.id; | ||
| 497 | + // first, remove associated hosts and links.. | ||
| 498 | + findAttachedHosts(id).forEach(removeHostElement); | ||
| 499 | + findAttachedLinks(id).forEach(removeLinkElement); | ||
| 500 | + | ||
| 501 | + // remove from lookup cache | ||
| 502 | + delete lu[id]; | ||
| 503 | + // remove from nodes array | ||
| 504 | + var idx = fs.find(id, network.nodes); | ||
| 505 | + network.nodes.splice(idx, 1); | ||
| 506 | + | ||
| 507 | + if (!network.nodes.length) { | ||
| 508 | + xlink.showNoDevs(true); | ||
| 509 | + } | ||
| 510 | + | ||
| 511 | + // remove from SVG | ||
| 512 | + updateNodes(); | ||
| 513 | + fResume(); | ||
| 514 | + } | ||
| 515 | + | ||
| 516 | + | ||
| 144 | function sendUpdateMeta(d, store) { | 517 | function sendUpdateMeta(d, store) { |
| 145 | var metaUi = {}, | 518 | var metaUi = {}, |
| 146 | ll; | 519 | ll; |
| 147 | 520 | ||
| 148 | - // TODO: fix this code to send event to server... | 521 | + if (store) { |
| 149 | - //if (store) { | 522 | + ll = lngLatFromCoord([d.x, d.y]); |
| 150 | - // ll = geoMapProj.invert([d.x, d.y]); | 523 | + metaUi = { |
| 151 | - // metaUi = { | 524 | + x: d.x, |
| 152 | - // x: d.x, | 525 | + y: d.y, |
| 153 | - // y: d.y, | 526 | + lng: ll[0], |
| 154 | - // lng: ll[0], | 527 | + lat: ll[1] |
| 155 | - // lat: ll[1] | 528 | + }; |
| 156 | - // }; | 529 | + } |
| 157 | - //} | 530 | + d.metaUi = metaUi; |
| 158 | - //d.metaUi = metaUi; | 531 | + uplink.sendEvent('updateMeta', { |
| 159 | - //sendMessage('updateMeta', { | 532 | + id: d.id, |
| 160 | - // id: d.id, | 533 | + 'class': d.class, |
| 161 | - // 'class': d.class, | 534 | + memento: metaUi |
| 162 | - // memento: metaUi | 535 | + }); |
| 163 | - //}); | ||
| 164 | } | 536 | } |
| 165 | 537 | ||
| 166 | 538 | ||
| ... | @@ -178,9 +550,13 @@ | ... | @@ -178,9 +550,13 @@ |
| 178 | // === Devices and hosts - helper functions | 550 | // === Devices and hosts - helper functions |
| 179 | 551 | ||
| 180 | function coordFromLngLat(loc) { | 552 | function coordFromLngLat(loc) { |
| 181 | - // Our hope is that the projection is installed before we start | 553 | + var p = uplink.projection(); |
| 182 | - // handling incoming nodes. But if not, we'll just return the origin. | 554 | + return p ? p([loc.lng, loc.lat]) : [0, 0]; |
| 183 | - return projection ? projection([loc.lng, loc.lat]) : [0, 0]; | 555 | + } |
| 556 | + | ||
| 557 | + function lngLatFromCoord(coord) { | ||
| 558 | + var p = uplink.projection(); | ||
| 559 | + return p ? p.invert([coord.x, coord.y]) : [0, 0]; | ||
| 184 | } | 560 | } |
| 185 | 561 | ||
| 186 | function positionNode(node, forUpdate) { | 562 | function positionNode(node, forUpdate) { |
| ... | @@ -230,8 +606,8 @@ | ... | @@ -230,8 +606,8 @@ |
| 230 | 606 | ||
| 231 | function rand() { | 607 | function rand() { |
| 232 | return { | 608 | return { |
| 233 | - x: randDim(network.view.width()), | 609 | + x: randDim(width), |
| 234 | - y: randDim(network.view.height()) | 610 | + y: randDim(height) |
| 235 | }; | 611 | }; |
| 236 | } | 612 | } |
| 237 | 613 | ||
| ... | @@ -246,7 +622,7 @@ | ... | @@ -246,7 +622,7 @@ |
| 246 | } | 622 | } |
| 247 | 623 | ||
| 248 | function getDevice(cp) { | 624 | function getDevice(cp) { |
| 249 | - var d = network.lookup[cp.device]; | 625 | + var d = lu[cp.device]; |
| 250 | return d || rand(); | 626 | return d || rand(); |
| 251 | } | 627 | } |
| 252 | 628 | ||
| ... | @@ -267,9 +643,83 @@ | ... | @@ -267,9 +643,83 @@ |
| 267 | return node; | 643 | return node; |
| 268 | } | 644 | } |
| 269 | 645 | ||
| 646 | + function createHostNode(host) { | ||
| 647 | + var node = host; | ||
| 648 | + | ||
| 649 | + // Augment as needed... | ||
| 650 | + node.class = 'host'; | ||
| 651 | + if (!node.type) { | ||
| 652 | + node.type = 'endstation'; | ||
| 653 | + } | ||
| 654 | + node.svgClass = 'node host ' + node.type; | ||
| 655 | + positionNode(node); | ||
| 656 | + return node; | ||
| 657 | + } | ||
| 658 | + | ||
| 659 | + function createHostLink(host) { | ||
| 660 | + var src = host.id, | ||
| 661 | + dst = host.cp.device, | ||
| 662 | + id = host.ingress, | ||
| 663 | + lnk = linkEndPoints(src, dst); | ||
| 664 | + | ||
| 665 | + if (!lnk) { | ||
| 666 | + return null; | ||
| 667 | + } | ||
| 668 | + | ||
| 669 | + // Synthesize link ... | ||
| 670 | + angular.extend(lnk, { | ||
| 671 | + key: id, | ||
| 672 | + class: 'link', | ||
| 673 | + | ||
| 674 | + type: function () { return 'hostLink'; }, | ||
| 675 | + online: function () { | ||
| 676 | + // hostlink target is edge switch | ||
| 677 | + return lnk.target.online; | ||
| 678 | + }, | ||
| 679 | + linkWidth: function () { return 1; } | ||
| 680 | + }); | ||
| 681 | + return lnk; | ||
| 682 | + } | ||
| 683 | + | ||
| 684 | + function linkEndPoints(srcId, dstId) { | ||
| 685 | + var srcNode = lu[srcId], | ||
| 686 | + dstNode = lu[dstId], | ||
| 687 | + sMiss = !srcNode ? missMsg('src', srcId) : '', | ||
| 688 | + dMiss = !dstNode ? missMsg('dst', dstId) : ''; | ||
| 689 | + | ||
| 690 | + if (sMiss || dMiss) { | ||
| 691 | + $log.error('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
| 692 | + //logicError('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
| 693 | + return null; | ||
| 694 | + } | ||
| 695 | + return { | ||
| 696 | + source: srcNode, | ||
| 697 | + target: dstNode, | ||
| 698 | + x1: srcNode.x, | ||
| 699 | + y1: srcNode.y, | ||
| 700 | + x2: dstNode.x, | ||
| 701 | + y2: dstNode.y | ||
| 702 | + }; | ||
| 703 | + } | ||
| 704 | + | ||
| 705 | + function missMsg(what, id) { | ||
| 706 | + return '\n[' + what + '] "' + id + '" missing '; | ||
| 707 | + } | ||
| 708 | + | ||
| 270 | // ========================== | 709 | // ========================== |
| 271 | // === Devices and hosts - D3 rendering | 710 | // === Devices and hosts - D3 rendering |
| 272 | 711 | ||
| 712 | + function nodeMouseOver(m) { | ||
| 713 | + // TODO | ||
| 714 | + $log.debug("TODO nodeMouseOver()...", m); | ||
| 715 | + } | ||
| 716 | + | ||
| 717 | + function nodeMouseOut(m) { | ||
| 718 | + // TODO | ||
| 719 | + $log.debug("TODO nodeMouseOut()...", m); | ||
| 720 | + } | ||
| 721 | + | ||
| 722 | + | ||
| 273 | // Returns the newly computed bounding box of the rectangle | 723 | // Returns the newly computed bounding box of the rectangle |
| 274 | function adjustRectToFitText(n) { | 724 | function adjustRectToFitText(n) { |
| 275 | var text = n.select('text'), | 725 | var text = n.select('text'), |
| ... | @@ -323,7 +773,7 @@ | ... | @@ -323,7 +773,7 @@ |
| 323 | var label = trimLabel(deviceLabel(d)), | 773 | var label = trimLabel(deviceLabel(d)), |
| 324 | noLabel = !label, | 774 | noLabel = !label, |
| 325 | node = d.el, | 775 | node = d.el, |
| 326 | - dim = is.iconConfig().device.dim, | 776 | + dim = icfg.device.dim, |
| 327 | devCfg = deviceIconConfig, | 777 | devCfg = deviceIconConfig, |
| 328 | box, dx, dy; | 778 | box, dx, dy; |
| 329 | 779 | ||
| ... | @@ -357,16 +807,6 @@ | ... | @@ -357,16 +807,6 @@ |
| 357 | d.el.select('text').text(label); | 807 | d.el.select('text').text(label); |
| 358 | } | 808 | } |
| 359 | 809 | ||
| 360 | - function nodeMouseOver(m) { | ||
| 361 | - // TODO | ||
| 362 | - $log.debug("TODO nodeMouseOver()...", m); | ||
| 363 | - } | ||
| 364 | - | ||
| 365 | - function nodeMouseOut(m) { | ||
| 366 | - // TODO | ||
| 367 | - $log.debug("TODO nodeMouseOut()...", m); | ||
| 368 | - } | ||
| 369 | - | ||
| 370 | function updateDeviceColors(d) { | 810 | function updateDeviceColors(d) { |
| 371 | if (d) { | 811 | if (d) { |
| 372 | setDeviceColor(d); | 812 | setDeviceColor(d); |
| ... | @@ -445,13 +885,14 @@ | ... | @@ -445,13 +885,14 @@ |
| 445 | return sus.cat7().getColor(id, !online, ts.theme()); | 885 | return sus.cat7().getColor(id, !online, ts.theme()); |
| 446 | } | 886 | } |
| 447 | 887 | ||
| 448 | - //============ | 888 | + // ========================== |
| 449 | 889 | ||
| 450 | function updateNodes() { | 890 | function updateNodes() { |
| 891 | + // select all the nodes in the layout: | ||
| 451 | node = nodeG.selectAll('.node') | 892 | node = nodeG.selectAll('.node') |
| 452 | .data(network.nodes, function (d) { return d.id; }); | 893 | .data(network.nodes, function (d) { return d.id; }); |
| 453 | 894 | ||
| 454 | - // operate on existing nodes... | 895 | + // operate on existing nodes: |
| 455 | node.filter('.device').each(deviceExisting); | 896 | node.filter('.device').each(deviceExisting); |
| 456 | node.filter('.host').each(hostExisting); | 897 | node.filter('.host').each(hostExisting); |
| 457 | 898 | ||
| ... | @@ -470,7 +911,7 @@ | ... | @@ -470,7 +911,7 @@ |
| 470 | .transition() | 911 | .transition() |
| 471 | .attr('opacity', 1); | 912 | .attr('opacity', 1); |
| 472 | 913 | ||
| 473 | - // augment nodes... | 914 | + // augment entering nodes: |
| 474 | entering.filter('.device').each(deviceEnter); | 915 | entering.filter('.device').each(deviceEnter); |
| 475 | entering.filter('.host').each(hostEnter); | 916 | entering.filter('.host').each(hostEnter); |
| 476 | 917 | ||
| ... | @@ -486,7 +927,7 @@ | ... | @@ -486,7 +927,7 @@ |
| 486 | .style('opacity', 0) | 927 | .style('opacity', 0) |
| 487 | .remove(); | 928 | .remove(); |
| 488 | 929 | ||
| 489 | - // node specific.... | 930 | + // exiting node specifics: |
| 490 | exiting.filter('.host').each(hostExit); | 931 | exiting.filter('.host').each(hostExit); |
| 491 | exiting.filter('.device').each(deviceExit); | 932 | exiting.filter('.device').each(deviceExit); |
| 492 | 933 | ||
| ... | @@ -539,25 +980,20 @@ | ... | @@ -539,25 +980,20 @@ |
| 539 | } | 980 | } |
| 540 | 981 | ||
| 541 | function hostEnter(d) { | 982 | function hostEnter(d) { |
| 542 | - var node = d3.select(this); | 983 | + var node = d3.select(this), |
| 543 | - | 984 | + gid = d.type || 'unknown', |
| 544 | - //cfg = config.icons.host, | 985 | + rad = icfg.host.radius, |
| 545 | - //r = cfg.radius[d.type] || cfg.defaultRadius, | 986 | + r = d.type ? rad.withGlyph : rad.noGlyph, |
| 546 | - //textDy = r + 10, | 987 | + textDy = r + 10; |
| 547 | - //TODO: iid = iconGlyphUrl(d), | ||
| 548 | - // _dummy; | ||
| 549 | 988 | ||
| 550 | d.el = node; | 989 | d.el = node; |
| 990 | + sus.makeVisible(node, showHosts); | ||
| 551 | 991 | ||
| 552 | - //TODO: showHostVis(node); | 992 | + is.addHostIcon(node, r, gid); |
| 553 | 993 | ||
| 554 | - node.append('circle').attr('r', r); | ||
| 555 | - //if (iid) { | ||
| 556 | - //TODO: addHostIcon(node, r, iid); | ||
| 557 | - //} | ||
| 558 | node.append('text') | 994 | node.append('text') |
| 559 | .text(hostLabel) | 995 | .text(hostLabel) |
| 560 | - //.attr('dy', textDy) | 996 | + .attr('dy', textDy) |
| 561 | .attr('text-anchor', 'middle'); | 997 | .attr('text-anchor', 'middle'); |
| 562 | } | 998 | } |
| 563 | 999 | ||
| ... | @@ -598,6 +1034,160 @@ | ... | @@ -598,6 +1034,160 @@ |
| 598 | .style('opacity', 0.5); | 1034 | .style('opacity', 0.5); |
| 599 | } | 1035 | } |
| 600 | 1036 | ||
| 1037 | + // ========================== | ||
| 1038 | + | ||
| 1039 | + function updateLinks() { | ||
| 1040 | + var th = ts.theme(); | ||
| 1041 | + | ||
| 1042 | + link = linkG.selectAll('.link') | ||
| 1043 | + .data(network.links, function (d) { return d.key; }); | ||
| 1044 | + | ||
| 1045 | + // operate on existing links: | ||
| 1046 | + //link.each(linkExisting); | ||
| 1047 | + | ||
| 1048 | + // operate on entering links: | ||
| 1049 | + var entering = link.enter() | ||
| 1050 | + .append('line') | ||
| 1051 | + .attr({ | ||
| 1052 | + x1: function (d) { return d.x1; }, | ||
| 1053 | + y1: function (d) { return d.y1; }, | ||
| 1054 | + x2: function (d) { return d.x2; }, | ||
| 1055 | + y2: function (d) { return d.y2; }, | ||
| 1056 | + stroke: linkConfig[th].inColor, | ||
| 1057 | + 'stroke-width': linkConfig.inWidth | ||
| 1058 | + }); | ||
| 1059 | + | ||
| 1060 | + // augment links | ||
| 1061 | + entering.each(linkEntering); | ||
| 1062 | + | ||
| 1063 | + // operate on both existing and new links: | ||
| 1064 | + //link.each(...) | ||
| 1065 | + | ||
| 1066 | + // apply or remove labels | ||
| 1067 | + var labelData = getLabelData(); | ||
| 1068 | + applyLinkLabels(labelData); | ||
| 1069 | + | ||
| 1070 | + // operate on exiting links: | ||
| 1071 | + link.exit() | ||
| 1072 | + .attr('stroke-dasharray', '3 3') | ||
| 1073 | + .style('opacity', 0.5) | ||
| 1074 | + .transition() | ||
| 1075 | + .duration(1500) | ||
| 1076 | + .attr({ | ||
| 1077 | + 'stroke-dasharray': '3 12', | ||
| 1078 | + stroke: linkConfig[th].outColor, | ||
| 1079 | + 'stroke-width': linkConfig.outWidth | ||
| 1080 | + }) | ||
| 1081 | + .style('opacity', 0.0) | ||
| 1082 | + .remove(); | ||
| 1083 | + | ||
| 1084 | + // NOTE: invoke a single tick to force the labels to position | ||
| 1085 | + // onto their links. | ||
| 1086 | + tick(); | ||
| 1087 | + // FIXME: this is a bug when in oblique view | ||
| 1088 | + // It causes the nodes to jump into "overhead" view positions, even | ||
| 1089 | + // though the oblique planes are still showing... | ||
| 1090 | + } | ||
| 1091 | + | ||
| 1092 | + // ========================== | ||
| 1093 | + // updateLinks - subfunctions | ||
| 1094 | + | ||
| 1095 | + function getLabelData() { | ||
| 1096 | + // create the backing data for showing labels.. | ||
| 1097 | + var data = []; | ||
| 1098 | + link.each(function (d) { | ||
| 1099 | + if (d.label) { | ||
| 1100 | + data.push({ | ||
| 1101 | + id: 'lab-' + d.key, | ||
| 1102 | + key: d.key, | ||
| 1103 | + label: d.label, | ||
| 1104 | + ldata: d | ||
| 1105 | + }); | ||
| 1106 | + } | ||
| 1107 | + }); | ||
| 1108 | + return data; | ||
| 1109 | + } | ||
| 1110 | + | ||
| 1111 | + //function linkExisting(d) { } | ||
| 1112 | + | ||
| 1113 | + function linkEntering(d) { | ||
| 1114 | + var link = d3.select(this); | ||
| 1115 | + d.el = link; | ||
| 1116 | + restyleLinkElement(d); | ||
| 1117 | + if (d.type() === 'hostLink') { | ||
| 1118 | + sus.makeVisible(link, showHosts); | ||
| 1119 | + } | ||
| 1120 | + } | ||
| 1121 | + | ||
| 1122 | + //function linkExiting(d) { } | ||
| 1123 | + | ||
| 1124 | + var linkLabelOffset = '0.3em'; | ||
| 1125 | + | ||
| 1126 | + function applyLinkLabels(data) { | ||
| 1127 | + var entering; | ||
| 1128 | + | ||
| 1129 | + linkLabel = linkLabelG.selectAll('.linkLabel') | ||
| 1130 | + .data(data, function (d) { return d.id; }); | ||
| 1131 | + | ||
| 1132 | + // for elements already existing, we need to update the text | ||
| 1133 | + // and adjust the rectangle size to fit | ||
| 1134 | + linkLabel.each(function (d) { | ||
| 1135 | + var el = d3.select(this), | ||
| 1136 | + rect = el.select('rect'), | ||
| 1137 | + text = el.select('text'); | ||
| 1138 | + text.text(d.label); | ||
| 1139 | + rect.attr(rectAroundText(el)); | ||
| 1140 | + }); | ||
| 1141 | + | ||
| 1142 | + entering = linkLabel.enter().append('g') | ||
| 1143 | + .classed('linkLabel', true) | ||
| 1144 | + .attr('id', function (d) { return d.id; }); | ||
| 1145 | + | ||
| 1146 | + entering.each(function (d) { | ||
| 1147 | + var el = d3.select(this), | ||
| 1148 | + rect, | ||
| 1149 | + text, | ||
| 1150 | + parms = { | ||
| 1151 | + x1: d.ldata.x1, | ||
| 1152 | + y1: d.ldata.y1, | ||
| 1153 | + x2: d.ldata.x2, | ||
| 1154 | + y2: d.ldata.y2 | ||
| 1155 | + }; | ||
| 1156 | + | ||
| 1157 | + d.el = el; | ||
| 1158 | + rect = el.append('rect'); | ||
| 1159 | + text = el.append('text').text(d.label); | ||
| 1160 | + rect.attr(rectAroundText(el)); | ||
| 1161 | + text.attr('dy', linkLabelOffset); | ||
| 1162 | + | ||
| 1163 | + el.attr('transform', transformLabel(parms)); | ||
| 1164 | + }); | ||
| 1165 | + | ||
| 1166 | + // Remove any labels that are no longer required. | ||
| 1167 | + linkLabel.exit().remove(); | ||
| 1168 | + } | ||
| 1169 | + | ||
| 1170 | + function rectAroundText(el) { | ||
| 1171 | + var text = el.select('text'), | ||
| 1172 | + box = text.node().getBBox(); | ||
| 1173 | + | ||
| 1174 | + // translate the bbox so that it is centered on [x,y] | ||
| 1175 | + box.x = -box.width / 2; | ||
| 1176 | + box.y = -box.height / 2; | ||
| 1177 | + | ||
| 1178 | + // add padding | ||
| 1179 | + box.x -= 1; | ||
| 1180 | + box.width += 2; | ||
| 1181 | + return box; | ||
| 1182 | + } | ||
| 1183 | + | ||
| 1184 | + function transformLabel(p) { | ||
| 1185 | + var dx = p.x2 - p.x1, | ||
| 1186 | + dy = p.y2 - p.y1, | ||
| 1187 | + xMid = dx/2 + p.x1, | ||
| 1188 | + yMid = dy/2 + p.y1; | ||
| 1189 | + return sus.translate(xMid, yMid); | ||
| 1190 | + } | ||
| 601 | 1191 | ||
| 602 | // ========================== | 1192 | // ========================== |
| 603 | // force layout tick function | 1193 | // force layout tick function |
| ... | @@ -620,34 +1210,31 @@ | ... | @@ -620,34 +1210,31 @@ |
| 620 | 1210 | ||
| 621 | angular.module('ovTopo') | 1211 | angular.module('ovTopo') |
| 622 | .factory('TopoForceService', | 1212 | .factory('TopoForceService', |
| 623 | - ['$log', 'SvgUtilService', 'IconService', 'ThemeService', | 1213 | + ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', |
| 624 | 'TopoInstService', | 1214 | 'TopoInstService', |
| 625 | 1215 | ||
| 626 | - function (_$log_, _sus_, _is_, _ts_, _tis_) { | 1216 | + function (_$log_, _fs_, _sus_, _is_, _ts_, _tis_) { |
| 627 | $log = _$log_; | 1217 | $log = _$log_; |
| 1218 | + fs = _fs_; | ||
| 628 | sus = _sus_; | 1219 | sus = _sus_; |
| 629 | is = _is_; | 1220 | is = _is_; |
| 630 | ts = _ts_; | 1221 | ts = _ts_; |
| 631 | tis = _tis_; | 1222 | tis = _tis_; |
| 632 | 1223 | ||
| 1224 | + icfg = is.iconConfig(); | ||
| 1225 | + | ||
| 633 | // forceG is the SVG group to display the force layout in | 1226 | // forceG is the SVG group to display the force layout in |
| 634 | // xlink is the cross-link api from the main topo source file | 1227 | // xlink is the cross-link api from the main topo source file |
| 635 | // w, h are the initial dimensions of the SVG | 1228 | // w, h are the initial dimensions of the SVG |
| 636 | // opts are, well, optional :) | 1229 | // opts are, well, optional :) |
| 637 | - function initForce(forceG, _xlink_, w, h, opts) { | 1230 | + function initForce(forceG, _uplink_, w, h, opts) { |
| 638 | $log.debug('initForce().. WxH = ' + w + 'x' + h); | 1231 | $log.debug('initForce().. WxH = ' + w + 'x' + h); |
| 639 | - xlink = _xlink_; | 1232 | + uplink = _uplink_; |
| 1233 | + width = w; | ||
| 1234 | + height = h; | ||
| 640 | 1235 | ||
| 641 | settings = angular.extend({}, defaultSettings, opts); | 1236 | settings = angular.extend({}, defaultSettings, opts); |
| 642 | 1237 | ||
| 643 | - // when the projection promise is resolved, cache the projection | ||
| 644 | - xlink.projectionPromise.then( | ||
| 645 | - function (proj) { | ||
| 646 | - projection = proj; | ||
| 647 | - $log.debug('** We installed the projection: ', proj); | ||
| 648 | - } | ||
| 649 | - ); | ||
| 650 | - | ||
| 651 | linkG = forceG.append('g').attr('id', 'topo-links'); | 1238 | linkG = forceG.append('g').attr('id', 'topo-links'); |
| 652 | linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); | 1239 | linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); |
| 653 | nodeG = forceG.append('g').attr('id', 'topo-nodes'); | 1240 | nodeG = forceG.append('g').attr('id', 'topo-nodes'); |
| ... | @@ -672,7 +1259,9 @@ | ... | @@ -672,7 +1259,9 @@ |
| 672 | } | 1259 | } |
| 673 | 1260 | ||
| 674 | function resize(dim) { | 1261 | function resize(dim) { |
| 675 | - force.size([dim.width, dim.height]); | 1262 | + width = dim.width; |
| 1263 | + height = dim.height; | ||
| 1264 | + force.size([width, height]); | ||
| 676 | // Review -- do we need to nudge the layout ? | 1265 | // Review -- do we need to nudge the layout ? |
| 677 | } | 1266 | } |
| 678 | 1267 | ||
| ... | @@ -683,7 +1272,14 @@ | ... | @@ -683,7 +1272,14 @@ |
| 683 | updateDeviceColors: updateDeviceColors, | 1272 | updateDeviceColors: updateDeviceColors, |
| 684 | 1273 | ||
| 685 | addDevice: addDevice, | 1274 | addDevice: addDevice, |
| 686 | - updateDevice: updateDevice | 1275 | + updateDevice: updateDevice, |
| 1276 | + removeDevice: removeDevice, | ||
| 1277 | + addHost: addHost, | ||
| 1278 | + updateHost: updateHost, | ||
| 1279 | + removeHost: removeHost, | ||
| 1280 | + addLink: addLink, | ||
| 1281 | + updateLink: updateLink, | ||
| 1282 | + removeLink: removeLink | ||
| 687 | }; | 1283 | }; |
| 688 | }]); | 1284 | }]); |
| 689 | }()); | 1285 | }()); | ... | ... |
| ... | @@ -323,9 +323,11 @@ | ... | @@ -323,9 +323,11 @@ |
| 323 | return { | 323 | return { |
| 324 | initInst: initInst, | 324 | initInst: initInst, |
| 325 | destroyInst: destroyInst, | 325 | destroyInst: destroyInst, |
| 326 | + | ||
| 326 | addInstance: addInstance, | 327 | addInstance: addInstance, |
| 327 | updateInstance: updateInstance, | 328 | updateInstance: updateInstance, |
| 328 | removeInstance: removeInstance, | 329 | removeInstance: removeInstance, |
| 330 | + | ||
| 329 | isVisible: function () { return oiBox.isVisible(); }, | 331 | isVisible: function () { return oiBox.isVisible(); }, |
| 330 | show: function () { oiBox.show(); }, | 332 | show: function () { oiBox.show(); }, |
| 331 | hide: function () { oiBox.hide(); } | 333 | hide: function () { oiBox.hide(); } | ... | ... |
| ... | @@ -34,7 +34,7 @@ describe('factory: view/topo/topoEvent.js', function() { | ... | @@ -34,7 +34,7 @@ describe('factory: view/topo/topoEvent.js', function() { |
| 34 | 34 | ||
| 35 | it('should define api functions', function () { | 35 | it('should define api functions', function () { |
| 36 | expect(fs.areFunctions(tes, [ | 36 | expect(fs.areFunctions(tes, [ |
| 37 | - 'bindDispatcher', 'openSock', 'closeSock' | 37 | + 'openSock', 'closeSock', 'sendEvent' |
| 38 | ])).toBeTruthy(); | 38 | ])).toBeTruthy(); |
| 39 | }); | 39 | }); |
| 40 | 40 | ... | ... |
| ... | @@ -35,7 +35,9 @@ describe('factory: view/topo/topoForce.js', function() { | ... | @@ -35,7 +35,9 @@ describe('factory: view/topo/topoForce.js', function() { |
| 35 | it('should define api functions', function () { | 35 | it('should define api functions', function () { |
| 36 | expect(fs.areFunctions(tfs, [ | 36 | expect(fs.areFunctions(tfs, [ |
| 37 | 'initForce', 'resize', 'updateDeviceColors', | 37 | 'initForce', 'resize', 'updateDeviceColors', |
| 38 | - 'addDevice', 'updateDevice' | 38 | + 'addDevice', 'updateDevice', 'removeDevice', |
| 39 | + 'addHost', 'updateHost', 'removeHost', | ||
| 40 | + 'addLink', 'updateLink', 'removeLink' | ||
| 39 | ])).toBeTruthy(); | 41 | ])).toBeTruthy(); |
| 40 | }); | 42 | }); |
| 41 | 43 | ... | ... |
-
Please register or login to post a comment