Simon Hunt
Committed by Gerrit Code Review

ONOS-1479 -- GUI - augmenting topology view for extensibility:

- Preliminary work in implementing installation of custom buttons to details panel for selected device.

Change-Id: Id26ac301f72b4521d2a388d34ee0a287f400c68c
...@@ -35,6 +35,7 @@ public class PropertyPanel { ...@@ -35,6 +35,7 @@ public class PropertyPanel {
35 private String typeId; 35 private String typeId;
36 private String id; 36 private String id;
37 private List<Prop> properties = new ArrayList<>(); 37 private List<Prop> properties = new ArrayList<>();
38 + private List<Button> buttons = new ArrayList<>();
38 39
39 /** 40 /**
40 * Constructs a property panel model with the given title and 41 * Constructs a property panel model with the given title and
...@@ -174,6 +175,16 @@ public class PropertyPanel { ...@@ -174,6 +175,16 @@ public class PropertyPanel {
174 return properties; 175 return properties;
175 } 176 }
176 177
178 + /**
179 + * Returns the list of button descriptors.
180 + *
181 + * @return the button list
182 + */
183 + // TODO: consider protecting this?
184 + public List<Button> buttons() {
185 + return buttons;
186 + }
187 +
177 // == MUTATORS 188 // == MUTATORS
178 189
179 /** 190 /**
...@@ -226,6 +237,17 @@ public class PropertyPanel { ...@@ -226,6 +237,17 @@ public class PropertyPanel {
226 return this; 237 return this;
227 } 238 }
228 239
240 + /**
241 + * Adds a button descriptor with the given identifier, to the panel data.
242 + *
243 + * @param id button identifier
244 + * @return self, for chaining
245 + */
246 + public PropertyPanel addButton(String id) {
247 + buttons.add(new Button(id));
248 + return this;
249 + }
250 +
229 // ==================== 251 // ====================
230 252
231 253
...@@ -300,4 +322,29 @@ public class PropertyPanel { ...@@ -300,4 +322,29 @@ public class PropertyPanel {
300 } 322 }
301 } 323 }
302 324
325 + /**
326 + * Button descriptor. Note that these work in conjunction with
327 + * "buttons" defined in the JavaScript code for the overlay.
328 + */
329 + public static class Button {
330 + private final String id;
331 +
332 + /**
333 + * Constructs a button descriptor with the given identifier.
334 + *
335 + * @param id button identifier
336 + */
337 + public Button(String id) {
338 + this.id = id;
339 + }
340 +
341 + /**
342 + * Returns the identifier for this button.
343 + *
344 + * @return button identifier
345 + */
346 + public String id() {
347 + return id;
348 + }
349 + }
303 } 350 }
......
...@@ -73,4 +73,37 @@ public final class TopoConstants { ...@@ -73,4 +73,37 @@ public final class TopoConstants {
73 public static final String STOP = "stop"; 73 public static final String STOP = "stop";
74 public static final String CLOUD = "cloud"; 74 public static final String CLOUD = "cloud";
75 } 75 }
76 +
77 + /**
78 + * Defines constants for property names on the default summary and
79 + * details panels.
80 + */
81 + public static final class Properties {
82 + // summary panel
83 + public static final String DEVICES = "Devices";
84 + public static final String LINKS = "Links";
85 + public static final String HOSTS = "Hosts";
86 + public static final String TOPOLOGY_SSCS = "Topology SCCs";
87 + public static final String INTENTS = "Intents";
88 + public static final String TUNNELS = "Tunnels";
89 + public static final String FLOWS = "Flows";
90 + public static final String VERSION = "Version";
91 +
92 + // device details
93 + public static final String URI = "URI";
94 + public static final String VENDOR = "Vendor";
95 + public static final String HW_VERSION = "H/W Version";
96 + public static final String SW_VERSION = "S/W Version";
97 + public static final String SERIAL_NUMBER = "Serial Number";
98 + public static final String PROTOCOL = "Protocol";
99 + public static final String LATITUDE = "Latitude";
100 + public static final String LONGITUDE = "Longitude";
101 + public static final String PORTS = "Ports";
102 +
103 + // host details
104 + public static final String MAC = "MAC";
105 + public static final String IP = "IP";
106 + public static final String VLAN = "VLAN";
107 + }
108 +
76 } 109 }
......
...@@ -107,6 +107,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; ...@@ -107,6 +107,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
107 import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED; 107 import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
108 import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW; 108 import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
109 import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT; 109 import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
110 +import static org.onosproject.ui.topo.TopoConstants.*;
110 111
111 /** 112 /**
112 * Facility for creating messages bound for the topology viewer. 113 * Facility for creating messages bound for the topology viewer.
...@@ -448,15 +449,15 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -448,15 +449,15 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
448 Topology topology = topologyService.currentTopology(); 449 Topology topology = topologyService.currentTopology();
449 450
450 return new PropertyPanel("ONOS Summary", "node") 451 return new PropertyPanel("ONOS Summary", "node")
451 - .addProp("Devices", topology.deviceCount()) 452 + .addProp(Properties.DEVICES, topology.deviceCount())
452 - .addProp("Links", topology.linkCount()) 453 + .addProp(Properties.LINKS, topology.linkCount())
453 - .addProp("Hosts", hostService.getHostCount()) 454 + .addProp(Properties.HOSTS, hostService.getHostCount())
454 - .addProp("Topology SCCs", topology.clusterCount()) 455 + .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
455 .addSeparator() 456 .addSeparator()
456 - .addProp("Intents", intentService.getIntentCount()) 457 + .addProp(Properties.INTENTS, intentService.getIntentCount())
457 - .addProp("Tunnels", tunnelService.tunnelCount()) 458 + .addProp(Properties.TUNNELS, tunnelService.tunnelCount())
458 - .addProp("Flows", flowService.getFlowRuleCount()) 459 + .addProp(Properties.FLOWS, flowService.getFlowRuleCount())
459 - .addProp("Version", version); 460 + .addProp(Properties.VERSION, version);
460 } 461 }
461 462
462 // Returns property panel model for device details response. 463 // Returns property panel model for device details response.
...@@ -472,20 +473,20 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -472,20 +473,20 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
472 String typeId = device.type().toString().toLowerCase(); 473 String typeId = device.type().toString().toLowerCase();
473 474
474 PropertyPanel pp = new PropertyPanel(title, typeId) 475 PropertyPanel pp = new PropertyPanel(title, typeId)
475 - .id(deviceId.toString()) 476 + .id(deviceId.toString())
476 - .addProp("URI", deviceId.toString()) 477 + .addProp(Properties.URI, deviceId.toString())
477 - .addProp("Vendor", device.manufacturer()) 478 + .addProp(Properties.VENDOR, device.manufacturer())
478 - .addProp("H/W Version", device.hwVersion()) 479 + .addProp(Properties.HW_VERSION, device.hwVersion())
479 - .addProp("S/W Version", device.swVersion()) 480 + .addProp(Properties.SW_VERSION, device.swVersion())
480 - .addProp("Serial Number", device.serialNumber()) 481 + .addProp(Properties.SERIAL_NUMBER, device.serialNumber())
481 - .addProp("Protocol", annot.value(AnnotationKeys.PROTOCOL)) 482 + .addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
482 - .addSeparator() 483 + .addSeparator()
483 - .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE)) 484 + .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
484 - .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE)) 485 + .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
485 - .addSeparator() 486 + .addSeparator()
486 - .addProp("Ports", portCount) 487 + .addProp(Properties.PORTS, portCount)
487 - .addProp("Flows", flowCount) 488 + .addProp(Properties.FLOWS, flowCount)
488 - .addProp("Tunnels", tunnelCount); 489 + .addProp(Properties.TUNNELS, tunnelCount);
489 490
490 // TODO: add button descriptors 491 // TODO: add button descriptors
491 492
...@@ -570,13 +571,13 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -570,13 +571,13 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
570 String typeId = isNullOrEmpty(type) ? "endstation" : type; 571 String typeId = isNullOrEmpty(type) ? "endstation" : type;
571 572
572 PropertyPanel pp = new PropertyPanel(title, typeId) 573 PropertyPanel pp = new PropertyPanel(title, typeId)
573 - .id(hostId.toString()) 574 + .id(hostId.toString())
574 - .addProp("MAC", host.mac()) 575 + .addProp(Properties.MAC, host.mac())
575 - .addProp("IP", host.ipAddresses(), "[\\[\\]]") 576 + .addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
576 - .addProp("VLAN", vlan.equals("-1") ? "none" : vlan) 577 + .addProp(Properties.VLAN, vlan.equals("-1") ? "none" : vlan)
577 - .addSeparator() 578 + .addSeparator()
578 - .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE)) 579 + .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
579 - .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE)); 580 + .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
580 581
581 // TODO: add button descriptors 582 // TODO: add button descriptors
582 return pp; 583 return pp;
...@@ -859,6 +860,12 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -859,6 +860,12 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
859 } 860 }
860 result.set("propOrder", porder); 861 result.set("propOrder", porder);
861 result.set("props", pnode); 862 result.set("props", pnode);
863 +
864 + ArrayNode buttons = arrayNode();
865 + for (PropertyPanel.Button b : pp.buttons()) {
866 + buttons.add(b.id());
867 + }
868 + result.set("buttons", buttons);
862 return result; 869 return result;
863 } 870 }
864 871
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + */
17 +
18 +package org.meowster.over;
19 +
20 +import org.onosproject.ui.UiTopoOverlay;
21 +import org.onosproject.ui.topo.PropertyPanel;
22 +import org.onosproject.ui.topo.TopoConstants.Glyphs;
23 +
24 +import static org.onosproject.ui.topo.TopoConstants.Properties.*;
25 +
26 +/**
27 + * Our topology overlay.
28 + */
29 +public class AppUiTopoOverlay extends UiTopoOverlay {
30 +
31 + // NOTE: this must match the ID defined in topov.js
32 + private static final String OVERLAY_ID = "meowster-overlay";
33 +
34 + private static final String MY_TITLE = "I changed the title";
35 + private static final String MY_VERSION = "Beta-1.0.0042";
36 + private static final String FOO = "foo";
37 + private static final String BAR = "bar";
38 +
39 +
40 + public AppUiTopoOverlay() {
41 + super(OVERLAY_ID);
42 + }
43 +
44 +
45 + @Override
46 + public void modifySummary(PropertyPanel pp) {
47 + pp.title("My App Rocks!")
48 + .typeId(Glyphs.CROWN)
49 + .removeProps(
50 + TOPOLOGY_SSCS,
51 + INTENTS,
52 + TUNNELS,
53 + FLOWS,
54 + VERSION
55 + )
56 + .addProp(VERSION, MY_VERSION);
57 +
58 + }
59 +
60 + @Override
61 + public void modifyDeviceDetails(PropertyPanel pp) {
62 + pp.title(MY_TITLE);
63 + pp.removeProps(LATITUDE, LONGITUDE);
64 + pp.addButton(FOO).addButton(BAR);
65 + }
66 +
67 +// TODO: override more methods, as required...
68 +
69 +}
1 +NOTE:
2 +
3 +This directory is putting under revision control some key files from
4 +an example stand-alone app, as I develop the topology overlay functionality.
...@@ -7,21 +7,52 @@ ...@@ -7,21 +7,52 @@
7 7
8 // our overlay definition 8 // our overlay definition
9 var overlay = { 9 var overlay = {
10 - overlayId: 'sampleTopoOver', 10 + // NOTE: this must match the ID defined in AppUiTopoOverlay
11 - 11 + overlayId: 'meowster-overlay',
12 - // NOTE: for the glyph, could alternately use id: <existingGlyphId> 12 + glyphId: '*star4',
13 - // instead of defining viewbox and data (vb, d) 13 + tooltip: 'Sample Meowster Topo Overlay',
14 - // glyph: { id: 'crown' } 14 +
15 - glyph: { 15 + // These glyphs get installed using the overlayId as a prefix.
16 - vb: '0 0 8 8', 16 + // e.g. 'star4' is installed as 'meowster-overlay-star4'
17 - d: 'M1,4l2,-1l1,-2l1,2l2,1l-2,1l-1,2l-1,-2z' 17 + // They can be referenced (from this overlay) as '*star4'
18 + // That is, the '*' prefix stands in for 'meowster-overlay-'
19 + glyphs: {
20 + star4: {
21 + vb: '0 0 8 8',
22 + d: 'M1,4l2,-1l1,-2l1,2l2,1l-2,1l-1,2l-1,-2z'
23 + },
24 + banner: {
25 + vb: '0 0 6 6',
26 + d: 'M1,1v4l2,-2l2,2v-4z'
27 + }
18 }, 28 },
19 - tooltip: 'Sample Topology Overlay',
20 29
21 activate: activateOverlay, 30 activate: activateOverlay,
22 - deactivate: deactivateOverlay 31 + deactivate: deactivateOverlay,
32 +
33 + // button descriptors - these can be added to overview or detail panels
34 + buttons: {
35 + foo: {
36 + gid: 'chain',
37 + tt: 'a FOO action',
38 + cb: fooCb
39 + },
40 + bar: {
41 + gid: '*banner',
42 + tt: 'a BAR action',
43 + cb: barCb
44 + }
45 + }
23 }; 46 };
24 47
48 + function fooCb(data) {
49 + $log.debug('FOO callback with data:', data);
50 + }
51 +
52 + function barCb(data) {
53 + $log.debug('BAR callback with data:', data);
54 + }
55 +
25 // === implementation of overlay API (essentially callbacks) 56 // === implementation of overlay API (essentially callbacks)
26 function activateOverlay() { 57 function activateOverlay() {
27 $log.debug("sample topology overlay ACTIVATED"); 58 $log.debug("sample topology overlay ACTIVATED");
......
...@@ -44,26 +44,36 @@ ...@@ -44,26 +44,36 @@
44 $log.warn(tos + fn + '(): ' + msg); 44 $log.warn(tos + fn + '(): ' + msg);
45 } 45 }
46 46
47 - function handleGlyph(o) { 47 + function mkGlyphId(oid, gid) {
48 - var gdata = fs.isO(o.glyph), 48 + return (gid[0] === '*') ? oid + '-' + gid.slice(1) : gid;
49 - oid, 49 + }
50 - data = {}; 50 +
51 - 51 + function handleGlyphs(o) {
52 - if (!gdata) { 52 + var gdata = fs.isO(o.glyphs),
53 - o._glyphId = 'unknown'; 53 + oid = o.overlayId,
54 - } else { 54 + gid = o.glyphId || 'unknown',
55 - if (gdata.id) { 55 + data = {},
56 - o._glyphId = gdata.id; 56 + note = [];
57 - } else if (gdata.vb && gdata.d) { 57 +
58 - oid = o.overlayId; 58 + o._glyphId = mkGlyphId(oid, gid);
59 - data['_' + oid] = gdata.vb; 59 +
60 - data[oid] = gdata.d; 60 + o.mkGid = function (g) {
61 - gs.registerGlyphs(data); 61 + return mkGlyphId(oid, g);
62 - o._glyphId = oid; 62 + };
63 - $log.debug('registered overlay glyph:', oid); 63 + o.mkId = function (s) {
64 - } else { 64 + return oid + '-' + s;
65 - warn('registerGlyph', 'problem with glyph data'); 65 + };
66 - } 66 +
67 + // process glyphs if defined
68 + if (gdata) {
69 + angular.forEach(gdata, function (value, key) {
70 + var fullkey = oid + '-' + key;
71 + data['_' + fullkey] = value.vb;
72 + data[fullkey] = value.d;
73 + note.push('*' + key);
74 + });
75 + gs.registerGlyphs(data);
76 + $log.debug('registered overlay glyphs:', oid, note);
67 } 77 }
68 } 78 }
69 79
...@@ -79,7 +89,7 @@ ...@@ -79,7 +89,7 @@
79 return warn(r, 'already registered: "' + id + '"'); 89 return warn(r, 'already registered: "' + id + '"');
80 } 90 }
81 overlays[id] = overlay; 91 overlays[id] = overlay;
82 - handleGlyph(overlay); 92 + handleGlyphs(overlay);
83 $log.debug(tos + 'registered overlay: ' + id, overlay); 93 $log.debug(tos + 'registered overlay: ' + id, overlay);
84 } 94 }
85 95
...@@ -132,6 +142,28 @@ ...@@ -132,6 +142,28 @@
132 } 142 }
133 } 143 }
134 144
145 + // install buttons from the current overlay
146 + function installButtons(bids, addFn, data) {
147 + if (current) {
148 + bids.forEach(function (bid) {
149 + var btn = current.buttons[bid],
150 + funcWrap = function () {
151 + btn.cb(data);
152 + };
153 +
154 + if (btn) {
155 + addFn({
156 + id: current.mkId(bid),
157 + gid: current.mkGid(btn.gid),
158 + cb: funcWrap,
159 + tt: btn.tt
160 + });
161 + }
162 + });
163 + }
164 +
165 + }
166 +
135 angular.module('ovTopo') 167 angular.module('ovTopo')
136 .factory('TopoOverlayService', 168 .factory('TopoOverlayService',
137 ['$log', 'FnService', 'GlyphService', 'WebSocketService', 169 ['$log', 'FnService', 'GlyphService', 'WebSocketService',
...@@ -147,7 +179,8 @@ ...@@ -147,7 +179,8 @@
147 unregister: unregister, 179 unregister: unregister,
148 list: list, 180 list: list,
149 overlay: overlay, 181 overlay: overlay,
150 - tbSelection: tbSelection 182 + tbSelection: tbSelection,
183 + installButtons: installButtons
151 } 184 }
152 }]); 185 }]);
153 186
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, fs, wss, tps, tts, ns; 26 + var $log, fs, wss, tov, tps, tts, ns;
27 27
28 // api to topoForce 28 // api to topoForce
29 var api; 29 var api;
...@@ -229,9 +229,13 @@ ...@@ -229,9 +229,13 @@
229 // Event Handlers 229 // Event Handlers
230 230
231 function showDetails(data) { 231 function showDetails(data) {
232 + var buttons = fs.isA(data.buttons);
233 +
232 // display the data for the single selected node 234 // display the data for the single selected node
233 tps.displaySingle(data); 235 tps.displaySingle(data);
234 236
237 + // TODO: use server-side-button-descriptors to add buttons
238 +
235 // always add the 'show traffic' action 239 // always add the 'show traffic' action
236 tps.addAction({ 240 tps.addAction({
237 id: '-sin-rel-traf-btn', 241 id: '-sin-rel-traf-btn',
...@@ -249,6 +253,13 @@ ...@@ -249,6 +253,13 @@
249 tt: 'Show Device Flows' 253 tt: 'Show Device Flows'
250 }); 254 });
251 } 255 }
256 +
257 + // TODO: for now, install overlay buttons here
258 + if (buttons) {
259 + tov.installButtons(buttons, tps.addAction, data);
260 + }
261 +
262 +
252 // TODO: have the server return explicit class and ID of each node 263 // TODO: have the server return explicit class and ID of each node
253 // for now, we assume the node is a device if it has a URI 264 // for now, we assume the node is a device if it has a URI
254 if ((data.props).hasOwnProperty('URI')) { 265 if ((data.props).hasOwnProperty('URI')) {
...@@ -308,13 +319,14 @@ ...@@ -308,13 +319,14 @@
308 319
309 angular.module('ovTopo') 320 angular.module('ovTopo')
310 .factory('TopoSelectService', 321 .factory('TopoSelectService',
311 - ['$log', 'FnService', 'WebSocketService', 322 + ['$log', 'FnService', 'WebSocketService', 'TopoOverlayService',
312 'TopoPanelService', 'TopoTrafficService', 'NavService', 323 'TopoPanelService', 'TopoTrafficService', 'NavService',
313 324
314 - function (_$log_, _fs_, _wss_, _tps_, _tts_, _ns_) { 325 + function (_$log_, _fs_, _wss_, _tov_, _tps_, _tts_, _ns_) {
315 $log = _$log_; 326 $log = _$log_;
316 fs = _fs_; 327 fs = _fs_;
317 wss = _wss_; 328 wss = _wss_;
329 + tov = _tov_;
318 tps = _tps_; 330 tps = _tps_;
319 tts = _tts_; 331 tts = _tts_;
320 ns = _ns_; 332 ns = _ns_;
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
29 // traffic overlay definition 29 // traffic overlay definition
30 var overlay = { 30 var overlay = {
31 overlayId: 'traffic', 31 overlayId: 'traffic',
32 - glyph: { id: 'allTraffic' }, 32 + glyphId: 'allTraffic',
33 tooltip: 'Traffic Overlay', 33 tooltip: 'Traffic Overlay',
34 34
35 activate: activateTraffic, 35 activate: activateTraffic,
......