Committed by
Gerrit Code Review
Updated fn-spec to include classNames
Removed Classnames file and added code to fn.js Fixed typo dimentions to dimensions Moved Device/Link logic from Topo2D3 into the model Model now calls onChange when any property is changed via the set Method WIP - Added d3 force layout for devices and lines Change-Id: I4d1afd3cd4cecf2f719e27f4be5d1e874bd9e342
Showing
22 changed files
with
1535 additions
and
127 deletions
| ... | @@ -386,6 +386,34 @@ | ... | @@ -386,6 +386,34 @@ |
| 386 | } | 386 | } |
| 387 | 387 | ||
| 388 | 388 | ||
| 389 | + var hasOwn = {}.hasOwnProperty; | ||
| 390 | + | ||
| 391 | + function classNames () { | ||
| 392 | + var classes = []; | ||
| 393 | + | ||
| 394 | + for (var i = 0; i < arguments.length; i++) { | ||
| 395 | + var arg = arguments[i]; | ||
| 396 | + if (!arg) continue; | ||
| 397 | + | ||
| 398 | + var argType = typeof arg; | ||
| 399 | + | ||
| 400 | + if (argType === 'string' || argType === 'number') { | ||
| 401 | + classes.push(arg); | ||
| 402 | + } else if (Array.isArray(arg)) { | ||
| 403 | + classes.push(classNames.apply(null, arg)); | ||
| 404 | + } else if (argType === 'object') { | ||
| 405 | + for (var key in arg) { | ||
| 406 | + if (hasOwn.call(arg, key) && arg[key]) { | ||
| 407 | + classes.push(key); | ||
| 408 | + } | ||
| 409 | + } | ||
| 410 | + } | ||
| 411 | + } | ||
| 412 | + | ||
| 413 | + return classes.join(' '); | ||
| 414 | + } | ||
| 415 | + | ||
| 416 | + | ||
| 389 | angular.module('onosUtil') | 417 | angular.module('onosUtil') |
| 390 | .factory('FnService', | 418 | .factory('FnService', |
| 391 | ['$window', '$location', '$log', function (_$window_, $loc, _$log_) { | 419 | ['$window', '$location', '$log', function (_$window_, $loc, _$log_) { |
| ... | @@ -423,7 +451,8 @@ | ... | @@ -423,7 +451,8 @@ |
| 423 | parseBitRate: parseBitRate, | 451 | parseBitRate: parseBitRate, |
| 424 | addToTrie: addToTrie, | 452 | addToTrie: addToTrie, |
| 425 | removeFromTrie: removeFromTrie, | 453 | removeFromTrie: removeFromTrie, |
| 426 | - trieLookup: trieLookup | 454 | + trieLookup: trieLookup, |
| 455 | + classNames: classNames | ||
| 427 | }; | 456 | }; |
| 428 | }]); | 457 | }]); |
| 429 | 458 | ... | ... |
| ... | @@ -320,6 +320,7 @@ | ... | @@ -320,6 +320,7 @@ |
| 320 | // updateLinks - subfunctions | 320 | // updateLinks - subfunctions |
| 321 | 321 | ||
| 322 | function linkEntering(d) { | 322 | function linkEntering(d) { |
| 323 | + | ||
| 323 | var link = d3.select(this); | 324 | var link = d3.select(this); |
| 324 | d.el = link; | 325 | d.el = link; |
| 325 | api.restyleLinkElement(d); | 326 | api.restyleLinkElement(d); | ... | ... |
| ... | @@ -103,6 +103,7 @@ | ... | @@ -103,6 +103,7 @@ |
| 103 | // === EVENT HANDLERS | 103 | // === EVENT HANDLERS |
| 104 | 104 | ||
| 105 | function addDevice(data) { | 105 | function addDevice(data) { |
| 106 | + console.log(data); | ||
| 106 | var id = data.id, | 107 | var id = data.id, |
| 107 | d; | 108 | d; |
| 108 | 109 | ||
| ... | @@ -1044,7 +1045,7 @@ | ... | @@ -1044,7 +1045,7 @@ |
| 1044 | updateLinks(); | 1045 | updateLinks(); |
| 1045 | updateNodes(); | 1046 | updateNodes(); |
| 1046 | } | 1047 | } |
| 1047 | - | 1048 | + |
| 1048 | angular.module('ovTopo') | 1049 | angular.module('ovTopo') |
| 1049 | .factory('TopoForceService', | 1050 | .factory('TopoForceService', |
| 1050 | ['$log', '$timeout', 'FnService', 'SvgUtilService', | 1051 | ['$log', '$timeout', 'FnService', 'SvgUtilService', | ... | ... |
| ... | @@ -14,7 +14,6 @@ | ... | @@ -14,7 +14,6 @@ |
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | - | ||
| 18 | /* | 17 | /* |
| 19 | ONOS GUI -- Topology View (theme) -- CSS file | 18 | ONOS GUI -- Topology View (theme) -- CSS file |
| 20 | */ | 19 | */ |
| ... | @@ -22,8 +21,7 @@ | ... | @@ -22,8 +21,7 @@ |
| 22 | /* --- Base SVG Layer --- */ | 21 | /* --- Base SVG Layer --- */ |
| 23 | 22 | ||
| 24 | #ov-topo2 svg { | 23 | #ov-topo2 svg { |
| 25 | - /*background-color: #f4f4f4;*/ | 24 | + background-color: #f4f4f4; |
| 26 | - background-color: goldenrod; /* just for testing */ | ||
| 27 | } | 25 | } |
| 28 | 26 | ||
| 29 | /* --- "No Devices" Layer --- */ | 27 | /* --- "No Devices" Layer --- */ |
| ... | @@ -32,15 +30,355 @@ | ... | @@ -32,15 +30,355 @@ |
| 32 | fill: #db7773; | 30 | fill: #db7773; |
| 33 | } | 31 | } |
| 34 | 32 | ||
| 35 | -#ov-topo2 svg #topo2-noDevsLayer text { | 33 | +#ov-topo2 svg #topo-noDevsLayer text { |
| 36 | fill: #7e9aa8; | 34 | fill: #7e9aa8; |
| 37 | } | 35 | } |
| 38 | 36 | ||
| 39 | /* --- Topo Map --- */ | 37 | /* --- Topo Map --- */ |
| 40 | 38 | ||
| 41 | -#ov-topo2 svg #topo2-map { | 39 | +#ov-topo2 svg #topo-map { |
| 42 | stroke-width: 2px; | 40 | stroke-width: 2px; |
| 43 | stroke: #f4f4f4; | 41 | stroke: #f4f4f4; |
| 44 | fill: #e5e5e6; | 42 | fill: #e5e5e6; |
| 45 | } | 43 | } |
| 46 | 44 | ||
| 45 | +/* --- general topo-panel styling --- */ | ||
| 46 | + | ||
| 47 | +.topo-p svg { | ||
| 48 | + background: #c0242b; | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +.topo-p svg .glyph { | ||
| 52 | + fill: #ffffff; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +.topo-p hr { | ||
| 56 | + background-color: #cccccc; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +#topo-p-detail svg { | ||
| 60 | + background: none; | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +#topo-p-detail .header svg .glyph { | ||
| 64 | + fill: #c0242b; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | + | ||
| 68 | +/* --- Topo Instance Panel --- */ | ||
| 69 | + | ||
| 70 | +#topo-p-instance svg rect { | ||
| 71 | + stroke-width: 0; | ||
| 72 | + fill: #fbfbfb; | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +/* body of an instance */ | ||
| 76 | +#topo-p-instance .online svg rect { | ||
| 77 | + opacity: 1; | ||
| 78 | + fill: #fbfbfb; | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +#topo-p-instance svg .glyph { | ||
| 82 | + fill: #fff; | ||
| 83 | +} | ||
| 84 | +#topo-p-instance .online svg .glyph { | ||
| 85 | + fill: #fff; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | + | ||
| 89 | +/* offline */ | ||
| 90 | +#topo-p-instance svg .badgeIcon { | ||
| 91 | + opacity: 0.4; | ||
| 92 | + fill: #939598; | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +/* online */ | ||
| 96 | +#topo-p-instance .online svg .badgeIcon { | ||
| 97 | + opacity: 1.0; | ||
| 98 | + fill: #939598; | ||
| 99 | +} | ||
| 100 | +#topo-p-instance .online svg .badgeIcon.bird { | ||
| 101 | + fill: #ffffff; | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +#topo-p-instance svg .readyBadge { | ||
| 105 | + visibility: hidden; | ||
| 106 | +} | ||
| 107 | +#topo-p-instance .ready svg .readyBadge { | ||
| 108 | + visibility: visible; | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +#topo-p-instance svg text { | ||
| 112 | + text-anchor: left; | ||
| 113 | + opacity: 0.5; | ||
| 114 | + fill: #3c3a3a; | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +#topo-p-instance .online svg text { | ||
| 118 | + opacity: 1.0; | ||
| 119 | + fill: #3c3a3a; | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +#topo-p-instance .onosInst.mastership { | ||
| 123 | + opacity: 0.3; | ||
| 124 | +} | ||
| 125 | +#topo-p-instance .onosInst.mastership.affinity { | ||
| 126 | + opacity: 1.0; | ||
| 127 | +} | ||
| 128 | +#topo-p-instance .onosInst.mastership.affinity svg rect { | ||
| 129 | + filter: url(#blue-glow); | ||
| 130 | +} | ||
| 131 | + | ||
| 132 | +.firefox #topo-p-instance .onosInst.mastership.affinity svg rect { | ||
| 133 | + filter: url("data:image/svg+xml;utf8, <svg xmlns = \'http://www.w3.org/2000/svg\'><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" id=\"blue-glow\"><feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0 0 0 1 0 \"></feColorMatrix><feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\"></feGaussianBlur><feMerge><feMergeNode in=\"coloredBlur\"></feMergeNode><feMergeNode in=\"SourceGraphic\"></feMergeNode></feMerge></filter></svg>#blue-glow"); | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +/* --- Topo Nodes --- */ | ||
| 137 | + | ||
| 138 | +#ov-topo2 svg .suppressed { | ||
| 139 | + opacity: 0.5 !important; | ||
| 140 | +} | ||
| 141 | + | ||
| 142 | +#ov-topo2 svg .suppressedmax { | ||
| 143 | + opacity: 0.2 !important; | ||
| 144 | +} | ||
| 145 | + | ||
| 146 | +/* Device Nodes */ | ||
| 147 | + | ||
| 148 | +/* note: device without the 'online' class is offline */ | ||
| 149 | +#ov-topo2 svg .node.device rect { | ||
| 150 | + /* TODO: theme */ | ||
| 151 | + fill: #f0f0f0; | ||
| 152 | +} | ||
| 153 | +#ov-topo2 svg .node.device text { | ||
| 154 | + /*TODO: theme*/ | ||
| 155 | + fill: #bbb; | ||
| 156 | +} | ||
| 157 | +#ov-topo2 svg .node.device use { | ||
| 158 | + /*TODO: theme*/ | ||
| 159 | + fill: #777; | ||
| 160 | +} | ||
| 161 | + | ||
| 162 | + | ||
| 163 | +#ov-topo2 svg .node.device.online rect { | ||
| 164 | + fill: #ffffff; | ||
| 165 | +} | ||
| 166 | +#ov-topo2 svg .node.device.online text { | ||
| 167 | + fill: #3c3a3a; | ||
| 168 | +} | ||
| 169 | +#ov-topo2 svg .node.device.online use { | ||
| 170 | + /* NOTE: this gets overridden programatically */ | ||
| 171 | + fill: #454545; | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | + | ||
| 175 | +#ov-topo2 svg .node.device.selected rect { | ||
| 176 | + stroke-width: 2.0; | ||
| 177 | + stroke: #009fdb; | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +/* Badges */ | ||
| 181 | +/* (... works for bothand dark themes...) */ | ||
| 182 | +#ov-topo2 svg .node .badge circle { | ||
| 183 | + stroke: #aaa; | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +#ov-topo2 svg .node .badge.badgeInfo circle { | ||
| 187 | + fill: #99d; | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +#ov-topo2 svg .node .badge.badgeWarn circle { | ||
| 191 | + fill: #da2; | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +#ov-topo2 svg .node .badge.badgeError circle { | ||
| 195 | + fill: #e44; | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +#ov-topo2 svg .node .badge use { | ||
| 199 | + fill: white !important; | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +#ov-topo2 svg .node .badge.badgeInfo use { | ||
| 203 | + fill: #448; | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +#ov-topo2 svg .node .badge text { | ||
| 207 | + fill: white !important; | ||
| 208 | +} | ||
| 209 | + | ||
| 210 | +#ov-topo2 svg .node .badge.badgeInfo text { | ||
| 211 | + fill: #448; | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +/* Host Nodes */ | ||
| 215 | + | ||
| 216 | +#ov-topo2 svg .node.host { | ||
| 217 | +} | ||
| 218 | + | ||
| 219 | +#ov-topo2 svg .node.host text { | ||
| 220 | + stroke: none; | ||
| 221 | + font: 9pt sans-serif; | ||
| 222 | + fill: #846; | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +#ov-topo2 svg .node.host circle { | ||
| 226 | + stroke: #a3a596; | ||
| 227 | + fill: #e0dfd6; | ||
| 228 | +} | ||
| 229 | +#ov-topo2 svg .node.host.selected .hostIcon > circle { | ||
| 230 | + stroke-width: 2.0; | ||
| 231 | + stroke: #009fdb; | ||
| 232 | +} | ||
| 233 | + | ||
| 234 | +#ov-topo2 svg .node.host use { | ||
| 235 | + fill: #3c3a3a; | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +/* --- Topo Links --- */ | ||
| 239 | + | ||
| 240 | +#ov-topo2 svg .link { | ||
| 241 | + opacity: .9; | ||
| 242 | +} | ||
| 243 | + | ||
| 244 | +#ov-topo2 svg .link.selected, | ||
| 245 | +#ov-topo2 svg .link.enhanced { | ||
| 246 | + stroke-width: 3.5; | ||
| 247 | + stroke: #009fdb; | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +#ov-topo2 svg .link.inactive { | ||
| 251 | + opacity: .5; | ||
| 252 | + stroke-dasharray: 8 4; | ||
| 253 | +} | ||
| 254 | +/* TODO: Review for not-permitted links */ | ||
| 255 | +#ov-topo2 svg .link.not-permitted { | ||
| 256 | + stroke: rgb(255,0,0); | ||
| 257 | + stroke-width: 5.0; | ||
| 258 | + stroke-dasharray: 8 4; | ||
| 259 | +} | ||
| 260 | + | ||
| 261 | +#ov-topo2 svg .link.secondary { | ||
| 262 | + stroke-width: 3px; | ||
| 263 | + stroke: rgba(0,153,51,0.5); | ||
| 264 | +} | ||
| 265 | + | ||
| 266 | +/* Port traffic color visualization for Kbps, Mbps, and Gbps */ | ||
| 267 | + | ||
| 268 | +#ov-topo2 svg .link.secondary.port-traffic-Kbps { | ||
| 269 | + stroke: rgb(0,153,51); | ||
| 270 | + stroke-width: 5.0; | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +#ov-topo2 svg .link.secondary.port-traffic-Mbps { | ||
| 274 | + stroke: rgb(128,145,27); | ||
| 275 | + stroke-width: 6.5; | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +#ov-topo2 svg .link.secondary.port-traffic-Gbps { | ||
| 279 | + stroke: rgb(255, 137, 3); | ||
| 280 | + stroke-width: 8.0; | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +#ov-topo2 svg .link.secondary.port-traffic-Gbps-choked { | ||
| 284 | + stroke: rgb(183, 30, 21); | ||
| 285 | + stroke-width: 8.0; | ||
| 286 | +} | ||
| 287 | + | ||
| 288 | + | ||
| 289 | + | ||
| 290 | +#ov-topo2 svg .link.animated { | ||
| 291 | + stroke-dasharray: 8 5; | ||
| 292 | + animation: ants 5s infinite linear; | ||
| 293 | + /* below line could be added via Javascript, based on path, if we cared | ||
| 294 | + * enough about the direction of ant-flow | ||
| 295 | + */ | ||
| 296 | + /*animation-direction: reverse;*/ | ||
| 297 | +} | ||
| 298 | +@keyframes ants { | ||
| 299 | + from { | ||
| 300 | + stroke-dashoffset: 0; | ||
| 301 | + } | ||
| 302 | + to { | ||
| 303 | + stroke-dashoffset: 400; | ||
| 304 | + } | ||
| 305 | +} | ||
| 306 | + | ||
| 307 | +#ov-topo2 svg .link.primary { | ||
| 308 | + stroke-width: 4px; | ||
| 309 | + stroke: #ffA300; | ||
| 310 | +} | ||
| 311 | + | ||
| 312 | +#ov-topo2 svg .link.secondary.optical { | ||
| 313 | + stroke-width: 4px; | ||
| 314 | + stroke: rgba(128,64,255,0.5); | ||
| 315 | +} | ||
| 316 | + | ||
| 317 | +#ov-topo2 svg .link.primary.optical { | ||
| 318 | + stroke-width: 6px; | ||
| 319 | + stroke: #74f; | ||
| 320 | +} | ||
| 321 | + | ||
| 322 | +/* Link Labels */ | ||
| 323 | +#ov-topo2 svg .linkLabel rect { | ||
| 324 | + stroke: none; | ||
| 325 | + fill: #ffffff; | ||
| 326 | +} | ||
| 327 | + | ||
| 328 | +#ov-topo2 svg .linkLabel text { | ||
| 329 | + fill: #444; | ||
| 330 | +} | ||
| 331 | + | ||
| 332 | +/* Port Labels */ | ||
| 333 | + | ||
| 334 | +#ov-topo2 svg .portLabel rect { | ||
| 335 | + stroke: #a3a596; | ||
| 336 | + fill: #ffffff; | ||
| 337 | +} | ||
| 338 | + | ||
| 339 | +#ov-topo2 svg .portLabel text { | ||
| 340 | + fill: #444; | ||
| 341 | +} | ||
| 342 | + | ||
| 343 | +/* Number of Links Labels */ | ||
| 344 | + | ||
| 345 | + | ||
| 346 | +#ov-topo2 text.numLinkText { | ||
| 347 | + fill: #444; | ||
| 348 | +} | ||
| 349 | + | ||
| 350 | +/* ------------------------------------------------- */ | ||
| 351 | +/* Sprite Layer */ | ||
| 352 | + | ||
| 353 | +#ov-topo2 svg #topo-sprites .gold1 use { | ||
| 354 | + stroke: #fda; | ||
| 355 | + fill: none; | ||
| 356 | +} | ||
| 357 | +#ov-topo2 svg #topo-sprites .gold1 text { | ||
| 358 | + fill: #eda; | ||
| 359 | +} | ||
| 360 | + | ||
| 361 | +#ov-topo2 svg #topo-sprites .blue1 use { | ||
| 362 | + stroke: #bbd; | ||
| 363 | + fill: none; | ||
| 364 | +} | ||
| 365 | +#ov-topo2 svg #topo-sprites .blue1 text { | ||
| 366 | + fill: #cce; | ||
| 367 | +} | ||
| 368 | + | ||
| 369 | +#ov-topo2 svg #topo-sprites .gray1 use { | ||
| 370 | + stroke: #ccc; | ||
| 371 | + fill: none; | ||
| 372 | +} | ||
| 373 | +#ov-topo2 svg #topo-sprites .gray1 text { | ||
| 374 | + fill: #ddd; | ||
| 375 | +} | ||
| 376 | + | ||
| 377 | +/* fills */ | ||
| 378 | +#ov-topo2 svg #topo-sprites use.fill-gray2 { | ||
| 379 | + fill: #eee; | ||
| 380 | +} | ||
| 381 | + | ||
| 382 | +#ov-topo2 svg #topo-sprites use.fill-blue2 { | ||
| 383 | + fill: #bce; | ||
| 384 | +} | ... | ... |
| 1 | <!-- Topology View partial HTML --> | 1 | <!-- Topology View partial HTML --> |
| 2 | <div id="ov-topo2"> | 2 | <div id="ov-topo2"> |
| 3 | - <div id="topo2tmp"> | 3 | + |
| 4 | + <!-- <div id="topo2tmp"> | ||
| 4 | <div class="parentRegion"> | 5 | <div class="parentRegion"> |
| 5 | Parent Region: <span> - </span> | 6 | Parent Region: <span> - </span> |
| 6 | </div> | 7 | </div> |
| ... | @@ -27,7 +28,7 @@ | ... | @@ -27,7 +28,7 @@ |
| 27 | <h4>Peers</h4> | 28 | <h4>Peers</h4> |
| 28 | <div></div> | 29 | <div></div> |
| 29 | </div> | 30 | </div> |
| 30 | - </div> | 31 | + </div> --> |
| 31 | 32 | ||
| 32 | <!-- Below here is good; Above here is temporary, for debugging --> | 33 | <!-- Below here is good; Above here is temporary, for debugging --> |
| 33 | 34 | ... | ... |
| ... | @@ -48,6 +48,7 @@ | ... | @@ -48,6 +48,7 @@ |
| 48 | // callback invoked when the SVG view has been resized.. | 48 | // callback invoked when the SVG view has been resized.. |
| 49 | function svgResized(s) { | 49 | function svgResized(s) { |
| 50 | $log.debug('topo2 view resized', s); | 50 | $log.debug('topo2 view resized', s); |
| 51 | + t2fs.newDim([s.width, s.height]); | ||
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | function setUpKeys(overlayKeys) { | 54 | function setUpKeys(overlayKeys) { |
| ... | @@ -68,7 +69,7 @@ | ... | @@ -68,7 +69,7 @@ |
| 68 | ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc}); | 69 | ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc}); |
| 69 | 70 | ||
| 70 | // keep the map lines constant width while zooming | 71 | // keep the map lines constant width while zooming |
| 71 | - mapG.style('stroke-width', (2.0 / sc) + 'px'); | 72 | +// mapG.style('stroke-width', (2.0 / sc) + 'px'); |
| 72 | } | 73 | } |
| 73 | 74 | ||
| 74 | function setUpZoom() { | 75 | function setUpZoom() { | ... | ... |
| ... | @@ -55,8 +55,6 @@ | ... | @@ -55,8 +55,6 @@ |
| 55 | _this._byId[d.id] = model; | 55 | _this._byId[d.id] = model; |
| 56 | }); | 56 | }); |
| 57 | } | 57 | } |
| 58 | - | ||
| 59 | -// this.sort(); | ||
| 60 | }, | 58 | }, |
| 61 | get: function (id) { | 59 | get: function (id) { |
| 62 | if (!id) { | 60 | if (!id) { |
| ... | @@ -77,7 +75,10 @@ | ... | @@ -77,7 +75,10 @@ |
| 77 | _reset: function () { | 75 | _reset: function () { |
| 78 | this._byId = []; | 76 | this._byId = []; |
| 79 | this.models = []; | 77 | this.models = []; |
| 80 | - } | 78 | + }, |
| 79 | + toJSON: function(options) { | ||
| 80 | + return this.models.map(function(model) { return model.toJSON(options); }); | ||
| 81 | + }, | ||
| 81 | }; | 82 | }; |
| 82 | 83 | ||
| 83 | Collection.extend = function (protoProps, staticProps) { | 84 | Collection.extend = function (protoProps, staticProps) { | ... | ... |
| 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 Layout Module. | ||
| 19 | +Module that contains the d3.force.layout logic | ||
| 20 | +*/ | ||
| 21 | + | ||
| 22 | +(function () { | ||
| 23 | + 'use strict'; | ||
| 24 | + | ||
| 25 | + var sus, is, ts; | ||
| 26 | + | ||
| 27 | + // internal state | ||
| 28 | + var deviceLabelIndex = 0, | ||
| 29 | + hostLabelIndex = 0; | ||
| 30 | + | ||
| 31 | + // configuration | ||
| 32 | + var devIconDim = 36, | ||
| 33 | + labelPad = 4, | ||
| 34 | + hostRadius = 14, | ||
| 35 | + badgeConfig = { | ||
| 36 | + radius: 12, | ||
| 37 | + yoff: 5, | ||
| 38 | + gdelta: 10 | ||
| 39 | + }, | ||
| 40 | + halfDevIcon = devIconDim / 2, | ||
| 41 | + devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon }, | ||
| 42 | + hostBadgeOff = { dx: -hostRadius, dy: -hostRadius }, | ||
| 43 | + status = { | ||
| 44 | + i: 'badgeInfo', | ||
| 45 | + w: 'badgeWarn', | ||
| 46 | + e: 'badgeError' | ||
| 47 | + }; | ||
| 48 | + | ||
| 49 | + // note: these are the device icon colors without affinity (no master) | ||
| 50 | + var dColTheme = { | ||
| 51 | + light: { | ||
| 52 | + online: '#444444', | ||
| 53 | + offline: '#cccccc' | ||
| 54 | + }, | ||
| 55 | + dark: { | ||
| 56 | + // TODO: theme | ||
| 57 | + online: '#444444', | ||
| 58 | + offline: '#cccccc' | ||
| 59 | + } | ||
| 60 | + }; | ||
| 61 | + | ||
| 62 | + function init() {} | ||
| 63 | + | ||
| 64 | + function renderBadge(node, bdg, boff) { | ||
| 65 | + var bsel, | ||
| 66 | + bcr = badgeConfig.radius, | ||
| 67 | + bcgd = badgeConfig.gdelta; | ||
| 68 | + | ||
| 69 | + node.select('g.badge').remove(); | ||
| 70 | + | ||
| 71 | + bsel = node.append('g') | ||
| 72 | + .classed('badge', true) | ||
| 73 | + .classed(badgeStatus(bdg), true) | ||
| 74 | + .attr('transform', sus.translate(boff.dx, boff.dy)); | ||
| 75 | + | ||
| 76 | + bsel.append('circle') | ||
| 77 | + .attr('r', bcr); | ||
| 78 | + | ||
| 79 | + if (bdg.txt) { | ||
| 80 | + bsel.append('text') | ||
| 81 | + .attr('dy', badgeConfig.yoff) | ||
| 82 | + .attr('text-anchor', 'middle') | ||
| 83 | + .text(bdg.txt); | ||
| 84 | + } else if (bdg.gid) { | ||
| 85 | + bsel.append('use') | ||
| 86 | + .attr({ | ||
| 87 | + width: bcgd * 2, | ||
| 88 | + height: bcgd * 2, | ||
| 89 | + transform: sus.translate(-bcgd, -bcgd), | ||
| 90 | + 'xlink:href': '#' + bdg.gid | ||
| 91 | + }); | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + // TODO: Move to Device Model when working on the Exit Devices | ||
| 96 | + function updateDeviceRendering(d) { | ||
| 97 | + var node = d.el, | ||
| 98 | + bdg = d.badge, | ||
| 99 | + label = trimLabel(deviceLabel(d)), | ||
| 100 | + labelWidth; | ||
| 101 | + | ||
| 102 | + node.select('text').text(label); | ||
| 103 | + labelWidth = label ? computeLabelWidth(node) : 0; | ||
| 104 | + | ||
| 105 | + node.select('rect') | ||
| 106 | + .transition() | ||
| 107 | + .attr(iconBox(devIconDim, labelWidth)); | ||
| 108 | + | ||
| 109 | + if (bdg) { | ||
| 110 | + renderBadge(node, bdg, devBadgeOff); | ||
| 111 | + } | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + function deviceEnter(device) { | ||
| 115 | + device.onEnter(this, device); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + function hostLabel(d) { | ||
| 119 | + return d.get('id'); | ||
| 120 | + | ||
| 121 | + // var idx = (hostLabelIndex < d.get('labels').length) ? hostLabelIndex : 0; | ||
| 122 | + // return d.labels[idx]; | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + function hostEnter(d) { | ||
| 126 | + var node = d3.select(this), | ||
| 127 | + gid = d.get('type') || 'unknown', | ||
| 128 | + textDy = hostRadius + 10; | ||
| 129 | + | ||
| 130 | + d.el = node; | ||
| 131 | + // sus.visible(node, api.showHosts()); | ||
| 132 | + | ||
| 133 | + is.addHostIcon(node, hostRadius, gid); | ||
| 134 | + | ||
| 135 | + node.append('text') | ||
| 136 | + .text(hostLabel) | ||
| 137 | + .attr('dy', textDy) | ||
| 138 | + .attr('text-anchor', 'middle'); | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + function linkEntering(link) { | ||
| 142 | + link.onEnter(this); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + angular.module('ovTopo2') | ||
| 146 | + .factory('Topo2D3Service', | ||
| 147 | + ['SvgUtilService', 'IconService', 'ThemeService', | ||
| 148 | + | ||
| 149 | + function (_sus_, _is_, _ts_) { | ||
| 150 | + sus = _sus_; | ||
| 151 | + is = _is_; | ||
| 152 | + ts = _ts_; | ||
| 153 | + | ||
| 154 | + return { | ||
| 155 | + init: init, | ||
| 156 | + deviceEnter: deviceEnter, | ||
| 157 | + hostEnter: hostEnter, | ||
| 158 | + linkEntering: linkEntering | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + ] | ||
| 162 | +); | ||
| 163 | +})(); |
| ... | @@ -22,16 +22,37 @@ | ... | @@ -22,16 +22,37 @@ |
| 22 | (function () { | 22 | (function () { |
| 23 | 'use strict'; | 23 | 'use strict'; |
| 24 | 24 | ||
| 25 | - var Collection, Model; | 25 | + var Collection, Model, is, sus, ts, t2vs; |
| 26 | + | ||
| 27 | + var remappedDeviceTypes = { | ||
| 28 | + virtual: 'cord' | ||
| 29 | + }; | ||
| 30 | + | ||
| 31 | + // configuration | ||
| 32 | + var devIconDim = 36, | ||
| 33 | + labelPad = 10, | ||
| 34 | + hostRadius = 14, | ||
| 35 | + badgeConfig = { | ||
| 36 | + radius: 12, | ||
| 37 | + yoff: 5, | ||
| 38 | + gdelta: 10 | ||
| 39 | + }, | ||
| 40 | + halfDevIcon = devIconDim / 2, | ||
| 41 | + devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon }, | ||
| 42 | + hostBadgeOff = { dx: -hostRadius, dy: -hostRadius }, | ||
| 43 | + status = { | ||
| 44 | + i: 'badgeInfo', | ||
| 45 | + w: 'badgeWarn', | ||
| 46 | + e: 'badgeError' | ||
| 47 | + }, | ||
| 48 | + deviceLabelIndex = 0; | ||
| 26 | 49 | ||
| 27 | function createDeviceCollection(data, region) { | 50 | function createDeviceCollection(data, region) { |
| 28 | 51 | ||
| 29 | var DeviceCollection = Collection.extend({ | 52 | var DeviceCollection = Collection.extend({ |
| 30 | model: Model, | 53 | model: Model, |
| 31 | - get: function () {}, | ||
| 32 | comparator: function(a, b) { | 54 | comparator: function(a, b) { |
| 33 | - | 55 | + var order = region.get('layerOrder'); |
| 34 | - var order = region.layerOrder; | ||
| 35 | return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer')); | 56 | return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer')); |
| 36 | } | 57 | } |
| 37 | }); | 58 | }); |
| ... | @@ -49,14 +70,106 @@ | ... | @@ -49,14 +70,106 @@ |
| 49 | return deviceCollection; | 70 | return deviceCollection; |
| 50 | } | 71 | } |
| 51 | 72 | ||
| 73 | + function mapDeviceTypeToGlyph(type) { | ||
| 74 | + return remappedDeviceTypes[type] || type || 'unknown'; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + function deviceLabel(d) { | ||
| 78 | + //TODO: Device Json is missing labels array | ||
| 79 | + return ""; | ||
| 80 | + var labels = this.get('labels'), | ||
| 81 | + idx = (deviceLabelIndex < labels.length) ? deviceLabelIndex : 0; | ||
| 82 | + return labels[idx]; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + function trimLabel(label) { | ||
| 86 | + return (label && label.trim()) || ''; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + function computeLabelWidth() { | ||
| 90 | + var text = this.select('text'), | ||
| 91 | + box = text.node().getBBox(); | ||
| 92 | + return box.width + labelPad * 2; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + function iconBox(dim, labelWidth) { | ||
| 96 | + return { | ||
| 97 | + x: -dim / 2, | ||
| 98 | + y: -dim / 2, | ||
| 99 | + width: dim + labelWidth, | ||
| 100 | + height: dim | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + function deviceGlyphColor(d) { | ||
| 105 | + | ||
| 106 | + var o = this.node.online, | ||
| 107 | + id = "127.0.0.1", // TODO: This should be from node.master | ||
| 108 | + otag = o ? 'online' : 'offline'; | ||
| 109 | + return o ? sus.cat7().getColor(id, 0, ts.theme()) | ||
| 110 | + : dColTheme[ts.theme()][otag]; | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + function setDeviceColor() { | ||
| 114 | + this.el.select('use') | ||
| 115 | + .style('fill', this.deviceGlyphColor()); | ||
| 116 | + } | ||
| 117 | + | ||
| 52 | angular.module('ovTopo2') | 118 | angular.module('ovTopo2') |
| 53 | .factory('Topo2DeviceService', | 119 | .factory('Topo2DeviceService', |
| 54 | - ['Topo2Collection', 'Topo2Model', | 120 | + ['Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService', |
| 121 | + 'ThemeService', 'Topo2ViewService', | ||
| 55 | 122 | ||
| 56 | - function (_Collection_, _Model_) { | 123 | + function (_Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) { |
| 57 | 124 | ||
| 125 | + t2vs = _t2vs_; | ||
| 126 | + is = _is_; | ||
| 127 | + sus = _sus_; | ||
| 128 | + ts = _ts_; | ||
| 58 | Collection = _Collection_; | 129 | Collection = _Collection_; |
| 59 | - Model = _Model_.extend({}); | 130 | + |
| 131 | + Model = _NodeModel_.extend({ | ||
| 132 | + initialize: function () { | ||
| 133 | + this.set('weight', 0); | ||
| 134 | + this.constructor.__super__.initialize.apply(this, arguments); | ||
| 135 | + }, | ||
| 136 | + nodeType: 'device', | ||
| 137 | + deviceLabel: deviceLabel, | ||
| 138 | + deviceGlyphColor: deviceGlyphColor, | ||
| 139 | + mapDeviceTypeToGlyph: mapDeviceTypeToGlyph, | ||
| 140 | + trimLabel: trimLabel, | ||
| 141 | + setDeviceColor: setDeviceColor, | ||
| 142 | + onEnter: function (el) { | ||
| 143 | + | ||
| 144 | + var node = d3.select(el), | ||
| 145 | + glyphId = mapDeviceTypeToGlyph(this.get('type')), | ||
| 146 | + label = trimLabel(this.deviceLabel()), | ||
| 147 | + rect, text, glyph, labelWidth; | ||
| 148 | + | ||
| 149 | + this.el = node; | ||
| 150 | + | ||
| 151 | + rect = node.append('rect'); | ||
| 152 | + | ||
| 153 | + text = node.append('text').text(label) | ||
| 154 | + .attr('text-anchor', 'left') | ||
| 155 | + .attr('y', '0.3em') | ||
| 156 | + .attr('x', halfDevIcon + labelPad); | ||
| 157 | + | ||
| 158 | + glyph = is.addDeviceIcon(node, glyphId, devIconDim); | ||
| 159 | + | ||
| 160 | + labelWidth = label ? computeLabelWidth(node) : 0; | ||
| 161 | + | ||
| 162 | + rect.attr(iconBox(devIconDim, labelWidth)); | ||
| 163 | + glyph.attr(iconBox(devIconDim, 0)); | ||
| 164 | + | ||
| 165 | + node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon)); | ||
| 166 | + this.render(); | ||
| 167 | + }, | ||
| 168 | + onExit: function () {}, | ||
| 169 | + render: function () { | ||
| 170 | + this.setDeviceColor(); | ||
| 171 | + } | ||
| 172 | + }); | ||
| 60 | 173 | ||
| 61 | return { | 174 | return { |
| 62 | createDeviceCollection: createDeviceCollection | 175 | createDeviceCollection: createDeviceCollection | ... | ... |
| ... | @@ -60,62 +60,17 @@ | ... | @@ -60,62 +60,17 @@ |
| 60 | linkLabel, | 60 | linkLabel, |
| 61 | node; | 61 | node; |
| 62 | 62 | ||
| 63 | - var $log, wss, t2is, t2rs; | 63 | + var $log, wss, t2is, t2rs, t2ls, t2vs; |
| 64 | + var svg, forceG, uplink, dim, opts; | ||
| 64 | 65 | ||
| 65 | // ========================== Helper Functions | 66 | // ========================== Helper Functions |
| 66 | 67 | ||
| 67 | - function init(_svg_, forceG, _uplink_, _dim_, opts) { | 68 | + function init(_svg_, _forceG_, _uplink_, _dim_, _opts_) { |
| 68 | - | 69 | + svg = _svg_; |
| 69 | - $log.debug('Initialize topo force layout'); | 70 | + forceG = _forceG_; |
| 70 | - | 71 | + uplink = _uplink_; |
| 71 | - nodeG = forceG.append('g').attr('id', 'topo-nodes'); | 72 | + dim = _dim_; |
| 72 | - node = nodeG.selectAll('.node'); | 73 | + opts = _opts_ |
| 73 | - | ||
| 74 | - linkG = forceG.append('g').attr('id', 'topo-links'); | ||
| 75 | - linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); | ||
| 76 | - numLinkLblsG = forceG.append('g').attr('id', 'topo-numLinkLabels'); | ||
| 77 | - nodeG = forceG.append('g').attr('id', 'topo-nodes'); | ||
| 78 | - portLabelG = forceG.append('g').attr('id', 'topo-portLabels'); | ||
| 79 | - | ||
| 80 | - link = linkG.selectAll('.link'); | ||
| 81 | - linkLabel = linkLabelG.selectAll('.linkLabel'); | ||
| 82 | - node = nodeG.selectAll('.node'); | ||
| 83 | - | ||
| 84 | - var width = 640, | ||
| 85 | - height = 480; | ||
| 86 | - | ||
| 87 | - var nodes = [ | ||
| 88 | - { x: width/3, y: height/2 }, | ||
| 89 | - { x: 2*width/3, y: height/2 } | ||
| 90 | - ]; | ||
| 91 | - | ||
| 92 | - var links = [ | ||
| 93 | - { source: 0, target: 1 } | ||
| 94 | - ]; | ||
| 95 | - | ||
| 96 | - var svg = d3.select('body').append('svg') | ||
| 97 | - .attr('width', width) | ||
| 98 | - .attr('height', height); | ||
| 99 | - | ||
| 100 | - var force = d3.layout.force() | ||
| 101 | - .size([width, height]) | ||
| 102 | - .nodes(nodes) | ||
| 103 | - .links(links); | ||
| 104 | - | ||
| 105 | - force.linkDistance(width/2); | ||
| 106 | - | ||
| 107 | - | ||
| 108 | - var link = svg.selectAll('.link') | ||
| 109 | - .data(links) | ||
| 110 | - .enter().append('line') | ||
| 111 | - .attr('class', 'link'); | ||
| 112 | - | ||
| 113 | - var node = svg.selectAll('.node') | ||
| 114 | - .data(nodes) | ||
| 115 | - .enter().append('circle') | ||
| 116 | - .attr('class', 'node'); | ||
| 117 | - | ||
| 118 | - force.start(); | ||
| 119 | } | 74 | } |
| 120 | 75 | ||
| 121 | function destroy() { | 76 | function destroy() { |
| ... | @@ -206,6 +161,9 @@ | ... | @@ -206,6 +161,9 @@ |
| 206 | $log.debug('>> topo2CurrentRegion event:', data); | 161 | $log.debug('>> topo2CurrentRegion event:', data); |
| 207 | doTmpCurrentRegion(data); | 162 | doTmpCurrentRegion(data); |
| 208 | t2rs.addRegion(data); | 163 | t2rs.addRegion(data); |
| 164 | + t2ls.init(svg, forceG, uplink, dim, opts); | ||
| 165 | + t2ls.update(); | ||
| 166 | + t2ls.start(); | ||
| 209 | } | 167 | } |
| 210 | 168 | ||
| 211 | function topo2PeerRegions(data) { | 169 | function topo2PeerRegions(data) { |
| ... | @@ -257,20 +215,37 @@ | ... | @@ -257,20 +215,37 @@ |
| 257 | // link.classed(cls, b); | 215 | // link.classed(cls, b); |
| 258 | } | 216 | } |
| 259 | 217 | ||
| 218 | + function newDim(_dim_) { | ||
| 219 | + dim = _dim_; | ||
| 220 | + t2vs.newDim(dim); | ||
| 221 | + // force.size(dim); | ||
| 222 | + // tms.newDim(dim); | ||
| 223 | + t2ls.setDimensions(); | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + function getDim() { | ||
| 227 | + return dim; | ||
| 228 | + } | ||
| 229 | + | ||
| 260 | // ========================== Main Service Definition | 230 | // ========================== Main Service Definition |
| 261 | 231 | ||
| 262 | angular.module('ovTopo2') | 232 | angular.module('ovTopo2') |
| 263 | .factory('Topo2ForceService', | 233 | .factory('Topo2ForceService', |
| 264 | ['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService', | 234 | ['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService', |
| 265 | - function (_$log_, _wss_, _t2is_, _t2rs_) { | 235 | + 'Topo2LayoutService', 'Topo2ViewService', |
| 236 | + function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_, _t2vs_) { | ||
| 237 | + | ||
| 266 | $log = _$log_; | 238 | $log = _$log_; |
| 267 | wss = _wss_; | 239 | wss = _wss_; |
| 268 | t2is = _t2is_; | 240 | t2is = _t2is_; |
| 269 | t2rs = _t2rs_; | 241 | t2rs = _t2rs_; |
| 242 | + t2ls = _t2ls_; | ||
| 243 | + t2vs = _t2vs_; | ||
| 270 | 244 | ||
| 271 | return { | 245 | return { |
| 272 | 246 | ||
| 273 | init: init, | 247 | init: init, |
| 248 | + newDim: newDim, | ||
| 274 | 249 | ||
| 275 | destroy: destroy, | 250 | destroy: destroy, |
| 276 | topo2AllInstances: allInstances, | 251 | topo2AllInstances: allInstances, | ... | ... |
| ... | @@ -22,7 +22,7 @@ | ... | @@ -22,7 +22,7 @@ |
| 22 | (function () { | 22 | (function () { |
| 23 | 'use strict'; | 23 | 'use strict'; |
| 24 | 24 | ||
| 25 | - var Collection, Model; | 25 | + var Collection, Model, t2vs; |
| 26 | 26 | ||
| 27 | function createHostCollection(data, region) { | 27 | function createHostCollection(data, region) { |
| 28 | 28 | ||
| ... | @@ -42,17 +42,21 @@ | ... | @@ -42,17 +42,21 @@ |
| 42 | 42 | ||
| 43 | angular.module('ovTopo2') | 43 | angular.module('ovTopo2') |
| 44 | .factory('Topo2HostService', | 44 | .factory('Topo2HostService', |
| 45 | - ['Topo2Collection', 'Topo2Model', | 45 | + [ |
| 46 | + 'Topo2Collection', 'Topo2NodeModel', 'Topo2ViewService', | ||
| 47 | + function (_Collection_, _NodeModel_, classnames, _t2vs_) { | ||
| 46 | 48 | ||
| 47 | - function (_Collection_, _Model_) { | 49 | + t2vs = _t2vs_; |
| 50 | + Collection = _Collection_; | ||
| 48 | 51 | ||
| 49 | - Collection = _Collection_; | 52 | + Model = _NodeModel_.extend({ |
| 50 | - Model = _Model_.extend(); | 53 | + nodeType: 'host' |
| 54 | + }); | ||
| 51 | 55 | ||
| 52 | - return { | 56 | + return { |
| 53 | - createHostCollection: createHostCollection | 57 | + createHostCollection: createHostCollection |
| 54 | - }; | 58 | + }; |
| 55 | - } | 59 | + } |
| 56 | - ]); | 60 | + ]); |
| 57 | 61 | ||
| 58 | })(); | 62 | })(); | ... | ... |
| 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 Layout Module. | ||
| 19 | + Module that contains the d3.force.layout logic | ||
| 20 | + */ | ||
| 21 | + | ||
| 22 | +(function () { | ||
| 23 | + 'use strict'; | ||
| 24 | + | ||
| 25 | + var $log, sus, t2rs, t2d3, t2vs; | ||
| 26 | + | ||
| 27 | + var linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG; | ||
| 28 | + var link, linkLabel, node; | ||
| 29 | + | ||
| 30 | + var nodes, links; | ||
| 31 | + | ||
| 32 | + var force; | ||
| 33 | + | ||
| 34 | + // default settings for force layout | ||
| 35 | + var defaultSettings = { | ||
| 36 | + gravity: 0.4, | ||
| 37 | + friction: 0.7, | ||
| 38 | + charge: { | ||
| 39 | + // note: key is node.class | ||
| 40 | + device: -8000, | ||
| 41 | + host: -5000, | ||
| 42 | + _def_: -12000 | ||
| 43 | + }, | ||
| 44 | + linkDistance: { | ||
| 45 | + // note: key is link.type | ||
| 46 | + direct: 100, | ||
| 47 | + optical: 120, | ||
| 48 | + hostLink: 3, | ||
| 49 | + _def_: 50 | ||
| 50 | + }, | ||
| 51 | + linkStrength: { | ||
| 52 | + // note: key is link.type | ||
| 53 | + // range: {0.0 ... 1.0} | ||
| 54 | + //direct: 1.0, | ||
| 55 | + //optical: 1.0, | ||
| 56 | + //hostLink: 1.0, | ||
| 57 | + _def_: 1.0 | ||
| 58 | + } | ||
| 59 | + }; | ||
| 60 | + | ||
| 61 | + // configuration | ||
| 62 | + var linkConfig = { | ||
| 63 | + light: { | ||
| 64 | + baseColor: '#939598', | ||
| 65 | + inColor: '#66f', | ||
| 66 | + outColor: '#f00' | ||
| 67 | + }, | ||
| 68 | + dark: { | ||
| 69 | + // TODO : theme | ||
| 70 | + baseColor: '#939598', | ||
| 71 | + inColor: '#66f', | ||
| 72 | + outColor: '#f00' | ||
| 73 | + }, | ||
| 74 | + inWidth: 12, | ||
| 75 | + outWidth: 10 | ||
| 76 | + }; | ||
| 77 | + | ||
| 78 | + // internal state | ||
| 79 | + var settings, // merged default settings and options | ||
| 80 | + force, // force layout object | ||
| 81 | + drag, // drag behavior handler | ||
| 82 | + network = { | ||
| 83 | + nodes: [], | ||
| 84 | + links: [], | ||
| 85 | + linksByDevice: {}, | ||
| 86 | + lookup: {}, | ||
| 87 | + revLinkToKey: {} | ||
| 88 | + }, | ||
| 89 | + lu, // shorthand for lookup | ||
| 90 | + rlk, // shorthand for revLinktoKey | ||
| 91 | + showHosts = false, // whether hosts are displayed | ||
| 92 | + showOffline = true, // whether offline devices are displayed | ||
| 93 | + nodeLock = false, // whether nodes can be dragged or not (locked) | ||
| 94 | + fTimer, // timer for delayed force layout | ||
| 95 | + fNodesTimer, // timer for delayed nodes update | ||
| 96 | + fLinksTimer, // timer for delayed links update | ||
| 97 | + dim, // the dimensions of the force layout [w,h] | ||
| 98 | + linkNums = []; // array of link number labels | ||
| 99 | + | ||
| 100 | + var tickStuff = { | ||
| 101 | + nodeAttr: { | ||
| 102 | + transform: function (d) { | ||
| 103 | + var dx = isNaN(d.x) ? 0 : d.x, | ||
| 104 | + dy = isNaN(d.y) ? 0 : d.y; | ||
| 105 | + return sus.translate(dx, dy); | ||
| 106 | + } | ||
| 107 | + }, | ||
| 108 | + linkAttr: { | ||
| 109 | + x1: function (d) { return d.get('position').x1; }, | ||
| 110 | + y1: function (d) { return d.get('position').y1; }, | ||
| 111 | + x2: function (d) { return d.get('position').x2; }, | ||
| 112 | + y2: function (d) { return d.get('position').y2; } | ||
| 113 | + }, | ||
| 114 | + linkLabelAttr: { | ||
| 115 | + transform: function (d) { | ||
| 116 | + var lnk = tms.findLinkById(d.get('key')); | ||
| 117 | + if (lnk) { | ||
| 118 | + return t2d3.transformLabel(lnk.get('position')); | ||
| 119 | + } | ||
| 120 | + } | ||
| 121 | + } | ||
| 122 | + }; | ||
| 123 | + | ||
| 124 | + function init(_svg_, forceG, _uplink_, _dim_, opts) { | ||
| 125 | + | ||
| 126 | + $log.debug("Initialising Topology Layout"); | ||
| 127 | + | ||
| 128 | + settings = angular.extend({}, defaultSettings, opts); | ||
| 129 | + | ||
| 130 | + linkG = forceG.append('g').attr('id', 'topo-links'); | ||
| 131 | + linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); | ||
| 132 | + numLinkLabelsG = forceG.append('g').attr('id', 'topo-numLinkLabels'); | ||
| 133 | + nodeG = forceG.append('g').attr('id', 'topo-nodes'); | ||
| 134 | + portLabelG = forceG.append('g').attr('id', 'topo-portLabels'); | ||
| 135 | + | ||
| 136 | + link = linkG.selectAll('.link'); | ||
| 137 | + linkLabel = linkLabelG.selectAll('.linkLabel'); | ||
| 138 | + node = nodeG.selectAll('.node'); | ||
| 139 | + | ||
| 140 | + force = d3.layout.force() | ||
| 141 | + .size(t2vs.getDimensions()) | ||
| 142 | + .nodes(t2rs.regionNodes()) | ||
| 143 | + .links(t2rs.regionLinks()) | ||
| 144 | + .gravity(settings.gravity) | ||
| 145 | + .friction(settings.friction) | ||
| 146 | + .charge(settings.charge._def_) | ||
| 147 | + .linkDistance(settings.linkDistance._def_) | ||
| 148 | + .linkStrength(settings.linkStrength._def_) | ||
| 149 | + .on('tick', tick); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + function tick() { | ||
| 153 | + // guard against null (which can happen when our view pages out)... | ||
| 154 | + if (node && node.size()) { | ||
| 155 | + node.attr(tickStuff.nodeAttr); | ||
| 156 | + } | ||
| 157 | + if (link && link.size()) { | ||
| 158 | + link.call(calcPosition) | ||
| 159 | + .attr(tickStuff.linkAttr); | ||
| 160 | + // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG); | ||
| 161 | + } | ||
| 162 | + if (linkLabel && linkLabel.size()) { | ||
| 163 | + linkLabel.attr(tickStuff.linkLabelAttr); | ||
| 164 | + } | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + function update() { | ||
| 168 | + _updateNodes(); | ||
| 169 | + _updateLinks(); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + function _updateNodes() { | ||
| 173 | + | ||
| 174 | + var regionNodes = t2rs.regionNodes(); | ||
| 175 | + | ||
| 176 | + // select all the nodes in the layout: | ||
| 177 | + node = nodeG.selectAll('.node') | ||
| 178 | + .data(regionNodes, function (d) { return d.get('id'); }); | ||
| 179 | + | ||
| 180 | + var entering = node.enter() | ||
| 181 | + .append('g') | ||
| 182 | + .attr({ | ||
| 183 | + id: function (d) { return sus.safeId(d.get('id')); }, | ||
| 184 | + class: function (d) { return d.svgClassName() }, | ||
| 185 | + transform: function (d) { | ||
| 186 | + // Need to guard against NaN here ?? | ||
| 187 | + return sus.translate(d.node.x, d.node.y); | ||
| 188 | + }, | ||
| 189 | + opacity: 0 | ||
| 190 | + }) | ||
| 191 | + // .on('mouseover', tss.nodeMouseOver) | ||
| 192 | + // .on('mouseout', tss.nodeMouseOut) | ||
| 193 | + .transition() | ||
| 194 | + .attr('opacity', 1); | ||
| 195 | + | ||
| 196 | + entering.filter('.device').each(t2d3.deviceEnter); | ||
| 197 | + entering.filter('.host').each(t2d3.hostEnter); | ||
| 198 | + | ||
| 199 | + // operate on both existing and new nodes: | ||
| 200 | + // node.filter('.device').each(function (device) { | ||
| 201 | + // t2d3.updateDeviceColors(device); | ||
| 202 | + // }); | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + function _updateLinks() { | ||
| 206 | + | ||
| 207 | + // var th = ts.theme(); | ||
| 208 | + var regionLinks = t2rs.regionLinks(); | ||
| 209 | + | ||
| 210 | + link = linkG.selectAll('.link') | ||
| 211 | + .data(regionLinks, function (d) { return d.get('key'); }); | ||
| 212 | + | ||
| 213 | + // operate on existing links: | ||
| 214 | + link.each(function (d) { | ||
| 215 | + // this is supposed to be an existing link, but we have observed | ||
| 216 | + // occasions (where links are deleted and added rapidly?) where | ||
| 217 | + // the DOM element has not been defined. So protect against that... | ||
| 218 | + if (d.el) { | ||
| 219 | + restyleLinkElement(d, true); | ||
| 220 | + } | ||
| 221 | + }); | ||
| 222 | + | ||
| 223 | + // operate on entering links: | ||
| 224 | + var entering = link.enter() | ||
| 225 | + .append('line') | ||
| 226 | + .call(calcPosition) | ||
| 227 | + .attr({ | ||
| 228 | + x1: function (d) { return d.get('position').x1; }, | ||
| 229 | + y1: function (d) { return d.get('position').y1; }, | ||
| 230 | + x2: function (d) { return d.get('position').x2; }, | ||
| 231 | + y2: function (d) { return d.get('position').y2; }, | ||
| 232 | + stroke: linkConfig['light'].inColor, | ||
| 233 | + 'stroke-width': linkConfig.inWidth | ||
| 234 | + }); | ||
| 235 | + | ||
| 236 | + entering.each(t2d3.linkEntering); | ||
| 237 | + | ||
| 238 | + // operate on both existing and new links: | ||
| 239 | + //link.each(...) | ||
| 240 | + | ||
| 241 | + // add labels for how many links are in a thick line | ||
| 242 | + // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG); | ||
| 243 | + | ||
| 244 | + // apply or remove labels | ||
| 245 | + // t2d3.applyLinkLabels(); | ||
| 246 | + | ||
| 247 | + // operate on exiting links: | ||
| 248 | + link.exit() | ||
| 249 | + .attr('stroke-dasharray', '3 3') | ||
| 250 | + .attr('stroke', linkConfig['light'].outColor) | ||
| 251 | + .style('opacity', 0.5) | ||
| 252 | + .transition() | ||
| 253 | + .duration(1500) | ||
| 254 | + .attr({ | ||
| 255 | + 'stroke-dasharray': '3 12', | ||
| 256 | + 'stroke-width': linkConfig.outWidth | ||
| 257 | + }) | ||
| 258 | + .style('opacity', 0.0) | ||
| 259 | + .remove(); | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + function calcPosition() { | ||
| 263 | + var lines = this, | ||
| 264 | + linkSrcId, | ||
| 265 | + linkNums = []; | ||
| 266 | + | ||
| 267 | + lines.each(function (d) { | ||
| 268 | + if (d.get('type') === 'hostLink') { | ||
| 269 | + d.set('position', getDefaultPos(d)); | ||
| 270 | + } | ||
| 271 | + }); | ||
| 272 | + | ||
| 273 | + function normalizeLinkSrc(link) { | ||
| 274 | + // ensure source device is consistent across set of links | ||
| 275 | + // temporary measure until link modeling is refactored | ||
| 276 | + if (!linkSrcId) { | ||
| 277 | + linkSrcId = link.source.id; | ||
| 278 | + return false; | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + return link.source.id !== linkSrcId; | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + lines.each(function (d) { | ||
| 285 | + d.set('position', getDefaultPos(d)); | ||
| 286 | + }); | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + function getDefaultPos(link) { | ||
| 290 | + | ||
| 291 | + return { | ||
| 292 | + x1: link.get('source').x, | ||
| 293 | + y1: link.get('source').y, | ||
| 294 | + x2: link.get('target').x, | ||
| 295 | + y2: link.get('target').y | ||
| 296 | + }; | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + function setDimensions() { | ||
| 300 | + if (force) { | ||
| 301 | + force.size(t2vs.getDimensions()); | ||
| 302 | + } | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + | ||
| 306 | + function start() { | ||
| 307 | + force.start(); | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + angular.module('ovTopo2') | ||
| 311 | + .factory('Topo2LayoutService', | ||
| 312 | + [ | ||
| 313 | + '$log', 'SvgUtilService', 'Topo2RegionService', | ||
| 314 | + 'Topo2D3Service', 'Topo2ViewService', | ||
| 315 | + | ||
| 316 | + function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) { | ||
| 317 | + | ||
| 318 | + $log = _$log_; | ||
| 319 | + t2rs = _t2rs_; | ||
| 320 | + t2d3 = _t2d3_; | ||
| 321 | + t2vs = _t2vs_; | ||
| 322 | + sus = _sus_; | ||
| 323 | + | ||
| 324 | + return { | ||
| 325 | + init: init, | ||
| 326 | + update: update, | ||
| 327 | + start: start, | ||
| 328 | + | ||
| 329 | + setDimensions: setDimensions | ||
| 330 | + } | ||
| 331 | + } | ||
| 332 | + ] | ||
| 333 | + ); | ||
| 334 | +})(); |
| ... | @@ -22,12 +22,162 @@ | ... | @@ -22,12 +22,162 @@ |
| 22 | (function () { | 22 | (function () { |
| 23 | 'use strict'; | 23 | 'use strict'; |
| 24 | 24 | ||
| 25 | - var Collection, Model; | 25 | + var Collection, Model, region, ts; |
| 26 | 26 | ||
| 27 | - function createLinkCollection(data, region) { | 27 | + var widthRatio = 1.4, |
| 28 | + linkScale = d3.scale.linear() | ||
| 29 | + .domain([1, 12]) | ||
| 30 | + .range([widthRatio, 12 * widthRatio]) | ||
| 31 | + .clamp(true), | ||
| 32 | + allLinkTypes = 'direct indirect optical tunnel UiDeviceLink', | ||
| 33 | + allLinkSubTypes = 'inactive not-permitted'; | ||
| 34 | + | ||
| 35 | + // configuration | ||
| 36 | + var linkConfig = { | ||
| 37 | + light: { | ||
| 38 | + baseColor: '#939598', | ||
| 39 | + inColor: '#66f', | ||
| 40 | + outColor: '#f00' | ||
| 41 | + }, | ||
| 42 | + dark: { | ||
| 43 | + // TODO : theme | ||
| 44 | + baseColor: '#939598', | ||
| 45 | + inColor: '#66f', | ||
| 46 | + outColor: '#f00' | ||
| 47 | + }, | ||
| 48 | + inWidth: 12, | ||
| 49 | + outWidth: 10 | ||
| 50 | + }; | ||
| 51 | + | ||
| 52 | + var defaultLinkType = 'direct', | ||
| 53 | + nearDist = 15; | ||
| 54 | + | ||
| 55 | + function createLink() { | ||
| 56 | + | ||
| 57 | + var linkPoints = this.linkEndPoints(this.get('epA'), this.get('epB')); | ||
| 58 | + console.log(this); | ||
| 59 | + | ||
| 60 | + var attrs = angular.extend({}, linkPoints, { | ||
| 61 | + key: this.get('id'), | ||
| 62 | + class: 'link', | ||
| 63 | + weight: 1, | ||
| 64 | + srcPort: this.get('srcPort'), | ||
| 65 | + tgtPort: this.get('dstPort'), | ||
| 66 | + position: { | ||
| 67 | + x1: 0, | ||
| 68 | + y1: 0, | ||
| 69 | + x2: 0, | ||
| 70 | + y2: 0 | ||
| 71 | + } | ||
| 72 | + // functions to aggregate dual link state | ||
| 73 | +// extra: link.extra | ||
| 74 | + }); | ||
| 75 | + | ||
| 76 | + this.set(attrs); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + function linkEndPoints(srcId, dstId) { | ||
| 80 | + | ||
| 81 | + var sourceNode = this.region.get('devices').get(srcId.substring(0, srcId.length -2)); | ||
| 82 | + var targetNode = this.region.get('devices').get(dstId.substring(0, dstId.length -2)); | ||
| 83 | + | ||
| 84 | +// var srcNode = lu[srcId], | ||
| 85 | +// dstNode = lu[dstId], | ||
| 86 | +// sMiss = !srcNode ? missMsg('src', srcId) : '', | ||
| 87 | +// dMiss = !dstNode ? missMsg('dst', dstId) : ''; | ||
| 88 | +// | ||
| 89 | +// if (sMiss || dMiss) { | ||
| 90 | +// $log.error('Node(s) not on map for link:' + sMiss + dMiss); | ||
| 91 | +// //logicError('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
| 92 | +// return null; | ||
| 93 | +// } | ||
| 94 | + | ||
| 95 | + this.source = sourceNode.toJSON(); | ||
| 96 | + this.target = targetNode.toJSON(); | ||
| 97 | + | ||
| 98 | + return { | ||
| 99 | + source: sourceNode, | ||
| 100 | + target: targetNode | ||
| 101 | + }; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + function createLinkCollection(data, _region) { | ||
| 105 | + | ||
| 106 | + var LinkModel = Model.extend({ | ||
| 107 | + region: _region, | ||
| 108 | + createLink: createLink, | ||
| 109 | + linkEndPoints: linkEndPoints, | ||
| 110 | + type: function () { | ||
| 111 | + return this.get('type'); | ||
| 112 | + }, | ||
| 113 | + expected: function () { | ||
| 114 | + //TODO: original code is: (s && s.expected) && (t && t.expected); | ||
| 115 | + return true; | ||
| 116 | + }, | ||
| 117 | + online: function () { | ||
| 118 | + return true; | ||
| 119 | + return both && (s && s.online) && (t && t.online); | ||
| 120 | + }, | ||
| 121 | + linkWidth: function () { | ||
| 122 | + var s = this.get('fromSource'), | ||
| 123 | + t = this.get('fromTarget'), | ||
| 124 | + ws = (s && s.linkWidth) || 0, | ||
| 125 | + wt = (t && t.linkWidth) || 0; | ||
| 126 | + | ||
| 127 | + // console.log(s); | ||
| 128 | + // TODO: Current json is missing linkWidth | ||
| 129 | + return 1.2; | ||
| 130 | + return this.get('position').multiLink ? 5 : Math.max(ws, wt); | ||
| 131 | + }, | ||
| 132 | + | ||
| 133 | + restyleLinkElement: function (immediate) { | ||
| 134 | + // this fn's job is to look at raw links and decide what svg classes | ||
| 135 | + // need to be applied to the line element in the DOM | ||
| 136 | + var th = ts.theme(), | ||
| 137 | + el = this.el, | ||
| 138 | + type = this.get('type'), | ||
| 139 | + lw = this.linkWidth(), | ||
| 140 | + online = this.online(), | ||
| 141 | + modeCls = this.expected() ? 'inactive' : 'not-permitted', | ||
| 142 | + delay = immediate ? 0 : 1000; | ||
| 143 | + | ||
| 144 | + console.log(type); | ||
| 145 | + | ||
| 146 | + // NOTE: understand why el is sometimes undefined on addLink events... | ||
| 147 | + // Investigated: | ||
| 148 | + // el is undefined when it's a reverse link that is being added. | ||
| 149 | + // updateLinks (which sets ldata.el) isn't called before this is called. | ||
| 150 | + // Calling _updateLinks in addLinkUpdate fixes it, but there might be | ||
| 151 | + // a more efficient way to fix it. | ||
| 152 | + if (el && !el.empty()) { | ||
| 153 | + el.classed('link', true); | ||
| 154 | + el.classed(allLinkSubTypes, false); | ||
| 155 | + el.classed(modeCls, !online); | ||
| 156 | + el.classed(allLinkTypes, false); | ||
| 157 | + if (type) { | ||
| 158 | + el.classed(type, true); | ||
| 159 | + } | ||
| 160 | + el.transition() | ||
| 161 | + .duration(delay) | ||
| 162 | + .attr('stroke-width', linkScale(lw)) | ||
| 163 | + .attr('stroke', linkConfig[th].baseColor); | ||
| 164 | + } | ||
| 165 | + }, | ||
| 166 | + | ||
| 167 | + onEnter: function (el) { | ||
| 168 | + var link = d3.select(el); | ||
| 169 | + this.el = link; | ||
| 170 | + | ||
| 171 | + this.restyleLinkElement(); | ||
| 172 | + | ||
| 173 | + if (this.get('type') === 'hostLink') { | ||
| 174 | + sus.visible(link, api.showHosts()); | ||
| 175 | + } | ||
| 176 | + } | ||
| 177 | + }); | ||
| 28 | 178 | ||
| 29 | var LinkCollection = Collection.extend({ | 179 | var LinkCollection = Collection.extend({ |
| 30 | - model: Model | 180 | + model: LinkModel, |
| 31 | }); | 181 | }); |
| 32 | 182 | ||
| 33 | return new LinkCollection(data); | 183 | return new LinkCollection(data); |
| ... | @@ -35,12 +185,13 @@ | ... | @@ -35,12 +185,13 @@ |
| 35 | 185 | ||
| 36 | angular.module('ovTopo2') | 186 | angular.module('ovTopo2') |
| 37 | .factory('Topo2LinkService', | 187 | .factory('Topo2LinkService', |
| 38 | - ['Topo2Collection', 'Topo2Model', | 188 | + ['Topo2Collection', 'Topo2Model', 'ThemeService', |
| 39 | 189 | ||
| 40 | - function (_Collection_, _Model_) { | 190 | + function (_Collection_, _Model_, _ts_) { |
| 41 | 191 | ||
| 192 | + ts = _ts_; | ||
| 42 | Collection = _Collection_; | 193 | Collection = _Collection_; |
| 43 | - Model = _Model_.extend({}); | 194 | + Model = _Model_; |
| 44 | 195 | ||
| 45 | return { | 196 | return { |
| 46 | createLinkCollection: createLinkCollection | 197 | createLinkCollection: createLinkCollection | ... | ... |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2016-present Open Networking Laboratory | 2 | +* Copyright 2016-present Open Networking Laboratory |
| 3 | - * | 3 | +* |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | +* Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | - * you may not use this file except in compliance with 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 | 6 | +* You may obtain a copy of the License at |
| 7 | - * | 7 | +* |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | 8 | +* http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | - * | 9 | +* |
| 10 | - * Unless required by applicable law or agreed to in writing, software | 10 | +* Unless required by applicable law or agreed to in writing, software |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | 11 | +* distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | - * See the License for the specific language governing permissions and | 13 | +* See the License for the specific language governing permissions and |
| 14 | - * limitations under the License. | 14 | +* limitations under the License. |
| 15 | - */ | 15 | +*/ |
| 16 | 16 | ||
| 17 | /* | 17 | /* |
| 18 | - ONOS GUI -- Topology Force Module. | 18 | +ONOS GUI -- Topology Force Module. |
| 19 | - Visualization of the topology in an SVG layer, using a D3 Force Layout. | 19 | +Visualization of the topology in an SVG layer, using a D3 Force Layout. |
| 20 | - */ | 20 | +*/ |
| 21 | 21 | ||
| 22 | (function () { | 22 | (function () { |
| 23 | 'use strict'; | 23 | 'use strict'; |
| ... | @@ -28,17 +28,86 @@ | ... | @@ -28,17 +28,86 @@ |
| 28 | this.attributes = {}; | 28 | this.attributes = {}; |
| 29 | 29 | ||
| 30 | attrs = angular.extend({}, attrs); | 30 | attrs = angular.extend({}, attrs); |
| 31 | - this.set(attrs); | 31 | + this.set(attrs, { silent: true }); |
| 32 | + this.initialize.apply(this, arguments); | ||
| 32 | } | 33 | } |
| 33 | 34 | ||
| 34 | Model.prototype = { | 35 | Model.prototype = { |
| 35 | 36 | ||
| 37 | + initialize: function () {}, | ||
| 38 | + | ||
| 39 | + onChange: function (property, value, options) {}, | ||
| 40 | + | ||
| 36 | get: function (attr) { | 41 | get: function (attr) { |
| 37 | return this.attributes[attr]; | 42 | return this.attributes[attr]; |
| 38 | }, | 43 | }, |
| 39 | 44 | ||
| 40 | - set: function(data) { | 45 | + set: function(key, val, options) { |
| 41 | - angular.extend(this.attributes, data); | 46 | + |
| 47 | + if (!key) { | ||
| 48 | + return this; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + var attributes; | ||
| 52 | + if (typeof key === 'object') { | ||
| 53 | + attributes = key; | ||
| 54 | + options = val; | ||
| 55 | + } else { | ||
| 56 | + (attributes = {})[key] = val; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + options || (options = {}); | ||
| 60 | + | ||
| 61 | + var unset = options.unset, | ||
| 62 | + silent = options.silent, | ||
| 63 | + changes = [], | ||
| 64 | + changing = this._changing; | ||
| 65 | + | ||
| 66 | + this._changing = true; | ||
| 67 | + | ||
| 68 | + if (!changing) { | ||
| 69 | + | ||
| 70 | + // NOTE: angular.copy causes issues in chrome | ||
| 71 | + this._previousAttributes = Object.create(Object.getPrototypeOf(this.attributes)); | ||
| 72 | + this.changed = {}; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + var current = this.attributes, | ||
| 76 | + changed = this.changed, | ||
| 77 | + previous = this._previousAttributes; | ||
| 78 | + | ||
| 79 | + angular.forEach(attributes, function (attribute, index) { | ||
| 80 | + | ||
| 81 | + val = attribute; | ||
| 82 | + | ||
| 83 | + if (!angular.equals(current[index], val)) { | ||
| 84 | + changes.push(index); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + if (!angular.equals(previous[index], val)) { | ||
| 88 | + changed[index] = val; | ||
| 89 | + } else { | ||
| 90 | + delete changed[index]; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + unset ? delete current[index] : current[index] = val; | ||
| 94 | + }); | ||
| 95 | + | ||
| 96 | + // Trigger all relevant attribute changes. | ||
| 97 | + if (!silent) { | ||
| 98 | + if (changes.length) { | ||
| 99 | + this._pending = options; | ||
| 100 | + } | ||
| 101 | + for (var i = 0; i < changes.length; i++) { | ||
| 102 | + this.onChange(changes[i], this, current[changes[i]], options); | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + this._changing = false; | ||
| 107 | + return this; | ||
| 108 | + }, | ||
| 109 | + toJSON: function(options) { | ||
| 110 | + return angular.copy(this.attributes) | ||
| 42 | }, | 111 | }, |
| 43 | }; | 112 | }; |
| 44 | 113 | ||
| ... | @@ -67,11 +136,11 @@ | ... | @@ -67,11 +136,11 @@ |
| 67 | }; | 136 | }; |
| 68 | 137 | ||
| 69 | angular.module('ovTopo2') | 138 | angular.module('ovTopo2') |
| 70 | - .factory('Topo2Model', | 139 | + .factory('Topo2Model', |
| 71 | - [ | 140 | + [ |
| 72 | - function () { | 141 | + function () { |
| 73 | - return Model; | 142 | + return Model; |
| 74 | - } | 143 | + } |
| 75 | - ]); | 144 | + ]); |
| 76 | 145 | ||
| 77 | })(); | 146 | })(); | ... | ... |
| 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 Layout Module. | ||
| 19 | + Module that contains the d3.force.layout logic | ||
| 20 | + */ | ||
| 21 | + | ||
| 22 | +(function () { | ||
| 23 | + 'use strict'; | ||
| 24 | + | ||
| 25 | + var randomService; | ||
| 26 | + var fn; | ||
| 27 | + | ||
| 28 | + //internal state; | ||
| 29 | + var defaultLinkType = 'direct', | ||
| 30 | + nearDist = 15; | ||
| 31 | + | ||
| 32 | + function positionNode(node, forUpdate) { | ||
| 33 | + | ||
| 34 | + var meta = node.metaUi, | ||
| 35 | + x = meta && meta.x, | ||
| 36 | + y = meta && meta.y, | ||
| 37 | + dim = [800, 600], | ||
| 38 | + xy; | ||
| 39 | + | ||
| 40 | + // if the device contains explicit LONG/LAT data, use that to position | ||
| 41 | + if (setLongLat(node)) { | ||
| 42 | + //indicate we want to update cached meta data... | ||
| 43 | + return true; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + // else if we have [x,y] cached in meta data, use that... | ||
| 47 | + if (x !== undefined && y !== undefined) { | ||
| 48 | + node.fixed = true; | ||
| 49 | + node.px = node.x = x; | ||
| 50 | + node.py = node.y = y; | ||
| 51 | + return; | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + // if this is a node update (not a node add).. skip randomizer | ||
| 55 | + if (forUpdate) { | ||
| 56 | + return; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + // Note: Placing incoming unpinned nodes at exactly the same point | ||
| 60 | + // (center of the view) causes them to explode outwards when | ||
| 61 | + // the force layout kicks in. So, we spread them out a bit | ||
| 62 | + // initially, to provide a more serene layout convergence. | ||
| 63 | + // Additionally, if the node is a host, we place it near | ||
| 64 | + // the device it is connected to. | ||
| 65 | + | ||
| 66 | + function rand() { | ||
| 67 | + return { | ||
| 68 | + x: randomService.randDim(dim[0]), | ||
| 69 | + y: randomService.randDim(dim[1]) | ||
| 70 | + }; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + function near(node) { | ||
| 74 | + return { | ||
| 75 | + x: node.x + nearDist + randomService.spread(nearDist), | ||
| 76 | + y: node.y + nearDist + randomService.spread(nearDist) | ||
| 77 | + }; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + function getDevice(cp) { | ||
| 81 | + // console.log(cp); | ||
| 82 | + // var d = lu[cp.device]; | ||
| 83 | + // return d || rand(); | ||
| 84 | + return rand(); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); | ||
| 88 | + angular.extend(node, xy); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + function setLongLat(node) { | ||
| 92 | + var loc = node.location, | ||
| 93 | + coord; | ||
| 94 | + | ||
| 95 | + if (loc && loc.type === 'lnglat') { | ||
| 96 | + coord = [0, 0]; | ||
| 97 | + node.fixed = true; | ||
| 98 | + node.px = node.x = coord[0]; | ||
| 99 | + node.py = node.y = coord[1]; | ||
| 100 | + return true; | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + angular.module('ovTopo2') | ||
| 105 | + .factory('Topo2NodeModel', | ||
| 106 | + ['Topo2Model', 'FnService', 'RandomService', | ||
| 107 | + function (Model, _fn_, _RandomService_) { | ||
| 108 | + | ||
| 109 | + randomService = _RandomService_; | ||
| 110 | + fn = _fn_; | ||
| 111 | + | ||
| 112 | + return Model.extend({ | ||
| 113 | + initialize: function () { | ||
| 114 | + this.node = this.createNode(); | ||
| 115 | + }, | ||
| 116 | + svgClassName: function () { | ||
| 117 | + return fn.classNames('node', this.nodeType, this.get('type'), { | ||
| 118 | + online: this.get('online') | ||
| 119 | + }); | ||
| 120 | + }, | ||
| 121 | + createNode: function () { | ||
| 122 | + | ||
| 123 | + var node = angular.extend({}, this.attributes); | ||
| 124 | + | ||
| 125 | + // Augment as needed... | ||
| 126 | + node.class = this.nodeType; | ||
| 127 | + node.svgClass = this.svgClassName(); | ||
| 128 | + positionNode(node); | ||
| 129 | + return node; | ||
| 130 | + } | ||
| 131 | + }); | ||
| 132 | + }] | ||
| 133 | + ); | ||
| 134 | +})(); |
| ... | @@ -24,12 +24,13 @@ | ... | @@ -24,12 +24,13 @@ |
| 24 | 24 | ||
| 25 | var $log, | 25 | var $log, |
| 26 | wss, | 26 | wss, |
| 27 | + Model, | ||
| 27 | t2sr, | 28 | t2sr, |
| 28 | t2ds, | 29 | t2ds, |
| 29 | t2hs, | 30 | t2hs, |
| 30 | t2ls; | 31 | t2ls; |
| 31 | 32 | ||
| 32 | - var regions; | 33 | + var region; |
| 33 | 34 | ||
| 34 | function init() { | 35 | function init() { |
| 35 | regions = {}; | 36 | regions = {}; |
| ... | @@ -37,25 +38,46 @@ | ... | @@ -37,25 +38,46 @@ |
| 37 | 38 | ||
| 38 | function addRegion(data) { | 39 | function addRegion(data) { |
| 39 | 40 | ||
| 40 | - var region = { | 41 | + region = new Model({ |
| 41 | - subregions: t2sr.createSubRegionCollection(data.subregions), | 42 | + id: data.id, |
| 42 | - devices: t2ds.createDeviceCollection(data.devices, data), | 43 | + layerOrder: data.layerOrder |
| 43 | - hosts: t2hs.createHostCollection(data.hosts), | 44 | + }); |
| 44 | - links: t2ls.createLinkCollection(data.links), | 45 | + |
| 45 | - }; | 46 | + region.set({ |
| 47 | + subregions: t2sr.createSubRegionCollection(data.subregions, region), | ||
| 48 | + devices: t2ds.createDeviceCollection(data.devices, region), | ||
| 49 | + hosts: t2hs.createHostCollection(data.hosts, region), | ||
| 50 | + links: t2ls.createLinkCollection(data.links, region), | ||
| 51 | + }); | ||
| 52 | + | ||
| 53 | + region.set('test', 2); | ||
| 54 | + | ||
| 55 | + angular.forEach(region.get('links').models, function (link) { | ||
| 56 | + link.createLink(); | ||
| 57 | + }); | ||
| 46 | 58 | ||
| 47 | $log.debug('Region: ', region); | 59 | $log.debug('Region: ', region); |
| 48 | } | 60 | } |
| 49 | 61 | ||
| 62 | + function regionNodes() { | ||
| 63 | + return [].concat(region.get('devices').models, region.get('hosts').models); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + | ||
| 67 | + function regionLinks() { | ||
| 68 | + return region.get('links').models; | ||
| 69 | + } | ||
| 70 | + | ||
| 50 | angular.module('ovTopo2') | 71 | angular.module('ovTopo2') |
| 51 | .factory('Topo2RegionService', | 72 | .factory('Topo2RegionService', |
| 52 | - ['$log', 'WebSocketService', 'Topo2SubRegionService', 'Topo2DeviceService', | 73 | + ['$log', 'WebSocketService', 'Topo2Model', 'Topo2SubRegionService', 'Topo2DeviceService', |
| 53 | 'Topo2HostService', 'Topo2LinkService', | 74 | 'Topo2HostService', 'Topo2LinkService', |
| 54 | 75 | ||
| 55 | - function (_$log_, _wss_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) { | 76 | + function (_$log_, _wss_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) { |
| 56 | 77 | ||
| 57 | $log = _$log_; | 78 | $log = _$log_; |
| 58 | wss = _wss_; | 79 | wss = _wss_; |
| 80 | + Model = _Model_ | ||
| 59 | t2sr = _t2sr_; | 81 | t2sr = _t2sr_; |
| 60 | t2ds = _t2ds_; | 82 | t2ds = _t2ds_; |
| 61 | t2hs = _t2hs_; | 83 | t2hs = _t2hs_; |
| ... | @@ -65,6 +87,9 @@ | ... | @@ -65,6 +87,9 @@ |
| 65 | init: init, | 87 | init: init, |
| 66 | 88 | ||
| 67 | addRegion: addRegion, | 89 | addRegion: addRegion, |
| 90 | + regionNodes: regionNodes, | ||
| 91 | + regionLinks: regionLinks, | ||
| 92 | + | ||
| 68 | getSubRegions: t2sr.getSubRegions | 93 | getSubRegions: t2sr.getSubRegions |
| 69 | }; | 94 | }; |
| 70 | }]); | 95 | }]); | ... | ... |
File mode changed
File mode changed
| 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 Layout Module. | ||
| 19 | + Module that contains the d3.force.layout logic | ||
| 20 | + */ | ||
| 21 | + | ||
| 22 | +(function () { | ||
| 23 | + 'use strict'; | ||
| 24 | + | ||
| 25 | + var dimensions; | ||
| 26 | + | ||
| 27 | + function newDim(_dimensions) { | ||
| 28 | + dimensions = _dimensions; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + function getDimensions() { | ||
| 32 | + return dimensions; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + angular.module('ovTopo2') | ||
| 36 | + .factory('Topo2ViewService', | ||
| 37 | + [ | ||
| 38 | + function () { | ||
| 39 | + return { | ||
| 40 | + newDim: newDim, | ||
| 41 | + getDimensions: getDimensions | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | + ] | ||
| 45 | + ); | ||
| 46 | +})(); |
| ... | @@ -128,15 +128,21 @@ | ... | @@ -128,15 +128,21 @@ |
| 128 | <!-- Under development for Region support. --> | 128 | <!-- Under development for Region support. --> |
| 129 | <script src="app/view/topo2/topo2.js"></script> | 129 | <script src="app/view/topo2/topo2.js"></script> |
| 130 | <script src="app/view/topo2/topo2Collection.js"></script> | 130 | <script src="app/view/topo2/topo2Collection.js"></script> |
| 131 | + <script src="app/view/topo2/topo2D3.js"></script> | ||
| 131 | <script src="app/view/topo2/topo2Device.js"></script> | 132 | <script src="app/view/topo2/topo2Device.js"></script> |
| 132 | - <script src="app/view/topo2/topo2Model.js"></script> | ||
| 133 | <script src="app/view/topo2/topo2Event.js"></script> | 133 | <script src="app/view/topo2/topo2Event.js"></script> |
| 134 | <script src="app/view/topo2/topo2Force.js"></script> | 134 | <script src="app/view/topo2/topo2Force.js"></script> |
| 135 | <script src="app/view/topo2/topo2Host.js"></script> | 135 | <script src="app/view/topo2/topo2Host.js"></script> |
| 136 | <script src="app/view/topo2/topo2Instance.js"></script> | 136 | <script src="app/view/topo2/topo2Instance.js"></script> |
| 137 | + <script src="app/view/topo2/topo2Layout.js"></script> | ||
| 137 | <script src="app/view/topo2/topo2Link.js"></script> | 138 | <script src="app/view/topo2/topo2Link.js"></script> |
| 139 | + <script src="app/view/topo2/topo2Model.js"></script> | ||
| 140 | + <script src="app/view/topo2/topo2NodeModel.js"></script> | ||
| 138 | <script src="app/view/topo2/topo2Region.js"></script> | 141 | <script src="app/view/topo2/topo2Region.js"></script> |
| 142 | + <script src="app/view/topo2/topo2Select.js"></script> | ||
| 139 | <script src="app/view/topo2/topo2SubRegion.js"></script> | 143 | <script src="app/view/topo2/topo2SubRegion.js"></script> |
| 144 | + <script src="app/view/topo2/topo2Theme.js"></script> | ||
| 145 | + <script src="app/view/topo2/topo2View.js"></script> | ||
| 140 | <link rel="stylesheet" href="app/view/topo2/topo2.css"> | 146 | <link rel="stylesheet" href="app/view/topo2/topo2.css"> |
| 141 | <link rel="stylesheet" href="app/view/topo2/topo2-theme.css"> | 147 | <link rel="stylesheet" href="app/view/topo2/topo2-theme.css"> |
| 142 | 148 | ... | ... |
| ... | @@ -216,7 +216,8 @@ describe('factory: fw/util/fn.js', function() { | ... | @@ -216,7 +216,8 @@ describe('factory: fw/util/fn.js', function() { |
| 216 | 'isMobile', 'isChrome', 'isSafari', 'isFirefox', | 216 | 'isMobile', 'isChrome', 'isSafari', 'isFirefox', |
| 217 | 'debugOn', 'debug', | 217 | 'debugOn', 'debug', |
| 218 | 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap', | 218 | 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap', |
| 219 | - 'eecode', 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup' | 219 | + 'eecode', 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup', |
| 220 | + 'classNames' | ||
| 220 | ])).toBeTruthy(); | 221 | ])).toBeTruthy(); |
| 221 | }); | 222 | }); |
| 222 | 223 | ... | ... |
web/gui/src/test/_karma/package.json
0 → 100644
| 1 | +{ | ||
| 2 | + "name": "karma", | ||
| 3 | + "version": "1.0.0", | ||
| 4 | + "description": "", | ||
| 5 | + "main": "mockserver.js", | ||
| 6 | + "dependencies": { | ||
| 7 | + "websocket": "^1.0.23" | ||
| 8 | + }, | ||
| 9 | + "devDependencies": {}, | ||
| 10 | + "scripts": { | ||
| 11 | + "test": "echo \"Error: no test specified\" && exit 1" | ||
| 12 | + }, | ||
| 13 | + "author": "", | ||
| 14 | + "license": "ISC" | ||
| 15 | +} |
-
Please register or login to post a comment