Thomas Vachuska
Committed by Gerrit Code Review

Removed legacy GUI.

FIxed an NPE in the devices table.
Added cord-gui to obs.exclude

Change-Id: I18a53d2c44c6c97fb7f5d16b7446942a1a37ddca
Showing 34 changed files with 5 additions and 8932 deletions
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
3 .*/build/conf/.* 3 .*/build/conf/.*
4 .*/docs/.* 4 .*/docs/.*
5 .*/openflow/drivers/.* 5 .*/openflow/drivers/.*
6 +.*/cord-gui/.*
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -121,6 +121,8 @@ public class DeviceViewMessageHandler extends UiMessageHandler { ...@@ -121,6 +121,8 @@ public class DeviceViewMessageHandler extends UiMessageHandler {
121 boolean available = ds.isAvailable(id); 121 boolean available = ds.isAvailable(id);
122 String iconId = available ? ICON_ID_ONLINE : ICON_ID_OFFLINE; 122 String iconId = available ? ICON_ID_ONLINE : ICON_ID_OFFLINE;
123 123
124 + String protocol = dev.annotations().value(PROTOCOL);
125 +
124 row.cell(ID, id) 126 row.cell(ID, id)
125 .cell(AVAILABLE, available) 127 .cell(AVAILABLE, available)
126 .cell(AVAILABLE_IID, iconId) 128 .cell(AVAILABLE_IID, iconId)
...@@ -128,7 +130,7 @@ public class DeviceViewMessageHandler extends UiMessageHandler { ...@@ -128,7 +130,7 @@ public class DeviceViewMessageHandler extends UiMessageHandler {
128 .cell(MFR, dev.manufacturer()) 130 .cell(MFR, dev.manufacturer())
129 .cell(HW, dev.hwVersion()) 131 .cell(HW, dev.hwVersion())
130 .cell(SW, dev.swVersion()) 132 .cell(SW, dev.swVersion())
131 - .cell(PROTOCOL, dev.annotations().value(PROTOCOL)) 133 + .cell(PROTOCOL, protocol != null ? protocol : "")
132 .cell(NUM_PORTS, ds.getPorts(id).size()) 134 .cell(NUM_PORTS, ds.getPorts(id).size())
133 .cell(MASTER_ID, ms.getMasterFor(id)); 135 .cell(MASTER_ID, ms.getMasterFor(id));
134 } 136 }
......
1 -/*
2 - * Copyright 2014-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 -package org.onosproject.ui.impl;
17 -
18 -import org.eclipse.jetty.websocket.WebSocket;
19 -import org.eclipse.jetty.websocket.WebSocketServlet;
20 -import org.onlab.osgi.DefaultServiceDirectory;
21 -import org.onlab.osgi.ServiceDirectory;
22 -
23 -import javax.servlet.ServletException;
24 -import javax.servlet.http.HttpServletRequest;
25 -import java.util.HashSet;
26 -import java.util.Iterator;
27 -import java.util.Set;
28 -import java.util.Timer;
29 -import java.util.TimerTask;
30 -
31 -/**
32 - * Web socket servlet capable of creating various sockets for the user interface.
33 - */
34 -@Deprecated
35 -public class GuiWebSocketServlet extends WebSocketServlet {
36 -
37 - private static final long PING_DELAY_MS = 5000;
38 -
39 - private ServiceDirectory directory = new DefaultServiceDirectory();
40 -
41 - private final Set<TopologyViewWebSocket> sockets = new HashSet<>();
42 - private final Timer timer = new Timer();
43 - private final TimerTask pruner = new Pruner();
44 -
45 - @Override
46 - public void init() throws ServletException {
47 - super.init();
48 - timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
49 - }
50 -
51 - @Override
52 - public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
53 - TopologyViewWebSocket socket = new TopologyViewWebSocket(directory);
54 - synchronized (sockets) {
55 - sockets.add(socket);
56 - }
57 - return socket;
58 - }
59 -
60 - // Task for pruning web-sockets that are idle.
61 - private class Pruner extends TimerTask {
62 - @Override
63 - public void run() {
64 - synchronized (sockets) {
65 - Iterator<TopologyViewWebSocket> it = sockets.iterator();
66 - while (it.hasNext()) {
67 - TopologyViewWebSocket socket = it.next();
68 - if (socket.isIdle()) {
69 - it.remove();
70 - socket.close();
71 - }
72 - }
73 - }
74 - }
75 - }
76 -}
...@@ -52,7 +52,7 @@ public class TopologyResource extends BaseResource { ...@@ -52,7 +52,7 @@ public class TopologyResource extends BaseResource {
52 ArrayNode devices = mapper.createArrayNode(); 52 ArrayNode devices = mapper.createArrayNode();
53 ArrayNode hosts = mapper.createArrayNode(); 53 ArrayNode hosts = mapper.createArrayNode();
54 54
55 - Map<String, ObjectNode> metaUi = TopologyViewMessages.getMetaUi(); 55 + Map<String, ObjectNode> metaUi = TopologyViewMessageHandler.getMetaUi();
56 for (String id : metaUi.keySet()) { 56 for (String id : metaUi.keySet()) {
57 ObjectNode memento = metaUi.get(id); 57 ObjectNode memento = metaUi.get(id);
58 if (id.charAt(17) == '/') { 58 if (id.charAt(17) == '/') {
......
1 -/*
2 - * Copyright 2014-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 -package org.onosproject.ui.impl;
17 -
18 -import com.fasterxml.jackson.databind.JsonNode;
19 -import com.fasterxml.jackson.databind.ObjectMapper;
20 -import com.fasterxml.jackson.databind.node.ArrayNode;
21 -import com.fasterxml.jackson.databind.node.ObjectNode;
22 -import org.onlab.osgi.ServiceDirectory;
23 -import org.onlab.packet.IpAddress;
24 -import org.onosproject.cluster.ClusterEvent;
25 -import org.onosproject.cluster.ClusterService;
26 -import org.onosproject.cluster.ControllerNode;
27 -import org.onosproject.cluster.NodeId;
28 -import org.onosproject.core.CoreService;
29 -import org.onosproject.mastership.MastershipService;
30 -import org.onosproject.net.Annotated;
31 -import org.onosproject.net.AnnotationKeys;
32 -import org.onosproject.net.Annotations;
33 -import org.onosproject.net.ConnectPoint;
34 -import org.onosproject.net.DefaultEdgeLink;
35 -import org.onosproject.net.Device;
36 -import org.onosproject.net.DeviceId;
37 -import org.onosproject.net.EdgeLink;
38 -import org.onosproject.net.Host;
39 -import org.onosproject.net.HostId;
40 -import org.onosproject.net.HostLocation;
41 -import org.onosproject.net.Link;
42 -import org.onosproject.net.LinkKey;
43 -import org.onosproject.net.PortNumber;
44 -import org.onosproject.net.device.DeviceEvent;
45 -import org.onosproject.net.device.DeviceService;
46 -import org.onosproject.net.flow.FlowEntry;
47 -import org.onosproject.net.flow.FlowRuleService;
48 -import org.onosproject.net.flow.TrafficTreatment;
49 -import org.onosproject.net.flow.instructions.Instruction;
50 -import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
51 -import org.onosproject.net.host.HostEvent;
52 -import org.onosproject.net.host.HostService;
53 -import org.onosproject.net.intent.Intent;
54 -import org.onosproject.net.intent.IntentService;
55 -import org.onosproject.net.intent.LinkCollectionIntent;
56 -import org.onosproject.net.intent.OpticalConnectivityIntent;
57 -import org.onosproject.net.intent.OpticalPathIntent;
58 -import org.onosproject.net.intent.PathIntent;
59 -import org.onosproject.net.link.LinkEvent;
60 -import org.onosproject.net.link.LinkService;
61 -import org.onosproject.net.provider.ProviderId;
62 -import org.onosproject.net.statistic.Load;
63 -import org.onosproject.net.statistic.StatisticService;
64 -import org.onosproject.net.topology.Topology;
65 -import org.onosproject.net.topology.TopologyService;
66 -import org.slf4j.Logger;
67 -import org.slf4j.LoggerFactory;
68 -
69 -import java.text.DecimalFormat;
70 -import java.util.ArrayList;
71 -import java.util.Collection;
72 -import java.util.Collections;
73 -import java.util.HashMap;
74 -import java.util.HashSet;
75 -import java.util.Iterator;
76 -import java.util.List;
77 -import java.util.Map;
78 -import java.util.Set;
79 -import java.util.concurrent.ConcurrentHashMap;
80 -
81 -import static com.google.common.base.Preconditions.checkNotNull;
82 -import static com.google.common.base.Strings.isNullOrEmpty;
83 -import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
84 -import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
85 -import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
86 -import static org.onosproject.net.DeviceId.deviceId;
87 -import static org.onosproject.net.HostId.hostId;
88 -import static org.onosproject.net.LinkKey.linkKey;
89 -import static org.onosproject.net.PortNumber.P0;
90 -import static org.onosproject.net.PortNumber.portNumber;
91 -import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
92 -import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
93 -import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
94 -import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
95 -import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
96 -import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
97 -
98 -/**
99 - * Facility for creating messages bound for the topology viewer.
100 - */
101 -@Deprecated
102 -public abstract class TopologyViewMessages {
103 -
104 - protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
105 -
106 - private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true);
107 - private static final String COMPACT = "%s/%s-%s/%s";
108 -
109 - private static final double KB = 1024;
110 - private static final double MB = 1024 * KB;
111 - private static final double GB = 1024 * MB;
112 -
113 - private static final String GB_UNIT = "GB";
114 - private static final String MB_UNIT = "MB";
115 - private static final String KB_UNIT = "KB";
116 - private static final String B_UNIT = "B";
117 -
118 - protected final ServiceDirectory directory;
119 - protected final ClusterService clusterService;
120 - protected final DeviceService deviceService;
121 - protected final LinkService linkService;
122 - protected final HostService hostService;
123 - protected final MastershipService mastershipService;
124 - protected final IntentService intentService;
125 - protected final FlowRuleService flowService;
126 - protected final StatisticService statService;
127 - protected final TopologyService topologyService;
128 -
129 - protected final ObjectMapper mapper = new ObjectMapper();
130 - private final String version;
131 -
132 - // TODO: extract into an external & durable state; good enough for now and demo
133 - private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
134 -
135 - /**
136 - * Returns read-only view of the meta-ui information.
137 - *
138 - * @return map of id to meta-ui mementos
139 - */
140 - static Map<String, ObjectNode> getMetaUi() {
141 - return Collections.unmodifiableMap(metaUi);
142 - }
143 -
144 - /**
145 - * Creates a messaging facility for creating messages for topology viewer.
146 - *
147 - * @param directory service directory
148 - */
149 - protected TopologyViewMessages(ServiceDirectory directory) {
150 - this.directory = checkNotNull(directory, "Directory cannot be null");
151 - clusterService = directory.get(ClusterService.class);
152 - deviceService = directory.get(DeviceService.class);
153 - linkService = directory.get(LinkService.class);
154 - hostService = directory.get(HostService.class);
155 - mastershipService = directory.get(MastershipService.class);
156 - intentService = directory.get(IntentService.class);
157 - flowService = directory.get(FlowRuleService.class);
158 - statService = directory.get(StatisticService.class);
159 - topologyService = directory.get(TopologyService.class);
160 -
161 - String ver = directory.get(CoreService.class).version().toString();
162 - version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
163 - }
164 -
165 - // Retrieves the payload from the specified event.
166 - protected ObjectNode payload(ObjectNode event) {
167 - return (ObjectNode) event.path("payload");
168 - }
169 -
170 - // Returns the specified node property as a number
171 - protected long number(ObjectNode node, String name) {
172 - return node.path(name).asLong();
173 - }
174 -
175 - // Returns the specified node property as a string.
176 - protected String string(ObjectNode node, String name) {
177 - return node.path(name).asText();
178 - }
179 -
180 - // Returns the specified node property as a string.
181 - protected String string(ObjectNode node, String name, String defaultValue) {
182 - return node.path(name).asText(defaultValue);
183 - }
184 -
185 - // Returns the specified set of IP addresses as a string.
186 - private String ip(Set<IpAddress> ipAddresses) {
187 - Iterator<IpAddress> it = ipAddresses.iterator();
188 - return it.hasNext() ? it.next().toString() : "unknown";
189 - }
190 -
191 - // Produces JSON structure from annotations.
192 - private JsonNode props(Annotations annotations) {
193 - ObjectNode props = mapper.createObjectNode();
194 - if (annotations != null) {
195 - for (String key : annotations.keys()) {
196 - props.put(key, annotations.value(key));
197 - }
198 - }
199 - return props;
200 - }
201 -
202 - // Produces an informational log message event bound to the client.
203 - protected ObjectNode info(long id, String message) {
204 - return message("info", id, message);
205 - }
206 -
207 - // Produces a warning log message event bound to the client.
208 - protected ObjectNode warning(long id, String message) {
209 - return message("warning", id, message);
210 - }
211 -
212 - // Produces an error log message event bound to the client.
213 - protected ObjectNode error(long id, String message) {
214 - return message("error", id, message);
215 - }
216 -
217 - // Produces a log message event bound to the client.
218 - private ObjectNode message(String severity, long id, String message) {
219 - return envelope("message", id,
220 - mapper.createObjectNode()
221 - .put("severity", severity)
222 - .put("message", message));
223 - }
224 -
225 - // Puts the payload into an envelope and returns it.
226 - protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
227 - ObjectNode event = mapper.createObjectNode();
228 - event.put("event", type);
229 - if (sid > 0) {
230 - event.put("sid", sid);
231 - }
232 - event.set("payload", payload);
233 - return event;
234 - }
235 -
236 - // Produces a set of all hosts listed in the specified JSON array.
237 - protected Set<Host> getHosts(ArrayNode array) {
238 - Set<Host> hosts = new HashSet<>();
239 - if (array != null) {
240 - for (JsonNode node : array) {
241 - try {
242 - addHost(hosts, hostId(node.asText()));
243 - } catch (IllegalArgumentException e) {
244 - log.debug("Skipping ID {}", node.asText());
245 - }
246 - }
247 - }
248 - return hosts;
249 - }
250 -
251 - // Adds the specified host to the set of hosts.
252 - private void addHost(Set<Host> hosts, HostId hostId) {
253 - Host host = hostService.getHost(hostId);
254 - if (host != null) {
255 - hosts.add(host);
256 - }
257 - }
258 -
259 -
260 - // Produces a set of all devices listed in the specified JSON array.
261 - protected Set<Device> getDevices(ArrayNode array) {
262 - Set<Device> devices = new HashSet<>();
263 - if (array != null) {
264 - for (JsonNode node : array) {
265 - try {
266 - addDevice(devices, deviceId(node.asText()));
267 - } catch (IllegalArgumentException e) {
268 - log.debug("Skipping ID {}", node.asText());
269 - }
270 - }
271 - }
272 - return devices;
273 - }
274 -
275 - private void addDevice(Set<Device> devices, DeviceId deviceId) {
276 - Device device = deviceService.getDevice(deviceId);
277 - if (device != null) {
278 - devices.add(device);
279 - }
280 - }
281 -
282 - protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
283 - try {
284 - addHost(hosts, hostId(hover));
285 - } catch (IllegalArgumentException e) {
286 - try {
287 - addDevice(devices, deviceId(hover));
288 - } catch (IllegalArgumentException ne) {
289 - log.debug("Skipping ID {}", hover);
290 - }
291 - }
292 - }
293 -
294 - // Produces a cluster instance message to the client.
295 - protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
296 - ControllerNode node = event.subject();
297 - int switchCount = mastershipService.getDevicesOf(node.id()).size();
298 - ObjectNode payload = mapper.createObjectNode()
299 - .put("id", node.id().toString())
300 - .put("ip", node.ip().toString())
301 - .put("online", clusterService.getState(node.id()) == ACTIVE)
302 - .put("uiAttached", event.subject().equals(clusterService.getLocalNode()))
303 - .put("switches", switchCount);
304 -
305 - ArrayNode labels = mapper.createArrayNode();
306 - labels.add(node.id().toString());
307 - labels.add(node.ip().toString());
308 -
309 - // Add labels, props and stuff the payload into envelope.
310 - payload.set("labels", labels);
311 - addMetaUi(node.id().toString(), payload);
312 -
313 - String type = messageType != null ? messageType :
314 - ((event.type() == INSTANCE_ADDED) ? "addInstance" :
315 - ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
316 - "addInstance")));
317 - return envelope(type, 0, payload);
318 - }
319 -
320 - // Produces a device event message to the client.
321 - protected ObjectNode deviceMessage(DeviceEvent event) {
322 - Device device = event.subject();
323 - ObjectNode payload = mapper.createObjectNode()
324 - .put("id", device.id().toString())
325 - .put("type", device.type().toString().toLowerCase())
326 - .put("online", deviceService.isAvailable(device.id()))
327 - .put("master", master(device.id()));
328 -
329 - // Generate labels: id, chassis id, no-label, optional-name
330 - String name = device.annotations().value(AnnotationKeys.NAME);
331 - ArrayNode labels = mapper.createArrayNode();
332 - labels.add("");
333 - labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
334 - labels.add(device.id().toString());
335 -
336 - // Add labels, props and stuff the payload into envelope.
337 - payload.set("labels", labels);
338 - payload.set("props", props(device.annotations()));
339 - addGeoLocation(device, payload);
340 - addMetaUi(device.id().toString(), payload);
341 -
342 - String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
343 - ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
344 - return envelope(type, 0, payload);
345 - }
346 -
347 - // Produces a link event message to the client.
348 - protected ObjectNode linkMessage(LinkEvent event) {
349 - Link link = event.subject();
350 - ObjectNode payload = mapper.createObjectNode()
351 - .put("id", compactLinkString(link))
352 - .put("type", link.type().toString().toLowerCase())
353 - .put("online", link.state() == Link.State.ACTIVE)
354 - .put("linkWidth", 1.2)
355 - .put("src", link.src().deviceId().toString())
356 - .put("srcPort", link.src().port().toString())
357 - .put("dst", link.dst().deviceId().toString())
358 - .put("dstPort", link.dst().port().toString());
359 - String type = (event.type() == LINK_ADDED) ? "addLink" :
360 - ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
361 - return envelope(type, 0, payload);
362 - }
363 -
364 - // Produces a host event message to the client.
365 - protected ObjectNode hostMessage(HostEvent event) {
366 - Host host = event.subject();
367 - String hostType = host.annotations().value(AnnotationKeys.TYPE);
368 - ObjectNode payload = mapper.createObjectNode()
369 - .put("id", host.id().toString())
370 - .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
371 - .put("ingress", compactLinkString(edgeLink(host, true)))
372 - .put("egress", compactLinkString(edgeLink(host, false)));
373 - payload.set("cp", hostConnect(mapper, host.location()));
374 - payload.set("labels", labels(mapper, ip(host.ipAddresses()),
375 - host.mac().toString()));
376 - payload.set("props", props(host.annotations()));
377 - addGeoLocation(host, payload);
378 - addMetaUi(host.id().toString(), payload);
379 -
380 - String type = (event.type() == HOST_ADDED) ? "addHost" :
381 - ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
382 - return envelope(type, 0, payload);
383 - }
384 -
385 - // Encodes the specified host location into a JSON object.
386 - private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
387 - return mapper.createObjectNode()
388 - .put("device", location.deviceId().toString())
389 - .put("port", location.port().toLong());
390 - }
391 -
392 - // Encodes the specified list of labels a JSON array.
393 - private ArrayNode labels(ObjectMapper mapper, String... labels) {
394 - ArrayNode json = mapper.createArrayNode();
395 - for (String label : labels) {
396 - json.add(label);
397 - }
398 - return json;
399 - }
400 -
401 - // Returns the name of the master node for the specified device id.
402 - private String master(DeviceId deviceId) {
403 - NodeId master = mastershipService.getMasterFor(deviceId);
404 - return master != null ? master.toString() : "";
405 - }
406 -
407 - // Generates an edge link from the specified host location.
408 - private EdgeLink edgeLink(Host host, boolean ingress) {
409 - return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
410 - host.location(), ingress);
411 - }
412 -
413 - // Adds meta UI information for the specified object.
414 - private void addMetaUi(String id, ObjectNode payload) {
415 - ObjectNode meta = metaUi.get(id);
416 - if (meta != null) {
417 - payload.set("metaUi", meta);
418 - }
419 - }
420 -
421 - // Adds a geo location JSON to the specified payload object.
422 - private void addGeoLocation(Annotated annotated, ObjectNode payload) {
423 - Annotations annotations = annotated.annotations();
424 - if (annotations == null) {
425 - return;
426 - }
427 -
428 - String slat = annotations.value(AnnotationKeys.LATITUDE);
429 - String slng = annotations.value(AnnotationKeys.LONGITUDE);
430 - try {
431 - if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
432 - double lat = Double.parseDouble(slat);
433 - double lng = Double.parseDouble(slng);
434 - ObjectNode loc = mapper.createObjectNode()
435 - .put("type", "latlng").put("lat", lat).put("lng", lng);
436 - payload.set("location", loc);
437 - }
438 - } catch (NumberFormatException e) {
439 - log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
440 - }
441 - }
442 -
443 - // Updates meta UI information for the specified object.
444 - protected void updateMetaUi(ObjectNode event) {
445 - ObjectNode payload = payload(event);
446 - metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
447 - }
448 -
449 - // Returns summary response.
450 - protected ObjectNode summmaryMessage(long sid) {
451 - Topology topology = topologyService.currentTopology();
452 - return envelope("showSummary", sid,
453 - json("ONOS Summary", "node",
454 - new Prop("Devices", format(topology.deviceCount())),
455 - new Prop("Links", format(topology.linkCount())),
456 - new Prop("Hosts", format(hostService.getHostCount())),
457 - new Prop("Topology SCCs", format(topology.clusterCount())),
458 - new Separator(),
459 - new Prop("Intents", format(intentService.getIntentCount())),
460 - new Prop("Flows", format(flowService.getFlowRuleCount())),
461 - new Prop("Version", version)));
462 - }
463 -
464 - // Returns device details response.
465 - protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
466 - Device device = deviceService.getDevice(deviceId);
467 - Annotations annot = device.annotations();
468 - String name = annot.value(AnnotationKeys.NAME);
469 - int portCount = deviceService.getPorts(deviceId).size();
470 - int flowCount = getFlowCount(deviceId);
471 - return envelope("showDetails", sid,
472 - json(isNullOrEmpty(name) ? deviceId.toString() : name,
473 - device.type().toString().toLowerCase(),
474 - new Prop("URI", deviceId.toString()),
475 - new Prop("Vendor", device.manufacturer()),
476 - new Prop("H/W Version", device.hwVersion()),
477 - new Prop("S/W Version", device.swVersion()),
478 - new Prop("Serial Number", device.serialNumber()),
479 - new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
480 - new Separator(),
481 - new Prop("Master", master(deviceId)),
482 - new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
483 - new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
484 - new Separator(),
485 - new Prop("Ports", Integer.toString(portCount)),
486 - new Prop("Flows", Integer.toString(flowCount))));
487 - }
488 -
489 - protected int getFlowCount(DeviceId deviceId) {
490 - int count = 0;
491 - Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
492 - while (it.hasNext()) {
493 - count++;
494 - it.next();
495 - }
496 - return count;
497 - }
498 -
499 - // Counts all entries that egress on the given device links.
500 - protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
501 - List<FlowEntry> entries = new ArrayList<>();
502 - Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
503 - Set<Host> hosts = hostService.getConnectedHosts(deviceId);
504 - Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
505 - while (it.hasNext()) {
506 - entries.add(it.next());
507 - }
508 -
509 - // Add all edge links to the set
510 - if (hosts != null) {
511 - for (Host host : hosts) {
512 - links.add(new DefaultEdgeLink(host.providerId(),
513 - new ConnectPoint(host.id(), P0),
514 - host.location(), false));
515 - }
516 - }
517 -
518 - Map<Link, Integer> counts = new HashMap<>();
519 - for (Link link : links) {
520 - counts.put(link, getEgressFlows(link, entries));
521 - }
522 - return counts;
523 - }
524 -
525 - // Counts all entries that egress on the link source port.
526 - private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
527 - int count = 0;
528 - PortNumber out = link.src().port();
529 - for (FlowEntry entry : entries) {
530 - TrafficTreatment treatment = entry.treatment();
531 - for (Instruction instruction : treatment.allInstructions()) {
532 - if (instruction.type() == Instruction.Type.OUTPUT &&
533 - ((OutputInstruction) instruction).port().equals(out)) {
534 - count++;
535 - }
536 - }
537 - }
538 - return count;
539 - }
540 -
541 -
542 - // Returns host details response.
543 - protected ObjectNode hostDetails(HostId hostId, long sid) {
544 - Host host = hostService.getHost(hostId);
545 - Annotations annot = host.annotations();
546 - String type = annot.value(AnnotationKeys.TYPE);
547 - String name = annot.value(AnnotationKeys.NAME);
548 - String vlan = host.vlan().toString();
549 - return envelope("showDetails", sid,
550 - json(isNullOrEmpty(name) ? hostId.toString() : name,
551 - isNullOrEmpty(type) ? "endstation" : type,
552 - new Prop("MAC", host.mac().toString()),
553 - new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
554 - new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
555 - new Separator(),
556 - new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
557 - new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
558 - }
559 -
560 -
561 - // Produces JSON message to trigger traffic overview visualization
562 - protected ObjectNode trafficSummaryMessage(long sid) {
563 - ObjectNode payload = mapper.createObjectNode();
564 - ArrayNode paths = mapper.createArrayNode();
565 - payload.set("paths", paths);
566 -
567 - ObjectNode pathNodeN = mapper.createObjectNode();
568 - ArrayNode linksNodeN = mapper.createArrayNode();
569 - ArrayNode labelsN = mapper.createArrayNode();
570 -
571 - pathNodeN.put("class", "plain").put("traffic", false);
572 - pathNodeN.set("links", linksNodeN);
573 - pathNodeN.set("labels", labelsN);
574 - paths.add(pathNodeN);
575 -
576 - ObjectNode pathNodeT = mapper.createObjectNode();
577 - ArrayNode linksNodeT = mapper.createArrayNode();
578 - ArrayNode labelsT = mapper.createArrayNode();
579 -
580 - pathNodeT.put("class", "secondary").put("traffic", true);
581 - pathNodeT.set("links", linksNodeT);
582 - pathNodeT.set("labels", labelsT);
583 - paths.add(pathNodeT);
584 -
585 - for (BiLink link : consolidateLinks(linkService.getLinks())) {
586 - boolean bi = link.two != null;
587 - if (isInfrastructureEgress(link.one) ||
588 - (bi && isInfrastructureEgress(link.two))) {
589 - link.addLoad(statService.load(link.one));
590 - link.addLoad(bi ? statService.load(link.two) : null);
591 - if (link.hasTraffic) {
592 - linksNodeT.add(compactLinkString(link.one));
593 - labelsT.add(formatBytes(link.bytes));
594 - } else {
595 - linksNodeN.add(compactLinkString(link.one));
596 - labelsN.add("");
597 - }
598 - }
599 - }
600 - return envelope("showTraffic", sid, payload);
601 - }
602 -
603 - private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
604 - Map<LinkKey, BiLink> biLinks = new HashMap<>();
605 - for (Link link : links) {
606 - addLink(biLinks, link);
607 - }
608 - return biLinks.values();
609 - }
610 -
611 - // Produces JSON message to trigger flow overview visualization
612 - protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
613 - ObjectNode payload = mapper.createObjectNode();
614 - ArrayNode paths = mapper.createArrayNode();
615 - payload.set("paths", paths);
616 -
617 - for (Device device : devices) {
618 - Map<Link, Integer> counts = getFlowCounts(device.id());
619 - for (Link link : counts.keySet()) {
620 - addLinkFlows(link, paths, counts.get(link));
621 - }
622 - }
623 - return envelope("showTraffic", sid, payload);
624 - }
625 -
626 - private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
627 - ObjectNode pathNode = mapper.createObjectNode();
628 - ArrayNode linksNode = mapper.createArrayNode();
629 - ArrayNode labels = mapper.createArrayNode();
630 - boolean noFlows = count == null || count == 0;
631 - pathNode.put("class", noFlows ? "secondary" : "primary");
632 - pathNode.put("traffic", false);
633 - pathNode.set("links", linksNode.add(compactLinkString(link)));
634 - pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
635 - (count == 1 ? " flow" : " flows"))));
636 - paths.add(pathNode);
637 - }
638 -
639 -
640 - // Produces JSON message to trigger traffic visualization
641 - protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
642 - ObjectNode payload = mapper.createObjectNode();
643 - ArrayNode paths = mapper.createArrayNode();
644 - payload.set("paths", paths);
645 -
646 - // Classify links based on their traffic traffic first...
647 - Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
648 -
649 - // Then separate the links into their respective classes and send them out.
650 - Map<String, ObjectNode> pathNodes = new HashMap<>();
651 - for (BiLink biLink : biLinks.values()) {
652 - boolean hasTraffic = biLink.hasTraffic;
653 - String tc = (biLink.classes + (hasTraffic ? " animated" : "")).trim();
654 - ObjectNode pathNode = pathNodes.get(tc);
655 - if (pathNode == null) {
656 - pathNode = mapper.createObjectNode()
657 - .put("class", tc).put("traffic", hasTraffic);
658 - pathNode.set("links", mapper.createArrayNode());
659 - pathNode.set("labels", mapper.createArrayNode());
660 - pathNodes.put(tc, pathNode);
661 - paths.add(pathNode);
662 - }
663 - ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
664 - ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
665 - }
666 -
667 - return envelope("showTraffic", sid, payload);
668 - }
669 -
670 - // Classifies the link traffic according to the specified classes.
671 - private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
672 - Map<LinkKey, BiLink> biLinks = new HashMap<>();
673 - for (TrafficClass trafficClass : trafficClasses) {
674 - for (Intent intent : trafficClass.intents) {
675 - boolean isOptical = intent instanceof OpticalConnectivityIntent;
676 - List<Intent> installables = intentService.getInstallableIntents(intent.key());
677 - if (installables != null) {
678 - for (Intent installable : installables) {
679 - String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
680 - if (installable instanceof PathIntent) {
681 - classifyLinks(type, biLinks, trafficClass.showTraffic,
682 - ((PathIntent) installable).path().links());
683 - } else if (installable instanceof LinkCollectionIntent) {
684 - classifyLinks(type, biLinks, trafficClass.showTraffic,
685 - ((LinkCollectionIntent) installable).links());
686 - } else if (installable instanceof OpticalPathIntent) {
687 - classifyLinks(type, biLinks, trafficClass.showTraffic,
688 - ((OpticalPathIntent) installable).path().links());
689 - }
690 - }
691 - }
692 - }
693 - }
694 - return biLinks;
695 - }
696 -
697 -
698 - // Adds the link segments (path or tree) associated with the specified
699 - // connectivity intent
700 - private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
701 - boolean showTraffic, Iterable<Link> links) {
702 - if (links != null) {
703 - for (Link link : links) {
704 - BiLink biLink = addLink(biLinks, link);
705 - if (isInfrastructureEgress(link)) {
706 - if (showTraffic) {
707 - biLink.addLoad(statService.load(link));
708 - }
709 - biLink.addClass(type);
710 - }
711 - }
712 - }
713 - }
714 -
715 -
716 - private BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
717 - LinkKey key = canonicalLinkKey(link);
718 - BiLink biLink = biLinks.get(key);
719 - if (biLink != null) {
720 - biLink.setOther(link);
721 - } else {
722 - biLink = new BiLink(key, link);
723 - biLinks.put(key, biLink);
724 - }
725 - return biLink;
726 - }
727 -
728 -
729 - // Adds the link segments (path or tree) associated with the specified
730 - // connectivity intent
731 - protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
732 - Iterable<Link> links) {
733 - ObjectNode pathNode = mapper.createObjectNode();
734 - ArrayNode linksNode = mapper.createArrayNode();
735 -
736 - if (links != null) {
737 - ArrayNode labels = mapper.createArrayNode();
738 - boolean hasTraffic = false;
739 - for (Link link : links) {
740 - if (isInfrastructureEgress(link)) {
741 - linksNode.add(compactLinkString(link));
742 - Load load = statService.load(link);
743 - String label = "";
744 - if (load.rate() > 0) {
745 - hasTraffic = true;
746 - label = formatBytes(load.latest());
747 - }
748 - labels.add(label);
749 - }
750 - }
751 - pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
752 - pathNode.put("traffic", hasTraffic);
753 - pathNode.set("links", linksNode);
754 - pathNode.set("labels", labels);
755 - paths.add(pathNode);
756 - }
757 - }
758 -
759 - // Poor-mans formatting to get the labels with byte counts looking nice.
760 - private String formatBytes(long bytes) {
761 - String unit;
762 - double value;
763 - if (bytes > GB) {
764 - value = bytes / GB;
765 - unit = GB_UNIT;
766 - } else if (bytes > MB) {
767 - value = bytes / MB;
768 - unit = MB_UNIT;
769 - } else if (bytes > KB) {
770 - value = bytes / KB;
771 - unit = KB_UNIT;
772 - } else {
773 - value = bytes;
774 - unit = B_UNIT;
775 - }
776 - DecimalFormat format = new DecimalFormat("#,###.##");
777 - return format.format(value) + " " + unit;
778 - }
779 -
780 - // Formats the given number into a string.
781 - private String format(Number number) {
782 - DecimalFormat format = new DecimalFormat("#,###");
783 - return format.format(number);
784 - }
785 -
786 - private boolean isInfrastructureEgress(Link link) {
787 - return link.src().elementId() instanceof DeviceId;
788 - }
789 -
790 - // Produces compact string representation of a link.
791 - private static String compactLinkString(Link link) {
792 - return String.format(COMPACT, link.src().elementId(), link.src().port(),
793 - link.dst().elementId(), link.dst().port());
794 - }
795 -
796 - // Produces JSON property details.
797 - private ObjectNode json(String id, String type, Prop... props) {
798 - ObjectMapper mapper = new ObjectMapper();
799 - ObjectNode result = mapper.createObjectNode()
800 - .put("id", id).put("type", type);
801 - ObjectNode pnode = mapper.createObjectNode();
802 - ArrayNode porder = mapper.createArrayNode();
803 - for (Prop p : props) {
804 - porder.add(p.key);
805 - pnode.put(p.key, p.value);
806 - }
807 - result.set("propOrder", porder);
808 - result.set("props", pnode);
809 - return result;
810 - }
811 -
812 - // Produces canonical link key, i.e. one that will match link and its inverse.
813 - private LinkKey canonicalLinkKey(Link link) {
814 - String sn = link.src().elementId().toString();
815 - String dn = link.dst().elementId().toString();
816 - return sn.compareTo(dn) < 0 ?
817 - linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
818 - }
819 -
820 - // Representation of link and its inverse and any traffic data.
821 - private class BiLink {
822 - public final LinkKey key;
823 - public final Link one;
824 - public Link two;
825 - public boolean hasTraffic = false;
826 - public long bytes = 0;
827 - public String classes = "";
828 -
829 - BiLink(LinkKey key, Link link) {
830 - this.key = key;
831 - this.one = link;
832 - }
833 -
834 - void setOther(Link link) {
835 - this.two = link;
836 - }
837 -
838 - void addLoad(Load load) {
839 - if (load != null) {
840 - this.hasTraffic = hasTraffic || load.rate() > 0;
841 - this.bytes += load.latest();
842 - }
843 - }
844 -
845 - void addClass(String trafficClass) {
846 - classes = classes + " " + trafficClass;
847 - }
848 - }
849 -
850 - // Auxiliary key/value carrier.
851 - private class Prop {
852 - public final String key;
853 - public final String value;
854 -
855 - protected Prop(String key, String value) {
856 - this.key = key;
857 - this.value = value;
858 - }
859 - }
860 -
861 - // Auxiliary properties separator
862 - private class Separator extends Prop {
863 - protected Separator() {
864 - super("-", "");
865 - }
866 - }
867 -
868 - // Auxiliary carrier of data for requesting traffic message.
869 - protected class TrafficClass {
870 - public final boolean showTraffic;
871 - public final String type;
872 - public final Iterable<Intent> intents;
873 -
874 - TrafficClass(String type, Iterable<Intent> intents) {
875 - this(type, intents, false);
876 - }
877 -
878 - TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
879 - this.type = type;
880 - this.intents = intents;
881 - this.showTraffic = showTraffic;
882 - }
883 - }
884 -
885 -}
1 -/*
2 - * Copyright 2014-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 -package org.onosproject.ui.impl;
17 -
18 -import java.io.IOException;
19 -import java.util.ArrayList;
20 -import java.util.Collections;
21 -import java.util.Comparator;
22 -import java.util.HashSet;
23 -import java.util.List;
24 -import java.util.Set;
25 -import java.util.Timer;
26 -import java.util.TimerTask;
27 -
28 -import org.eclipse.jetty.websocket.WebSocket;
29 -import org.onlab.osgi.ServiceDirectory;
30 -import org.onlab.util.AbstractAccumulator;
31 -import org.onlab.util.Accumulator;
32 -import org.onosproject.cluster.ClusterEvent;
33 -import org.onosproject.cluster.ClusterEventListener;
34 -import org.onosproject.cluster.ControllerNode;
35 -import org.onosproject.core.ApplicationId;
36 -import org.onosproject.core.CoreService;
37 -import org.onosproject.event.Event;
38 -import org.onosproject.mastership.MastershipAdminService;
39 -import org.onosproject.mastership.MastershipEvent;
40 -import org.onosproject.mastership.MastershipListener;
41 -import org.onosproject.net.ConnectPoint;
42 -import org.onosproject.net.Device;
43 -import org.onosproject.net.Host;
44 -import org.onosproject.net.HostId;
45 -import org.onosproject.net.HostLocation;
46 -import org.onosproject.net.Link;
47 -import org.onosproject.net.device.DeviceEvent;
48 -import org.onosproject.net.device.DeviceListener;
49 -import org.onosproject.net.flow.DefaultTrafficSelector;
50 -import org.onosproject.net.flow.DefaultTrafficTreatment;
51 -import org.onosproject.net.flow.FlowRuleEvent;
52 -import org.onosproject.net.flow.FlowRuleListener;
53 -import org.onosproject.net.flow.TrafficSelector;
54 -import org.onosproject.net.flow.TrafficTreatment;
55 -import org.onosproject.net.host.HostEvent;
56 -import org.onosproject.net.host.HostListener;
57 -import org.onosproject.net.intent.HostToHostIntent;
58 -import org.onosproject.net.intent.Intent;
59 -import org.onosproject.net.intent.IntentEvent;
60 -import org.onosproject.net.intent.IntentListener;
61 -import org.onosproject.net.intent.MultiPointToSinglePointIntent;
62 -import org.onosproject.net.link.LinkEvent;
63 -import org.onosproject.net.link.LinkListener;
64 -
65 -import com.fasterxml.jackson.databind.JsonNode;
66 -import com.fasterxml.jackson.databind.node.ArrayNode;
67 -import com.fasterxml.jackson.databind.node.ObjectNode;
68 -
69 -import static com.google.common.base.Strings.isNullOrEmpty;
70 -import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
71 -import static org.onosproject.net.DeviceId.deviceId;
72 -import static org.onosproject.net.HostId.hostId;
73 -import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
74 -import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_UPDATED;
75 -import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
76 -import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
77 -
78 -/**
79 - * Web socket capable of interacting with the GUI topology view.
80 - */
81 -@Deprecated
82 -public class TopologyViewWebSocket
83 - extends TopologyViewMessages
84 - implements WebSocket.OnTextMessage, WebSocket.OnControl {
85 -
86 - private static final long MAX_AGE_MS = 15000;
87 -
88 - private static final byte PING = 0x9;
89 - private static final byte PONG = 0xA;
90 - private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
91 -
92 - private static final String APP_ID = "org.onosproject.gui";
93 -
94 - private static final long TRAFFIC_FREQUENCY = 5000;
95 - private static final long SUMMARY_FREQUENCY = 30000;
96 -
97 - private static final Comparator<? super ControllerNode> NODE_COMPARATOR =
98 - new Comparator<ControllerNode>() {
99 - @Override
100 - public int compare(ControllerNode o1, ControllerNode o2) {
101 - return o1.id().toString().compareTo(o2.id().toString());
102 - }
103 - };
104 -
105 -
106 - private final Timer timer = new Timer("topology-view");
107 -
108 - private static final int MAX_EVENTS = 1000;
109 - private static final int MAX_BATCH_MS = 5000;
110 - private static final int MAX_IDLE_MS = 1000;
111 -
112 - private final ApplicationId appId;
113 -
114 - private Connection connection;
115 - private FrameConnection control;
116 -
117 - private final ClusterEventListener clusterListener = new InternalClusterListener();
118 - private final MastershipListener mastershipListener = new InternalMastershipListener();
119 - private final DeviceListener deviceListener = new InternalDeviceListener();
120 - private final LinkListener linkListener = new InternalLinkListener();
121 - private final HostListener hostListener = new InternalHostListener();
122 - private final IntentListener intentListener = new InternalIntentListener();
123 - private final FlowRuleListener flowListener = new InternalFlowListener();
124 -
125 - private final Accumulator<Event> eventAccummulator = new InternalEventAccummulator();
126 -
127 - private TimerTask trafficTask;
128 - private ObjectNode trafficEvent;
129 -
130 - private TimerTask summaryTask;
131 - private ObjectNode summaryEvent;
132 -
133 - private long lastActive = System.currentTimeMillis();
134 - private boolean listenersRemoved = false;
135 -
136 - private TopologyViewIntentFilter intentFilter;
137 -
138 - // Current selection context
139 - private Set<Host> selectedHosts;
140 - private Set<Device> selectedDevices;
141 - private List<Intent> selectedIntents;
142 - private int currentIntentIndex = -1;
143 -
144 - /**
145 - * Creates a new web-socket for serving data to GUI topology view.
146 - *
147 - * @param directory service directory
148 - */
149 - public TopologyViewWebSocket(ServiceDirectory directory) {
150 - super(directory);
151 - intentFilter = new TopologyViewIntentFilter(intentService, deviceService,
152 - hostService, linkService);
153 - appId = directory.get(CoreService.class).registerApplication(APP_ID);
154 - }
155 -
156 - /**
157 - * Issues a close on the connection.
158 - */
159 - synchronized void close() {
160 - removeListeners();
161 - if (connection.isOpen()) {
162 - connection.close();
163 - }
164 - }
165 -
166 - /**
167 - * Indicates if this connection is idle.
168 - *
169 - * @return true if idle or closed
170 - */
171 - synchronized boolean isIdle() {
172 - boolean idle = (System.currentTimeMillis() - lastActive) > MAX_AGE_MS;
173 - if (idle || (connection != null && !connection.isOpen())) {
174 - return true;
175 - } else if (connection != null) {
176 - try {
177 - control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
178 - } catch (IOException e) {
179 - log.warn("Unable to send ping message due to: ", e);
180 - }
181 - }
182 - return false;
183 - }
184 -
185 - @Override
186 - public void onOpen(Connection connection) {
187 - log.info("Legacy GUI client connected");
188 - this.connection = connection;
189 - this.control = (FrameConnection) connection;
190 - addListeners();
191 -
192 - sendAllInstances(null);
193 - sendAllDevices();
194 - sendAllLinks();
195 - sendAllHosts();
196 - }
197 -
198 - @Override
199 - public synchronized void onClose(int closeCode, String message) {
200 - removeListeners();
201 - timer.cancel();
202 - log.info("Legacy GUI client disconnected");
203 - }
204 -
205 - @Override
206 - public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
207 - lastActive = System.currentTimeMillis();
208 - return true;
209 - }
210 -
211 - @Override
212 - public void onMessage(String data) {
213 - lastActive = System.currentTimeMillis();
214 - try {
215 - processMessage((ObjectNode) mapper.reader().readTree(data));
216 - } catch (Exception e) {
217 - log.warn("Unable to parse GUI request {} due to {}", data, e);
218 - log.debug("Boom!!!", e);
219 - }
220 - }
221 -
222 - // Processes the specified event.
223 - private void processMessage(ObjectNode event) {
224 - String type = string(event, "event", "unknown");
225 - if (type.equals("requestDetails")) {
226 - requestDetails(event);
227 - } else if (type.equals("updateMeta")) {
228 - updateMetaUi(event);
229 -
230 - } else if (type.equals("addHostIntent")) {
231 - createHostIntent(event);
232 - } else if (type.equals("addMultiSourceIntent")) {
233 - createMultiSourceIntent(event);
234 -
235 - } else if (type.equals("requestRelatedIntents")) {
236 - stopTrafficMonitoring();
237 - requestRelatedIntents(event);
238 -
239 - } else if (type.equals("requestNextRelatedIntent")) {
240 - stopTrafficMonitoring();
241 - requestAnotherRelatedIntent(event, +1);
242 - } else if (type.equals("requestPrevRelatedIntent")) {
243 - stopTrafficMonitoring();
244 - requestAnotherRelatedIntent(event, -1);
245 - } else if (type.equals("requestSelectedIntentTraffic")) {
246 - requestSelectedIntentTraffic(event);
247 - startTrafficMonitoring(event);
248 -
249 - } else if (type.equals("requestAllTraffic")) {
250 - requestAllTraffic(event);
251 - startTrafficMonitoring(event);
252 -
253 - } else if (type.equals("requestDeviceLinkFlows")) {
254 - requestDeviceLinkFlows(event);
255 - startTrafficMonitoring(event);
256 -
257 - } else if (type.equals("cancelTraffic")) {
258 - cancelTraffic(event);
259 -
260 - } else if (type.equals("requestSummary")) {
261 - requestSummary(event);
262 - startSummaryMonitoring(event);
263 - } else if (type.equals("cancelSummary")) {
264 - stopSummaryMonitoring();
265 -
266 - } else if (type.equals("equalizeMasters")) {
267 - equalizeMasters(event);
268 - }
269 - }
270 -
271 - // Sends the specified data to the client.
272 - protected synchronized void sendMessage(ObjectNode data) {
273 - try {
274 - if (connection.isOpen()) {
275 - connection.sendMessage(data.toString());
276 - }
277 - } catch (IOException e) {
278 - log.warn("Unable to send message {} to GUI due to {}", data, e);
279 - log.debug("Boom!!!", e);
280 - }
281 - }
282 -
283 - // Sends all controller nodes to the client as node-added messages.
284 - private void sendAllInstances(String messageType) {
285 - List<ControllerNode> nodes = new ArrayList<>(clusterService.getNodes());
286 - Collections.sort(nodes, NODE_COMPARATOR);
287 - for (ControllerNode node : nodes) {
288 - sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node),
289 - messageType));
290 - }
291 - }
292 -
293 - // Sends all devices to the client as device-added messages.
294 - private void sendAllDevices() {
295 - // Send optical first, others later for layered rendering
296 - for (Device device : deviceService.getDevices()) {
297 - if (device.type() == Device.Type.ROADM) {
298 - sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
299 - }
300 - }
301 - for (Device device : deviceService.getDevices()) {
302 - if (device.type() != Device.Type.ROADM) {
303 - sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
304 - }
305 - }
306 - }
307 -
308 - // Sends all links to the client as link-added messages.
309 - private void sendAllLinks() {
310 - // Send optical first, others later for layered rendering
311 - for (Link link : linkService.getLinks()) {
312 - if (link.type() == Link.Type.OPTICAL) {
313 - sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
314 - }
315 - }
316 - for (Link link : linkService.getLinks()) {
317 - if (link.type() != Link.Type.OPTICAL) {
318 - sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
319 - }
320 - }
321 - }
322 -
323 - // Sends all hosts to the client as host-added messages.
324 - private void sendAllHosts() {
325 - for (Host host : hostService.getHosts()) {
326 - sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
327 - }
328 - }
329 -
330 - // Sends back device or host details.
331 - private void requestDetails(ObjectNode event) {
332 - ObjectNode payload = payload(event);
333 - String type = string(payload, "class", "unknown");
334 - long sid = number(event, "sid");
335 -
336 - if (type.equals("device")) {
337 - sendMessage(deviceDetails(deviceId(string(payload, "id")), sid));
338 - } else if (type.equals("host")) {
339 - sendMessage(hostDetails(hostId(string(payload, "id")), sid));
340 - }
341 - }
342 -
343 -
344 - // Creates host-to-host intent.
345 - private void createHostIntent(ObjectNode event) {
346 - ObjectNode payload = payload(event);
347 - long id = number(event, "sid");
348 - // TODO: add protection against device ids and non-existent hosts.
349 - HostId one = hostId(string(payload, "one"));
350 - HostId two = hostId(string(payload, "two"));
351 -
352 - HostToHostIntent intent =
353 - HostToHostIntent.builder()
354 - .appId(appId)
355 - .one(one)
356 - .two(two)
357 - .build();
358 -
359 -
360 - intentService.submit(intent);
361 - startMonitoringIntent(event, intent);
362 - }
363 -
364 - // Creates multi-source-to-single-dest intent.
365 - private void createMultiSourceIntent(ObjectNode event) {
366 - ObjectNode payload = payload(event);
367 - long id = number(event, "sid");
368 - // TODO: add protection against device ids and non-existent hosts.
369 - Set<HostId> src = getHostIds((ArrayNode) payload.path("src"));
370 - HostId dst = hostId(string(payload, "dst"));
371 - Host dstHost = hostService.getHost(dst);
372 -
373 - Set<ConnectPoint> ingressPoints = getHostLocations(src);
374 -
375 - // FIXME: clearly, this is not enough
376 - TrafficSelector selector = DefaultTrafficSelector.builder()
377 - .matchEthDst(dstHost.mac()).build();
378 - TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
379 -
380 - MultiPointToSinglePointIntent intent =
381 - MultiPointToSinglePointIntent.builder()
382 - .appId(appId)
383 - .selector(selector)
384 - .treatment(treatment)
385 - .ingressPoints(ingressPoints)
386 - .egressPoint(dstHost.location())
387 - .build();
388 -
389 - intentService.submit(intent);
390 - startMonitoringIntent(event, intent);
391 - }
392 -
393 -
394 - private synchronized void startMonitoringIntent(ObjectNode event, Intent intent) {
395 - selectedHosts = new HashSet<>();
396 - selectedDevices = new HashSet<>();
397 - selectedIntents = new ArrayList<>();
398 - selectedIntents.add(intent);
399 - currentIntentIndex = -1;
400 - requestAnotherRelatedIntent(event, +1);
401 - requestSelectedIntentTraffic(event);
402 - }
403 -
404 -
405 - private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
406 - Set<ConnectPoint> points = new HashSet<>();
407 - for (HostId hostId : hostIds) {
408 - points.add(getHostLocation(hostId));
409 - }
410 - return points;
411 - }
412 -
413 - private HostLocation getHostLocation(HostId hostId) {
414 - return hostService.getHost(hostId).location();
415 - }
416 -
417 - // Produces a list of host ids from the specified JSON array.
418 - private Set<HostId> getHostIds(ArrayNode ids) {
419 - Set<HostId> hostIds = new HashSet<>();
420 - for (JsonNode id : ids) {
421 - hostIds.add(hostId(id.asText()));
422 - }
423 - return hostIds;
424 - }
425 -
426 -
427 - private synchronized long startTrafficMonitoring(ObjectNode event) {
428 - stopTrafficMonitoring();
429 - trafficEvent = event;
430 - trafficTask = new TrafficMonitor();
431 - timer.schedule(trafficTask, TRAFFIC_FREQUENCY, TRAFFIC_FREQUENCY);
432 - return number(event, "sid");
433 - }
434 -
435 - private synchronized void stopTrafficMonitoring() {
436 - if (trafficTask != null) {
437 - trafficTask.cancel();
438 - trafficTask = null;
439 - trafficEvent = null;
440 - }
441 - }
442 -
443 - // Subscribes for host traffic messages.
444 - private synchronized void requestAllTraffic(ObjectNode event) {
445 - long sid = startTrafficMonitoring(event);
446 - sendMessage(trafficSummaryMessage(sid));
447 - }
448 -
449 - private void requestDeviceLinkFlows(ObjectNode event) {
450 - ObjectNode payload = payload(event);
451 - long sid = startTrafficMonitoring(event);
452 -
453 - // Get the set of selected hosts and their intents.
454 - ArrayNode ids = (ArrayNode) payload.path("ids");
455 - Set<Host> hosts = new HashSet<>();
456 - Set<Device> devices = getDevices(ids);
457 -
458 - // If there is a hover node, include it in the hosts and find intents.
459 - String hover = string(payload, "hover");
460 - if (!isNullOrEmpty(hover)) {
461 - addHover(hosts, devices, hover);
462 - }
463 - sendMessage(flowSummaryMessage(sid, devices));
464 - }
465 -
466 -
467 - // Requests related intents message.
468 - private synchronized void requestRelatedIntents(ObjectNode event) {
469 - ObjectNode payload = payload(event);
470 - if (!payload.has("ids")) {
471 - return;
472 - }
473 -
474 - long sid = number(event, "sid");
475 -
476 - // Cancel any other traffic monitoring mode.
477 - stopTrafficMonitoring();
478 -
479 - // Get the set of selected hosts and their intents.
480 - ArrayNode ids = (ArrayNode) payload.path("ids");
481 - selectedHosts = getHosts(ids);
482 - selectedDevices = getDevices(ids);
483 - selectedIntents = intentFilter.findPathIntents(selectedHosts, selectedDevices,
484 - intentService.getIntents());
485 - currentIntentIndex = -1;
486 -
487 - if (haveSelectedIntents()) {
488 - // Send a message to highlight all links of all monitored intents.
489 - sendMessage(trafficMessage(sid, new TrafficClass("primary", selectedIntents)));
490 - }
491 -
492 - // FIXME: Re-introduce one the client click vs hover gesture stuff is sorted out.
493 -// String hover = string(payload, "hover");
494 -// if (!isNullOrEmpty(hover)) {
495 -// // If there is a hover node, include it in the selection and find intents.
496 -// processHoverExtendedSelection(sid, hover);
497 -// }
498 - }
499 -
500 - private boolean haveSelectedIntents() {
501 - return selectedIntents != null && !selectedIntents.isEmpty();
502 - }
503 -
504 - // Processes the selection extended with hovered item to segregate items
505 - // into primary (those including the hover) vs secondary highlights.
506 - private void processHoverExtendedSelection(long sid, String hover) {
507 - Set<Host> hoverSelHosts = new HashSet<>(selectedHosts);
508 - Set<Device> hoverSelDevices = new HashSet<>(selectedDevices);
509 - addHover(hoverSelHosts, hoverSelDevices, hover);
510 -
511 - List<Intent> primary = selectedIntents == null ? new ArrayList<>() :
512 - intentFilter.findPathIntents(hoverSelHosts, hoverSelDevices,
513 - selectedIntents);
514 - Set<Intent> secondary = new HashSet<>(selectedIntents);
515 - secondary.removeAll(primary);
516 -
517 - // Send a message to highlight all links of all monitored intents.
518 - sendMessage(trafficMessage(sid, new TrafficClass("primary", primary),
519 - new TrafficClass("secondary", secondary)));
520 - }
521 -
522 - // Requests next or previous related intent.
523 - private void requestAnotherRelatedIntent(ObjectNode event, int offset) {
524 - if (haveSelectedIntents()) {
525 - currentIntentIndex = currentIntentIndex + offset;
526 - if (currentIntentIndex < 0) {
527 - currentIntentIndex = selectedIntents.size() - 1;
528 - } else if (currentIntentIndex >= selectedIntents.size()) {
529 - currentIntentIndex = 0;
530 - }
531 - sendSelectedIntent(event);
532 - }
533 - }
534 -
535 - // Sends traffic information on the related intents with the currently
536 - // selected intent highlighted.
537 - private void sendSelectedIntent(ObjectNode event) {
538 - Intent selectedIntent = selectedIntents.get(currentIntentIndex);
539 - log.info("Requested next intent {}", selectedIntent.id());
540 -
541 - Set<Intent> primary = new HashSet<>();
542 - primary.add(selectedIntent);
543 -
544 - Set<Intent> secondary = new HashSet<>(selectedIntents);
545 - secondary.remove(selectedIntent);
546 -
547 - // Send a message to highlight all links of the selected intent.
548 - sendMessage(trafficMessage(number(event, "sid"),
549 - new TrafficClass("primary", primary),
550 - new TrafficClass("secondary", secondary)));
551 - }
552 -
553 - // Requests monitoring of traffic for the selected intent.
554 - private void requestSelectedIntentTraffic(ObjectNode event) {
555 - if (haveSelectedIntents()) {
556 - if (currentIntentIndex < 0) {
557 - currentIntentIndex = 0;
558 - }
559 - Intent selectedIntent = selectedIntents.get(currentIntentIndex);
560 - log.info("Requested traffic for selected {}", selectedIntent.id());
561 -
562 - Set<Intent> primary = new HashSet<>();
563 - primary.add(selectedIntent);
564 -
565 - // Send a message to highlight all links of the selected intent.
566 - sendMessage(trafficMessage(number(event, "sid"),
567 - new TrafficClass("primary", primary, true)));
568 - }
569 - }
570 -
571 - // Cancels sending traffic messages.
572 - private void cancelTraffic(ObjectNode event) {
573 - selectedIntents = null;
574 - sendMessage(trafficMessage(number(event, "sid")));
575 - stopTrafficMonitoring();
576 - }
577 -
578 -
579 - private synchronized long startSummaryMonitoring(ObjectNode event) {
580 - stopSummaryMonitoring();
581 - summaryEvent = event;
582 - summaryTask = new SummaryMonitor();
583 - timer.schedule(summaryTask, SUMMARY_FREQUENCY, SUMMARY_FREQUENCY);
584 - return number(event, "sid");
585 - }
586 -
587 - private synchronized void stopSummaryMonitoring() {
588 - if (summaryEvent != null) {
589 - summaryTask.cancel();
590 - summaryTask = null;
591 - summaryEvent = null;
592 - }
593 - }
594 -
595 - // Subscribes for summary messages.
596 - private synchronized void requestSummary(ObjectNode event) {
597 - sendMessage(summmaryMessage(number(event, "sid")));
598 - }
599 -
600 -
601 - // Forces mastership role rebalancing.
602 - private void equalizeMasters(ObjectNode event) {
603 - directory.get(MastershipAdminService.class).balanceRoles();
604 - }
605 -
606 -
607 - // Adds all internal listeners.
608 - private void addListeners() {
609 - clusterService.addListener(clusterListener);
610 - mastershipService.addListener(mastershipListener);
611 - deviceService.addListener(deviceListener);
612 - linkService.addListener(linkListener);
613 - hostService.addListener(hostListener);
614 - intentService.addListener(intentListener);
615 - flowService.addListener(flowListener);
616 - }
617 -
618 - // Removes all internal listeners.
619 - private synchronized void removeListeners() {
620 - if (!listenersRemoved) {
621 - listenersRemoved = true;
622 - clusterService.removeListener(clusterListener);
623 - mastershipService.removeListener(mastershipListener);
624 - deviceService.removeListener(deviceListener);
625 - linkService.removeListener(linkListener);
626 - hostService.removeListener(hostListener);
627 - intentService.removeListener(intentListener);
628 - flowService.removeListener(flowListener);
629 - }
630 - }
631 -
632 - // Cluster event listener.
633 - private class InternalClusterListener implements ClusterEventListener {
634 - @Override
635 - public void event(ClusterEvent event) {
636 - sendMessage(instanceMessage(event, null));
637 - }
638 - }
639 -
640 - // Mastership change listener
641 - private class InternalMastershipListener implements MastershipListener {
642 - @Override
643 - public void event(MastershipEvent event) {
644 - sendAllInstances("updateInstance");
645 - Device device = deviceService.getDevice(event.subject());
646 - sendMessage(deviceMessage(new DeviceEvent(DEVICE_UPDATED, device)));
647 - }
648 - }
649 -
650 - // Device event listener.
651 - private class InternalDeviceListener implements DeviceListener {
652 - @Override
653 - public void event(DeviceEvent event) {
654 - sendMessage(deviceMessage(event));
655 - eventAccummulator.add(event);
656 - }
657 - }
658 -
659 - // Link event listener.
660 - private class InternalLinkListener implements LinkListener {
661 - @Override
662 - public void event(LinkEvent event) {
663 - sendMessage(linkMessage(event));
664 - eventAccummulator.add(event);
665 - }
666 - }
667 -
668 - // Host event listener.
669 - private class InternalHostListener implements HostListener {
670 - @Override
671 - public void event(HostEvent event) {
672 - sendMessage(hostMessage(event));
673 - eventAccummulator.add(event);
674 - }
675 - }
676 -
677 - // Intent event listener.
678 - private class InternalIntentListener implements IntentListener {
679 - @Override
680 - public void event(IntentEvent event) {
681 - if (trafficEvent != null) {
682 - requestSelectedIntentTraffic(trafficEvent);
683 - }
684 - eventAccummulator.add(event);
685 - }
686 - }
687 -
688 - // Intent event listener.
689 - private class InternalFlowListener implements FlowRuleListener {
690 - @Override
691 - public void event(FlowRuleEvent event) {
692 - eventAccummulator.add(event);
693 - }
694 - }
695 -
696 - // Periodic update of the traffic information
697 - private class TrafficMonitor extends TimerTask {
698 - @Override
699 - public void run() {
700 - try {
701 - if (trafficEvent != null) {
702 - String type = string(trafficEvent, "event", "unknown");
703 - if (type.equals("requestAllTraffic")) {
704 - requestAllTraffic(trafficEvent);
705 - } else if (type.equals("requestDeviceLinkFlows")) {
706 - requestDeviceLinkFlows(trafficEvent);
707 - } else if (type.equals("requestSelectedIntentTraffic")) {
708 - requestSelectedIntentTraffic(trafficEvent);
709 - }
710 - }
711 - } catch (Exception e) {
712 - log.warn("Unable to handle traffic request due to {}", e.getMessage());
713 - log.debug("Boom!", e);
714 - }
715 - }
716 - }
717 -
718 - // Periodic update of the summary information
719 - private class SummaryMonitor extends TimerTask {
720 - @Override
721 - public void run() {
722 - try {
723 - if (summaryEvent != null) {
724 - requestSummary(summaryEvent);
725 - }
726 - } catch (Exception e) {
727 - log.warn("Unable to handle summary request due to {}", e.getMessage());
728 - log.debug("Boom!", e);
729 - }
730 - }
731 - }
732 -
733 - // Accumulates events to drive methodic update of the summary pane.
734 - private class InternalEventAccummulator extends AbstractAccumulator<Event> {
735 - protected InternalEventAccummulator() {
736 - super(new Timer("topo-summary"), MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS);
737 - }
738 -
739 - @Override
740 - public void processItems(List<Event> items) {
741 - try {
742 - if (summaryEvent != null) {
743 - sendMessage(summmaryMessage(0));
744 - }
745 - } catch (Exception e) {
746 - log.warn("Unable to handle summary request due to {}", e.getMessage());
747 - log.debug("Boom!", e);
748 - }
749 - }
750 - }
751 -}
752 -
...@@ -161,15 +161,4 @@ ...@@ -161,15 +161,4 @@
161 <url-pattern>/websock/*</url-pattern> 161 <url-pattern>/websock/*</url-pattern>
162 </servlet-mapping> 162 </servlet-mapping>
163 163
164 - <servlet>
165 - <servlet-name>Legacy Web Socket Service</servlet-name>
166 - <servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
167 - <load-on-startup>2</load-on-startup>
168 - </servlet>
169 -
170 - <servlet-mapping>
171 - <servlet-name>Legacy Web Socket Service</servlet-name>
172 - <url-pattern>/ws/*</url-pattern>
173 - </servlet-mapping>
174 -
175 </web-app> 164 </web-app>
......
1 -/*
2 - * Copyright 2014 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 - Base CSS file
19 - */
20 -
21 -html {
22 - font-family: sans-serif;
23 - -webkit-text-size-adjust: 100%;
24 - -ms-text-size-adjust: 100%;
25 -}
26 -
27 -body {
28 - margin: 0;
29 -}
This diff could not be displayed because it is too large.
1 -/*
2 - * Copyright 2014 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 - D3 Utilities CSS file
19 - */
20 -
21 -#d3u .light.norm.c1 {
22 - color: #1f77b4;
23 -}
24 -
25 -#d3u .light.norm.c2 {
26 - color: #2ca02c;
27 -}
28 -
29 -#d3u .light.norm.c3 {
30 - color: #d62728;
31 -}
32 -
33 -#d3u .light.norm.c4 {
34 - color: #9467bd;
35 -}
36 -
37 -#d3u .light.norm.c5 {
38 - color: #e377c2;
39 -}
40 -
41 -#d3u .light.norm.c6 {
42 - color: #bcbd22;
43 -}
44 -
45 -#d3u .light.norm.c7 {
46 - color: #17becf;
47 -}
48 -
49 -
50 -
51 -#d3u .light.mute.c1 {
52 - color: #aec7e8;
53 -}
54 -
55 -#d3u .light.mute.c2 {
56 - color: #98df8a;
57 -}
58 -
59 -#d3u .light.mute.c3 {
60 - color: #ff9896;
61 -}
62 -
63 -#d3u .light.mute.c4 {
64 - color: #c5b0d5;
65 -}
66 -
67 -#d3u .light.mute.c5 {
68 - color: #f7b6d2;
69 -}
70 -
71 -#d3u .light.mute.c6 {
72 - color: #dbdb8d;
73 -}
74 -
75 -#d3u .light.mute.c7 {
76 - color: #9edae5;
77 -}
78 -
79 -
80 -
81 -#d3u .dark.norm.c1 {
82 - color: #1f77b4;
83 -}
84 -
85 -#d3u .dark.norm.c2 {
86 - color: #2ca02c;
87 -}
88 -
89 -#d3u .dark.norm.c3 {
90 - color: #d62728;
91 -}
92 -
93 -#d3u .dark.norm.c4 {
94 - color: #9467bd;
95 -}
96 -
97 -#d3u .dark.norm.c5 {
98 - color: #e377c2;
99 -}
100 -
101 -#d3u .dark.norm.c6 {
102 - color: #bcbd22;
103 -}
104 -
105 -#d3u .dark.norm.c7 {
106 - color: #17becf;
107 -}
108 -
109 -
110 -
111 -#d3u .dark.mute.c1 {
112 - color: #aec7e8;
113 -}
114 -
115 -#d3u .dark.mute.c2 {
116 - color: #639a56;
117 -}
118 -
119 -#d3u .dark.mute.c3 {
120 - color: #ff9896;
121 -}
122 -
123 -#d3u .dark.mute.c4 {
124 - color: #c5b0d5;
125 -}
126 -
127 -#d3u .dark.mute.c5 {
128 - color: #f7b6d2;
129 -}
130 -
131 -#d3u .dark.mute.c6 {
132 - color: #dbdb8d;
133 -}
134 -
135 -#d3u .dark.mute.c7 {
136 - color: #9edae5;
137 -}
138 -
1 -/*
2 - * Copyright 2014 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 - Utility functions for D3 visualizations.
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - function isF(f) {
25 - return $.isFunction(f) ? f : null;
26 - }
27 -
28 - // TODO: sensible defaults
29 - function createDragBehavior(force, selectCb, atDragEnd,
30 - dragEnabled, clickEnabled) {
31 - var draggedThreshold = d3.scale.linear()
32 - .domain([0, 0.1])
33 - .range([5, 20])
34 - .clamp(true),
35 - drag,
36 - fSel = isF(selectCb),
37 - fEnd = isF(atDragEnd),
38 - fDEn = isF(dragEnabled),
39 - fCEn = isF(clickEnabled),
40 - bad = [];
41 -
42 - function naf(what) {
43 - return 'd3util.createDragBehavior(): '+ what + ' is not a function';
44 - }
45 -
46 - if (!fSel) {
47 - bad.push(naf('selectCb'));
48 - }
49 - if (!fEnd) {
50 - bad.push(naf('atDragEnd'));
51 - }
52 - if (!fDEn) {
53 - bad.push(naf('dragEnabled'));
54 - }
55 - if (!fCEn) {
56 - bad.push(naf('clickEnabled'));
57 - }
58 -
59 - if (bad.length) {
60 - alert(bad.join('\n'));
61 - return null;
62 - }
63 -
64 -
65 - function dragged(d) {
66 - var threshold = draggedThreshold(force.alpha()),
67 - dx = d.oldX - d.px,
68 - dy = d.oldY - d.py;
69 - if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
70 - d.dragged = true;
71 - }
72 - return d.dragged;
73 - }
74 -
75 - drag = d3.behavior.drag()
76 - .origin(function(d) { return d; })
77 - .on('dragstart', function(d) {
78 - if (clickEnabled() || dragEnabled()) {
79 - d3.event.sourceEvent.stopPropagation();
80 -
81 - d.oldX = d.x;
82 - d.oldY = d.y;
83 - d.dragged = false;
84 - d.fixed |= 2;
85 - d.dragStarted = true;
86 - }
87 - })
88 - .on('drag', function(d) {
89 - if (dragEnabled()) {
90 - d.px = d3.event.x;
91 - d.py = d3.event.y;
92 - if (dragged(d)) {
93 - if (!force.alpha()) {
94 - force.alpha(.025);
95 - }
96 - }
97 - }
98 - })
99 - .on('dragend', function(d) {
100 - if (d.dragStarted) {
101 - d.dragStarted = false;
102 - if (!dragged(d)) {
103 - // consider this the same as a 'click'
104 - // (selection of a node)
105 - if (clickEnabled()) {
106 - selectCb(d, this); // TODO: set 'this' context instead of param
107 - }
108 - }
109 - d.fixed &= ~6;
110 -
111 - // hook at the end of a drag gesture
112 - if (dragEnabled()) {
113 - atDragEnd(d, this); // TODO: set 'this' context instead of param
114 - }
115 - }
116 - });
117 -
118 - return drag;
119 - }
120 -
121 - function loadGlow(defs) {
122 - // TODO: parameterize color
123 -
124 - var glow = defs.append('filter')
125 - .attr('x', '-50%')
126 - .attr('y', '-50%')
127 - .attr('width', '200%')
128 - .attr('height', '200%')
129 - .attr('id', 'blue-glow');
130 -
131 - glow.append('feColorMatrix')
132 - .attr('type', 'matrix')
133 - .attr('values',
134 - '0 0 0 0 0 ' +
135 - '0 0 0 0 0 ' +
136 - '0 0 0 0 .7 ' +
137 - '0 0 0 1 0 ');
138 -
139 - glow.append('feGaussianBlur')
140 - .attr('stdDeviation', 3)
141 - .attr('result', 'coloredBlur');
142 -
143 - glow.append('feMerge').selectAll('feMergeNode')
144 - .data(['coloredBlur', 'SourceGraphic'])
145 - .enter().append('feMergeNode')
146 - .attr('in', String);
147 - }
148 -
149 - // --- Ordinal scales for 7 values.
150 - // TODO: tune colors for light and dark themes
151 - // Note: These colors look good on the white background. Still, need to tune for dark.
152 -
153 - // blue brown brick red sea green purple dark teal lime
154 - var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
155 - lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
156 -
157 - darkNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
158 - darkMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'];
159 -
160 - function cat7() {
161 - var colors = {
162 - light: {
163 - norm: d3.scale.ordinal().range(lightNorm),
164 - mute: d3.scale.ordinal().range(lightMute)
165 - },
166 - dark: {
167 - norm: d3.scale.ordinal().range(darkNorm),
168 - mute: d3.scale.ordinal().range(darkMute)
169 - }
170 - },
171 - tcid = 'd3utilTestCard';
172 -
173 - function get(id, muted, theme) {
174 - // NOTE: since we are lazily assigning domain ids, we need to
175 - // get the color from all 4 scales, to keep the domains
176 - // in sync.
177 - var ln = colors.light.norm(id),
178 - lm = colors.light.mute(id),
179 - dn = colors.dark.norm(id),
180 - dm = colors.dark.mute(id);
181 - if (theme === 'dark') {
182 - return muted ? dm : dn;
183 - } else {
184 - return muted ? lm : ln;
185 - }
186 - }
187 -
188 - function testCard(svg) {
189 - var g = svg.select('g#' + tcid),
190 - dom = d3.range(7),
191 - k, muted, theme, what;
192 -
193 - if (!g.empty()) {
194 - g.remove();
195 -
196 - } else {
197 - g = svg.append('g')
198 - .attr('id', tcid)
199 - .attr('transform', 'scale(4)translate(20,20)');
200 -
201 - for (k=0; k<4; k++) {
202 - muted = k%2;
203 - what = muted ? ' muted' : ' normal';
204 - theme = k < 2 ? 'light' : 'dark';
205 - dom.forEach(function (id, i) {
206 - var x = i * 20,
207 - y = k * 20,
208 - f = get(id, muted, theme);
209 - g.append('circle').attr({
210 - cx: x,
211 - cy: y,
212 - r: 5,
213 - fill: f
214 - });
215 - });
216 - g.append('rect').attr({
217 - x: 140,
218 - y: k * 20 - 5,
219 - width: 32,
220 - height: 10,
221 - rx: 2,
222 - fill: '#888'
223 - });
224 - g.append('text').text(theme + what)
225 - .attr({
226 - x: 142,
227 - y: k * 20 + 2,
228 - fill: 'white'
229 - })
230 - .style('font-size', '4pt');
231 - }
232 - }
233 - }
234 -
235 - return {
236 - testCard: testCard,
237 - get: get
238 - };
239 - }
240 -
241 - // === register the functions as a library
242 - onos.ui.addLib('d3util', {
243 - createDragBehavior: createDragBehavior,
244 - loadGlow: loadGlow,
245 - cat7: cat7
246 - });
247 -
248 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - Geometry library - based on work by Mike Bostock.
19 - */
20 -
21 -(function() {
22 -
23 - if (typeof geo == 'undefined') {
24 - geo = {};
25 - }
26 -
27 - var tolerance = 1e-10;
28 -
29 - function eq(a, b) {
30 - return (Math.abs(a - b) < tolerance);
31 - }
32 -
33 - function gt(a, b) {
34 - return (a - b > -tolerance);
35 - }
36 -
37 - function lt(a, b) {
38 - return gt(b, a);
39 - }
40 -
41 - geo.eq = eq;
42 - geo.gt = gt;
43 - geo.lt = lt;
44 -
45 - geo.LineSegment = function(x1, y1, x2, y2) {
46 - this.x1 = x1;
47 - this.y1 = y1;
48 - this.x2 = x2;
49 - this.y2 = y2;
50 -
51 - // Ax + By = C
52 - this.a = y2 - y1;
53 - this.b = x1 - x2;
54 - this.c = x1 * this.a + y1 * this.b;
55 -
56 - if (eq(this.a, 0) && eq(this.b, 0)) {
57 - throw new Error(
58 - 'Cannot construct a LineSegment with two equal endpoints.');
59 - }
60 - };
61 -
62 - geo.LineSegment.prototype.intersect = function(that) {
63 - var d = (this.x1 - this.x2) * (that.y1 - that.y2) -
64 - (this.y1 - this.y2) * (that.x1 - that.x2);
65 -
66 - if (eq(d, 0)) {
67 - // The two lines are parallel or very close.
68 - return {
69 - x : NaN,
70 - y : NaN
71 - };
72 - }
73 -
74 - var t1 = this.x1 * this.y2 - this.y1 * this.x2,
75 - t2 = that.x1 * that.y2 - that.y1 * that.x2,
76 - x = (t1 * (that.x1 - that.x2) - t2 * (this.x1 - this.x2)) / d,
77 - y = (t1 * (that.y1 - that.y2) - t2 * (this.y1 - this.y2)) / d,
78 - in1 = (gt(x, Math.min(this.x1, this.x2)) && lt(x, Math.max(this.x1, this.x2)) &&
79 - gt(y, Math.min(this.y1, this.y2)) && lt(y, Math.max(this.y1, this.y2))),
80 - in2 = (gt(x, Math.min(that.x1, that.x2)) && lt(x, Math.max(that.x1, that.x2)) &&
81 - gt(y, Math.min(that.y1, that.y2)) && lt(y, Math.max(that.y1, that.y2)));
82 -
83 - return {
84 - x : x,
85 - y : y,
86 - in1 : in1,
87 - in2 : in2
88 - };
89 - };
90 -
91 - geo.LineSegment.prototype.x = function(y) {
92 - // x = (C - By) / a;
93 - if (this.a) {
94 - return (this.c - this.b * y) / this.a;
95 - } else {
96 - // a == 0 -> horizontal line
97 - return NaN;
98 - }
99 - };
100 -
101 - geo.LineSegment.prototype.y = function(x) {
102 - // y = (C - Ax) / b;
103 - if (this.b) {
104 - return (this.c - this.a * x) / this.b;
105 - } else {
106 - // b == 0 -> vertical line
107 - return NaN;
108 - }
109 - };
110 -
111 - geo.LineSegment.prototype.length = function() {
112 - return Math.sqrt(
113 - (this.y2 - this.y1) * (this.y2 - this.y1) +
114 - (this.x2 - this.x1) * (this.x2 - this.x1));
115 - };
116 -
117 - geo.LineSegment.prototype.offset = function(x, y) {
118 - return new geo.LineSegment(
119 - this.x1 + x, this.y1 + y,
120 - this.x2 + x, this.y2 + y);
121 - };
122 -
123 -})();
1 -/*
2 - * Copyright 2014 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 - ONOS -- SVG Glyphs Library.
19 - */
20 -
21 -
22 -(function (onos) {
23 - 'use strict';
24 -
25 - var birdViewBox = '352 224 113 112',
26 -
27 - birdData = {
28 - bird: "M427.7,300.4 c-6.9,0.6-13.1,5-19.2,7.1c-18.1,6.2-33.9," +
29 - "9.1-56.5,4.7c24.6,17.2,36.6,13,63.7,0.1c-0.5,0.6-0.7,1.3-1.3," +
30 - "1.9c1.4-0.4,2.4-1.7,3.4-2.2c-0.4,0.7-0.9,1.5-1.4,1.9c2.2-0.6," +
31 - "3.7-2.3,5.9-3.9c-2.4,2.1-4.2,5-6,8c-1.5,2.5-3.1,4.8-5.1,6.9c-1," +
32 - "1-1.9,1.9-2.9,2.9c-1.4,1.3-2.9,2.5-5.1,2.9c1.7,0.1,3.6-0.3,6.5" +
33 - "-1.9c-1.6,2.4-7.1,6.2-9.9,7.2c10.5-2.6,19.2-15.9,25.7-18c18.3" +
34 - "-5.9,13.8-3.4,27-14.2c1.6-1.3,3-1,5.1-0.8c1.1,0.1,2.1,0.3,3.2," +
35 - "0.5c0.8,0.2,1.4,0.4,2.2,0.8l1.8,0.9c-1.9-4.5-2.3-4.1-5.9-6c-2.3" +
36 - "-1.3-3.3-3.8-6.2-4.9c-7.1-2.6-11.9,11.7-11.7-5c0.1-8,4.2-14.4," +
37 - "6.4-22c1.1-3.8,2.3-7.6,2.4-11.5c0.1-2.3,0-4.7-0.4-7c-2-11.2-8.4" +
38 - "-21.5-19.7-24.8c-1-0.3-1.1-0.3-0.9,0c9.6,17.1,7.2,38.3,3.1,54.2" +
39 - "C429.9,285.5,426.7,293.2,427.7,300.4z"
40 - },
41 -
42 - glyphViewBox = '0 0 110 110',
43 -
44 - glyphData = {
45 - unknown: "M35,40a5,5,0,0,1,5-5h30a5,5,0,0,1,5,5v30a5,5,0,0,1-5,5" +
46 - "h-30a5,5,0,0,1-5-5z",
47 -
48 - node: "M15,100a5,5,0,0,1-5-5v-65a5,5,0,0,1,5-5h80a5,5,0,0,1,5,5" +
49 - "v65a5,5,0,0,1-5,5zM14,22.5l11-11a10,3,0,0,1,10-2h40a10,3,0,0,1," +
50 - "10,2l11,11zM16,35a5,5,0,0,1,10,0a5,5,0,0,1-10,0z",
51 -
52 - switch: "M10,20a10,10,0,0,1,10-10h70a10,10,0,0,1,10,10v70a10,10," +
53 - "0,0,1-10,10h-70a10,10,0,0,1-10-10zM60,26l12,0,0-8,18,13-18,13,0" +
54 - "-8-12,0zM60,60l12,0,0-8,18,13-18,13,0-8-12,0zM50,40l-12,0,0-8" +
55 - "-18,13,18,13,0-8,12,0zM50,74l-12,0,0-8-18,13,18,13,0-8,12,0z",
56 -
57 - roadm: "M10,35l25-25h40l25,25v40l-25,25h-40l-25-25zM58,26l12,0,0" +
58 - "-8,18,13-18,13,0-8-12,0zM58,60l12,0,0-8,18,13-18,13,0-8-12,0z" +
59 - "M52,40l-12,0,0-8-18,13,18,13,0-8,12,0zM52,74l-12,0,0-8-18,13," +
60 - "18,13,0-8,12,0z",
61 -
62 - endstation: "M10,15a5,5,0,0,1,5-5h65a5,5,0,0,1,5,5v80a5,5,0,0,1" +
63 - "-5,5h-65a5,5,0,0,1-5-5zM87.5,14l11,11a3,10,0,0,1,2,10v40a3,10," +
64 - "0,0,1,-2,10l-11,11zM17,19a2,2,0,0,1,2-2h56a2,2,0,0,1,2,2v26a2," +
65 - "2,0,0,1-2,2h-56a2,2,0,0,1-2-2zM20,20h54v10h-54zM20,33h54v10h" +
66 - "-54zM42,70a5,5,0,0,1,10,0a5,5,0,0,1-10,0z",
67 -
68 - router: "M10,55A45,45,0,0,1,100,55A45,45,0,0,1,10,55M20,50l12,0," +
69 - "0-8,18,13-18,13,0-8-12,0zM90,50l-12,0,0-8-18,13,18,13,0-8,12,0z" +
70 - "M50,47l0-12-8,0,13-18,13,18-8,0,0,12zM50,63l0,12-8,0,13,18,13" +
71 - "-18-8,0,0-12z",
72 -
73 - bgpSpeaker: "M10,40a45,35,0,0,1,90,0Q100,77,55,100Q10,77,10,40z" +
74 - "M50,29l12,0,0-8,18,13-18,13,0-8-12,0zM60,57l-12,0,0-8-18,13," +
75 - "18,13,0-8,12,0z",
76 -
77 - chain: "M60.4,77.6c-4.9,5.2-9.6,11.3-15.3,16.3c-8.6,7.5-20.4,6.8" +
78 - "-28-0.8c-7.7-7.7-8.4-19.6-0.8-28.4c6.5-7.4,13.5-14.4,20.9-20.9" +
79 - "c7.5-6.7,19.2-6.7,26.5-0.8c3.5,2.8,4.4,6.1,2.2,8.7c-2.7,3.1" +
80 - "-5.5,2.5-8.5,0.3c-4.7-3.4-9.7-3.2-14,0.9C37.1,58.7,31,64.8," +
81 - "25.2,71c-4.2,4.4-4.2,10.6-0.6,14.3c3.7,3.7,9.7,3.7,14.3-0.4" +
82 - "c2.9-2.5,5.3-5.5,8.3-8c1-0.9,3-1.1,4.4-0.9C54.8,76.3,57.9,77.1," +
83 - "60.4,77.6zM49.2,32.2c5-5.2,9.7-10.9,15.2-15.7c12.8-11,31.2" +
84 - "-4.9,34.3,11.2C100,34.2,98.3,40.2,94,45c-6.7,7.4-13.7,14.6" +
85 - "-21.2,21.2C65.1,73,53.2,72.7,46,66.5c-3.2-2.8-3.9-5.8-1.6-8.4" +
86 - "c2.6-2.9,5.3-2.4,8.2-0.3c5.2,3.7,10,3.3,14.7-1.1c5.8-5.6,11.6" +
87 - "-11.3,17.2-17.2c4.6-4.8,4.9-11.1,0.9-15c-3.9-3.9-10.1-3.4-15," +
88 - "1.2c-3.1,2.9-5.7,7.4-9.3,8.5C57.6,35.3,53,33,49.2,32.2z",
89 -
90 - crown: "M99.5,21.6c0,3-2.3,5.4-5.1,5.4c-0.3,0-0.7,0-1-0.1c-1.8," +
91 - "4-4.8,10-7.2,17.3c-3.4,10.6-0.9,26.2,2.7,27.3C90.4,72,91.3," +
92 - "75,88,75H22.7c-3.3,0-2.4-3-0.9-3.5c3.6-1.1,6.1-16.7,2.7-27.3" +
93 - "c-2.4-7.4-5.4-13.5-7.2-17.5c-0.5,0.2-1,0.3-1.6,0.3c-2.8,0" +
94 - "-5.1-2.4-5.1-5.4c0-3,2.3-5.4,5.1-5.4c2.8,0,5.1,2.4,5.1,5.4c0," +
95 - "1-0.2,1.9-0.7,2.7c0.7,0.8,1.4,1.6,2.4,2.6c8.8,8.9,11.9,12.7," +
96 - "18.1,11.7c6.5-1,11-8.2,13.3-14.1c-2-0.8-3.3-2.7-3.3-5.1c0-3," +
97 - "2.3-5.4,5.1-5.4c2.8,0,5.1,2.4,5.1,5.4c0,2.5-1.6,4.5-3.7,5.2" +
98 - "c2.3,5.9,6.8,13,13.2,14c6.2,1,9.3-2.7,18.1-11.7c0.7-0.7,1.4" +
99 - "-1.5,2-2.1c-0.6-0.9-1-2-1-3.1c0-3,2.3-5.4,5.1-5.4C97.2,16.2," +
100 - "99.5,18.6,99.5,21.6zM92,87.9c0,2.2-1.7,4.1-3.8,4.1H22.4c" +
101 - "-2.1,0-4.4-1.9-4.4-4.1v-3.3c0-2.2,2.3-4.5,4.4-4.5h65.8c2.1," +
102 - "0,3.8,2.3,3.8,4.5V87.9z"
103 - },
104 -
105 - badgeViewBox = '0 0 10 10',
106 -
107 - badgeData = {
108 - uiAttached: "M2,2.5a.5,.5,0,0,1,.5-.5h5a.5,.5,0,0,1,.5,.5v3" +
109 - "a.5,.5,0,0,1-.5,.5h-5a.5,.5,0,0,1-.5-.5zM2.5,2.8a.3,.3,0,0,1," +
110 - ".3-.3h4.4a.3,.3,0,0,1,.3,.3v2.4a.3,.3,0,0,1-.3,.3h-4.4" +
111 - "a.3,.3,0,0,1-.3-.3zM2,6.55h6l1,1.45h-8z"
112 - };
113 -
114 - function defStuff(defs, viewbox, data) {
115 - d3.map(data).keys().forEach(function (key) {
116 - defs.append('symbol')
117 - .attr({ id: key, viewBox: viewbox })
118 - .append('path').attr('d', data[key]);
119 - });
120 - }
121 -
122 - function loadDefs(defs) {
123 - defStuff(defs, birdViewBox, birdData);
124 - defStuff(defs, glyphViewBox, glyphData);
125 - defStuff(defs, badgeViewBox, badgeData);
126 - }
127 -
128 - onos.ui.addLib('glyphs', {
129 - loadDefs: loadDefs
130 - });
131 -
132 -}(ONOS));
1 -<!DOCTYPE html>
2 -<!--
3 - ~ Copyright 2014 Open Networking Laboratory
4 - ~
5 - ~ Licensed under the Apache License, Version 2.0 (the "License");
6 - ~ you may not use this file except in compliance with the License.
7 - ~ You may obtain a copy of the License at
8 - ~
9 - ~ http://www.apache.org/licenses/LICENSE-2.0
10 - ~
11 - ~ Unless required by applicable law or agreed to in writing, software
12 - ~ distributed under the License is distributed on an "AS IS" BASIS,
13 - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 - ~ See the License for the specific language governing permissions and
15 - ~ limitations under the License.
16 - -->
17 -
18 -<!--
19 - ONOS UI - single page web app
20 - Version 1.1
21 - -->
22 -<html>
23 -<head>
24 - <meta charset="utf-8">
25 - <link rel="shortcut icon" href="../img/onos-logo.png">
26 - <title>ONOS</title>
27 -
28 - <!-- Third party library code included here -->
29 - <!--TODO: use the minified version of d3, once debugging is complete -->
30 - <script src="../tp/d3.js"></script>
31 - <script src="../tp/topojson.v1.min.js"></script>
32 - <script src="../tp/jquery-2.1.1.min.js"></script>
33 -
34 - <!-- ONOS UI Framework included here -->
35 - <script src="onos.js"></script>
36 -
37 - <!-- Framework and library stylesheets included here -->
38 - <link rel="stylesheet" href="base.css">
39 - <link rel="stylesheet" href="onos.css">
40 - <link rel="stylesheet" href="onosMast.css">
41 - <link rel="stylesheet" href="onosFloatPanel.css">
42 - <link rel="stylesheet" href="onosFlash.css">
43 - <link rel="stylesheet" href="onosQuickHelp.css">
44 -
45 - <!-- This is where contributed stylesheets get INJECTED -->
46 - <!-- TODO: replace with template marker and inject refs server-side -->
47 - <link rel="stylesheet" href="topo.css">
48 -</head>
49 -<body>
50 - <div id="frame">
51 - <div id="mast">
52 - <!-- NOTE: masthead injected here by mast.js -->
53 - </div>
54 - <div id="view">
55 - <!-- NOTE: views injected here by onos.js -->
56 - </div>
57 - <div id="floatPanels">
58 - <!-- NOTE: floating panels injected here, as needed -->
59 - <!-- see onos.ui.addFloatingPanel -->
60 - </div>
61 - <div id="alerts">
62 - <!-- NOTE: alert content injected here, as needed -->
63 - </div>
64 - <div id="feedback">
65 - <!-- NOTE: feedback flashes to user -->
66 - </div>
67 - <div id="quickhelp">
68 - <!-- NOTE: key bindings and mouse gesture help -->
69 - </div>
70 - </div>
71 -
72 - <!-- Initialize the UI...-->
73 - <script type="text/javascript">
74 - var ONOS = $.onos({
75 - comment: 'configuration options',
76 - theme: 'dark',
77 - startVid: 'topo'
78 - });
79 - </script>
80 -
81 - <!-- Library modules included here -->
82 - <script src="d3Utils.js"></script>
83 - <script src="geometry.js"></script>
84 - <script src="glyphs.js"></script>
85 -
86 - <!-- Framework modules included here -->
87 - <script src="onosMast.js"></script>
88 - <script src="onosFlash.js"></script>
89 - <script src="onosQuickHelp.js"></script>
90 -
91 - <!-- View Module Templates; REMOVE THESE LINES, FOR PRODUCTION -->
92 - <script src="module-svg-template.js"></script>
93 - <script src="module-div-template.js"></script>
94 -
95 - <!-- Sample Views; REMOVE THESE LINES, FOR PRODUCTION -->
96 - <script src="sample.js"></script>
97 - <script src="sampleRadio.js"></script>
98 - <script src="sampleKeys.js"></script>
99 - <script src="sampleHash.js"></script>
100 -
101 - <!-- Contributed (application) views injected here -->
102 - <!-- TODO: replace with template marker and inject refs server-side -->
103 - <script src="topo.js"></script>
104 -
105 - <!-- finally, build the UI-->
106 - <script type="text/javascript">
107 - $(ONOS.buildUi);
108 - </script>
109 -
110 -</body>
111 -</html>
1 -/*
2 - * Copyright 2014 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 - Module template file for DIV based view.
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - var list,
25 - data = [ 'foo', 'bar', 'baz' ];
26 -
27 - // invoked only the first time the view is loaded
28 - // - used to initialize the view contents
29 - function init(view, ctx, flags) {
30 - // NOTE: view.$div is a D3 selection of the view's div
31 - list = view.$div.append('ul');
32 - // ... further code to initialize the SVG view ...
33 -
34 - }
35 -
36 - // invoked just prior to loading the view
37 - // - used to clear the view of stale data
38 - function reset(view, ctx, flags) {
39 -
40 - }
41 -
42 - // invoked when the view is loaded
43 - // - used to load data into the view,
44 - // when the view is shown
45 - function load(view, ctx, flags) {
46 - list.selectAll('li')
47 - .data(data)
48 - .enter()
49 - .append('li')
50 - .text(function (d) { return d; })
51 - }
52 -
53 - // invoked when the view is unloaded
54 - // - used to clean up data that should be removed,
55 - // when the view is hidden
56 - function unload(view, ctx, flags) {
57 -
58 - }
59 -
60 - // invoked when the view is resized
61 - // - used to reconfigure elements to the new view size
62 - function resize(view, ctx, flags) {
63 - var w = view.width(),
64 - h = view.height();
65 -
66 - }
67 -
68 - // invoked when the framework needs to alert the view of an error
69 - // - (EXPERIMENTAL -- not currently used)
70 - function error(view, ctx, flags) {
71 -
72 - }
73 -
74 - // ================================================================
75 - // == register the view here, with links to lifecycle callbacks
76 -
77 - // A typical setup that initializes the view once, then reacts to
78 - // load and resize events would look like this:
79 -
80 - onos.ui.addView('myDivViewId', {
81 - init: init,
82 - load: load,
83 - resize: resize
84 - });
85 -
86 - // A minimum setup that builds the view every time it is loaded
87 - // would look like this:
88 - //
89 - // onos.ui.addView('myViewId', {
90 - // reset: true, // clear view contents on reset
91 - // load: load
92 - // });
93 -
94 - // The complete gamut of callbacks would look like this:
95 - //
96 - // onos.ui.addView('myViewId', {
97 - // init: init,
98 - // reset: reset,
99 - // load: load,
100 - // unload: unload,
101 - // resize: resize,
102 - // error: error
103 - // });
104 -
105 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - Module template file for SVG based view.
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - var svg,
25 - data = [ 60 ];
26 -
27 - // invoked only the first time the view is loaded
28 - // - used to initialize the view contents
29 - function init(view, ctx, flags) {
30 - svg = view.$div.append('svg');
31 - resize(view);
32 - // ... further code to initialize the SVG view ...
33 -
34 - }
35 -
36 - // invoked just prior to loading the view
37 - // - used to clear the view of stale data
38 - function reset(view, ctx, flags) {
39 - // e.g. clear svg of all objects...
40 - // svg.html('');
41 -
42 - }
43 -
44 - // invoked when the view is loaded
45 - // - used to load data into the view,
46 - // when the view is shown
47 - function load(view, ctx, flags) {
48 - var w = view.width(),
49 - h = view.height();
50 -
51 - // as an example...
52 - svg.selectAll('circle')
53 - .data(data)
54 - .enter()
55 - .append('circle')
56 - .attr({
57 - cx: w / 2,
58 - cy: h / 2,
59 - r: function (d) { return d; }
60 - })
61 - .style({
62 - fill: 'goldenrod',
63 - stroke: 'black',
64 - 'stroke-width': 3.5,
65 - });
66 - }
67 -
68 - // invoked when the view is unloaded
69 - // - used to clean up data that should be removed,
70 - // when the view is hidden
71 - function unload(view, ctx, flags) {
72 -
73 - }
74 -
75 - // invoked when the view is resized
76 - // - used to reconfigure elements to the new size of the view
77 - function resize(view, ctx, flags) {
78 - var w = view.width(),
79 - h = view.height();
80 -
81 - // resize svg layer to match new size of view
82 - svg.attr({
83 - width: w,
84 - height: h
85 - });
86 -
87 - // as an example...
88 - svg.selectAll('circle')
89 - .attr({
90 - cx: w / 2,
91 - cy: h / 2
92 - });
93 -
94 - // ... further code to handle resize of view ...
95 -
96 - }
97 -
98 - // invoked when the framework needs to alert the view of an error
99 - // - (EXPERIMENTAL -- not currently used)
100 - function error(view, ctx, flags) {
101 -
102 - }
103 -
104 - // ================================================================
105 - // == register the view here, with links to lifecycle callbacks
106 -
107 - // A typical setup that initializes the view once, then reacts to
108 - // load and resize events would look like this:
109 -
110 - onos.ui.addView('mySvgViewId', {
111 - init: init,
112 - load: load,
113 - resize: resize
114 - });
115 -
116 - // A minimum setup that builds the view every time it is loaded
117 - // would look like this:
118 - //
119 - // onos.ui.addView('myViewId', {
120 - // reset: true, // clear view contents on reset
121 - // load: load
122 - // });
123 -
124 - // The complete gamut of callbacks would look like this:
125 - //
126 - // onos.ui.addView('myViewId', {
127 - // init: init,
128 - // reset: reset,
129 - // load: load,
130 - // unload: unload,
131 - // resize: resize,
132 - // error: error
133 - // });
134 -
135 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Base Framework -- CSS file
19 - */
20 -
21 -html, body {
22 - height: 100%;
23 -}
24 -
25 -/* This is to ensure that the body does not expand to account for the
26 - flyout details pane, that is positioned "off screen".
27 - */
28 -body {
29 - overflow: hidden;
30 -}
31 -
32 -#frame {
33 - width: 100%;
34 - height: 100%;
35 - background-color: #fff;
36 -}
37 -
38 -div.onosView {
39 - display: none;
40 -}
41 -
42 -div.onosView.currentView {
43 - display: block;
44 -}
45 -
46 -
47 -#alerts {
48 - display: none;
49 - position: absolute;
50 - z-index: 1200;
51 - opacity: 0.65;
52 - background-color: #006;
53 - color: white;
54 - top: 80px;
55 - left: 40px;
56 - padding: 3px 6px;
57 - -moz-border-radius: 6px;
58 - border-radius: 6px;
59 - box-shadow: 0px 2px 12px #777;
60 -}
61 -
62 -#alerts pre {
63 - margin: 0.2em 6px;
64 -}
65 -
66 -#alerts span.close {
67 - color: #6af;
68 - float: right;
69 - right: 2px;
70 - cursor: pointer;
71 -}
72 -
73 -#alerts span.close:hover {
74 - color: #fff;
75 -}
76 -
77 -#alerts p.footnote {
78 - text-align: right;
79 - font-size: 8pt;
80 - margin: 8px 0 0 0;
81 - color: #66d;
82 -}
83 -
84 -#floatPanels {
85 - z-index: 1100;
86 -}
87 -
88 -#flyout {
89 - position: absolute;
90 - display: block;
91 - top: 10%;
92 - width: 280px;
93 - right: -300px;
94 - opacity: 0;
95 - background-color: rgba(255,255,255,0.8);
96 -
97 - padding: 10px;
98 - color: black;
99 - font-size: 10pt;
100 - box-shadow: 2px 2px 16px #777;
101 -}
102 -
103 -#flyout h2 {
104 - margin: 8px 4px;
105 - color: black;
106 - vertical-align: middle;
107 -}
108 -
109 -#flyout h2 img {
110 - height: 32px;
111 - padding-right: 8px;
112 - vertical-align: middle;
113 -}
114 -
115 -#flyout p, table {
116 - margin: 4px 4px;
117 -}
118 -
119 -#flyout td.label {
120 - font-style: italic;
121 - color: #777;
122 - padding-right: 12px;
123 -}
124 -
125 -#flyout td.value {
126 -
127 -}
128 -
129 -#flyout hr {
130 - height: 1px;
131 - color: #ccc;
132 - background-color: #ccc;
133 - border: 0;
134 -}
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Base Framework
19 - */
20 -
21 -(function ($) {
22 - 'use strict';
23 - var tsI = new Date().getTime(), // initialize time stamp
24 - tsB, // build time stamp
25 - mastHeight = 36, // see mast2.css
26 - defaultVid = 'sample';
27 -
28 -
29 - // attach our main function to the jQuery object
30 - $.onos = function (options) {
31 - var uiApi,
32 - viewApi,
33 - navApi,
34 - libApi,
35 - exported = {};
36 -
37 - var defaultOptions = {
38 - trace: false,
39 - theme: 'dark',
40 - startVid: defaultVid
41 - };
42 -
43 - // compute runtime settings
44 - var settings = $.extend({}, defaultOptions, options);
45 -
46 - // set the selected theme
47 - d3.select('body').classed(settings.theme, true);
48 -
49 - // internal state
50 - var views = {},
51 - fpanels = {},
52 - current = {
53 - view: null,
54 - ctx: '',
55 - flags: {},
56 - theme: settings.theme
57 - },
58 - built = false,
59 - buildErrors = [],
60 - keyHandler = {
61 - globalKeys: {},
62 - maskedKeys: {},
63 - viewKeys: {},
64 - viewFn: null,
65 - viewGestures: []
66 - },
67 - alerts = {
68 - open: false,
69 - count: 0
70 - };
71 -
72 - // DOM elements etc.
73 - // TODO: verify existence of following elements...
74 - var $view = d3.select('#view'),
75 - $floatPanels = d3.select('#floatPanels'),
76 - $alerts = d3.select('#alerts'),
77 - // note, following elements added programmatically...
78 - $mastRadio;
79 -
80 -
81 - function whatKey(code) {
82 - switch (code) {
83 - case 13: return 'enter';
84 - case 16: return 'shift';
85 - case 17: return 'ctrl';
86 - case 18: return 'alt';
87 - case 27: return 'esc';
88 - case 32: return 'space';
89 - case 37: return 'leftArrow';
90 - case 38: return 'upArrow';
91 - case 39: return 'rightArrow';
92 - case 40: return 'downArrow';
93 - case 91: return 'cmdLeft';
94 - case 93: return 'cmdRight';
95 - case 187: return 'equals';
96 - case 189: return 'dash';
97 - case 191: return 'slash';
98 - case 192: return 'backQuote';
99 - case 220: return 'backSlash';
100 - default:
101 - if ((code >= 48 && code <= 57) ||
102 - (code >= 65 && code <= 90)) {
103 - return String.fromCharCode(code);
104 - } else if (code >= 112 && code <= 123) {
105 - return 'F' + (code - 111);
106 - }
107 - return '.';
108 - }
109 - }
110 -
111 -
112 - // ..........................................................
113 - // Internal functions
114 -
115 - // throw an error
116 - function throwError(msg) {
117 - // separate function, as we might add tracing here too, later
118 - throw new Error(msg);
119 - }
120 -
121 - function doError(msg) {
122 - console.error(msg);
123 - doAlert(msg);
124 - }
125 -
126 - function trace(msg) {
127 - if (settings.trace) {
128 - console.log(msg);
129 - }
130 - }
131 -
132 - function traceFn(fn, params) {
133 - if (settings.trace) {
134 - console.log('*FN* ' + fn + '(...): ' + params);
135 - }
136 - }
137 -
138 - // hash navigation
139 - function hash() {
140 - var hash = window.location.hash,
141 - redo = false,
142 - view,
143 - t;
144 -
145 - traceFn('hash', hash);
146 -
147 - if (!hash) {
148 - hash = settings.startVid;
149 - redo = true;
150 - }
151 -
152 - t = parseHash(hash);
153 - if (!t || !t.vid) {
154 - doError('Unable to parse target hash: "' + hash + '"');
155 - }
156 -
157 - view = views[t.vid];
158 - if (!view) {
159 - doError('No view defined with id: ' + t.vid);
160 - }
161 -
162 - if (redo) {
163 - window.location.hash = makeHash(t);
164 - // the above will result in a hashchange event, invoking
165 - // this function again
166 - } else {
167 - // hash was not modified... navigate to where we need to be
168 - navigate(hash, view, t);
169 - }
170 - }
171 -
172 - function parseHash(s) {
173 - // extract navigation coordinates from the supplied string
174 - // "vid,ctx?flag1,flag2" --> { vid:vid, ctx:ctx, flags:{...} }
175 - traceFn('parseHash', s);
176 -
177 - // look for use of flags, first
178 - var vidctx,
179 - vid,
180 - ctx,
181 - flags,
182 - flagMap,
183 - m;
184 -
185 - // RE that includes flags ('?flag1,flag2')
186 - m = /^[#]{0,1}(.+)\?(.+)$/.exec(s);
187 - if (m) {
188 - vidctx = m[1];
189 - flags = m[2];
190 - flagMap = {};
191 - } else {
192 - // no flags
193 - m = /^[#]{0,1}((.+)(,.+)*)$/.exec(s);
194 - if (m) {
195 - vidctx = m[1];
196 - } else {
197 - // bad hash
198 - return null;
199 - }
200 - }
201 -
202 - vidctx = vidctx.split(',');
203 - vid = vidctx[0];
204 - ctx = vidctx[1];
205 - if (flags) {
206 - flags.split(',').forEach(function (f) {
207 - flagMap[f.trim()] = true;
208 - });
209 - }
210 -
211 - return {
212 - vid: vid.trim(),
213 - ctx: ctx ? ctx.trim() : '',
214 - flags: flagMap
215 - };
216 -
217 - }
218 -
219 - function makeHash(t, ctx, flags) {
220 - traceFn('makeHash');
221 - // make a hash string from the given navigation coordinates,
222 - // and optional flags map.
223 - // if t is not an object, then it is a vid
224 - var h = t,
225 - c = ctx || '',
226 - f = $.isPlainObject(flags) ? flags : null;
227 -
228 - if ($.isPlainObject(t)) {
229 - h = t.vid;
230 - c = t.ctx || '';
231 - f = t.flags || null;
232 - }
233 -
234 - if (c) {
235 - h += ',' + c;
236 - }
237 - if (f) {
238 - h += '?' + d3.map(f).keys().join(',');
239 - }
240 - trace('hash = "' + h + '"');
241 - return h;
242 - }
243 -
244 - function navigate(hash, view, t) {
245 - traceFn('navigate', view.vid);
246 - // closePanes() // flyouts etc.
247 - // updateNav() // accordion / selected nav item etc.
248 - createView(view);
249 - setView(view, hash, t);
250 - }
251 -
252 - function buildError(msg) {
253 - buildErrors.push(msg);
254 - }
255 -
256 - function reportBuildErrors() {
257 - traceFn('reportBuildErrors');
258 - var nerr = buildErrors.length,
259 - errmsg;
260 - if (!nerr) {
261 - console.log('(no build errors)');
262 - } else {
263 - errmsg = 'Build errors: ' + nerr + ' found...\n\n' +
264 - buildErrors.join('\n');
265 - doAlert(errmsg);
266 - console.error(errmsg);
267 - }
268 - }
269 -
270 - // returns the reference if it is a function, null otherwise
271 - function isF(f) {
272 - return $.isFunction(f) ? f : null;
273 - }
274 -
275 - // returns the reference if it is an array, null otherwise
276 - function isA(a) {
277 - return $.isArray(a) ? a : null;
278 - }
279 -
280 - // ..........................................................
281 - // View life-cycle functions
282 -
283 - function setViewDimensions(sel) {
284 - var w = window.innerWidth,
285 - h = window.innerHeight - mastHeight;
286 - sel.each(function () {
287 - $(this)
288 - .css('width', w + 'px')
289 - .css('height', h + 'px')
290 - });
291 - }
292 -
293 - function createView(view) {
294 - var $d;
295 -
296 - // lazy initialization of the view
297 - if (view && !view.$div) {
298 - trace('creating view for ' + view.vid);
299 - $d = $view.append('div')
300 - .attr({
301 - id: view.vid,
302 - class: 'onosView'
303 - });
304 - setViewDimensions($d);
305 - view.$div = $d; // cache a reference to the D3 selection
306 - }
307 - }
308 -
309 - function setView(view, hash, t) {
310 - traceFn('setView', view.vid);
311 - // set the specified view as current, while invoking the
312 - // appropriate life-cycle callbacks
313 -
314 - // first, we'll start by closing the alerts pane, if open
315 - closeAlerts();
316 -
317 - // if there is a current view, and it is not the same as
318 - // the incoming view, then unload it...
319 - if (current.view && (current.view.vid !== view.vid)) {
320 - current.view.unload();
321 -
322 - // detach radio buttons, key handlers, etc.
323 - $('#mastRadio').children().detach();
324 - keyHandler.viewKeys = {};
325 - keyHandler.viewFn = null;
326 - }
327 -
328 - // cache new view and context
329 - current.view = view;
330 - current.ctx = t.ctx || '';
331 - current.flags = t.flags || {};
332 -
333 - // init is called only once, after the view is in the DOM
334 - if (!view.inited) {
335 - view.init(current.ctx, current.flags);
336 - view.inited = true;
337 - }
338 -
339 - // clear the view of stale data
340 - view.reset();
341 -
342 - // load the view
343 - view.load(current.ctx, current.flags);
344 - }
345 -
346 - // generate 'unique' id by prefixing view id
347 - function makeUid(view, id) {
348 - return view.vid + '-' + id;
349 - }
350 -
351 - // restore id by removing view id prefix
352 - function unmakeUid(view, uid) {
353 - var re = new RegExp('^' + view.vid + '-');
354 - return uid.replace(re, '');
355 - }
356 -
357 - function setRadioButtons(vid, btnSet) {
358 - var view = views[vid],
359 - btnG,
360 - api = {};
361 -
362 - // lazily create the buttons...
363 - if (!(btnG = view.radioButtons)) {
364 - btnG = d3.select(document.createElement('div'));
365 - btnG.buttonDef = {};
366 -
367 - btnSet.forEach(function (btn, i) {
368 - var bid = btn.id || 'b' + i,
369 - txt = btn.text || 'Button #' + i,
370 - uid = makeUid(view, bid),
371 - button = btnG.append('span')
372 - .attr({
373 - id: uid,
374 - class: 'radio'
375 - })
376 - .text(txt);
377 -
378 - btn.id = bid;
379 - btnG.buttonDef[uid] = btn;
380 -
381 - if (i === 0) {
382 - button.classed('active', true);
383 - btnG.selected = bid;
384 - }
385 - });
386 -
387 - btnG.selectAll('span')
388 - .on('click', function (d) {
389 - var button = d3.select(this),
390 - uid = button.attr('id'),
391 - btn = btnG.buttonDef[uid],
392 - act = button.classed('active');
393 -
394 - if (!act) {
395 - btnG.selectAll('span').classed('active', false);
396 - button.classed('active', true);
397 - btnG.selected = btn.id;
398 - if (isF(btn.cb)) {
399 - btn.cb(view.token(), btn);
400 - }
401 - }
402 - });
403 -
404 - view.radioButtons = btnG;
405 -
406 - api.selected = function () {
407 - return btnG.selected;
408 - }
409 - }
410 -
411 - // attach the buttons to the masthead
412 - $mastRadio.node().appendChild(btnG.node());
413 - // return an api for interacting with the button set
414 - return api;
415 - }
416 -
417 - function setupGlobalKeys() {
418 - $.extend(keyHandler, {
419 - globalKeys: {
420 - backSlash: [quickHelp, 'Show / hide Quick Help'],
421 - slash: [quickHelp, 'Show / hide Quick Help'],
422 - esc: [escapeKey, 'Dismiss dialog or cancel selections'],
423 - T: [toggleTheme, "Toggle theme"]
424 - },
425 - globalFormat: ['backSlash', 'slash', 'esc', 'T'],
426 -
427 - // Masked keys are global key handlers that always return true.
428 - // That is, the view will never see the event for that key.
429 - maskedKeys: {
430 - slash: true,
431 - backSlash: true,
432 - T: true
433 - }
434 - });
435 - }
436 -
437 - function quickHelp(view, key, code, ev) {
438 - libApi.quickHelp.show(keyHandler);
439 - return true;
440 - }
441 -
442 - function escapeKey(view, key, code, ev) {
443 - if (alerts.open) {
444 - closeAlerts();
445 - return true;
446 - }
447 - if (libApi.quickHelp.hide()) {
448 - return true;
449 - }
450 - return false;
451 - }
452 -
453 - function toggleTheme(view, key, code, ev) {
454 - var body = d3.select('body');
455 - current.theme = (current.theme === 'light') ? 'dark' : 'light';
456 - body.classed('light dark', false);
457 - body.classed(current.theme, true);
458 - theme(view);
459 - return true;
460 - }
461 -
462 - function setGestureNotes(g) {
463 - keyHandler.viewGestures = isA(g) || [];
464 - }
465 -
466 - function setKeyBindings(keyArg) {
467 - var viewKeys,
468 - masked = [];
469 -
470 - if ($.isFunction(keyArg)) {
471 - // set general key handler callback
472 - keyHandler.viewFn = keyArg;
473 - } else {
474 - // set specific key filter map
475 - viewKeys = d3.map(keyArg).keys();
476 - viewKeys.forEach(function (key) {
477 - if (keyHandler.maskedKeys[key]) {
478 - masked.push(' Key "' + key + '" is reserved');
479 - }
480 - });
481 -
482 - if (masked.length) {
483 - doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
484 - }
485 - keyHandler.viewKeys = keyArg;
486 - }
487 - }
488 -
489 - function keyIn() {
490 - var event = d3.event,
491 - keyCode = event.keyCode,
492 - key = whatKey(keyCode),
493 - kh = keyHandler,
494 - gk = kh.globalKeys[key],
495 - gcb = isF(gk) || (isA(gk) && isF(gk[0])),
496 - vk = kh.viewKeys[key],
497 - vcb = isF(vk) || (isA(vk) && isF(vk[0])) || isF(kh.viewFn),
498 - token = current.view.token();
499 -
500 - // global callback?
501 - if (gcb && gcb(token, key, keyCode, event)) {
502 - // if the event was 'handled', we are done
503 - return;
504 - }
505 - // otherwise, let the view callback have a shot
506 - if (vcb) {
507 - vcb(token, key, keyCode, event);
508 - }
509 - }
510 -
511 - function createAlerts() {
512 - $alerts.style('display', 'block');
513 - $alerts.append('span')
514 - .attr('class', 'close')
515 - .text('X')
516 - .on('click', closeAlerts);
517 - $alerts.append('pre');
518 - $alerts.append('p').attr('class', 'footnote')
519 - .text('Press ESCAPE to close');
520 - alerts.open = true;
521 - alerts.count = 0;
522 - }
523 -
524 - function closeAlerts() {
525 - $alerts.style('display', 'none')
526 - .html('');
527 - alerts.open = false;
528 - }
529 -
530 - function addAlert(msg) {
531 - var lines,
532 - oldContent;
533 -
534 - if (alerts.count) {
535 - oldContent = $alerts.select('pre').html();
536 - }
537 -
538 - lines = msg.split('\n');
539 - lines[0] += ' '; // spacing so we don't crowd 'X'
540 - lines = lines.join('\n');
541 -
542 - if (oldContent) {
543 - lines += '\n----\n' + oldContent;
544 - }
545 -
546 - $alerts.select('pre').html(lines);
547 - alerts.count++;
548 - }
549 -
550 - function doAlert(msg) {
551 - if (!alerts.open) {
552 - createAlerts();
553 - }
554 - addAlert(msg);
555 - }
556 -
557 - function resize(e) {
558 - d3.selectAll('.onosView').call(setViewDimensions);
559 - // allow current view to react to resize event...
560 - if (current.view) {
561 - current.view.resize(current.ctx, current.flags);
562 - }
563 - }
564 -
565 - function theme() {
566 - // allow current view to react to theme event...
567 - if (current.view) {
568 - current.view.theme(current.ctx, current.flags);
569 - }
570 - }
571 -
572 - // ..........................................................
573 - // View class
574 - // Captures state information about a view.
575 -
576 - // Constructor
577 - // vid : view id
578 - // nid : id of associated nav-item (optional)
579 - // cb : callbacks (init, reset, load, unload, resize, theme, error)
580 - function View(vid) {
581 - var av = 'addView(): ',
582 - args = Array.prototype.slice.call(arguments),
583 - nid,
584 - cb;
585 -
586 - args.shift(); // first arg is always vid
587 - if (typeof args[0] === 'string') { // nid specified
588 - nid = args.shift();
589 - }
590 - cb = args.shift();
591 -
592 - this.vid = vid;
593 -
594 - if (validateViewArgs(vid)) {
595 - this.nid = nid; // explicit navitem id (can be null)
596 - this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
597 - this.$div = null; // view not yet added to DOM
598 - this.radioButtons = null; // no radio buttons yet
599 - this.ok = true; // valid view
600 - }
601 - }
602 -
603 - function validateViewArgs(vid) {
604 - var av = "ui.addView(...): ",
605 - ok = false;
606 - if (typeof vid !== 'string' || !vid) {
607 - doError(av + 'vid required');
608 - } else if (views[vid]) {
609 - doError(av + 'View ID "' + vid + '" already exists');
610 - } else {
611 - ok = true;
612 - }
613 - return ok;
614 - }
615 -
616 - var viewInstanceMethods = {
617 - token: function () {
618 - return {
619 - // attributes
620 - vid: this.vid,
621 - nid: this.nid,
622 - $div: this.$div,
623 -
624 - // functions
625 - width: this.width,
626 - height: this.height,
627 - uid: this.uid,
628 - setRadio: this.setRadio,
629 - setKeys: this.setKeys,
630 - setGestures: this.setGestures,
631 - dataLoadError: this.dataLoadError,
632 - alert: this.alert,
633 - flash: this.flash,
634 - getTheme: this.getTheme
635 - }
636 - },
637 -
638 - // == Life-cycle functions
639 - // TODO: factor common code out of life-cycle
640 - init: function (ctx, flags) {
641 - var c = ctx || '',
642 - fn = isF(this.cb.init);
643 - traceFn('View.init', this.vid + ', ' + c);
644 - if (fn) {
645 - trace('INIT cb for ' + this.vid);
646 - fn(this.token(), c, flags);
647 - }
648 - },
649 -
650 - reset: function (ctx, flags) {
651 - var c = ctx || '',
652 - fn = isF(this.cb.reset);
653 - traceFn('View.reset', this.vid);
654 - if (fn) {
655 - trace('RESET cb for ' + this.vid);
656 - fn(this.token(), c, flags);
657 - } else if (this.cb.reset === true) {
658 - // boolean true signifies "clear view"
659 - trace(' [true] cleaing view...');
660 - viewApi.empty();
661 - }
662 - },
663 -
664 - load: function (ctx, flags) {
665 - var c = ctx || '',
666 - fn = isF(this.cb.load);
667 - traceFn('View.load', this.vid + ', ' + c);
668 - this.$div.classed('currentView', true);
669 - if (fn) {
670 - trace('LOAD cb for ' + this.vid);
671 - fn(this.token(), c, flags);
672 - }
673 - },
674 -
675 - unload: function (ctx, flags) {
676 - var c = ctx | '',
677 - fn = isF(this.cb.unload);
678 - traceFn('View.unload', this.vid);
679 - this.$div.classed('currentView', false);
680 - if (fn) {
681 - trace('UNLOAD cb for ' + this.vid);
682 - fn(this.token(), c, flags);
683 - }
684 - },
685 -
686 - resize: function (ctx, flags) {
687 - var c = ctx || '',
688 - fn = isF(this.cb.resize),
689 - w = this.width(),
690 - h = this.height();
691 - traceFn('View.resize', this.vid + '/' + c +
692 - ' [' + w + 'x' + h + ']');
693 - if (fn) {
694 - trace('RESIZE cb for ' + this.vid);
695 - fn(this.token(), c, flags);
696 - }
697 - },
698 -
699 - theme: function (ctx, flags) {
700 - var c = ctx | '',
701 - fn = isF(this.cb.theme);
702 - traceFn('View.theme', this.vid);
703 - if (fn) {
704 - trace('THEME cb for ' + this.vid);
705 - fn(this.token(), c, flags);
706 - }
707 - },
708 -
709 - error: function (ctx, flags) {
710 - var c = ctx || '',
711 - fn = isF(this.cb.error);
712 - traceFn('View.error', this.vid + ', ' + c);
713 - if (fn) {
714 - trace('ERROR cb for ' + this.vid);
715 - fn(this.token(), c, flags);
716 - }
717 - },
718 -
719 - // == Token API functions
720 - width: function () {
721 - return $(this.$div.node()).width();
722 - },
723 -
724 - height: function () {
725 - return $(this.$div.node()).height();
726 - },
727 -
728 - setRadio: function (btnSet) {
729 - return setRadioButtons(this.vid, btnSet);
730 - },
731 -
732 - setKeys: function (keyArg) {
733 - setKeyBindings(keyArg);
734 - },
735 -
736 - setGestures: function (g) {
737 - setGestureNotes(g);
738 - },
739 -
740 - getTheme: function () {
741 - return current.theme;
742 - },
743 -
744 - uid: function (id) {
745 - return makeUid(this, id);
746 - },
747 -
748 - // TODO : add exportApi and importApi methods
749 - // TODO : implement custom dialogs
750 -
751 - // Consider enhancing alert mechanism to handle multiples
752 - // as individually closable.
753 - alert: function (msg) {
754 - doAlert(msg);
755 - },
756 -
757 - flash: function (msg) {
758 - libApi.feedback.flash(msg);
759 - },
760 -
761 - dataLoadError: function (err, url) {
762 - var msg = 'Data Load Error\n\n' +
763 - err.status + ' -- ' + err.statusText + '\n\n' +
764 - 'relative-url: "' + url + '"\n\n' +
765 - 'complete-url: "' + err.responseURL + '"';
766 - this.alert(msg);
767 - }
768 -
769 - // TODO: consider schedule, clearTimer, etc.
770 - };
771 -
772 - // attach instance methods to the view prototype
773 - $.extend(View.prototype, viewInstanceMethods);
774 -
775 - // ..........................................................
776 - // UI API
777 -
778 - var fpConfig = {
779 - TR: {
780 - side: 'right'
781 - },
782 - TL: {
783 - side: 'left'
784 - }
785 - };
786 -
787 - uiApi = {
788 - addLib: function (libName, api) {
789 - // TODO: validation of args
790 - libApi[libName] = api;
791 - },
792 -
793 - // TODO: implement floating panel as a class
794 - // TODO: parameterize position (currently hard-coded to TopRight)
795 - /*
796 - * Creates div in floating panels block, with the given id.
797 - * Returns panel token used to interact with the panel
798 - */
799 - addFloatingPanel: function (id, position) {
800 - var pos = position || 'TR',
801 - cfg = fpConfig[pos],
802 - el,
803 - fp,
804 - on = false;
805 -
806 - if (fpanels[id]) {
807 - buildError('Float panel with id "' + id + '" already exists.');
808 - return null;
809 - }
810 -
811 - el = $floatPanels.append('div')
812 - .attr('id', id)
813 - .attr('class', 'fpanel')
814 - .style('opacity', 0);
815 -
816 - // has to be called after el is set.
817 - el.style(cfg.side, pxHide());
818 -
819 - function pxShow() {
820 - return '20px';
821 - }
822 - function pxHide() {
823 - return (-20 - widthVal()) + 'px';
824 - }
825 - function noPx(what) {
826 - return el.style(what).replace(/px$/, '');
827 - }
828 - function widthVal() {
829 - return noPx('width');
830 - }
831 - function heightVal() {
832 - return noPx('height');
833 - }
834 -
835 - function noop() {}
836 -
837 - fp = {
838 - id: id,
839 - el: el,
840 - pos: pos,
841 - isVisible: function () {
842 - return on;
843 - },
844 -
845 - show: function (cb) {
846 - var endCb = isF(cb) || noop;
847 - on = true;
848 - el.transition().duration(750)
849 - .each('end', endCb)
850 - .style(cfg.side, pxShow())
851 - .style('opacity', 1);
852 - },
853 - hide: function (cb) {
854 - var endCb = isF(cb) || noop;
855 - on = false;
856 - el.transition().duration(750)
857 - .each('end', endCb)
858 - .style(cfg.side, pxHide())
859 - .style('opacity', 0);
860 - },
861 - empty: function () {
862 - return el.html('');
863 - },
864 - append: function (what) {
865 - return el.append(what);
866 - },
867 - width: function (w) {
868 - if (w === undefined) {
869 - return widthVal();
870 - }
871 - el.style('width', w + 'px');
872 - },
873 - height: function (h) {
874 - if (h === undefined) {
875 - return heightVal();
876 - }
877 - el.style('height', h + 'px');
878 - }
879 - };
880 - fpanels[id] = fp;
881 - return fp;
882 - },
883 -
884 - // TODO: it remains to be seen whether we keep this style of docs
885 - /** @api ui addView( vid, nid, cb )
886 - * Adds a view to the UI.
887 - * <p>
888 - * Views are loaded/unloaded into the view content pane at
889 - * appropriate times, by the navigation framework. This method
890 - * adds a view to the UI and returns a token object representing
891 - * the view. A view's token is always passed as the first
892 - * argument to each of the view's life-cycle callback functions.
893 - * <p>
894 - * Note that if the view is directly referenced by a nav-item,
895 - * or in a group of views with one of those views referenced by
896 - * a nav-item, then the <i>nid</i> argument can be omitted as
897 - * the framework can infer it.
898 - * <p>
899 - * <i>cb</i> is a plain object containing callback functions:
900 - * "init", "reset", "load", "unload", "resize", "theme", "error".
901 - * <pre>
902 - * function myLoad(view, ctx) { ... }
903 - * ...
904 - * // short form...
905 - * onos.ui.addView('viewId', {
906 - * load: myLoad
907 - * });
908 - * </pre>
909 - *
910 - * @param vid (string) [*] view ID (a unique DOM element id)
911 - * @param nid (string) nav-item ID (a unique DOM element id)
912 - * @param cb (object) [*] callbacks object
913 - * @return the view token
914 - */
915 - addView: function (vid, nid, cb) {
916 - traceFn('addView', vid);
917 - var view = new View(vid, nid, cb),
918 - token;
919 - if (view.ok) {
920 - views[vid] = view;
921 - token = view.token();
922 - } else {
923 - token = { vid: view.vid, bad: true };
924 - }
925 - return token;
926 - }
927 - };
928 -
929 - // ..........................................................
930 - // View API
931 -
932 - // TODO: deprecated
933 - viewApi = {
934 - /** @api view empty( )
935 - * Empties the current view.
936 - * <p>
937 - * More specifically, removes all DOM elements from the
938 - * current view's display div.
939 - */
940 - empty: function () {
941 - if (!current.view) {
942 - return;
943 - }
944 - current.view.$div.html('');
945 - }
946 - };
947 -
948 - // ..........................................................
949 - // Nav API
950 - navApi = {
951 -
952 - };
953 -
954 - // ..........................................................
955 - // Library API
956 - libApi = {
957 -
958 - };
959 -
960 - // ..........................................................
961 - // Exported API
962 -
963 - // function to be called from index.html to build the ONOS UI
964 - function buildOnosUi() {
965 - tsB = new Date().getTime();
966 - tsI = tsB - tsI; // initialization duration
967 -
968 - console.log('ONOS UI initialized in ' + tsI + 'ms');
969 -
970 - if (built) {
971 - throwError("ONOS UI already built!");
972 - }
973 - built = true;
974 -
975 - $mastRadio = d3.select('#mastRadio');
976 -
977 - $(window).on('hashchange', hash);
978 - $(window).on('resize', resize);
979 -
980 - d3.select('body').on('keydown', keyIn);
981 - setupGlobalKeys();
982 -
983 - // Invoke hashchange callback to navigate to content
984 - // indicated by the window location hash.
985 - hash();
986 -
987 - // If there were any build errors, report them
988 - reportBuildErrors();
989 - }
990 -
991 - // export the api and build-UI function
992 - return {
993 - ui: uiApi,
994 - lib: libApi,
995 - //view: viewApi,
996 - nav: navApi,
997 - buildUi: buildOnosUi,
998 - exported: exported
999 - };
1000 - };
1001 -
1002 -}(jQuery));
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Feedback layer -- CSS file
19 - */
20 -
21 -#feedback {
22 - z-index: 1400;
23 -}
24 -
25 -#feedback svg {
26 - position: absolute;
27 - bottom: 0;
28 - opacity: 0.8;
29 -}
30 -
31 -#feedback svg g.feedbackItem {
32 - background-color: teal;
33 -}
34 -
35 -#feedback svg g.feedbackItem rect {
36 - fill: #ccc;
37 -}
38 -
39 -#feedback svg g.feedbackItem text {
40 - fill: #333;
41 - stroke: none;
42 - text-anchor: middle;
43 - alignment-baseline: middle;
44 -
45 - font-size: 16pt;
46 -}
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Feedback layer
19 -
20 - Defines the feedback layer for the UI. Used to give user visual feedback
21 - of choices, typically responding to keystrokes.
22 - */
23 -
24 -(function (onos){
25 - 'use strict';
26 -
27 - // API's
28 - var api = onos.api;
29 -
30 - // Config variables
31 - var w = '100%',
32 - h = 200,
33 - fade = 200,
34 - showFor = 1200,
35 - vb = '-200 -' + (h/2) + ' 400 ' + h,
36 - xpad = 20,
37 - ypad = 10;
38 -
39 - // State variables
40 - var timer = null,
41 - data = [];
42 -
43 - // DOM elements and the like
44 - var fb = d3.select('#feedback');
45 -
46 - var svg;
47 -
48 - //var svg = fb.append('svg').attr({
49 - // width: w,
50 - // height: h,
51 - // viewBox: vb
52 - //});
53 -
54 - function computeBox(el) {
55 - var text = el.select('text'),
56 - box = text.node().getBBox();
57 -
58 - // center
59 - box.x = -box.width / 2;
60 - box.y = -box.height / 2;
61 -
62 - // add some padding
63 - box.x -= xpad;
64 - box.width += xpad * 2;
65 - box.y -= ypad;
66 - box.height += ypad * 2;
67 -
68 - return box;
69 - }
70 -
71 - function updateFeedback() {
72 - if (!svg) {
73 - svg = fb.append('svg').attr({
74 - width: w,
75 - height: h,
76 - viewBox: vb
77 - });
78 - }
79 -
80 - var items = svg.selectAll('.feedbackItem')
81 - .data(data);
82 -
83 - var entering = items.enter()
84 - .append('g')
85 - .attr({
86 - class: 'feedbackItem',
87 - opacity: 0
88 - })
89 - .transition()
90 - .duration(fade)
91 - .attr('opacity', 1);
92 -
93 - entering.each(function (d) {
94 - var el = d3.select(this),
95 - box;
96 -
97 - d.el = el;
98 - el.append('rect').attr({ rx: 10, ry: 10});
99 - el.append('text').text(d.label);
100 - box = computeBox(el);
101 - el.select('rect').attr(box);
102 - });
103 -
104 - items.exit()
105 - .transition()
106 - .duration(fade)
107 - .attr({ opacity: 0})
108 - .remove();
109 -
110 - if (svg && data.length === 0) {
111 - svg.transition()
112 - .delay(fade + 10)
113 - .remove();
114 - svg = null;
115 - }
116 - }
117 -
118 - function clearFlash() {
119 - if (timer) {
120 - clearInterval(timer);
121 - }
122 - data = [];
123 - updateFeedback();
124 - }
125 -
126 - // for now, simply display some text feedback
127 - function flash(text) {
128 - // cancel old scheduled event if there was one
129 - if (timer) {
130 - clearInterval(timer);
131 - }
132 - timer = setInterval(function () {
133 - clearFlash();
134 - }, showFor);
135 -
136 - data = [{
137 - label: text
138 - }];
139 - updateFeedback();
140 - }
141 -
142 - onos.ui.addLib('feedback', {
143 - flash: flash
144 - });
145 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Floating Panels -- CSS file
19 - */
20 -
21 -.fpanel {
22 - position: absolute;
23 - z-index: 100;
24 - display: block;
25 - top: 64px;
26 - width: 260px;
27 - right: -300px;
28 - opacity: 0;
29 - background-color: rgba(255,255,255,0.8);
30 -
31 - padding: 10px;
32 - color: black;
33 - font-size: 10pt;
34 -
35 - -moz-border-radius: 6px;
36 - border-radius: 6px;
37 - box-shadow: 0px 2px 12px #777;
38 -}
39 -
40 -/* TODO: light/dark themes */
41 -.light .fpanel {
42 -
43 -}
44 -.dark .fpanel {
45 -
46 -}
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Masthead -- CSS file
19 - */
20 -
21 -#mast {
22 - height: 36px;
23 - padding: 4px;
24 - vertical-align: baseline;
25 -}
26 -
27 -.light #mast {
28 - background-color: #bbb;
29 - box-shadow: 0 2px 8px #777;
30 -}
31 -.dark #mast {
32 - background-color: #444;
33 - box-shadow: 0 2px 8px #777;
34 -}
35 -
36 -#mast img#logo {
37 - height: 38px;
38 - padding-left: 8px;
39 - padding-right: 8px;
40 -}
41 -
42 -#mast span.title {
43 - font-size: 14pt;
44 - font-style: italic;
45 - vertical-align: 12px;
46 -}
47 -
48 -.light #mast span.title {
49 - color: #369;
50 -}
51 -.dark #mast span.title {
52 - color: #eee;
53 -}
54 -
55 -#mast span.right {
56 - padding-top: 8px;
57 - padding-right: 16px;
58 - float: right;
59 -}
60 -
61 -#mast span.radio {
62 - font-size: 10pt;
63 - margin: 4px 2px;
64 - padding: 1px 6px;
65 - -moz-border-radius: 3px;
66 - border-radius: 3px;
67 - cursor: pointer;
68 -}
69 -
70 -.light #mast span.radio {
71 - border: 1px dotted #222;
72 - color: #eee;
73 -}
74 -.dark #mast span.radio {
75 - border: 1px dotted #bbb;
76 - color: #888;
77 -}
78 -
79 -#mast span.radio.active {
80 - padding: 1px 6px;
81 -}
82 -
83 -.light #mast span.radio.active {
84 - background-color: #bbb;
85 - border: 1px solid #eee;
86 - color: #666;
87 -
88 -}
89 -.dark #mast span.radio.active {
90 - background-color: #222;
91 - border: 1px solid #eee;
92 - color: #78a;
93 -}
94 -
95 -/* Button Bar */
96 -
97 -#bb {
98 - margin: 0 30px;
99 - padding: 0 2px;
100 -}
101 -
102 -#bb .btn {
103 - margin: 0 4px;
104 - padding: 2px 6px;
105 - -moz-border-radius: 3px;
106 - border-radius: 3px;
107 - font-size: 9pt;
108 - cursor: pointer;
109 -}
110 -
111 -.light #bb .btn {
112 - border: 1px solid #fff;
113 - border-right-color: #444;
114 - border-bottom-color: #444;
115 - color: #222;
116 -}
117 -
118 -.light #bb .btn.active {
119 - border: 1px solid #444;
120 - border-right-color: #fff;
121 - border-bottom-color: #fff;
122 - background-color: #888;
123 - color: #ddf;
124 -}
125 -
126 -.dark #bb .btn {
127 - border: 1px solid #888;
128 - border-right-color: #111;
129 - border-bottom-color: #111;
130 - color: #888;
131 -}
132 -
133 -.dark #bb .btn.active {
134 - border: 1px solid #111;
135 - border-right-color: #888;
136 - border-bottom-color: #888;
137 - background-color: #555;
138 - color: #78a;
139 -}
140 -
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Masthead script
19 -
20 - Defines the masthead for the UI. Injects logo and title, as well as providing
21 - the placeholder for a set of radio buttons.
22 - */
23 -
24 -(function (onos){
25 - 'use strict';
26 -
27 - // API's
28 - var api = onos.api;
29 -
30 - // Config variables
31 - var guiTitle = 'Open Network Operating System';
32 -
33 - // DOM elements and the like
34 - var mast = d3.select('#mast');
35 -
36 - mast.append('img')
37 - .attr({
38 - id: 'logo',
39 - src: 'onos-logo.png'
40 - });
41 -
42 - mast.append('span')
43 - .attr({
44 - class: 'title'
45 - })
46 - .text(guiTitle);
47 -
48 - mast.append('span')
49 - .attr({
50 - id: 'mastRadio',
51 - class: 'right'
52 - });
53 -
54 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Quick Help layer -- CSS file
19 - */
20 -
21 -#quickhelp {
22 - z-index: 1300;
23 -}
24 -
25 -#quickhelp svg {
26 - position: absolute;
27 - top: 180px;
28 - opacity: 1;
29 -}
30 -
31 -#quickhelp svg g.help rect {
32 - fill: black;
33 - opacity: 0.7;
34 -}
35 -
36 -#quickhelp svg text.title {
37 - font-size: 10pt;
38 - font-style: italic;
39 - text-anchor: middle;
40 - fill: #999;
41 -}
42 -
43 -#quickhelp svg g.keyItem {
44 - fill: white;
45 -}
46 -
47 -#quickhelp svg g line.qhrowsep {
48 - stroke: #888;
49 - stroke-dasharray: 2 2;
50 -}
51 -
52 -#quickhelp svg text {
53 - font-size: 7pt;
54 - alignment-baseline: middle;
55 -}
56 -
57 -#quickhelp svg text.key {
58 - fill: #add;
59 -}
60 -
61 -#quickhelp svg text.desc {
62 - fill: #ddd;
63 -}
64 -
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Quick Help Layer
19 -
20 - Defines the key-map layer for the UI. Used to give user a list of
21 - key bindings; both global, and for the current view.
22 - */
23 -
24 -(function (onos){
25 - 'use strict';
26 -
27 - // Config variables
28 - var w = '100%',
29 - h = '80%',
30 - fade = 500,
31 - vb = '-200 0 400 400';
32 -
33 - // layout configuration
34 - var pad = 10,
35 - offy = 45,
36 - sepYDelta = 20,
37 - colXDelta = 16,
38 - yTextSpc = 12,
39 - offDesc = 8;
40 -
41 - // State variables
42 - var data = [],
43 - yCount;
44 -
45 - // DOM elements and the like
46 - var qhdiv = d3.select('#quickhelp'),
47 - svg = qhdiv.select('svg'),
48 - pane, rect, items;
49 -
50 - // General functions
51 - function isA(a) { return $.isArray(a) ? a : null; }
52 - function isS(s) { return typeof s === 'string'; }
53 -
54 - function cap(s) {
55 - return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); });
56 - }
57 -
58 - var keyDisp = {
59 - equals: '=',
60 - dash: '-',
61 - slash: '/',
62 - backSlash: '\\',
63 - backQuote: '`',
64 - leftArrow: 'L-arrow',
65 - upArrow: 'U-arrow',
66 - rightArrow: 'R-arrow',
67 - downArrow: 'D-arrow'
68 - };
69 -
70 - function mkKeyDisp(id) {
71 - var v = keyDisp[id] || id;
72 - return cap(v);
73 - }
74 -
75 - function addSeparator(el, i) {
76 - var y = sepYDelta/2 - 5;
77 - el.append('line')
78 - .attr({ 'class': 'qhrowsep', x1: 0, y1: y, x2: 0, y2: y });
79 - }
80 -
81 - function addContent(el, data, ri) {
82 - var xCount = 0,
83 - clsPfx = 'qh-r' + ri + '-c';
84 -
85 - function addColumn(el, c, i) {
86 - var cls = clsPfx + i,
87 - oy = 0,
88 - aggKey = el.append('g').attr('visibility', 'hidden'),
89 - gcol = el.append('g').attr({
90 - 'class': cls,
91 - transform: translate(xCount, 0)
92 - });
93 -
94 - c.forEach(function (j) {
95 - var k = j[0],
96 - v = j[1];
97 -
98 - if (k !== '-') {
99 - aggKey.append('text').text(k);
100 -
101 - gcol.append('text').text(k)
102 - .attr({
103 - 'class': 'key',
104 - y: oy
105 - });
106 - gcol.append('text').text(v)
107 - .attr({
108 - 'class': 'desc',
109 - y: oy
110 - });
111 - }
112 -
113 - oy += yTextSpc;
114 - });
115 -
116 - // adjust position of descriptions, based on widest key
117 - var kbox = aggKey.node().getBBox(),
118 - ox = kbox.width + offDesc;
119 - gcol.selectAll('.desc').attr('x', ox);
120 - aggKey.remove();
121 -
122 - // now update x-offset for next column
123 - var bbox = gcol.node().getBBox();
124 - xCount += bbox.width + colXDelta;
125 - }
126 -
127 - data.forEach(function (d, i) {
128 - addColumn(el, d, i);
129 - });
130 -
131 - // finally, return the height of the row..
132 - return el.node().getBBox().height;
133 - }
134 -
135 - function updateKeyItems() {
136 - var rows = items.selectAll('.qhRow').data(data);
137 -
138 - yCount = offy;
139 -
140 - var entering = rows.enter()
141 - .append('g')
142 - .attr({
143 - 'class': 'qhrow'
144 - });
145 -
146 - entering.each(function (r, i) {
147 - var el = d3.select(this),
148 - sep = r.type === 'sep',
149 - dy;
150 -
151 - el.attr('transform', translate(0, yCount));
152 -
153 - if (sep) {
154 - addSeparator(el, i);
155 - yCount += sepYDelta;
156 - } else {
157 - dy = addContent(el, r.data, i);
158 - yCount += dy;
159 - }
160 - });
161 -
162 - // size the backing rectangle
163 - var ibox = items.node().getBBox(),
164 - paneW = ibox.width + pad * 2,
165 - paneH = ibox.height + offy;
166 -
167 - items.selectAll('.qhrowsep').attr('x2', ibox.width);
168 - items.attr('transform', translate(-paneW/2, -pad));
169 - rect.attr({
170 - width: paneW,
171 - height: paneH,
172 - transform: translate(-paneW/2-pad, 0)
173 - });
174 -
175 - }
176 -
177 - function translate(x, y) {
178 - return 'translate(' + x + ',' + y + ')';
179 - }
180 -
181 - function checkFmt(fmt) {
182 - // should be a single array of keys,
183 - // or array of arrays of keys (one per column).
184 - // return null if there is a problem.
185 - var a = isA(fmt),
186 - n = a && a.length,
187 - ns = 0,
188 - na = 0;
189 -
190 - if (n) {
191 - // it is an array which has some content
192 - a.forEach(function (d) {
193 - isA(d) && na++;
194 - isS(d) && ns++;
195 - });
196 - if (na === n || ns === n) {
197 - // all arrays or all strings...
198 - return a;
199 - }
200 - }
201 - return null;
202 - }
203 -
204 - function buildBlock(map, fmt) {
205 - var b = [];
206 - fmt.forEach(function (k) {
207 - var v = map.get(k),
208 - a = isA(v),
209 - d = (a && a[1]);
210 -
211 - // '-' marks a separator; d is the description
212 - if (k === '-' || d) {
213 - b.push([mkKeyDisp(k), d]);
214 - }
215 - });
216 - return b;
217 - }
218 -
219 - function emptyRow() {
220 - return { type: 'row', data: []};
221 - }
222 -
223 - function mkArrRow(fmt) {
224 - var d = emptyRow();
225 - d.data.push(fmt);
226 - return d;
227 - }
228 -
229 - function mkColumnarRow(map, fmt) {
230 - var d = emptyRow();
231 - fmt.forEach(function (a) {
232 - d.data.push(buildBlock(map, a));
233 - });
234 - return d;
235 - }
236 -
237 - function mkMapRow(map, fmt) {
238 - var d = emptyRow();
239 - d.data.push(buildBlock(map, fmt));
240 - return d;
241 - }
242 -
243 - function addRow(row) {
244 - var d = row || { type: 'sep' };
245 - data.push(d);
246 - }
247 -
248 - function aggregateData(bindings) {
249 - var hf = '_helpFormat',
250 - gmap = d3.map(bindings.globalKeys),
251 - gfmt = bindings.globalFormat,
252 - vmap = d3.map(bindings.viewKeys),
253 - vgest = bindings.viewGestures,
254 - vfmt, vkeys;
255 -
256 - // filter out help format entry
257 - vfmt = checkFmt(vmap.get(hf));
258 - vmap.remove(hf);
259 -
260 - // if bad (or no) format, fallback to sorted keys
261 - if (!vfmt) {
262 - vkeys = vmap.keys();
263 - vfmt = vkeys.sort();
264 - }
265 -
266 - data = [];
267 -
268 - addRow(mkMapRow(gmap, gfmt));
269 - addRow();
270 - addRow(isA(vfmt[0]) ? mkColumnarRow(vmap, vfmt) : mkMapRow(vmap, vfmt));
271 - addRow();
272 - addRow(mkArrRow(vgest));
273 - }
274 -
275 -
276 - function popBind(bindings) {
277 - pane = svg.append('g')
278 - .attr({
279 - class: 'help',
280 - opacity: 0
281 - });
282 -
283 - rect = pane.append('rect')
284 - .attr('rx', 8);
285 -
286 - pane.append('text')
287 - .text('Quick Help')
288 - .attr({
289 - class: 'title',
290 - dy: '1.2em',
291 - transform: translate(-pad,0)
292 - });
293 -
294 - items = pane.append('g');
295 - aggregateData(bindings);
296 - updateKeyItems();
297 -
298 - _fade(1);
299 - }
300 -
301 - function fadeBindings() {
302 - _fade(0);
303 - }
304 -
305 - function _fade(o) {
306 - svg.selectAll('g.help')
307 - .transition()
308 - .duration(fade)
309 - .attr('opacity', o);
310 - }
311 -
312 - function addSvg() {
313 - svg = qhdiv.append('svg')
314 - .attr({
315 - width: w,
316 - height: h,
317 - viewBox: vb
318 - });
319 - }
320 -
321 - function removeSvg() {
322 - svg.transition()
323 - .delay(fade + 20)
324 - .remove();
325 - }
326 -
327 - function showQuickHelp(bindings) {
328 - svg = qhdiv.select('svg');
329 - if (svg.empty()) {
330 - addSvg();
331 - popBind(bindings);
332 - } else {
333 - hideQuickHelp();
334 - }
335 - }
336 -
337 - function hideQuickHelp() {
338 - svg = qhdiv.select('svg');
339 - if (!svg.empty()) {
340 - fadeBindings();
341 - removeSvg();
342 - return true;
343 - }
344 - return false;
345 - }
346 -
347 - onos.ui.addLib('quickHelp', {
348 - show: showQuickHelp,
349 - hide: hideQuickHelp
350 - });
351 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - Sample module file to illustrate framework integration.
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - var pi = Math.PI,
25 - svg,
26 - dotG,
27 - nCircles = 12,
28 - circleData = [],
29 - dotId = 0,
30 - angle = 360 / nCircles,
31 - baseAngle = -90 - angle,
32 - groupRadius = 120,
33 - dotRadius = 24,
34 - dotMoveMs = 800,
35 - dotAppearMs = 300,
36 - dotEase = 'elastic',
37 - colorScale = d3.scale.linear()
38 - .domain([-pi/2, 2*pi/4, 3*pi/2])
39 - .range(['green', 'goldenrod', 'blue']);
40 -
41 - // set the size of the SVG layer to match that of the view
42 - function sizeSvg(view) {
43 - svg.attr({
44 - width: view.width(),
45 - height: view.height()
46 - });
47 - }
48 -
49 - // gets invoked only the first time the view is loaded
50 - function init(view, ctx, flags) {
51 - // prepare our SVG layer...
52 - svg = view.$div.append('svg');
53 - sizeSvg(view);
54 - dotG = svg.append('g').attr('id', 'dots');
55 - }
56 -
57 - // gets invoked just before our view is loaded
58 - function reset(view, ctx, flags) {
59 - // clear dot group and reset circle data
60 - dotG.html('');
61 - circleData = [];
62 - // also clear text, if any
63 - svg.selectAll('text').remove();
64 - }
65 -
66 - function updateCirclePositions(view, addNew) {
67 - var w = view.width(),
68 - h = view.height(),
69 - ox = w / 2,
70 - oy = h / 2;
71 -
72 - // reposition existing dots
73 - circleData.forEach(function (c, i) {
74 - var inc = addNew ? 1 : 0,
75 - theta = ((i + inc) * angle + baseAngle) * pi/180,
76 - dx = Math.cos(theta) * groupRadius,
77 - dy = Math.sin(theta) * groupRadius,
78 - x = ox + dx,
79 - y = oy + dy;
80 - if (!addNew && i === 0) {
81 - x = ox;
82 - y = oy;
83 - }
84 - c.cx = x;
85 - c.cy = y;
86 - c.rgb = colorScale(theta);
87 - });
88 -
89 - if (addNew) {
90 - // introduce a new dot
91 - circleData.unshift({
92 - cx: ox,
93 - cy: oy,
94 - id: dotId++
95 - });
96 - }
97 -
98 - // +1 to account for the circle in the center..
99 - if (circleData.length > nCircles + 1) {
100 - circleData.splice(nCircles + 1, 1);
101 - }
102 - }
103 -
104 - function doCircles(view) {
105 - var ox = view.width() / 2,
106 - oy = view.height() / 2,
107 - stroke = 'black',
108 - fill = 'red',
109 - hoverFill = 'magenta';
110 -
111 - // move existing circles, and add a new one
112 - updateCirclePositions(view, true);
113 -
114 - var circ = dotG.selectAll('circle')
115 - .data(circleData, function (d) { return d.id; });
116 -
117 - // operate on existing elements
118 - circ.on('mouseover', null)
119 - .on('mouseout', null)
120 - .on('click', null)
121 - .transition()
122 - .duration(dotMoveMs)
123 - .ease(dotEase)
124 - .attr({
125 - cx: function (d) { return d.cx; },
126 - cy: function (d) { return d.cy; }
127 - })
128 - .style({
129 - cursor: 'default',
130 - fill: function (d) { return d.rgb; }
131 - });
132 -
133 - // operate on entering elements
134 - circ.enter()
135 - .append('circle')
136 - .attr({
137 - cx: function (d) { return d.cx; },
138 - cy: function (d) { return d.cy; },
139 - r: 0
140 - })
141 - .style({
142 - fill: fill,
143 - stroke: stroke,
144 - 'stroke-width': 3.5,
145 - cursor: 'pointer',
146 - opacity: 0
147 - })
148 - .on('mouseover', function (d) {
149 - d3.select(this).style('fill', hoverFill);
150 - })
151 - .on('mouseout', function (d) {
152 - d3.select(this).style('fill', fill);
153 - })
154 - .on('click', function (d) {
155 - setTimeout(function() {
156 - doCircles(view, true);
157 - }, 10);
158 - })
159 - .transition()
160 - .delay(dotMoveMs)
161 - .duration(dotAppearMs)
162 - .attr('r', dotRadius)
163 - .style('opacity', 1);
164 -
165 - // operate on exiting elements
166 - circ.exit()
167 - .transition()
168 - .duration(750)
169 - .style('opacity', 0)
170 - .attr({
171 - cx: ox,
172 - cy: oy,
173 - r: groupRadius - dotRadius
174 - })
175 - .remove();
176 - }
177 -
178 - function load(view, ctx, flags) {
179 - var ctxText = ctx ? 'Context is "' + ctx + '"' : '';
180 -
181 - // display our view context
182 - if (ctxText) {
183 - svg.append('text')
184 - .text(ctxText)
185 - .attr({
186 - x: 20,
187 - y: '1.5em'
188 - })
189 - .style({
190 - fill: 'darkgreen',
191 - 'font-size': '20pt'
192 - });
193 - }
194 -
195 - doCircles(view);
196 - }
197 -
198 - function resize(view, ctx, flags) {
199 - sizeSvg(view);
200 - updateCirclePositions(view);
201 -
202 - // move exiting dots into new positions, relative to view size
203 - var circ = dotG.selectAll('circle')
204 - .data(circleData, function (d) { return d.id; });
205 - circ.attr({
206 - cx: function (d) { return d.cx; },
207 - cy: function (d) { return d.cy; }
208 - });
209 - }
210 -
211 - // == register our view here, with links to lifecycle callbacks
212 -
213 - onos.ui.addView('sample', {
214 - init: init,
215 - reset: reset,
216 - load: load,
217 - resize: resize
218 - });
219 -
220 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - Sample view to illustrate hash formats.
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - var intro = "Try using the following hashes in the address bar:",
25 - hashPrefix = '#sampleHash',
26 - suffixes = [
27 - '',
28 - ',one',
29 - ',two',
30 - ',context,ignored',
31 - ',context,ignored?a,b,c',
32 - ',two?foo',
33 - ',three?foo,bar'
34 - ],
35 - $d;
36 -
37 - function note(txt) {
38 - $d.append('p')
39 - .text(txt)
40 - .style({
41 - 'font-size': '10pt',
42 - color: 'darkorange',
43 - padding: '0 20px',
44 - margin: 0
45 - });
46 - }
47 -
48 - function para(txt, color) {
49 - var c = color || 'black';
50 - $d.append('p')
51 - .text(txt)
52 - .style({
53 - padding: '2px 8px',
54 - color: c
55 - });
56 - }
57 -
58 - function load(view, ctx, flags) {
59 - var c = ctx || '(undefined)',
60 - f = flags ? d3.map(flags).keys() : [];
61 -
62 - $d = view.$div;
63 -
64 - para(intro);
65 -
66 - suffixes.forEach(function (s) {
67 - note(hashPrefix + s);
68 - });
69 -
70 - para('View ID: ' + view.vid, 'blue');
71 - para('Context: ' + c, 'blue');
72 - para('Flags: { ' + f.join(', ') + ' }', 'magenta');
73 - }
74 -
75 - // == register the view here, with links to lifecycle callbacks
76 -
77 - onos.ui.addView('sampleHash', {
78 - reset: true, // empty the div on reset
79 - load: load
80 - });
81 -
82 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - Sample view to illustrate key bindings.
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - var keyDispatch = {
25 - Z: keyUndo,
26 - X: keyCut,
27 - C: keyCopy,
28 - V: keyPaste,
29 - space: keySpace
30 - };
31 -
32 - function keyUndo(view) {
33 - note(view, 'Z = UNDO');
34 - }
35 -
36 - function keyCut(view) {
37 - note(view, 'X = CUT');
38 - }
39 -
40 - function keyCopy(view) {
41 - note(view, 'C = COPY');
42 - }
43 -
44 - function keyPaste(view) {
45 - note(view, 'V = PASTE');
46 - }
47 -
48 - function keySpace(view) {
49 - note(view, 'The SpaceBar');
50 - }
51 -
52 - function note(view, msg) {
53 - view.$div.append('p')
54 - .text(msg)
55 - .style({
56 - 'font-size': '10pt',
57 - color: 'darkorange',
58 - padding: '0 20px',
59 - margin: 0
60 - });
61 - }
62 -
63 - function keyCallback(view, key, keyCode, event) {
64 - note(view, 'Key = ' + key + ' KeyCode = ' + keyCode);
65 - }
66 -
67 - function load(view, ctx) {
68 - // this maps specific keys to specific functions (1)
69 - view.setKeys(keyDispatch);
70 - // whereas, this installs a general key handler function (2)
71 - view.setKeys(keyCallback);
72 -
73 - // Note that (1) takes precedence over (2)
74 -
75 - view.$div.append('p')
76 - .text('Press a key or two (try Z,X,C,V and others) ...')
77 - .style('padding', '2px 8px');
78 - }
79 -
80 - // == register the view here, with links to lifecycle callbacks
81 -
82 - onos.ui.addView('sampleKeys', {
83 - reset: true, // empty the div on reset
84 - load: load
85 - });
86 -
87 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - Sample view to illustrate radio buttons.
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - var intro = [ 'Yo, radio button set...', 'Time to shine' ],
25 - btnSet = [
26 - { text: 'First Button', cb: cbRadio },
27 - { text: 'Second Button', cb: cbRadio },
28 - { text: 'Third Button', cb: cbRadio }
29 - ];
30 -
31 - // radio button callback
32 - function cbRadio(view, btn) {
33 - write(view, 'You pressed the ' + btn.text);
34 - }
35 -
36 - function write(view, msg) {
37 - view.$div.append('p')
38 - .text(msg)
39 - .style({
40 - 'font-size': '10pt',
41 - color: 'green',
42 - padding: '0 20px',
43 - margin: '2px'
44 - });
45 - }
46 -
47 - // invoked when the view is loaded
48 - function load(view, ctx) {
49 - view.setRadio(btnSet);
50 -
51 - view.$div.selectAll('p')
52 - .data(intro)
53 - .enter()
54 - .append('p')
55 - .text(function (d) { return d; })
56 - .style('padding', '2px 8px');
57 - }
58 -
59 - // == register the view here, with links to lifecycle callbacks
60 -
61 - onos.ui.addView('sampleRadio', {
62 - reset: true, // empty the div on reset
63 - load: load
64 - });
65 -
66 -}(ONOS));
1 -/*
2 - * Copyright 2014 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 - ONOS GUI -- Topology view -- CSS file
19 - */
20 -
21 -#topo svg #topo-bg {
22 - opacity: 0.5;
23 -}
24 -
25 -#topo #map {
26 - stroke-width: 2px;
27 - stroke: #eee;
28 - fill: transparent;
29 -}
30 -
31 -/* TODO: move glyphs into framework */
32 -
33 -#topo svg .glyphIcon {
34 - fill: black;
35 - stroke: none;
36 - fill-rule: evenodd;
37 -}
38 -#topo svg .glyphIcon rect {
39 - fill: #ddd;
40 - stroke: none;
41 -}
42 -
43 -
44 -#topo svg .noDevsLayer {
45 - visibility: hidden;
46 -}
47 -
48 -#topo svg .noDevsLayer text {
49 - font-size: 60pt;
50 - font-style: italic;
51 - fill: #dde;
52 -}
53 -
54 -#topo svg .noDevsBird {
55 - fill: #ecd;
56 -}
57 -
58 -/* NODES */
59 -
60 -#topo svg .node {
61 - cursor: pointer;
62 -}
63 -
64 -#topo svg .node.selected rect,
65 -#topo svg .node.selected circle {
66 - fill: #f90;
67 - filter: url(#blue-glow);
68 -}
69 -
70 -#topo svg .node text {
71 - pointer-events: none;
72 -}
73 -
74 -/* Device Nodes */
75 -
76 -#topo svg .node.device {
77 -}
78 -
79 -#topo svg .node.device rect {
80 - stroke-width: 1.5;
81 -}
82 -
83 -#topo svg .node.device.fixed rect {
84 - stroke-width: 1.5;
85 - stroke: #ccc;
86 -}
87 -
88 -/* note: device is offline without the 'online' class */
89 -#topo svg .node.device {
90 - fill: #777;
91 -}
92 -
93 -#topo svg .node.device.online {
94 - fill: #6e7fa3;
95 -}
96 -
97 -/* note: device is offline without the 'online' class */
98 -#topo svg .node.device text {
99 - fill: #bbb;
100 - font: 10pt sans-serif;
101 -}
102 -
103 -#topo svg .node.device.online text {
104 - fill: white;
105 -}
106 -
107 -#topo svg .node.device .glyphIcon rect {
108 - fill: #aaa;
109 -}
110 -#topo svg .node.device .glyphIcon use {
111 - fill: #777;
112 -}
113 -#topo svg .node.device.selected .glyphIcon rect {
114 - fill: #f90;
115 -}
116 -#topo svg .node.device.online .glyphIcon rect {
117 - fill: #ccc;
118 -}
119 -#topo svg .node.device.online .glyphIcon use {
120 - fill: #000;
121 -}
122 -#topo svg .node.device.online.selected .glyphIcon rect {
123 - fill: #f90;
124 -}
125 -
126 -
127 -/* Host Nodes */
128 -
129 -#topo svg .node.host {
130 - stroke: #000;
131 -}
132 -
133 -#topo svg .node.host text {
134 - fill: #846;
135 - stroke: none;
136 - font: 9pt sans-serif;
137 -}
138 -
139 -svg .node.host circle {
140 - stroke: #000;
141 - fill: #edb;
142 -}
143 -
144 -/* LINKS */
145 -
146 -#topo svg .link {
147 - opacity: .9;
148 -}
149 -
150 -#topo svg .link.inactive {
151 - opacity: .5;
152 - stroke-dasharray: 8 4;
153 -}
154 -
155 -#topo svg .link.secondary {
156 - stroke: rgba(0,153,51,0.5);
157 - stroke-width: 3px;
158 -}
159 -#topo svg .link.primary {
160 - stroke: #ffA300;
161 - stroke-width: 4px;
162 -}
163 -#topo svg .link.animated {
164 - stroke: #ffA300;
165 -}
166 -
167 -#topo svg .link.secondary.optical {
168 - stroke: rgba(128,64,255,0.5);
169 - stroke-width: 4px;
170 -}
171 -#topo svg .link.primary.optical {
172 - stroke: #74f;
173 - stroke-width: 6px;
174 -}
175 -#topo svg .link.animated.optical {
176 - stroke: #74f;
177 - stroke-width: 10px;
178 -}
179 -
180 -#topo svg .linkLabel rect {
181 - fill: #eee;
182 - stroke: none;
183 -}
184 -#topo svg .linkLabel text {
185 - text-anchor: middle;
186 - stroke: #777;
187 - stroke-width: 0.1;
188 - font-size: 9pt;
189 -}
190 -
191 -/* Fly-in summary pane */
192 -
193 -#topo-summary {
194 - /* gets base CSS from .fpanel in floatPanel.css */
195 - top: 64px;
196 -}
197 -
198 -#topo-summary svg {
199 - display: inline-block;
200 - width: 42px;
201 - height: 42px;
202 -}
203 -
204 -#topo-summary svg .glyphIcon {
205 - fill: black;
206 - stroke: none;
207 - fill-rule: evenodd;
208 -}
209 -
210 -#topo-summary h2 {
211 - position: absolute;
212 - margin: 0 4px;
213 - top: 20px;
214 - left: 50px;
215 - color: black;
216 -}
217 -
218 -#topo-summary h3 {
219 - margin: 0 4px;
220 - top: 20px;
221 - left: 50px;
222 - color: black;
223 -}
224 -
225 -#topo-summary p, table {
226 - margin: 4px 4px;
227 -}
228 -
229 -#topo-summary td.label {
230 - font-style: italic;
231 - color: #777;
232 - padding-right: 12px;
233 -}
234 -
235 -#topo-summary td.value {
236 -}
237 -
238 -#topo-summary hr {
239 - height: 1px;
240 - color: #ccc;
241 - background-color: #ccc;
242 - border: 0;
243 -}
244 -
245 -/* Fly-in details pane */
246 -
247 -#topo-detail {
248 - /* gets base CSS from .fpanel in floatPanel.css */
249 - top: 320px;
250 -
251 -}
252 -
253 -#topo-detail svg {
254 - display: inline-block;
255 - width: 42px;
256 - height: 42px;
257 -}
258 -
259 -#topo-detail svg .glyphIcon {
260 - fill: black;
261 - stroke: none;
262 - fill-rule: evenodd;
263 -}
264 -
265 -#topo-detail h2 {
266 - position: absolute;
267 - margin: 0 4px;
268 - top: 20px;
269 - left: 50px;
270 - color: black;
271 -}
272 -
273 -#topo-detail h3 {
274 - margin: 0 4px;
275 - top: 20px;
276 - left: 50px;
277 - color: black;
278 -}
279 -
280 -#topo-detail p, table {
281 - margin: 4px 4px;
282 -}
283 -
284 -#topo-detail td.label {
285 - font-style: italic;
286 - color: #777;
287 - padding-right: 12px;
288 -}
289 -
290 -#topo-detail td.value {
291 -}
292 -
293 -
294 -#topo-detail .actionBtn {
295 - margin: 6px 12px;
296 - padding: 2px 6px;
297 - font-size: 9pt;
298 - cursor: pointer;
299 - width: 200px;
300 - text-align: center;
301 -
302 - /* theme specific... */
303 - border: 2px solid #ddd;
304 - border-radius: 4px;
305 - color: #eee;
306 - background: #888;
307 -}
308 -
309 -#topo-detail .actionBtn:hover {
310 - /* theme specific... */
311 - border: 2px solid #ddd;
312 - color: #eee;
313 - background: #444;
314 -}
315 -
316 -
317 -#topo-detail hr {
318 - height: 1px;
319 - color: #ccc;
320 - background-color: #ccc;
321 - border: 0;
322 -}
323 -
324 -/* ONOS instance stuff */
325 -
326 -#topo-oibox {
327 - height: 100px;
328 -}
329 -
330 -#topo-oibox div.onosInst {
331 - display: inline-block;
332 - width: 170px;
333 - height: 85px;
334 - cursor: pointer;
335 -}
336 -
337 -#topo-oibox svg rect {
338 - fill: #ccc;
339 - stroke: #aaa;
340 - stroke-width: 3.5;
341 -}
342 -#topo-oibox .online svg rect {
343 - opacity: 1;
344 - fill: #9cf;
345 - stroke: #555;
346 -}
347 -
348 -#topo-oibox svg .glyphIcon {
349 - fill: #888;
350 - fill-rule: evenodd;
351 -}
352 -#topo-oibox .online svg .glyphIcon {
353 - fill: #000;
354 -}
355 -
356 -#topo-oibox svg .badgeIcon {
357 - fill: #777;
358 - fill-rule: evenodd;
359 -}
360 -
361 -#topo-oibox .online svg .badgeIcon {
362 - fill: #fff;
363 -}
364 -
365 -#topo-oibox svg text {
366 - text-anchor: middle;
367 - fill: #777;
368 -}
369 -#topo-oibox .online svg text {
370 - fill: #eee;
371 -}
372 -
373 -#topo-oibox svg text.instTitle {
374 - font-size: 11pt;
375 - font-weight: bold;
376 -}
377 -#topo-oibox svg text.instLabel {
378 - font-size: 9pt;
379 - font-style: italic;
380 -}
381 -
382 -#topo-oibox .onosInst.mastership {
383 - opacity: 0.3;
384 -}
385 -#topo-oibox .onosInst.mastership.affinity {
386 - opacity: 1.0;
387 -}
388 -#topo-oibox .onosInst.mastership.affinity svg rect {
389 - filter: url(#blue-glow);
390 -}
391 -
392 -
393 -#topo svg .suppressed {
394 - opacity: 0.2;
395 -}
396 -
397 -/* Death Mask (starts hidden) */
398 -
399 -#topo-mask {
400 - display: none;
401 - position: absolute;
402 - top: 0;
403 - left: 0;
404 - width: 10000px;
405 - height: 8000px;
406 - z-index: 5000;
407 - background-color: rgba(0,0,0,0.75);
408 - padding: 60px;
409 -}
410 -
411 -#topo-mask p {
412 - margin: 8px 20px;
413 - color: #ddd;
414 - font-size: 14pt;
415 - font-style: italic;
416 -}
417 -
1 -/*
2 - * Copyright 2014 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 - ONOS network topology viewer - version 1.1
19 - */
20 -
21 -(function (onos) {
22 - 'use strict';
23 -
24 - // shorter names for library APIs
25 - var d3u = onos.lib.d3util,
26 - gly = onos.lib.glyphs;
27 -
28 - // configuration data
29 - var config = {
30 - useLiveData: true,
31 - fnTrace: true,
32 - debugOn: false,
33 - birdDim: 400,
34 - options: {
35 - showBackground: true
36 - },
37 - webSockUrl: '../ws/topology',
38 - data: {
39 - live: {
40 - jsonUrl: '../rs/topology/graph',
41 - detailPrefix: '../rs/topology/graph/',
42 - detailSuffix: ''
43 - },
44 - fake: {
45 - jsonUrl: 'json/network2.json',
46 - detailPrefix: 'json/',
47 - detailSuffix: '.json'
48 - }
49 - },
50 - labels: {
51 - imgPad: 16,
52 - padLR: 4,
53 - padTB: 3,
54 - marginLR: 3,
55 - marginTB: 2,
56 - port: {
57 - gap: 3,
58 - width: 18,
59 - height: 14
60 - }
61 - },
62 - topo: {
63 - linkBaseColor: '#666',
64 - linkInColor: '#66f',
65 - linkInWidth: 12,
66 - linkOutColor: '#f00',
67 - linkOutWidth: 10
68 - },
69 - icons: {
70 - device: {
71 - dim: 36,
72 - rx: 4,
73 - xoff: -20,
74 - yoff: -18
75 - },
76 - host: {
77 - defaultRadius: 9,
78 - radius: {
79 - endstation: 14,
80 - bgpSpeaker: 14,
81 - router: 14
82 - }
83 - }
84 - },
85 - force: {
86 - note_for_links: 'link.type is used to differentiate',
87 - linkDistance: {
88 - direct: 100,
89 - optical: 120,
90 - hostLink: 3
91 - },
92 - linkStrength: {
93 - direct: 1.0,
94 - optical: 1.0,
95 - hostLink: 1.0
96 - },
97 - note_for_nodes: 'node.class is used to differentiate',
98 - charge: {
99 - device: -8000,
100 - host: -5000
101 - }
102 - },
103 - // see below in creation of viewBox on main svg
104 - logicalSize: 1000
105 - };
106 -
107 - // radio buttons
108 - var layerButtons = [
109 - { text: 'All Layers', id: 'all', cb: showAllLayers },
110 - { text: 'Packet Only', id: 'pkt', cb: showPacketLayer },
111 - { text: 'Optical Only', id: 'opt', cb: showOpticalLayer }
112 - ],
113 - layerBtnSet,
114 - layerBtnDispatch = {
115 - all: showAllLayers,
116 - pkt: showPacketLayer,
117 - opt: showOpticalLayer
118 - };
119 -
120 - // key bindings
121 - var keyDispatch = {
122 - // ==== "development mode" ====
123 - //0: testMe,
124 - //equals: injectStartupEvents,
125 - //dash: injectTestEvent,
126 -
127 - O: [toggleSummary, 'Toggle ONOS summary pane'],
128 - I: [toggleInstances, 'Toggle ONOS instances pane'],
129 - D: [toggleDetails, 'Disable / enable details pane'],
130 -
131 - H: [toggleHosts, 'Toggle host visibility'],
132 - M: [toggleOffline, 'Toggle offline visibility'],
133 - B: [toggleBg, 'Toggle background image'],
134 - P: togglePorts,
135 -
136 - X: [toggleNodeLock, 'Lock / unlock node positions'],
137 - Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
138 - L: [cycleLabels, 'Cycle device labels'],
139 - U: [unpin, 'Unpin node (hover mouse over)'],
140 - R: [resetPanZoom, 'Reset pan / zoom'],
141 -
142 - V: [showRelatedIntentsAction, 'Show all related intents'],
143 - rightArrow: [showNextIntentAction, 'Show next related intent'],
144 - leftArrow: [showPrevIntentAction, 'Show previous related intent'],
145 - W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
146 - A: [showAllTrafficAction, 'Monitor all traffic'],
147 - F: [showDeviceLinkFlowsAction, 'Show device link flows'],
148 -
149 - E: [equalizeMasters, 'Equalize mastership roles'],
150 -
151 - esc: handleEscape,
152 -
153 - _helpFormat: [
154 - ['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
155 - ['X', 'Z', 'L', 'U', 'R' ],
156 - ['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
157 - ]
158 - };
159 -
160 - // mouse gestures
161 - var gestures = [
162 - ['click', 'Select the item and show details'],
163 - ['shift-click', 'Toggle selection state'],
164 - ['drag', 'Reposition (and pin) device / host'],
165 - ['cmd-scroll', 'Zoom in / out'],
166 - ['cmd-drag', 'Pan']
167 - ];
168 -
169 - // state variables
170 - var network = {
171 - view: null, // view token reference
172 - nodes: [],
173 - links: [],
174 - lookup: {},
175 - revLinkToKey: {}
176 - },
177 - scenario = {
178 - evDir: 'json/ev/',
179 - evScenario: '/scenario.json',
180 - evPrefix: '/ev_',
181 - evOnos: '_onos.json',
182 - evUi: '_ui.json',
183 - ctx: null,
184 - params: {},
185 - evNumber: 0,
186 - view: null
187 - },
188 - webSock,
189 - sid = 0,
190 - deviceLabelCount = 3,
191 - hostLabelCount = 2,
192 - deviceLabelIndex = 0,
193 - hostLabelIndex = 0,
194 - selections = {},
195 - selectOrder = [],
196 - hovered = null,
197 - summaryPane,
198 - detailPane,
199 - antTimer = null,
200 - guiSuccessor = null,
201 - onosInstances = {},
202 - onosOrder = [],
203 - oiBox,
204 - oiShowMaster = false,
205 - portLabelsOn = false,
206 - cat7 = d3u.cat7(),
207 - colorAffinity = false,
208 - showHosts = false,
209 - showOffline = true,
210 - useDetails = true,
211 - haveDetails = false,
212 - nodeLock = false,
213 - oblique = false;
214 -
215 - // constants
216 - var hoverModeNone = 0,
217 - hoverModeAll = 1,
218 - hoverModeFlows = 2,
219 - hoverModeIntents = 3,
220 - hoverMode = hoverModeNone;
221 -
222 - // D3 selections
223 - var svg,
224 - panZoomContainer,
225 - noDevices,
226 - bgImg,
227 - topoG,
228 - nodeG,
229 - linkG,
230 - linkLabelG,
231 - node,
232 - link,
233 - linkLabel,
234 - mask;
235 -
236 - // the projection for the map background
237 - var geoMapProj;
238 -
239 - // the zoom function
240 - var zoom;
241 -
242 - // ==============================
243 - // For Debugging / Development
244 -
245 - function fnTrace(msg, id) {
246 - if (config.fnTrace) {
247 - console.log('FN: ' + msg + ' [' + id + ']');
248 - }
249 - }
250 -
251 - function evTrace(data) {
252 - fnTrace(data.event, data.payload.id);
253 - }
254 -
255 - // ==============================
256 - // Key Callbacks
257 -
258 - function flash(txt) {
259 - network.view.flash(txt);
260 - }
261 -
262 - function testMe(view) {
263 - //view.alert('Theme is ' + view.getTheme());
264 - //view.flash('This is some text');
265 - cat7.testCard(svg);
266 - }
267 -
268 - function injectTestEvent(view) {
269 - if (config.useLiveData) { return; }
270 -
271 - var sc = scenario,
272 - evn = ++sc.evNumber,
273 - pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
274 - onosUrl = pfx + sc.evOnos,
275 - uiUrl = pfx + sc.evUi,
276 - stack = [
277 - { url: onosUrl, cb: handleServerEvent },
278 - { url: uiUrl, cb: handleUiEvent }
279 - ];
280 - recurseFetchEvent(stack, evn);
281 - }
282 -
283 - function recurseFetchEvent(stack, evn) {
284 - var v = scenario.view,
285 - frame;
286 - if (stack.length === 0) {
287 - v.alert('Oops!\n\nNo event #' + evn + ' found.');
288 - return;
289 - }
290 - frame = stack.shift();
291 -
292 - d3.json(frame.url, function (err, data) {
293 - if (err) {
294 - if (err.status === 404) {
295 - // if we didn't find the data, try the next stack frame
296 - recurseFetchEvent(stack, evn);
297 - } else {
298 - v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
299 - }
300 - } else {
301 - wsTrace('test', JSON.stringify(data));
302 - frame.cb(data);
303 - }
304 - });
305 -
306 - }
307 -
308 - function handleUiEvent(data) {
309 - scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
310 - JSON.stringify(data));
311 - }
312 -
313 - function injectStartupEvents(view) {
314 - var last = scenario.params.lastAuto || 0;
315 - if (config.useLiveData) { return; }
316 -
317 - while (scenario.evNumber < last) {
318 - injectTestEvent(view);
319 - }
320 - }
321 -
322 - function toggleBg() {
323 - var vis = bgImg.style('visibility');
324 - bgImg.style('visibility', visVal(vis === 'hidden'));
325 - }
326 -
327 - function opacifyBg(b) {
328 - bgImg.transition()
329 - .duration(1000)
330 - .attr('opacity', b ? 1 : 0);
331 - }
332 -
333 - function toggleNodeLock() {
334 - nodeLock = !nodeLock;
335 - flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked'))
336 - }
337 -
338 - function toggleOblique() {
339 - oblique = !oblique;
340 - if (oblique) {
341 - network.force.stop();
342 - toObliqueView();
343 - } else {
344 - toNormalView();
345 - }
346 - }
347 -
348 - function toggleHosts() {
349 - showHosts = !showHosts;
350 - updateHostVisibility();
351 - flash('Hosts ' + visVal(showHosts));
352 - }
353 -
354 - function toggleOffline() {
355 - showOffline = !showOffline;
356 - updateOfflineVisibility();
357 - flash('Offline devices ' + visVal(showOffline));
358 - }
359 -
360 - function cycleLabels() {
361 - deviceLabelIndex = (deviceLabelIndex === 2)
362 - ? 0 : deviceLabelIndex + 1;
363 -
364 - network.nodes.forEach(function (d) {
365 - if (d.class === 'device') {
366 - updateDeviceLabel(d);
367 - }
368 - });
369 - }
370 -
371 - function togglePorts(view) {
372 - //view.alert('togglePorts() callback')
373 - }
374 -
375 - function unpin() {
376 - if (hovered) {
377 - sendUpdateMeta(hovered);
378 - hovered.fixed = false;
379 - hovered.el.classed('fixed', false);
380 - fResume();
381 - }
382 - }
383 -
384 - function handleEscape(view) {
385 - if (oiShowMaster) {
386 - cancelAffinity();
387 - } else if (haveDetails) {
388 - deselectAll();
389 - } else if (oiBox.isVisible()) {
390 - hideInstances();
391 - } else if (summaryPane.isVisible()) {
392 - cancelSummary();
393 - stopAntTimer();
394 - } else {
395 - hoverMode = hoverModeNone;
396 - }
397 - }
398 -
399 - function showNoDevs(b) {
400 - noDevices.style('visibility', visVal(b));
401 - }
402 -
403 - // ==============================
404 - // Oblique view ...
405 -
406 - var obview = {
407 - tt: -.7, // x skew y factor
408 - xsk: -35, // x skew angle
409 - ysc: 0.5, // y scale
410 - pad: 50,
411 - time: 1500,
412 - fill: {
413 - pkt: 'rgba(130,130,170,0.3)',
414 - opt: 'rgba(170,130,170,0.3)'
415 - },
416 - id: function (tag) {
417 - return 'obview-' + tag + 'Plane';
418 - },
419 - yt: function (h, dir) {
420 - return h * obview.ysc * dir * 1.1;
421 - },
422 - obXform: function (h, dir) {
423 - var yt = obview.yt(h, dir);
424 - return scale(1, obview.ysc) + translate(0, yt) + skewX(obview.xsk);
425 - },
426 - noXform: function () {
427 - return skewX(0) + translate(0,0) + scale(1,1);
428 - },
429 - xffn: null,
430 - plane: {}
431 - };
432 -
433 -
434 - function toObliqueView() {
435 - var box = nodeG.node().getBBox(),
436 - ox, oy;
437 -
438 - padBox(box, obview.pad);
439 -
440 - ox = box.x + box.width / 2;
441 - oy = box.y + box.height / 2;
442 -
443 - // remember node lock state, then lock the nodes down
444 - obview.nodeLock = nodeLock;
445 - nodeLock = true;
446 - opacifyBg(false);
447 -
448 - insertPlanes(ox, oy);
449 -
450 - obview.xffn = function (xy, dir) {
451 - var yt = obview.yt(box.height, dir),
452 - ax = xy.x - ox,
453 - ay = xy.y - oy,
454 - x = ax + ay * obview.tt,
455 - y = ay * obview.ysc + obview.ysc * yt;
456 - return {x: ox + x, y: oy + y};
457 - };
458 -
459 - showPlane('pkt', box, -1);
460 - showPlane('opt', box, 1);
461 - obTransitionNodes();
462 - }
463 -
464 - function toNormalView() {
465 - obview.xffn = null;
466 -
467 - hidePlane('pkt');
468 - hidePlane('opt');
469 - obTransitionNodes();
470 -
471 - removePlanes();
472 -
473 - // restore node lock state
474 - nodeLock = obview.nodeLock;
475 - opacifyBg(true);
476 - }
477 -
478 - function obTransitionNodes() {
479 - var xffn = obview.xffn;
480 -
481 - // return the direction for the node
482 - // -1 for pkt layer, 1 for optical layer
483 - function dir(d) {
484 - return inLayer(d, 'pkt') ? -1 : 1;
485 - }
486 -
487 - if (xffn) {
488 - network.nodes.forEach(function (d) {
489 - var oldxy = {x: d.x, y: d.y},
490 - coords = xffn(oldxy, dir(d));
491 - d.oldxy = oldxy;
492 - d.px = d.x = coords.x;
493 - d.py = d.y = coords.y;
494 - });
495 - } else {
496 - network.nodes.forEach(function (d) {
497 - var old = d.oldxy || {x: d.x, y: d.y};
498 - d.px = d.x = old.x;
499 - d.py = d.y = old.y;
500 - delete d.oldxy;
501 - });
502 - }
503 -
504 - node.transition()
505 - .duration(obview.time)
506 - .attr(tickStuff.nodeAttr);
507 - link.transition()
508 - .duration(obview.time)
509 - .attr(tickStuff.linkAttr);
510 - linkLabel.transition()
511 - .duration(obview.time)
512 - .attr(tickStuff.linkLabelAttr);
513 - }
514 -
515 - function showPlane(tag, box, dir) {
516 - var g = obview.plane[tag];
517 -
518 - // set box origin at center..
519 - box.x = -box.width/2;
520 - box.y = -box.height/2;
521 -
522 - g.select('rect')
523 - .attr(box)
524 - .attr('opacity', 0)
525 - .transition()
526 - .duration(obview.time)
527 - .attr('opacity', 1)
528 - .attr('transform', obview.obXform(box.height, dir));
529 - }
530 -
531 - function hidePlane(tag) {
532 - var g = obview.plane[tag];
533 -
534 - g.select('rect')
535 - .transition()
536 - .duration(obview.time)
537 - .attr('opacity', 0)
538 - .attr('transform', obview.noXform());
539 - }
540 -
541 - function insertPlanes(ox, oy) {
542 - function ins(tag) {
543 - var id = obview.id(tag),
544 - g = panZoomContainer.insert('g', '#topo-G')
545 - .attr('id', id)
546 - .attr('transform', translate(ox,oy));
547 - g.append('rect')
548 - .attr('fill', obview.fill[tag])
549 - .attr('opacity', 0);
550 - obview.plane[tag] = g;
551 - }
552 - ins('opt');
553 - ins('pkt');
554 - }
555 -
556 - function removePlanes() {
557 - function rem(tag) {
558 - var id = obview.id(tag);
559 - panZoomContainer.select('#'+id)
560 - .transition()
561 - .duration(obview.time + 50)
562 - .remove();
563 - delete obview.plane[tag];
564 - }
565 - rem('opt');
566 - rem('pkt');
567 - }
568 -
569 - function padBox(box, p) {
570 - box.x -= p;
571 - box.y -= p;
572 - box.width += p*2;
573 - box.height += p*2;
574 - }
575 -
576 - // ==============================
577 - // Radio Button Callbacks
578 -
579 - var layerLookup = {
580 - host: {
581 - endstation: 'pkt', // default, if host event does not define type
582 - router: 'pkt',
583 - bgpSpeaker: 'pkt'
584 - },
585 - device: {
586 - switch: 'pkt',
587 - roadm: 'opt'
588 - },
589 - link: {
590 - hostLink: 'pkt',
591 - direct: 'pkt',
592 - indirect: '',
593 - tunnel: '',
594 - optical: 'opt'
595 - }
596 - };
597 -
598 - function inLayer(d, layer) {
599 - var type = d.class === 'link' ? d.type() : d.type,
600 - look = layerLookup[d.class],
601 - lyr = look && look[type];
602 - return lyr === layer;
603 - }
604 -
605 - function unsuppressLayer(which) {
606 - node.each(function (d) {
607 - var node = d.el;
608 - if (inLayer(d, which)) {
609 - node.classed('suppressed', false);
610 - }
611 - });
612 -
613 - link.each(function (d) {
614 - var link = d.el;
615 - if (inLayer(d, which)) {
616 - link.classed('suppressed', false);
617 - }
618 - });
619 - }
620 -
621 - function suppressLayers(b) {
622 - node.classed('suppressed', b);
623 - link.classed('suppressed', b);
624 -// d3.selectAll('svg .port').classed('inactive', false);
625 -// d3.selectAll('svg .portText').classed('inactive', false);
626 - }
627 -
628 - function showAllLayers() {
629 - suppressLayers(false);
630 - }
631 -
632 - function showPacketLayer() {
633 - node.classed('suppressed', true);
634 - link.classed('suppressed', true);
635 - unsuppressLayer('pkt');
636 - }
637 -
638 - function showOpticalLayer() {
639 - node.classed('suppressed', true);
640 - link.classed('suppressed', true);
641 - unsuppressLayer('opt');
642 - }
643 -
644 - function restoreLayerState() {
645 - layerBtnDispatch[layerBtnSet.selected()]();
646 - }
647 -
648 - // ==============================
649 - // Private functions
650 -
651 - function safeId(s) {
652 - return s.replace(/[^a-z0-9]/gi, '-');
653 - }
654 -
655 - // set the size of the given element to that of the view (reduced if padded)
656 - function setSize(el, view, pad) {
657 - var padding = pad ? pad * 2 : 0;
658 - el.attr({
659 - width: view.width() - padding,
660 - height: view.height() - padding
661 - });
662 - }
663 -
664 - function makeNodeKey(d, what) {
665 - var port = what + 'Port';
666 - return d[what] + '/' + d[port];
667 - }
668 -
669 - function makeLinkKey(d, flipped) {
670 - var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
671 - two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
672 - return one + '-' + two;
673 - }
674 -
675 - function findLinkById(id) {
676 - // check to see if this is a reverse lookup, else default to given id
677 - var key = network.revLinkToKey[id] || id;
678 - return key && network.lookup[key];
679 - }
680 -
681 - function findLink(linkData, op) {
682 - var key = makeLinkKey(linkData),
683 - keyrev = makeLinkKey(linkData, 1),
684 - link = network.lookup[key],
685 - linkRev = network.lookup[keyrev],
686 - result = {},
687 - ldata = link || linkRev,
688 - rawLink;
689 -
690 - if (op === 'add') {
691 - if (link) {
692 - // trying to add a link that we already know about
693 - result.ldata = link;
694 - result.badLogic = 'addLink: link already added';
695 -
696 - } else if (linkRev) {
697 - // we found the reverse of the link to be added
698 - result.ldata = linkRev;
699 - if (linkRev.fromTarget) {
700 - result.badLogic = 'addLink: link already added';
701 - }
702 - }
703 - } else if (op === 'update') {
704 - if (!ldata) {
705 - result.badLogic = 'updateLink: link not found';
706 - } else {
707 - rawLink = link ? ldata.fromSource : ldata.fromTarget;
708 - result.updateWith = function (data) {
709 - $.extend(rawLink, data);
710 - restyleLinkElement(ldata);
711 - }
712 - }
713 - } else if (op === 'remove') {
714 - if (!ldata) {
715 - result.badLogic = 'removeLink: link not found';
716 - } else {
717 - rawLink = link ? ldata.fromSource : ldata.fromTarget;
718 -
719 - if (!rawLink) {
720 - result.badLogic = 'removeLink: link not found';
721 -
722 - } else {
723 - result.removeRawLink = function () {
724 - if (link) {
725 - // remove fromSource
726 - ldata.fromSource = null;
727 - if (ldata.fromTarget) {
728 - // promote target into source position
729 - ldata.fromSource = ldata.fromTarget;
730 - ldata.fromTarget = null;
731 - ldata.key = keyrev;
732 - delete network.lookup[key];
733 - network.lookup[keyrev] = ldata;
734 - delete network.revLinkToKey[keyrev];
735 - }
736 - } else {
737 - // remove fromTarget
738 - ldata.fromTarget = null;
739 - delete network.revLinkToKey[keyrev];
740 - }
741 - if (ldata.fromSource) {
742 - restyleLinkElement(ldata);
743 - } else {
744 - removeLinkElement(ldata);
745 - }
746 - }
747 - }
748 - }
749 - }
750 - return result;
751 - }
752 -
753 - function addLinkUpdate(ldata, link) {
754 - // add link event, but we already have the reverse link installed
755 - ldata.fromTarget = link;
756 - network.revLinkToKey[link.id] = ldata.key;
757 - restyleLinkElement(ldata);
758 - }
759 -
760 - var allLinkTypes = 'direct indirect optical tunnel',
761 - defaultLinkType = 'direct';
762 -
763 - function restyleLinkElement(ldata) {
764 - // this fn's job is to look at raw links and decide what svg classes
765 - // need to be applied to the line element in the DOM
766 - var el = ldata.el,
767 - type = ldata.type(),
768 - lw = ldata.linkWidth(),
769 - online = ldata.online();
770 -
771 - el.classed('link', true);
772 - el.classed('inactive', !online);
773 - el.classed(allLinkTypes, false);
774 - if (type) {
775 - el.classed(type, true);
776 - }
777 - el.transition()
778 - .duration(1000)
779 - .attr('stroke-width', linkScale(lw))
780 - .attr('stroke', config.topo.linkBaseColor);
781 - }
782 -
783 - // ==============================
784 - // Event handlers for server-pushed events
785 -
786 - function logicError(msg) {
787 - // TODO, report logic error to server, via websock, so it can be logged
788 - console.warn(msg);
789 - }
790 -
791 - var eventDispatch = {
792 - addInstance: addInstance,
793 - addDevice: addDevice,
794 - addLink: addLink,
795 - addHost: addHost,
796 -
797 - updateInstance: updateInstance,
798 - updateDevice: updateDevice,
799 - updateLink: updateLink,
800 - updateHost: updateHost,
801 -
802 - removeInstance: removeInstance,
803 - removeDevice: removeDevice,
804 - removeLink: removeLink,
805 - removeHost: removeHost,
806 -
807 - showDetails: showDetails,
808 - showSummary: showSummary,
809 - showTraffic: showTraffic
810 - };
811 -
812 - function addInstance(data) {
813 - evTrace(data);
814 - var inst = data.payload,
815 - id = inst.id;
816 - if (onosInstances[id]) {
817 - updateInstance(data);
818 - return;
819 - }
820 - onosInstances[id] = inst;
821 - onosOrder.push(inst);
822 - updateInstances();
823 - }
824 -
825 - function addDevice(data) {
826 - evTrace(data);
827 - var device = data.payload,
828 - id = device.id,
829 - d;
830 -
831 - showNoDevs(false);
832 -
833 - if (network.lookup[id]) {
834 - updateDevice(data);
835 - return;
836 - }
837 -
838 - d = createDeviceNode(device);
839 - network.nodes.push(d);
840 - network.lookup[id] = d;
841 - updateNodes();
842 - fStart();
843 - }
844 -
845 - function addLink(data) {
846 - evTrace(data);
847 - var link = data.payload,
848 - result = findLink(link, 'add'),
849 - bad = result.badLogic,
850 - d = result.ldata;
851 -
852 - if (bad) {
853 - logicError(bad + ': ' + link.id);
854 - return;
855 - }
856 -
857 - if (d) {
858 - // we already have a backing store link for src/dst nodes
859 - addLinkUpdate(d, link);
860 - return;
861 - }
862 -
863 - // no backing store link yet
864 - d = createLink(link);
865 - if (d) {
866 - network.links.push(d);
867 - network.lookup[d.key] = d;
868 - updateLinks();
869 - fStart();
870 - }
871 - }
872 -
873 - function addHost(data) {
874 - evTrace(data);
875 - var host = data.payload,
876 - id = host.id,
877 - d,
878 - lnk;
879 -
880 - if (network.lookup[id]) {
881 - logicError('Host already added: ' + id);
882 - return;
883 - }
884 -
885 - d = createHostNode(host);
886 - network.nodes.push(d);
887 - network.lookup[host.id] = d;
888 - updateNodes();
889 -
890 - lnk = createHostLink(host);
891 - if (lnk) {
892 - d.linkData = lnk; // cache ref on its host
893 - network.links.push(lnk);
894 - network.lookup[d.ingress] = lnk;
895 - network.lookup[d.egress] = lnk;
896 - updateLinks();
897 - }
898 - fStart();
899 - }
900 -
901 - function updateInstance(data) {
902 - evTrace(data);
903 - var inst = data.payload,
904 - id = inst.id,
905 - d = onosInstances[id];
906 - if (d) {
907 - $.extend(d, inst);
908 - updateInstances();
909 - } else {
910 - logicError('updateInstance lookup fail. ID = "' + id + '"');
911 - }
912 - }
913 -
914 - function updateDevice(data) {
915 - evTrace(data);
916 - var device = data.payload,
917 - id = device.id,
918 - d = network.lookup[id],
919 - wasOnline;
920 -
921 - if (d) {
922 - wasOnline = d.online;
923 - $.extend(d, device);
924 - if (positionNode(d, true)) {
925 - sendUpdateMeta(d, true);
926 - }
927 - updateNodes();
928 - if (wasOnline !== d.online) {
929 - findAttachedLinks(d.id).forEach(restyleLinkElement);
930 - updateOfflineVisibility(d);
931 - }
932 - } else {
933 - logicError('updateDevice lookup fail. ID = "' + id + '"');
934 - }
935 - }
936 -
937 - function updateLink(data) {
938 - evTrace(data);
939 - var link = data.payload,
940 - result = findLink(link, 'update'),
941 - bad = result.badLogic;
942 - if (bad) {
943 - logicError(bad + ': ' + link.id);
944 - return;
945 - }
946 - result.updateWith(link);
947 - }
948 -
949 - function updateHost(data) {
950 - evTrace(data);
951 - var host = data.payload,
952 - id = host.id,
953 - d = network.lookup[id];
954 - if (d) {
955 - $.extend(d, host);
956 - if (positionNode(d, true)) {
957 - sendUpdateMeta(d, true);
958 - }
959 - updateNodes(d);
960 - } else {
961 - logicError('updateHost lookup fail. ID = "' + id + '"');
962 - }
963 - }
964 -
965 - function removeInstance(data) {
966 - evTrace(data);
967 - var inst = data.payload,
968 - id = inst.id,
969 - d = onosInstances[id];
970 - if (d) {
971 - var idx = find(id, onosOrder);
972 - if (idx >= 0) {
973 - onosOrder.splice(idx, 1);
974 - }
975 - delete onosInstances[id];
976 - updateInstances();
977 - } else {
978 - logicError('updateInstance lookup fail. ID = "' + id + '"');
979 - }
980 - }
981 -
982 - function removeDevice(data) {
983 - evTrace(data);
984 - var device = data.payload,
985 - id = device.id,
986 - d = network.lookup[id];
987 - if (d) {
988 - removeDeviceElement(d);
989 - } else {
990 - logicError('removeDevice lookup fail. ID = "' + id + '"');
991 - }
992 - }
993 -
994 - function removeLink(data) {
995 - evTrace(data);
996 - var link = data.payload,
997 - result = findLink(link, 'remove'),
998 - bad = result.badLogic;
999 - if (bad) {
1000 - // may have already removed link, if attached to removed device
1001 - console.warn(bad + ': ' + link.id);
1002 - return;
1003 - }
1004 - result.removeRawLink();
1005 - }
1006 -
1007 - function removeHost(data) {
1008 - evTrace(data);
1009 - var host = data.payload,
1010 - id = host.id,
1011 - d = network.lookup[id];
1012 - if (d) {
1013 - removeHostElement(d, true);
1014 - } else {
1015 - // may have already removed host, if attached to removed device
1016 - console.warn('removeHost lookup fail. ID = "' + id + '"');
1017 - }
1018 - }
1019 -
1020 - // the following events are server responses to user actions
1021 - function showSummary(data) {
1022 - evTrace(data);
1023 - populateSummary(data.payload);
1024 - showSummaryPane();
1025 - }
1026 -
1027 - function showDetails(data) {
1028 - evTrace(data);
1029 - haveDetails = true;
1030 - populateDetails(data.payload);
1031 - if (useDetails) {
1032 - showDetailPane();
1033 - }
1034 - }
1035 -
1036 - function showTraffic(data) {
1037 - evTrace(data);
1038 - var paths = data.payload.paths,
1039 - hasTraffic = false;
1040 -
1041 - // Revert any links hilighted previously.
1042 - link.style('stroke-width', null)
1043 - .classed('primary secondary animated optical', false);
1044 - // Remove all previous labels.
1045 - removeLinkLabels();
1046 -
1047 - // Now hilight all links in the paths payload, and attach
1048 - // labels to them, if they are defined.
1049 - paths.forEach(function (p) {
1050 - var n = p.links.length,
1051 - i,
1052 - ldata;
1053 -
1054 - hasTraffic = hasTraffic || p.traffic;
1055 - for (i=0; i<n; i++) {
1056 - ldata = findLinkById(p.links[i]);
1057 - if (ldata && ldata.el) {
1058 - ldata.el.classed(p.class, true);
1059 - ldata.label = p.labels[i];
1060 - }
1061 - }
1062 - });
1063 -
1064 - updateLinks();
1065 -
1066 - if (hasTraffic && !antTimer) {
1067 - startAntTimer();
1068 - } else if (!hasTraffic && antTimer) {
1069 - stopAntTimer();
1070 - }
1071 - }
1072 -
1073 - // ...............................
1074 -
1075 - function unknownEvent(data) {
1076 - console.warn('Unknown event type: "' + data.event + '"', data);
1077 - }
1078 -
1079 - function handleServerEvent(data) {
1080 - var fn = eventDispatch[data.event] || unknownEvent;
1081 - fn(data);
1082 - }
1083 -
1084 - // ==============================
1085 - // Out-going messages...
1086 -
1087 - function nSel() {
1088 - return selectOrder.length;
1089 - }
1090 - function getSel(idx) {
1091 - return selections[selectOrder[idx]];
1092 - }
1093 - function allSelectionsClass(cls) {
1094 - for (var i=0, n=nSel(); i<n; i++) {
1095 - if (getSel(i).obj.class !== cls) {
1096 - return false;
1097 - }
1098 - }
1099 - return true;
1100 - }
1101 -
1102 - function toggleInstances() {
1103 - if (!oiBox.isVisible()) {
1104 - showInstances();
1105 - } else {
1106 - hideInstances();
1107 - }
1108 - }
1109 -
1110 - function showInstances() {
1111 - oiBox.show();
1112 - colorAffinity = true;
1113 - updateDeviceColors();
1114 - }
1115 -
1116 - function hideInstances() {
1117 - oiBox.hide();
1118 - colorAffinity = false;
1119 - cancelAffinity();
1120 - updateDeviceColors();
1121 - }
1122 -
1123 - function equalizeMasters() {
1124 - sendMessage('equalizeMasters');
1125 - flash('Equalizing master roles');
1126 - }
1127 -
1128 - function toggleSummary() {
1129 - if (!summaryPane.isVisible()) {
1130 - requestSummary();
1131 - } else {
1132 - cancelSummary();
1133 - }
1134 - }
1135 -
1136 - function requestSummary() {
1137 - sendMessage('requestSummary');
1138 - }
1139 -
1140 - function cancelSummary() {
1141 - sendMessage('cancelSummary');
1142 - hideSummaryPane();
1143 - }
1144 -
1145 - function toggleDetails() {
1146 - useDetails = !useDetails;
1147 - if (useDetails) {
1148 - flash('Enable details pane');
1149 - if (haveDetails) {
1150 - showDetailPane();
1151 - }
1152 - } else {
1153 - flash('Disable details pane');
1154 - hideDetailPane();
1155 - }
1156 - }
1157 -
1158 - // encapsulate interaction between summary and details panes
1159 - function showSummaryPane() {
1160 - if (detailPane.isVisible()) {
1161 - detailPane.down(summaryPane.show);
1162 - } else {
1163 - summaryPane.show();
1164 - }
1165 - }
1166 -
1167 - function hideSummaryPane() {
1168 - summaryPane.hide(function () {
1169 - if (detailPane.isVisible()) {
1170 - detailPane.up();
1171 - }
1172 - });
1173 - }
1174 -
1175 - function showDetailPane() {
1176 - if (summaryPane.isVisible()) {
1177 - detailPane.down(detailPane.show);
1178 - } else {
1179 - detailPane.up(detailPane.show);
1180 - }
1181 - }
1182 -
1183 - function hideDetailPane() {
1184 - detailPane.hide();
1185 - }
1186 -
1187 -
1188 - // request details for the selected element
1189 - // invoked from selection of a single node.
1190 - function requestDetails() {
1191 - var data = getSel(0).obj;
1192 - sendMessage('requestDetails', {
1193 - id: data.id,
1194 - class: data.class
1195 - });
1196 - }
1197 -
1198 - function addHostIntentAction() {
1199 - sendMessage('addHostIntent', {
1200 - one: selectOrder[0],
1201 - two: selectOrder[1],
1202 - ids: selectOrder
1203 - });
1204 - flash('Host-to-Host flow added');
1205 - }
1206 -
1207 - function addMultiSourceIntentAction() {
1208 - sendMessage('addMultiSourceIntent', {
1209 - src: selectOrder.slice(0, selectOrder.length - 1),
1210 - dst: selectOrder[selectOrder.length - 1],
1211 - ids: selectOrder
1212 - });
1213 - flash('Multi-Source flow added');
1214 - }
1215 -
1216 - function cancelTraffic() {
1217 - sendMessage('cancelTraffic');
1218 - }
1219 -
1220 - function requestTrafficForMode() {
1221 - if (hoverMode === hoverModeFlows) {
1222 - requestDeviceLinkFlows();
1223 - } else if (hoverMode === hoverModeIntents) {
1224 - requestRelatedIntents();
1225 - }
1226 - }
1227 -
1228 - function showRelatedIntentsAction() {
1229 - hoverMode = hoverModeIntents;
1230 - requestRelatedIntents();
1231 - flash('Related Paths');
1232 - }
1233 -
1234 - function requestRelatedIntents() {
1235 - function hoverValid() {
1236 - return hoverMode === hoverModeIntents &&
1237 - hovered &&
1238 - (hovered.class === 'host' || hovered.class === 'device');
1239 - }
1240 -
1241 - if (validateSelectionContext()) {
1242 - sendMessage('requestRelatedIntents', {
1243 - ids: selectOrder,
1244 - hover: hoverValid() ? hovered.id : ''
1245 - });
1246 - }
1247 - }
1248 -
1249 - function showNextIntentAction() {
1250 - hoverMode = hoverModeNone;
1251 - sendMessage('requestNextRelatedIntent');
1252 - flash('>');
1253 - }
1254 -
1255 - function showPrevIntentAction() {
1256 - hoverMode = hoverModeNone;
1257 - sendMessage('requestPrevRelatedIntent');
1258 - flash('<');
1259 - }
1260 -
1261 - function showSelectedIntentTrafficAction() {
1262 - hoverMode = hoverModeNone;
1263 - sendMessage('requestSelectedIntentTraffic');
1264 - flash('Traffic on Selected Path');
1265 - }
1266 -
1267 - function showDeviceLinkFlowsAction() {
1268 - hoverMode = hoverModeFlows;
1269 - requestDeviceLinkFlows();
1270 - flash('Device Flows');
1271 - }
1272 -
1273 - function requestDeviceLinkFlows() {
1274 - function hoverValid() {
1275 - return hoverMode === hoverModeFlows &&
1276 - hovered && (hovered.class === 'device');
1277 - }
1278 -
1279 - if (validateSelectionContext()) {
1280 - sendMessage('requestDeviceLinkFlows', {
1281 - ids: selectOrder,
1282 - hover: hoverValid() ? hovered.id : ''
1283 - });
1284 - }
1285 - }
1286 -
1287 - function showAllTrafficAction() {
1288 - hoverMode = hoverModeAll;
1289 - requestAllTraffic();
1290 - flash('All Traffic');
1291 - }
1292 -
1293 - function requestAllTraffic() {
1294 - sendMessage('requestAllTraffic');
1295 - }
1296 -
1297 - function validateSelectionContext() {
1298 - if (!hovered && nSel() === 0) {
1299 - cancelTraffic();
1300 - return false;
1301 - }
1302 - return true;
1303 - }
1304 -
1305 -
1306 - // ==============================
1307 - // onos instance panel functions
1308 -
1309 - var instCfg = {
1310 - rectPad: 8,
1311 - nodeOx: 9,
1312 - nodeOy: 9,
1313 - nodeDim: 40,
1314 - birdOx: 19,
1315 - birdOy: 21,
1316 - birdDim: 21,
1317 - uiDy: 45,
1318 - titleDy: 30,
1319 - textYOff: 20,
1320 - textYSpc: 15
1321 - };
1322 -
1323 - function viewBox(dim) {
1324 - return '0 0 ' + dim.w + ' ' + dim.h;
1325 - }
1326 -
1327 - function instRectAttr(dim) {
1328 - var pad = instCfg.rectPad;
1329 - return {
1330 - x: pad,
1331 - y: pad,
1332 - width: dim.w - pad*2,
1333 - height: dim.h - pad*2,
1334 - rx: 6
1335 - };
1336 - }
1337 -
1338 - function computeDim(self) {
1339 - var css = window.getComputedStyle(self);
1340 - return {
1341 - w: stripPx(css.width),
1342 - h: stripPx(css.height)
1343 - };
1344 - }
1345 -
1346 - function updateInstances() {
1347 - var onoses = oiBox.el.selectAll('.onosInst')
1348 - .data(onosOrder, function (d) { return d.id; }),
1349 - instDim = {w:0,h:0},
1350 - c = instCfg;
1351 -
1352 - function nSw(n) {
1353 - return '# Switches: ' + n;
1354 - }
1355 -
1356 - // operate on existing onos instances if necessary
1357 - onoses.each(function (d) {
1358 - var el = d3.select(this),
1359 - svg = el.select('svg');
1360 - instDim = computeDim(this);
1361 -
1362 - // update online state
1363 - el.classed('online', d.online);
1364 -
1365 - // update ui-attached state
1366 - svg.select('use.uiBadge').remove();
1367 - if (d.uiAttached) {
1368 - attachUiBadge(svg);
1369 - }
1370 -
1371 - function updAttr(id, value) {
1372 - svg.select('text.instLabel.'+id).text(value);
1373 - }
1374 -
1375 - updAttr('ip', d.ip);
1376 - updAttr('ns', nSw(d.switches));
1377 - });
1378 -
1379 -
1380 - // operate on new onos instances
1381 - var entering = onoses.enter()
1382 - .append('div')
1383 - .attr('class', 'onosInst')
1384 - .classed('online', function (d) { return d.online; })
1385 - .on('click', clickInst);
1386 -
1387 - entering.each(function (d) {
1388 - var el = d3.select(this),
1389 - rectAttr,
1390 - svg;
1391 - instDim = computeDim(this);
1392 - rectAttr = instRectAttr(instDim);
1393 -
1394 - svg = el.append('svg').attr({
1395 - width: instDim.w,
1396 - height: instDim.h,
1397 - viewBox: viewBox(instDim)
1398 - });
1399 -
1400 - svg.append('rect').attr(rectAttr);
1401 -
1402 - appendBadge(svg, 14, 14, 28, '#bird');
1403 -
1404 - if (d.uiAttached) {
1405 - attachUiBadge(svg);
1406 - }
1407 -
1408 - var left = c.nodeOx + c.nodeDim,
1409 - len = rectAttr.width - left,
1410 - hlen = len / 2,
1411 - midline = hlen + left;
1412 -
1413 - // title
1414 - svg.append('text')
1415 - .attr({
1416 - class: 'instTitle',
1417 - x: midline,
1418 - y: c.titleDy
1419 - })
1420 - .text(d.id);
1421 -
1422 - // a couple of attributes
1423 - var ty = c.titleDy + c.textYOff;
1424 -
1425 - function addAttr(id, label) {
1426 - svg.append('text').attr({
1427 - class: 'instLabel ' + id,
1428 - x: midline,
1429 - y: ty
1430 - }).text(label);
1431 - ty += c.textYSpc;
1432 - }
1433 -
1434 - addAttr('ip', d.ip);
1435 - addAttr('ns', nSw(d.switches));
1436 - });
1437 -
1438 - // operate on existing + new onoses here
1439 - // set the affinity colors...
1440 - onoses.each(function (d) {
1441 - var el = d3.select(this),
1442 - rect = el.select('svg').select('rect'),
1443 - col = instColor(d.id, d.online);
1444 - rect.style('fill', col);
1445 - });
1446 -
1447 - // adjust the panel size appropriately...
1448 - oiBox.width(instDim.w * onosOrder.length);
1449 - oiBox.height(instDim.h);
1450 -
1451 - // remove any outgoing instances
1452 - onoses.exit().remove();
1453 - }
1454 -
1455 - function instColor(id, online) {
1456 - return cat7.get(id, !online, network.view.getTheme());
1457 - }
1458 -
1459 - function clickInst(d) {
1460 - var el = d3.select(this),
1461 - aff = el.classed('affinity');
1462 - if (!aff) {
1463 - setAffinity(el, d);
1464 - } else {
1465 - cancelAffinity();
1466 - }
1467 - }
1468 -
1469 - function setAffinity(el, d) {
1470 - d3.selectAll('.onosInst')
1471 - .classed('mastership', true)
1472 - .classed('affinity', false);
1473 - el.classed('affinity', true);
1474 -
1475 - suppressLayers(true);
1476 - node.each(function (n) {
1477 - if (n.master === d.id) {
1478 - n.el.classed('suppressed', false);
1479 - }
1480 - });
1481 - oiShowMaster = true;
1482 - }
1483 -
1484 - function cancelAffinity() {
1485 - d3.selectAll('.onosInst')
1486 - .classed('mastership affinity', false);
1487 - restoreLayerState();
1488 - oiShowMaster = false;
1489 - }
1490 -
1491 - // TODO: these should be moved out to utility module.
1492 - function stripPx(s) {
1493 - return s.replace(/px$/,'');
1494 - }
1495 -
1496 - function appendUse(svg, ox, oy, dim, iid, cls) {
1497 - var use = svg.append('use').attr({
1498 - transform: translate(ox,oy),
1499 - 'xlink:href': iid,
1500 - width: dim,
1501 - height: dim
1502 - });
1503 - if (cls) {
1504 - use.classed(cls, true);
1505 - }
1506 - return use;
1507 - }
1508 -
1509 - function appendGlyph(svg, ox, oy, dim, iid, cls) {
1510 - appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
1511 - }
1512 -
1513 - function appendBadge(svg, ox, oy, dim, iid, cls) {
1514 - appendUse(svg, ox, oy, dim, iid, cls).classed('badgeIcon', true);
1515 - }
1516 -
1517 - function attachUiBadge(svg) {
1518 - appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
1519 - }
1520 -
1521 - function visVal(b) {
1522 - return b ? 'visible' : 'hidden';
1523 - }
1524 -
1525 - // ==============================
1526 - // force layout modification functions
1527 -
1528 - function translate(x, y) {
1529 - return 'translate(' + x + ',' + y + ')';
1530 - }
1531 - function scale(x,y) {
1532 - return 'scale(' + x + ',' + y + ')';
1533 - }
1534 - function skewX(x) {
1535 - return 'skewX(' + x + ')';
1536 - }
1537 - function rotate(deg) {
1538 - return 'rotate(' + deg + ')';
1539 - }
1540 -
1541 - function missMsg(what, id) {
1542 - return '\n[' + what + '] "' + id + '" missing ';
1543 - }
1544 -
1545 - function linkEndPoints(srcId, dstId) {
1546 - var srcNode = network.lookup[srcId],
1547 - dstNode = network.lookup[dstId],
1548 - sMiss = !srcNode ? missMsg('src', srcId) : '',
1549 - dMiss = !dstNode ? missMsg('dst', dstId) : '';
1550 -
1551 - if (sMiss || dMiss) {
1552 - logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
1553 - return null;
1554 - }
1555 - return {
1556 - source: srcNode,
1557 - target: dstNode,
1558 - x1: srcNode.x,
1559 - y1: srcNode.y,
1560 - x2: dstNode.x,
1561 - y2: dstNode.y
1562 - };
1563 - }
1564 -
1565 - function createHostLink(host) {
1566 - var src = host.id,
1567 - dst = host.cp.device,
1568 - id = host.ingress,
1569 - lnk = linkEndPoints(src, dst);
1570 -
1571 - if (!lnk) {
1572 - return null;
1573 - }
1574 -
1575 - // Synthesize link ...
1576 - $.extend(lnk, {
1577 - key: id,
1578 - class: 'link',
1579 -
1580 - type: function () { return 'hostLink'; },
1581 - online: function () {
1582 - // hostlink target is edge switch
1583 - return lnk.target.online;
1584 - },
1585 - linkWidth: function () { return 1; }
1586 - });
1587 - return lnk;
1588 - }
1589 -
1590 - function createLink(link) {
1591 - var lnk = linkEndPoints(link.src, link.dst);
1592 -
1593 - if (!lnk) {
1594 - return null;
1595 - }
1596 -
1597 - $.extend(lnk, {
1598 - key: link.id,
1599 - class: 'link',
1600 - fromSource: link,
1601 -
1602 - // functions to aggregate dual link state
1603 - type: function () {
1604 - var s = lnk.fromSource,
1605 - t = lnk.fromTarget;
1606 - return (s && s.type) || (t && t.type) || defaultLinkType;
1607 - },
1608 - online: function () {
1609 - var s = lnk.fromSource,
1610 - t = lnk.fromTarget,
1611 - both = lnk.source.online && lnk.target.online;
1612 - return both && ((s && s.online) || (t && t.online));
1613 - },
1614 - linkWidth: function () {
1615 - var s = lnk.fromSource,
1616 - t = lnk.fromTarget,
1617 - ws = (s && s.linkWidth) || 0,
1618 - wt = (t && t.linkWidth) || 0;
1619 - return Math.max(ws, wt);
1620 - }
1621 - });
1622 - return lnk;
1623 - }
1624 -
1625 - function removeLinkLabels() {
1626 - network.links.forEach(function (d) {
1627 - d.label = '';
1628 - });
1629 - }
1630 -
1631 - function showHostVis(el) {
1632 - el.style('visibility', visVal(showHosts));
1633 - }
1634 -
1635 - var widthRatio = 1.4,
1636 - linkScale = d3.scale.linear()
1637 - .domain([1, 12])
1638 - .range([widthRatio, 12 * widthRatio])
1639 - .clamp(true);
1640 -
1641 - function updateLinks() {
1642 - link = linkG.selectAll('.link')
1643 - .data(network.links, function (d) { return d.key; });
1644 -
1645 - // operate on existing links, if necessary
1646 - // link .foo() .bar() ...
1647 -
1648 - // operate on entering links:
1649 - var entering = link.enter()
1650 - .append('line')
1651 - .attr({
1652 - x1: function (d) { return d.x1; },
1653 - y1: function (d) { return d.y1; },
1654 - x2: function (d) { return d.x2; },
1655 - y2: function (d) { return d.y2; },
1656 - stroke: config.topo.linkInColor,
1657 - 'stroke-width': config.topo.linkInWidth
1658 - });
1659 -
1660 - // augment links
1661 - entering.each(function (d) {
1662 - var link = d3.select(this);
1663 - // provide ref to element selection from backing data....
1664 - d.el = link;
1665 - restyleLinkElement(d);
1666 - if (d.type() === 'hostLink') {
1667 - showHostVis(link);
1668 - }
1669 - });
1670 -
1671 - // operate on both existing and new links, if necessary
1672 - //link .foo() .bar() ...
1673 -
1674 - // apply or remove labels
1675 - var labelData = getLabelData();
1676 - applyLinkLabels(labelData);
1677 -
1678 - // operate on exiting links:
1679 - link.exit()
1680 - .attr('stroke-dasharray', '3 3')
1681 - .style('opacity', 0.5)
1682 - .transition()
1683 - .duration(1500)
1684 - .attr({
1685 - 'stroke-dasharray': '3 12',
1686 - stroke: config.topo.linkOutColor,
1687 - 'stroke-width': config.topo.linkOutWidth
1688 - })
1689 - .style('opacity', 0.0)
1690 - .remove();
1691 -
1692 - // NOTE: invoke a single tick to force the labels to position
1693 - // onto their links.
1694 - tick();
1695 - }
1696 -
1697 - function getLabelData() {
1698 - // create the backing data for showing labels..
1699 - var data = [];
1700 - link.each(function (d) {
1701 - if (d.label) {
1702 - data.push({
1703 - id: 'lab-' + d.key,
1704 - key: d.key,
1705 - label: d.label,
1706 - ldata: d
1707 - });
1708 - }
1709 - });
1710 - return data;
1711 - }
1712 -
1713 - var linkLabelOffset = '0.3em';
1714 -
1715 - function applyLinkLabels(data) {
1716 - var entering;
1717 -
1718 - linkLabel = linkLabelG.selectAll('.linkLabel')
1719 - .data(data, function (d) { return d.id; });
1720 -
1721 - // for elements already existing, we need to update the text
1722 - // and adjust the rectangle size to fit
1723 - linkLabel.each(function (d) {
1724 - var el = d3.select(this),
1725 - rect = el.select('rect'),
1726 - text = el.select('text');
1727 - text.text(d.label);
1728 - rect.attr(rectAroundText(el));
1729 - });
1730 -
1731 - entering = linkLabel.enter().append('g')
1732 - .classed('linkLabel', true)
1733 - .attr('id', function (d) { return d.id; });
1734 -
1735 - entering.each(function (d) {
1736 - var el = d3.select(this),
1737 - rect,
1738 - text,
1739 - parms = {
1740 - x1: d.ldata.x1,
1741 - y1: d.ldata.y1,
1742 - x2: d.ldata.x2,
1743 - y2: d.ldata.y2
1744 - };
1745 -
1746 - d.el = el;
1747 - rect = el.append('rect');
1748 - text = el.append('text').text(d.label);
1749 - rect.attr(rectAroundText(el));
1750 - text.attr('dy', linkLabelOffset);
1751 -
1752 - el.attr('transform', transformLabel(parms));
1753 - });
1754 -
1755 - // Remove any labels that are no longer required.
1756 - linkLabel.exit().remove();
1757 - }
1758 -
1759 - function rectAroundText(el) {
1760 - var text = el.select('text'),
1761 - box = text.node().getBBox();
1762 -
1763 - // translate the bbox so that it is centered on [x,y]
1764 - box.x = -box.width / 2;
1765 - box.y = -box.height / 2;
1766 -
1767 - // add padding
1768 - box.x -= 1;
1769 - box.width += 2;
1770 - return box;
1771 - }
1772 -
1773 - function transformLabel(p) {
1774 - var dx = p.x2 - p.x1,
1775 - dy = p.y2 - p.y1,
1776 - xMid = dx/2 + p.x1,
1777 - yMid = dy/2 + p.y1;
1778 - return translate(xMid, yMid);
1779 - }
1780 -
1781 - function createDeviceNode(device) {
1782 - // start with the object as is
1783 - var node = device,
1784 - type = device.type,
1785 - svgCls = type ? 'node device ' + type : 'node device';
1786 -
1787 - // Augment as needed...
1788 - node.class = 'device';
1789 - node.svgClass = device.online ? svgCls + ' online' : svgCls;
1790 - positionNode(node);
1791 - return node;
1792 - }
1793 -
1794 - function createHostNode(host) {
1795 - // start with the object as is
1796 - var node = host;
1797 -
1798 - // Augment as needed...
1799 - node.class = 'host';
1800 - if (!node.type) {
1801 - node.type = 'endstation';
1802 - }
1803 - node.svgClass = 'node host ' + node.type;
1804 - positionNode(node);
1805 - return node;
1806 - }
1807 -
1808 - function positionNode(node, forUpdate) {
1809 - var meta = node.metaUi,
1810 - x = meta && meta.x,
1811 - y = meta && meta.y,
1812 - xy;
1813 -
1814 - // If we have [x,y] already, use that...
1815 - if (x && y) {
1816 - node.fixed = true;
1817 - node.px = node.x = x;
1818 - node.py = node.y = y;
1819 - return;
1820 - }
1821 -
1822 - var location = node.location;
1823 - if (location && location.type === 'latlng') {
1824 - var coord = geoMapProj([location.lng, location.lat]);
1825 - node.fixed = true;
1826 - node.px = node.x = coord[0];
1827 - node.py = node.y = coord[1];
1828 - return true;
1829 - }
1830 -
1831 - // if this is a node update (not a node add).. skip randomizer
1832 - if (forUpdate) {
1833 - return;
1834 - }
1835 -
1836 - // Note: Placing incoming unpinned nodes at exactly the same point
1837 - // (center of the view) causes them to explode outwards when
1838 - // the force layout kicks in. So, we spread them out a bit
1839 - // initially, to provide a more serene layout convergence.
1840 - // Additionally, if the node is a host, we place it near
1841 - // the device it is connected to.
1842 -
1843 - function spread(s) {
1844 - return Math.floor((Math.random() * s) - s/2);
1845 - }
1846 -
1847 - function randDim(dim) {
1848 - return dim / 2 + spread(dim * 0.7071);
1849 - }
1850 -
1851 - function rand() {
1852 - return {
1853 - x: randDim(network.view.width()),
1854 - y: randDim(network.view.height())
1855 - };
1856 - }
1857 -
1858 - function near(node) {
1859 - var min = 12,
1860 - dx = spread(12),
1861 - dy = spread(12);
1862 - return {
1863 - x: node.x + min + dx,
1864 - y: node.y + min + dy
1865 - };
1866 - }
1867 -
1868 - function getDevice(cp) {
1869 - var d = network.lookup[cp.device];
1870 - return d || rand();
1871 - }
1872 -
1873 - xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
1874 - $.extend(node, xy);
1875 - }
1876 -
1877 -
1878 - function iconGlyphUrl(d) {
1879 - var which = d.type || 'unknown';
1880 - return '#' + which;
1881 - }
1882 -
1883 - // returns the newly computed bounding box of the rectangle
1884 - function adjustRectToFitText(n) {
1885 - var text = n.select('text'),
1886 - box = text.node().getBBox(),
1887 - lab = config.labels;
1888 -
1889 - text.attr('text-anchor', 'middle')
1890 - .attr('y', '-0.8em')
1891 - .attr('x', lab.imgPad/2);
1892 -
1893 - // translate the bbox so that it is centered on [x,y]
1894 - box.x = -box.width / 2;
1895 - box.y = -box.height / 2;
1896 -
1897 - // add padding
1898 - box.x -= (lab.padLR + lab.imgPad/2);
1899 - box.width += lab.padLR * 2 + lab.imgPad;
1900 - box.y -= lab.padTB;
1901 - box.height += lab.padTB * 2;
1902 -
1903 - return box;
1904 - }
1905 -
1906 - function mkSvgClass(d) {
1907 - return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
1908 - }
1909 -
1910 - function hostLabel(d) {
1911 - var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
1912 - return d.labels[idx];
1913 - }
1914 - function deviceLabel(d) {
1915 - var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
1916 - return d.labels[idx];
1917 - }
1918 - function trimLabel(label) {
1919 - return (label && label.trim()) || '';
1920 - }
1921 -
1922 - function emptyBox() {
1923 - return {
1924 - x: -2,
1925 - y: -2,
1926 - width: 4,
1927 - height: 4
1928 - };
1929 - }
1930 -
1931 - function updateDeviceLabel(d) {
1932 - var label = trimLabel(deviceLabel(d)),
1933 - noLabel = !label,
1934 - node = d.el,
1935 - box,
1936 - dx,
1937 - dy,
1938 - cfg = config.icons.device;
1939 -
1940 - node.select('text')
1941 - .text(label)
1942 - .style('opacity', 0)
1943 - .transition()
1944 - .style('opacity', 1);
1945 -
1946 - if (noLabel) {
1947 - box = emptyBox();
1948 - dx = -cfg.dim/2;
1949 - dy = -cfg.dim/2;
1950 - } else {
1951 - box = adjustRectToFitText(node);
1952 - dx = box.x + cfg.xoff;
1953 - dy = box.y + cfg.yoff;
1954 - }
1955 -
1956 - node.select('rect')
1957 - .transition()
1958 - .attr(box);
1959 -
1960 - node.select('g.deviceIcon')
1961 - .transition()
1962 - .attr('transform', translate(dx, dy));
1963 - }
1964 -
1965 - function updateHostLabel(d) {
1966 - var label = trimLabel(hostLabel(d));
1967 - d.el.select('text').text(label);
1968 - }
1969 -
1970 - function updateHostVisibility() {
1971 - var v = visVal(showHosts);
1972 - nodeG.selectAll('.host').style('visibility', v);
1973 - linkG.selectAll('.hostLink').style('visibility', v);
1974 - }
1975 -
1976 - function findOfflineNodes() {
1977 - var a = [];
1978 - network.nodes.forEach(function (d) {
1979 - if (d.class === 'device' && !d.online) {
1980 - a.push(d);
1981 - }
1982 - });
1983 - return a;
1984 - }
1985 -
1986 - function updateOfflineVisibility(dev) {
1987 - var so = showOffline,
1988 - sh = showHosts,
1989 - vb = 'visibility',
1990 - v, off, al, ah, db, b;
1991 -
1992 - function updAtt(show) {
1993 - al.forEach(function (d) {
1994 - b = show && ((d.type() !== 'hostLink') || sh);
1995 - d.el.style(vb, visVal(b));
1996 - });
1997 - ah.forEach(function (d) {
1998 - b = show && sh;
1999 - d.el.style(vb, visVal(b));
2000 - });
2001 - }
2002 -
2003 - if (dev) {
2004 - // updating a specific device that just toggled off/on-line
2005 - db = dev.online || so;
2006 - dev.el.style(vb, visVal(db));
2007 - al = findAttachedLinks(dev.id);
2008 - ah = findAttachedHosts(dev.id);
2009 - updAtt(db);
2010 - } else {
2011 - // updating all offline devices
2012 - v = visVal(so);
2013 - off = findOfflineNodes();
2014 - off.forEach(function (d) {
2015 - d.el.style(vb, v);
2016 - al = findAttachedLinks(d.id);
2017 - ah = findAttachedHosts(d.id);
2018 - updAtt(so);
2019 - });
2020 - }
2021 - }
2022 -
2023 - function nodeMouseOver(d) {
2024 - if (hovered != d) {
2025 - hovered = d;
2026 - requestTrafficForMode();
2027 - }
2028 - }
2029 -
2030 - function nodeMouseOut(d) {
2031 - if (hovered != null) {
2032 - hovered = null;
2033 - requestTrafficForMode();
2034 - }
2035 - }
2036 -
2037 - function addHostIcon(node, radius, iid) {
2038 - var dim = radius * 1.5,
2039 - xlate = -dim / 2;
2040 -
2041 - node.append('use').attr({
2042 - class: 'glyphIcon hostIcon',
2043 - transform: translate(xlate,xlate),
2044 - 'xlink:href': iid,
2045 - width: dim,
2046 - height: dim
2047 - });
2048 - }
2049 -
2050 - function updateNodes() {
2051 - node = nodeG.selectAll('.node')
2052 - .data(network.nodes, function (d) { return d.id; });
2053 -
2054 - // operate on existing nodes...
2055 - node.filter('.device').each(function (d) {
2056 - var node = d.el;
2057 - node.classed('online', d.online);
2058 - updateDeviceLabel(d);
2059 - positionNode(d, true);
2060 - });
2061 -
2062 - node.filter('.host').each(function (d) {
2063 - updateHostLabel(d);
2064 - positionNode(d, true);
2065 - });
2066 -
2067 - // operate on entering nodes:
2068 - var entering = node.enter()
2069 - .append('g')
2070 - .attr({
2071 - id: function (d) { return safeId(d.id); },
2072 - class: mkSvgClass,
2073 - transform: function (d) { return translate(d.x, d.y); },
2074 - opacity: 0
2075 - })
2076 - .call(network.drag)
2077 - .on('mouseover', nodeMouseOver)
2078 - .on('mouseout', nodeMouseOut)
2079 - .transition()
2080 - .attr('opacity', 1);
2081 -
2082 - // augment device nodes...
2083 - entering.filter('.device').each(function (d) {
2084 - var node = d3.select(this),
2085 - label = trimLabel(deviceLabel(d)),
2086 - noLabel = !label,
2087 - box;
2088 -
2089 - // provide ref to element from backing data....
2090 - d.el = node;
2091 -
2092 - node.append('rect').attr({ rx: 5, ry: 5 });
2093 - node.append('text').text(label).attr('dy', '1.1em');
2094 - box = adjustRectToFitText(node);
2095 - node.select('rect').attr(box);
2096 - addDeviceIcon(node, box, noLabel, iconGlyphUrl(d));
2097 - });
2098 -
2099 - // augment host nodes...
2100 - entering.filter('.host').each(function (d) {
2101 - var node = d3.select(this),
2102 - cfg = config.icons.host,
2103 - r = cfg.radius[d.type] || cfg.defaultRadius,
2104 - textDy = r + 10,
2105 - iid = iconGlyphUrl(d);
2106 -
2107 - // provide ref to element from backing data....
2108 - d.el = node;
2109 - showHostVis(node);
2110 -
2111 - node.append('circle').attr('r', r);
2112 - if (iid) {
2113 - addHostIcon(node, r, iid);
2114 - }
2115 - node.append('text')
2116 - .text(hostLabel)
2117 - .attr('dy', textDy)
2118 - .attr('text-anchor', 'middle');
2119 - });
2120 -
2121 - // operate on both existing and new nodes, if necessary
2122 - updateDeviceColors();
2123 -
2124 - // operate on exiting nodes:
2125 - // Note that the node is removed after 2 seconds.
2126 - // Sub element animations should be shorter than 2 seconds.
2127 - var exiting = node.exit()
2128 - .transition()
2129 - .duration(2000)
2130 - .style('opacity', 0)
2131 - .remove();
2132 -
2133 - // host node exits....
2134 - exiting.filter('.host').each(function (d) {
2135 - var node = d.el;
2136 - node.select('use')
2137 - .style('opacity', 0.5)
2138 - .transition()
2139 - .duration(800)
2140 - .style('opacity', 0);
2141 -
2142 - node.select('text')
2143 - .style('opacity', 0.5)
2144 - .transition()
2145 - .duration(800)
2146 - .style('opacity', 0);
2147 -
2148 - node.select('circle')
2149 - .style('stroke-fill', '#555')
2150 - .style('fill', '#888')
2151 - .style('opacity', 0.5)
2152 - .transition()
2153 - .duration(1500)
2154 - .attr('r', 0);
2155 - });
2156 -
2157 - // device node exits....
2158 - exiting.filter('.device').each(function (d) {
2159 - var node = d.el;
2160 - node.select('use')
2161 - .style('opacity', 0.5)
2162 - .transition()
2163 - .duration(800)
2164 - .style('opacity', 0);
2165 -
2166 - node.selectAll('rect')
2167 - .style('stroke-fill', '#555')
2168 - .style('fill', '#888')
2169 - .style('opacity', 0.5);
2170 - });
2171 - fResume();
2172 - }
2173 -
2174 - var dCol = {
2175 - black: '#000',
2176 - paleblue: '#acf',
2177 - offwhite: '#ddd',
2178 - midgrey: '#888',
2179 - lightgrey: '#bbb',
2180 - orange: '#f90'
2181 - };
2182 -
2183 - // note: these are the device icon colors without affinity
2184 - var dColTheme = {
2185 - light: {
2186 - online: {
2187 - glyph: dCol.black,
2188 - rect: dCol.paleblue
2189 - },
2190 - offline: {
2191 - glyph: dCol.midgrey,
2192 - rect: dCol.lightgrey
2193 - }
2194 - },
2195 - // TODO: theme
2196 - dark: {
2197 - online: {
2198 - glyph: dCol.black,
2199 - rect: dCol.paleblue
2200 - },
2201 - offline: {
2202 - glyph: dCol.midgrey,
2203 - rect: dCol.lightgrey
2204 - }
2205 - }
2206 - };
2207 -
2208 - function devBaseColor(d) {
2209 - var t = network.view.getTheme(),
2210 - o = d.online ? 'online' : 'offline';
2211 - return dColTheme[t][o];
2212 - }
2213 -
2214 - function setDeviceColor(d) {
2215 - var o = d.online,
2216 - s = d.el.classed('selected'),
2217 - c = devBaseColor(d),
2218 - a = instColor(d.master, o),
2219 - g, r,
2220 - icon = d.el.select('g.deviceIcon');
2221 -
2222 - if (s) {
2223 - g = c.glyph;
2224 - r = dColTheme.sel;
2225 - } else if (colorAffinity) {
2226 - g = o ? a : c.glyph;
2227 - r = o ? dCol.offwhite : a;
2228 - } else {
2229 - g = c.glyph;
2230 - r = c.rect;
2231 - }
2232 -
2233 - icon.select('use')
2234 - .style('fill', g);
2235 - icon.select('rect')
2236 - .style('fill', r);
2237 - }
2238 -
2239 - function addDeviceIcon(node, box, noLabel, iid) {
2240 - var cfg = config.icons.device,
2241 - dx,
2242 - dy,
2243 - g;
2244 -
2245 - if (noLabel) {
2246 - dx = -cfg.dim/2;
2247 - dy = -cfg.dim/2;
2248 - } else {
2249 - box = adjustRectToFitText(node);
2250 - dx = box.x + cfg.xoff;
2251 - dy = box.y + cfg.yoff;
2252 - }
2253 -
2254 - g = node.append('g')
2255 - .attr('class', 'glyphIcon deviceIcon')
2256 - .attr('transform', translate(dx, dy));
2257 -
2258 - g.append('rect').attr({
2259 - x: 0,
2260 - y: 0,
2261 - rx: cfg.rx,
2262 - width: cfg.dim,
2263 - height: cfg.dim
2264 - });
2265 -
2266 - g.append('use').attr({
2267 - 'xlink:href': iid,
2268 - width: cfg.dim,
2269 - height: cfg.dim
2270 - });
2271 -
2272 - }
2273 -
2274 - function find(key, array, tag) {
2275 - var _tag = tag || 'id',
2276 - idx, n, d;
2277 - for (idx = 0, n = array.length; idx < n; idx++) {
2278 - d = array[idx];
2279 - if (d[_tag] === key) {
2280 - return idx;
2281 - }
2282 - }
2283 - return -1;
2284 - }
2285 -
2286 - function removeLinkElement(d) {
2287 - var idx = find(d.key, network.links, 'key'),
2288 - removed;
2289 - if (idx >=0) {
2290 - // remove from links array
2291 - removed = network.links.splice(idx, 1);
2292 - // remove from lookup cache
2293 - delete network.lookup[removed[0].key];
2294 - updateLinks();
2295 - fResume();
2296 - }
2297 - }
2298 -
2299 - function removeHostElement(d, upd) {
2300 - var lu = network.lookup;
2301 - // first, remove associated hostLink...
2302 - removeLinkElement(d.linkData);
2303 -
2304 - // remove hostLink bindings
2305 - delete lu[d.ingress];
2306 - delete lu[d.egress];
2307 -
2308 - // remove from lookup cache
2309 - delete lu[d.id];
2310 - // remove from nodes array
2311 - var idx = find(d.id, network.nodes);
2312 - network.nodes.splice(idx, 1);
2313 - // remove from SVG
2314 - // NOTE: upd is false if we were called from removeDeviceElement()
2315 - if (upd) {
2316 - updateNodes();
2317 - fResume();
2318 - }
2319 - }
2320 -
2321 -
2322 - function removeDeviceElement(d) {
2323 - var id = d.id;
2324 - // first, remove associated hosts and links..
2325 - findAttachedHosts(id).forEach(removeHostElement);
2326 - findAttachedLinks(id).forEach(removeLinkElement);
2327 -
2328 - // remove from lookup cache
2329 - delete network.lookup[id];
2330 - // remove from nodes array
2331 - var idx = find(id, network.nodes);
2332 - network.nodes.splice(idx, 1);
2333 -
2334 - if (!network.nodes.length) {
2335 - showNoDevs(true);
2336 - }
2337 -
2338 - // remove from SVG
2339 - updateNodes();
2340 - fResume();
2341 - }
2342 -
2343 - function findAttachedHosts(devId) {
2344 - var hosts = [];
2345 - network.nodes.forEach(function (d) {
2346 - if (d.class === 'host' && d.cp.device === devId) {
2347 - hosts.push(d);
2348 - }
2349 - });
2350 - return hosts;
2351 - }
2352 -
2353 - function findAttachedLinks(devId) {
2354 - var links = [];
2355 - network.links.forEach(function (d) {
2356 - if (d.source.id === devId || d.target.id === devId) {
2357 - links.push(d);
2358 - }
2359 - });
2360 - return links;
2361 - }
2362 -
2363 - function fResume() {
2364 - if (!oblique) {
2365 - network.force.resume();
2366 - }
2367 - }
2368 -
2369 - function fStart() {
2370 - if (!oblique) {
2371 - network.force.start();
2372 - }
2373 - }
2374 -
2375 - var tickStuff = {
2376 - nodeAttr: {
2377 - transform: function (d) { return translate(d.x, d.y); }
2378 - },
2379 - linkAttr: {
2380 - x1: function (d) { return d.source.x; },
2381 - y1: function (d) { return d.source.y; },
2382 - x2: function (d) { return d.target.x; },
2383 - y2: function (d) { return d.target.y; }
2384 - },
2385 - linkLabelAttr: {
2386 - transform: function (d) {
2387 - var lnk = findLinkById(d.key);
2388 -
2389 - if (lnk) {
2390 - var parms = {
2391 - x1: lnk.source.x,
2392 - y1: lnk.source.y,
2393 - x2: lnk.target.x,
2394 - y2: lnk.target.y
2395 - };
2396 - return transformLabel(parms);
2397 - }
2398 - }
2399 - }
2400 - };
2401 -
2402 - function tick() {
2403 - node.attr(tickStuff.nodeAttr);
2404 - link.attr(tickStuff.linkAttr);
2405 - linkLabel.attr(tickStuff.linkLabelAttr);
2406 - }
2407 -
2408 - // ==============================
2409 - // Web-Socket for live data
2410 -
2411 - function findGuiSuccessor() {
2412 - var idx = -1;
2413 - onosOrder.forEach(function (d, i) {
2414 - if (d.uiAttached) {
2415 - idx = i;
2416 - }
2417 - });
2418 -
2419 - for (var i = 0; i < onosOrder.length - 1; i++) {
2420 - var ni = (idx + 1 + i) % onosOrder.length;
2421 - if (onosOrder[ni].online) {
2422 - return onosOrder[ni].ip;
2423 - }
2424 - }
2425 - return null;
2426 - }
2427 -
2428 - function webSockUrl() {
2429 - var url = document.location.toString()
2430 - .replace(/\#.*/, '')
2431 - .replace('http://', 'ws://')
2432 - .replace('https://', 'wss://')
2433 - .replace('index.html', config.webSockUrl);
2434 - if (guiSuccessor) {
2435 - url = url.replace(location.hostname, guiSuccessor);
2436 - }
2437 - return url;
2438 - }
2439 -
2440 - webSock = {
2441 - ws : null,
2442 - retries: 0,
2443 -
2444 - connect : function() {
2445 - webSock.ws = new WebSocket(webSockUrl());
2446 -
2447 - webSock.ws.onopen = function() {
2448 - noWebSock(false);
2449 - requestSummary();
2450 - showInstances();
2451 - webSock.retries = 0;
2452 - };
2453 -
2454 - webSock.ws.onmessage = function(m) {
2455 - if (m.data) {
2456 - wsTraceRx(m.data);
2457 - handleServerEvent(JSON.parse(m.data));
2458 - }
2459 - };
2460 -
2461 - webSock.ws.onclose = function(m) {
2462 - webSock.ws = null;
2463 - guiSuccessor = findGuiSuccessor();
2464 - if (guiSuccessor && webSock.retries < onosOrder.length) {
2465 - webSock.retries++;
2466 - webSock.connect();
2467 - } else {
2468 - noWebSock(true);
2469 - }
2470 - };
2471 - },
2472 -
2473 - send : function(text) {
2474 - if (text != null) {
2475 - webSock._send(text);
2476 - }
2477 - },
2478 -
2479 - _send : function(message) {
2480 - if (webSock.ws) {
2481 - webSock.ws.send(message);
2482 - } else if (config.useLiveData) {
2483 - console.warn('no web socket open', message);
2484 - } else {
2485 - console.log('WS Send: ', message);
2486 - }
2487 - }
2488 -
2489 - };
2490 -
2491 - function noWebSock(b) {
2492 - mask.style('display',b ? 'block' : 'none');
2493 - }
2494 -
2495 - function sendMessage(evType, payload) {
2496 - var p = payload || {},
2497 - toSend = {
2498 - event: evType,
2499 - sid: ++sid,
2500 - payload: p
2501 - },
2502 - asText = JSON.stringify(toSend);
2503 - wsTraceTx(asText);
2504 - webSock.send(asText);
2505 -
2506 - // Temporary measure for debugging UI behavior ...
2507 - if (!config.useLiveData) {
2508 - handleTestSend(toSend);
2509 - }
2510 - }
2511 -
2512 - function wsTraceTx(msg) {
2513 - wsTrace('tx', msg);
2514 - }
2515 - function wsTraceRx(msg) {
2516 - wsTrace('rx', msg);
2517 - }
2518 - function wsTrace(rxtx, msg) {
2519 - // console.log('[' + rxtx + '] ' + msg);
2520 - }
2521 -
2522 - function handleTestSend(msg) { }
2523 -
2524 - // ==============================
2525 - // Selection stuff
2526 -
2527 - function selectObject(obj, el) {
2528 - var n,
2529 - ev = d3.event.sourceEvent;
2530 -
2531 - // if the meta or alt key is pressed, we are panning/zooming, so ignore
2532 - if (ev.metaKey || ev.altKey) {
2533 - return;
2534 - }
2535 -
2536 - if (el) {
2537 - n = d3.select(el);
2538 - } else {
2539 - node.each(function(d) {
2540 - if (d == obj) {
2541 - n = d3.select(el = this);
2542 - }
2543 - });
2544 - }
2545 - if (!n) return;
2546 -
2547 - if (ev.shiftKey && n.classed('selected')) {
2548 - deselectObject(obj.id);
2549 - updateDetailPane();
2550 - return;
2551 - }
2552 -
2553 - if (!ev.shiftKey) {
2554 - deselectAll();
2555 - }
2556 -
2557 - selections[obj.id] = { obj: obj, el: el };
2558 - selectOrder.push(obj.id);
2559 -
2560 - n.classed('selected', true);
2561 - updateDeviceColors(obj);
2562 - updateDetailPane();
2563 - }
2564 -
2565 - function deselectObject(id) {
2566 - var obj = selections[id],
2567 - idx;
2568 - if (obj) {
2569 - d3.select(obj.el).classed('selected', false);
2570 - delete selections[id];
2571 - idx = $.inArray(id, selectOrder);
2572 - if (idx >= 0) {
2573 - selectOrder.splice(idx, 1);
2574 - }
2575 - updateDeviceColors(obj.obj);
2576 - }
2577 - }
2578 -
2579 - function deselectAll() {
2580 - // deselect all nodes in the network...
2581 - node.classed('selected', false);
2582 - selections = {};
2583 - selectOrder = [];
2584 - updateDeviceColors();
2585 - updateDetailPane();
2586 - }
2587 -
2588 - function updateDeviceColors(d) {
2589 - if (d) {
2590 - setDeviceColor(d);
2591 - } else {
2592 - node.filter('.device').each(function (d) {
2593 - setDeviceColor(d);
2594 - });
2595 - }
2596 - }
2597 -
2598 - // update the state of the detail pane, based on current selections
2599 - function updateDetailPane() {
2600 - var nSel = selectOrder.length;
2601 - if (!nSel) {
2602 - emptySelect();
2603 - } else if (nSel === 1) {
2604 - singleSelect();
2605 - } else {
2606 - multiSelect();
2607 - }
2608 - }
2609 -
2610 - function emptySelect() {
2611 - haveDetails = false;
2612 - hideDetailPane();
2613 - cancelTraffic();
2614 - }
2615 -
2616 - function singleSelect() {
2617 - // NOTE: detail is shown from showDetails event callback
2618 - requestDetails();
2619 - cancelTraffic();
2620 - requestTrafficForMode();
2621 - }
2622 -
2623 - function multiSelect() {
2624 - haveDetails = true;
2625 - populateMultiSelect();
2626 - cancelTraffic();
2627 - requestTrafficForMode();
2628 - }
2629 -
2630 - function addSep(tbody) {
2631 - var tr = tbody.append('tr');
2632 - $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
2633 - }
2634 -
2635 - function addProp(tbody, label, value) {
2636 - var tr = tbody.append('tr');
2637 -
2638 - tr.append('td')
2639 - .attr('class', 'label')
2640 - .text(label + ' :');
2641 -
2642 - tr.append('td')
2643 - .attr('class', 'value')
2644 - .text(value);
2645 - }
2646 -
2647 - function populateMultiSelect() {
2648 - detailPane.empty();
2649 -
2650 - var title = detailPane.append('h3'),
2651 - table = detailPane.append('table'),
2652 - tbody = table.append('tbody');
2653 -
2654 - title.text('Selected Nodes');
2655 -
2656 - selectOrder.forEach(function (d, i) {
2657 - addProp(tbody, i+1, d);
2658 - });
2659 -
2660 - addMultiSelectActions();
2661 - }
2662 -
2663 - function populateSummary(data) {
2664 - summaryPane.empty();
2665 -
2666 - var svg = summaryPane.append('svg'),
2667 - iid = iconGlyphUrl(data);
2668 -
2669 - var title = summaryPane.append('h2'),
2670 - table = summaryPane.append('table'),
2671 - tbody = table.append('tbody');
2672 -
2673 - appendGlyph(svg, 0, 0, 40, iid);
2674 -
2675 - svg.append('use')
2676 - .attr({
2677 - class: 'birdBadge',
2678 - transform: translate(8,12),
2679 - 'xlink:href': '#bird',
2680 - width: 24,
2681 - height: 24,
2682 - fill: '#fff'
2683 - });
2684 -
2685 - title.text('ONOS Summary');
2686 -
2687 - data.propOrder.forEach(function(p) {
2688 - if (p === '-') {
2689 - addSep(tbody);
2690 - } else {
2691 - addProp(tbody, p, data.props[p]);
2692 - }
2693 - });
2694 - }
2695 -
2696 - function populateDetails(data) {
2697 - detailPane.empty();
2698 -
2699 - var svg = detailPane.append('svg'),
2700 - iid = iconGlyphUrl(data);
2701 -
2702 - var title = detailPane.append('h2'),
2703 - table = detailPane.append('table'),
2704 - tbody = table.append('tbody');
2705 -
2706 - appendGlyph(svg, 0, 0, 40, iid);
2707 - title.text(data.id);
2708 -
2709 - data.propOrder.forEach(function(p) {
2710 - if (p === '-') {
2711 - addSep(tbody);
2712 - } else {
2713 - addProp(tbody, p, data.props[p]);
2714 - }
2715 - });
2716 -
2717 - addSingleSelectActions(data);
2718 - }
2719 -
2720 - function addSingleSelectActions(data) {
2721 - detailPane.append('hr');
2722 - // always want to allow 'show traffic'
2723 - addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
2724 -
2725 - if (data.type === 'switch') {
2726 - addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
2727 - }
2728 - }
2729 -
2730 - function addMultiSelectActions() {
2731 - detailPane.append('hr');
2732 - // always want to allow 'show traffic'
2733 - addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
2734 - // if exactly two hosts are selected, also want 'add host intent'
2735 - if (nSel() === 2 && allSelectionsClass('host')) {
2736 - addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2737 - } else if (nSel() >= 2 && allSelectionsClass('host')) {
2738 - addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
2739 - }
2740 - }
2741 -
2742 - function addAction(panel, text, cb) {
2743 - panel.append('div')
2744 - .classed('actionBtn', true)
2745 - .text(text)
2746 - .on('click', cb);
2747 - }
2748 -
2749 -
2750 - // === Pan and Zoom behaviors...
2751 -
2752 - function panZoom(translate, scale) {
2753 - panZoomContainer.attr('transform',
2754 - 'translate(' + translate + ')scale(' + scale + ')');
2755 - // keep the map lines constant width while zooming
2756 - bgImg.style('stroke-width', 2.0 / scale + 'px');
2757 - }
2758 -
2759 - function resetPanZoom() {
2760 - panZoom([0,0], 1);
2761 - zoom.translate([0,0]).scale(1);
2762 - }
2763 -
2764 - function setupPanZoom() {
2765 - function zoomed() {
2766 - var ev = d3.event.sourceEvent;
2767 - // pan/zoom active when meta or alt key is pressed...
2768 - if (ev.metaKey || ev.altKey) {
2769 - panZoom(d3.event.translate, d3.event.scale);
2770 - }
2771 - }
2772 -
2773 - zoom = d3.behavior.zoom()
2774 - .translate([0, 0])
2775 - .scale(1)
2776 - .scaleExtent([0.25, 10])
2777 - .on("zoom", zoomed);
2778 -
2779 - svg.call(zoom);
2780 - }
2781 -
2782 -
2783 - function setupNoDevices() {
2784 - var g = noDevices.append('g');
2785 - appendBadge(g, 0, 0, 100, '#bird', 'noDevsBird');
2786 - var text = g.append('text')
2787 - .text('No devices are connected')
2788 - .attr({ x: 120, y: 80});
2789 - }
2790 -
2791 - function repositionNoDevices() {
2792 - var g = noDevices.select('g');
2793 - var box = g.node().getBBox();
2794 - box.x -= box.width/2;
2795 - box.y -= box.height/2;
2796 - g.attr('transform', translate(box.x, box.y));
2797 - }
2798 -
2799 -
2800 - // ==============================
2801 - // Test harness code
2802 -
2803 - function prepareScenario(view, ctx, dbg) {
2804 - var sc = scenario,
2805 - urlSc = sc.evDir + ctx + sc.evScenario;
2806 -
2807 - if (!ctx) {
2808 - view.alert("No scenario specified (null ctx)");
2809 - return;
2810 - }
2811 -
2812 - sc.view = view;
2813 - sc.ctx = ctx;
2814 - sc.debug = dbg;
2815 - sc.evNumber = 0;
2816 -
2817 - d3.json(urlSc, function(err, data) {
2818 - var p = data && data.params || {},
2819 - desc = data && data.description || null,
2820 - intro = data && data.title;
2821 -
2822 - if (err) {
2823 - view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
2824 - } else {
2825 - sc.params = p;
2826 - if (desc) {
2827 - intro += '\n\n ' + desc.join('\n ');
2828 - }
2829 - view.alert(intro);
2830 - }
2831 - });
2832 -
2833 - }
2834 -
2835 - function setupDefs(svg) {
2836 - var defs = svg.append('defs');
2837 - gly.loadDefs(defs);
2838 - d3u.loadGlow(defs);
2839 - }
2840 -
2841 - function sendUpdateMeta(d, store) {
2842 - var metaUi = {},
2843 - ll;
2844 -
2845 - if (store) {
2846 - ll = geoMapProj.invert([d.x, d.y]);
2847 - metaUi = {
2848 - x: d.x,
2849 - y: d.y,
2850 - lng: ll[0],
2851 - lat: ll[1]
2852 - };
2853 - }
2854 - d.metaUi = metaUi;
2855 - sendMessage('updateMeta', {
2856 - id: d.id,
2857 - 'class': d.class,
2858 - memento: metaUi
2859 - });
2860 - }
2861 -
2862 - // ==============================
2863 - // View life-cycle callbacks
2864 -
2865 - function init(view, ctx, flags) {
2866 - var w = view.width(),
2867 - h = view.height(),
2868 - logSize = config.logicalSize,
2869 - fcfg = config.force;
2870 -
2871 - // NOTE: view.$div is a D3 selection of the view's div
2872 - var viewBox = '0 0 ' + logSize + ' ' + logSize;
2873 - svg = view.$div.append('svg').attr('viewBox', viewBox);
2874 - setSize(svg, view);
2875 -
2876 - // load glyphs, filters, and other definitions...
2877 - setupDefs(svg);
2878 -
2879 - panZoomContainer = svg.append('g').attr('id', 'panZoomContainer');
2880 - setupPanZoom();
2881 -
2882 - noDevices = svg.append('g')
2883 - .attr('class', 'noDevsLayer')
2884 - .attr('transform', translate(logSize/2, logSize/2));
2885 - setupNoDevices();
2886 -
2887 - // group for the topology
2888 - topoG = panZoomContainer.append('g')
2889 - .attr('id', 'topo-G');
2890 -
2891 - // subgroups for links, link labels, and nodes
2892 - linkG = topoG.append('g').attr('id', 'links');
2893 - linkLabelG = topoG.append('g').attr('id', 'linkLabels');
2894 - nodeG = topoG.append('g').attr('id', 'nodes');
2895 -
2896 - // selection of links, linkLabels, and nodes
2897 - link = linkG.selectAll('.link');
2898 - linkLabel = linkLabelG.selectAll('.linkLabel');
2899 - node = nodeG.selectAll('.node');
2900 -
2901 - function chrg(d) {
2902 - return fcfg.charge[d.class] || -12000;
2903 - }
2904 - function ldist(d) {
2905 - return fcfg.linkDistance[d.type] || 50;
2906 - }
2907 - function lstrg(d) {
2908 - // 0.0 - 1.0
2909 - return fcfg.linkStrength[d.type] || 1.0;
2910 - }
2911 -
2912 - function selectCb(d, self) {
2913 - selectObject(d, self);
2914 - }
2915 -
2916 - function atDragEnd(d, self) {
2917 - // once we've finished moving, pin the node in position
2918 - d.fixed = true;
2919 - d3.select(self).classed('fixed', true);
2920 - if (config.useLiveData) {
2921 - sendUpdateMeta(d, true);
2922 - } else {
2923 - console.log('Moving node ' + d.id + ' to [' + d.x + ',' + d.y + ']');
2924 - }
2925 - }
2926 -
2927 - // predicate that indicates when dragging is active
2928 - function dragEnabled() {
2929 - var ev = d3.event.sourceEvent;
2930 - // nodeLock means we aren't allowing nodes to be dragged...
2931 - // meta or alt key pressed means we are zooming/panning...
2932 - return !nodeLock && !(ev.metaKey || ev.altKey);
2933 - }
2934 -
2935 - // predicate that indicates when clicking is active
2936 - function clickEnabled() {
2937 - return true;
2938 - }
2939 -
2940 - // set up the force layout
2941 - network.force = d3.layout.force()
2942 - .size([w, h])
2943 - .nodes(network.nodes)
2944 - .links(network.links)
2945 - .gravity(0.4)
2946 - .friction(0.7)
2947 - .charge(chrg)
2948 - .linkDistance(ldist)
2949 - .linkStrength(lstrg)
2950 - .on('tick', tick);
2951 -
2952 - network.drag = d3u.createDragBehavior(network.force,
2953 - selectCb, atDragEnd, dragEnabled, clickEnabled);
2954 -
2955 -
2956 - // create mask layer for when we lose connection to server.
2957 - // TODO: this should be part of the framework
2958 -
2959 - function para(sel, text) {
2960 - sel.append('p').text(text);
2961 - }
2962 -
2963 - mask = view.$div.append('div').attr('id','topo-mask');
2964 - para(mask, 'Oops!');
2965 - para(mask, 'Web-socket connection to server closed...');
2966 - para(mask, 'Try refreshing the page.');
2967 -
2968 - mask.append('svg')
2969 - .attr({
2970 - id: 'mask-bird',
2971 - width: w,
2972 - height: h
2973 - })
2974 - .append('g')
2975 - .attr('transform', birdTranslate(w, h))
2976 - .style('opacity', 0.3)
2977 - .append('use')
2978 - .attr({
2979 - 'xlink:href': '#bird',
2980 - width: config.birdDim,
2981 - height: config.birdDim,
2982 - fill: '#111'
2983 - })
2984 - }
2985 -
2986 -
2987 - function load(view, ctx, flags) {
2988 - // resize, in case the window was resized while we were not loaded
2989 - resize(view, ctx, flags);
2990 -
2991 - // cache the view token, so network topo functions can access it
2992 - network.view = view;
2993 - config.useLiveData = !flags.local;
2994 -
2995 - if (!config.useLiveData) {
2996 - prepareScenario(view, ctx, flags.debug);
2997 - }
2998 -
2999 - // set our radio buttons and key bindings
3000 - layerBtnSet = view.setRadio(layerButtons);
3001 - view.setKeys(keyDispatch);
3002 - view.setGestures(gestures);
3003 -
3004 - // Load map data asynchronously; complete startup after that..
3005 - loadGeoJsonData();
3006 - }
3007 -
3008 - function startAntTimer() {
3009 - // Note: disabled until traffic can be allotted to intents properly
3010 - if (false && !antTimer) {
3011 - var pulses = [5, 3, 1.2, 3],
3012 - pulse = 0;
3013 - antTimer = setInterval(function () {
3014 - pulse = pulse + 1;
3015 - pulse = pulse === pulses.length ? 0 : pulse;
3016 - d3.selectAll('.animated').style('stroke-width', pulses[pulse]);
3017 - }, 200);
3018 - }
3019 - }
3020 -
3021 - function stopAntTimer() {
3022 - if (antTimer) {
3023 - clearInterval(antTimer);
3024 - antTimer = null;
3025 - }
3026 - }
3027 -
3028 - function unload(view, ctx, flags) {
3029 - stopAntTimer();
3030 - }
3031 -
3032 - var geoJsonUrl = 'continental_us.json',
3033 - geoJson;
3034 -
3035 - function loadGeoJsonData() {
3036 - d3.json(geoJsonUrl, function (err, data) {
3037 - if (err) {
3038 - console.error('failed to load Map data', err);
3039 - } else {
3040 - geoJson = data;
3041 - loadGeoMap();
3042 - }
3043 -
3044 - repositionNoDevices();
3045 - showNoDevs(true);
3046 -
3047 - // finally, connect to the server...
3048 - if (config.useLiveData) {
3049 - webSock.connect();
3050 - }
3051 - });
3052 - }
3053 -
3054 - function setProjForView(path, topoData) {
3055 - var dim = config.logicalSize;
3056 -
3057 - // start with unit scale, no translation..
3058 - geoMapProj.scale(1).translate([0, 0]);
3059 -
3060 - // figure out dimensions of map data..
3061 - var b = path.bounds(topoData),
3062 - x1 = b[0][0],
3063 - y1 = b[0][1],
3064 - x2 = b[1][0],
3065 - y2 = b[1][1],
3066 - dx = x2 - x1,
3067 - dy = y2 - y1,
3068 - x = (x1 + x2) / 2,
3069 - y = (y1 + y2) / 2;
3070 -
3071 - // size map to 95% of minimum dimension to fill space..
3072 - var s = .95 / Math.min(dx / dim, dy / dim);
3073 - var t = [dim / 2 - s * x, dim / 2 - s * y];
3074 -
3075 - // set new scale, translation on the projection..
3076 - geoMapProj.scale(s).translate(t);
3077 - }
3078 -
3079 - function loadGeoMap() {
3080 - fnTrace('loadGeoMap', geoJsonUrl);
3081 -
3082 - // extracts the topojson data into geocoordinate-based geometry
3083 - var topoData = topojson.feature(geoJson, geoJson.objects.states);
3084 -
3085 - // see: http://bl.ocks.org/mbostock/4707858
3086 - geoMapProj = d3.geo.mercator();
3087 - var path = d3.geo.path().projection(geoMapProj);
3088 -
3089 - setProjForView(path, topoData);
3090 -
3091 - bgImg = panZoomContainer.insert("g", '#topo-G');
3092 - bgImg.attr('id', 'map').selectAll('path')
3093 - .data(topoData.features)
3094 - .enter()
3095 - .append('path')
3096 - .attr('d', path);
3097 - }
3098 -
3099 - function resize(view, ctx, flags) {
3100 - var w = view.width(),
3101 - h = view.height();
3102 -
3103 - setSize(svg, view);
3104 -
3105 - d3.select('#mask-bird').attr({ width: w, height: h})
3106 - .select('g').attr('transform', birdTranslate(w, h));
3107 - }
3108 -
3109 - function theme(view, ctx, flags) {
3110 - updateInstances();
3111 - updateDeviceColors();
3112 - }
3113 -
3114 - function birdTranslate(w, h) {
3115 - var bdim = config.birdDim;
3116 - return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
3117 - }
3118 -
3119 - function isF(f) { return $.isFunction(f) ? f : null; }
3120 - function noop() {}
3121 -
3122 - function augmentDetailPane() {
3123 - var dp = detailPane;
3124 - dp.ypos = { up: 64, down: 320, current: 320};
3125 -
3126 - dp._move = function (y, cb) {
3127 - var endCb = isF(cb) || noop,
3128 - yp = dp.ypos;
3129 - if (yp.current !== y) {
3130 - yp.current = y;
3131 - dp.el.transition().duration(300)
3132 - .each('end', endCb)
3133 - .style('top', yp.current + 'px');
3134 - } else {
3135 - endCb();
3136 - }
3137 - };
3138 -
3139 - dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
3140 - dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
3141 - }
3142 -
3143 - // ==============================
3144 - // View registration
3145 -
3146 - onos.ui.addView('topo', {
3147 - init: init,
3148 - load: load,
3149 - unload: unload,
3150 - resize: resize,
3151 - theme: theme
3152 - });
3153 -
3154 - summaryPane = onos.ui.addFloatingPanel('topo-summary');
3155 - detailPane = onos.ui.addFloatingPanel('topo-detail');
3156 - augmentDetailPane();
3157 - oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
3158 - oiBox.width(20);
3159 -
3160 -}(ONOS));
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 -package org.onosproject.ui.impl;
18 -
19 -import com.fasterxml.jackson.databind.node.ObjectNode;
20 -import com.google.common.collect.ImmutableSet;
21 -import org.junit.Before;
22 -import org.junit.Test;
23 -import org.onlab.osgi.ServiceDirectory;
24 -import org.onlab.osgi.TestServiceDirectory;
25 -import org.onlab.packet.ChassisId;
26 -import org.onlab.packet.IpAddress;
27 -import org.onlab.packet.MacAddress;
28 -import org.onlab.packet.VlanId;
29 -import org.onosproject.cluster.ClusterService;
30 -import org.onosproject.cluster.ClusterServiceAdapter;
31 -import org.onosproject.core.CoreService;
32 -import org.onosproject.core.CoreServiceAdapter;
33 -import org.onosproject.core.Version;
34 -import org.onosproject.mastership.MastershipService;
35 -import org.onosproject.mastership.MastershipServiceAdapter;
36 -import org.onosproject.net.DefaultDevice;
37 -import org.onosproject.net.DefaultHost;
38 -import org.onosproject.net.Device;
39 -import org.onosproject.net.DeviceId;
40 -import org.onosproject.net.Host;
41 -import org.onosproject.net.HostId;
42 -import org.onosproject.net.HostLocation;
43 -import org.onosproject.net.Port;
44 -import org.onosproject.net.PortNumber;
45 -import org.onosproject.net.device.DeviceService;
46 -import org.onosproject.net.device.DeviceServiceAdapter;
47 -import org.onosproject.net.flow.FlowEntry;
48 -import org.onosproject.net.flow.FlowRuleService;
49 -import org.onosproject.net.flow.FlowRuleServiceAdapter;
50 -import org.onosproject.net.host.HostService;
51 -import org.onosproject.net.host.HostServiceAdapter;
52 -import org.onosproject.net.intent.IntentService;
53 -import org.onosproject.net.intent.IntentServiceAdapter;
54 -import org.onosproject.net.link.LinkService;
55 -import org.onosproject.net.link.LinkServiceAdapter;
56 -import org.onosproject.net.provider.ProviderId;
57 -import org.onosproject.net.statistic.StatisticService;
58 -import org.onosproject.net.statistic.StatisticServiceAdapter;
59 -import org.onosproject.net.topology.TopologyService;
60 -import org.onosproject.net.topology.TopologyServiceAdapter;
61 -
62 -import java.util.ArrayList;
63 -import java.util.List;
64 -import java.util.Set;
65 -
66 -import static org.junit.Assert.assertEquals;
67 -
68 -public class TopologyViewWebSocketTest {
69 -
70 - private static final ProviderId PID = new ProviderId("of", "foo.bar");
71 - private static final ChassisId CHID = new ChassisId(123L);
72 - private static final MacAddress MAC = MacAddress.valueOf("00:00:00:00:00:19");
73 - private static final DeviceId DID = DeviceId.deviceId("of:foo");
74 - private static final Set<IpAddress> IPS = ImmutableSet.of(IpAddress.valueOf("1.2.3.4"));
75 -
76 - private TestWebSocket ws;
77 - private TestServiceDirectory sd;
78 -
79 - @Before
80 - public void setUp() {
81 - sd = new TestServiceDirectory();
82 - sd.add(DeviceService.class, new TestDeviceService());
83 - sd.add(ClusterService.class, new ClusterServiceAdapter());
84 - sd.add(LinkService.class, new LinkServiceAdapter());
85 - sd.add(HostService.class, new TestHostService());
86 - sd.add(MastershipService.class, new MastershipServiceAdapter());
87 - sd.add(IntentService.class, new IntentServiceAdapter());
88 - sd.add(FlowRuleService.class, new TestFlowService());
89 - sd.add(StatisticService.class, new StatisticServiceAdapter());
90 - sd.add(TopologyService.class, new TopologyServiceAdapter());
91 - sd.add(CoreService.class, new TestCoreService());
92 - ws = new TestWebSocket(sd);
93 - }
94 -
95 - @Test
96 - public void requestDetailsDevice() {
97 - // build the request
98 - String request = "{\"event\":\"requestDetails\", \"sid\":0, "
99 - + "\"payload\":{\"id\":\"of:000001\",\"class\":\"device\"}}";
100 - ws.onMessage(request);
101 - // look at the ws reply, and verify that it is correct
102 - assertEquals("incorrect id", "of:000001", ws.reply.path("payload").path("id").asText());
103 - assertEquals("incorrect mfr", "foo", ws.reply.path("payload").path("props").path("Vendor").asText());
104 - }
105 -
106 - @Test
107 - public void requestDetailsHost() {
108 - // build the request
109 - String request = "{\"event\":\"requestDetails\", \"sid\":0, "
110 - + "\"payload\":{\"id\":\"00:00:00:00:00:19/-1\",\"class\":\"host\"}}";
111 - ws.onMessage(request);
112 - // look at the ws reply, and verify that it is correct
113 - assertEquals("incorrect id", "00:00:00:00:00:19/-1", ws.reply.path("payload").path("id").asText());
114 - assertEquals("incorrect ip address", "1.2.3.4", ws.reply.path("payload").path("props").path("IP").asText());
115 - }
116 -
117 - private class TestWebSocket extends TopologyViewWebSocket {
118 -
119 - private ObjectNode reply;
120 -
121 - /**
122 - * Creates a new web-socket for serving data to GUI topology view.
123 - *
124 - * @param directory service directory
125 - */
126 - public TestWebSocket(ServiceDirectory directory) {
127 - super(directory);
128 - }
129 -
130 - @Override
131 - protected synchronized void sendMessage(ObjectNode data) {
132 - reply = data;
133 - }
134 - }
135 -
136 - private class TestCoreService extends CoreServiceAdapter {
137 - @Override
138 - public Version version() {
139 - return Version.version("1.2.3");
140 - }
141 - }
142 -
143 - private class TestDeviceService extends DeviceServiceAdapter {
144 -
145 - @Override
146 - public Device getDevice(DeviceId deviceId) {
147 - return new DefaultDevice(PID, deviceId, Device.Type.SWITCH,
148 - "foo", "hw", "sw", "sn", CHID);
149 - }
150 -
151 - @Override
152 - public List<Port> getPorts(DeviceId deviceId) {
153 - return new ArrayList<>();
154 - }
155 - }
156 -
157 - private class TestFlowService extends FlowRuleServiceAdapter {
158 - @Override
159 - public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
160 - return new ArrayList<>();
161 - }
162 - }
163 -
164 - private class TestHostService extends HostServiceAdapter {
165 - @Override
166 - public Host getHost(HostId hostId) {
167 - return new DefaultHost(PID, hostId, MAC, VlanId.NONE,
168 - new HostLocation(DID, PortNumber.P0, 123L), IPS);
169 - }
170 - }
171 -}
...\ No newline at end of file ...\ No newline at end of file