Steven Burrows

Topo2: Reset node position and unpin

Refactored NodeModel
Added class to the surrounding rect for selected class
Renamed Panels to avoid conflict with classic topo
Topo2: Details Panel for single device selection
Topo2: Added Equalize Masters keyboard shortcut
Topo2: Toggle Link Port highlighting
Topo2: Node Labels was returning empty string
       if friendly name was null
Topo2: Reset map zoom and panning

Change-Id: I0a949b2f8205e1abcfcac5aaec65c18d76e77cff
......@@ -66,7 +66,6 @@
}
function updatePrefs(data) {
$log.info('User properties updated');
cache = data;
listeners.forEach(function (lsnr) { lsnr(); });
}
......
......@@ -3,7 +3,8 @@ module.exports = {
"installedESLint": true,
"globals": {
"angular": true,
"d3": true
"d3": true,
"_": true
},
"rules": {
"brace-style": 0,
......
......@@ -181,7 +181,7 @@
}
#ov-topo2 svg .node.device.selected rect {
#ov-topo2 svg .node.device.selected .node-container {
stroke-width: 2.0;
stroke: #009fdb;
}
......
......@@ -99,3 +99,29 @@
cursor: pointer;
fill-rule: evenodd;
}
/* --- Topo Summary Panel --- */
#topo2-p-summary {
padding: 16px;
}
/* --- Topo Detail Panel --- */
#topo2-p-detail {
padding: 16px;
top: 370px;
}
html[data-platform='iPad'] #topo2-p-detail {
top: 386px;
}
#topo2-p-detail .actionBtns .actionBtn {
display: inline-block;
}
#topo2-p-detail .actionBtns .actionBtn svg {
width: 28px;
height: 28px;
}
......
......@@ -24,7 +24,7 @@
'use strict';
// references to injected services
var $scope, $log, fs, mast, ks, zs,
var $scope, $log, fs, mast, ks,
gs, sus, ps, t2es, t2fs, t2is, t2bcs, t2kcs, t2ms, t2mcs, t2zs;
// DOM elements
......@@ -81,19 +81,20 @@
angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote'])
.controller('OvTopo2Ctrl',
['$scope', '$log', '$location',
'FnService', 'MastService', 'KeyService', 'ZoomService',
'FnService', 'MastService', 'KeyService',
'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
'WebSocketService', 'PrefsService', 'ThemeService',
'Topo2EventService', 'Topo2ForceService', 'Topo2InstanceService',
'Topo2BreadcrumbService', 'Topo2KeyCommandService', 'Topo2MapService',
'Topo2MapConfigService', 'Topo2SummaryPanelService', 'Topo2ZoomService',
'Topo2MapConfigService', 'Topo2ZoomService',
'Topo2SummaryPanelService', 'Topo2DeviceDetailsPanel',
function (_$scope_, _$log_, _$loc_,
_fs_, _mast_, _ks_, _zs_,
_fs_, _mast_, _ks_,
_gs_, _ms_, _sus_, _flash_,
_wss_, _ps_, _th_,
_t2es_, _t2fs_, _t2is_, _t2bcs_, _t2kcs_, _t2ms_, _t2mcs_,
summaryPanel, _t2zs_
_t2zs_, summaryPanel, detailsPanel
) {
var params = _$loc_.search(),
......@@ -115,7 +116,6 @@
fs = _fs_;
mast = _mast_;
ks = _ks_;
zs = _zs_;
gs = _gs_;
sus = _sus_;
......@@ -156,6 +156,8 @@
ks.unbindKeys();
t2fs.destroy();
t2is.destroy();
summaryPanel.destroy();
detailsPanel.destroy();
});
// svg layer and initialization of components
......@@ -176,7 +178,7 @@
t2es.bindHandlers();
// Add the map SVG Group
t2ms.init(zoomLayer).then(
t2ms.init(zoomLayer, zoomer).then(
function (proj) {
var z = ps.getPrefs('topo_zoom', { tx: 0, ty: 0, sc: 1 });
zoomer.panZoom([z.tx, z.ty], z.sc);
......@@ -219,6 +221,7 @@
// $log.debug('registered overlays...', tov.list());
summaryPanel.init();
detailsPanel.init();
$log.log('OvTopo2Ctrl has been created');
}]);
......
......@@ -34,10 +34,10 @@
var DeviceCollection = Collection.extend({
model: Model,
comparator: function (a, b) {
var order = region.get('layerOrder'),
aLayer = a.get('layer'),
bLayer = b.get('layer');
return order.indexOf(aLayer - order.indexOf(bLayer));
// var order = region.get('layerOrder'),
// aLayer = a.get('id'),
// bLayer = b.get('layer');
// return order.indexOf(aLayer - order.indexOf(bLayer));
}
});
......@@ -54,11 +54,10 @@
return deviceCollection;
}
angular.module('ovTopo2')
.factory('Topo2DeviceService',
['Topo2Collection', 'Topo2NodeModel',
function (_c_, _nm_) {
['Topo2Collection', 'Topo2NodeModel', 'Topo2DeviceDetailsPanel',
function (_c_, _nm_, detailsPanel) {
Collection = _c_;
......@@ -67,11 +66,27 @@
this.super = this.constructor.__super__;
this.super.initialize.apply(this, arguments);
},
events: {
'click': 'onClick'
},
nodeType: 'device',
icon: function () {
var type = this.get('type');
return remappedDeviceTypes[type] || type || 'unknown';
},
onClick: function () {
if (this.get('selected')) {
this.set('selected', false);
detailsPanel.hide();
} else {
this.set('selected', true);
detailsPanel.updateDetails(this.get('id'), this.get('nodeType'));
detailsPanel.show();
}
this.el.attr('class', this.svgClassName());
},
onExit: function () {
var node = this.el;
node.select('use')
......
/*
* 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 View Module.
Module that displays the details panel for selected nodes
*/
(function () {
'use strict';
// Injected Services
var Panel, gs, wss, flash, bs, fs, ns;
// Internal State
var detailsPanel;
// configuration
var id = 'topo2-p-detail',
className = 'topo-p',
panelOpts = {
width: 260 // summary and detail panel width
},
handlerMap = {
'showDetails': showDetails
};
var coreButtons = {
showDeviceView: {
gid: 'switch',
tt: 'Show Device View',
path: 'device'
},
showFlowView: {
gid: 'flowTable',
tt: 'Show Flow View for this Device',
path: 'flow'
},
showPortView: {
gid: 'portTable',
tt: 'Show Port View for this Device',
path: 'port'
},
showGroupView: {
gid: 'groupTable',
tt: 'Show Group View for this Device',
path: 'group'
},
showMeterView: {
gid: 'meterTable',
tt: 'Show Meter View for this Device',
path: 'meter'
}
};
function init() {
bindHandlers();
var options = angular.extend({}, panelOpts, {
class: className
});
detailsPanel = new Panel(id, options);
detailsPanel.p.classed(className, true);
}
function addProp(tbody, label, value) {
var tr = tbody.append('tr'),
lab;
if (typeof label === 'string') {
lab = label.replace(/_/g, ' ');
} else {
lab = label;
}
function addCell(cls, txt) {
tr.append('td').attr('class', cls).html(txt);
}
addCell('label', lab + ' :');
addCell('value', value);
}
function addSep(tbody) {
tbody.append('tr').append('td').attr('colspan', 2).append('hr');
}
function listProps(tbody, data) {
data.propOrder.forEach(function (p) {
if (p === '-') {
addSep(tbody);
} else {
addProp(tbody, p, data.props[p]);
}
});
}
function addBtnFooter() {
detailsPanel.appendToFooter('hr');
detailsPanel.appendToFooter('div').classed('actionBtns', true);
}
function addAction(o) {
var btnDiv = d3.select('#' + id)
.select('.actionBtns')
.append('div')
.classed('actionBtn', true);
bs.button(btnDiv, id + '-' + o.id, o.gid, o.cb, o.tt);
}
function installButtons(buttons, data, devId) {
buttons.forEach(function (id) {
var btn = coreButtons[id],
gid = btn && btn.gid,
tt = btn && btn.tt,
path = btn && btn.path;
if (btn) {
addAction({
id: 'core-' + id,
gid: gid,
tt: tt,
cb: function () { ns.navTo(path, { devId: devId }); }
});
}
// else if (btn = _getButtonDef(id, data)) {
// addAction(btn);
// }
});
}
function renderSingle(data) {
detailsPanel.emptyRegions();
var svg = detailsPanel.appendToHeader('div')
.classed('icon clickable', true)
.append('svg'),
title = detailsPanel.appendToHeader('h2')
.classed('clickable', true),
table = detailsPanel.appendToBody('table'),
tbody = table.append('tbody'),
navFn;
gs.addGlyph(svg, (data.type || 'unknown'), 26);
title.text(data.title);
// // only add navigation when displaying a device
// if (isDevice[data.type]) {
// navFn = function () {
// ns.navTo(devPath, { devId: data.id });
// };
//
// svg.on('click', navFn);
// title.on('click', navFn);
// }
listProps(tbody, data);
addBtnFooter();
}
function bindHandlers() {
wss.bindHandlers(handlerMap);
}
function updateDetails(id, nodeType) {
wss.sendEvent('requestDetails', {
id: id,
class: nodeType
});
}
function showDetails(data) {
var buttons = fs.isA(data.buttons) || [];
renderSingle(data);
installButtons(buttons, data, data.id);
}
function toggle() {
var on = detailsPanel.p.toggle(),
verb = on ? 'Show' : 'Hide';
flash.flash(verb + ' Summary Panel');
}
function show() {
detailsPanel.p.show();
}
function hide() {
detailsPanel.p.hide();
}
function destroy() {
wss.unbindHandlers(handlerMap);
detailsPanel.destroy();
}
angular.module('ovTopo2')
.factory('Topo2DeviceDetailsPanel',
['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService',
'ButtonService', 'FnService', 'NavService',
function (_ps_, _gs_, _wss_, _flash_, _bs_, _fs_, _ns_) {
Panel = _ps_;
gs = _gs_;
wss = _wss_;
flash = _flash_;
bs = _bs_;
fs = _fs_;
ns = _ns_;
return {
init: init,
updateDetails: updateDetails,
toggle: toggle,
show: show,
hide: hide,
destroy: destroy
};
}
]);
})();
......@@ -190,18 +190,49 @@
// ========================== Main Service Definition
function update(elements) {
angular.forEach(elements, function (el) {
el.update();
});
}
function updateNodes() {
var allNodes = t2rs.regionNodes();
angular.forEach(allNodes, function (node) {
node.update();
update(t2rs.regionNodes());
}
function updateLinks() {
update(t2rs.regionLinks());
}
function resetAllLocations() {
var nodes = t2rs.regionNodes();
angular.forEach(nodes, function (node) {
node.resetPosition();
});
t2ls.update();
t2ls.tick();
}
function unpin() {
var hovered = t2rs.filterRegionNodes(function (model) {
return model.get('hovered');
});
angular.forEach(hovered, function (model) {
model.fixed = false;
model.el.classed('fixed', false);
});
}
angular.module('ovTopo2')
.factory('Topo2ForceService',
['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService',
'Topo2LayoutService', 'Topo2ViewService', 'Topo2BreadcrumbService', 'Topo2ZoomService',
function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_, _t2vs_, _t2bcs_, zoomService) {
['$log', 'WebSocketService', 'Topo2InstanceService',
'Topo2RegionService', 'Topo2LayoutService', 'Topo2ViewService',
'Topo2BreadcrumbService', 'Topo2ZoomService',
function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_,
_t2vs_, _t2bcs_, zoomService) {
$log = _$log_;
wss = _wss_;
......@@ -238,7 +269,10 @@
showMastership: showMastership,
topo2PeerRegions: topo2PeerRegions,
updateNodes: updateNodes
updateNodes: updateNodes,
updateLinks: updateLinks,
resetAllLocations: resetAllLocations,
unpin: unpin
};
}]);
})();
......
......@@ -17,9 +17,9 @@
(function () {
// Injected Services
var ks, t2ps, t2ms, ps, t2is, t2sp;
var ks, flash, wss, t2ps, t2ms, ps, t2is, t2sp, t2vs;
var topo2ForceService;
var t2fs;
// Commmands
var actionMap = {
......@@ -27,11 +27,16 @@
G: [openMapSelection, 'Select background geo map'],
B: [toggleMap, 'Toggle background geo map'],
I: [toggleInstancePanel, 'Toggle ONOS Instance Panel'],
O: [toggleSummary, 'Toggle the Summary Panel']
O: [toggleSummary, 'Toggle the Summary Panel'],
R: [resetZoom, 'Reset pan / zoom'],
P: [togglePorts, 'Toggle Port Highlighting'],
E: [equalizeMasters, 'Equalize mastership roles'],
X: [resetAllNodeLocations, 'Reset Node Location'],
U: [unpinNode, 'Unpin node (mouse over)']
};
function init(t2fs) {
topo2ForceService = t2fs;
function init(_t2fs_) {
t2fs = _t2fs_;
bindCommands();
}
......@@ -58,7 +63,7 @@
function cycleDeviceLabels() {
var deviceLabelIndex = t2ps.get('dlbls') + 1;
t2ps.set('dlbls', deviceLabelIndex % 3);
topo2ForceService.updateNodes();
t2fs.updateNodes();
}
function openMapSelection() {
......@@ -77,17 +82,47 @@
t2sp.toggle();
}
function resetZoom() {
t2ms.resetZoom();
flash.flash('Pan and zoom reset');
}
function togglePorts(x) {
updatePrefsState('porthl', t2vs.togglePortHighlights(x));
t2fs.updateLinks();
}
function equalizeMasters() {
wss.sendEvent('equalizeMasters');
flash.flash('Equalizing master roles');
}
function resetAllNodeLocations() {
t2fs.resetAllLocations();
flash.flash('Reset node locations');
}
function unpinNode() {
t2fs.unpin();
flash.flash('Unpin node');
}
angular.module('ovTopo2')
.factory('Topo2KeyCommandService',
['KeyService', 'Topo2PrefsService', 'Topo2MapService', 'PrefsService',
'Topo2InstanceService', 'Topo2SummaryPanelService',
function (_ks_, _t2ps_, _t2ms_, _ps_, _t2is_, _t2sp_) {
['KeyService', 'FlashService', 'WebSocketService', 'Topo2PrefsService',
'Topo2MapService', 'PrefsService', 'Topo2InstanceService',
'Topo2SummaryPanelService', 'Topo2ViewService',
function (_ks_, _flash_, _wss_, _t2ps_, _t2ms_, _ps_, _t2is_, _t2sp_, _t2vs_) {
ks = _ks_;
flash = _flash_;
wss = _wss_;
t2ps = _t2ps_;
t2ms = _t2ms_;
t2is = _t2is_;
ps = _ps_;
ks = _ks_;
t2sp = _t2sp_;
t2vs = _t2vs_;
return {
init: init,
......
......@@ -438,6 +438,7 @@
init: init,
createForceLayout: createForceLayout,
update: update,
tick: tick,
start: start,
setDimensions: setDimensions
......
......@@ -22,8 +22,7 @@
(function () {
'use strict';
var $log;
var Collection, Model, ts, sus, t2zs;
var $log, Collection, Model, ts, sus, t2zs, t2vs;
var linkLabelOffset = '0.35em';
......@@ -59,7 +58,6 @@
var attrs = angular.extend({}, linkPoints, {
key: this.get('id'),
class: 'link',
weight: 1,
srcPort: this.get('srcPort'),
tgtPort: this.get('dstPort'),
position: {
......@@ -144,6 +142,8 @@
});
this.el.classed('enhanced', true);
if (showPort()) {
point = this.locatePortLabel();
angular.extend(point, {
id: 'topo-port-tgt',
......@@ -182,6 +182,7 @@
el.attr('transform', sus.translate(d.x, d.y));
});
}
},
unenhance: function () {
this.el.classed('enhanced', false);
......@@ -248,6 +249,11 @@
setScale: function () {
var width = linkScale(widthRatio / t2zs.scale());
this.el.style('stroke-width', width + 'px');
},
update: function () {
if (this.el.classed('enhanced')) {
this.enhance();
}
}
});
......@@ -258,17 +264,23 @@
return new LinkCollection(data);
}
function showPort() {
return t2vs.getPortHighlighting();
}
angular.module('ovTopo2')
.factory('Topo2LinkService',
['$log', 'Topo2Collection', 'Topo2Model',
'ThemeService', 'SvgUtilService', 'Topo2ZoomService',
function (_$log_, _Collection_, _Model_, _ts_, _sus_, _t2zs_) {
'Topo2ViewService',
function (_$log_, _Collection_, _Model_, _ts_, _sus_,
_t2zs_, _t2vs_) {
$log = _$log_;
ts = _ts_;
sus = _sus_;
t2zs = _t2zs_;
t2vs = _t2vs_;
Collection = _Collection_;
Model = _Model_;
......
......@@ -29,13 +29,15 @@
var MapSelectionDialog;
// internal state
var mapG;
var mapG, zoomLayer, zoomer;
function init(zoomLayer) {
return setUpMap(zoomLayer);
function init(_zoomLayer_, _zoomer_) {
zoomLayer = _zoomLayer_;
zoomer = _zoomer_;
return setUpMap();
}
function setUpMap(zoomLayer) {
function setUpMap() {
var prefs = currentMap(),
mapId = prefs.mapid,
mapFilePath = prefs.mapfilepath,
......@@ -124,6 +126,10 @@
}).open();
}
function resetZoom() {
zoomer.reset();
}
angular.module('ovTopo2')
.factory('Topo2MapService',
['$location', 'PrefsService', 'MapService',
......@@ -140,7 +146,9 @@
return {
init: init,
openMapSelection: openMapSelection,
toggle: toggle
toggle: toggle,
resetZoom: resetZoom
};
}
]);
......
......@@ -15,18 +15,14 @@
*/
/*
ONOS GUI -- Topology Layout Module.
Module that contains the d3.force.layout logic
ONOS GUI -- Topology Node Module.
Module that contains model for nodes within the topology
*/
(function () {
'use strict';
var randomService, ps, sus, is, ts, t2mcs;
var fn;
// Internal state;
var nearDist = 15;
var ps, sus, is, ts, t2mcs, t2nps, fn;
var devIconDim = 36,
devIconDimMin = 20,
......@@ -56,133 +52,56 @@
dColTheme[ts.theme()][otag];
}
function positionNode(node, forUpdate) {
var meta = node.get('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) {
return rand();
}
xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
if (node.class === 'sub-region') {
xy = rand();
node.x = node.px = xy.x;
node.y = node.py = xy.y;
}
angular.extend(node, xy);
}
function setLongLat(el) {
var loc = el.get('location'),
coord;
if (loc && loc.type === 'lnglat') {
if (loc.lat === 0 && loc.lng === 0) {
return false;
}
coord = coordFromLngLat(loc);
el.fixed = true;
el.x = el.px = coord[0];
el.y = el.py = coord[1];
return true;
}
}
function coordFromLngLat(loc) {
var p = t2mcs.projection();
return p ? p([loc.lng, loc.lat]) : [0, 0];
}
angular.module('ovTopo2')
.factory('Topo2NodeModel',
['Topo2Model', 'FnService', 'RandomService', 'Topo2PrefsService',
['Topo2Model', 'FnService', 'Topo2PrefsService',
'SvgUtilService', 'IconService', 'ThemeService',
'Topo2MapConfigService', 'Topo2ZoomService',
function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_,
_t2mcs_, zoomService) {
'Topo2MapConfigService', 'Topo2ZoomService', 'Topo2NodePositionService',
function (Model, _fn_, _ps_, _sus_, _is_, _ts_,
_t2mcs_, zoomService, _t2nps_) {
randomService = _RandomService_;
ts = _ts_;
fn = _fn_;
ps = _ps_;
sus = _sus_;
is = _is_;
t2mcs = _t2mcs_;
t2nps = _t2nps_;
return Model.extend({
initialize: function () {
this.set('class', this.nodeType);
this.set('svgClass', this.svgClassName());
this.node = this.createNode();
this._events = {
'mouseover': 'mouseoverHandler',
'mouseout': 'mouseoutHandler'
};
},
createNode: function () {
this.set('class', this.nodeType);
this.set('svgClass', this.svgClassName());
positionNode(this);
t2nps.positionNode(this);
return this;
},
setUpEvents: function () {
var _this = this;
angular.forEach(this.events, function (handler, key) {
var _this = this,
events = angular.extend({}, this._events, this.events);
angular.forEach(events, function (handler, key) {
_this.el.on(key, _this[handler].bind(_this));
});
},
mouseoverHandler: function () {
this.set('hovered', true);
},
mouseoutHandler: function () {
this.set('hovered', false);
},
icon: function () {
return 'unknown';
},
label: function () {
var props = this.get('props'),
id = this.get('id'),
friendlyName = props ? props.name : id,
labels = ['', friendlyName, id],
friendlyName = props && props.name ? props.name : id,
labels = ['', friendlyName || id, id],
nli = ps.get('dlbls'),
idx = (nli < labels.length) ? nli : 0;
......@@ -197,7 +116,8 @@
return box.width + labelPad * 2;
},
addLabelElements: function (label) {
var rect = this.el.append('rect');
var rect = this.el.append('rect')
.attr('class', 'node-container');
var glythRect = this.el.append('rect')
.attr('y', -halfDevIcon)
.attr('x', -halfDevIcon)
......@@ -243,7 +163,8 @@
this.nodeType,
this.get('type'),
{
online: this.get('online')
online: this.get('online'),
selected: this.get('selected')
}
);
},
......@@ -251,6 +172,9 @@
var p = t2mcs.projection();
return p ? p.invert(coord) : [0, 0];
},
resetPosition: function () {
t2nps.setLongLat(this);
},
update: function () {
this.updateLabel();
},
......@@ -281,8 +205,8 @@
multipler = devIconDimMax / (dim * zoomService.scale());
}
this.el.selectAll('*').style('transform', 'scale(' + multipler + ')');
this.el.selectAll('*')
.style('transform', 'scale(' + multipler + ')');
},
render: function () {
var node = this.el,
......@@ -293,7 +217,8 @@
// Label
var labelElements = this.addLabelElements(label);
labelWidth = label ? this.computeLabelWidth(node) : 0;
labelElements.rect.attr(this.labelBox(devIconDim, labelWidth));
labelElements.rect
.attr(this.labelBox(devIconDim, labelWidth));
// Icon
glyph = is.addDeviceIcon(node, glyphId, devIconDim);
......
/*
* 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 Node Position Module.
Module that helps position nodes in the topology
*/
(function () {
'use strict';
// Injected vars
var rs, t2mcs;
// Internal state;
var nearDist = 15;
function positionNode(node, forUpdate) {
var meta = node.get('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: rs.randDim(dim[0]),
y: rs.randDim(dim[1])
};
}
function near(node) {
return {
x: node.x + nearDist + rs.spread(nearDist),
y: node.y + nearDist + rs.spread(nearDist)
};
}
function getDevice(cp) {
return rand();
}
xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
if (node.class === 'sub-region') {
xy = rand();
node.x = node.px = xy.x;
node.y = node.py = xy.y;
}
angular.extend(node, xy);
}
function setLongLat(el) {
var loc = el.get('location'),
coord;
if (loc && loc.type === 'lnglat') {
if (loc.lat === 0 && loc.lng === 0) {
return false;
}
coord = coordFromLngLat(loc);
el.fixed = true;
el.x = el.px = coord[0];
el.y = el.py = coord[1];
return true;
}
}
function coordFromLngLat(loc) {
var p = t2mcs.projection();
return p ? p([loc.lng, loc.lat]) : [0, 0];
}
angular.module('ovTopo2')
.factory('Topo2NodePositionService',
['RandomService',
function (_rs_, _t2mcs_) {
rs = _rs_;
t2mcs = _t2mcs_;
return {
positionNode: positionNode,
setLongLat: setLongLat
};
}
]);
})();
......@@ -29,7 +29,9 @@
this.p = ps.createPanel(this.id, options);
this.setup();
if (options.show) {
this.p.show();
}
};
Panel.prototype = {
......@@ -59,8 +61,8 @@
this.body.selectAll("*").remove();
this.footer.selectAll("*").remove();
},
destory: function () {
ps.destroy(this.id);
destroy: function () {
ps.destroyPanel(this.id);
}
};
......
......@@ -80,6 +80,11 @@
return [];
}
function filterRegionNodes(predicate) {
var nodes = regionNodes();
return _.filter(nodes, predicate);
}
function regionLinks() {
return (region) ? region.get('links').models : [];
}
......@@ -105,6 +110,7 @@
addRegion: addRegion,
regionNodes: regionNodes,
regionLinks: regionLinks,
filterRegionNodes: filterRegionNodes,
getSubRegions: t2sr.getSubRegions
};
......
......@@ -29,8 +29,12 @@
var summaryPanel, summaryData;
// configuration
var id = 'topo-p-summary',
var id = 'topo2-p-summary',
className = 'topo-p',
panelOpts = {
show: true,
width: 260 // summary and detail panel width
},
handlerMap = {
showSummary: handleSummaryData
};
......@@ -40,10 +44,12 @@
bindHandlers();
wss.sendEvent('requestSummary');
summaryPanel = new Panel(id, {
var options = angular.extend({}, panelOpts, {
class: className
});
summaryPanel = new Panel(id, options);
summaryPanel.p.classed(className, true);
}
......@@ -107,6 +113,11 @@
flash.flash(verb + ' Summary Panel');
}
function destroy() {
wss.unbindHandlers(handlerMap);
summaryPanel.destroy();
}
angular.module('ovTopo2')
.factory('Topo2SummaryPanelService',
['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService',
......@@ -120,7 +131,8 @@
return {
init: init,
toggle: toggle
toggle: toggle,
destroy: destroy
};
}
]);
......
......@@ -15,14 +15,21 @@
*/
/*
ONOS GUI -- Topology Layout Module.
Module that contains the d3.force.layout logic
ONOS GUI -- Topology View Module.
Module that contains the topology view variables
*/
(function () {
'use strict';
var dimensions;
// Injected Services
var flash;
// Internal State
var dimensions,
viewOptions = {
linkPortHighlighting: true
};
function newDim(_dimensions) {
dimensions = _dimensions;
......@@ -32,13 +39,33 @@
return dimensions;
}
function togglePortHighlights(x) {
var kev = (x === 'keyev'),
on = kev ? !viewOptions.linkPortHighlighting : Boolean(x),
what = on ? 'Enable' : 'Disable';
viewOptions.linkPortHighlighting = on;
flash.flash(what + ' port highlighting');
return on;
}
function getPortHighlighting() {
return viewOptions.linkPortHighlighting;
}
angular.module('ovTopo2')
.factory('Topo2ViewService',
[
function () {
['FlashService',
function (_flash_) {
flash = _flash_;
return {
newDim: newDim,
getDimensions: getDimensions
getDimensions: getDimensions,
togglePortHighlights: togglePortHighlights,
getPortHighlighting: getPortHighlighting
};
}
]
......
......@@ -40,6 +40,7 @@
<script src="tp/Chart.min.js"></script>
<script src="tp/angular-chart.min.js"></script>
<script src="tp/lodash.min.js"></script>
<!-- {INJECTED-USER-START} -->
<!-- {INJECTED-USER-END} -->
......@@ -132,6 +133,7 @@
<script src="app/view/topo2/topo2D3.js"></script>
<script src="app/view/topo2/topo2Dialog.js"></script>
<script src="app/view/topo2/topo2Device.js"></script>
<script src="app/view/topo2/topo2DeviceDetailsPanel.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>
......@@ -145,6 +147,7 @@
<script src="app/view/topo2/topo2MapDialog.js"></script>
<script src="app/view/topo2/topo2Model.js"></script>
<script src="app/view/topo2/topo2NodeModel.js"></script>
<script src="app/view/topo2/topo2NodePosition.js"></script>
<script src="app/view/topo2/topo2Panel.js"></script>
<script src="app/view/topo2/topo2Prefs.js"></script>
<script src="app/view/topo2/topo2Region.js"></script>
......
This diff is collapsed. Click to expand it.