ONOS-5409: Added details panel to Meters view
Change-Id: Id0614572f7b9e4233dacbfa908d03973fba42a17
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java
index 8371353..734551a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java
@@ -24,6 +24,7 @@
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.meter.Band;
import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterId;
import org.onosproject.net.meter.MeterService;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiMessageHandler;
@@ -52,12 +53,20 @@
private static final Set<String> UNSUPPORTED_PROTOCOLS =
ImmutableSet.of(OF_10, OF_11, OF_12);
+ private static final String METER_DETAILS_REQ = "meterDetailsRequest";
+ private static final String METER_DETAILS_RESP = "meterDetailsResponse";
+ private static final String DETAILS = "details";
+
+ private static final String DEV_ID = "devId";
private static final String ID = "id";
private static final String APP_ID = "app_id";
private static final String STATE = "state";
private static final String PACKETS = "packets";
private static final String BYTES = "bytes";
private static final String BANDS = "bands";
+ private static final String BURST = "isBurst";
+ private static final String LIFE = "life";
+ private static final String TYPE_IID = "_iconid_type";
private static final String[] COL_IDS = {
ID, APP_ID, STATE, PACKETS, BYTES, BANDS
@@ -65,7 +74,10 @@
@Override
protected Collection<RequestHandler> createRequestHandlers() {
- return ImmutableSet.of(new MeterDataRequest());
+ return ImmutableSet.of(
+ new MeterDataRequest(),
+ new DetailRequestHandler()
+ );
}
// handler for meter table requests
@@ -160,4 +172,43 @@
}
}
}
+
+
+ private final class DetailRequestHandler extends RequestHandler {
+ private DetailRequestHandler() {
+ super(METER_DETAILS_REQ);
+ }
+
+ @Override
+ public void process(ObjectNode payload) {
+ Long id = Long.decode(string(payload, ID));
+ String devId = string(payload, DEV_ID);
+
+ DeviceId deviceId = DeviceId.deviceId(devId);
+ MeterService ms = get(MeterService.class);
+ MeterId meterId = MeterId.meterId(id);
+ Meter meter = ms.getMeter(deviceId, meterId);
+
+ ObjectNode data = objectNode();
+
+ data.put(ID, id);
+ data.put(DEV_ID, devId);
+ data.put(APP_ID, meter.appId().name());
+ data.put(BYTES, meter.bytesSeen());
+ data.put(BURST, meter.isBurst());
+ data.put(LIFE, meter.life());
+ data.put(PACKETS, meter.packetsSeen());
+ data.put(STATE, meter.state().toString());
+
+ data.put(TYPE_IID, "meter");
+
+ 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 meter. See ONOS-5976.
+ // TODO: rootNode.set(METER, getJsonCodecContext().encode(meter, Meter.class));
+ sendMessage(METER_DETAILS_RESP, rootNode);
+ }
+ }
}
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 39dd26c..edc1c5e 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
@@ -103,7 +103,6 @@
}
function addProp(tbody, key, value) {
- console.log(tbody);
var tr = tbody.append('tr');
function addCell(cls, txt, width) {
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 f0c4063..af61e52 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.js
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -61,6 +61,8 @@
portIcon_DEFAULT: 'm_ports',
+ meter: 'meterTable', // TODO: m_meter icon?
+
deviceTable: 'switch',
flowTable: 'flowTable',
portTable: 'portTable',
diff --git a/web/gui/src/main/webapp/app/view/meter/meter.css b/web/gui/src/main/webapp/app/view/meter/meter.css
index b1abf46..be1b6e7 100644
--- a/web/gui/src/main/webapp/app/view/meter/meter.css
+++ b/web/gui/src/main/webapp/app/view/meter/meter.css
@@ -36,3 +36,29 @@
padding-left: 36px;
opacity: 0.65;
}
+
+#meter-details-panel.floatpanel {
+ z-index: 0;
+}
+
+#meter-details-panel .container {
+ padding: 8px 12px;
+}
+
+#meter-details-panel .close-btn {
+ position: absolute;
+ right: 12px;
+ top: 12px;
+ cursor: pointer;
+}
+
+#meter-details-panel .port-icon {
+ display: inline-block;
+ padding: 0 6px 0 0;
+ vertical-align: middle;
+}
+
+#meter-details-panel h2 {
+ display: inline-block;
+ margin: 8px 0;
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/meter/meter.html b/web/gui/src/main/webapp/app/view/meter/meter.html
index ddd17f0..cd54db9 100644
--- a/web/gui/src/main/webapp/app/view/meter/meter.html
+++ b/web/gui/src/main/webapp/app/view/meter/meter.html
@@ -78,6 +78,8 @@
</tr>
<tr ng-repeat-start="meter in tableData | filter:queryFilter track by $index"
+ ng-click="selectCallback($event, meter)"
+ ng-class="{selected: meter.id === selId}"
ng-repeat-complete row-id="{{meter.id}}">
<td>{{meter.id}}</td>
<td>{{meter.app_id}}</td>
@@ -94,4 +96,6 @@
</div>
+ <meter-details-panel></meter-details-panel>
+
</div>
diff --git a/web/gui/src/main/webapp/app/view/meter/meter.js b/web/gui/src/main/webapp/app/view/meter/meter.js
index 32058b6..fb4d15d 100644
--- a/web/gui/src/main/webapp/app/view/meter/meter.js
+++ b/web/gui/src/main/webapp/app/view/meter/meter.js
@@ -21,14 +21,109 @@
'use strict';
// injected references
- var $log, $scope, $location, fs, tbs, ns;
+ var $log, $scope, $location, fs, tbs, ns, prefs,
+ fs, mast, wss, ns, dps, is, ps;
+
+ var detailsPanel,
+ pStartY,
+ pHeight,
+ wSize,
+ meter;
+
+ // constants
+ var topPdg = 28,
+ dPanelWidth = 480,
+
+ pName = 'meter-details-panel',
+ detailsReq = 'meterDetailsRequest',
+ detailsResp = 'meterDetailsResponse';
+
+
+ var keyBindings = {
+ esc: [closePanel, 'Close the details panel'],
+ _helpFormat: ['esc'],
+ };
+
+ function closePanel() {
+ if (detailsPanel.isVisible()) {
+ $scope.selId = null;
+ detailsPanel.hide();
+ return true;
+ }
+ return false;
+ }
+
+ 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) {
+ $log.debug(details);
+ return {
+ 'ID': details['id'],
+ 'Device': details['devId'],
+ 'App Id': details['app_id'],
+ 'Bytes': details['bytes'],
+ 'Burst': details['isBurst'],
+ 'Packets': details['packets'],
+ 'State': details['state'],
+ };
+ }
+
+
+ function populateTop(tblDiv, details) {
+ is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
+ dps.top().select('h2').text(details.devId + ' Meter ' + 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;
+ meter = data.meter;
+ $scope.$apply();
+ }
angular.module('ovMeter', [])
.controller('OvMeterCtrl',
['$log', '$scope', '$location', '$sce',
- 'FnService', 'TableBuilderService', 'NavService',
+ 'FnService', 'TableBuilderService', 'NavService', 'PrefsService',
+ 'MastService', 'WebSocketService', 'DetailsPanelService', 'IconService',
+ 'PanelService',
- function (_$log_, _$scope_, _$location_, $sce, _fs_, _tbs_, _ns_) {
+ function (_$log_, _$scope_, _$location_, $sce,
+ _fs_, _tbs_, _ns_, _prefs_,
+ _mast_, _wss_, _dps_, _is_, _ps_) {
var params;
$log = _$log_;
$scope = _$scope_;
@@ -36,6 +131,13 @@
fs = _fs_;
tbs = _tbs_;
ns = _ns_;
+ fs = _fs_;
+ mast = _mast_;
+ wss = _wss_;
+ prefs = _prefs_;
+ dps = _dps_;
+ is = _is_;
+
$scope.deviceTip = 'Show device table';
$scope.flowTip = 'Show flow view for this device';
$scope.portTip = 'Show port view for this device';
@@ -47,10 +149,23 @@
$scope.devId = params['devId'];
}
+ 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);
+ }
+
tbs.buildTable({
scope: $scope,
tag: 'meter',
query: params,
+ selCb: selCb,
});
$scope.$watch('tableData', function () {
@@ -75,7 +190,73 @@
},
});
+ $scope.$on('$destroy', function () {
+ dps.destroy();
+ });
$log.log('OvMeterCtrl has been created');
- }]);
+ }])
+ .directive('meterDetailsPanel',
+ ['$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);
+ });
+ };
+ }]);
}());
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 a0a02be..d35c41a 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,7 @@
</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}}">