Madan Jampani

Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Showing 62 changed files with 980 additions and 214 deletions
...@@ -296,7 +296,7 @@ public class FlowRuleManager ...@@ -296,7 +296,7 @@ public class FlowRuleManager
296 post(event); 296 post(event);
297 } 297 }
298 } else { 298 } else {
299 - log.info("Removing flow rules...."); 299 + log.debug("Removing flow rules....");
300 removeFlowRules(flowEntry); 300 removeFlowRules(flowEntry);
301 } 301 }
302 302
......
...@@ -2,13 +2,16 @@ package org.onlab.onos.store.service.impl; ...@@ -2,13 +2,16 @@ package org.onlab.onos.store.service.impl;
2 2
3 import static com.google.common.base.Preconditions.checkArgument; 3 import static com.google.common.base.Preconditions.checkArgument;
4 import static com.google.common.base.Preconditions.checkState; 4 import static com.google.common.base.Preconditions.checkState;
5 +import static com.google.common.base.Verify.verifyNotNull;
5 import static org.slf4j.LoggerFactory.getLogger; 6 import static org.slf4j.LoggerFactory.getLogger;
6 7
7 import java.io.File; 8 import java.io.File;
8 import java.io.IOException; 9 import java.io.IOException;
9 import java.util.ArrayList; 10 import java.util.ArrayList;
10 import java.util.Arrays; 11 import java.util.Arrays;
12 +import java.util.Iterator;
11 import java.util.List; 13 import java.util.List;
14 +import java.util.Map;
12 import java.util.concurrent.ConcurrentNavigableMap; 15 import java.util.concurrent.ConcurrentNavigableMap;
13 16
14 import net.kuujo.copycat.log.Entry; 17 import net.kuujo.copycat.log.Entry;
...@@ -25,8 +28,6 @@ import org.mapdb.TxMaker; ...@@ -25,8 +28,6 @@ import org.mapdb.TxMaker;
25 import org.onlab.onos.store.serializers.StoreSerializer; 28 import org.onlab.onos.store.serializers.StoreSerializer;
26 import org.slf4j.Logger; 29 import org.slf4j.Logger;
27 30
28 -import com.google.common.collect.Lists;
29 -
30 /** 31 /**
31 * MapDB based log implementation. 32 * MapDB based log implementation.
32 */ 33 */
...@@ -84,7 +85,7 @@ public class MapDBLog implements Log { ...@@ -84,7 +85,7 @@ public class MapDBLog implements Log {
84 public List<Long> appendEntries(List<Entry> entries) { 85 public List<Long> appendEntries(List<Entry> entries) {
85 assertIsOpen(); 86 assertIsOpen();
86 checkArgument(entries != null, "expecting non-null entries"); 87 checkArgument(entries != null, "expecting non-null entries");
87 - final List<Long> indices = Lists.newArrayList(); 88 + final List<Long> indices = new ArrayList<>(entries.size());
88 89
89 txMaker.execute(new TxBlock() { 90 txMaker.execute(new TxBlock() {
90 @Override 91 @Override
...@@ -92,13 +93,15 @@ public class MapDBLog implements Log { ...@@ -92,13 +93,15 @@ public class MapDBLog implements Log {
92 BTreeMap<Long, byte[]> log = getLogMap(db); 93 BTreeMap<Long, byte[]> log = getLogMap(db);
93 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); 94 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
94 long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1; 95 long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1;
96 + long addedBytes = 0;
95 for (Entry entry : entries) { 97 for (Entry entry : entries) {
96 byte[] entryBytes = serializer.encode(entry); 98 byte[] entryBytes = serializer.encode(entry);
97 log.put(nextIndex, entryBytes); 99 log.put(nextIndex, entryBytes);
98 - size.addAndGet(entryBytes.length); 100 + addedBytes += entryBytes.length;
99 indices.add(nextIndex); 101 indices.add(nextIndex);
100 nextIndex++; 102 nextIndex++;
101 } 103 }
104 + size.addAndGet(addedBytes);
102 } 105 }
103 }); 106 });
104 107
...@@ -236,12 +239,15 @@ public class MapDBLog implements Log { ...@@ -236,12 +239,15 @@ public class MapDBLog implements Log {
236 public void tx(DB db) { 239 public void tx(DB db) {
237 BTreeMap<Long, byte[]> log = getLogMap(db); 240 BTreeMap<Long, byte[]> log = getLogMap(db);
238 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); 241 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
239 - long startIndex = index + 1; 242 + long removedBytes = 0;
240 - long endIndex = log.lastKey(); 243 + ConcurrentNavigableMap<Long, byte[]> tailMap = log.tailMap(index, false);
241 - for (long i = startIndex; i <= endIndex; ++i) { 244 + Iterator<Map.Entry<Long, byte[]>> it = tailMap.entrySet().iterator();
242 - byte[] entryBytes = log.remove(i); 245 + while (it.hasNext()) {
243 - size.addAndGet(-1L * entryBytes.length); 246 + Map.Entry<Long, byte[]> entry = it.next();
247 + removedBytes += entry.getValue().length;
248 + it.remove();
244 } 249 }
250 + size.addAndGet(-removedBytes);
245 } 251 }
246 }); 252 });
247 } 253 }
...@@ -273,9 +279,16 @@ public class MapDBLog implements Log { ...@@ -273,9 +279,16 @@ public class MapDBLog implements Log {
273 BTreeMap<Long, byte[]> log = getLogMap(db); 279 BTreeMap<Long, byte[]> log = getLogMap(db);
274 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); 280 Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
275 ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index); 281 ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index);
276 - long deletedBytes = headMap.keySet().stream().mapToLong(i -> log.remove(i).length).sum(); 282 + Iterator<Map.Entry<Long, byte[]>> it = headMap.entrySet().iterator();
277 - size.addAndGet(-1 * deletedBytes); 283 +
278 - byte[] entryBytes = serializer.encode(entry); 284 + long deletedBytes = 0;
285 + while (it.hasNext()) {
286 + Map.Entry<Long, byte[]> e = it.next();
287 + deletedBytes += e.getValue().length;
288 + it.remove();
289 + }
290 + size.addAndGet(-deletedBytes);
291 + byte[] entryBytes = verifyNotNull(serializer.encode(entry));
279 byte[] existingEntry = log.put(index, entryBytes); 292 byte[] existingEntry = log.put(index, entryBytes);
280 if (existingEntry != null) { 293 if (existingEntry != null) {
281 size.addAndGet(entryBytes.length - existingEntry.length); 294 size.addAndGet(entryBytes.length - existingEntry.length);
......
...@@ -139,7 +139,7 @@ public class FlowModBuilderVer13 extends FlowModBuilder { ...@@ -139,7 +139,7 @@ public class FlowModBuilderVer13 extends FlowModBuilder {
139 .setXid(cookie) 139 .setXid(cookie)
140 .setCookie(U64.of(cookie)) 140 .setCookie(U64.of(cookie))
141 .setBufferId(OFBufferId.NO_BUFFER) 141 .setBufferId(OFBufferId.NO_BUFFER)
142 - .setActions(actions) 142 + //.setActions(actions) //FIXME do we want to send actions in flowdel?
143 //.setInstructions(Collections.singletonList(writeActions)) 143 //.setInstructions(Collections.singletonList(writeActions))
144 .setMatch(match) 144 .setMatch(match)
145 .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) 145 .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
......
...@@ -19,13 +19,13 @@ fi ...@@ -19,13 +19,13 @@ fi
19 export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2} 19 export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2}
20 20
21 export KARAF_VERSION=${KARAF_VERSION:-3.0.2} 21 export KARAF_VERSION=${KARAF_VERSION:-3.0.2}
22 -export KARAF=${KARAF:-~/Applications/apache-karaf-$KARAF_VERSION} 22 +export KARAF_HOME=${KARAF:-~/Applications/apache-karaf-$KARAF_VERSION}
23 -export KARAF_LOG=$KARAF/data/log/karaf.log 23 +export KARAF_LOG=$KARAF_HOME/data/log/karaf.log
24 24
25 # Setup a path 25 # Setup a path
26 export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin" 26 export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin"
27 export PATH="$PATH:$ONOS_ROOT/tools/build" 27 export PATH="$PATH:$ONOS_ROOT/tools/build"
28 -export PATH="$PATH:$MAVEN/bin:$KARAF/bin" 28 +export PATH="$PATH:$MAVEN/bin:$KARAF_HOME/bin"
29 29
30 # Convenience utility to warp to various ONOS source projects 30 # Convenience utility to warp to various ONOS source projects
31 # e.g. 'o api', 'o dev', 'o' 31 # e.g. 'o api', 'o dev', 'o'
......
...@@ -29,8 +29,8 @@ endif ...@@ -29,8 +29,8 @@ endif
29 if ( ! $?KARAF_VERSION ) then 29 if ( ! $?KARAF_VERSION ) then
30 setenv KARAF_VERSION 3.0.2 30 setenv KARAF_VERSION 3.0.2
31 endif 31 endif
32 -if ( ! $?KARAF ) then 32 +if ( ! $?KARAF_HOME ) then
33 - setenv KARAF $HOME/Applications/apache-karaf-$KARAF_VERSION 33 + setenv KARAF_HOME $HOME/Applications/apache-karaf-$KARAF_VERSION
34 endif 34 endif
35 setenv KARAF_LOG $KARAF/data/log/karaf.log 35 setenv KARAF_LOG $KARAF/data/log/karaf.log
36 36
......
...@@ -20,50 +20,95 @@ import com.fasterxml.jackson.databind.ObjectMapper; ...@@ -20,50 +20,95 @@ import com.fasterxml.jackson.databind.ObjectMapper;
20 import com.fasterxml.jackson.databind.node.ArrayNode; 20 import com.fasterxml.jackson.databind.node.ArrayNode;
21 import com.fasterxml.jackson.databind.node.ObjectNode; 21 import com.fasterxml.jackson.databind.node.ObjectNode;
22 import org.eclipse.jetty.websocket.WebSocket; 22 import org.eclipse.jetty.websocket.WebSocket;
23 -import org.onlab.onos.event.Event; 23 +import org.onlab.onos.core.ApplicationId;
24 +import org.onlab.onos.core.CoreService;
25 +import org.onlab.onos.mastership.MastershipEvent;
26 +import org.onlab.onos.mastership.MastershipListener;
27 +import org.onlab.onos.mastership.MastershipService;
24 import org.onlab.onos.net.Annotations; 28 import org.onlab.onos.net.Annotations;
29 +import org.onlab.onos.net.ConnectPoint;
30 +import org.onlab.onos.net.DefaultEdgeLink;
25 import org.onlab.onos.net.Device; 31 import org.onlab.onos.net.Device;
26 import org.onlab.onos.net.DeviceId; 32 import org.onlab.onos.net.DeviceId;
33 +import org.onlab.onos.net.ElementId;
34 +import org.onlab.onos.net.Host;
35 +import org.onlab.onos.net.HostId;
36 +import org.onlab.onos.net.HostLocation;
27 import org.onlab.onos.net.Link; 37 import org.onlab.onos.net.Link;
28 import org.onlab.onos.net.Path; 38 import org.onlab.onos.net.Path;
29 import org.onlab.onos.net.device.DeviceEvent; 39 import org.onlab.onos.net.device.DeviceEvent;
40 +import org.onlab.onos.net.device.DeviceListener;
30 import org.onlab.onos.net.device.DeviceService; 41 import org.onlab.onos.net.device.DeviceService;
42 +import org.onlab.onos.net.flow.DefaultTrafficSelector;
43 +import org.onlab.onos.net.flow.DefaultTrafficTreatment;
44 +import org.onlab.onos.net.host.HostEvent;
45 +import org.onlab.onos.net.host.HostListener;
46 +import org.onlab.onos.net.host.HostService;
47 +import org.onlab.onos.net.intent.HostToHostIntent;
48 +import org.onlab.onos.net.intent.Intent;
49 +import org.onlab.onos.net.intent.IntentEvent;
50 +import org.onlab.onos.net.intent.IntentId;
51 +import org.onlab.onos.net.intent.IntentListener;
52 +import org.onlab.onos.net.intent.IntentService;
53 +import org.onlab.onos.net.intent.PathIntent;
31 import org.onlab.onos.net.link.LinkEvent; 54 import org.onlab.onos.net.link.LinkEvent;
32 -import org.onlab.onos.net.topology.Topology; 55 +import org.onlab.onos.net.link.LinkListener;
33 -import org.onlab.onos.net.topology.TopologyEdge; 56 +import org.onlab.onos.net.link.LinkService;
34 -import org.onlab.onos.net.topology.TopologyEvent; 57 +import org.onlab.onos.net.provider.ProviderId;
35 -import org.onlab.onos.net.topology.TopologyGraph; 58 +import org.onlab.onos.net.topology.PathService;
36 -import org.onlab.onos.net.topology.TopologyListener;
37 -import org.onlab.onos.net.topology.TopologyService;
38 -import org.onlab.onos.net.topology.TopologyVertex;
39 import org.onlab.osgi.ServiceDirectory; 59 import org.onlab.osgi.ServiceDirectory;
60 +import org.onlab.packet.IpAddress;
40 61
41 import java.io.IOException; 62 import java.io.IOException;
42 -import java.util.HashMap; 63 +import java.util.Iterator;
64 +import java.util.List;
43 import java.util.Map; 65 import java.util.Map;
44 import java.util.Set; 66 import java.util.Set;
67 +import java.util.concurrent.ConcurrentHashMap;
45 68
69 +import static com.google.common.base.Preconditions.checkNotNull;
46 import static org.onlab.onos.net.DeviceId.deviceId; 70 import static org.onlab.onos.net.DeviceId.deviceId;
71 +import static org.onlab.onos.net.HostId.hostId;
72 +import static org.onlab.onos.net.PortNumber.portNumber;
47 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED; 73 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
48 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED; 74 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
75 +import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
76 +import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
49 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED; 77 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
50 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED; 78 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
51 79
52 /** 80 /**
53 * Web socket capable of interacting with the GUI topology view. 81 * Web socket capable of interacting with the GUI topology view.
54 */ 82 */
55 -public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListener { 83 +public class TopologyWebSocket implements WebSocket.OnTextMessage {
56 84
85 + private static final String APP_ID = "org.onlab.onos.gui";
86 + private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
87 +
88 + private final ApplicationId appId;
57 private final ServiceDirectory directory; 89 private final ServiceDirectory directory;
58 - private final TopologyService topologyService;
59 - private final DeviceService deviceService;
60 90
61 private final ObjectMapper mapper = new ObjectMapper(); 91 private final ObjectMapper mapper = new ObjectMapper();
62 92
63 private Connection connection; 93 private Connection connection;
64 94
95 + private final DeviceService deviceService;
96 + private final LinkService linkService;
97 + private final HostService hostService;
98 + private final MastershipService mastershipService;
99 + private final IntentService intentService;
100 +
101 + private final DeviceListener deviceListener = new InternalDeviceListener();
102 + private final LinkListener linkListener = new InternalLinkListener();
103 + private final HostListener hostListener = new InternalHostListener();
104 + private final MastershipListener mastershipListener = new InternalMastershipListener();
105 + private final IntentListener intentListener = new InternalIntentListener();
106 +
65 // TODO: extract into an external & durable state; good enough for now and demo 107 // TODO: extract into an external & durable state; good enough for now and demo
66 - private static Map<String, ObjectNode> metaUi = new HashMap<>(); 108 + private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
109 +
110 + // Intents that are being monitored for the GUI
111 + private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
67 112
68 private static final String COMPACT = "%s/%s-%s/%s"; 113 private static final String COMPACT = "%s/%s-%s/%s";
69 114
...@@ -74,81 +119,231 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe ...@@ -74,81 +119,231 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
74 * @param directory service directory 119 * @param directory service directory
75 */ 120 */
76 public TopologyWebSocket(ServiceDirectory directory) { 121 public TopologyWebSocket(ServiceDirectory directory) {
77 - this.directory = directory; 122 + this.directory = checkNotNull(directory, "Directory cannot be null");
78 - topologyService = directory.get(TopologyService.class);
79 deviceService = directory.get(DeviceService.class); 123 deviceService = directory.get(DeviceService.class);
124 + linkService = directory.get(LinkService.class);
125 + hostService = directory.get(HostService.class);
126 + mastershipService = directory.get(MastershipService.class);
127 + intentService = directory.get(IntentService.class);
128 +
129 + appId = directory.get(CoreService.class).registerApplication(APP_ID);
80 } 130 }
81 131
82 @Override 132 @Override
83 public void onOpen(Connection connection) { 133 public void onOpen(Connection connection) {
84 this.connection = connection; 134 this.connection = connection;
135 + deviceService.addListener(deviceListener);
136 + linkService.addListener(linkListener);
137 + hostService.addListener(hostListener);
138 + mastershipService.addListener(mastershipListener);
139 + intentService.addListener(intentListener);
140 +
141 + sendAllDevices();
142 + sendAllLinks();
143 + sendAllHosts();
144 + }
85 145
86 - // Register for topology events... 146 + private void sendAllHosts() {
87 - if (topologyService != null && deviceService != null) { 147 + for (Host host : hostService.getHosts()) {
88 - topologyService.addListener(this); 148 + sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
89 - 149 + }
90 - Topology topology = topologyService.currentTopology(); 150 + }
91 - TopologyGraph graph = topologyService.getGraph(topology);
92 - for (TopologyVertex vertex : graph.getVertexes()) {
93 - sendMessage(message(new DeviceEvent(DEVICE_ADDED,
94 - deviceService.getDevice(vertex.deviceId()))));
95 - }
96 151
97 - for (TopologyEdge edge : graph.getEdges()) { 152 + private void sendAllDevices() {
98 - sendMessage(message(new LinkEvent(LINK_ADDED, edge.link()))); 153 + for (Device device : deviceService.getDevices()) {
99 - } 154 + sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
155 + }
156 + }
100 157
101 - } else { 158 + private void sendAllLinks() {
102 - sendMessage(message("error", "No topology service!!!")); 159 + for (Link link : linkService.getLinks()) {
160 + sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
103 } 161 }
104 } 162 }
105 163
106 @Override 164 @Override
107 public void onClose(int closeCode, String message) { 165 public void onClose(int closeCode, String message) {
108 - TopologyService topologyService = directory.get(TopologyService.class); 166 + deviceService.removeListener(deviceListener);
109 - if (topologyService != null) { 167 + linkService.removeListener(linkListener);
110 - topologyService.removeListener(this); 168 + hostService.removeListener(hostListener);
111 - } 169 + mastershipService.removeListener(mastershipListener);
112 } 170 }
113 171
114 @Override 172 @Override
115 public void onMessage(String data) { 173 public void onMessage(String data) {
116 try { 174 try {
117 ObjectNode event = (ObjectNode) mapper.reader().readTree(data); 175 ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
118 - String type = event.path("event").asText("unknown"); 176 + String type = string(event, "event", "unknown");
119 - ObjectNode payload = (ObjectNode) event.path("payload"); 177 + if (type.equals("showDetails")) {
120 - 178 + showDetails(event);
121 - switch (type) { 179 + } else if (type.equals("updateMeta")) {
122 - case "updateMeta": 180 + updateMetaInformation(event);
123 - metaUi.put(payload.path("id").asText(), payload); 181 + } else if (type.equals("requestPath")) {
124 - break; 182 + createHostIntent(event);
125 - case "requestPath": 183 + } else if (type.equals("requestTraffic")) {
126 - findPath(deviceId(payload.path("one").asText()), 184 + sendTraffic(event);
127 - deviceId(payload.path("two").asText())); 185 + } else if (type.equals("cancelTraffic")) {
128 - default: 186 + cancelTraffic(event);
129 - break;
130 } 187 }
188 + } catch (Exception e) {
189 + System.out.println("WTF?! " + data);
190 + e.printStackTrace();
191 + }
192 + }
193 +
194 + // Sends the specified data to the client.
195 + private void sendMessage(ObjectNode data) {
196 + try {
197 + connection.sendMessage(data.toString());
131 } catch (IOException e) { 198 } catch (IOException e) {
132 - System.out.println("Received: " + data); 199 + e.printStackTrace();
133 } 200 }
134 } 201 }
135 202
136 - private void findPath(DeviceId one, DeviceId two) { 203 + // Retrieves the payload from the specified event.
137 - Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(), 204 + private ObjectNode payload(ObjectNode event) {
138 - one, two); 205 + return (ObjectNode) event.path("payload");
139 - if (!paths.isEmpty()) { 206 + }
140 - ObjectNode payload = mapper.createObjectNode();
141 - ArrayNode links = mapper.createArrayNode();
142 207
143 - Path path = paths.iterator().next(); 208 + // Returns the specified node property as a number
144 - for (Link link : path.links()) { 209 + private long number(ObjectNode node, String name) {
145 - links.add(compactLinkString(link)); 210 + return node.path(name).asLong();
146 - } 211 + }
212 +
213 + // Returns the specified node property as a string.
214 + private String string(ObjectNode node, String name) {
215 + return node.path(name).asText();
216 + }
217 +
218 + // Returns the specified node property as a string.
219 + private String string(ObjectNode node, String name, String defaultValue) {
220 + return node.path(name).asText(defaultValue);
221 + }
222 +
223 + // Returns the specified set of IP addresses as a string.
224 + private String ip(Set<IpAddress> ipAddresses) {
225 + Iterator<IpAddress> it = ipAddresses.iterator();
226 + return it.hasNext() ? it.next().toString() : "unknown";
227 + }
228 +
229 + // Encodes the specified host location into a JSON object.
230 + private ObjectNode location(ObjectMapper mapper, HostLocation location) {
231 + return mapper.createObjectNode()
232 + .put("device", location.deviceId().toString())
233 + .put("port", location.port().toLong());
234 + }
235 +
236 + // Encodes the specified list of labels a JSON array.
237 + private ArrayNode labels(ObjectMapper mapper, String... labels) {
238 + ArrayNode json = mapper.createArrayNode();
239 + for (String label : labels) {
240 + json.add(label);
241 + }
242 + return json;
243 + }
244 +
245 + // Produces JSON structure from annotations.
246 + private JsonNode props(Annotations annotations) {
247 + ObjectNode props = mapper.createObjectNode();
248 + for (String key : annotations.keys()) {
249 + props.put(key, annotations.value(key));
250 + }
251 + return props;
252 + }
253 +
254 + // Produces a log message event bound to the client.
255 + private ObjectNode message(String severity, long id, String message) {
256 + return envelope("message", id,
257 + mapper.createObjectNode()
258 + .put("severity", severity)
259 + .put("message", message));
260 + }
261 +
262 + // Puts the payload into an envelope and returns it.
263 + private ObjectNode envelope(String type, long sid, ObjectNode payload) {
264 + ObjectNode event = mapper.createObjectNode();
265 + event.put("event", type);
266 + if (sid > 0) {
267 + event.put("sid", sid);
268 + }
269 + event.set("payload", payload);
270 + return event;
271 + }
272 +
273 + // Sends back device or host details.
274 + private void showDetails(ObjectNode event) {
275 + ObjectNode payload = payload(event);
276 + String type = string(payload, "type", "unknown");
277 + if (type.equals("device")) {
278 + sendMessage(deviceDetails(deviceId(string(payload, "id")),
279 + number(event, "sid")));
280 + } else if (type.equals("host")) {
281 + sendMessage(hostDetails(hostId(string(payload, "id")),
282 + number(event, "sid")));
283 + }
284 + }
285 +
286 + // Updates device/host meta information.
287 + private void updateMetaInformation(ObjectNode event) {
288 + ObjectNode payload = payload(event);
289 + metaUi.put(string(payload, "id"), payload);
290 + }
291 +
292 + // Creates host-to-host intent.
293 + private void createHostIntent(ObjectNode event) {
294 + ObjectNode payload = payload(event);
295 + long id = number(event, "sid");
296 + // TODO: add protection against device ids and non-existent hosts.
297 + HostId one = hostId(string(payload, "one"));
298 + HostId two = hostId(string(payload, "two"));
299 +
300 + HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
301 + DefaultTrafficSelector.builder().build(),
302 + DefaultTrafficTreatment.builder().build());
303 + intentsToMonitor.put(hostIntent.id(), number(event, "sid"));
304 + intentService.submit(hostIntent);
305 + }
306 +
307 + // Sends traffic message.
308 + private void sendTraffic(ObjectNode event) {
309 + ObjectNode payload = payload(event);
310 + long id = number(event, "sid");
311 + IntentId intentId = IntentId.valueOf(payload.path("intentId").asLong());
147 312
148 - payload.set("links", links); 313 + if (payload != null) {
149 - sendMessage(envelope("showPath", payload)); 314 + payload.put("traffic", true);
315 + sendMessage(envelope("showPath", id, payload));
316 + } else {
317 + sendMessage(message("warn", id, "No path found"));
150 } 318 }
151 - // TODO: when no path, send a message to the client 319 + }
320 +
321 + // Cancels sending traffic messages.
322 + private void cancelTraffic(ObjectNode event) {
323 + // TODO: implement this
324 + }
325 +
326 + // Finds the path between the specified devices.
327 + private ObjectNode findPath(DeviceId one, DeviceId two) {
328 + PathService pathService = directory.get(PathService.class);
329 + Set<Path> paths = pathService.getPaths(one, two);
330 + if (paths.isEmpty()) {
331 + return null;
332 + } else {
333 + return pathMessage(paths.iterator().next());
334 + }
335 + }
336 +
337 + // Produces a path message to the client.
338 + private ObjectNode pathMessage(Path path) {
339 + ObjectNode payload = mapper.createObjectNode();
340 + ArrayNode links = mapper.createArrayNode();
341 + for (Link link : path.links()) {
342 + links.add(compactLinkString(link));
343 + }
344 +
345 + payload.set("links", links);
346 + return payload;
152 } 347 }
153 348
154 /** 349 /**
...@@ -158,21 +353,13 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe ...@@ -158,21 +353,13 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
158 * @return formatted link string 353 * @return formatted link string
159 */ 354 */
160 public static String compactLinkString(Link link) { 355 public static String compactLinkString(Link link) {
161 - return String.format(COMPACT, link.src().deviceId(), link.src().port(), 356 + return String.format(COMPACT, link.src().elementId(), link.src().port(),
162 - link.dst().deviceId(), link.dst().port()); 357 + link.dst().elementId(), link.dst().port());
163 } 358 }
164 359
165 360
166 - private void sendMessage(String data) {
167 - try {
168 - connection.sendMessage(data);
169 - } catch (IOException e) {
170 - e.printStackTrace();
171 - }
172 - }
173 -
174 // Produces a link event message to the client. 361 // Produces a link event message to the client.
175 - private String message(DeviceEvent event) { 362 + private ObjectNode deviceMessage(DeviceEvent event) {
176 Device device = event.subject(); 363 Device device = event.subject();
177 ObjectNode payload = mapper.createObjectNode() 364 ObjectNode payload = mapper.createObjectNode()
178 .put("id", device.id().toString()) 365 .put("id", device.id().toString())
...@@ -183,27 +370,24 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe ...@@ -183,27 +370,24 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
183 ArrayNode labels = mapper.createArrayNode(); 370 ArrayNode labels = mapper.createArrayNode();
184 labels.add(device.id().toString()); 371 labels.add(device.id().toString());
185 labels.add(device.chassisId().toString()); 372 labels.add(device.chassisId().toString());
186 - labels.add(" "); // compact no-label view 373 + labels.add(""); // compact no-label view
187 labels.add(device.annotations().value("name")); 374 labels.add(device.annotations().value("name"));
188 375
189 // Add labels, props and stuff the payload into envelope. 376 // Add labels, props and stuff the payload into envelope.
190 payload.set("labels", labels); 377 payload.set("labels", labels);
191 payload.set("props", props(device.annotations())); 378 payload.set("props", props(device.annotations()));
192 - 379 + addMetaUi(device.id(), payload);
193 - ObjectNode meta = metaUi.get(device.id().toString());
194 - if (meta != null) {
195 - payload.set("metaUi", meta);
196 - }
197 380
198 String type = (event.type() == DEVICE_ADDED) ? "addDevice" : 381 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
199 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice"); 382 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
200 - return envelope(type, payload); 383 + return envelope(type, 0, payload);
201 } 384 }
202 385
203 // Produces a link event message to the client. 386 // Produces a link event message to the client.
204 - private String message(LinkEvent event) { 387 + private ObjectNode linkMessage(LinkEvent event) {
205 Link link = event.subject(); 388 Link link = event.subject();
206 ObjectNode payload = mapper.createObjectNode() 389 ObjectNode payload = mapper.createObjectNode()
390 + .put("id", compactLinkString(link))
207 .put("type", link.type().toString().toLowerCase()) 391 .put("type", link.type().toString().toLowerCase())
208 .put("linkWidth", 2) 392 .put("linkWidth", 2)
209 .put("src", link.src().deviceId().toString()) 393 .put("src", link.src().deviceId().toString())
...@@ -211,42 +395,147 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe ...@@ -211,42 +395,147 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
211 .put("dst", link.dst().deviceId().toString()) 395 .put("dst", link.dst().deviceId().toString())
212 .put("dstPort", link.dst().port().toString()); 396 .put("dstPort", link.dst().port().toString());
213 String type = (event.type() == LINK_ADDED) ? "addLink" : 397 String type = (event.type() == LINK_ADDED) ? "addLink" :
214 - ((event.type() == LINK_REMOVED) ? "removeLink" : "removeLink"); 398 + ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
215 - return envelope(type, payload); 399 + return envelope(type, 0, payload);
216 } 400 }
217 401
218 - // Produces JSON structure from annotations. 402 + // Produces a host event message to the client.
219 - private JsonNode props(Annotations annotations) { 403 + private ObjectNode hostMessage(HostEvent event) {
220 - ObjectNode props = mapper.createObjectNode(); 404 + Host host = event.subject();
221 - for (String key : annotations.keys()) { 405 + ObjectNode payload = mapper.createObjectNode()
222 - props.put(key, annotations.value(key)); 406 + .put("id", host.id().toString())
407 + .put("ingress", compactLinkString(edgeLink(host, true)))
408 + .put("egress", compactLinkString(edgeLink(host, false)));
409 + payload.set("cp", location(mapper, host.location()));
410 + payload.set("labels", labels(mapper, ip(host.ipAddresses()),
411 + host.mac().toString()));
412 + payload.set("props", props(host.annotations()));
413 + addMetaUi(host.id(), payload);
414 +
415 + String type = (event.type() == HOST_ADDED) ? "addHost" :
416 + ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
417 + return envelope(type, 0, payload);
418 + }
419 +
420 + private DefaultEdgeLink edgeLink(Host host, boolean ingress) {
421 + return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
422 + host.location(), ingress);
423 + }
424 +
425 + private void addMetaUi(ElementId id, ObjectNode payload) {
426 + ObjectNode meta = metaUi.get(id.toString());
427 + if (meta != null) {
428 + payload.set("metaUi", meta);
223 } 429 }
224 - return props;
225 } 430 }
226 431
227 - // Produces a log message event bound to the client. 432 +
228 - private String message(String severity, String message) { 433 + // Returns device details response.
229 - return envelope("message", 434 + private ObjectNode deviceDetails(DeviceId deviceId, long sid) {
230 - mapper.createObjectNode() 435 + Device device = deviceService.getDevice(deviceId);
231 - .put("severity", severity) 436 + Annotations annot = device.annotations();
232 - .put("message", message)); 437 + int portCount = deviceService.getPorts(deviceId).size();
438 + return envelope("showDetails", sid,
439 + json(deviceId.toString(),
440 + device.type().toString().toLowerCase(),
441 + new Prop("Name", annot.value("name")),
442 + new Prop("Vendor", device.manufacturer()),
443 + new Prop("H/W Version", device.hwVersion()),
444 + new Prop("S/W Version", device.swVersion()),
445 + new Prop("Serial Number", device.serialNumber()),
446 + new Separator(),
447 + new Prop("Latitude", annot.value("latitude")),
448 + new Prop("Longitude", annot.value("longitude")),
449 + new Prop("Ports", Integer.toString(portCount))));
233 } 450 }
234 451
235 - // Puts the payload into an envelope and returns it. 452 + // Returns host details response.
236 - private String envelope(String type, ObjectNode payload) { 453 + private ObjectNode hostDetails(HostId hostId, long sid) {
237 - ObjectNode event = mapper.createObjectNode(); 454 + Host host = hostService.getHost(hostId);
238 - event.put("event", type); 455 + Annotations annot = host.annotations();
239 - event.set("payload", payload); 456 + return envelope("showDetails", sid,
240 - return event.toString(); 457 + json(hostId.toString(), "host",
458 + new Prop("MAC", host.mac().toString()),
459 + new Prop("IP", host.ipAddresses().toString()),
460 + new Separator(),
461 + new Prop("Latitude", annot.value("latitude")),
462 + new Prop("Longitude", annot.value("longitude"))));
241 } 463 }
242 464
243 - @Override 465 + // Produces JSON property details.
244 - public void event(TopologyEvent event) { 466 + private ObjectNode json(String id, String type, Prop... props) {
245 - for (Event reason : event.reasons()) { 467 + ObjectMapper mapper = new ObjectMapper();
246 - if (reason instanceof DeviceEvent) { 468 + ObjectNode result = mapper.createObjectNode()
247 - sendMessage(message((DeviceEvent) reason)); 469 + .put("id", id).put("type", type);
248 - } else if (reason instanceof LinkEvent) { 470 + ObjectNode pnode = mapper.createObjectNode();
249 - sendMessage(message((LinkEvent) reason)); 471 + ArrayNode porder = mapper.createArrayNode();
472 + for (Prop p : props) {
473 + porder.add(p.key);
474 + pnode.put(p.key, p.value);
475 + }
476 + result.set("propOrder", porder);
477 + result.set("props", pnode);
478 + return result;
479 + }
480 +
481 + // Auxiliary key/value carrier.
482 + private class Prop {
483 + private final String key;
484 + private final String value;
485 +
486 + protected Prop(String key, String value) {
487 + this.key = key;
488 + this.value = value;
489 + }
490 + }
491 +
492 + private class Separator extends Prop {
493 + protected Separator() {
494 + super("-", "");
495 + }
496 + }
497 +
498 + private class InternalDeviceListener implements DeviceListener {
499 + @Override
500 + public void event(DeviceEvent event) {
501 + sendMessage(deviceMessage(event));
502 + }
503 + }
504 +
505 + private class InternalLinkListener implements LinkListener {
506 + @Override
507 + public void event(LinkEvent event) {
508 + sendMessage(linkMessage(event));
509 + }
510 + }
511 +
512 + private class InternalHostListener implements HostListener {
513 + @Override
514 + public void event(HostEvent event) {
515 + sendMessage(hostMessage(event));
516 + }
517 + }
518 +
519 + private class InternalMastershipListener implements MastershipListener {
520 + @Override
521 + public void event(MastershipEvent event) {
522 +
523 + }
524 + }
525 +
526 + private class InternalIntentListener implements IntentListener {
527 + @Override
528 + public void event(IntentEvent event) {
529 + Intent intent = event.subject();
530 + Long sid = intentsToMonitor.get(intent.id());
531 + if (sid != null) {
532 + List<Intent> installable = intentService.getInstallableIntents(intent.id());
533 + if (installable != null && !installable.isEmpty()) {
534 + PathIntent pathIntent = (PathIntent) installable.iterator().next();
535 + Path path = pathIntent.path();
536 + ObjectNode payload = pathMessage(path).put("intentId", intent.id().toString());
537 + sendMessage(envelope("showPath", sid, payload));
538 + }
250 } 539 }
251 } 540 }
252 } 541 }
......
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
90 <script src="sampleAlt2.js"></script> 90 <script src="sampleAlt2.js"></script>
91 <script src="sampleRadio.js"></script> 91 <script src="sampleRadio.js"></script>
92 <script src="sampleKeys.js"></script> 92 <script src="sampleKeys.js"></script>
93 + <script src="sampleHash.js"></script>
93 94
94 <!-- Contributed (application) views injected here --> 95 <!-- Contributed (application) views injected here -->
95 <!-- TODO: replace with template marker and inject refs server-side --> 96 <!-- TODO: replace with template marker and inject refs server-side -->
......
1 +{
2 + "comments": [
3 + "This scenario steps through adding a host intent."
4 + ],
5 + "title": "Host Intent Scenario",
6 + "params": {
7 + "lastAuto": 0
8 + }
9 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "of:0000ffffffff0008",
5 + "type": "switch",
6 + "online": false,
7 + "labels": [
8 + "0000ffffffff0008",
9 + "FF:FF:FF:FF:00:08",
10 + "sw-8"
11 + ],
12 + "metaUi": {
13 + "x": 400,
14 + "y": 280
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "of:0000ffffffff0003",
5 + "type": "switch",
6 + "online": false,
7 + "labels": [
8 + "0000ffffffff0003",
9 + "FF:FF:FF:FF:00:03",
10 + "sw-3"
11 + ],
12 + "metaUi": {
13 + "x": 800,
14 + "y": 280
15 + }
16 + }
17 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "src": "of:0000ffffffff0003",
5 + "srcPort": "21",
6 + "dst": "of:0000ffffffff0008",
7 + "dstPort": "20",
8 + "type": "infra",
9 + "linkWidth": 2,
10 + "props" : {
11 + "BW": "70 G"
12 + }
13 + }
14 +}
1 +{
2 + "event": "addHost",
3 + "payload": {
4 + "id": "00:00:00:00:00:03/-1",
5 + "cp": {
6 + "device": "of:0000ffffffff0003",
7 + "port": 1
8 + },
9 + "labels": [
10 + "10.0.0.3",
11 + "00:00:00:00:00:03"
12 + ],
13 + "metaUi": {
14 + }
15 + }
16 +}
1 +{
2 + "event": "addHost",
3 + "payload": {
4 + "id": "00:00:00:00:00:08/-1",
5 + "cp": {
6 + "device": "of:0000ffffffff0008",
7 + "port": 1
8 + },
9 + "labels": [
10 + "10.0.0.8",
11 + "00:00:00:00:00:08"
12 + ],
13 + "metaUi": {
14 + }
15 + }
16 +}
1 +{
2 + "comments": [
3 + "Add two devices and one link (auto), and two hosts."
4 + ],
5 + "title": "Simple Startup Scenario",
6 + "params": {
7 + "lastAuto": 0
8 + }
9 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "comments": [
3 + "This scenario steps through adding devices and links.",
4 + "(Typical 'start-ip' of the view.)"
5 + ],
6 + "title": "Startup Scenario",
7 + "params": {
8 + "lastAuto": 32
9 + }
10 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
52 current = { 52 current = {
53 view: null, 53 view: null,
54 ctx: '', 54 ctx: '',
55 + flags: {},
55 theme: settings.theme 56 theme: settings.theme
56 }, 57 },
57 built = false, 58 built = false,
...@@ -110,6 +111,7 @@ ...@@ -110,6 +111,7 @@
110 function doError(msg) { 111 function doError(msg) {
111 errorCount++; 112 errorCount++;
112 console.error(msg); 113 console.error(msg);
114 + doAlert(msg);
113 } 115 }
114 116
115 function trace(msg) { 117 function trace(msg) {
...@@ -140,7 +142,7 @@ ...@@ -140,7 +142,7 @@
140 142
141 t = parseHash(hash); 143 t = parseHash(hash);
142 if (!t || !t.vid) { 144 if (!t || !t.vid) {
143 - doError('Unable to parse target hash: ' + hash); 145 + doError('Unable to parse target hash: "' + hash + '"');
144 } 146 }
145 147
146 view = views[t.vid]; 148 view = views[t.vid];
...@@ -160,33 +162,72 @@ ...@@ -160,33 +162,72 @@
160 162
161 function parseHash(s) { 163 function parseHash(s) {
162 // extract navigation coordinates from the supplied string 164 // extract navigation coordinates from the supplied string
163 - // "vid,ctx" --> { vid:vid, ctx:ctx } 165 + // "vid,ctx?flag1,flag2" --> { vid:vid, ctx:ctx, flags:{...} }
164 traceFn('parseHash', s); 166 traceFn('parseHash', s);
165 167
166 - var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s); 168 + // look for use of flags, first
169 + var vidctx,
170 + vid,
171 + ctx,
172 + flags,
173 + flagMap,
174 + m;
175 +
176 + // RE that includes flags ('?flag1,flag2')
177 + m = /^[#]{0,1}(.+)\?(.+)$/.exec(s);
167 if (m) { 178 if (m) {
168 - return { vid: m[1], ctx: m[2] }; 179 + vidctx = m[1];
180 + flags = m[2];
181 + flagMap = {};
182 + } else {
183 + // no flags
184 + m = /^[#]{0,1}((.+)(,.+)*)$/.exec(s);
185 + if (m) {
186 + vidctx = m[1];
187 + } else {
188 + // bad hash
189 + return null;
190 + }
191 + }
192 +
193 + vidctx = vidctx.split(',');
194 + vid = vidctx[0];
195 + ctx = vidctx[1];
196 + if (flags) {
197 + flags.split(',').forEach(function (f) {
198 + flagMap[f.trim()] = true;
199 + });
169 } 200 }
170 201
171 - m = /^[#]{0,1}(\S+)$/.exec(s); 202 + return {
172 - return m ? { vid: m[1] } : null; 203 + vid: vid.trim(),
204 + ctx: ctx ? ctx.trim() : '',
205 + flags: flagMap
206 + };
207 +
173 } 208 }
174 209
175 - function makeHash(t, ctx) { 210 + function makeHash(t, ctx, flags) {
176 traceFn('makeHash'); 211 traceFn('makeHash');
177 - // make a hash string from the given navigation coordinates. 212 + // make a hash string from the given navigation coordinates,
213 + // and optional flags map.
178 // if t is not an object, then it is a vid 214 // if t is not an object, then it is a vid
179 var h = t, 215 var h = t,
180 - c = ctx || ''; 216 + c = ctx || '',
217 + f = $.isPlainObject(flags) ? flags : null;
181 218
182 if ($.isPlainObject(t)) { 219 if ($.isPlainObject(t)) {
183 h = t.vid; 220 h = t.vid;
184 c = t.ctx || ''; 221 c = t.ctx || '';
222 + f = t.flags || null;
185 } 223 }
186 224
187 if (c) { 225 if (c) {
188 h += ',' + c; 226 h += ',' + c;
189 } 227 }
228 + if (f) {
229 + h += '?' + d3.map(f).keys().join(',');
230 + }
190 trace('hash = "' + h + '"'); 231 trace('hash = "' + h + '"');
191 return h; 232 return h;
192 } 233 }
...@@ -244,6 +285,9 @@ ...@@ -244,6 +285,9 @@
244 // set the specified view as current, while invoking the 285 // set the specified view as current, while invoking the
245 // appropriate life-cycle callbacks 286 // appropriate life-cycle callbacks
246 287
288 + // first, we'll start by closing the alerts pane, if open
289 + closeAlerts();
290 +
247 // if there is a current view, and it is not the same as 291 // if there is a current view, and it is not the same as
248 // the incoming view, then unload it... 292 // the incoming view, then unload it...
249 if (current.view && (current.view.vid !== view.vid)) { 293 if (current.view && (current.view.vid !== view.vid)) {
...@@ -258,10 +302,11 @@ ...@@ -258,10 +302,11 @@
258 // cache new view and context 302 // cache new view and context
259 current.view = view; 303 current.view = view;
260 current.ctx = t.ctx || ''; 304 current.ctx = t.ctx || '';
305 + current.flags = t.flags || {};
261 306
262 // preload is called only once, after the view is in the DOM 307 // preload is called only once, after the view is in the DOM
263 if (!view.preloaded) { 308 if (!view.preloaded) {
264 - view.preload(current.ctx); 309 + view.preload(current.ctx, current.flags);
265 view.preloaded = true; 310 view.preloaded = true;
266 } 311 }
267 312
...@@ -269,7 +314,7 @@ ...@@ -269,7 +314,7 @@
269 view.reset(); 314 view.reset();
270 315
271 // load the view 316 // load the view
272 - view.load(current.ctx); 317 + view.load(current.ctx, current.flags);
273 } 318 }
274 319
275 // generate 'unique' id by prefixing view id 320 // generate 'unique' id by prefixing view id
...@@ -454,7 +499,7 @@ ...@@ -454,7 +499,7 @@
454 d3.selectAll('.onosView').call(setViewDimensions); 499 d3.selectAll('.onosView').call(setViewDimensions);
455 // allow current view to react to resize event... 500 // allow current view to react to resize event...
456 if (current.view) { 501 if (current.view) {
457 - current.view.resize(current.ctx); 502 + current.view.resize(current.ctx, current.flags);
458 } 503 }
459 } 504 }
460 505
...@@ -521,13 +566,13 @@ ...@@ -521,13 +566,13 @@
521 } 566 }
522 }, 567 },
523 568
524 - preload: function (ctx) { 569 + preload: function (ctx, flags) {
525 var c = ctx || '', 570 var c = ctx || '',
526 fn = isF(this.cb.preload); 571 fn = isF(this.cb.preload);
527 traceFn('View.preload', this.vid + ', ' + c); 572 traceFn('View.preload', this.vid + ', ' + c);
528 if (fn) { 573 if (fn) {
529 trace('PRELOAD cb for ' + this.vid); 574 trace('PRELOAD cb for ' + this.vid);
530 - fn(this.token(), c); 575 + fn(this.token(), c, flags);
531 } 576 }
532 }, 577 },
533 578
...@@ -544,15 +589,14 @@ ...@@ -544,15 +589,14 @@
544 } 589 }
545 }, 590 },
546 591
547 - load: function (ctx) { 592 + load: function (ctx, flags) {
548 var c = ctx || '', 593 var c = ctx || '',
549 fn = isF(this.cb.load); 594 fn = isF(this.cb.load);
550 traceFn('View.load', this.vid + ', ' + c); 595 traceFn('View.load', this.vid + ', ' + c);
551 this.$div.classed('currentView', true); 596 this.$div.classed('currentView', true);
552 - // TODO: add radio button set, if needed
553 if (fn) { 597 if (fn) {
554 trace('LOAD cb for ' + this.vid); 598 trace('LOAD cb for ' + this.vid);
555 - fn(this.token(), c); 599 + fn(this.token(), c, flags);
556 } 600 }
557 }, 601 },
558 602
...@@ -560,14 +604,13 @@ ...@@ -560,14 +604,13 @@
560 var fn = isF(this.cb.unload); 604 var fn = isF(this.cb.unload);
561 traceFn('View.unload', this.vid); 605 traceFn('View.unload', this.vid);
562 this.$div.classed('currentView', false); 606 this.$div.classed('currentView', false);
563 - // TODO: remove radio button set, if needed
564 if (fn) { 607 if (fn) {
565 trace('UNLOAD cb for ' + this.vid); 608 trace('UNLOAD cb for ' + this.vid);
566 fn(this.token()); 609 fn(this.token());
567 } 610 }
568 }, 611 },
569 612
570 - resize: function (ctx) { 613 + resize: function (ctx, flags) {
571 var c = ctx || '', 614 var c = ctx || '',
572 fn = isF(this.cb.resize), 615 fn = isF(this.cb.resize),
573 w = this.width(), 616 w = this.width(),
...@@ -576,7 +619,7 @@ ...@@ -576,7 +619,7 @@
576 ' [' + w + 'x' + h + ']'); 619 ' [' + w + 'x' + h + ']');
577 if (fn) { 620 if (fn) {
578 trace('RESIZE cb for ' + this.vid); 621 trace('RESIZE cb for ' + this.vid);
579 - fn(this.token(), c); 622 + fn(this.token(), c, flags);
580 } 623 }
581 }, 624 },
582 625
......
...@@ -21,12 +21,17 @@ ...@@ -21,12 +21,17 @@
21 */ 21 */
22 22
23 (function () { 23 (function () {
24 +
25 + // NOTE: DON'T Want to do this.. we want to be able to
26 + // use the parameter section, for example:
27 + // #viewId,context?flag1,flag2,flag3
28 +
24 // Check if the URL in the address bar contains a parameter section 29 // Check if the URL in the address bar contains a parameter section
25 // (delineated by '?'). If this is the case, rewrite using '#' instead. 30 // (delineated by '?'). If this is the case, rewrite using '#' instead.
26 31
27 - var m = /([^?]*)\?(.*)/.exec(window.location.href); 32 + //var m = /([^?]*)\?(.*)/.exec(window.location.href);
28 - if (m) { 33 + //if (m) {
29 - window.location.href = m[1] + '#' + m[2]; 34 + // window.location.href = m[1] + '#' + m[2];
30 - } 35 + //}
31 36
32 }()); 37 }());
......
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 + @author Simon Hunt
21 + */
22 +
23 +(function (onos) {
24 + 'use strict';
25 +
26 + var intro = "Try using the following hashes in the address bar:",
27 + hashPrefix = '#sampleHash',
28 + suffixes = [
29 + '',
30 + ',one',
31 + ',two',
32 + ',context,ignored',
33 + ',context,ignored?a,b,c',
34 + ',two?foo',
35 + ',three?foo,bar'
36 + ],
37 + $d;
38 +
39 + function note(txt) {
40 + $d.append('p')
41 + .text(txt)
42 + .style({
43 + 'font-size': '10pt',
44 + color: 'darkorange',
45 + padding: '0 20px',
46 + margin: 0
47 + });
48 + }
49 +
50 + function para(txt, color) {
51 + var c = color || 'black';
52 + $d.append('p')
53 + .text(txt)
54 + .style({
55 + padding: '2px 8px',
56 + color: c
57 + });
58 + }
59 +
60 + function load(view, ctx, flags) {
61 + var c = ctx || '(undefined)',
62 + f = flags ? d3.map(flags).keys() : [];
63 +
64 + $d = view.$div;
65 +
66 + para(intro);
67 +
68 + suffixes.forEach(function (s) {
69 + note(hashPrefix + s);
70 + });
71 +
72 + para('View ID: ' + view.vid, 'blue');
73 + para('Context: ' + c, 'blue');
74 + para('Flags: { ' + f.join(', ') + ' }', 'magenta');
75 + }
76 +
77 + // == register the view here, with links to lifecycle callbacks
78 +
79 + onos.ui.addView('sampleHash', {
80 + reset: true, // empty the div on reset
81 + load: load
82 + });
83 +
84 +}(ONOS));
...@@ -20,55 +20,64 @@ ...@@ -20,55 +20,64 @@
20 @author Simon Hunt 20 @author Simon Hunt
21 */ 21 */
22 22
23 -svg #topo-bg { 23 +#topo svg #topo-bg {
24 opacity: 0.5; 24 opacity: 0.5;
25 } 25 }
26 26
27 /* NODES */ 27 /* NODES */
28 28
29 -svg .node.device { 29 +#topo svg .node.device {
30 stroke: none; 30 stroke: none;
31 stroke-width: 1.5px; 31 stroke-width: 1.5px;
32 cursor: pointer; 32 cursor: pointer;
33 } 33 }
34 34
35 -svg .node.device rect { 35 +#topo svg .node.device rect {
36 stroke-width: 1.5px; 36 stroke-width: 1.5px;
37 } 37 }
38 38
39 -svg .node.device.fixed rect { 39 +#topo svg .node.device.fixed rect {
40 stroke-width: 1.5; 40 stroke-width: 1.5;
41 stroke: #ccc; 41 stroke: #ccc;
42 } 42 }
43 43
44 -svg .node.device.switch { 44 +#topo svg .node.device.switch {
45 fill: #17f; 45 fill: #17f;
46 } 46 }
47 47
48 -svg .node.device.roadm { 48 +#topo svg .node.device.roadm {
49 fill: #03c; 49 fill: #03c;
50 } 50 }
51 51
52 -svg .node text { 52 +#topo svg .node.host {
53 + fill: #846;
54 +}
55 +
56 +#topo svg .node text {
53 stroke: none; 57 stroke: none;
54 fill: white; 58 fill: white;
55 font: 10pt sans-serif; 59 font: 10pt sans-serif;
56 pointer-events: none; 60 pointer-events: none;
57 } 61 }
58 62
59 -svg .node.selected rect, 63 +#topo svg .node.selected rect,
60 -svg .node.selected circle { 64 +#topo svg .node.selected circle {
61 filter: url(#blue-glow); 65 filter: url(#blue-glow);
62 } 66 }
63 67
64 /* LINKS */ 68 /* LINKS */
65 69
66 -svg .link { 70 +#topo svg .link {
67 opacity: .7; 71 opacity: .7;
68 } 72 }
69 73
74 +#topo svg .link.showPath {
75 + stroke: #f00;
76 + stroke-width: 4px;
77 +}
78 +
70 /* for debugging */ 79 /* for debugging */
71 -svg .node circle.debug { 80 +#topo svg .node circle.debug {
72 fill: white; 81 fill: white;
73 stroke: red; 82 stroke: red;
74 } 83 }
......
...@@ -132,8 +132,21 @@ ...@@ -132,8 +132,21 @@
132 links: [], 132 links: [],
133 lookup: {} 133 lookup: {}
134 }, 134 },
135 + scenario = {
136 + evDir: 'json/ev/',
137 + evScenario: '/scenario.json',
138 + evPrefix: '/ev_',
139 + evOnos: '_onos.json',
140 + evUi: '_ui.json',
141 + ctx: null,
142 + params: {},
143 + evNumber: 0,
144 + view: null,
145 + debug: false
146 + },
135 webSock, 147 webSock,
136 - labelIdx = 0, 148 + deviceLabelIndex = 0,
149 + hostLabelIndex = 0,
137 150
138 selectOrder = [], 151 selectOrder = [],
139 selections = {}, 152 selections = {},
...@@ -155,10 +168,6 @@ ...@@ -155,10 +168,6 @@
155 // ============================== 168 // ==============================
156 // For Debugging / Development 169 // For Debugging / Development
157 170
158 - var eventPrefix = 'json/eventTest_',
159 - eventNumber = 0,
160 - alertNumber = 0;
161 -
162 function note(label, msg) { 171 function note(label, msg) {
163 console.log('NOTE: ' + label + ': ' + msg); 172 console.log('NOTE: ' + label + ': ' + msg);
164 } 173 }
...@@ -175,32 +184,71 @@ ...@@ -175,32 +184,71 @@
175 view.alert('test'); 184 view.alert('test');
176 } 185 }
177 186
178 - function injectTestEvent(view) { 187 + function abortIfLive() {
179 if (config.useLiveData) { 188 if (config.useLiveData) {
180 - view.alert("Sorry, currently using live data.."); 189 + scenario.view.alert("Sorry, currently using live data..");
181 - return; 190 + return true;
182 } 191 }
192 + return false;
193 + }
183 194
184 - eventNumber++; 195 + function testDebug(msg) {
185 - var eventUrl = eventPrefix + eventNumber + '.json'; 196 + if (scenario.debug) {
197 + scenario.view.alert(msg);
198 + }
199 + }
186 200
187 - d3.json(eventUrl, function(err, data) { 201 + function injectTestEvent(view) {
202 + if (abortIfLive()) { return; }
203 + var sc = scenario,
204 + evn = ++sc.evNumber,
205 + pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
206 + onosUrl = pfx + sc.evOnos,
207 + uiUrl = pfx + sc.evUi;
208 +
209 + tryOnosEvent(onosUrl, uiUrl);
210 + }
211 +
212 + // TODO: tryOnosEvent/tryUiEvent folded into recursive function.
213 + function tryOnosEvent(onosUrl, uiUrl) {
214 + var v = scenario.view;
215 + d3.json(onosUrl, function(err, data) {
188 if (err) { 216 if (err) {
189 - view.dataLoadError(err, eventUrl); 217 + if (err.status === 404) {
218 + tryUiEvent(uiUrl);
219 + } else {
220 + v.alert('non-404 error:\n\n' + onosUrl + '\n\n' + err);
221 + }
190 } else { 222 } else {
223 + testDebug('loaded: ' + onosUrl);
191 handleServerEvent(data); 224 handleServerEvent(data);
192 } 225 }
193 }); 226 });
194 } 227 }
195 228
229 + function tryUiEvent(uiUrl) {
230 + var v = scenario.view;
231 + d3.json(uiUrl, function(err, data) {
232 + if (err) {
233 + v.alert('Error:\n\n' + uiUrl + '\n\n' +
234 + err.status + ': ' + err.statusText);
235 + } else {
236 + testDebug('loaded: ' + uiUrl);
237 + handleUiEvent(data);
238 + }
239 + });
240 + }
241 +
242 + function handleUiEvent(data) {
243 + testDebug('handleUiEvent(): ' + data.event);
244 + // TODO:
245 + }
246 +
196 function injectStartupEvents(view) { 247 function injectStartupEvents(view) {
197 - if (config.useLiveData) { 248 + var last = scenario.params.lastAuto || 0;
198 - view.alert("Sorry, currently using live data.."); 249 + if (abortIfLive()) { return; }
199 - return;
200 - }
201 250
202 - var lastStartupEvent = 32; 251 + while (scenario.evNumber < last) {
203 - while (eventNumber < lastStartupEvent) {
204 injectTestEvent(view); 252 injectTestEvent(view);
205 } 253 }
206 } 254 }
...@@ -211,14 +259,16 @@ ...@@ -211,14 +259,16 @@
211 } 259 }
212 260
213 function cycleLabels() { 261 function cycleLabels() {
214 - labelIdx = (labelIdx === network.deviceLabelCount - 1) ? 0 : labelIdx + 1; 262 + deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1) ? 0 : deviceLabelIndex + 1;
215 263
216 function niceLabel(label) { 264 function niceLabel(label) {
217 return (label && label.trim()) ? label : '.'; 265 return (label && label.trim()) ? label : '.';
218 } 266 }
219 267
220 network.nodes.forEach(function (d) { 268 network.nodes.forEach(function (d) {
221 - var idx = (labelIdx < d.labels.length) ? labelIdx : 0, 269 + if (d.class !== 'device') { return; }
270 +
271 + var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0,
222 node = d3.select('#' + safeId(d.id)), 272 node = d3.select('#' + safeId(d.id)),
223 box; 273 box;
224 274
...@@ -303,9 +353,14 @@ ...@@ -303,9 +353,14 @@
303 353
304 var eventDispatch = { 354 var eventDispatch = {
305 addDevice: addDevice, 355 addDevice: addDevice,
306 - updateDevice: updateDevice, 356 + updateDevice: stillToImplement,
307 - removeDevice: removeDevice, 357 + removeDevice: stillToImplement,
308 addLink: addLink, 358 addLink: addLink,
359 + updateLink: stillToImplement,
360 + removeLink: stillToImplement,
361 + addHost: addHost,
362 + updateHost: stillToImplement,
363 + removeHost: stillToImplement,
309 showPath: showPath 364 showPath: showPath
310 }; 365 };
311 366
...@@ -320,18 +375,6 @@ ...@@ -320,18 +375,6 @@
320 network.force.start(); 375 network.force.start();
321 } 376 }
322 377
323 - function updateDevice(data) {
324 - var device = data.payload;
325 - note('updateDevice', device.id);
326 -
327 - }
328 -
329 - function removeDevice(data) {
330 - var device = data.payload;
331 - note('removeDevice', device.id);
332 -
333 - }
334 -
335 function addLink(data) { 378 function addLink(data) {
336 var link = data.payload, 379 var link = data.payload,
337 lnk = createLink(link); 380 lnk = createLink(link);
...@@ -340,16 +383,57 @@ ...@@ -340,16 +383,57 @@
340 note('addLink', lnk.id); 383 note('addLink', lnk.id);
341 384
342 network.links.push(lnk); 385 network.links.push(lnk);
386 + network.lookup[lnk.id] = lnk;
343 updateLinks(); 387 updateLinks();
344 network.force.start(); 388 network.force.start();
345 } 389 }
346 } 390 }
347 391
392 + function addHost(data) {
393 + var host = data.payload,
394 + node = createHostNode(host),
395 + lnk;
396 +
397 + note('addHost', node.id);
398 + network.nodes.push(node);
399 + network.lookup[host.id] = node;
400 + updateNodes();
401 +
402 + lnk = createHostLink(host);
403 + if (lnk) {
404 + network.links.push(lnk);
405 + network.lookup[host.ingress] = lnk;
406 + network.lookup[host.egress] = lnk;
407 + updateLinks();
408 + }
409 + network.force.start();
410 + }
411 +
348 function showPath(data) { 412 function showPath(data) {
349 - network.view.alert(data.event + "\n" + data.payload.links.length); 413 + var links = data.payload.links,
414 + s = [ data.event + "\n" + links.length ];
415 + links.forEach(function (d, i) {
416 + s.push(d);
417 + });
418 + network.view.alert(s.join('\n'));
419 +
420 + links.forEach(function (d, i) {
421 + var link = network.lookup[d];
422 + if (link) {
423 + d3.select('#' + link.svgId).classed('showPath', true);
424 + }
425 + });
426 +
427 + // TODO: add selection-highlite lines to links
350 } 428 }
351 429
352 - // .... 430 + // ...............................
431 +
432 + function stillToImplement(data) {
433 + var p = data.payload;
434 + note(data.event, p.id);
435 + //network.view.alert('Not yet implemented: "' + data.event + '"');
436 + }
353 437
354 function unknownEvent(data) { 438 function unknownEvent(data) {
355 network.view.alert('Unknown event type: "' + data.event + '"'); 439 network.view.alert('Unknown event type: "' + data.event + '"');
...@@ -367,10 +451,42 @@ ...@@ -367,10 +451,42 @@
367 return 'translate(' + x + ',' + y + ')'; 451 return 'translate(' + x + ',' + y + ')';
368 } 452 }
369 453
454 + function createHostLink(host) {
455 + var src = host.id,
456 + dst = host.cp.device,
457 + id = host.id,
458 + srcNode = network.lookup[src],
459 + dstNode = network.lookup[dst],
460 + lnk;
461 +
462 + if (!dstNode) {
463 + // TODO: send warning message back to server on websocket
464 + network.view.alert('switch not on map for link\n\n' +
465 + 'src = ' + src + '\ndst = ' + dst);
466 + return null;
467 + }
468 +
469 + lnk = {
470 + svgId: safeId(src) + '-' + safeId(dst),
471 + id: id,
472 + source: srcNode,
473 + target: dstNode,
474 + class: 'link',
475 + svgClass: 'link hostLink',
476 + x1: srcNode.x,
477 + y1: srcNode.y,
478 + x2: dstNode.x,
479 + y2: dstNode.y,
480 + width: 1
481 + };
482 + return lnk;
483 + }
484 +
370 function createLink(link) { 485 function createLink(link) {
371 var type = link.type, 486 var type = link.type,
372 src = link.src, 487 src = link.src,
373 dst = link.dst, 488 dst = link.dst,
489 + id = link.id,
374 w = link.linkWidth, 490 w = link.linkWidth,
375 srcNode = network.lookup[src], 491 srcNode = network.lookup[src],
376 dstNode = network.lookup[dst], 492 dstNode = network.lookup[dst],
...@@ -384,7 +500,8 @@ ...@@ -384,7 +500,8 @@
384 } 500 }
385 501
386 lnk = { 502 lnk = {
387 - id: safeId(src) + '~' + safeId(dst), 503 + svgId: safeId(src) + '-' + safeId(dst),
504 + id: id,
388 source: srcNode, 505 source: srcNode,
389 target: dstNode, 506 target: dstNode,
390 class: 'link', 507 class: 'link',
...@@ -415,7 +532,7 @@ ...@@ -415,7 +532,7 @@
415 var entering = link.enter() 532 var entering = link.enter()
416 .append('line') 533 .append('line')
417 .attr({ 534 .attr({
418 - id: function (d) { return d.id; }, 535 + id: function (d) { return d.svgId; },
419 class: function (d) { return d.svgClass; }, 536 class: function (d) { return d.svgClass; },
420 x1: function (d) { return d.x1; }, 537 x1: function (d) { return d.x1; },
421 y1: function (d) { return d.y1; }, 538 y1: function (d) { return d.y1; },
...@@ -433,6 +550,19 @@ ...@@ -433,6 +550,19 @@
433 // augment links 550 // augment links
434 // TODO: add src/dst port labels etc. 551 // TODO: add src/dst port labels etc.
435 552
553 +
554 + // operate on both existing and new links, if necessary
555 + //link .foo() .bar() ...
556 +
557 + // operate on exiting links:
558 + // TODO: figure out how to remove the node 'g' AND its children
559 + link.exit()
560 + .transition()
561 + .duration(750)
562 + .attr({
563 + opacity: 0
564 + })
565 + .remove();
436 } 566 }
437 567
438 function createDeviceNode(device) { 568 function createDeviceNode(device) {
...@@ -451,6 +581,22 @@ ...@@ -451,6 +581,22 @@
451 return node; 581 return node;
452 } 582 }
453 583
584 + function createHostNode(host) {
585 + // start with the object as is
586 + var node = host;
587 +
588 + // Augment as needed...
589 + node.class = 'host';
590 + node.svgClass = 'node host';
591 + // TODO: consider placing near its switch, if [x,y] not defined
592 + positionNode(node);
593 +
594 + // cache label array length
595 + network.hostLabelCount = host.labels.length;
596 +
597 + return node;
598 + }
599 +
454 function positionNode(node) { 600 function positionNode(node) {
455 var meta = node.metaUi, 601 var meta = node.metaUi,
456 x = 0, 602 x = 0,
...@@ -525,7 +671,7 @@ ...@@ -525,7 +671,7 @@
525 entering.filter('.device').each(function (d) { 671 entering.filter('.device').each(function (d) {
526 var node = d3.select(this), 672 var node = d3.select(this),
527 icon = iconUrl(d), 673 icon = iconUrl(d),
528 - idx = (labelIdx < d.labels.length) ? labelIdx : 0, 674 + idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0,
529 box; 675 box;
530 676
531 node.append('rect') 677 node.append('rect')
...@@ -568,6 +714,32 @@ ...@@ -568,6 +714,32 @@
568 } 714 }
569 }); 715 });
570 716
717 + // augment host nodes...
718 + entering.filter('.host').each(function (d) {
719 + var node = d3.select(this),
720 + idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0,
721 + box;
722 +
723 + node.append('circle')
724 + .attr('r', 8); // TODO: define host circle radius
725 +
726 + // TODO: are we attaching labels to hosts?
727 + node.append('text')
728 + .text(d.labels[idx])
729 + .attr('dy', '1.1em');
730 +
731 + // debug function to show the modelled x,y coordinates of nodes...
732 + if (debug('showNodeXY')) {
733 + node.select('circle').attr('fill-opacity', 0.5);
734 + node.append('circle')
735 + .attr({
736 + class: 'debug',
737 + cx: 0,
738 + cy: 0,
739 + r: '3px'
740 + });
741 + }
742 + });
571 743
572 // operate on both existing and new nodes, if necessary 744 // operate on both existing and new nodes, if necessary
573 //node .foo() .bar() ... 745 //node .foo() .bar() ...
...@@ -618,7 +790,6 @@ ...@@ -618,7 +790,6 @@
618 webSock.ws = new WebSocket(webSockUrl()); 790 webSock.ws = new WebSocket(webSockUrl());
619 791
620 webSock.ws.onopen = function() { 792 webSock.ws.onopen = function() {
621 - webSock._send("Hi there!");
622 }; 793 };
623 794
624 webSock.ws.onmessage = function(m) { 795 webSock.ws.onmessage = function(m) {
...@@ -643,7 +814,7 @@ ...@@ -643,7 +814,7 @@
643 if (webSock.ws) { 814 if (webSock.ws) {
644 webSock.ws.send(message); 815 webSock.ws.send(message);
645 } else { 816 } else {
646 - network.view.alert('no web socket open'); 817 + network.view.alert('no web socket open\n\n' + message);
647 } 818 }
648 } 819 }
649 820
...@@ -714,7 +885,7 @@ ...@@ -714,7 +885,7 @@
714 //flyinPane(null); 885 //flyinPane(null);
715 } 886 }
716 887
717 - 888 + // TODO: this click handler does not get unloaded when the view does
718 $('#view').on('click', function(e) { 889 $('#view').on('click', function(e) {
719 if (!$(e.target).closest('.node').length) { 890 if (!$(e.target).closest('.node').length) {
720 if (!e.metaKey) { 891 if (!e.metaKey) {
...@@ -723,6 +894,33 @@ ...@@ -723,6 +894,33 @@
723 } 894 }
724 }); 895 });
725 896
897 +
898 + function prepareScenario(view, ctx, dbg) {
899 + var sc = scenario,
900 + urlSc = sc.evDir + ctx + sc.evScenario;
901 +
902 + if (!ctx) {
903 + view.alert("No scenario specified (null ctx)");
904 + return;
905 + }
906 +
907 + sc.view = view;
908 + sc.ctx = ctx;
909 + sc.debug = dbg;
910 + sc.evNumber = 0;
911 +
912 + d3.json(urlSc, function(err, data) {
913 + var p = data && data.params || {};
914 + if (err) {
915 + view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
916 + } else {
917 + sc.params = p;
918 + view.alert("Scenario loaded: " + ctx + '\n\n' + data.title);
919 + }
920 + });
921 +
922 + }
923 +
726 // ============================== 924 // ==============================
727 // View life-cycle callbacks 925 // View life-cycle callbacks
728 926
...@@ -767,10 +965,12 @@ ...@@ -767,10 +965,12 @@
767 node = nodeG.selectAll('.node'); 965 node = nodeG.selectAll('.node');
768 966
769 function ldist(d) { 967 function ldist(d) {
770 - return fcfg.linkDistance[d.class] || 150; 968 + return 2 * 30;
969 + //return fcfg.linkDistance[d.class] || 150;
771 } 970 }
772 function lstrg(d) { 971 function lstrg(d) {
773 - return fcfg.linkStrength[d.class] || 1; 972 + return 2 * 0.6;
973 + //return fcfg.linkStrength[d.class] || 1;
774 } 974 }
775 function lchrg(d) { 975 function lchrg(d) {
776 return fcfg.charge[d.class] || -200; 976 return fcfg.charge[d.class] || -200;
...@@ -781,14 +981,11 @@ ...@@ -781,14 +981,11 @@
781 } 981 }
782 982
783 function atDragEnd(d, self) { 983 function atDragEnd(d, self) {
784 - // once we've finished moving, pin the node in position, 984 + // once we've finished moving, pin the node in position
785 - // if it is a device (not a host) 985 + d.fixed = true;
786 - if (d.class === 'device') { 986 + d3.select(self).classed('fixed', true);
787 - d.fixed = true; 987 + if (config.useLiveData) {
788 - d3.select(self).classed('fixed', true); 988 + tellServerCoords(d);
789 - if (config.useLiveData) {
790 - tellServerCoords(d);
791 - }
792 } 989 }
793 } 990 }
794 991
...@@ -806,17 +1003,34 @@ ...@@ -806,17 +1003,34 @@
806 .size(forceDim) 1003 .size(forceDim)
807 .nodes(network.nodes) 1004 .nodes(network.nodes)
808 .links(network.links) 1005 .links(network.links)
809 - .charge(lchrg) 1006 + .gravity(0.3)
1007 + .charge(-15000)
1008 + .friction(0.1)
1009 + //.charge(lchrg)
810 .linkDistance(ldist) 1010 .linkDistance(ldist)
811 .linkStrength(lstrg) 1011 .linkStrength(lstrg)
812 .on('tick', tick); 1012 .on('tick', tick);
813 1013
1014 + // TVUE
1015 + //.gravity(0.3)
1016 + //.charge(-15000)
1017 + //.friction(0.1)
1018 + //.linkDistance(function(d) { return d.value * 30; })
1019 + //.linkStrength(function(d) { return d.value * 0.6; })
1020 + //.size([w, h])
1021 + //.start();
1022 +
814 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd); 1023 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
815 } 1024 }
816 1025
817 - function load(view, ctx) { 1026 + function load(view, ctx, flags) {
818 // cache the view token, so network topo functions can access it 1027 // cache the view token, so network topo functions can access it
819 network.view = view; 1028 network.view = view;
1029 + config.useLiveData = !flags.local;
1030 +
1031 + if (!config.useLiveData) {
1032 + prepareScenario(view, ctx, flags.debug);
1033 + }
820 1034
821 // set our radio buttons and key bindings 1035 // set our radio buttons and key bindings
822 view.setRadio(btnSet); 1036 view.setRadio(btnSet);
......