Bri Prebilic Cole
Committed by Gerrit Code Review

GUI -- WIP Device View details panel. Egress Links backend added, updated FnServ…

…ice, added CSS for panel, populates panel with properties and a close button.

Change-Id: Ia510b1e47fecc9140adcb1596c365a4114784b88
......@@ -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 org.onosproject.net.link.LinkService;
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 @@ public class DeviceViewMessageHandler extends AbstractTabularViewMessageHandler
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 @@ public class DeviceViewMessageHandler extends AbstractTabularViewMessageHandler
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 @@ public class DeviceViewMessageHandler extends AbstractTabularViewMessageHandler
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 @@ public class DeviceViewMessageHandler extends AbstractTabularViewMessageHandler
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) {
......
......@@ -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
......
......@@ -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();
});
}
......
......@@ -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;
}
......
......@@ -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>
......
......@@ -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, _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);
}
function ($log, $scope, tbs) {
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');
......
......@@ -386,6 +386,8 @@ describe('factory: fw/util/fn.js', function() {
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()
......