ONOS-5407: Added details panel for Ports View.
Change-Id: I716a22103f1ab37bc092bad6c672abea479146b7
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
index d9ad4b5..205be39 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
@@ -20,6 +20,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.device.PortStatistics;
import org.onosproject.ui.RequestHandler;
@@ -31,6 +32,9 @@
import java.util.Collection;
import java.util.List;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
/**
* Message handler for port view related messages.
@@ -43,6 +47,12 @@
private static final String DELTA = "showDelta";
private static final String NZ = "nzFilter";
+ private static final String PORT_DETAILS_REQ = "portDetailsRequest";
+ private static final String PORT_DETAILS_RESP = "portDetailsResponse";
+ private static final String DETAILS = "details";
+ private static final String PORT = "port";
+
+ private static final String DEV_ID = "devId";
private static final String ID = "id";
private static final String PKT_RX = "pkt_rx";
private static final String PKT_TX = "pkt_tx";
@@ -51,6 +61,13 @@
private static final String PKT_RX_DRP = "pkt_rx_drp";
private static final String PKT_TX_DRP = "pkt_tx_drp";
private static final String DURATION = "duration";
+ private static final String SPEED = "speed";
+ private static final String ENABLED = "enabled";
+ private static final String TYPE = "type";
+ private static final String TYPE_IID = "_iconid_type";
+ private static final String PORT_ICON_PREFIX = "portIcon_";
+
+
private static final String[] COL_IDS = {
ID, PKT_RX, PKT_TX, BYTES_RX, BYTES_TX,
@@ -59,7 +76,10 @@
@Override
protected Collection<RequestHandler> createRequestHandlers() {
- return ImmutableSet.of(new PortDataRequest());
+ return ImmutableSet.of(
+ new PortDataRequest(),
+ new DetailRequestHandler()
+ );
}
// handler for port table requests
@@ -124,4 +144,68 @@
.cell(DURATION, stat.durationSec());
}
}
+
+ private final class DetailRequestHandler extends RequestHandler {
+ private DetailRequestHandler() {
+ super(PORT_DETAILS_REQ);
+ }
+
+ @Override
+ public void process(ObjectNode payload) {
+ String id = string(payload, ID);
+ String devId = string(payload, DEV_ID);
+
+ DeviceService deviceService = get(DeviceService.class);
+ Port port = deviceService.getPort(deviceId(devId), portNumber(id));
+
+ ObjectNode data = objectNode();
+
+ data.put(ID, id);
+ data.put(DEV_ID, devId);
+ data.put(TYPE, displayType(port.type()));
+ data.put(SPEED, displaySpeed(port.portSpeed()));
+ data.put(ENABLED, port.isEnabled());
+
+ data.put(TYPE_IID, getIconIdForPortType(port.type()));
+
+ ObjectNode rootNode = objectNode();
+ rootNode.set(DETAILS, data);
+
+ // NOTE: ... an alternate way of getting all the details of an item:
+ // Use the codec context to get a JSON of the port. See ONOS-5976.
+ rootNode.set(PORT, getJsonCodecContext().encode(port, Port.class));
+
+ sendMessage(PORT_DETAILS_RESP, rootNode);
+ }
+
+ private String getIconIdForPortType(Port.Type type) {
+ String typeStr = "DEFAULT";
+ // TODO: consider providing alternate icon ID for different types
+ return PORT_ICON_PREFIX + typeStr;
+ // NOTE: look in icon.js for glyphMapping structure to see which
+ // glyph will be used for the port type.
+ }
+
+ /**
+ * Returns the port type as a displayable string.
+ *
+ * @param type the port type
+ * @return human readable port type
+ */
+ private String displayType(Port.Type type) {
+ // TODO: consider better display values?
+ return type.toString();
+ }
+
+ /**
+ * Returns port speed as a displayable string.
+ *
+ * @param portSpeed port speed in Mbps
+ * @return human readable port speed
+ */
+ private String displaySpeed(long portSpeed) {
+ // TODO: better conversion between Gbps, Mbps, Kbps, etc.
+ return "" + portSpeed + " Mbps";
+ }
+ }
}
diff --git a/web/gui/src/main/webapp/app/fw/layer/details-panel.js b/web/gui/src/main/webapp/app/fw/layer/details-panel.js
index cc72a06..39dd26c 100644
--- a/web/gui/src/main/webapp/app/fw/layer/details-panel.js
+++ b/web/gui/src/main/webapp/app/fw/layer/details-panel.js
@@ -84,13 +84,18 @@
closeBtn.on('click', onClose || function () {});
}
- function addHeading(icon) {
+ function addHeading(icon, makeEditable) {
top.append('div').classed('iconDiv ' + icon, true);
- new EditableTextComponent(top.append('h2'), {
- scope: options.scope,
- nameChangeRequest: options.nameChangeRequest,
- keyBindings: options.keyBindings,
- });
+
+ if (makeEditable) {
+ new EditableTextComponent(top.append('h2'), {
+ scope: options.scope,
+ nameChangeRequest: options.nameChangeRequest,
+ keyBindings: options.keyBindings,
+ });
+ } else {
+ top.append('h2'); // note: title is inserted later
+ }
}
function addTable(parent, className) {
diff --git a/web/gui/src/main/webapp/app/fw/svg/icon.js b/web/gui/src/main/webapp/app/fw/svg/icon.js
index f2a6b66..f0c4063 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.js
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -58,6 +58,9 @@
devIcon_SWITCH: 'switch',
devIcon_ROADM: 'roadm',
devIcon_OTN: 'otn',
+
+ portIcon_DEFAULT: 'm_ports',
+
deviceTable: 'switch',
flowTable: 'flowTable',
portTable: 'portTable',
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index 536b789..d0d5707 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -74,7 +74,7 @@
var top = dps.top();
var bottom = dps.bottom();
- dps.addHeading('dev-icon');
+ dps.addHeading('dev-icon', true);
top.append('div').classed('top-content', true);
top.append('hr');
@@ -232,7 +232,6 @@
$scope.pipeconfTip = 'Show pipeconf view for selected device';
// details panel handlers
- // handlers[detailsResp] = respDetailsCb;
handlers[nameChangeResp] = respNameCb;
wss.bindHandlers(handlers);
diff --git a/web/gui/src/main/webapp/app/view/host/host.js b/web/gui/src/main/webapp/app/view/host/host.js
index 37668bb..b95ebc8 100644
--- a/web/gui/src/main/webapp/app/view/host/host.js
+++ b/web/gui/src/main/webapp/app/view/host/host.js
@@ -62,7 +62,7 @@
var top = dps.top();
- dps.addHeading('host-icon');
+ dps.addHeading('host-icon', true);
top.append('div').classed('top-content', true);
top.append('hr');
diff --git a/web/gui/src/main/webapp/app/view/port/port.css b/web/gui/src/main/webapp/app/view/port/port.css
index 669cc0f..5ed80d3 100644
--- a/web/gui/src/main/webapp/app/view/port/port.css
+++ b/web/gui/src/main/webapp/app/view/port/port.css
@@ -45,3 +45,30 @@
#ov-port tr.no-data td {
text-align: center;
}
+
+#port-details-panel.floatpanel {
+ z-index: 0;
+}
+
+
+#port-details-panel .container {
+ padding: 8px 12px;
+}
+
+#port-details-panel .close-btn {
+ position: absolute;
+ right: 12px;
+ top: 12px;
+ cursor: pointer;
+}
+
+#port-details-panel .port-icon {
+ display: inline-block;
+ padding: 0 6px 0 0;
+ vertical-align: middle;
+}
+
+#port-details-panel h2 {
+ display: inline-block;
+ margin: 8px 0;
+}
diff --git a/web/gui/src/main/webapp/app/view/port/port.html b/web/gui/src/main/webapp/app/view/port/port.html
index 7a5caa6..a0a02be 100644
--- a/web/gui/src/main/webapp/app/view/port/port.html
+++ b/web/gui/src/main/webapp/app/view/port/port.html
@@ -96,7 +96,9 @@
</td>
</tr>
- <tr ng-repeat="port in tableData | filter: customFilter(queryFilter, query) track by $index"
+ <tr ng-repeat="port in tableData | filter: customFilter(queryFilter, query) track by $index">
+ ng-click="selectCallback($event, port)"
+ ng-class="{selected: port.id === selId}"
ng-repeat-complete row-id="{{port.id}}">
<td>{{port.id}}</td>
<td ng-class="(isDelta() ? 'delta' : 'right')">{{port.pkt_rx}}</td>
@@ -112,4 +114,6 @@
</div>
+ <port-details-panel></port-details-panel>
+
</div>
diff --git a/web/gui/src/main/webapp/app/view/port/port.js b/web/gui/src/main/webapp/app/view/port/port.js
index 915c6d5..be5d6dc 100644
--- a/web/gui/src/main/webapp/app/view/port/port.js
+++ b/web/gui/src/main/webapp/app/view/port/port.js
@@ -22,14 +22,42 @@
'use strict';
// injected references
- var $log, $scope, $location, tbs, ns, ps;
+ var $log, $scope, $location, tbs, fs, mast, wss, ns, prefs, dps, is, ps;
var nz = 'nzFilter',
del = 'showDelta';
// internal state
var nzFilter = true,
- showDelta = false;
+ showDelta = false,
+ detailsPanel,
+ pStartY,
+ pHeight,
+ wSize,
+ port;
+
+ // constants
+ var topPdg = 28,
+ dPanelWidth = 480,
+
+ pName = 'port-details-panel',
+ detailsReq = 'portDetailsRequest',
+ detailsResp = 'portDetailsResponse';
+
+
+ var keyBindings = {
+ esc: [closePanel, 'Close the details panel'],
+ _helpFormat: ['esc'],
+ };
+
+ function closePanel() {
+ if (detailsPanel.isVisible()) {
+ $scope.selId = null;
+ detailsPanel.hide();
+ return true;
+ }
+ return false;
+ }
var defaultPortPrefsState = {
nzFilter: 1,
@@ -40,7 +68,7 @@
function updatePrefsState(what, b) {
prefsState[what] = b ? 1 : 0;
- ps.setPrefs('port_prefs', prefsState);
+ prefs.setPrefs('port_prefs', prefsState);
}
function toggleNZState(b) {
@@ -62,8 +90,8 @@
}
function restoreConfigFromPrefs() {
- prefsState = ps.asNumbers(
- ps.getPrefs('port_prefs', defaultPortPrefsState)
+ prefsState = prefs.asNumbers(
+ prefs.getPrefs('port_prefs', defaultPortPrefsState)
);
$log.debug('Port - Prefs State:', prefsState);
@@ -71,21 +99,91 @@
toggleNZState(prefsState.nzFilter);
}
+ function createDetailsPanel() {
+ detailsPanel = dps.create(pName, {
+ width: wSize.width,
+ margin: 0,
+ hideMargin: 0,
+ scope: $scope,
+ keyBindings: keyBindings,
+ });
+
+ dps.setResponse(detailsResp, respDetailsCb);
+
+ $scope.hidePanel = function () { detailsPanel.hide(); };
+ }
+
+ function setUpPanel() {
+ dps.empty();
+ dps.addContainers();
+ dps.addCloseButton(closePanel);
+
+ var top = dps.top();
+
+ dps.addHeading('port-icon');
+ top.append('div').classed('top-content', true);
+
+ top.append('hr');
+ }
+
+ function friendlyPropsList(details) {
+ return {
+ 'ID': details['id'],
+ 'Device': details['devId'],
+ 'Type': details['type'],
+ 'Speed': details['speed'],
+ 'Enabled': details['enabled'],
+ };
+ }
+
+
+ function populateTop(tblDiv, details) {
+ is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
+ dps.top().select('h2').text(details.devId + ' port ' + details.id);
+ dps.addPropsList(tblDiv, friendlyPropsList(details));
+ }
+
+ function populateDetails(details) {
+ setUpPanel();
+ populateTop(dps.select('.top-content'), details);
+ detailsPanel.height(pHeight);
+ detailsPanel.width(dPanelWidth);
+
+ }
+
+ function respDetailsCb(data) {
+ $scope.panelData = data.details;
+ port = data.port;
+ $scope.$apply();
+ }
+
angular.module('ovPort', [])
.controller('OvPortCtrl', [
'$log', '$scope', '$location',
- 'TableBuilderService', 'NavService', 'PrefsService',
+ 'TableBuilderService', 'FnService', 'MastService', 'WebSocketService',
+ 'NavService', 'PrefsService', 'DetailsPanelService', 'IconService',
+ 'PanelService',
- function (_$log_, _$scope_, _$location_, _tbs_, _ns_, _ps_) {
+ function (_$log_, _$scope_, _$location_,
+ _tbs_, _fs_, _mast_, _wss_,
+ _ns_, _prefs_, _dps_, _is_, _ps_) {
var params;
var tableApi;
$log = _$log_;
$scope = _$scope_;
$location = _$location_;
tbs = _tbs_;
+ fs = _fs_;
+ mast = _mast_;
+ wss = _wss_;
ns = _ns_;
+ prefs = _prefs_;
+ dps = _dps_;
+ is = _is_;
ps = _ps_;
+ params = $location.search();
+
$scope.deviceTip = 'Show device table';
$scope.flowTip = 'Show flow view for this device';
$scope.groupTip = 'Show group view for this device';
@@ -94,7 +192,6 @@
$scope.toggleDeltaTip = 'Toggle port delta statistics';
$scope.toggleNZTip = 'Toggle non zero port statistics';
- params = $location.search();
if (params.hasOwnProperty('devId')) {
$scope.devId = params['devId'];
}
@@ -104,10 +201,23 @@
showDelta: showDelta,
};
+ function selCb($event, row) {
+ if ($scope.selId) {
+ wss.sendEvent(detailsReq, {
+ id: row.id,
+ devId: $scope.devId,
+ });
+ } else {
+ $scope.hidePanel();
+ }
+ $log.debug('Got a click on:', row);
+ }
+
tableApi = tbs.buildTable({
scope: $scope,
tag: 'port',
query: params,
+ selCb: selCb,
});
function filterToggleState() {
@@ -204,6 +314,74 @@
};
restoreConfigFromPrefs();
+
+ $scope.$on('$destroy', function () {
+ dps.destroy();
+ });
+
$log.log('OvPortCtrl has been created');
+ }])
+ .directive('portDetailsPanel',
+ ['$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();
+ createDetailsPanel();
+ }
+
+ // 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(keyBindings);
+
+ ks.gestureNotes([
+ ['click', 'Select a row to show port details'],
+ ['scroll down', 'See more ports'],
+ ]);
+
+ // 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);
+ });
+ };
}]);
}());