Added mechanism for apps to easily add their own custom link/node/host highlighting wihout having to create a new UI extensions
Change-Id: Iefa21d76190c60db79a4b07a8b22e301d29fe58e
diff --git a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
index 9e03a51..f1ea508 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
@@ -56,6 +56,22 @@
}
/**
+ * Registers the specified topo hilighter factory.
+ *
+ * @param factory UI topo higlighter factory to register
+ */
+ default void register(UiTopoHighlighterFactory factory) {
+ }
+
+ /**
+ * Unregisters the specified user interface extension.
+ *
+ * @param factory UI topo higlighter factory to unregister
+ */
+ default void unregister(UiTopoHighlighterFactory factory) {
+ }
+
+ /**
* Returns the list of registered user interface extensions.
*
* @return list of extensions
@@ -80,6 +96,15 @@
}
/**
+ * Returns the list of registered topo highlighter factories.
+ *
+ * @return list of highlighter factories
+ */
+ default List<UiTopoHighlighterFactory> getTopoHighlighterFactories() {
+ return new ArrayList<UiTopoHighlighterFactory>();
+ }
+
+ /**
* Returns the navigation pane localization bundle.
*
* @return the navigation localization bundle
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java
new file mode 100644
index 0000000..9c12603
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui;
+
+import org.onosproject.ui.topo.Highlights;
+
+/**
+ * Abstraction of an entity capable of generating a set of topology highlights
+ * for devices, hosts and links.
+ */
+public interface UiTopoHighlighter {
+
+ /**
+ * Returns the self-assigned name of the hilighter.
+ *
+ * @return highlighter name
+ */
+ String name();
+
+ /**
+ * Generate a set of highlights.
+ *
+ * @return topology highlights
+ */
+ Highlights createHighlights();
+
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java
new file mode 100644
index 0000000..8a2d976
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ui;
+
+/**
+ * Abstraction of an entity capable of producing a a new topology highlighter.
+ */
+public interface UiTopoHighlighterFactory {
+
+ /**
+ * Produces a new topology highlighter.
+ *
+ * @return newly created topology highlighter
+ */
+ UiTopoHighlighter newTopoHighlighter();
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java b/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java
new file mode 100644
index 0000000..509d817
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ui.impl;
+
+import org.onosproject.net.EdgeLink;
+import org.onosproject.net.Host;
+import org.onosproject.net.Link;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiTopoHighlighter;
+import org.onosproject.ui.UiTopoHighlighterFactory;
+import org.onosproject.ui.topo.BaseLink;
+import org.onosproject.ui.topo.BaseLinkMap;
+import org.onosproject.ui.topo.Highlights;
+import org.onosproject.ui.topo.HostHighlight;
+import org.onosproject.ui.topo.LinkHighlight;
+import org.onosproject.ui.topo.Mod;
+import org.onosproject.ui.topo.NodeBadge;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLinks;
+import static org.onosproject.ui.topo.NodeBadge.text;
+
+@Component(immediate = false)
+public class SampleHighlighterFactory {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected UiExtensionService uiExtensionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected LinkService linkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected HostService hostService;
+
+ private UiTopoHighlighterFactory foo = () ->
+ new TestHighlighter("foo", new Mod("style=\"stroke: #0ff; stroke-width: 10px;\""));
+ private UiTopoHighlighterFactory bar = () ->
+ new TestHighlighter("bar", new Mod("style=\"stroke: #f0f; stroke-width: 4px; stroke-dasharray: 5 2;\""));
+
+ @Activate
+ protected void activate() {
+ uiExtensionService.register(foo);
+ uiExtensionService.register(bar);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ uiExtensionService.unregister(foo);
+ uiExtensionService.unregister(bar);
+ }
+
+ private final class TestHighlighter implements UiTopoHighlighter {
+
+ private final String name;
+ private final Mod mod;
+
+ private TestHighlighter(String name, Mod mod) {
+ this.name = name;
+ this.mod = mod;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Highlights createHighlights() {
+ Highlights highlights = new Highlights();
+ BaseLinkMap linkMap = new BaseLinkMap();
+
+ // Create a map of base bi-links from the set of active links first.
+ for (Link link : linkService.getActiveLinks()) {
+ linkMap.add(link);
+ }
+
+ for (Host host : hostService.getHosts()) {
+ for (EdgeLink link : createEdgeLinks(host, false)) {
+ linkMap.add(link);
+ }
+
+ // Also add a host badge for kicks.
+ HostHighlight hostHighlight = new HostHighlight(host.id().toString());
+ hostHighlight.setBadge(text(NodeBadge.Status.WARN, name));
+ highlights.add(hostHighlight);
+ }
+
+ // Now scan through the links and annotate them with desired highlights
+ for (BaseLink link : linkMap.biLinks()) {
+ highlights.add(new LinkHighlight(link.linkId(), LinkHighlight.Flavor.PRIMARY_HIGHLIGHT)
+ .addMod(mod).setLabel(name + "-" + link.one().src().port()));
+ }
+
+ return highlights;
+ }
+ }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 455b0f3..cae4760 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -113,6 +113,7 @@
private static final String REQ_SEL_INTENT_TRAFFIC = "requestSelectedIntentTraffic";
private static final String SEL_INTENT = "selectIntent";
private static final String REQ_ALL_TRAFFIC = "requestAllTraffic";
+ private static final String REQ_CUSTOM_TRAFFIC = "requestCustomTraffic";
private static final String REQ_DEV_LINK_FLOWS = "requestDeviceLinkFlows";
private static final String CANCEL_TRAFFIC = "cancelTraffic";
private static final String REQ_SUMMARY = "requestSummary";
@@ -246,6 +247,7 @@
new RemoveIntents(),
new ReqAllTraffic(),
+ new ReqCustomTraffic(),
new ReqDevLinkFlows(),
new ReqRelatedIntents(),
new ReqNextIntent(),
@@ -623,6 +625,17 @@
}
}
+ private final class ReqCustomTraffic extends RequestHandler {
+ private ReqCustomTraffic() {
+ super(REQ_CUSTOM_TRAFFIC);
+ }
+
+ @Override
+ public void process(ObjectNode payload) {
+ traffic.monitor((int) number(payload, "index"));
+ }
+ }
+
private NodeSelection makeNodeSelection(ObjectNode payload) {
return new NodeSelection(payload, services.device(), services.host(),
services.link());
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
index 9d9b3c3..b585ffc 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
@@ -151,6 +151,14 @@
}
@Override
+ protected void sendCustomTraffic() {
+ log.debug("sendCustomTraffic");
+ if (topoHighlighter != null) {
+ msgHandler.sendHighlights(topoHighlighter.createHighlights());
+ }
+ }
+
+ @Override
protected void sendClearHighlights() {
log.debug("sendClearHighlights");
msgHandler.sendHighlights(new Highlights());
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
index 2f14ac4..ae3c909 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
@@ -38,6 +38,9 @@
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.statistic.Load;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiTopoHighlighter;
+import org.onosproject.ui.UiTopoHighlighterFactory;
import org.onosproject.ui.impl.topo.TopoologyTrafficMessageHandlerAbstract;
import org.onosproject.ui.impl.topo.util.IntentSelection;
import org.onosproject.ui.impl.topo.util.ServicesBundle;
@@ -69,8 +72,7 @@
import static org.onosproject.net.statistic.PortStatisticsService.MetricType.BYTES;
import static org.onosproject.net.statistic.PortStatisticsService.MetricType.PACKETS;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLinks;
-import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE;
-import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.SELECTED_INTENT;
+import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.*;
/**
* Base superclass for traffic monitor (both 'classic' and 'topo2' versions).
@@ -85,6 +87,7 @@
protected IntentSelection selectedIntents = null;
protected final TopoologyTrafficMessageHandlerAbstract msgHandler;
protected NodeSelection selectedNodes = null;
+ protected UiTopoHighlighter topoHighlighter = null;
protected void sendSelectedIntents() {
log.debug("sendSelectedIntents: {}", selectedIntents);
@@ -285,7 +288,8 @@
ALL_PORT_TRAFFIC_PKT_PS,
DEV_LINK_FLOWS,
RELATED_INTENTS,
- SELECTED_INTENT
+ SELECTED_INTENT,
+ CUSTOM_TRAFFIC_MONITOR
}
/**
@@ -375,6 +379,22 @@
}
}
+
+ public synchronized void monitor(int index) {
+ mode = CUSTOM_TRAFFIC_MONITOR;
+ List<UiTopoHighlighterFactory> factories = services.get(UiExtensionService.class)
+ .getTopoHighlighterFactories();
+ if (factories.isEmpty()) {
+ return;
+ }
+
+ UiTopoHighlighterFactory factory = factories.get(index % factories.size());
+ topoHighlighter = factory.newTopoHighlighter();
+ clearSelection();
+ scheduleTask();
+ sendCustomTraffic();
+ }
+
/**
* Monitor for traffic data to be sent back to the web client, under
* the given mode, using the given selection of devices and hosts.
@@ -474,6 +494,12 @@
protected abstract void sendSelectedIntentTraffic();
/**
+ * Subclass should compile and send appropriate highlights data showing
+ * custom traffic on links.
+ */
+ protected abstract void sendCustomTraffic();
+
+ /**
* Subclass should send a "clear highlights" event.
*/
protected abstract void sendClearHighlights();
@@ -712,6 +738,9 @@
case SELECTED_INTENT:
sendSelectedIntentTraffic();
break;
+ case CUSTOM_TRAFFIC_MONITOR:
+ sendCustomTraffic();
+ break;
default:
// RELATED_INTENTS and IDLE modes should never invoke
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 7d5c324..73de4c1 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -49,6 +49,7 @@
import org.onosproject.ui.UiSessionToken;
import org.onosproject.ui.UiTokenService;
import org.onosproject.ui.UiTopo2OverlayFactory;
+import org.onosproject.ui.UiTopoHighlighterFactory;
import org.onosproject.ui.UiTopoMap;
import org.onosproject.ui.UiTopoMapFactory;
import org.onosproject.ui.UiTopoOverlayFactory;
@@ -140,6 +141,7 @@
private final List<UiExtension> extensions = Lists.newArrayList();
private final List<UiGlyph> glyphs = Lists.newArrayList();
+ private final List<UiTopoHighlighterFactory> highlighterFactories = Lists.newArrayList();
// Map of views to extensions
private final Map<String, UiExtension> views = Maps.newHashMap();
@@ -366,6 +368,22 @@
}
@Override
+ public synchronized void register(UiTopoHighlighterFactory factory) {
+ checkPermission(UI_WRITE);
+ if (!highlighterFactories.contains(factory)) {
+ highlighterFactories.add(factory);
+ UiWebSocketServlet.sendToAll(GUI_ADDED, null);
+ }
+ }
+
+ @Override
+ public synchronized void unregister(UiTopoHighlighterFactory factory) {
+ checkPermission(UI_WRITE);
+ highlighterFactories.remove(factory);
+ UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
+ }
+
+ @Override
public synchronized List<UiExtension> getExtensions() {
checkPermission(UI_READ);
return ImmutableList.copyOf(extensions);
@@ -378,6 +396,12 @@
}
@Override
+ public synchronized List<UiTopoHighlighterFactory> getTopoHighlighterFactories() {
+ checkPermission(UI_READ);
+ return ImmutableList.copyOf(highlighterFactories);
+ }
+
+ @Override
public synchronized UiExtension getViewExtension(String viewId) {
checkPermission(UI_READ);
return views.get(viewId);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
index 76b9dc5..6a41cf2 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
@@ -65,6 +65,10 @@
}
@Override
+ protected void sendCustomTraffic() {
+ }
+
+ @Override
protected void sendAllPortTrafficBits() {
log.debug("TOPO-2-TRAFFIC: sendAllPortTrafficBits");
msgHandler.sendHighlights(trafficSummary(TrafficLink.StatsType.PORT_STATS));
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
index e58b0f2..1567718 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
@@ -37,6 +37,8 @@
*/
public class ServicesBundle {
+ private ServiceDirectory directory;
+
private ClusterService clusterService;
private TopologyService topologyService;
@@ -60,6 +62,7 @@
*/
public ServicesBundle(ServiceDirectory directory) {
checkNotNull(directory, "Directory cannot be null");
+ this.directory = directory;
clusterService = directory.get(ClusterService.class);
@@ -184,4 +187,15 @@
public PortStatisticsService portStats() {
return portStatsService;
}
+
+ /**
+ * Returns the implementation of the specified service class.
+ *
+ * @param serviceClass service class
+ * @param <T> class of service
+ * @return implementation of the service class
+ */
+ public <T> T get(Class<T> serviceClass) {
+ return directory.get(serviceClass);
+ }
}
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
index c2cdfae..8f9334e 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
@@ -103,6 +103,7 @@
tr_btn_show_related_traffic=Show Related Traffic
tr_btn_cancel_monitoring=Cancel traffic monitoring
tr_btn_monitor_all=Monitor all traffic
+tr_btn_monitor_custom_all=Custom traffic monitor
tr_btn_show_dev_link_flows=Show device link flows
tr_btn_show_all_rel_intents=Show all related intents
tr_btn_show_prev_rel_intent=Show previous related intent
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 8819107..912e851 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -1023,6 +1023,7 @@
function clearLinkTrafficStyle() {
link.style('stroke-width', null)
+ .style('stroke', null)
.classed(allTrafficClasses, false);
}
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index 4e9133a..7cf47ff 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -398,6 +398,8 @@
}
});
+ const stylePattern = /style=\"[^\"]*\"/g;
+
data.links.forEach(function (link) {
var ldata = api.findLinkById(link.id);
@@ -405,6 +407,14 @@
if (!link.subdue) {
api.unsupLink(ldata.key, less);
}
+ var styleFound = link.css.match(stylePattern);
+ if (styleFound) {
+ link.css = link.css.replace(stylePattern, '');
+ var style = styleFound[0].replace('style="', '').replace('"$', '')
+ ldata.el.attr('style', style);
+ } else {
+ ldata.el.attr('style', '');
+ }
ldata.el.classed(link.css, true);
ldata.label = link.label;
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
index 640a233..c643494 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
@@ -47,7 +47,8 @@
// internal state
var trafficMode = null,
hoverMode = null,
- allTrafficIndex = 0;
+ allTrafficIndex = 0,
+ customTrafficIndex = 0;
// === -----------------------------------------------------
@@ -137,6 +138,16 @@
allTrafficIndex = (allTrafficIndex + 1) % 3;
}
+ function showCustomTraffic() {
+ trafficMode = 'allCustom';
+ hoverMode = null;
+ wss.sendEvent('requestCustomTraffic', {
+ index: customTrafficIndex,
+ });
+ flash.flash('Custom Traffic');
+ customTrafficIndex = customTrafficIndex + 1;
+ }
+
function showDeviceLinkFlows() {
trafficMode = hoverMode = 'flows';
requestDeviceLinkFlows();
@@ -270,6 +281,7 @@
showNextIntent: showNextIntent,
showSelectedIntentTraffic: showSelectedIntentTraffic,
selectIntent: selectIntent,
+ showCustomTraffic: showCustomTraffic,
// invoked from mouseover/mouseout and selection change
requestTrafficForMode: requestTrafficForMode,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
index 143aa30..2057f7b 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
@@ -109,9 +109,14 @@
tt: function () { return topoLion('tr_btn_monitor_sel_intent'); },
gid: 'm_intentTraffic',
},
+ C: {
+ cb: function () { tts.showCustomTraffic(); },
+ tt: function () { return topoLion('tr_btn_monitor_custom_all'); },
+ gid: 'm_allTraffic',
+ },
_keyOrder: [
- '0', 'A', 'F', 'V', 'leftArrow', 'rightArrow', 'W',
+ '0', 'A', 'F', 'V', 'leftArrow', 'rightArrow', 'W', 'C'
],
},