mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-25 22:31:07 +02:00
GUI -- TopoView - Implemented much of the node selection logic. (WIP)
- introduced topoSelect.js. Change-Id: Ic843c7d8dc2249fe0cb8c33de60dce12c07aea44
This commit is contained in:
parent
b0e66be049
commit
08f841d0fa
@ -77,6 +77,7 @@
|
||||
width: panelWidth,
|
||||
height: panelHeight,
|
||||
isVisible: panelIsVisible,
|
||||
classed: classed,
|
||||
el: panelEl
|
||||
};
|
||||
|
||||
@ -146,6 +147,10 @@
|
||||
return p.on;
|
||||
}
|
||||
|
||||
function classed(cls, bool) {
|
||||
return p.el.classed(cls, bool);
|
||||
}
|
||||
|
||||
function panelEl() {
|
||||
return p.el;
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
$log = _$log_;
|
||||
fs = _fs_;
|
||||
|
||||
// TODO: change 'force' ref to be 'force.alpha' ref.
|
||||
function createDragBehavior(force, selectCb, atDragEnd,
|
||||
dragEnabled, clickEnabled) {
|
||||
var draggedThreshold = d3.scale.linear()
|
||||
|
||||
@ -82,9 +82,10 @@
|
||||
<script src="view/topo/topo.js"></script>
|
||||
<script src="view/topo/topoEvent.js"></script>
|
||||
<script src="view/topo/topoForce.js"></script>
|
||||
<script src="view/topo/topoInst.js"></script>
|
||||
<script src="view/topo/topoModel.js"></script>
|
||||
<script src="view/topo/topoPanel.js"></script>
|
||||
<script src="view/topo/topoInst.js"></script>
|
||||
<script src="view/topo/topoSelect.js"></script>
|
||||
<script src="view/device/device.js"></script>
|
||||
<!-- TODO: inject javascript refs server-side -->
|
||||
|
||||
|
||||
@ -72,71 +72,121 @@
|
||||
|
||||
#topo-p-summary {
|
||||
/* Base css from panel.css */
|
||||
|
||||
}
|
||||
|
||||
#topo-p-summary svg {
|
||||
/* --- Topo Detail Panel --- */
|
||||
|
||||
#topo-p-detail {
|
||||
/* Base css from panel.css */
|
||||
top: 320px;
|
||||
}
|
||||
|
||||
/* --- general topo-panel styling --- */
|
||||
|
||||
.topo-p svg {
|
||||
display: inline-block;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
#topo-p-summary h2 {
|
||||
.light .topo-p svg .glyph {
|
||||
fill: #222;
|
||||
}
|
||||
|
||||
.dark .topo-p svg .glyph.overlay {
|
||||
fill: #222;
|
||||
}
|
||||
|
||||
.dark .topo-p svg .glyph {
|
||||
fill: #ddd;
|
||||
}
|
||||
.light .topo-p svg .glyph.overlay {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
|
||||
.topo-p h2 {
|
||||
position: absolute;
|
||||
margin: 0 4px;
|
||||
top: 20px;
|
||||
left: 50px;
|
||||
}
|
||||
.light #topo-p-summary h2 {
|
||||
.light .topo-p h2 {
|
||||
color: black;
|
||||
}
|
||||
.dark #topo-p-summary h2 {
|
||||
.dark .topo-p h2 {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
#topo-p-summary h3 {
|
||||
.topo-p h3 {
|
||||
margin: 0 4px;
|
||||
top: 20px;
|
||||
left: 50px;
|
||||
}
|
||||
.light #topo-p-summary h3 {
|
||||
.light .topo-p h3 {
|
||||
color: black;
|
||||
}
|
||||
.dark #topo-p-summary h3 {
|
||||
.dark .topo-p h3 {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
#topo-p-summary p, table {
|
||||
.topo-p p, table {
|
||||
margin: 4px 4px;
|
||||
}
|
||||
|
||||
#topo-p-summary td.label {
|
||||
.topo-p td.label {
|
||||
font-style: italic;
|
||||
padding-right: 12px;
|
||||
/* works for both light and dark themes ... */
|
||||
color: #777;
|
||||
}
|
||||
|
||||
#topo-p-summary td.value {
|
||||
.topo-p td.value {
|
||||
}
|
||||
|
||||
#topo-p-summary hr {
|
||||
.topo-p hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
}
|
||||
.light #topo-p-summary hr {
|
||||
.light .topo-p hr {
|
||||
background-color: #ccc;
|
||||
color: #ccc;
|
||||
}
|
||||
.dark #topo-p-summary hr {
|
||||
.dark .topo-p hr {
|
||||
background-color: #888;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
|
||||
/* --- Topo Detail Panel --- */
|
||||
.topo-p .actionBtn {
|
||||
margin: 6px 12px;
|
||||
padding: 2px 6px;
|
||||
font-size: 9pt;
|
||||
cursor: pointer;
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.light .topo-p .actionBtn {
|
||||
border: 2px solid #ddd;
|
||||
color: #eee;
|
||||
background: #888;
|
||||
}
|
||||
.dark .topo-p .actionBtn {
|
||||
border: 2px solid #222;
|
||||
color: #888;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.light .topo-p .actionBtn:hover {
|
||||
color: #eee;
|
||||
background: #444;
|
||||
}
|
||||
.dark .topo-p .actionBtn:hover {
|
||||
color: #eee;
|
||||
background: #666;
|
||||
}
|
||||
|
||||
/* TODO: add CSS rules */
|
||||
|
||||
|
||||
/* --- Topo Instance Panel --- */
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
|
||||
//E: [equalizeMasters, 'Equalize mastership roles'],
|
||||
|
||||
//esc: handleEscape,
|
||||
esc: handleEscape,
|
||||
|
||||
_helpFormat: [
|
||||
['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
|
||||
@ -85,12 +85,29 @@
|
||||
];
|
||||
}
|
||||
|
||||
// --- Keystroke functions -------------------------------------------
|
||||
|
||||
function toggleInstances() {
|
||||
tis.toggle();
|
||||
tfs.updateDeviceColors();
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
zoomer.reset();
|
||||
}
|
||||
|
||||
function handleEscape() {
|
||||
$log.debug("TODO: handle-ESCAPE...");
|
||||
// if showingAffinity: cancelAffinity
|
||||
|
||||
// else if showingDetails: deselectAll
|
||||
|
||||
// else if oiBox visible: hide oiBox
|
||||
|
||||
// else if summary panel visible: cancel Summary
|
||||
|
||||
// else: hoverMode = hoverModeNone
|
||||
}
|
||||
|
||||
// --- Glyphs, Icons, and the like -----------------------------------
|
||||
|
||||
@ -124,10 +141,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
zoomer.reset();
|
||||
}
|
||||
|
||||
|
||||
// callback invoked when the SVG view has been resized..
|
||||
function svgResized(s) {
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
'use strict';
|
||||
|
||||
// injected refs
|
||||
var $log, wss, wes, tps, tis, tfs;
|
||||
var $log, wss, wes, tps, tis, tfs, tss;
|
||||
|
||||
// internal state
|
||||
var wsock, evApis;
|
||||
@ -37,9 +37,13 @@
|
||||
function bindApis() {
|
||||
evApis = {
|
||||
showSummary: tps,
|
||||
|
||||
showDetails: tss,
|
||||
|
||||
addInstance: tis,
|
||||
updateInstance: tis,
|
||||
removeInstance: tis,
|
||||
|
||||
addDevice: tfs,
|
||||
updateDevice: tfs,
|
||||
removeDevice: tfs,
|
||||
@ -100,14 +104,16 @@
|
||||
.factory('TopoEventService',
|
||||
['$log', '$location', 'WebSocketService', 'WsEventService',
|
||||
'TopoPanelService', 'TopoInstService', 'TopoForceService',
|
||||
'TopoSelectService',
|
||||
|
||||
function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_) {
|
||||
function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_, _tss_) {
|
||||
$log = _$log_;
|
||||
wss = _wss_;
|
||||
wes = _wes_;
|
||||
tps = _tps_;
|
||||
tis = _tis_;
|
||||
tfs = _tfs_;
|
||||
tss = _tss_;
|
||||
|
||||
bindApis();
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
'use strict';
|
||||
|
||||
// injected refs
|
||||
var $log, fs, sus, is, ts, flash, tis, tms, icfg, uplink;
|
||||
var $log, fs, sus, is, ts, flash, tis, tms, tss, icfg, uplink;
|
||||
|
||||
// configuration
|
||||
var labelConfig = {
|
||||
@ -77,10 +77,7 @@
|
||||
showOffline = true, // whether offline devices are displayed
|
||||
oblique = false, // whether we are in the oblique view
|
||||
nodeLock = false, // whether nodes can be dragged or not (locked)
|
||||
dim, // the dimensions of the force layout [w,h]
|
||||
hovered, // the node over which the mouse is hovering
|
||||
selections = {}, // what is currently selected
|
||||
selectOrder = []; // the order in which we made selections
|
||||
dim; // the dimensions of the force layout [w,h]
|
||||
|
||||
// SVG elements;
|
||||
var linkG, linkLabelG, nodeG;
|
||||
@ -311,8 +308,6 @@
|
||||
.attr('stroke', linkConfig[th].baseColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function removeLinkElement(d) {
|
||||
var idx = fs.find(d.key, network.links, 'key'),
|
||||
removed;
|
||||
@ -418,34 +413,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
function requestTrafficForMode() {
|
||||
$log.debug('TODO: requestTrafficForMode()...');
|
||||
}
|
||||
|
||||
|
||||
// ==========================
|
||||
// === Devices and hosts - D3 rendering
|
||||
|
||||
function nodeMouseOver(m) {
|
||||
if (!m.dragStarted) {
|
||||
$log.debug("MouseOver()...", m);
|
||||
if (hovered != m) {
|
||||
hovered = m;
|
||||
requestTrafficForMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nodeMouseOut(m) {
|
||||
if (!m.dragStarted) {
|
||||
if (hovered) {
|
||||
hovered = null;
|
||||
requestTrafficForMode();
|
||||
}
|
||||
$log.debug("MouseOut()...", m);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns the newly computed bounding box of the rectangle
|
||||
function adjustRectToFitText(n) {
|
||||
@ -568,10 +539,11 @@
|
||||
}
|
||||
|
||||
function unpin() {
|
||||
if (hovered) {
|
||||
sendUpdateMeta(hovered, true);
|
||||
hovered.fixed = false;
|
||||
hovered.el.classed('fixed', false);
|
||||
var hov = tss.hovered();
|
||||
if (hov) {
|
||||
sendUpdateMeta(hov, true);
|
||||
hov.fixed = false;
|
||||
hov.el.classed('fixed', false);
|
||||
fResume();
|
||||
}
|
||||
}
|
||||
@ -668,8 +640,8 @@
|
||||
opacity: 0
|
||||
})
|
||||
.call(drag)
|
||||
.on('mouseover', nodeMouseOver)
|
||||
.on('mouseout', nodeMouseOut)
|
||||
.on('mouseover', tss.nodeMouseOver)
|
||||
.on('mouseout', tss.nodeMouseOut)
|
||||
.transition()
|
||||
.attr('opacity', 1);
|
||||
|
||||
@ -998,72 +970,6 @@
|
||||
}
|
||||
|
||||
|
||||
function updateDetailPanel() {
|
||||
// TODO update detail panel
|
||||
$log.debug("TODO: updateDetailPanel() ...");
|
||||
}
|
||||
|
||||
|
||||
// ==========================
|
||||
// === SELECTION / DESELECTION
|
||||
|
||||
function selectObject(obj) {
|
||||
var el = this,
|
||||
ev = d3.event.sourceEvent,
|
||||
n;
|
||||
|
||||
if (zoomingOrPanning(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el) {
|
||||
n = d3.select(el);
|
||||
} else {
|
||||
node.each(function (d) {
|
||||
if (d == obj) {
|
||||
n = d3.select(el = this);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!n) return;
|
||||
|
||||
if (ev.shiftKey && n.classed('selected')) {
|
||||
deselectObject(obj.id);
|
||||
updateDetailPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ev.shiftKey) {
|
||||
deselectAll();
|
||||
}
|
||||
|
||||
selections[obj.id] = { obj: obj, el: el };
|
||||
selectOrder.push(obj.id);
|
||||
|
||||
n.classed('selected', true);
|
||||
updateDeviceColors(obj);
|
||||
updateDetailPanel();
|
||||
}
|
||||
|
||||
function deselectObject(id) {
|
||||
var obj = selections[id];
|
||||
if (obj) {
|
||||
d3.select(obj.el).classed('selected', false);
|
||||
delete selections[id];
|
||||
fs.removeFromArray(id, selectOrder);
|
||||
updateDeviceColors(obj.obj);
|
||||
}
|
||||
}
|
||||
|
||||
function deselectAll() {
|
||||
// deselect all nodes in the network...
|
||||
node.classed('selected', false);
|
||||
selections = {};
|
||||
selectOrder = [];
|
||||
updateDeviceColors();
|
||||
updateDetailPanel();
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// === MOUSE GESTURE HANDLERS
|
||||
|
||||
@ -1103,12 +1009,22 @@
|
||||
};
|
||||
}
|
||||
|
||||
function mkSelectApi(uplink) {
|
||||
return {
|
||||
node: function () { return node; },
|
||||
zoomingOrPanning: zoomingOrPanning,
|
||||
updateDeviceColors: updateDeviceColors,
|
||||
sendEvent: uplink.sendEvent
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('ovTopo')
|
||||
.factory('TopoForceService',
|
||||
['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
|
||||
'FlashService', 'TopoInstService', 'TopoModelService',
|
||||
'TopoSelectService',
|
||||
|
||||
function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_, _tms_) {
|
||||
function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_, _tms_, _tss_) {
|
||||
$log = _$log_;
|
||||
fs = _fs_;
|
||||
sus = _sus_;
|
||||
@ -1117,6 +1033,7 @@
|
||||
flash = _flash_;
|
||||
tis = _tis_;
|
||||
tms = _tms_;
|
||||
tss = _tss_;
|
||||
|
||||
icfg = is.iconConfig();
|
||||
|
||||
@ -1131,6 +1048,7 @@
|
||||
$log.debug('initForce().. dim = ' + dim);
|
||||
|
||||
tms.initModel(mkModelApi(uplink), dim);
|
||||
tss.initSelect(mkSelectApi(uplink));
|
||||
|
||||
settings = angular.extend({}, defaultSettings, opts);
|
||||
|
||||
@ -1154,7 +1072,7 @@
|
||||
.on('tick', tick);
|
||||
|
||||
drag = sus.createDragBehavior(force,
|
||||
selectObject, atDragEnd, dragEnabled, clickEnabled);
|
||||
tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
|
||||
}
|
||||
|
||||
function newDim(_dim_) {
|
||||
|
||||
@ -24,18 +24,21 @@
|
||||
'use strict';
|
||||
|
||||
// injected refs
|
||||
var $log, fs, rnd, api;
|
||||
var $log, fs, rnd;
|
||||
|
||||
// api to topoForce
|
||||
var api;
|
||||
/*
|
||||
projection()
|
||||
network {...}
|
||||
restyleLinkElement( ldata )
|
||||
removeLinkElement( ldata )
|
||||
*/
|
||||
|
||||
// shorthand
|
||||
var lu, rlk, nodes, links;
|
||||
|
||||
// api:
|
||||
// projection: func()
|
||||
// network {...}
|
||||
// restyleLinkElement: func(ldata)
|
||||
// removeLinkElement: func(ldata)
|
||||
|
||||
var dim; // dimensions of layout, as [w,h]
|
||||
var dim; // dimensions of layout [w,h]
|
||||
|
||||
// configuration 'constants'
|
||||
var defaultLinkType = 'direct',
|
||||
|
||||
@ -26,7 +26,8 @@
|
||||
var $log, ps, gs;
|
||||
|
||||
// constants
|
||||
var idSum = 'topo-p-summary',
|
||||
var pCls = 'topo-p',
|
||||
idSum = 'topo-p-summary',
|
||||
idDet = 'topo-p-detail',
|
||||
panelOpts = {
|
||||
width: 260
|
||||
@ -36,35 +37,9 @@
|
||||
var summaryPanel,
|
||||
detailPanel;
|
||||
|
||||
// ==========================
|
||||
// *** SHOW SUMMARY ***
|
||||
|
||||
function showSummary(data) {
|
||||
populateSummary(data);
|
||||
showSummaryPanel();
|
||||
}
|
||||
|
||||
function populateSummary(data) {
|
||||
summaryPanel.empty();
|
||||
|
||||
var svg = summaryPanel.append('svg'),
|
||||
title = summaryPanel.append('h2'),
|
||||
table = summaryPanel.append('table'),
|
||||
tbody = table.append('tbody');
|
||||
|
||||
gs.addGlyph(svg, 'node', 40);
|
||||
gs.addGlyph(svg, 'bird', 24, true, [8,12]);
|
||||
|
||||
title.text(data.id);
|
||||
|
||||
data.propOrder.forEach(function(p) {
|
||||
if (p === '-') {
|
||||
addSep(tbody);
|
||||
} else {
|
||||
addProp(tbody, p, data.props[p]);
|
||||
}
|
||||
});
|
||||
}
|
||||
// === -----------------------------------------------------
|
||||
// Utility functions
|
||||
|
||||
function addSep(tbody) {
|
||||
tbody.append('tr').append('td').attr('colspan', 2).append('hr');
|
||||
@ -80,16 +55,116 @@
|
||||
addCell('value', value);
|
||||
}
|
||||
|
||||
function listProps(tbody, data) {
|
||||
data.propOrder.forEach(function(p) {
|
||||
if (p === '-') {
|
||||
addSep(tbody);
|
||||
} else {
|
||||
addProp(tbody, p, data.props[p]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dpa(x) {
|
||||
return detailPanel.append(x);
|
||||
}
|
||||
|
||||
function spa(x) {
|
||||
return summaryPanel.append(x);
|
||||
}
|
||||
|
||||
// === -----------------------------------------------------
|
||||
// Functions for populating the summary panel
|
||||
|
||||
function populateSummary(data) {
|
||||
summaryPanel.empty();
|
||||
|
||||
var svg = spa('svg'),
|
||||
title = spa('h2'),
|
||||
table = spa('table'),
|
||||
tbody = table.append('tbody');
|
||||
|
||||
gs.addGlyph(svg, 'node', 40);
|
||||
gs.addGlyph(svg, 'bird', 24, true, [8,12]);
|
||||
|
||||
title.text(data.id);
|
||||
listProps(tbody, data);
|
||||
}
|
||||
|
||||
// === -----------------------------------------------------
|
||||
// Functions for populating the detail panel
|
||||
|
||||
function displaySingle(data) {
|
||||
detailPanel.empty();
|
||||
|
||||
var svg = dpa('svg'),
|
||||
title = dpa('h2'),
|
||||
table = dpa('table'),
|
||||
tbody = table.append('tbody');
|
||||
|
||||
gs.addGlyph(svg, (data.type || 'unknown'), 40);
|
||||
title.text(data.id);
|
||||
listProps(tbody, data);
|
||||
dpa('hr');
|
||||
}
|
||||
|
||||
function displayMulti(ids) {
|
||||
detailPanel.empty();
|
||||
|
||||
var title = dpa('h3'),
|
||||
table = dpa('table'),
|
||||
tbody = table.append('tbody');
|
||||
|
||||
title.text('Selected Nodes');
|
||||
ids.forEach(function (d, i) {
|
||||
addProp(tbody, i+1, d);
|
||||
});
|
||||
dpa('hr');
|
||||
}
|
||||
|
||||
function addAction(text, cb) {
|
||||
dpa('div')
|
||||
.classed('actionBtn', true)
|
||||
.text(text)
|
||||
.on('click', cb);
|
||||
}
|
||||
|
||||
// === -----------------------------------------------------
|
||||
// Event Handlers
|
||||
|
||||
function showSummary(data) {
|
||||
populateSummary(data);
|
||||
showSummaryPanel();
|
||||
}
|
||||
|
||||
|
||||
// === -----------------------------------------------------
|
||||
// === LOGIC For showing/hiding summary and detail panels...
|
||||
|
||||
function showSummaryPanel() {
|
||||
summaryPanel.show();
|
||||
// TODO: augment, once we have the details pane also
|
||||
}
|
||||
|
||||
function showDetailPanel() {
|
||||
// TODO: augment with summary-accomodation-logic
|
||||
detailPanel.show();
|
||||
}
|
||||
|
||||
function hideDetailPanel() {
|
||||
detailPanel.hide();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
|
||||
function initPanels() {
|
||||
summaryPanel = ps.createPanel(idSum, panelOpts);
|
||||
detailPanel = ps.createPanel(idDet, panelOpts);
|
||||
|
||||
summaryPanel.classed(pCls, true);
|
||||
detailPanel.classed(pCls, true);
|
||||
}
|
||||
|
||||
function destroyPanels() {
|
||||
@ -112,7 +187,18 @@
|
||||
return {
|
||||
initPanels: initPanels,
|
||||
destroyPanels: destroyPanels,
|
||||
showSummary: showSummary
|
||||
|
||||
showSummary: showSummary,
|
||||
|
||||
displaySingle: displaySingle,
|
||||
displayMulti: displayMulti,
|
||||
addAction: addAction,
|
||||
|
||||
showDetailPanel: showDetailPanel,
|
||||
hideDetailPanel: hideDetailPanel,
|
||||
|
||||
detailVisible: function () { return detailPanel.isVisible(); },
|
||||
summaryVisible: function () { return summaryPanel.isVisible(); }
|
||||
};
|
||||
}]);
|
||||
}());
|
||||
|
||||
293
web/gui/src/main/webapp/app/view/topo/topoSelect.js
Normal file
293
web/gui/src/main/webapp/app/view/topo/topoSelect.js
Normal file
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 2015 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 Selection Module.
|
||||
Defines behavior when selecting nodes.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// injected refs
|
||||
var $log, fs, tps;
|
||||
|
||||
// api to topoForce
|
||||
var api;
|
||||
/*
|
||||
node() // get ref to D3 selection of nodes
|
||||
zoomingOrPanning( ev )
|
||||
updateDeviceColors( [dev] )
|
||||
sendEvent( type, {payload} )
|
||||
*/
|
||||
|
||||
// internal state
|
||||
var hovered, // the node over which the mouse is hovering
|
||||
selections = {}, // currently selected nodes (by id)
|
||||
selectOrder = [], // the order in which we made selections
|
||||
haveDetails = false, // do we have details of one or more nodes?
|
||||
useDetails = true; // should we show details if we have 'em?
|
||||
|
||||
// ==========================
|
||||
|
||||
function nSel() {
|
||||
return selectOrder.length;
|
||||
}
|
||||
function getSel(idx) {
|
||||
return selections[selectOrder[idx]];
|
||||
}
|
||||
function allSelectionsClass(cls) {
|
||||
for (var i=0, n=nSel(); i<n; i++) {
|
||||
if (getSel(i).obj.class !== cls) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==========================
|
||||
|
||||
function nodeMouseOver(m) {
|
||||
if (!m.dragStarted) {
|
||||
$log.debug("MouseOver()...", m);
|
||||
if (hovered != m) {
|
||||
hovered = m;
|
||||
requestTrafficForMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nodeMouseOut(m) {
|
||||
if (!m.dragStarted) {
|
||||
if (hovered) {
|
||||
hovered = null;
|
||||
requestTrafficForMode();
|
||||
}
|
||||
$log.debug("MouseOut()...", m);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================
|
||||
|
||||
function selectObject(obj) {
|
||||
var el = this,
|
||||
ev = d3.event.sourceEvent,
|
||||
n;
|
||||
|
||||
if (api.zoomingOrPanning(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el) {
|
||||
n = d3.select(el);
|
||||
} else {
|
||||
api.node().each(function (d) {
|
||||
if (d == obj) {
|
||||
n = d3.select(el = this);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!n) return;
|
||||
|
||||
if (ev.shiftKey && n.classed('selected')) {
|
||||
deselectObject(obj.id);
|
||||
updateDetail();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ev.shiftKey) {
|
||||
deselectAll();
|
||||
}
|
||||
|
||||
selections[obj.id] = { obj: obj, el: el };
|
||||
selectOrder.push(obj.id);
|
||||
|
||||
n.classed('selected', true);
|
||||
api.updateDeviceColors(obj);
|
||||
updateDetail();
|
||||
|
||||
debugSel();
|
||||
}
|
||||
|
||||
function deselectObject(id) {
|
||||
var obj = selections[id];
|
||||
if (obj) {
|
||||
d3.select(obj.el).classed('selected', false);
|
||||
delete selections[id];
|
||||
fs.removeFromArray(id, selectOrder);
|
||||
api.updateDeviceColors(obj.obj);
|
||||
}
|
||||
|
||||
debugSel();
|
||||
}
|
||||
|
||||
function deselectAll() {
|
||||
// deselect all nodes in the network...
|
||||
api.node().classed('selected', false);
|
||||
selections = {};
|
||||
selectOrder = [];
|
||||
api.updateDeviceColors();
|
||||
updateDetail();
|
||||
|
||||
debugSel();
|
||||
}
|
||||
|
||||
function debugSel() {
|
||||
$log.debug(' ..... Selected now >> ', selectOrder);
|
||||
}
|
||||
|
||||
// === -----------------------------------------------------
|
||||
|
||||
function requestDetails() {
|
||||
var data = getSel(0).obj;
|
||||
api.sendEvent('requestDetails', {
|
||||
id: data.id,
|
||||
class: data.class
|
||||
});
|
||||
}
|
||||
|
||||
// === -----------------------------------------------------
|
||||
|
||||
function updateDetail() {
|
||||
var nSel = selectOrder.length;
|
||||
if (!nSel) {
|
||||
emptySelect();
|
||||
} else if (nSel === 1) {
|
||||
singleSelect();
|
||||
} else {
|
||||
multiSelect();
|
||||
}
|
||||
}
|
||||
|
||||
function emptySelect() {
|
||||
haveDetails = false;
|
||||
tps.hideDetailPanel();
|
||||
cancelTraffic();
|
||||
}
|
||||
|
||||
function singleSelect() {
|
||||
// NOTE: detail is shown from 'showDetails' event callback
|
||||
requestDetails();
|
||||
cancelTraffic();
|
||||
requestTrafficForMode();
|
||||
}
|
||||
|
||||
function multiSelect() {
|
||||
haveDetails = true;
|
||||
|
||||
// display the selected nodes in the detail panel
|
||||
tps.displayMulti(selectOrder);
|
||||
|
||||
// always add the 'show traffic' action
|
||||
tps.addAction('Show Related Traffic', showRelatedIntentsAction);
|
||||
|
||||
// add other actions, based on what is selected...
|
||||
if (nSel() === 2 && allSelectionsClass('host')) {
|
||||
tps.addAction('Create Host-to-Host Flow', addHostIntentAction);
|
||||
} else if (nSel() >= 2 && allSelectionsClass('host')) {
|
||||
tps.addAction('Create Multi-Source Flow', addMultiSourceIntentAction);
|
||||
}
|
||||
|
||||
cancelTraffic();
|
||||
requestTrafficForMode();
|
||||
}
|
||||
|
||||
|
||||
// === -----------------------------------------------------
|
||||
// Event Handlers
|
||||
|
||||
function showDetails(data) {
|
||||
haveDetails = true;
|
||||
|
||||
// display the data for the single selected node
|
||||
tps.displaySingle(data);
|
||||
|
||||
// always add the 'show traffic' action
|
||||
tps.addAction('Show Related Traffic', showRelatedIntentsAction);
|
||||
|
||||
// add other actions, based on what is selected...
|
||||
if (data.type === 'switch') {
|
||||
tps.addAction('Show Device Flows', showDeviceLinkFlowsAction);
|
||||
}
|
||||
|
||||
// only show the details panel if the user hasn't "hidden" it
|
||||
if (useDetails) {
|
||||
tps.showDetailPanel();
|
||||
}
|
||||
}
|
||||
|
||||
// === -----------------------------------------------------
|
||||
// TODO: migrate these to topoTraffic.js
|
||||
|
||||
function cancelTraffic() {
|
||||
$log.debug('TODO: cancelTraffic');
|
||||
|
||||
}
|
||||
function requestTrafficForMode() {
|
||||
$log.debug('TODO: requestTrafficForMode');
|
||||
|
||||
}
|
||||
function showRelatedIntentsAction () {
|
||||
$log.debug('TODO: showRelatedIntentsAction');
|
||||
|
||||
}
|
||||
function addHostIntentAction () {
|
||||
$log.debug('TODO: addHostIntentAction');
|
||||
|
||||
}
|
||||
function addMultiSourceIntentAction () {
|
||||
$log.debug('TODO: addMultiSourceIntentAction');
|
||||
|
||||
}
|
||||
function showDeviceLinkFlowsAction () {
|
||||
$log.debug('TODO: showDeviceLinkFlowsAction');
|
||||
|
||||
}
|
||||
|
||||
|
||||
// === -----------------------------------------------------
|
||||
// === MODULE DEFINITION ===
|
||||
|
||||
angular.module('ovTopo')
|
||||
.factory('TopoSelectService',
|
||||
['$log', 'FnService', 'TopoPanelService',
|
||||
|
||||
function (_$log_, _fs_, _tps_) {
|
||||
$log = _$log_;
|
||||
fs = _fs_;
|
||||
tps = _tps_;
|
||||
|
||||
function initSelect(_api_) {
|
||||
api = _api_;
|
||||
}
|
||||
|
||||
function destroySelect() { }
|
||||
|
||||
return {
|
||||
initSelect: initSelect,
|
||||
destroySelect: destroySelect,
|
||||
|
||||
showDetails: showDetails,
|
||||
|
||||
nodeMouseOver: nodeMouseOver,
|
||||
nodeMouseOut: nodeMouseOut,
|
||||
selectObject: selectObject,
|
||||
deselectObject: deselectObject,
|
||||
deselectAll: deselectAll,
|
||||
hovered: function () { return hovered; }
|
||||
};
|
||||
}]);
|
||||
}());
|
||||
@ -88,7 +88,7 @@ describe('factory: fw/layer/panel.js', function () {
|
||||
var p = ps.createPanel('foo');
|
||||
expect(fs.areFunctions(p, [
|
||||
'show', 'hide', 'toggle', 'empty', 'append',
|
||||
'width', 'height', 'isVisible', 'el'
|
||||
'width', 'height', 'isVisible', 'classed', 'el'
|
||||
])).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@ -34,7 +34,16 @@ describe('factory: view/topo/topoPanel.js', function() {
|
||||
|
||||
it('should define api functions', function () {
|
||||
expect(fs.areFunctions(tps, [
|
||||
'initPanels', 'destroyPanels', 'showSummary'
|
||||
'initPanels',
|
||||
'destroyPanels',
|
||||
'showSummary',
|
||||
'displaySingle',
|
||||
'displayMulti',
|
||||
'addAction',
|
||||
'showDetailPanel',
|
||||
'hideDetailPanel',
|
||||
'detailVisible',
|
||||
'summaryVisible'
|
||||
])).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2015 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 -- Topo View -- Topo Selection Service - Unit Tests
|
||||
*/
|
||||
describe('factory: view/topo/topoSelect.js', function() {
|
||||
var $log, fs, tss;
|
||||
|
||||
beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
|
||||
|
||||
beforeEach(inject(function (_$log_, FnService, TopoSelectService) {
|
||||
$log = _$log_;
|
||||
fs = FnService;
|
||||
tss = TopoSelectService;
|
||||
}));
|
||||
|
||||
it('should define TopoSelectService', function () {
|
||||
expect(tss).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define api functions', function () {
|
||||
expect(fs.areFunctions(tss, [
|
||||
'initSelect', 'destroySelect', 'showDetails',
|
||||
'nodeMouseOver', 'nodeMouseOut', 'selectObject', 'deselectObject',
|
||||
'deselectAll', 'hovered'
|
||||
])).toBeTruthy();
|
||||
});
|
||||
|
||||
// TODO: more tests...
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user