mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-26 13:51:14 +01: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,
|
width: panelWidth,
|
||||||
height: panelHeight,
|
height: panelHeight,
|
||||||
isVisible: panelIsVisible,
|
isVisible: panelIsVisible,
|
||||||
|
classed: classed,
|
||||||
el: panelEl
|
el: panelEl
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,6 +147,10 @@
|
|||||||
return p.on;
|
return p.on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function classed(cls, bool) {
|
||||||
|
return p.el.classed(cls, bool);
|
||||||
|
}
|
||||||
|
|
||||||
function panelEl() {
|
function panelEl() {
|
||||||
return p.el;
|
return p.el;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
$log = _$log_;
|
$log = _$log_;
|
||||||
fs = _fs_;
|
fs = _fs_;
|
||||||
|
|
||||||
|
// TODO: change 'force' ref to be 'force.alpha' ref.
|
||||||
function createDragBehavior(force, selectCb, atDragEnd,
|
function createDragBehavior(force, selectCb, atDragEnd,
|
||||||
dragEnabled, clickEnabled) {
|
dragEnabled, clickEnabled) {
|
||||||
var draggedThreshold = d3.scale.linear()
|
var draggedThreshold = d3.scale.linear()
|
||||||
|
|||||||
@ -82,9 +82,10 @@
|
|||||||
<script src="view/topo/topo.js"></script>
|
<script src="view/topo/topo.js"></script>
|
||||||
<script src="view/topo/topoEvent.js"></script>
|
<script src="view/topo/topoEvent.js"></script>
|
||||||
<script src="view/topo/topoForce.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/topoModel.js"></script>
|
||||||
<script src="view/topo/topoPanel.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>
|
<script src="view/device/device.js"></script>
|
||||||
<!-- TODO: inject javascript refs server-side -->
|
<!-- TODO: inject javascript refs server-side -->
|
||||||
|
|
||||||
|
|||||||
@ -72,71 +72,121 @@
|
|||||||
|
|
||||||
#topo-p-summary {
|
#topo-p-summary {
|
||||||
/* Base css from panel.css */
|
/* 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;
|
display: inline-block;
|
||||||
width: 42px;
|
width: 42px;
|
||||||
height: 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;
|
position: absolute;
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
left: 50px;
|
left: 50px;
|
||||||
}
|
}
|
||||||
.light #topo-p-summary h2 {
|
.light .topo-p h2 {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
.dark #topo-p-summary h2 {
|
.dark .topo-p h2 {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topo-p-summary h3 {
|
.topo-p h3 {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
left: 50px;
|
left: 50px;
|
||||||
}
|
}
|
||||||
.light #topo-p-summary h3 {
|
.light .topo-p h3 {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
.dark #topo-p-summary h3 {
|
.dark .topo-p h3 {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topo-p-summary p, table {
|
.topo-p p, table {
|
||||||
margin: 4px 4px;
|
margin: 4px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topo-p-summary td.label {
|
.topo-p td.label {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
/* works for both light and dark themes ... */
|
/* works for both light and dark themes ... */
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topo-p-summary td.value {
|
.topo-p td.value {
|
||||||
}
|
}
|
||||||
|
|
||||||
#topo-p-summary hr {
|
.topo-p hr {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
.light #topo-p-summary hr {
|
.light .topo-p hr {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
.dark #topo-p-summary hr {
|
.dark .topo-p hr {
|
||||||
background-color: #888;
|
background-color: #888;
|
||||||
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 --- */
|
/* --- Topo Instance Panel --- */
|
||||||
|
|||||||
@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
//E: [equalizeMasters, 'Equalize mastership roles'],
|
//E: [equalizeMasters, 'Equalize mastership roles'],
|
||||||
|
|
||||||
//esc: handleEscape,
|
esc: handleEscape,
|
||||||
|
|
||||||
_helpFormat: [
|
_helpFormat: [
|
||||||
['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
|
['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
|
||||||
@ -85,12 +85,29 @@
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Keystroke functions -------------------------------------------
|
||||||
|
|
||||||
function toggleInstances() {
|
function toggleInstances() {
|
||||||
tis.toggle();
|
tis.toggle();
|
||||||
tfs.updateDeviceColors();
|
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 -----------------------------------
|
// --- Glyphs, Icons, and the like -----------------------------------
|
||||||
|
|
||||||
@ -124,10 +141,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetZoom() {
|
|
||||||
zoomer.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// callback invoked when the SVG view has been resized..
|
// callback invoked when the SVG view has been resized..
|
||||||
function svgResized(s) {
|
function svgResized(s) {
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// injected refs
|
// injected refs
|
||||||
var $log, wss, wes, tps, tis, tfs;
|
var $log, wss, wes, tps, tis, tfs, tss;
|
||||||
|
|
||||||
// internal state
|
// internal state
|
||||||
var wsock, evApis;
|
var wsock, evApis;
|
||||||
@ -37,9 +37,13 @@
|
|||||||
function bindApis() {
|
function bindApis() {
|
||||||
evApis = {
|
evApis = {
|
||||||
showSummary: tps,
|
showSummary: tps,
|
||||||
|
|
||||||
|
showDetails: tss,
|
||||||
|
|
||||||
addInstance: tis,
|
addInstance: tis,
|
||||||
updateInstance: tis,
|
updateInstance: tis,
|
||||||
removeInstance: tis,
|
removeInstance: tis,
|
||||||
|
|
||||||
addDevice: tfs,
|
addDevice: tfs,
|
||||||
updateDevice: tfs,
|
updateDevice: tfs,
|
||||||
removeDevice: tfs,
|
removeDevice: tfs,
|
||||||
@ -100,14 +104,16 @@
|
|||||||
.factory('TopoEventService',
|
.factory('TopoEventService',
|
||||||
['$log', '$location', 'WebSocketService', 'WsEventService',
|
['$log', '$location', 'WebSocketService', 'WsEventService',
|
||||||
'TopoPanelService', 'TopoInstService', 'TopoForceService',
|
'TopoPanelService', 'TopoInstService', 'TopoForceService',
|
||||||
|
'TopoSelectService',
|
||||||
|
|
||||||
function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_) {
|
function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_, _tss_) {
|
||||||
$log = _$log_;
|
$log = _$log_;
|
||||||
wss = _wss_;
|
wss = _wss_;
|
||||||
wes = _wes_;
|
wes = _wes_;
|
||||||
tps = _tps_;
|
tps = _tps_;
|
||||||
tis = _tis_;
|
tis = _tis_;
|
||||||
tfs = _tfs_;
|
tfs = _tfs_;
|
||||||
|
tss = _tss_;
|
||||||
|
|
||||||
bindApis();
|
bindApis();
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// injected refs
|
// 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
|
// configuration
|
||||||
var labelConfig = {
|
var labelConfig = {
|
||||||
@ -77,10 +77,7 @@
|
|||||||
showOffline = true, // whether offline devices are displayed
|
showOffline = true, // whether offline devices are displayed
|
||||||
oblique = false, // whether we are in the oblique view
|
oblique = false, // whether we are in the oblique view
|
||||||
nodeLock = false, // whether nodes can be dragged or not (locked)
|
nodeLock = false, // whether nodes can be dragged or not (locked)
|
||||||
dim, // the dimensions of the force layout [w,h]
|
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
|
|
||||||
|
|
||||||
// SVG elements;
|
// SVG elements;
|
||||||
var linkG, linkLabelG, nodeG;
|
var linkG, linkLabelG, nodeG;
|
||||||
@ -311,8 +308,6 @@
|
|||||||
.attr('stroke', linkConfig[th].baseColor);
|
.attr('stroke', linkConfig[th].baseColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function removeLinkElement(d) {
|
function removeLinkElement(d) {
|
||||||
var idx = fs.find(d.key, network.links, 'key'),
|
var idx = fs.find(d.key, network.links, 'key'),
|
||||||
removed;
|
removed;
|
||||||
@ -418,34 +413,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestTrafficForMode() {
|
|
||||||
$log.debug('TODO: requestTrafficForMode()...');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// === Devices and hosts - D3 rendering
|
// === 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
|
// Returns the newly computed bounding box of the rectangle
|
||||||
function adjustRectToFitText(n) {
|
function adjustRectToFitText(n) {
|
||||||
@ -568,10 +539,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function unpin() {
|
function unpin() {
|
||||||
if (hovered) {
|
var hov = tss.hovered();
|
||||||
sendUpdateMeta(hovered, true);
|
if (hov) {
|
||||||
hovered.fixed = false;
|
sendUpdateMeta(hov, true);
|
||||||
hovered.el.classed('fixed', false);
|
hov.fixed = false;
|
||||||
|
hov.el.classed('fixed', false);
|
||||||
fResume();
|
fResume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,8 +640,8 @@
|
|||||||
opacity: 0
|
opacity: 0
|
||||||
})
|
})
|
||||||
.call(drag)
|
.call(drag)
|
||||||
.on('mouseover', nodeMouseOver)
|
.on('mouseover', tss.nodeMouseOver)
|
||||||
.on('mouseout', nodeMouseOut)
|
.on('mouseout', tss.nodeMouseOut)
|
||||||
.transition()
|
.transition()
|
||||||
.attr('opacity', 1);
|
.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
|
// === 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')
|
angular.module('ovTopo')
|
||||||
.factory('TopoForceService',
|
.factory('TopoForceService',
|
||||||
['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
|
['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
|
||||||
'FlashService', 'TopoInstService', 'TopoModelService',
|
'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_;
|
$log = _$log_;
|
||||||
fs = _fs_;
|
fs = _fs_;
|
||||||
sus = _sus_;
|
sus = _sus_;
|
||||||
@ -1117,6 +1033,7 @@
|
|||||||
flash = _flash_;
|
flash = _flash_;
|
||||||
tis = _tis_;
|
tis = _tis_;
|
||||||
tms = _tms_;
|
tms = _tms_;
|
||||||
|
tss = _tss_;
|
||||||
|
|
||||||
icfg = is.iconConfig();
|
icfg = is.iconConfig();
|
||||||
|
|
||||||
@ -1131,6 +1048,7 @@
|
|||||||
$log.debug('initForce().. dim = ' + dim);
|
$log.debug('initForce().. dim = ' + dim);
|
||||||
|
|
||||||
tms.initModel(mkModelApi(uplink), dim);
|
tms.initModel(mkModelApi(uplink), dim);
|
||||||
|
tss.initSelect(mkSelectApi(uplink));
|
||||||
|
|
||||||
settings = angular.extend({}, defaultSettings, opts);
|
settings = angular.extend({}, defaultSettings, opts);
|
||||||
|
|
||||||
@ -1154,7 +1072,7 @@
|
|||||||
.on('tick', tick);
|
.on('tick', tick);
|
||||||
|
|
||||||
drag = sus.createDragBehavior(force,
|
drag = sus.createDragBehavior(force,
|
||||||
selectObject, atDragEnd, dragEnabled, clickEnabled);
|
tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
function newDim(_dim_) {
|
function newDim(_dim_) {
|
||||||
|
|||||||
@ -24,18 +24,21 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// injected refs
|
// injected refs
|
||||||
var $log, fs, rnd, api;
|
var $log, fs, rnd;
|
||||||
|
|
||||||
|
// api to topoForce
|
||||||
|
var api;
|
||||||
|
/*
|
||||||
|
projection()
|
||||||
|
network {...}
|
||||||
|
restyleLinkElement( ldata )
|
||||||
|
removeLinkElement( ldata )
|
||||||
|
*/
|
||||||
|
|
||||||
// shorthand
|
// shorthand
|
||||||
var lu, rlk, nodes, links;
|
var lu, rlk, nodes, links;
|
||||||
|
|
||||||
// api:
|
var dim; // dimensions of layout [w,h]
|
||||||
// projection: func()
|
|
||||||
// network {...}
|
|
||||||
// restyleLinkElement: func(ldata)
|
|
||||||
// removeLinkElement: func(ldata)
|
|
||||||
|
|
||||||
var dim; // dimensions of layout, as [w,h]
|
|
||||||
|
|
||||||
// configuration 'constants'
|
// configuration 'constants'
|
||||||
var defaultLinkType = 'direct',
|
var defaultLinkType = 'direct',
|
||||||
|
|||||||
@ -26,7 +26,8 @@
|
|||||||
var $log, ps, gs;
|
var $log, ps, gs;
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
var idSum = 'topo-p-summary',
|
var pCls = 'topo-p',
|
||||||
|
idSum = 'topo-p-summary',
|
||||||
idDet = 'topo-p-detail',
|
idDet = 'topo-p-detail',
|
||||||
panelOpts = {
|
panelOpts = {
|
||||||
width: 260
|
width: 260
|
||||||
@ -36,35 +37,9 @@
|
|||||||
var summaryPanel,
|
var summaryPanel,
|
||||||
detailPanel;
|
detailPanel;
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// *** SHOW SUMMARY ***
|
|
||||||
|
|
||||||
function showSummary(data) {
|
// === -----------------------------------------------------
|
||||||
populateSummary(data);
|
// Utility functions
|
||||||
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]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSep(tbody) {
|
function addSep(tbody) {
|
||||||
tbody.append('tr').append('td').attr('colspan', 2).append('hr');
|
tbody.append('tr').append('td').attr('colspan', 2).append('hr');
|
||||||
@ -80,16 +55,116 @@
|
|||||||
addCell('value', value);
|
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() {
|
function showSummaryPanel() {
|
||||||
summaryPanel.show();
|
summaryPanel.show();
|
||||||
// TODO: augment, once we have the details pane also
|
// 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() {
|
function initPanels() {
|
||||||
summaryPanel = ps.createPanel(idSum, panelOpts);
|
summaryPanel = ps.createPanel(idSum, panelOpts);
|
||||||
detailPanel = ps.createPanel(idDet, panelOpts);
|
detailPanel = ps.createPanel(idDet, panelOpts);
|
||||||
|
|
||||||
|
summaryPanel.classed(pCls, true);
|
||||||
|
detailPanel.classed(pCls, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function destroyPanels() {
|
function destroyPanels() {
|
||||||
@ -112,7 +187,18 @@
|
|||||||
return {
|
return {
|
||||||
initPanels: initPanels,
|
initPanels: initPanels,
|
||||||
destroyPanels: destroyPanels,
|
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');
|
var p = ps.createPanel('foo');
|
||||||
expect(fs.areFunctions(p, [
|
expect(fs.areFunctions(p, [
|
||||||
'show', 'hide', 'toggle', 'empty', 'append',
|
'show', 'hide', 'toggle', 'empty', 'append',
|
||||||
'width', 'height', 'isVisible', 'el'
|
'width', 'height', 'isVisible', 'classed', 'el'
|
||||||
])).toBeTruthy();
|
])).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,16 @@ describe('factory: view/topo/topoPanel.js', function() {
|
|||||||
|
|
||||||
it('should define api functions', function () {
|
it('should define api functions', function () {
|
||||||
expect(fs.areFunctions(tps, [
|
expect(fs.areFunctions(tps, [
|
||||||
'initPanels', 'destroyPanels', 'showSummary'
|
'initPanels',
|
||||||
|
'destroyPanels',
|
||||||
|
'showSummary',
|
||||||
|
'displaySingle',
|
||||||
|
'displayMulti',
|
||||||
|
'addAction',
|
||||||
|
'showDetailPanel',
|
||||||
|
'hideDetailPanel',
|
||||||
|
'detailVisible',
|
||||||
|
'summaryVisible'
|
||||||
])).toBeTruthy();
|
])).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