/* * 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 -- Device View Module */ (function () { 'use strict'; // injected refs var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks; // internal state var detailsPanel, pStartY, pHeight, top, bottom, iconDiv, wSize, editingName = false; // constants var topPdg = 13, ctnrPdg = 24, scrollSize = 17, portsTblPdg = 50, pName = 'device-details-panel', detailsReq = 'deviceDetailsRequest', detailsResp = 'deviceDetailsResponse', nameChangeReq = 'deviceNameChangeRequest', nameChangeResp = 'deviceNameChangeResponse', propOrder = [ 'id', 'type', 'masterid', 'chassisid', 'mfr', 'hw', 'sw', 'protocol', 'serial' ], friendlyProps = [ 'URI', 'Type', 'Master ID', 'Chassis ID', 'Vendor', 'H/W Version', 'S/W Version', 'Protocol', 'Serial #' ], portCols = [ 'enabled', 'id', 'speed', 'type', 'elinks_dest', 'name' ], friendlyPortCols = [ 'Enabled', 'ID', 'Speed', 'Type', 'Egress Links', 'Name' ]; function closePanel() { if (detailsPanel.isVisible()) { $scope.selId = null; detailsPanel.hide(); return true; } return false; } function addCloseBtn(div) { is.loadEmbeddedIcon(div, 'plus', 30); div.select('g').attr('transform', 'translate(25, 0) rotate(45)'); div.on('click', closePanel); } function exitEditMode(nameH2, name) { nameH2.html(name); nameH2.classed('editable', true); editingName = false; ks.enableGlobalKeys(true); } function editNameSave() { var nameH2 = top.select('h2'), id = $scope.panelData.id, val, newVal; if (editingName) { val = nameH2.select('input').property('value').trim(); newVal = val || id; exitEditMode(nameH2, newVal); $scope.panelData.name = newVal; wss.sendEvent(nameChangeReq, { id: id, name: val }); } } function editNameCancel() { if (editingName) { exitEditMode(top.select('h2'), $scope.panelData.name); return true; } return false; } function editName() { var nameH2 = top.select('h2'), tf, el; if (!editingName) { nameH2.classed('editable', false); nameH2.html(''); tf = nameH2.append('input').classed('name-input', true) .attr('type', 'text') .attr('value', $scope.panelData.name); el = tf[0][0]; el.focus(); el.select(); editingName = true; ks.enableGlobalKeys(false); } } function handleEscape() { return editNameCancel() || closePanel(); } function setUpPanel() { var container, closeBtn, tblDiv; detailsPanel.empty(); container = detailsPanel.append('div').classed('container', true); top = container.append('div').classed('top', true); closeBtn = top.append('div').classed('close-btn', true); addCloseBtn(closeBtn); iconDiv = top.append('div').classed('dev-icon', true); top.append('h2').classed('editable', true).on('click', editName); tblDiv = top.append('div').classed('top-tables', true); tblDiv.append('div').classed('left', true).append('table'); tblDiv.append('div').classed('right', true).append('table'); top.append('hr'); bottom = container.append('div').classed('bottom', true); bottom.append('h2').classed('ports-title', true).html('Ports'); bottom.append('table'); } function addProp(tbody, index, value) { var tr = tbody.append('tr'); function addCell(cls, txt) { tr.append('td').attr('class', cls).html(txt); } addCell('label', friendlyProps[index] + ' :'); addCell('value', value); } function populateTop(tblDiv, details) { var leftTbl = tblDiv.select('.left') .select('table') .append('tbody'), rightTbl = tblDiv.select('.right') .select('table') .append('tbody'); is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40); top.select('h2').html(details.name); propOrder.forEach(function (prop, i) { // properties are split into two tables addProp(i < 4 ? leftTbl : rightTbl, i, details[prop]); }); } function addPortRow(tbody, port) { var tr = tbody.append('tr'); portCols.forEach(function (col) { tr.append('td').html(port[col]); }); } function populateBottom(table, ports) { var theader = table.append('thead').append('tr'), tbody = table.append('tbody'), tbWidth, tbHeight; friendlyPortCols.forEach(function (col) { theader.append('th').html(col); }); ports.forEach(function (port) { addPortRow(tbody, port); }); tbWidth = fs.noPxStyle(tbody, 'width') + scrollSize; tbHeight = pHeight - (fs.noPxStyle(detailsPanel.el() .select('.top'), 'height') + fs.noPxStyle(detailsPanel.el() .select('.ports-title'), 'height') + portsTblPdg); table.style({ height: tbHeight + 'px', width: tbWidth + 'px', overflow: 'auto', display: 'block' }); detailsPanel.width(tbWidth + ctnrPdg); } function populateName(div, name) { var lab = div.select('.label'), val = div.select('.value'); lab.html('Friendly Name:'); val.html(name); } function populateDetails(details) { var nameDiv, topTbs, btmTbl, ports; setUpPanel(); nameDiv = top.select('.name-div'); topTbs = top.select('.top-tables'); btmTbl = bottom.select('table'); ports = details.ports; populateName(nameDiv, details.name); populateTop(topTbs, details); populateBottom(btmTbl, ports); detailsPanel.height(pHeight); } function respDetailsCb(data) { $scope.panelData = data.details; $scope.$apply(); } function respNameCb(data) { if (data.warn) { $log.warn(data.warn, data.id); top.select('h2').html(data.id); } } function createDetailsPane() { detailsPanel = ps.createPanel(pName, { width: wSize.width, margin: 0, hideMargin: 0 }); detailsPanel.el().style({ position: 'absolute', top: pStartY + 'px' }); $scope.hidePanel = function () { detailsPanel.hide(); }; detailsPanel.hide(); } angular.module('ovDevice', []) .controller('OvDeviceCtrl', ['$log', '$scope', '$location', 'TableBuilderService', 'FnService', 'MastService', 'PanelService', 'WebSocketService', 'IconService', 'NavService', 'KeyService', function (_$log_, _$scope_, _$location_, tbs, _fs_, _mast_, _ps_, _wss_, _is_, _ns_, _ks_) { var params, handlers = {}; $log = _$log_; $scope = _$scope_; $loc = _$location_; fs = _fs_; mast = _mast_; ps = _ps_; wss = _wss_; is = _is_; ns = _ns_; ks = _ks_; params = $loc.search(); $scope.panelData = {}; $scope.flowTip = 'Show flow view for selected device'; $scope.portTip = 'Show port view for selected device'; $scope.groupTip = 'Show group view for selected device'; // details panel handlers handlers[detailsResp] = respDetailsCb; handlers[nameChangeResp] = respNameCb; wss.bindHandlers(handlers); // query for if a certain device needs to be highlighted if (params.hasOwnProperty('devId')) { $scope.selId = params['devId']; wss.sendEvent(detailsReq, { id: $scope.selId }); } function selCb($event, row) { if ($scope.selId) { wss.sendEvent(detailsReq, { id: row.id }); } else { $scope.hidePanel(); } $log.debug('Got a click on:', row); } tbs.buildTable({ scope: $scope, tag: 'device', selCb: selCb }); $scope.nav = function (path) { if ($scope.selId) { ns.navTo(path, { devId: $scope.selId }); } }; $scope.$on('$destroy', function () { wss.unbindHandlers(handlers); }); $log.log('OvDeviceCtrl has been created'); }]) .directive('deviceDetailsPanel', ['$rootScope', '$window', '$timeout', 'KeyService', function ($rootScope, $window, $timeout, ks) { return function (scope) { var unbindWatch; function heightCalc() { pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height') + mast.mastHeight() + topPdg; wSize = fs.windowSize(pStartY); pHeight = wSize.height; } function initPanel() { heightCalc(); createDetailsPane(); } // Safari has a bug where it renders the fixed-layout table wrong // if you ask for the window's size too early if (scope.onos.browser === 'safari') { $timeout(initPanel); } else { initPanel(); } // create key bindings to handle panel ks.keyBindings({ enter: editNameSave, esc: [handleEscape, 'Close the details panel'], _helpFormat: ['esc'] }); ks.gestureNotes([ ['click', 'Select a row to show device details'], ['scroll down', 'See more devices'] ]); // if the panelData changes scope.$watch('panelData', function () { if (!fs.isEmptyObject(scope.panelData)) { populateDetails(scope.panelData); detailsPanel.show(); } }); // if the window size changes unbindWatch = $rootScope.$watchCollection( function () { return { h: $window.innerHeight, w: $window.innerWidth }; }, function () { if (!fs.isEmptyObject(scope.panelData)) { heightCalc(); populateDetails(scope.panelData); } } ); scope.$on('$destroy', function () { unbindWatch(); ks.unbindKeys(); ps.destroyPanel(pName); }); }; }]); }());