diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/web/gui/src/main/webapp/app/view/topo/topo.js | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/web/gui/src/main/webapp/app/view/topo/topo.js')
-rw-r--r-- | framework/src/onos/web/gui/src/main/webapp/app/view/topo/topo.js | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topo.js b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topo.js new file mode 100644 index 00000000..21894100 --- /dev/null +++ b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topo.js @@ -0,0 +1,529 @@ +/* + * Copyright 2014,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 View Module + */ + +(function () { + 'use strict'; + + var moduleDependencies = [ + 'ngCookies', + 'onosUtil', + 'onosSvg', + 'onosRemote' + ]; + + // references to injected services etc. + var $scope, $log, $cookies, fs, ks, zs, gs, ms, sus, flash, wss, ps, + tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs, ttip, tov; + + // DOM elements + var ovtopo, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer; + + // Internal state + var zoomer, actionMap; + + // --- Short Cut Keys ------------------------------------------------ + + function setUpKeys(overlayKeys) { + // key bindings need to be made after the services have been injected + // thus, deferred to here... + actionMap = { + I: [toggleInstances, 'Toggle ONOS instances panel'], + O: [toggleSummary, 'Toggle ONOS summary panel'], + D: [toggleUseDetailsFlag, 'Disable / enable details panel'], + + H: [toggleHosts, 'Toggle host visibility'], + M: [toggleOffline, 'Toggle offline visibility'], + P: [togglePorts, 'Toggle Port Highlighting'], + dash: [tfs.showBadLinks, 'Show bad links'], + B: [toggleMap, 'Toggle background map'], + S: [toggleSprites, 'Toggle sprite layer'], + + //X: [toggleNodeLock, 'Lock / unlock node positions'], + Z: [tos.toggleOblique, 'Toggle oblique view (Experimental)'], + N: [fltr.clickAction, 'Cycle node layers'], + L: [tfs.cycleDeviceLabels, 'Cycle device labels'], + U: [tfs.unpin, 'Unpin node (hover mouse over)'], + R: [resetZoom, 'Reset pan / zoom'], + dot: [ttbs.toggleToolbar, 'Toggle Toolbar'], + + E: [equalizeMasters, 'Equalize mastership roles'], + + esc: handleEscape, + + _keyListener: ttbs.keyListener, + + _helpFormat: [ + ['I', 'O', 'D', 'H', 'M', 'P', 'dash', 'B', 'S' ], + ['X', 'Z', 'N', 'L', 'U', 'R', '-', 'E', '-', 'dot'], + [] // this column reserved for overlay actions + ] + }; + + if (fs.isO(overlayKeys)) { + mergeKeys(overlayKeys); + } + + ks.keyBindings(actionMap); + + ks.gestureNotes([ + ['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'] + ]); + } + + // when a topology overlay is activated, we need to bind their keystrokes + // and include them in the quick-help panel + function mergeKeys(extra) { + var _hf = actionMap._helpFormat[2]; + extra._keyOrder.forEach(function (k) { + var d = extra[k], + cb = d && d.cb, + tt = d && d.tt; + // NOTE: ignore keys that are already defined + if (d && !actionMap[k]) { + actionMap[k] = [cb, tt]; + _hf.push(k); + } + }); + } + + // --- Keystroke functions ------------------------------------------- + + function toggleInstances(x) { + updatePrefsState('insts', tis.toggle(x)); + tfs.updateDeviceColors(); + } + + function toggleSummary(x) { + updatePrefsState('summary', tps.toggleSummary(x)); + } + + function toggleUseDetailsFlag(x) { + updatePrefsState('detail', tps.toggleUseDetailsFlag(x)); + } + + function toggleHosts(x) { + updatePrefsState('hosts', tfs.toggleHosts(x)); + } + + function toggleOffline(x) { + updatePrefsState('offdev', tfs.toggleOffline(x)); + } + + function togglePorts(x) { + updatePrefsState('porthl', tfs.togglePorts(x)); + } + + function _togSvgLayer(x, G, tag, what) { + var on = (x === 'keyev') ? !sus.visible(G) : !!x, + verb = on ? 'Show' : 'Hide'; + sus.visible(G, on); + updatePrefsState(tag, on); + flash.flash(verb + ' ' + what); + } + + function toggleMap(x) { + _togSvgLayer(x, mapG, 'bg', 'background map'); + } + + function toggleSprites(x) { + _togSvgLayer(x, spriteG, 'spr', 'sprite layer'); + } + + function resetZoom() { + zoomer.reset(); + flash.flash('Pan and zoom reset'); + } + + function equalizeMasters() { + wss.sendEvent('equalizeMasters'); + flash.flash('Equalizing master roles'); + } + + function handleEscape() { + if (tis.showMaster()) { + // if an instance is selected, cancel the affinity mapping + tis.cancelAffinity() + + } else if (tov.hooks.escape()) { + // else if the overlay consumed the ESC event... + // (work already done) + + } else if (tss.deselectAll()) { + // else if we have node selections, deselect them all + // (work already done) + + } else if (tls.deselectLink()) { + // else if we have a link selected, deselect it + // (work already done) + + } else if (tis.isVisible()) { + // else if the Instance Panel is visible, hide it + tis.hide(); + tfs.updateDeviceColors(); + + } else if (tps.summaryVisible()) { + // else if the Summary Panel is visible, hide it + tps.hideSummaryPanel(); + } + } + + // --- Toolbar Functions --------------------------------------------- + + function notValid(what) { + $log.warn('topo.js getActionEntry(): Not a valid ' + what); + } + + function getActionEntry(key) { + var entry; + + if (!key) { + notValid('key'); + return null; + } + + entry = actionMap[key]; + + if (!entry) { + notValid('actionMap entry'); + return null; + } + return fs.isA(entry) || [entry, '']; + } + + function setUpToolbar() { + ttbs.init({ + getActionEntry: getActionEntry, + setUpKeys: setUpKeys + }); + ttbs.createToolbar(); + } + + // --- Glyphs, Icons, and the like ----------------------------------- + + function setUpDefs() { + defs = svg.append('defs'); + gs.loadDefs(defs); + sus.loadGlowDefs(defs); + } + + + // --- Pan and Zoom -------------------------------------------------- + + // zoom enabled predicate. ev is a D3 source event. + function zoomEnabled(ev) { + return fs.isMobile() || (ev.metaKey || ev.altKey); + } + + function zoomCallback() { + var sc = zoomer.scale(), + tr = zoomer.translate(); + + ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc}); + + // keep the map lines constant width while zooming + mapG.style('stroke-width', (2.0 / sc) + 'px'); + } + + function setUpZoom() { + zoomLayer = svg.append('g').attr('id', 'topo-zoomlayer'); + zoomer = zs.createZoomer({ + svg: svg, + zoomLayer: zoomLayer, + zoomEnabled: zoomEnabled, + zoomCallback: zoomCallback + }); + } + + + // callback invoked when the SVG view has been resized.. + function svgResized(s) { + tfs.newDim([s.width, s.height]); + } + + // --- Background Map ------------------------------------------------ + + function setUpNoDevs() { + var g, box; + noDevsLayer = svg.append('g').attr({ + id: 'topo-noDevsLayer', + transform: sus.translate(500,500) + }); + // Note, SVG viewbox is '0 0 1000 1000', defined in topo.html. + // We are translating this layer to have its origin at the center + + g = noDevsLayer.append('g'); + gs.addGlyph(g, 'bird', 100).attr('class', 'noDevsBird'); + g.append('text').text('No devices are connected') + .attr({ x: 120, y: 80}); + + box = g.node().getBBox(); + box.x -= box.width/2; + box.y -= box.height/2; + g.attr('transform', sus.translate(box.x, box.y)); + + showNoDevs(true); + } + + function showNoDevs(b) { + sus.visible(noDevsLayer, b); + } + + + var countryFilters = { + world: function (c) { + return c.properties.continent !== 'Antarctica'; + }, + + // NOTE: for "usa" we are using our hand-crafted topojson file + + s_america: function (c) { + return c.properties.continent === 'South America'; + }, + + japan: function (c) { + return c.properties.geounit === 'Japan'; + }, + + europe: function (c) { + return c.properties.continent === 'Europe'; + }, + + italy: function (c) { + return c.properties.geounit === 'Italy'; + }, + + uk: function (c) { + // technically, Ireland is not part of the United Kingdom, + // but the map looks weird without it showing. + return c.properties.adm0_a3 === 'GBR' || + c.properties.adm0_a3 === 'IRL'; + }, + + s_korea: function (c) { + return c.properties.adm0_a3 === 'KOR'; + }, + + australia: function (c) { + return c.properties.adm0_a3 === 'AUS'; + } + }; + + + function setUpMap($loc) { + var s1 = $loc.search().mapid, + s2 = ps.getPrefs('topo_mapid'), + mapId = s1 || (s2 && s2.id) || 'usa', + promise, + cfilter, + opts; + + mapG = zoomLayer.append('g').attr('id', 'topo-map'); + if (mapId === 'usa') { + promise = ms.loadMapInto(mapG, '*continental_us'); + } else { + ps.setPrefs('topo_mapid', {id:mapId}); + cfilter = countryFilters[mapId] || countryFilters.world; + opts = { countryFilter: cfilter }; + promise = ms.loadMapRegionInto(mapG, opts); + } + return promise; + } + + function opacifyMap(b) { + mapG.transition() + .duration(1000) + .attr('opacity', b ? 1 : 0); + } + + function setUpSprites($loc, tspr) { + var s1 = $loc.search().sprites, + s2 = ps.getPrefs('topo_sprites'), + sprId = s1 || (s2 && s2.id); + + spriteG = zoomLayer.append ('g').attr('id', 'topo-sprites'); + if (sprId) { + ps.setPrefs('topo_sprites', {id:sprId}); + tspr.loadSprites(spriteG, defs, sprId); + } + } + + // --- User Preferemces ---------------------------------------------- + + var prefsState = {}; + + function updatePrefsState(what, b) { + prefsState[what] = b ? 1 : 0; + ps.setPrefs('topo_prefs', prefsState); + } + + + function restoreConfigFromPrefs() { + // NOTE: toolbar will have set this for us.. + prefsState = ps.asNumbers(ps.getPrefs('topo_prefs')); + + $log.debug('TOPO- Prefs State:', prefsState); + + flash.enable(false); + toggleInstances(prefsState.insts); + toggleSummary(prefsState.summary); + toggleUseDetailsFlag(prefsState.detail); + toggleHosts(prefsState.hosts); + toggleOffline(prefsState.offdev); + togglePorts(prefsState.porthl); + toggleMap(prefsState.bg); + toggleSprites(prefsState.spr); + flash.enable(true); + } + + + // somewhat hackish, because summary update cannot happen until we + // have opened the websocket to the server; hence this extra function + // invoked after tes.start() + function restoreSummaryFromPrefs() { + prefsState = ps.asNumbers(ps.getPrefs('topo_prefs')); + $log.debug('TOPO- Prefs SUMMARY State:', prefsState.summary); + + flash.enable(false); + toggleSummary(prefsState.summary); + flash.enable(true); + } + + + // --- Controller Definition ----------------------------------------- + + angular.module('ovTopo', moduleDependencies) + .controller('OvTopoCtrl', ['$scope', '$log', '$location', '$timeout', + '$cookies', 'FnService', 'MastService', 'KeyService', 'ZoomService', + 'GlyphService', 'MapService', 'SvgUtilService', 'FlashService', + 'WebSocketService', 'PrefsService', + 'TopoEventService', 'TopoForceService', 'TopoPanelService', + 'TopoInstService', 'TopoSelectService', 'TopoLinkService', + 'TopoTrafficService', 'TopoObliqueService', 'TopoFilterService', + 'TopoToolbarService', 'TopoSpriteService', 'TooltipService', + 'TopoOverlayService', + + function (_$scope_, _$log_, $loc, $timeout, _$cookies_, _fs_, mast, _ks_, + _zs_, _gs_, _ms_, _sus_, _flash_, _wss_, _ps_, _tes_, _tfs_, + _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, _ttbs_, tspr, + _ttip_, _tov_) { + var projection, + dim, + uplink = { + // provides function calls back into this space + showNoDevs: showNoDevs, + projection: function () { return projection; }, + zoomLayer: function () { return zoomLayer; }, + zoomer: function () { return zoomer; }, + opacifyMap: opacifyMap + }; + + $scope = _$scope_; + $log = _$log_; + $cookies = _$cookies_; + fs = _fs_; + ks = _ks_; + zs = _zs_; + gs = _gs_; + ms = _ms_; + sus = _sus_; + flash = _flash_; + wss = _wss_; + ps = _ps_; + tes = _tes_; + tfs = _tfs_; + // TODO: consider funnelling actions through TopoForceService... + // rather than injecting references to these 'sub-modules', + // just so we can invoke functions on them. + tps = _tps_; + tis = _tis_; + tss = _tss_; + tls = _tls_; + tts = _tts_; + tos = _tos_; + fltr = _fltr_; + ttbs = _ttbs_; + ttip = _ttip_; + tov = _tov_; + + $scope.notifyResize = function () { + svgResized(fs.windowSize(mast.mastHeight())); + }; + + // Cleanup on destroyed scope.. + $scope.$on('$destroy', function () { + $log.log('OvTopoCtrl is saying Buh-Bye!'); + tes.stop(); + ks.unbindKeys(); + tps.destroyPanels(); + tis.destroyInst(); + tfs.destroyForce(); + ttbs.destroyToolbar(); + }); + + // svg layer and initialization of components + ovtopo = d3.select('#ov-topo'); + svg = ovtopo.select('svg'); + // set the svg size to match that of the window, less the masthead + svg.attr(fs.windowSize(mast.mastHeight())); + dim = [svg.attr('width'), svg.attr('height')]; + + setUpKeys(); + setUpToolbar(); + setUpDefs(); + setUpZoom(); + setUpNoDevs(); + setUpMap($loc).then( + function (proj) { + var z = ps.getPrefs('topo_zoom') || {tx:0, ty:0, sc:1}; + zoomer.panZoom([z.tx, z.ty], z.sc); + $log.debug('** Zoom restored:', z); + + projection = proj; + $log.debug('** We installed the projection:', proj); + flash.enable(false); + toggleMap(prefsState.bg); + flash.enable(true); + + // now we have the map projection, we are ready for + // the server to send us device/host data... + tes.start(); + // need to do the following so we immediately get + // the summary panel data back from the server + restoreSummaryFromPrefs(); + } + ); + setUpSprites($loc, tspr); + + forceG = zoomLayer.append('g').attr('id', 'topo-force'); + tfs.initForce(svg, forceG, uplink, dim); + tis.initInst({ showMastership: tfs.showMastership }); + tps.initPanels(); + + // temporary solution for persisting user settings + restoreConfigFromPrefs(); + + $log.debug('registered overlays...', tov.list()); + $log.log('OvTopoCtrl has been created'); + }]); +}()); |