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
... | @@ -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)); |

5.61 KB
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 |
-
Please register or login to post a comment