GUI -- WIP Device View details panel. Egress Links backend added, updated FnService, added CSS for panel, populates panel with properties and a close button.
Change-Id: Ia510b1e47fecc9140adcb1596c365a4114784b88
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
index a3c7e2f..b7fb40d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
@@ -15,12 +15,15 @@
*/
package org.onosproject.ui.impl;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableSet;
import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.link.LinkService;
@@ -28,31 +31,61 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-
-//import org.onosproject.net.Link;
-//import java.util.Set;
+import java.util.Set;
/**
* Message handler for device view related messages.
*/
public class DeviceViewMessageHandler extends AbstractTabularViewMessageHandler {
+ private static final String ID = "id";
+ private static final String TYPE = "type";
+ private static final String AVAILABLE = "available";
+ private static final String AVAILABLE_IID = "_iconid_available";
+ private static final String TYPE_IID = "_iconid_type";
+ private static final String DEV_ICON_PREFIX = "devIcon_";
+ private static final String NUM_PORTS = "num_ports";
+ private static final String LINK_DEST = "elinks_dest";
+ private static final String MFR = "mfr";
+ private static final String HW = "hw";
+ private static final String SW = "sw";
+ private static final String PROTOCOL = "protocol";
+ private static final String MASTER_ID = "masterid";
+ private static final String CHASSIS_ID = "chassisid";
+ private static final String SERIAL = "serial";
+ private static final String PORTS = "ports";
+ private static final String ENABLED = "enabled";
+ private static final String SPEED = "speed";
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+
/**
* Creates a new message handler for the device messages.
*/
protected DeviceViewMessageHandler() {
- super(ImmutableSet.of("deviceDataRequest"));
+ super(ImmutableSet.of("deviceDataRequest", "deviceDetailsRequest"));
}
@Override
- public void process(ObjectNode message) {
- ObjectNode payload = payload(message);
+ public void process(ObjectNode event) {
+ String type = string(event, "event", "unknown");
+ if (type.equals("deviceDataRequest")) {
+ dataRequest(event);
+ } else if (type.equals("deviceDetailsRequest")) {
+ detailsRequest(event);
+ }
+ }
+
+ private void dataRequest(ObjectNode event) {
+ ObjectNode payload = payload(event);
String sortCol = string(payload, "sortCol", "id");
String sortDir = string(payload, "sortDir", "asc");
DeviceService service = get(DeviceService.class);
MastershipService mastershipService = get(MastershipService.class);
LinkService linkService = get(LinkService.class);
+
TableRow[] rows = generateTableRows(service,
mastershipService,
linkService);
@@ -66,6 +99,37 @@
connection().sendMessage("deviceDataResponse", 0, rootNode);
}
+ private void detailsRequest(ObjectNode event) {
+ ObjectNode payload = payload(event);
+ String id = string(payload, "id", "of:0000000000000000");
+
+ DeviceId deviceId = DeviceId.deviceId(id);
+ DeviceService service = get(DeviceService.class);
+ MastershipService ms = get(MastershipService.class);
+ Device device = service.getDevice(deviceId);
+ ObjectNode data = MAPPER.createObjectNode();
+
+ data.put(ID, deviceId.toString());
+ data.put(TYPE, device.type().toString());
+ data.put(MFR, device.manufacturer());
+ data.put(HW, device.hwVersion());
+ data.put(SW, device.swVersion());
+ data.put(SERIAL, device.serialNumber());
+ data.put(CHASSIS_ID, device.chassisId().toString());
+ data.put(MASTER_ID, ms.getMasterFor(deviceId).toString());
+ data.put(PROTOCOL, device.annotations().value(PROTOCOL));
+
+ ArrayNode ports = MAPPER.createArrayNode();
+ for (Port p : service.getPorts(deviceId)) {
+ ports.add(portData(p, deviceId));
+ }
+ data.set(PORTS, ports);
+
+ ObjectNode rootNode = mapper.createObjectNode();
+ rootNode.set("details", data);
+ connection().sendMessage("deviceDetailsResponse", 0, rootNode);
+ }
+
private TableRow[] generateTableRows(DeviceService service,
MastershipService mastershipService,
LinkService linkService) {
@@ -79,48 +143,44 @@
return list.toArray(new TableRow[list.size()]);
}
+ private ObjectNode portData(Port p, DeviceId id) {
+ ObjectNode port = MAPPER.createObjectNode();
+ LinkService ls = get(LinkService.class);
+
+ port.put(ID, p.number().toString());
+ port.put(TYPE, p.type().toString());
+ port.put(SPEED, p.portSpeed());
+ port.put(ENABLED, p.isEnabled());
+
+ Set<Link> links = ls.getEgressLinks(new ConnectPoint(id, p.number()));
+ if (!links.isEmpty()) {
+ String egressLinks = "";
+ for (Link l : links) {
+ ConnectPoint dest = l.dst();
+ egressLinks += dest.elementId().toString()
+ + "/" + dest.port().toString() + " ";
+ }
+ port.put(LINK_DEST, egressLinks);
+ }
+
+ return port;
+ }
+
/**
* TableRow implementation for {@link Device devices}.
*/
private static class DeviceTableRow extends AbstractTableRow {
- private static final String ID = "id";
- private static final String AVAILABLE = "available";
- private static final String AVAILABLE_IID = "_iconid_available";
- private static final String TYPE_IID = "_iconid_type";
- private static final String DEV_ICON_PREFIX = "devIcon_";
- private static final String NUM_PORTS = "num_ports";
- private static final String NUM_EGRESS_LINKS = "num_elinks";
- private static final String MFR = "mfr";
- private static final String HW = "hw";
- private static final String SW = "sw";
- private static final String PROTOCOL = "protocol";
- private static final String MASTERID = "masterid";
- private static final String CHASSISID = "chassisid";
- private static final String SERIAL = "serial";
-
private static final String[] COL_IDS = {
AVAILABLE, AVAILABLE_IID, TYPE_IID, ID,
- NUM_PORTS, NUM_EGRESS_LINKS, MASTERID, MFR, HW, SW,
- PROTOCOL, CHASSISID, SERIAL
+ NUM_PORTS, MASTER_ID, MFR, HW, SW,
+ PROTOCOL, CHASSIS_ID, SERIAL
};
private static final String ICON_ID_ONLINE = "deviceOnline";
private static final String ICON_ID_OFFLINE = "deviceOffline";
// TODO: use in details pane
-// private String getPorts(List<Port> ports) {
-// String formattedString = "";
-// int numPorts = 0;
-//
-// for (Port p : ports) {
-// numPorts++;
-// formattedString += p.number().toString() + ", ";
-// }
-// return formattedString + "Total: " + numPorts;
-// }
-
- // TODO: use in details pane
// private String getEgressLinks(Set<Link> links) {
// String formattedString = "";
//
@@ -139,7 +199,6 @@
String iconId = available ? ICON_ID_ONLINE : ICON_ID_OFFLINE;
DeviceId id = d.id();
List<Port> ports = service.getPorts(id);
-// Set<Link> links = ls.getDeviceEgressLinks(id);
add(ID, id.toString());
add(AVAILABLE, Boolean.toString(available));
@@ -148,12 +207,9 @@
add(MFR, d.manufacturer());
add(HW, d.hwVersion());
add(SW, d.swVersion());
-// add(SERIAL, d.serialNumber());
add(PROTOCOL, d.annotations().value(PROTOCOL));
add(NUM_PORTS, Integer.toString(ports.size()));
-// add(NUM_EGRESS_LINKS, Integer.toString(links.size()));
-// add(CHASSISID, d.chassisId().toString());
- add(MASTERID, ms.getMasterFor(d.id()).toString());
+ add(MASTER_ID, ms.getMasterFor(d.id()).toString());
}
private String getTypeIconId(Device d) {
diff --git a/web/gui/src/main/webapp/app/fw/README.txt b/web/gui/src/main/webapp/app/fw/README.txt
index e173c52..5ce4ebf 100644
--- a/web/gui/src/main/webapp/app/fw/README.txt
+++ b/web/gui/src/main/webapp/app/fw/README.txt
@@ -5,6 +5,7 @@
- Key Handler
- Theme Service
- Alert Service
+ - Preference Service
- Mast
- Masthead
@@ -27,4 +28,6 @@
- Widget
- Table Styling Directives
+ - Table Builder Service
- Toolbar Service
+ - Button Service
diff --git a/web/gui/src/main/webapp/app/fw/util/fn.js b/web/gui/src/main/webapp/app/fw/util/fn.js
index aa546e1..56e105b 100644
--- a/web/gui/src/main/webapp/app/fw/util/fn.js
+++ b/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -161,7 +161,7 @@
// return the given string with the first character capitalized.
function cap(s) {
- return s.replace(/^[a-z]/, function (m) {
+ return s.toLowerCase().replace(/^[a-z]/, function (m) {
return m.toUpperCase();
});
}
diff --git a/web/gui/src/main/webapp/app/view/device/device.css b/web/gui/src/main/webapp/app/view/device/device.css
index 53cdabe..4b11f21 100644
--- a/web/gui/src/main/webapp/app/view/device/device.css
+++ b/web/gui/src/main/webapp/app/view/device/device.css
@@ -18,5 +18,86 @@
ONOS GUI -- Device View -- CSS file
*/
-#ov-device td {
+/* More in generic panel.css */
+
+#device-details-panel.floatpanel {
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.light #device-details-panel.floatpanel {
+ background-color: rgb(226, 248, 255);
+}
+.dark #device-details-panel.floatpanel {
+ background-color: #444;
+}
+
+#device-details-panel .container {
+ padding: 0 12px;
+}
+
+#device-details-panel .close-btn {
+ position: absolute;
+ right: 10px;
+ top: 0;
+}
+.close-btn svg.embeddedIcon .icon.appPlus .glyph {
+ /* works for both dark and light themes */
+ fill: #ccc;
+}
+
+#device-details-panel h2 {
+ margin: 8px 0;
+}
+
+#device-details-panel td.label {
+ font-style: italic;
+ padding-right: 12px;
+ /* works for both light and dark themes ... */
+ color: #777;
+}
+
+#device-details-panel hr {
+ margin: 12px 0;
+}
+
+.light #device-details-panel hr {
+ opacity: .5;
+ border-color: #FFF;
+}
+.dark #device-details-panel hr {
+ border-color: #666;
+}
+
+#device-details-panel .bottom table {
+ border-spacing: 0;
+}
+
+#device-details-panel .bottom th {
+ letter-spacing: 0.02em;
+}
+
+.light #device-details-panel .bottom th {
+ background-color: #D0E1ED;
+ /* default text color */
+}
+.dark #device-details-panel .bottom th {
+ background-color: #2b2b2b;
+ color: #ccc;
+}
+
+#device-details-panel .bottom th,
+#device-details-panel .bottom td {
+ padding: 6px 12px;
+ text-align: center;
+}
+
+.light #device-details-panel .bottom tr:nth-child(odd) {
+ background-color: #f9f9f9;
+}
+.light #device-details-panel .bottom tr:nth-child(even) {
+ background-color: #EBEDF2;
+}
+.dark #device-details-panel .bottom tr:nth-child(odd) {
+ background-color: #333;
}
diff --git a/web/gui/src/main/webapp/app/view/device/device.html b/web/gui/src/main/webapp/app/view/device/device.html
index ffd7b81..4cc1e3d 100644
--- a/web/gui/src/main/webapp/app/view/device/device.html
+++ b/web/gui/src/main/webapp/app/view/device/device.html
@@ -27,6 +27,8 @@
</tr>
<tr ng-repeat="dev in ctrl.tableData"
+ ng-click="selectCallback(dev)"
+ ng-class="{selected: dev === sel}"
ng-repeat-done>
<td class="table-icon">
<div icon icon-id="{{dev._iconid_available}}"></div>
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 cd8d8b0..fbe5dbb 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -21,15 +21,209 @@
(function () {
'use strict';
+ // injected refs
+ var $log, $scope, fs, mast, ps, wss, is;
+
+ // internal state
+ var self,
+ detailsPane,
+ container, top, bottom, closeBtn;
+
+ // constants
+ // TODO: consider having a set y height that all tables start at
+ var h2Pdg = 40,
+ mastPdg = 8,
+ tbodyPdg = 5,
+ cntrPdg = 24,
+ pName = 'device-details-panel',
+ detailsReq = 'deviceDetailsRequest',
+ detailsResp = 'deviceDetailsResponse',
+ propOrder = [
+ 'type', 'masterid', 'chassisid',
+ 'mfr', 'hw', 'sw', 'protocol', 'serial'
+ ],
+ friendlyProps = [
+ 'Type', 'Master ID', 'Chassis ID',
+ 'Vendor', 'H/W Version', 'S/W Version', 'Protocol', 'Serial #'
+ ],
+ portCols = [
+ 'enabled', 'id', 'speed', 'type', 'elinks_dest'
+ ],
+ friendlyPortCols = [
+ 'Enabled', 'ID', 'Speed', 'Type', 'Egress Links'
+ ];
+
+ function setUpPanel() {
+ detailsPane.empty();
+
+ container = detailsPane.append('div').classed('container', true);
+
+ top = container.append('div').classed('top', true);
+ closeBtn = top.append('div').classed('close-btn', true);
+ addCloseBtn(closeBtn);
+ top.append('h2');
+ top.append('table');
+
+ container.append('hr');
+
+ bottom = container.append('div').classed('bottom', true);
+ bottom.append('h2').text('Ports');
+ bottom.append('table');
+ }
+
+ function createDetailsPane() {
+ var headerHeight = h2Pdg + fs.noPxStyle(d3.select('h2'), 'height'),
+ panelTop = headerHeight + tbodyPdg + mast.mastHeight() + mastPdg,
+ wSize = fs.windowSize(panelTop);
+
+ detailsPane = ps.createPanel(pName, {
+ height: wSize.height,
+ width: wSize.width / 2,
+ margin: 0,
+ hideMargin: 0
+ });
+
+ detailsPane.el().style({
+ position: 'absolute',
+ top: panelTop + 'px'
+ });
+
+ setUpPanel();
+
+ detailsPane.hide();
+ }
+
+ function addCloseBtn(div) {
+ is.loadEmbeddedIcon(div, 'appPlus', 30);
+ div.select('g').attr('transform', 'translate(25, 0) rotate(45)');
+ div.on('click', function () {
+ detailsPane.hide();
+ // TODO: deselect the table row when button is clicked
+ //$scope.sel = null;
+ });
+ }
+
+ function populateTopHalf(tbody, details) {
+ top.select('h2').text(details['id']);
+
+ propOrder.forEach(function (prop, i) {
+ addProp(tbody, i, details[prop]);
+ });
+ }
+
+ function populateBottomHalf(table, ports) {
+ var theader = table.append('thead').append('tr'),
+ tbody = table.append('tbody'),
+ tbWidth, tbHeight,
+ scrollSize = 20,
+ btmPdg = 50;
+
+ friendlyPortCols.forEach(function (header) {
+ theader.append('th').html(header);
+ });
+ ports.forEach(function (port) {
+ addPortRow(tbody, port);
+ });
+
+ tbWidth = fs.noPxStyle(tbody, 'width') + scrollSize;
+ tbHeight = detailsPane.height()
+ - (fs.noPxStyle(detailsPane.el().select('.top'), 'height')
+ + fs.noPxStyle(detailsPane.el().select('hr'), 'height')
+ + fs.noPxStyle(detailsPane.el().select('h2'), 'height')
+ + btmPdg);
+
+ table.style({
+ height: tbHeight + 'px',
+ width: tbWidth + 'px',
+ overflow: 'auto',
+ display: 'block'
+ });
+
+ detailsPane.width(tbWidth + cntrPdg);
+ }
+
+ 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 addPortRow(tbody, port) {
+ var tr = tbody.append('tr');
+
+ portCols.forEach(function (col) {
+ if (col === 'type' || col === 'id') {
+ port[col] = fs.cap(port[col]);
+ }
+ tr.append('td').html(port[col]);
+ });
+ }
+
+ function populateDetails(details) {
+ setUpPanel();
+
+ var toptbody = top.select('table').append('tbody'),
+ btmTable = bottom.select('table'),
+ ports = details.ports;
+
+ populateTopHalf(toptbody, details);
+ populateBottomHalf(btmTable, ports);
+ }
+
+ function respDetailsCb(data) {
+ self.panelData = data['details'];
+ populateDetails(self.panelData);
+ detailsPane.show();
+ }
+
angular.module('ovDevice', [])
.controller('OvDeviceCtrl',
- ['$log', '$scope', 'TableBuilderService',
+ ['$log', '$scope', 'TableBuilderService', 'FnService',
+ 'MastService', 'PanelService', 'WebSocketService', 'IconService',
- function ($log, $scope, tbs) {
+ function (_$log_, _$scope_, tbs, _fs_, _mast_, _ps_, _wss_, _is_) {
+ $log = _$log_;
+ $scope = _$scope_;
+ fs = _fs_;
+ mast = _mast_;
+ ps = _ps_;
+ wss = _wss_;
+ is = _is_;
+ self = this;
+ var handlers = {};
+ self.panelData = [];
+
+ function selCb(row) {
+ // request the server for more information
+ // get the id from the row to request details with
+ if ($scope.sel) {
+ wss.sendEvent(detailsReq, { id: row.id });
+ } else {
+ detailsPane.hide();
+ }
+ $log.debug('Got a click on:', row);
+ }
+
tbs.buildTable({
- self: this,
+ self: self,
scope: $scope,
- tag: 'device'
+ tag: 'device',
+ selCb: selCb
+ });
+
+ createDetailsPane();
+
+ // bind websocket handlers
+ handlers[detailsResp] = respDetailsCb;
+ wss.bindHandlers(handlers);
+
+ $scope.$on('$destroy', function () {
+ ps.destroyPanel(pName);
+ wss.unbindHandlers(handlers);
});
$log.log('OvDeviceCtrl has been created');
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
index cbce523..d04b3c2 100644
--- a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
@@ -386,6 +386,8 @@
expect(fs.cap('Foo')).toEqual('Foo');
expect(fs.cap('foo')).toEqual('Foo');
expect(fs.cap('foo bar')).toEqual('Foo bar');
+ expect(fs.cap('FOO BAR')).toEqual('Foo bar');
+ expect(fs.cap('foo Bar')).toEqual('Foo bar');
});
// === Tests for noPx()