mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-18 02:41:49 +02:00
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
This commit is contained in:
parent
ee5d121524
commit
ec1f45c00c
@ -386,6 +386,34 @@
|
||||
}
|
||||
|
||||
|
||||
var hasOwn = {}.hasOwnProperty;
|
||||
|
||||
function classNames () {
|
||||
var classes = [];
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var arg = arguments[i];
|
||||
if (!arg) continue;
|
||||
|
||||
var argType = typeof arg;
|
||||
|
||||
if (argType === 'string' || argType === 'number') {
|
||||
classes.push(arg);
|
||||
} else if (Array.isArray(arg)) {
|
||||
classes.push(classNames.apply(null, arg));
|
||||
} else if (argType === 'object') {
|
||||
for (var key in arg) {
|
||||
if (hasOwn.call(arg, key) && arg[key]) {
|
||||
classes.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return classes.join(' ');
|
||||
}
|
||||
|
||||
|
||||
angular.module('onosUtil')
|
||||
.factory('FnService',
|
||||
['$window', '$location', '$log', function (_$window_, $loc, _$log_) {
|
||||
@ -423,7 +451,8 @@
|
||||
parseBitRate: parseBitRate,
|
||||
addToTrie: addToTrie,
|
||||
removeFromTrie: removeFromTrie,
|
||||
trieLookup: trieLookup
|
||||
trieLookup: trieLookup,
|
||||
classNames: classNames
|
||||
};
|
||||
}]);
|
||||
|
||||
|
@ -320,6 +320,7 @@
|
||||
// updateLinks - subfunctions
|
||||
|
||||
function linkEntering(d) {
|
||||
|
||||
var link = d3.select(this);
|
||||
d.el = link;
|
||||
api.restyleLinkElement(d);
|
||||
|
@ -103,6 +103,7 @@
|
||||
// === EVENT HANDLERS
|
||||
|
||||
function addDevice(data) {
|
||||
console.log(data);
|
||||
var id = data.id,
|
||||
d;
|
||||
|
||||
@ -1044,7 +1045,7 @@
|
||||
updateLinks();
|
||||
updateNodes();
|
||||
}
|
||||
|
||||
|
||||
angular.module('ovTopo')
|
||||
.factory('TopoForceService',
|
||||
['$log', '$timeout', 'FnService', 'SvgUtilService',
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
ONOS GUI -- Topology View (theme) -- CSS file
|
||||
*/
|
||||
@ -22,8 +21,7 @@
|
||||
/* --- Base SVG Layer --- */
|
||||
|
||||
#ov-topo2 svg {
|
||||
/*background-color: #f4f4f4;*/
|
||||
background-color: goldenrod; /* just for testing */
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
/* --- "No Devices" Layer --- */
|
||||
@ -32,15 +30,355 @@
|
||||
fill: #db7773;
|
||||
}
|
||||
|
||||
#ov-topo2 svg #topo2-noDevsLayer text {
|
||||
#ov-topo2 svg #topo-noDevsLayer text {
|
||||
fill: #7e9aa8;
|
||||
}
|
||||
|
||||
/* --- Topo Map --- */
|
||||
|
||||
#ov-topo2 svg #topo2-map {
|
||||
#ov-topo2 svg #topo-map {
|
||||
stroke-width: 2px;
|
||||
stroke: #f4f4f4;
|
||||
fill: #e5e5e6;
|
||||
}
|
||||
|
||||
/* --- general topo-panel styling --- */
|
||||
|
||||
.topo-p svg {
|
||||
background: #c0242b;
|
||||
}
|
||||
|
||||
.topo-p svg .glyph {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.topo-p hr {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
||||
#topo-p-detail svg {
|
||||
background: none;
|
||||
}
|
||||
|
||||
#topo-p-detail .header svg .glyph {
|
||||
fill: #c0242b;
|
||||
}
|
||||
|
||||
|
||||
/* --- Topo Instance Panel --- */
|
||||
|
||||
#topo-p-instance svg rect {
|
||||
stroke-width: 0;
|
||||
fill: #fbfbfb;
|
||||
}
|
||||
|
||||
/* body of an instance */
|
||||
#topo-p-instance .online svg rect {
|
||||
opacity: 1;
|
||||
fill: #fbfbfb;
|
||||
}
|
||||
|
||||
#topo-p-instance svg .glyph {
|
||||
fill: #fff;
|
||||
}
|
||||
#topo-p-instance .online svg .glyph {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
|
||||
/* offline */
|
||||
#topo-p-instance svg .badgeIcon {
|
||||
opacity: 0.4;
|
||||
fill: #939598;
|
||||
}
|
||||
|
||||
/* online */
|
||||
#topo-p-instance .online svg .badgeIcon {
|
||||
opacity: 1.0;
|
||||
fill: #939598;
|
||||
}
|
||||
#topo-p-instance .online svg .badgeIcon.bird {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
#topo-p-instance svg .readyBadge {
|
||||
visibility: hidden;
|
||||
}
|
||||
#topo-p-instance .ready svg .readyBadge {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#topo-p-instance svg text {
|
||||
text-anchor: left;
|
||||
opacity: 0.5;
|
||||
fill: #3c3a3a;
|
||||
}
|
||||
|
||||
#topo-p-instance .online svg text {
|
||||
opacity: 1.0;
|
||||
fill: #3c3a3a;
|
||||
}
|
||||
|
||||
#topo-p-instance .onosInst.mastership {
|
||||
opacity: 0.3;
|
||||
}
|
||||
#topo-p-instance .onosInst.mastership.affinity {
|
||||
opacity: 1.0;
|
||||
}
|
||||
#topo-p-instance .onosInst.mastership.affinity svg rect {
|
||||
filter: url(#blue-glow);
|
||||
}
|
||||
|
||||
.firefox #topo-p-instance .onosInst.mastership.affinity svg rect {
|
||||
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");
|
||||
}
|
||||
|
||||
/* --- Topo Nodes --- */
|
||||
|
||||
#ov-topo2 svg .suppressed {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .suppressedmax {
|
||||
opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
/* Device Nodes */
|
||||
|
||||
/* note: device without the 'online' class is offline */
|
||||
#ov-topo2 svg .node.device rect {
|
||||
/* TODO: theme */
|
||||
fill: #f0f0f0;
|
||||
}
|
||||
#ov-topo2 svg .node.device text {
|
||||
/*TODO: theme*/
|
||||
fill: #bbb;
|
||||
}
|
||||
#ov-topo2 svg .node.device use {
|
||||
/*TODO: theme*/
|
||||
fill: #777;
|
||||
}
|
||||
|
||||
|
||||
#ov-topo2 svg .node.device.online rect {
|
||||
fill: #ffffff;
|
||||
}
|
||||
#ov-topo2 svg .node.device.online text {
|
||||
fill: #3c3a3a;
|
||||
}
|
||||
#ov-topo2 svg .node.device.online use {
|
||||
/* NOTE: this gets overridden programatically */
|
||||
fill: #454545;
|
||||
}
|
||||
|
||||
|
||||
#ov-topo2 svg .node.device.selected rect {
|
||||
stroke-width: 2.0;
|
||||
stroke: #009fdb;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
/* (... works for bothand dark themes...) */
|
||||
#ov-topo2 svg .node .badge circle {
|
||||
stroke: #aaa;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node .badge.badgeInfo circle {
|
||||
fill: #99d;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node .badge.badgeWarn circle {
|
||||
fill: #da2;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node .badge.badgeError circle {
|
||||
fill: #e44;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node .badge use {
|
||||
fill: white !important;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node .badge.badgeInfo use {
|
||||
fill: #448;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node .badge text {
|
||||
fill: white !important;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node .badge.badgeInfo text {
|
||||
fill: #448;
|
||||
}
|
||||
|
||||
/* Host Nodes */
|
||||
|
||||
#ov-topo2 svg .node.host {
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node.host text {
|
||||
stroke: none;
|
||||
font: 9pt sans-serif;
|
||||
fill: #846;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node.host circle {
|
||||
stroke: #a3a596;
|
||||
fill: #e0dfd6;
|
||||
}
|
||||
#ov-topo2 svg .node.host.selected .hostIcon > circle {
|
||||
stroke-width: 2.0;
|
||||
stroke: #009fdb;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .node.host use {
|
||||
fill: #3c3a3a;
|
||||
}
|
||||
|
||||
/* --- Topo Links --- */
|
||||
|
||||
#ov-topo2 svg .link {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.selected,
|
||||
#ov-topo2 svg .link.enhanced {
|
||||
stroke-width: 3.5;
|
||||
stroke: #009fdb;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.inactive {
|
||||
opacity: .5;
|
||||
stroke-dasharray: 8 4;
|
||||
}
|
||||
/* TODO: Review for not-permitted links */
|
||||
#ov-topo2 svg .link.not-permitted {
|
||||
stroke: rgb(255,0,0);
|
||||
stroke-width: 5.0;
|
||||
stroke-dasharray: 8 4;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.secondary {
|
||||
stroke-width: 3px;
|
||||
stroke: rgba(0,153,51,0.5);
|
||||
}
|
||||
|
||||
/* Port traffic color visualization for Kbps, Mbps, and Gbps */
|
||||
|
||||
#ov-topo2 svg .link.secondary.port-traffic-Kbps {
|
||||
stroke: rgb(0,153,51);
|
||||
stroke-width: 5.0;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.secondary.port-traffic-Mbps {
|
||||
stroke: rgb(128,145,27);
|
||||
stroke-width: 6.5;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.secondary.port-traffic-Gbps {
|
||||
stroke: rgb(255, 137, 3);
|
||||
stroke-width: 8.0;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.secondary.port-traffic-Gbps-choked {
|
||||
stroke: rgb(183, 30, 21);
|
||||
stroke-width: 8.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ov-topo2 svg .link.animated {
|
||||
stroke-dasharray: 8 5;
|
||||
animation: ants 5s infinite linear;
|
||||
/* below line could be added via Javascript, based on path, if we cared
|
||||
* enough about the direction of ant-flow
|
||||
*/
|
||||
/*animation-direction: reverse;*/
|
||||
}
|
||||
@keyframes ants {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: 400;
|
||||
}
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.primary {
|
||||
stroke-width: 4px;
|
||||
stroke: #ffA300;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.secondary.optical {
|
||||
stroke-width: 4px;
|
||||
stroke: rgba(128,64,255,0.5);
|
||||
}
|
||||
|
||||
#ov-topo2 svg .link.primary.optical {
|
||||
stroke-width: 6px;
|
||||
stroke: #74f;
|
||||
}
|
||||
|
||||
/* Link Labels */
|
||||
#ov-topo2 svg .linkLabel rect {
|
||||
stroke: none;
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .linkLabel text {
|
||||
fill: #444;
|
||||
}
|
||||
|
||||
/* Port Labels */
|
||||
|
||||
#ov-topo2 svg .portLabel rect {
|
||||
stroke: #a3a596;
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
#ov-topo2 svg .portLabel text {
|
||||
fill: #444;
|
||||
}
|
||||
|
||||
/* Number of Links Labels */
|
||||
|
||||
|
||||
#ov-topo2 text.numLinkText {
|
||||
fill: #444;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------- */
|
||||
/* Sprite Layer */
|
||||
|
||||
#ov-topo2 svg #topo-sprites .gold1 use {
|
||||
stroke: #fda;
|
||||
fill: none;
|
||||
}
|
||||
#ov-topo2 svg #topo-sprites .gold1 text {
|
||||
fill: #eda;
|
||||
}
|
||||
|
||||
#ov-topo2 svg #topo-sprites .blue1 use {
|
||||
stroke: #bbd;
|
||||
fill: none;
|
||||
}
|
||||
#ov-topo2 svg #topo-sprites .blue1 text {
|
||||
fill: #cce;
|
||||
}
|
||||
|
||||
#ov-topo2 svg #topo-sprites .gray1 use {
|
||||
stroke: #ccc;
|
||||
fill: none;
|
||||
}
|
||||
#ov-topo2 svg #topo-sprites .gray1 text {
|
||||
fill: #ddd;
|
||||
}
|
||||
|
||||
/* fills */
|
||||
#ov-topo2 svg #topo-sprites use.fill-gray2 {
|
||||
fill: #eee;
|
||||
}
|
||||
|
||||
#ov-topo2 svg #topo-sprites use.fill-blue2 {
|
||||
fill: #bce;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<!-- Topology View partial HTML -->
|
||||
<div id="ov-topo2">
|
||||
<div id="topo2tmp">
|
||||
|
||||
<!-- <div id="topo2tmp">
|
||||
<div class="parentRegion">
|
||||
Parent Region: <span> - </span>
|
||||
</div>
|
||||
@ -27,7 +28,7 @@
|
||||
<h4>Peers</h4>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- Below here is good; Above here is temporary, for debugging -->
|
||||
|
||||
|
@ -48,6 +48,7 @@
|
||||
// callback invoked when the SVG view has been resized..
|
||||
function svgResized(s) {
|
||||
$log.debug('topo2 view resized', s);
|
||||
t2fs.newDim([s.width, s.height]);
|
||||
}
|
||||
|
||||
function setUpKeys(overlayKeys) {
|
||||
@ -68,7 +69,7 @@
|
||||
ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc});
|
||||
|
||||
// keep the map lines constant width while zooming
|
||||
mapG.style('stroke-width', (2.0 / sc) + 'px');
|
||||
// mapG.style('stroke-width', (2.0 / sc) + 'px');
|
||||
}
|
||||
|
||||
function setUpZoom() {
|
||||
|
@ -55,8 +55,6 @@
|
||||
_this._byId[d.id] = model;
|
||||
});
|
||||
}
|
||||
|
||||
// this.sort();
|
||||
},
|
||||
get: function (id) {
|
||||
if (!id) {
|
||||
@ -77,7 +75,10 @@
|
||||
_reset: function () {
|
||||
this._byId = [];
|
||||
this.models = [];
|
||||
}
|
||||
},
|
||||
toJSON: function(options) {
|
||||
return this.models.map(function(model) { return model.toJSON(options); });
|
||||
},
|
||||
};
|
||||
|
||||
Collection.extend = function (protoProps, staticProps) {
|
||||
|
163
web/gui/src/main/webapp/app/view/topo2/topo2D3.js
Normal file
163
web/gui/src/main/webapp/app/view/topo2/topo2D3.js
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
ONOS GUI -- Topology Layout Module.
|
||||
Module that contains the d3.force.layout logic
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var sus, is, ts;
|
||||
|
||||
// internal state
|
||||
var deviceLabelIndex = 0,
|
||||
hostLabelIndex = 0;
|
||||
|
||||
// configuration
|
||||
var devIconDim = 36,
|
||||
labelPad = 4,
|
||||
hostRadius = 14,
|
||||
badgeConfig = {
|
||||
radius: 12,
|
||||
yoff: 5,
|
||||
gdelta: 10
|
||||
},
|
||||
halfDevIcon = devIconDim / 2,
|
||||
devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
|
||||
hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
|
||||
status = {
|
||||
i: 'badgeInfo',
|
||||
w: 'badgeWarn',
|
||||
e: 'badgeError'
|
||||
};
|
||||
|
||||
// note: these are the device icon colors without affinity (no master)
|
||||
var dColTheme = {
|
||||
light: {
|
||||
online: '#444444',
|
||||
offline: '#cccccc'
|
||||
},
|
||||
dark: {
|
||||
// TODO: theme
|
||||
online: '#444444',
|
||||
offline: '#cccccc'
|
||||
}
|
||||
};
|
||||
|
||||
function init() {}
|
||||
|
||||
function renderBadge(node, bdg, boff) {
|
||||
var bsel,
|
||||
bcr = badgeConfig.radius,
|
||||
bcgd = badgeConfig.gdelta;
|
||||
|
||||
node.select('g.badge').remove();
|
||||
|
||||
bsel = node.append('g')
|
||||
.classed('badge', true)
|
||||
.classed(badgeStatus(bdg), true)
|
||||
.attr('transform', sus.translate(boff.dx, boff.dy));
|
||||
|
||||
bsel.append('circle')
|
||||
.attr('r', bcr);
|
||||
|
||||
if (bdg.txt) {
|
||||
bsel.append('text')
|
||||
.attr('dy', badgeConfig.yoff)
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(bdg.txt);
|
||||
} else if (bdg.gid) {
|
||||
bsel.append('use')
|
||||
.attr({
|
||||
width: bcgd * 2,
|
||||
height: bcgd * 2,
|
||||
transform: sus.translate(-bcgd, -bcgd),
|
||||
'xlink:href': '#' + bdg.gid
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move to Device Model when working on the Exit Devices
|
||||
function updateDeviceRendering(d) {
|
||||
var node = d.el,
|
||||
bdg = d.badge,
|
||||
label = trimLabel(deviceLabel(d)),
|
||||
labelWidth;
|
||||
|
||||
node.select('text').text(label);
|
||||
labelWidth = label ? computeLabelWidth(node) : 0;
|
||||
|
||||
node.select('rect')
|
||||
.transition()
|
||||
.attr(iconBox(devIconDim, labelWidth));
|
||||
|
||||
if (bdg) {
|
||||
renderBadge(node, bdg, devBadgeOff);
|
||||
}
|
||||
}
|
||||
|
||||
function deviceEnter(device) {
|
||||
device.onEnter(this, device);
|
||||
}
|
||||
|
||||
function hostLabel(d) {
|
||||
return d.get('id');
|
||||
|
||||
// var idx = (hostLabelIndex < d.get('labels').length) ? hostLabelIndex : 0;
|
||||
// return d.labels[idx];
|
||||
}
|
||||
|
||||
function hostEnter(d) {
|
||||
var node = d3.select(this),
|
||||
gid = d.get('type') || 'unknown',
|
||||
textDy = hostRadius + 10;
|
||||
|
||||
d.el = node;
|
||||
// sus.visible(node, api.showHosts());
|
||||
|
||||
is.addHostIcon(node, hostRadius, gid);
|
||||
|
||||
node.append('text')
|
||||
.text(hostLabel)
|
||||
.attr('dy', textDy)
|
||||
.attr('text-anchor', 'middle');
|
||||
}
|
||||
|
||||
function linkEntering(link) {
|
||||
link.onEnter(this);
|
||||
}
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2D3Service',
|
||||
['SvgUtilService', 'IconService', 'ThemeService',
|
||||
|
||||
function (_sus_, _is_, _ts_) {
|
||||
sus = _sus_;
|
||||
is = _is_;
|
||||
ts = _ts_;
|
||||
|
||||
return {
|
||||
init: init,
|
||||
deviceEnter: deviceEnter,
|
||||
hostEnter: hostEnter,
|
||||
linkEntering: linkEntering
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
})();
|
@ -22,16 +22,37 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Collection, Model;
|
||||
var Collection, Model, is, sus, ts, t2vs;
|
||||
|
||||
var remappedDeviceTypes = {
|
||||
virtual: 'cord'
|
||||
};
|
||||
|
||||
// configuration
|
||||
var devIconDim = 36,
|
||||
labelPad = 10,
|
||||
hostRadius = 14,
|
||||
badgeConfig = {
|
||||
radius: 12,
|
||||
yoff: 5,
|
||||
gdelta: 10
|
||||
},
|
||||
halfDevIcon = devIconDim / 2,
|
||||
devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
|
||||
hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
|
||||
status = {
|
||||
i: 'badgeInfo',
|
||||
w: 'badgeWarn',
|
||||
e: 'badgeError'
|
||||
},
|
||||
deviceLabelIndex = 0;
|
||||
|
||||
function createDeviceCollection(data, region) {
|
||||
|
||||
var DeviceCollection = Collection.extend({
|
||||
model: Model,
|
||||
get: function () {},
|
||||
comparator: function(a, b) {
|
||||
|
||||
var order = region.layerOrder;
|
||||
var order = region.get('layerOrder');
|
||||
return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer'));
|
||||
}
|
||||
});
|
||||
@ -49,14 +70,106 @@
|
||||
return deviceCollection;
|
||||
}
|
||||
|
||||
function mapDeviceTypeToGlyph(type) {
|
||||
return remappedDeviceTypes[type] || type || 'unknown';
|
||||
}
|
||||
|
||||
function deviceLabel(d) {
|
||||
//TODO: Device Json is missing labels array
|
||||
return "";
|
||||
var labels = this.get('labels'),
|
||||
idx = (deviceLabelIndex < labels.length) ? deviceLabelIndex : 0;
|
||||
return labels[idx];
|
||||
}
|
||||
|
||||
function trimLabel(label) {
|
||||
return (label && label.trim()) || '';
|
||||
}
|
||||
|
||||
function computeLabelWidth() {
|
||||
var text = this.select('text'),
|
||||
box = text.node().getBBox();
|
||||
return box.width + labelPad * 2;
|
||||
}
|
||||
|
||||
function iconBox(dim, labelWidth) {
|
||||
return {
|
||||
x: -dim / 2,
|
||||
y: -dim / 2,
|
||||
width: dim + labelWidth,
|
||||
height: dim
|
||||
}
|
||||
}
|
||||
|
||||
function deviceGlyphColor(d) {
|
||||
|
||||
var o = this.node.online,
|
||||
id = "127.0.0.1", // TODO: This should be from node.master
|
||||
otag = o ? 'online' : 'offline';
|
||||
return o ? sus.cat7().getColor(id, 0, ts.theme())
|
||||
: dColTheme[ts.theme()][otag];
|
||||
}
|
||||
|
||||
function setDeviceColor() {
|
||||
this.el.select('use')
|
||||
.style('fill', this.deviceGlyphColor());
|
||||
}
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2DeviceService',
|
||||
['Topo2Collection', 'Topo2Model',
|
||||
['Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
|
||||
'ThemeService', 'Topo2ViewService',
|
||||
|
||||
function (_Collection_, _Model_) {
|
||||
function (_Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
|
||||
|
||||
t2vs = _t2vs_;
|
||||
is = _is_;
|
||||
sus = _sus_;
|
||||
ts = _ts_;
|
||||
Collection = _Collection_;
|
||||
Model = _Model_.extend({});
|
||||
|
||||
Model = _NodeModel_.extend({
|
||||
initialize: function () {
|
||||
this.set('weight', 0);
|
||||
this.constructor.__super__.initialize.apply(this, arguments);
|
||||
},
|
||||
nodeType: 'device',
|
||||
deviceLabel: deviceLabel,
|
||||
deviceGlyphColor: deviceGlyphColor,
|
||||
mapDeviceTypeToGlyph: mapDeviceTypeToGlyph,
|
||||
trimLabel: trimLabel,
|
||||
setDeviceColor: setDeviceColor,
|
||||
onEnter: function (el) {
|
||||
|
||||
var node = d3.select(el),
|
||||
glyphId = mapDeviceTypeToGlyph(this.get('type')),
|
||||
label = trimLabel(this.deviceLabel()),
|
||||
rect, text, glyph, labelWidth;
|
||||
|
||||
this.el = node;
|
||||
|
||||
rect = node.append('rect');
|
||||
|
||||
text = node.append('text').text(label)
|
||||
.attr('text-anchor', 'left')
|
||||
.attr('y', '0.3em')
|
||||
.attr('x', halfDevIcon + labelPad);
|
||||
|
||||
glyph = is.addDeviceIcon(node, glyphId, devIconDim);
|
||||
|
||||
labelWidth = label ? computeLabelWidth(node) : 0;
|
||||
|
||||
rect.attr(iconBox(devIconDim, labelWidth));
|
||||
glyph.attr(iconBox(devIconDim, 0));
|
||||
|
||||
node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
|
||||
this.render();
|
||||
},
|
||||
onExit: function () {},
|
||||
render: function () {
|
||||
this.setDeviceColor();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
createDeviceCollection: createDeviceCollection
|
||||
|
@ -60,62 +60,17 @@
|
||||
linkLabel,
|
||||
node;
|
||||
|
||||
var $log, wss, t2is, t2rs;
|
||||
var $log, wss, t2is, t2rs, t2ls, t2vs;
|
||||
var svg, forceG, uplink, dim, opts;
|
||||
|
||||
// ========================== Helper Functions
|
||||
|
||||
function init(_svg_, forceG, _uplink_, _dim_, opts) {
|
||||
|
||||
$log.debug('Initialize topo force layout');
|
||||
|
||||
nodeG = forceG.append('g').attr('id', 'topo-nodes');
|
||||
node = nodeG.selectAll('.node');
|
||||
|
||||
linkG = forceG.append('g').attr('id', 'topo-links');
|
||||
linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
|
||||
numLinkLblsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
|
||||
nodeG = forceG.append('g').attr('id', 'topo-nodes');
|
||||
portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
|
||||
|
||||
link = linkG.selectAll('.link');
|
||||
linkLabel = linkLabelG.selectAll('.linkLabel');
|
||||
node = nodeG.selectAll('.node');
|
||||
|
||||
var width = 640,
|
||||
height = 480;
|
||||
|
||||
var nodes = [
|
||||
{ x: width/3, y: height/2 },
|
||||
{ x: 2*width/3, y: height/2 }
|
||||
];
|
||||
|
||||
var links = [
|
||||
{ source: 0, target: 1 }
|
||||
];
|
||||
|
||||
var svg = d3.select('body').append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
|
||||
var force = d3.layout.force()
|
||||
.size([width, height])
|
||||
.nodes(nodes)
|
||||
.links(links);
|
||||
|
||||
force.linkDistance(width/2);
|
||||
|
||||
|
||||
var link = svg.selectAll('.link')
|
||||
.data(links)
|
||||
.enter().append('line')
|
||||
.attr('class', 'link');
|
||||
|
||||
var node = svg.selectAll('.node')
|
||||
.data(nodes)
|
||||
.enter().append('circle')
|
||||
.attr('class', 'node');
|
||||
|
||||
force.start();
|
||||
function init(_svg_, _forceG_, _uplink_, _dim_, _opts_) {
|
||||
svg = _svg_;
|
||||
forceG = _forceG_;
|
||||
uplink = _uplink_;
|
||||
dim = _dim_;
|
||||
opts = _opts_
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
@ -206,6 +161,9 @@
|
||||
$log.debug('>> topo2CurrentRegion event:', data);
|
||||
doTmpCurrentRegion(data);
|
||||
t2rs.addRegion(data);
|
||||
t2ls.init(svg, forceG, uplink, dim, opts);
|
||||
t2ls.update();
|
||||
t2ls.start();
|
||||
}
|
||||
|
||||
function topo2PeerRegions(data) {
|
||||
@ -257,20 +215,37 @@
|
||||
// link.classed(cls, b);
|
||||
}
|
||||
|
||||
function newDim(_dim_) {
|
||||
dim = _dim_;
|
||||
t2vs.newDim(dim);
|
||||
// force.size(dim);
|
||||
// tms.newDim(dim);
|
||||
t2ls.setDimensions();
|
||||
}
|
||||
|
||||
function getDim() {
|
||||
return dim;
|
||||
}
|
||||
|
||||
// ========================== Main Service Definition
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2ForceService',
|
||||
['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService',
|
||||
function (_$log_, _wss_, _t2is_, _t2rs_) {
|
||||
'Topo2LayoutService', 'Topo2ViewService',
|
||||
function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_, _t2vs_) {
|
||||
|
||||
$log = _$log_;
|
||||
wss = _wss_;
|
||||
t2is = _t2is_;
|
||||
t2rs = _t2rs_;
|
||||
t2ls = _t2ls_;
|
||||
t2vs = _t2vs_;
|
||||
|
||||
return {
|
||||
|
||||
init: init,
|
||||
newDim: newDim,
|
||||
|
||||
destroy: destroy,
|
||||
topo2AllInstances: allInstances,
|
||||
|
@ -22,7 +22,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Collection, Model;
|
||||
var Collection, Model, t2vs;
|
||||
|
||||
function createHostCollection(data, region) {
|
||||
|
||||
@ -42,17 +42,21 @@
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2HostService',
|
||||
['Topo2Collection', 'Topo2Model',
|
||||
[
|
||||
'Topo2Collection', 'Topo2NodeModel', 'Topo2ViewService',
|
||||
function (_Collection_, _NodeModel_, classnames, _t2vs_) {
|
||||
|
||||
function (_Collection_, _Model_) {
|
||||
t2vs = _t2vs_;
|
||||
Collection = _Collection_;
|
||||
|
||||
Collection = _Collection_;
|
||||
Model = _Model_.extend();
|
||||
Model = _NodeModel_.extend({
|
||||
nodeType: 'host'
|
||||
});
|
||||
|
||||
return {
|
||||
createHostCollection: createHostCollection
|
||||
};
|
||||
}
|
||||
]);
|
||||
return {
|
||||
createHostCollection: createHostCollection
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
||||
|
334
web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
Normal file
334
web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
Normal file
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
ONOS GUI -- Topology Layout Module.
|
||||
Module that contains the d3.force.layout logic
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var $log, sus, t2rs, t2d3, t2vs;
|
||||
|
||||
var linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
|
||||
var link, linkLabel, node;
|
||||
|
||||
var nodes, links;
|
||||
|
||||
var force;
|
||||
|
||||
// default settings for force layout
|
||||
var defaultSettings = {
|
||||
gravity: 0.4,
|
||||
friction: 0.7,
|
||||
charge: {
|
||||
// note: key is node.class
|
||||
device: -8000,
|
||||
host: -5000,
|
||||
_def_: -12000
|
||||
},
|
||||
linkDistance: {
|
||||
// note: key is link.type
|
||||
direct: 100,
|
||||
optical: 120,
|
||||
hostLink: 3,
|
||||
_def_: 50
|
||||
},
|
||||
linkStrength: {
|
||||
// note: key is link.type
|
||||
// range: {0.0 ... 1.0}
|
||||
//direct: 1.0,
|
||||
//optical: 1.0,
|
||||
//hostLink: 1.0,
|
||||
_def_: 1.0
|
||||
}
|
||||
};
|
||||
|
||||
// configuration
|
||||
var linkConfig = {
|
||||
light: {
|
||||
baseColor: '#939598',
|
||||
inColor: '#66f',
|
||||
outColor: '#f00'
|
||||
},
|
||||
dark: {
|
||||
// TODO : theme
|
||||
baseColor: '#939598',
|
||||
inColor: '#66f',
|
||||
outColor: '#f00'
|
||||
},
|
||||
inWidth: 12,
|
||||
outWidth: 10
|
||||
};
|
||||
|
||||
// internal state
|
||||
var settings, // merged default settings and options
|
||||
force, // force layout object
|
||||
drag, // drag behavior handler
|
||||
network = {
|
||||
nodes: [],
|
||||
links: [],
|
||||
linksByDevice: {},
|
||||
lookup: {},
|
||||
revLinkToKey: {}
|
||||
},
|
||||
lu, // shorthand for lookup
|
||||
rlk, // shorthand for revLinktoKey
|
||||
showHosts = false, // whether hosts are displayed
|
||||
showOffline = true, // whether offline devices are displayed
|
||||
nodeLock = false, // whether nodes can be dragged or not (locked)
|
||||
fTimer, // timer for delayed force layout
|
||||
fNodesTimer, // timer for delayed nodes update
|
||||
fLinksTimer, // timer for delayed links update
|
||||
dim, // the dimensions of the force layout [w,h]
|
||||
linkNums = []; // array of link number labels
|
||||
|
||||
var tickStuff = {
|
||||
nodeAttr: {
|
||||
transform: function (d) {
|
||||
var dx = isNaN(d.x) ? 0 : d.x,
|
||||
dy = isNaN(d.y) ? 0 : d.y;
|
||||
return sus.translate(dx, dy);
|
||||
}
|
||||
},
|
||||
linkAttr: {
|
||||
x1: function (d) { return d.get('position').x1; },
|
||||
y1: function (d) { return d.get('position').y1; },
|
||||
x2: function (d) { return d.get('position').x2; },
|
||||
y2: function (d) { return d.get('position').y2; }
|
||||
},
|
||||
linkLabelAttr: {
|
||||
transform: function (d) {
|
||||
var lnk = tms.findLinkById(d.get('key'));
|
||||
if (lnk) {
|
||||
return t2d3.transformLabel(lnk.get('position'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function init(_svg_, forceG, _uplink_, _dim_, opts) {
|
||||
|
||||
$log.debug("Initialising Topology Layout");
|
||||
|
||||
settings = angular.extend({}, defaultSettings, opts);
|
||||
|
||||
linkG = forceG.append('g').attr('id', 'topo-links');
|
||||
linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
|
||||
numLinkLabelsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
|
||||
nodeG = forceG.append('g').attr('id', 'topo-nodes');
|
||||
portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
|
||||
|
||||
link = linkG.selectAll('.link');
|
||||
linkLabel = linkLabelG.selectAll('.linkLabel');
|
||||
node = nodeG.selectAll('.node');
|
||||
|
||||
force = d3.layout.force()
|
||||
.size(t2vs.getDimensions())
|
||||
.nodes(t2rs.regionNodes())
|
||||
.links(t2rs.regionLinks())
|
||||
.gravity(settings.gravity)
|
||||
.friction(settings.friction)
|
||||
.charge(settings.charge._def_)
|
||||
.linkDistance(settings.linkDistance._def_)
|
||||
.linkStrength(settings.linkStrength._def_)
|
||||
.on('tick', tick);
|
||||
}
|
||||
|
||||
function tick() {
|
||||
// guard against null (which can happen when our view pages out)...
|
||||
if (node && node.size()) {
|
||||
node.attr(tickStuff.nodeAttr);
|
||||
}
|
||||
if (link && link.size()) {
|
||||
link.call(calcPosition)
|
||||
.attr(tickStuff.linkAttr);
|
||||
// t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
|
||||
}
|
||||
if (linkLabel && linkLabel.size()) {
|
||||
linkLabel.attr(tickStuff.linkLabelAttr);
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
_updateNodes();
|
||||
_updateLinks();
|
||||
}
|
||||
|
||||
function _updateNodes() {
|
||||
|
||||
var regionNodes = t2rs.regionNodes();
|
||||
|
||||
// select all the nodes in the layout:
|
||||
node = nodeG.selectAll('.node')
|
||||
.data(regionNodes, function (d) { return d.get('id'); });
|
||||
|
||||
var entering = node.enter()
|
||||
.append('g')
|
||||
.attr({
|
||||
id: function (d) { return sus.safeId(d.get('id')); },
|
||||
class: function (d) { return d.svgClassName() },
|
||||
transform: function (d) {
|
||||
// Need to guard against NaN here ??
|
||||
return sus.translate(d.node.x, d.node.y);
|
||||
},
|
||||
opacity: 0
|
||||
})
|
||||
// .on('mouseover', tss.nodeMouseOver)
|
||||
// .on('mouseout', tss.nodeMouseOut)
|
||||
.transition()
|
||||
.attr('opacity', 1);
|
||||
|
||||
entering.filter('.device').each(t2d3.deviceEnter);
|
||||
entering.filter('.host').each(t2d3.hostEnter);
|
||||
|
||||
// operate on both existing and new nodes:
|
||||
// node.filter('.device').each(function (device) {
|
||||
// t2d3.updateDeviceColors(device);
|
||||
// });
|
||||
}
|
||||
|
||||
function _updateLinks() {
|
||||
|
||||
// var th = ts.theme();
|
||||
var regionLinks = t2rs.regionLinks();
|
||||
|
||||
link = linkG.selectAll('.link')
|
||||
.data(regionLinks, function (d) { return d.get('key'); });
|
||||
|
||||
// operate on existing links:
|
||||
link.each(function (d) {
|
||||
// this is supposed to be an existing link, but we have observed
|
||||
// occasions (where links are deleted and added rapidly?) where
|
||||
// the DOM element has not been defined. So protect against that...
|
||||
if (d.el) {
|
||||
restyleLinkElement(d, true);
|
||||
}
|
||||
});
|
||||
|
||||
// operate on entering links:
|
||||
var entering = link.enter()
|
||||
.append('line')
|
||||
.call(calcPosition)
|
||||
.attr({
|
||||
x1: function (d) { return d.get('position').x1; },
|
||||
y1: function (d) { return d.get('position').y1; },
|
||||
x2: function (d) { return d.get('position').x2; },
|
||||
y2: function (d) { return d.get('position').y2; },
|
||||
stroke: linkConfig['light'].inColor,
|
||||
'stroke-width': linkConfig.inWidth
|
||||
});
|
||||
|
||||
entering.each(t2d3.linkEntering);
|
||||
|
||||
// operate on both existing and new links:
|
||||
//link.each(...)
|
||||
|
||||
// add labels for how many links are in a thick line
|
||||
// t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
|
||||
|
||||
// apply or remove labels
|
||||
// t2d3.applyLinkLabels();
|
||||
|
||||
// operate on exiting links:
|
||||
link.exit()
|
||||
.attr('stroke-dasharray', '3 3')
|
||||
.attr('stroke', linkConfig['light'].outColor)
|
||||
.style('opacity', 0.5)
|
||||
.transition()
|
||||
.duration(1500)
|
||||
.attr({
|
||||
'stroke-dasharray': '3 12',
|
||||
'stroke-width': linkConfig.outWidth
|
||||
})
|
||||
.style('opacity', 0.0)
|
||||
.remove();
|
||||
}
|
||||
|
||||
function calcPosition() {
|
||||
var lines = this,
|
||||
linkSrcId,
|
||||
linkNums = [];
|
||||
|
||||
lines.each(function (d) {
|
||||
if (d.get('type') === 'hostLink') {
|
||||
d.set('position', getDefaultPos(d));
|
||||
}
|
||||
});
|
||||
|
||||
function normalizeLinkSrc(link) {
|
||||
// ensure source device is consistent across set of links
|
||||
// temporary measure until link modeling is refactored
|
||||
if (!linkSrcId) {
|
||||
linkSrcId = link.source.id;
|
||||
return false;
|
||||
}
|
||||
|
||||
return link.source.id !== linkSrcId;
|
||||
}
|
||||
|
||||
lines.each(function (d) {
|
||||
d.set('position', getDefaultPos(d));
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultPos(link) {
|
||||
|
||||
return {
|
||||
x1: link.get('source').x,
|
||||
y1: link.get('source').y,
|
||||
x2: link.get('target').x,
|
||||
y2: link.get('target').y
|
||||
};
|
||||
}
|
||||
|
||||
function setDimensions() {
|
||||
if (force) {
|
||||
force.size(t2vs.getDimensions());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function start() {
|
||||
force.start();
|
||||
}
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2LayoutService',
|
||||
[
|
||||
'$log', 'SvgUtilService', 'Topo2RegionService',
|
||||
'Topo2D3Service', 'Topo2ViewService',
|
||||
|
||||
function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) {
|
||||
|
||||
$log = _$log_;
|
||||
t2rs = _t2rs_;
|
||||
t2d3 = _t2d3_;
|
||||
t2vs = _t2vs_;
|
||||
sus = _sus_;
|
||||
|
||||
return {
|
||||
init: init,
|
||||
update: update,
|
||||
start: start,
|
||||
|
||||
setDimensions: setDimensions
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
})();
|
@ -22,12 +22,162 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Collection, Model;
|
||||
var Collection, Model, region, ts;
|
||||
|
||||
function createLinkCollection(data, region) {
|
||||
var widthRatio = 1.4,
|
||||
linkScale = d3.scale.linear()
|
||||
.domain([1, 12])
|
||||
.range([widthRatio, 12 * widthRatio])
|
||||
.clamp(true),
|
||||
allLinkTypes = 'direct indirect optical tunnel UiDeviceLink',
|
||||
allLinkSubTypes = 'inactive not-permitted';
|
||||
|
||||
// configuration
|
||||
var linkConfig = {
|
||||
light: {
|
||||
baseColor: '#939598',
|
||||
inColor: '#66f',
|
||||
outColor: '#f00'
|
||||
},
|
||||
dark: {
|
||||
// TODO : theme
|
||||
baseColor: '#939598',
|
||||
inColor: '#66f',
|
||||
outColor: '#f00'
|
||||
},
|
||||
inWidth: 12,
|
||||
outWidth: 10
|
||||
};
|
||||
|
||||
var defaultLinkType = 'direct',
|
||||
nearDist = 15;
|
||||
|
||||
function createLink() {
|
||||
|
||||
var linkPoints = this.linkEndPoints(this.get('epA'), this.get('epB'));
|
||||
console.log(this);
|
||||
|
||||
var attrs = angular.extend({}, linkPoints, {
|
||||
key: this.get('id'),
|
||||
class: 'link',
|
||||
weight: 1,
|
||||
srcPort: this.get('srcPort'),
|
||||
tgtPort: this.get('dstPort'),
|
||||
position: {
|
||||
x1: 0,
|
||||
y1: 0,
|
||||
x2: 0,
|
||||
y2: 0
|
||||
}
|
||||
// functions to aggregate dual link state
|
||||
// extra: link.extra
|
||||
});
|
||||
|
||||
this.set(attrs);
|
||||
}
|
||||
|
||||
function linkEndPoints(srcId, dstId) {
|
||||
|
||||
var sourceNode = this.region.get('devices').get(srcId.substring(0, srcId.length -2));
|
||||
var targetNode = this.region.get('devices').get(dstId.substring(0, dstId.length -2));
|
||||
|
||||
// var srcNode = lu[srcId],
|
||||
// dstNode = lu[dstId],
|
||||
// sMiss = !srcNode ? missMsg('src', srcId) : '',
|
||||
// dMiss = !dstNode ? missMsg('dst', dstId) : '';
|
||||
//
|
||||
// if (sMiss || dMiss) {
|
||||
// $log.error('Node(s) not on map for link:' + sMiss + dMiss);
|
||||
// //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
|
||||
// return null;
|
||||
// }
|
||||
|
||||
this.source = sourceNode.toJSON();
|
||||
this.target = targetNode.toJSON();
|
||||
|
||||
return {
|
||||
source: sourceNode,
|
||||
target: targetNode
|
||||
};
|
||||
}
|
||||
|
||||
function createLinkCollection(data, _region) {
|
||||
|
||||
var LinkModel = Model.extend({
|
||||
region: _region,
|
||||
createLink: createLink,
|
||||
linkEndPoints: linkEndPoints,
|
||||
type: function () {
|
||||
return this.get('type');
|
||||
},
|
||||
expected: function () {
|
||||
//TODO: original code is: (s && s.expected) && (t && t.expected);
|
||||
return true;
|
||||
},
|
||||
online: function () {
|
||||
return true;
|
||||
return both && (s && s.online) && (t && t.online);
|
||||
},
|
||||
linkWidth: function () {
|
||||
var s = this.get('fromSource'),
|
||||
t = this.get('fromTarget'),
|
||||
ws = (s && s.linkWidth) || 0,
|
||||
wt = (t && t.linkWidth) || 0;
|
||||
|
||||
// console.log(s);
|
||||
// TODO: Current json is missing linkWidth
|
||||
return 1.2;
|
||||
return this.get('position').multiLink ? 5 : Math.max(ws, wt);
|
||||
},
|
||||
|
||||
restyleLinkElement: function (immediate) {
|
||||
// this fn's job is to look at raw links and decide what svg classes
|
||||
// need to be applied to the line element in the DOM
|
||||
var th = ts.theme(),
|
||||
el = this.el,
|
||||
type = this.get('type'),
|
||||
lw = this.linkWidth(),
|
||||
online = this.online(),
|
||||
modeCls = this.expected() ? 'inactive' : 'not-permitted',
|
||||
delay = immediate ? 0 : 1000;
|
||||
|
||||
console.log(type);
|
||||
|
||||
// NOTE: understand why el is sometimes undefined on addLink events...
|
||||
// Investigated:
|
||||
// el is undefined when it's a reverse link that is being added.
|
||||
// updateLinks (which sets ldata.el) isn't called before this is called.
|
||||
// Calling _updateLinks in addLinkUpdate fixes it, but there might be
|
||||
// a more efficient way to fix it.
|
||||
if (el && !el.empty()) {
|
||||
el.classed('link', true);
|
||||
el.classed(allLinkSubTypes, false);
|
||||
el.classed(modeCls, !online);
|
||||
el.classed(allLinkTypes, false);
|
||||
if (type) {
|
||||
el.classed(type, true);
|
||||
}
|
||||
el.transition()
|
||||
.duration(delay)
|
||||
.attr('stroke-width', linkScale(lw))
|
||||
.attr('stroke', linkConfig[th].baseColor);
|
||||
}
|
||||
},
|
||||
|
||||
onEnter: function (el) {
|
||||
var link = d3.select(el);
|
||||
this.el = link;
|
||||
|
||||
this.restyleLinkElement();
|
||||
|
||||
if (this.get('type') === 'hostLink') {
|
||||
sus.visible(link, api.showHosts());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var LinkCollection = Collection.extend({
|
||||
model: Model
|
||||
model: LinkModel,
|
||||
});
|
||||
|
||||
return new LinkCollection(data);
|
||||
@ -35,12 +185,13 @@
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2LinkService',
|
||||
['Topo2Collection', 'Topo2Model',
|
||||
['Topo2Collection', 'Topo2Model', 'ThemeService',
|
||||
|
||||
function (_Collection_, _Model_) {
|
||||
function (_Collection_, _Model_, _ts_) {
|
||||
|
||||
ts = _ts_;
|
||||
Collection = _Collection_;
|
||||
Model = _Model_.extend({});
|
||||
Model = _Model_;
|
||||
|
||||
return {
|
||||
createLinkCollection: createLinkCollection
|
||||
|
@ -1,23 +1,23 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
ONOS GUI -- Topology Force Module.
|
||||
Visualization of the topology in an SVG layer, using a D3 Force Layout.
|
||||
*/
|
||||
ONOS GUI -- Topology Force Module.
|
||||
Visualization of the topology in an SVG layer, using a D3 Force Layout.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
@ -28,17 +28,86 @@
|
||||
this.attributes = {};
|
||||
|
||||
attrs = angular.extend({}, attrs);
|
||||
this.set(attrs);
|
||||
this.set(attrs, { silent: true });
|
||||
this.initialize.apply(this, arguments);
|
||||
}
|
||||
|
||||
Model.prototype = {
|
||||
|
||||
initialize: function () {},
|
||||
|
||||
onChange: function (property, value, options) {},
|
||||
|
||||
get: function (attr) {
|
||||
return this.attributes[attr];
|
||||
},
|
||||
|
||||
set: function(data) {
|
||||
angular.extend(this.attributes, data);
|
||||
set: function(key, val, options) {
|
||||
|
||||
if (!key) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var attributes;
|
||||
if (typeof key === 'object') {
|
||||
attributes = key;
|
||||
options = val;
|
||||
} else {
|
||||
(attributes = {})[key] = val;
|
||||
}
|
||||
|
||||
options || (options = {});
|
||||
|
||||
var unset = options.unset,
|
||||
silent = options.silent,
|
||||
changes = [],
|
||||
changing = this._changing;
|
||||
|
||||
this._changing = true;
|
||||
|
||||
if (!changing) {
|
||||
|
||||
// NOTE: angular.copy causes issues in chrome
|
||||
this._previousAttributes = Object.create(Object.getPrototypeOf(this.attributes));
|
||||
this.changed = {};
|
||||
}
|
||||
|
||||
var current = this.attributes,
|
||||
changed = this.changed,
|
||||
previous = this._previousAttributes;
|
||||
|
||||
angular.forEach(attributes, function (attribute, index) {
|
||||
|
||||
val = attribute;
|
||||
|
||||
if (!angular.equals(current[index], val)) {
|
||||
changes.push(index);
|
||||
}
|
||||
|
||||
if (!angular.equals(previous[index], val)) {
|
||||
changed[index] = val;
|
||||
} else {
|
||||
delete changed[index];
|
||||
}
|
||||
|
||||
unset ? delete current[index] : current[index] = val;
|
||||
});
|
||||
|
||||
// Trigger all relevant attribute changes.
|
||||
if (!silent) {
|
||||
if (changes.length) {
|
||||
this._pending = options;
|
||||
}
|
||||
for (var i = 0; i < changes.length; i++) {
|
||||
this.onChange(changes[i], this, current[changes[i]], options);
|
||||
}
|
||||
}
|
||||
|
||||
this._changing = false;
|
||||
return this;
|
||||
},
|
||||
toJSON: function(options) {
|
||||
return angular.copy(this.attributes)
|
||||
},
|
||||
};
|
||||
|
||||
@ -67,11 +136,11 @@
|
||||
};
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2Model',
|
||||
[
|
||||
function () {
|
||||
return Model;
|
||||
}
|
||||
]);
|
||||
.factory('Topo2Model',
|
||||
[
|
||||
function () {
|
||||
return Model;
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
||||
|
134
web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
Normal file
134
web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
ONOS GUI -- Topology Layout Module.
|
||||
Module that contains the d3.force.layout logic
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var randomService;
|
||||
var fn;
|
||||
|
||||
//internal state;
|
||||
var defaultLinkType = 'direct',
|
||||
nearDist = 15;
|
||||
|
||||
function positionNode(node, forUpdate) {
|
||||
|
||||
var meta = node.metaUi,
|
||||
x = meta && meta.x,
|
||||
y = meta && meta.y,
|
||||
dim = [800, 600],
|
||||
xy;
|
||||
|
||||
// if the device contains explicit LONG/LAT data, use that to position
|
||||
if (setLongLat(node)) {
|
||||
//indicate we want to update cached meta data...
|
||||
return true;
|
||||
}
|
||||
|
||||
// else if we have [x,y] cached in meta data, use that...
|
||||
if (x !== undefined && y !== undefined) {
|
||||
node.fixed = true;
|
||||
node.px = node.x = x;
|
||||
node.py = node.y = y;
|
||||
return;
|
||||
}
|
||||
|
||||
// if this is a node update (not a node add).. skip randomizer
|
||||
if (forUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: Placing incoming unpinned nodes at exactly the same point
|
||||
// (center of the view) causes them to explode outwards when
|
||||
// the force layout kicks in. So, we spread them out a bit
|
||||
// initially, to provide a more serene layout convergence.
|
||||
// Additionally, if the node is a host, we place it near
|
||||
// the device it is connected to.
|
||||
|
||||
function rand() {
|
||||
return {
|
||||
x: randomService.randDim(dim[0]),
|
||||
y: randomService.randDim(dim[1])
|
||||
};
|
||||
}
|
||||
|
||||
function near(node) {
|
||||
return {
|
||||
x: node.x + nearDist + randomService.spread(nearDist),
|
||||
y: node.y + nearDist + randomService.spread(nearDist)
|
||||
};
|
||||
}
|
||||
|
||||
function getDevice(cp) {
|
||||
// console.log(cp);
|
||||
// var d = lu[cp.device];
|
||||
// return d || rand();
|
||||
return rand();
|
||||
}
|
||||
|
||||
xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
|
||||
angular.extend(node, xy);
|
||||
}
|
||||
|
||||
function setLongLat(node) {
|
||||
var loc = node.location,
|
||||
coord;
|
||||
|
||||
if (loc && loc.type === 'lnglat') {
|
||||
coord = [0, 0];
|
||||
node.fixed = true;
|
||||
node.px = node.x = coord[0];
|
||||
node.py = node.y = coord[1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2NodeModel',
|
||||
['Topo2Model', 'FnService', 'RandomService',
|
||||
function (Model, _fn_, _RandomService_) {
|
||||
|
||||
randomService = _RandomService_;
|
||||
fn = _fn_;
|
||||
|
||||
return Model.extend({
|
||||
initialize: function () {
|
||||
this.node = this.createNode();
|
||||
},
|
||||
svgClassName: function () {
|
||||
return fn.classNames('node', this.nodeType, this.get('type'), {
|
||||
online: this.get('online')
|
||||
});
|
||||
},
|
||||
createNode: function () {
|
||||
|
||||
var node = angular.extend({}, this.attributes);
|
||||
|
||||
// Augment as needed...
|
||||
node.class = this.nodeType;
|
||||
node.svgClass = this.svgClassName();
|
||||
positionNode(node);
|
||||
return node;
|
||||
}
|
||||
});
|
||||
}]
|
||||
);
|
||||
})();
|
@ -24,12 +24,13 @@
|
||||
|
||||
var $log,
|
||||
wss,
|
||||
Model,
|
||||
t2sr,
|
||||
t2ds,
|
||||
t2hs,
|
||||
t2ls;
|
||||
|
||||
var regions;
|
||||
var region;
|
||||
|
||||
function init() {
|
||||
regions = {};
|
||||
@ -37,25 +38,46 @@
|
||||
|
||||
function addRegion(data) {
|
||||
|
||||
var region = {
|
||||
subregions: t2sr.createSubRegionCollection(data.subregions),
|
||||
devices: t2ds.createDeviceCollection(data.devices, data),
|
||||
hosts: t2hs.createHostCollection(data.hosts),
|
||||
links: t2ls.createLinkCollection(data.links),
|
||||
};
|
||||
region = new Model({
|
||||
id: data.id,
|
||||
layerOrder: data.layerOrder
|
||||
});
|
||||
|
||||
region.set({
|
||||
subregions: t2sr.createSubRegionCollection(data.subregions, region),
|
||||
devices: t2ds.createDeviceCollection(data.devices, region),
|
||||
hosts: t2hs.createHostCollection(data.hosts, region),
|
||||
links: t2ls.createLinkCollection(data.links, region),
|
||||
});
|
||||
|
||||
region.set('test', 2);
|
||||
|
||||
angular.forEach(region.get('links').models, function (link) {
|
||||
link.createLink();
|
||||
});
|
||||
|
||||
$log.debug('Region: ', region);
|
||||
}
|
||||
|
||||
function regionNodes() {
|
||||
return [].concat(region.get('devices').models, region.get('hosts').models);
|
||||
}
|
||||
|
||||
|
||||
function regionLinks() {
|
||||
return region.get('links').models;
|
||||
}
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2RegionService',
|
||||
['$log', 'WebSocketService', 'Topo2SubRegionService', 'Topo2DeviceService',
|
||||
['$log', 'WebSocketService', 'Topo2Model', 'Topo2SubRegionService', 'Topo2DeviceService',
|
||||
'Topo2HostService', 'Topo2LinkService',
|
||||
|
||||
function (_$log_, _wss_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) {
|
||||
function (_$log_, _wss_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) {
|
||||
|
||||
$log = _$log_;
|
||||
wss = _wss_;
|
||||
Model = _Model_
|
||||
t2sr = _t2sr_;
|
||||
t2ds = _t2ds_;
|
||||
t2hs = _t2hs_;
|
||||
@ -65,6 +87,9 @@
|
||||
init: init,
|
||||
|
||||
addRegion: addRegion,
|
||||
regionNodes: regionNodes,
|
||||
regionLinks: regionLinks,
|
||||
|
||||
getSubRegions: t2sr.getSubRegions
|
||||
};
|
||||
}]);
|
||||
|
46
web/gui/src/main/webapp/app/view/topo2/topo2View.js
Normal file
46
web/gui/src/main/webapp/app/view/topo2/topo2View.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
ONOS GUI -- Topology Layout Module.
|
||||
Module that contains the d3.force.layout logic
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var dimensions;
|
||||
|
||||
function newDim(_dimensions) {
|
||||
dimensions = _dimensions;
|
||||
}
|
||||
|
||||
function getDimensions() {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
angular.module('ovTopo2')
|
||||
.factory('Topo2ViewService',
|
||||
[
|
||||
function () {
|
||||
return {
|
||||
newDim: newDim,
|
||||
getDimensions: getDimensions
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
})();
|
@ -128,15 +128,21 @@
|
||||
<!-- Under development for Region support. -->
|
||||
<script src="app/view/topo2/topo2.js"></script>
|
||||
<script src="app/view/topo2/topo2Collection.js"></script>
|
||||
<script src="app/view/topo2/topo2D3.js"></script>
|
||||
<script src="app/view/topo2/topo2Device.js"></script>
|
||||
<script src="app/view/topo2/topo2Model.js"></script>
|
||||
<script src="app/view/topo2/topo2Event.js"></script>
|
||||
<script src="app/view/topo2/topo2Force.js"></script>
|
||||
<script src="app/view/topo2/topo2Host.js"></script>
|
||||
<script src="app/view/topo2/topo2Instance.js"></script>
|
||||
<script src="app/view/topo2/topo2Layout.js"></script>
|
||||
<script src="app/view/topo2/topo2Link.js"></script>
|
||||
<script src="app/view/topo2/topo2Model.js"></script>
|
||||
<script src="app/view/topo2/topo2NodeModel.js"></script>
|
||||
<script src="app/view/topo2/topo2Region.js"></script>
|
||||
<script src="app/view/topo2/topo2Select.js"></script>
|
||||
<script src="app/view/topo2/topo2SubRegion.js"></script>
|
||||
<script src="app/view/topo2/topo2Theme.js"></script>
|
||||
<script src="app/view/topo2/topo2View.js"></script>
|
||||
<link rel="stylesheet" href="app/view/topo2/topo2.css">
|
||||
<link rel="stylesheet" href="app/view/topo2/topo2-theme.css">
|
||||
|
||||
|
@ -216,7 +216,8 @@ describe('factory: fw/util/fn.js', function() {
|
||||
'isMobile', 'isChrome', 'isSafari', 'isFirefox',
|
||||
'debugOn', 'debug',
|
||||
'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap',
|
||||
'eecode', 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup'
|
||||
'eecode', 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup',
|
||||
'classNames'
|
||||
])).toBeTruthy();
|
||||
});
|
||||
|
||||
|
15
web/gui/src/test/_karma/package.json
Normal file
15
web/gui/src/test/_karma/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "karma",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "mockserver.js",
|
||||
"dependencies": {
|
||||
"websocket": "^1.0.23"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user