Simon Hunt

GUI -- Implemented Show/Hide Offline devices & Show/Hide Hosts (also used Flash Service).

- added 'toggle(cb)' to panel API.
- deferred keybindings to allow direct reference to sub-API functions.
- re-implemented tick() function.
- added 'list scenarios' command to mockserver.

Change-Id: I1cc0009266e1015747b1d8106bd1f088adb2feb5
......@@ -71,6 +71,7 @@
api = {
show: showPanel,
hide: hidePanel,
toggle: togglePanel,
empty: emptyPanel,
append: appendPanel,
width: panelWidth,
......@@ -111,6 +112,14 @@
.style('opacity', 0);
}
function togglePanel(cb) {
if (p.on) {
hidePanel(cb);
} else {
showPanel(cb);
}
}
function emptyPanel() {
return p.el.html('');
}
......
......@@ -141,7 +141,7 @@
lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
darkNorm = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'],
darkMute = ['#16203A', '#281810', '#4F1206', '#00331C', '#3D063A', '#002D2D', '#1B4400'];
darkMute = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'];
var colors= {
light: {
......
......@@ -36,73 +36,59 @@
// Internal state
var zoomer;
// Note: "exported" state should be properties on 'self' variable
// --- Short Cut Keys ------------------------------------------------
var keyBindings = {
//O: [toggleSummary, 'Toggle ONOS summary pane'],
I: [toggleInstances, 'Toggle ONOS instances pane'],
//D: [toggleDetails, 'Disable / enable details pane'],
//H: [toggleHosts, 'Toggle host visibility'],
//M: [toggleOffline, 'Toggle offline visibility'],
//B: [toggleBg, 'Toggle background image'],
//P: togglePorts,
//X: [toggleNodeLock, 'Lock / unlock node positions'],
//Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
L: [cycleLabels, 'Cycle device labels'],
//U: [unpin, 'Unpin node (hover mouse over)'],
R: [resetZoom, 'Reset pan / zoom'],
//V: [showRelatedIntentsAction, 'Show all related intents'],
//rightArrow: [showNextIntentAction, 'Show next related intent'],
//leftArrow: [showPrevIntentAction, 'Show previous related intent'],
//W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
//A: [showAllTrafficAction, 'Monitor all traffic'],
//F: [showDeviceLinkFlowsAction, 'Show device link flows'],
//E: [equalizeMasters, 'Equalize mastership roles'],
//esc: handleEscape,
_helpFormat: [
['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
['X', 'Z', 'L', 'U', 'R' ],
['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
]
};
// mouse gestures
var gestures = [
['click', 'Select the item and show details'],
['shift-click', 'Toggle selection state'],
['drag', 'Reposition (and pin) device / host'],
['cmd-scroll', 'Zoom in / out'],
['cmd-drag', 'Pan']
];
function setUpKeys() {
// key bindings need to be made after the services have been injected
// thus, deferred to here...
ks.keyBindings({
//O: [toggleSummary, 'Toggle ONOS summary pane'],
I: [toggleInstances, 'Toggle ONOS instances pane'],
//D: [toggleDetails, 'Disable / enable details pane'],
H: [tfs.toggleHosts, 'Toggle host visibility'],
M: [tfs.toggleOffline, 'Toggle offline visibility'],
//B: [toggleBg, 'Toggle background image'],
//P: togglePorts,
//X: [toggleNodeLock, 'Lock / unlock node positions'],
//Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
L: [tfs.cycleDeviceLabels, 'Cycle device labels'],
//U: [unpin, 'Unpin node (hover mouse over)'],
R: [resetZoom, 'Reset pan / zoom'],
//V: [showRelatedIntentsAction, 'Show all related intents'],
//rightArrow: [showNextIntentAction, 'Show next related intent'],
//leftArrow: [showPrevIntentAction, 'Show previous related intent'],
//W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
//A: [showAllTrafficAction, 'Monitor all traffic'],
//F: [showDeviceLinkFlowsAction, 'Show device link flows'],
//E: [equalizeMasters, 'Equalize mastership roles'],
//esc: handleEscape,
_helpFormat: [
['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
['X', 'Z', 'L', 'U', 'R' ],
['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
]
});
function toggleInstances() {
if (tis.isVisible()) {
tis.hide();
} else {
tis.show();
}
tfs.updateDeviceColors();
// TODO: // mouse gestures
var gestures = [
['click', 'Select the item and show details'],
['shift-click', 'Toggle selection state'],
['drag', 'Reposition (and pin) device / host'],
['cmd-scroll', 'Zoom in / out'],
['cmd-drag', 'Pan']
];
}
function cycleLabels() {
$log.debug('Cycle Labels.....');
}
function resetZoom() {
zoomer.reset();
}
function setUpKeys() {
ks.keyBindings(keyBindings);
function toggleInstances() {
tis.toggle();
tfs.updateDeviceColors();
}
......@@ -122,8 +108,7 @@
}
function zoomCallback() {
var tr = zoomer.translate(),
sc = zoomer.scale();
var sc = zoomer.scale();
// keep the map lines constant width while zooming
mapG.style('stroke-width', (2.0 / sc) + 'px');
......@@ -139,6 +124,10 @@
});
}
function resetZoom() {
zoomer.reset();
}
// callback invoked when the SVG view has been resized..
function svgResized(dim) {
......
......@@ -23,9 +23,7 @@
'use strict';
// injected refs
var $log, fs, sus, is, ts, tis, uplink;
var icfg;
var $log, fs, sus, is, ts, flash, tis, icfg, uplink;
// configuration
var labelConfig = {
......@@ -53,9 +51,9 @@
outColor: '#f00',
},
dark: {
baseColor: '#666',
baseColor: '#aaa',
inColor: '#66f',
outColor: '#f00',
outColor: '#f66'
},
inWidth: 12,
outWidth: 10
......@@ -74,7 +72,9 @@
lu = network.lookup, // shorthand
deviceLabelIndex = 0, // for device label cycling
hostLabelIndex = 0, // for host label cycling
showHosts = 1, // whether hosts are displayed
showHosts = true, // whether hosts are displayed
showOffline = true, // whether offline devices are displayed
oblique = false, // whether we are in the oblique view
width, height;
// SVG elements;
......@@ -150,9 +150,8 @@
}
updateNodes();
if (wasOnline !== d.online) {
// TODO: re-instate link update, and offline visibility
//findAttachedLinks(d.id).forEach(restyleLinkElement);
//updateOfflineVisibility(d);
findAttachedLinks(d.id).forEach(restyleLinkElement);
updateOfflineVisibility(d);
}
} else {
// TODO: decide whether we want to capture logic errors
......@@ -338,9 +337,8 @@
linkScale = d3.scale.linear()
.domain([1, 12])
.range([widthRatio, 12 * widthRatio])
.clamp(true);
var allLinkTypes = 'direct indirect optical tunnel',
.clamp(true),
allLinkTypes = 'direct indirect optical tunnel',
defaultLinkType = 'direct';
function restyleLinkElement(ldata) {
......@@ -364,6 +362,12 @@
.attr('stroke', linkConfig[th].baseColor);
}
function findLinkById(id) {
// check to see if this is a reverse lookup, else default to given id
var key = network.revLinkToKey[id] || id;
return key && lu[key];
}
function findLink(linkData, op) {
var key = makeLinkKey(linkData),
keyrev = makeLinkKey(linkData, 1),
......@@ -436,6 +440,15 @@
return result;
}
function findOfflineNodes() {
var a = [];
network.nodes.forEach(function (d) {
if (d.class === 'device' && !d.online) {
a.push(d);
}
});
return a;
}
function findAttachedHosts(devId) {
var hosts = [];
......@@ -513,6 +526,36 @@
fResume();
}
function updateHostVisibility() {
sus.makeVisible(nodeG.selectAll('.host'), showHosts);
sus.makeVisible(linkG.selectAll('.hostLink'), showHosts);
}
function updateOfflineVisibility(dev) {
function updDev(d, show) {
sus.makeVisible(d.el, show);
findAttachedLinks(d.id).forEach(function (link) {
b = show && ((link.type() !== 'hostLink') || showHosts);
sus.makeVisible(link.el, b);
});
findAttachedHosts(d.id).forEach(function (host) {
b = show && showHosts;
sus.makeVisible(host.el, b);
});
}
if (dev) {
// updating a specific device that just toggled off/on-line
updDev(dev, dev.online || showOffline);
} else {
// updating all offline devices
findOfflineNodes().forEach(function (d) {
updDev(d, showOffline);
});
}
}
function sendUpdateMeta(d, store) {
var metaUi = {},
......@@ -536,16 +579,6 @@
}
function fStart() {
$log.debug('TODO fStart()...');
// TODO...
}
function fResume() {
$log.debug('TODO fResume()...');
// TODO...
}
// ==========================
// === Devices and hosts - helper functions
......@@ -817,6 +850,28 @@
}
}
function vis(b) {
return b ? 'visible' : 'hidden';
}
function toggleHosts() {
showHosts = !showHosts;
updateHostVisibility();
flash.flash('Hosts ' + vis(showHosts));
}
function toggleOffline() {
showOffline = !showOffline;
updateOfflineVisibility();
flash.flash('Offline devices ' + vis(showOffline));
}
function cycleDeviceLabels() {
// TODO cycle device labels
}
// ==========================================
var dCol = {
black: '#000',
paleblue: '#acf',
......@@ -1070,12 +1125,12 @@
// operate on exiting links:
link.exit()
.attr('stroke-dasharray', '3 3')
.attr('stroke', linkConfig[th].outColor)
.style('opacity', 0.5)
.transition()
.duration(1500)
.attr({
'stroke-dasharray': '3 12',
stroke: linkConfig[th].outColor,
'stroke-width': linkConfig.outWidth
})
.style('opacity', 0.0)
......@@ -1084,7 +1139,7 @@
// NOTE: invoke a single tick to force the labels to position
// onto their links.
tick();
// FIXME: this is a bug when in oblique view
// TODO: this causes undesirable behavior when in oblique view
// It causes the nodes to jump into "overhead" view positions, even
// though the oblique planes are still showing...
}
......@@ -1191,14 +1246,55 @@
// ==========================
// force layout tick function
function tick() {
function fResume() {
if (!oblique) {
force.resume();
}
}
function fStart() {
if (!oblique) {
force.start();
}
}
var tickStuff = {
nodeAttr: {
transform: function (d) { return sus.translate(d.x, d.y); }
},
linkAttr: {
x1: function (d) { return d.source.x; },
y1: function (d) { return d.source.y; },
x2: function (d) { return d.target.x; },
y2: function (d) { return d.target.y; }
},
linkLabelAttr: {
transform: function (d) {
var lnk = findLinkById(d.key);
if (lnk) {
return transformLabel({
x1: lnk.source.x,
y1: lnk.source.y,
x2: lnk.target.x,
y2: lnk.target.y
});
}
}
}
};
function tick() {
node.attr(tickStuff.nodeAttr);
link.attr(tickStuff.linkAttr);
linkLabel.attr(tickStuff.linkLabelAttr);
}
// ==========================
// === MOUSE GESTURE HANDLERS
// FIXME:
function selectCb() { }
function atDragEnd() {}
function dragEnabled() {}
......@@ -1211,14 +1307,15 @@
angular.module('ovTopo')
.factory('TopoForceService',
['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
'TopoInstService',
'FlashService', 'TopoInstService',
function (_$log_, _fs_, _sus_, _is_, _ts_, _tis_) {
function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_) {
$log = _$log_;
fs = _fs_;
sus = _sus_;
is = _is_;
ts = _ts_;
flash = _flash_;
tis = _tis_;
icfg = is.iconConfig();
......@@ -1270,6 +1367,9 @@
resize: resize,
updateDeviceColors: updateDeviceColors,
toggleHosts: toggleHosts,
toggleOffline: toggleOffline,
cycleDeviceLabels: cycleDeviceLabels,
addDevice: addDevice,
updateDevice: updateDevice,
......
......@@ -55,7 +55,6 @@
// ==========================
// *** ADD INSTANCE ***
function addInstance(data) {
var id = data.id;
......@@ -330,7 +329,8 @@
isVisible: function () { return oiBox.isVisible(); },
show: function () { oiBox.show(); },
hide: function () { oiBox.hide(); }
hide: function () { oiBox.hide(); },
toggle: function () { oiBox.toggle(); }
};
}]);
}());
......
......@@ -87,7 +87,8 @@ describe('factory: fw/layer/panel.js', function () {
it('should provide an api of panel functions', function () {
var p = ps.createPanel('foo');
expect(fs.areFunctions(p, [
'show', 'hide', 'empty', 'append', 'width', 'height', 'isVisible', 'el'
'show', 'hide', 'toggle', 'empty', 'append',
'width', 'height', 'isVisible', 'el'
])).toBeTruthy();
});
......
......@@ -76,7 +76,7 @@ describe('factory: fw/svg/svgUtil.js', function() {
});
it('should provide an alternate (dark) shade of blue for muted', function () {
expect(sus.cat7().getColor('foo', true, 'dark')).toEqual('#16203A');
expect(sus.cat7().getColor('foo', true, 'dark')).toEqual('#304860');
});
it('should iterate across the colors', function () {
......
......@@ -35,6 +35,7 @@ describe('factory: view/topo/topoForce.js', function() {
it('should define api functions', function () {
expect(fs.areFunctions(tfs, [
'initForce', 'resize', 'updateDeviceColors',
'toggleHosts', 'toggleOffline','cycleDeviceLabels',
'addDevice', 'updateDevice', 'removeDevice',
'addHost', 'updateHost', 'removeHost',
'addLink', 'updateLink', 'removeLink'
......
......@@ -36,7 +36,7 @@ describe('factory: view/topo/topoInst.js', function() {
expect(fs.areFunctions(tis, [
'initInst', 'destroyInst',
'addInstance', 'updateInstance', 'removeInstance',
'isVisible', 'show', 'hide'
'isVisible', 'show', 'hide', 'toggle'
])).toBeTruthy();
});
......
......@@ -13,8 +13,8 @@
"0E:2A:69:30:13:86"
],
"metaUi": {
"x": 800,
"y": 180
"Xx": 800,
"Xy": 180
},
"props": {}
}
......
......@@ -13,8 +13,8 @@
"A6:96:E5:03:52:5F"
],
"metaUi": {
"x": 520,
"y": 250
"Xx": 520,
"Xy": 250
},
"props": {}
}
......
......@@ -25,10 +25,7 @@ var lastcmd, // last command executed
var scFiles = fs.readdirSync(scenarioRoot);
console.log('Mock Server v1.0');
console.log('================');
console.log('Scenarios ...');
console.log(scFiles.join(', '));
console.log();
listScenarios();
var rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('ws> ');
......@@ -118,6 +115,7 @@ function doCli() {
}
switch(cmd) {
case 'l': listScenarios(); break;
case 'c': connStatus(); break;
case 'm': customMessage(str); break;
case 's': setScenario(str); break;
......@@ -137,10 +135,11 @@ function doCli() {
}
var helptext = '\n' +
'l - list scenarios\n' +
'c - show connection status\n' +
'm {text} - send custom message to client\n' +
's {id} - load scenario {id}\n' +
's - show scenario staus\n' +
's - show scenario status\n' +
//'a - auto-send events\n' +
'n - send next event\n' +
'r - restart the scenario\n' +
......@@ -151,6 +150,12 @@ function showHelp() {
console.log(helptext);
}
function listScenarios() {
console.log('Scenarios ...');
console.log(scFiles.join(', '));
console.log();
}
function connStatus() {
if (connection) {
console.log('Connection from ' + origin + ' established.');
......