/* * 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 Panel Module. Defines functions for manipulating the summary, detail, and instance panels. */ (function () { 'use strict'; // injected refs var $log, $window, $rootScope, fs, ps, gs, flash, wss, bns, mast, ns; // constants var pCls = 'topo-p', idSum = 'topo-p-summary', idDet = 'topo-p-detail', panelOpts = { width: 260 }, sumMax = 262, padTop = 20, devPath = 'device'; // internal state var useDetails = true, // should we show details if we have 'em? haveDetails = false, // do we have details that we could show? sumFromTop, // summary panel distance from top of screen unbindWatch; // panels var summary, detail; // === ----------------------------------------------------- // Panel API function createTopoPanel(id, opts) { var p = ps.createPanel(id, opts), pid = id, header, body, footer; p.classed(pCls, true); function panel() { return p; } function hAppend(x) { return header.append(x); } function bAppend(x) { return body.append(x); } function fAppend(x) { return footer.append(x); } function setup() { p.empty(); p.append('div').classed('header', true); p.append('div').classed('body', true); p.append('div').classed('footer', true); header = p.el().select('.header'); body = p.el().select('.body'); footer = p.el().select('.footer'); } function destroy() { ps.destroyPanel(pid); } // fromTop is how many pixels from the top of the page the panel is // max is the max height of the panel in pixels // only adjusts if the body content would be 10px or larger function adjustHeight(fromTop, max) { var totalPHeight, avSpace, overflow = 0, pdg = 30; if (!fromTop) { $log.warn('adjustHeight: height from top of page not given'); return null; } else if (!body || !p) { $log.warn('adjustHeight: panel contents are not defined'); return null; } p.el().style('height', null); body.style('height', null); totalPHeight = fromTop + p.height(); avSpace = fs.windowSize(pdg).height; if (totalPHeight >= avSpace) { overflow = totalPHeight - avSpace; } function _adjustBody(height) { if (height < 10) { return false; } else { body.style('height', height + 'px'); } return true; } if (!_adjustBody(fs.noPxStyle(body, 'height') - overflow)) { return; } if (max && p.height() > max) { _adjustBody(fs.noPxStyle(body, 'height') - (p.height() - max)); } } return { panel: panel, setup: setup, destroy: destroy, appendHeader: hAppend, appendBody: bAppend, appendFooter: fAppend, adjustHeight: adjustHeight }; } // === ----------------------------------------------------- // Utility functions function addSep(tbody) { tbody.append('tr').append('td').attr('colspan', 2).append('hr'); } function addBtnFooter() { detail.appendFooter('hr'); detail.appendFooter('div').classed('actionBtns', true); } function addProp(tbody, label, value) { var tr = tbody.append('tr'), lab; if (typeof label === 'string') { lab = label.replace(/_/g, ' '); } else { lab = label; } function addCell(cls, txt) { tr.append('td').attr('class', cls).html(txt); } addCell('label', lab + ' :'); addCell('value', value); } function listProps(tbody, data) { data.propOrder.forEach(function (p) { if (p === '-') { addSep(tbody); } else { addProp(tbody, p, data.props[p]); } }); } function watchWindow() { unbindWatch = $rootScope.$watchCollection( function () { return { h: $window.innerHeight, w: $window.innerWidth }; }, function () { summary.adjustHeight(sumFromTop, sumMax); detail.adjustHeight(detail.ypos.current); } ); } // === ----------------------------------------------------- // Functions for populating the summary panel function populateSummary(data) { summary.setup(); var svg = summary.appendHeader('div') .classed('icon', true) .append('svg'), title = summary.appendHeader('h2'), table = summary.appendBody('table'), tbody = table.append('tbody'), glyphId = data.type || 'node'; gs.addGlyph(svg, glyphId, 40); if (glyphId === 'node') { gs.addGlyph(svg, 'bird', 24, true, [8,12]); } title.text(data.title); listProps(tbody, data); } // === ----------------------------------------------------- // Functions for populating the detail panel var isDevice = { switch: 1, roadm: 1 }; function displaySingle(data) { detail.setup(); var svg = detail.appendHeader('div') .classed('icon clickable', true) .append('svg'), title = detail.appendHeader('h2') .classed('clickable', true), table = detail.appendBody('table'), tbody = table.append('tbody'), navFn; gs.addGlyph(svg, (data.type || 'unknown'), 40); title.text(data.title); // only add navigation when displaying a device if (isDevice[data.type]) { navFn = function () { ns.navTo(devPath, { devId: data.id }); }; svg.on('click', navFn); title.on('click', navFn); } listProps(tbody, data); addBtnFooter(); } function displayMulti(ids) { detail.setup(); var title = detail.appendHeader('h3'), table = detail.appendBody('table'), tbody = table.append('tbody'); title.text('Selected Nodes'); ids.forEach(function (d, i) { addProp(tbody, i+1, d); }); addBtnFooter(); } function addAction(o) { var btnDiv = d3.select('#' + idDet) .select('.actionBtns') .append('div') .classed('actionBtn', true); bns.button(btnDiv, idDet + '-' + o.id, o.gid, o.cb, o.tt); } var friendlyIndex = { device: 1, host: 0 }; function friendly(d) { var i = friendlyIndex[d.class] || 0; return (d.labels && d.labels[i]) || ''; } function linkSummary(d) { var o = d && d.online ? 'online' : 'offline'; return d ? d.type + ' / ' + o : '-'; } // provided to change presentation of internal type name var linkTypePres = { hostLink: 'edge link' }; function linkType(d) { return linkTypePres[d.type()] || d.type(); } var coreOrder = [ 'Type', '-', 'A_type', 'A_id', 'A_label', 'A_port', '-', 'B_type', 'B_id', 'B_label', 'B_port', '-' ], edgeOrder = [ 'Type', '-', 'A_type', 'A_id', 'A_label', '-', 'B_type', 'B_id', 'B_label', 'B_port' ]; function displayLink(data) { detail.setup(); var svg = detail.appendHeader('div') .classed('icon', true) .append('svg'), title = detail.appendHeader('h2'), table = detail.appendBody('table'), tbody = table.append('tbody'), edgeLink = data.type() === 'hostLink', order = edgeLink ? edgeOrder : coreOrder; gs.addGlyph(svg, 'ports', 40); title.text('Link'); listProps(tbody, { propOrder: order, props: { Type: linkType(data), A_type: data.source.class, A_id: data.source.id, A_label: friendly(data.source), A_port: data.srcPort, B_type: data.target.class, B_id: data.target.id, B_label: friendly(data.target), B_port: data.tgtPort } }); if (!edgeLink) { addProp(tbody, 'A → B', linkSummary(data.fromSource)); addProp(tbody, 'B → A', linkSummary(data.fromTarget)); } } function displayNothing() { haveDetails = false; hideDetailPanel(); } function displaySomething() { haveDetails = true; if (useDetails) { showDetailPanel(); } } // === ----------------------------------------------------- // Event Handlers function showSummary(data) { populateSummary(data); showSummaryPanel(); } function toggleSummary(x) { var kev = (x === 'keyev'), on = kev ? !summary.panel().isVisible() : !!x, verb = on ? 'Show' : 'Hide'; if (on) { // ask server to start sending summary data. wss.sendEvent('requestSummary'); // note: the summary panel will appear, once data arrives } else { hideSummaryPanel(); } flash.flash(verb + ' summary panel'); return on; } // === ----------------------------------------------------- // === LOGIC For showing/hiding summary and detail panels... function showSummaryPanel() { function _show() { summary.panel().show(); summary.adjustHeight(sumFromTop, sumMax); } if (detail.panel().isVisible()) { detail.down(_show); } else { _show(); } } function hideSummaryPanel() { // instruct server to stop sending summary data wss.sendEvent("cancelSummary"); summary.panel().hide(detail.up); } function showDetailPanel() { if (summary.panel().isVisible()) { detail.down(detail.panel().show); } else { detail.up(detail.panel().show); } } function hideDetailPanel() { detail.panel().hide(); } // ========================== function augmentDetailPanel() { var d = detail, downPos = sumFromTop + sumMax + 20; d.ypos = { up: sumFromTop, down: downPos, current: downPos}; d._move = function (y, cb) { var yp = d.ypos, endCb; if (fs.isF(cb)) { endCb = function () { cb(); d.adjustHeight(d.ypos.current); } } else { endCb = function () { d.adjustHeight(d.ypos.current); } } if (yp.current !== y) { yp.current = y; d.panel().el().transition().duration(300) .each('end', endCb) .style('top', yp.current + 'px'); } else { endCb(); } }; d.down = function (cb) { d._move(d.ypos.down, cb); }; d.up = function (cb) { d._move(d.ypos.up, cb); }; } function toggleUseDetailsFlag(x) { var kev = (x === 'keyev'), verb; useDetails = kev ? !useDetails : !!x; verb = useDetails ? 'Enable' : 'Disable'; if (useDetails) { if (haveDetails) { showDetailPanel(); } } else { hideDetailPanel(); } flash.flash(verb + ' details panel'); return useDetails; } // ========================== function initPanels() { sumFromTop = mast.mastHeight() + padTop; summary = createTopoPanel(idSum, panelOpts); detail = createTopoPanel(idDet, panelOpts); augmentDetailPanel(); watchWindow(); } function destroyPanels() { summary.destroy(); summary = null; detail.destroy(); detail = null; haveDetails = false; unbindWatch(); } // ========================== angular.module('ovTopo') .factory('TopoPanelService', ['$log', '$window', '$rootScope', 'FnService', 'PanelService', 'GlyphService', 'FlashService', 'WebSocketService', 'ButtonService', 'MastService', 'NavService', function (_$log_, _$window_, _$rootScope_, _fs_, _ps_, _gs_, _flash_, _wss_, _bns_, _mast_, _ns_) { $log = _$log_; $window = _$window_; $rootScope = _$rootScope_; fs = _fs_; ps = _ps_; gs = _gs_; flash = _flash_; wss = _wss_; bns = _bns_; mast = _mast_; ns = _ns_; return { initPanels: initPanels, destroyPanels: destroyPanels, createTopoPanel: createTopoPanel, showSummary: showSummary, toggleSummary: toggleSummary, toggleUseDetailsFlag: toggleUseDetailsFlag, displaySingle: displaySingle, displayMulti: displayMulti, displayLink: displayLink, displayNothing: displayNothing, displaySomething: displaySomething, addAction: addAction, hideSummaryPanel: hideSummaryPanel, detailVisible: function () { return detail.panel().isVisible(); }, summaryVisible: function () { return summary.panel().isVisible(); } }; }]); }());