Simon Hunt

GUI -- augmented hash parsing to include flags (after '?'), which are passed int…

…o view callbacks as a boolean map.
 - moved event test files into sub directories
 - prepared topo2.js for scenario choice via hash context and 'local' (and 'debug') flag.
 - added 'simple' scenario: 2 switches, 1 link, and 2 hosts.
 - augmented topo event dispatch for yet-to-be-implemented event handlers.
 - implemented addHost() event handler.

Change-Id: I06b032684fd4d5f85262d13d58ad10edae23b3ed
Showing 56 changed files with 490 additions and 78 deletions
...@@ -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,59 @@ ...@@ -20,55 +20,59 @@
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
70 /* for debugging */ 74 /* for debugging */
71 -svg .node circle.debug { 75 +#topo svg .node circle.debug {
72 fill: white; 76 fill: white;
73 stroke: red; 77 stroke: red;
74 } 78 }
......
...@@ -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;
191 + }
192 + return false;
182 } 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);
190 } else { 219 } else {
220 + v.alert('non-404 error:\n\n' + onosUrl + '\n\n' + err);
221 + }
222 + } else {
223 + testDebug('loaded: ' + onosUrl);
191 handleServerEvent(data); 224 handleServerEvent(data);
192 } 225 }
193 }); 226 });
194 } 227 }
195 228
196 - function injectStartupEvents(view) { 229 + function tryUiEvent(uiUrl) {
197 - if (config.useLiveData) { 230 + var v = scenario.view;
198 - view.alert("Sorry, currently using live data.."); 231 + d3.json(uiUrl, function(err, data) {
199 - return; 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:
200 } 245 }
201 246
202 - var lastStartupEvent = 32; 247 + function injectStartupEvents(view) {
203 - while (eventNumber < lastStartupEvent) { 248 + var last = scenario.params.lastAuto || 0;
249 + if (abortIfLive()) { return; }
250 +
251 + while (scenario.evNumber < last) {
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);
...@@ -345,11 +388,35 @@ ...@@ -345,11 +388,35 @@
345 } 388 }
346 } 389 }
347 390
391 + function addHost(data) {
392 + var host = data.payload,
393 + node = createHostNode(host),
394 + lnk;
395 +
396 + note('addHost', node.id);
397 + network.nodes.push(node);
398 + network.lookup[host.id] = node;
399 + updateNodes();
400 +
401 + lnk = createHostLink(host);
402 + if (lnk) {
403 + network.links.push(lnk);
404 + updateLinks();
405 + }
406 + network.force.start();
407 + }
408 +
348 function showPath(data) { 409 function showPath(data) {
349 network.view.alert(data.event + "\n" + data.payload.links.length); 410 network.view.alert(data.event + "\n" + data.payload.links.length);
350 } 411 }
351 412
352 - // .... 413 + // ...............................
414 +
415 + function stillToImplement(data) {
416 + var p = data.payload;
417 + note(data.event, p.id);
418 + network.view.alert('Not yet implemented: "' + data.event + '"');
419 + }
353 420
354 function unknownEvent(data) { 421 function unknownEvent(data) {
355 network.view.alert('Unknown event type: "' + data.event + '"'); 422 network.view.alert('Unknown event type: "' + data.event + '"');
...@@ -367,6 +434,35 @@ ...@@ -367,6 +434,35 @@
367 return 'translate(' + x + ',' + y + ')'; 434 return 'translate(' + x + ',' + y + ')';
368 } 435 }
369 436
437 + function createHostLink(host) {
438 + var src = host.id,
439 + dst = host.cp.device,
440 + srcNode = network.lookup[src],
441 + dstNode = network.lookup[dst],
442 + lnk;
443 +
444 + if (!dstNode) {
445 + // TODO: send warning message back to server on websocket
446 + network.view.alert('switch not on map for link\n\n' +
447 + 'src = ' + src + '\ndst = ' + dst);
448 + return null;
449 + }
450 +
451 + lnk = {
452 + id: safeId(src) + '~' + safeId(dst),
453 + source: srcNode,
454 + target: dstNode,
455 + class: 'link',
456 + svgClass: 'link hostLink',
457 + x1: srcNode.x,
458 + y1: srcNode.y,
459 + x2: dstNode.x,
460 + y2: dstNode.y,
461 + width: 1
462 + };
463 + return lnk;
464 + }
465 +
370 function createLink(link) { 466 function createLink(link) {
371 var type = link.type, 467 var type = link.type,
372 src = link.src, 468 src = link.src,
...@@ -451,6 +547,22 @@ ...@@ -451,6 +547,22 @@
451 return node; 547 return node;
452 } 548 }
453 549
550 + function createHostNode(host) {
551 + // start with the object as is
552 + var node = host;
553 +
554 + // Augment as needed...
555 + node.class = 'host';
556 + node.svgClass = 'node host';
557 + // TODO: consider placing near its switch, if [x,y] not defined
558 + positionNode(node);
559 +
560 + // cache label array length
561 + network.hostLabelCount = host.labels.length;
562 +
563 + return node;
564 + }
565 +
454 function positionNode(node) { 566 function positionNode(node) {
455 var meta = node.metaUi, 567 var meta = node.metaUi,
456 x = 0, 568 x = 0,
...@@ -525,7 +637,7 @@ ...@@ -525,7 +637,7 @@
525 entering.filter('.device').each(function (d) { 637 entering.filter('.device').each(function (d) {
526 var node = d3.select(this), 638 var node = d3.select(this),
527 icon = iconUrl(d), 639 icon = iconUrl(d),
528 - idx = (labelIdx < d.labels.length) ? labelIdx : 0, 640 + idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0,
529 box; 641 box;
530 642
531 node.append('rect') 643 node.append('rect')
...@@ -568,6 +680,32 @@ ...@@ -568,6 +680,32 @@
568 } 680 }
569 }); 681 });
570 682
683 + // augment host nodes...
684 + entering.filter('.host').each(function (d) {
685 + var node = d3.select(this),
686 + idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0,
687 + box;
688 +
689 + node.append('circle')
690 + .attr('r', 8); // TODO: define host circle radius
691 +
692 + // TODO: are we attaching labels to hosts?
693 + node.append('text')
694 + .text(d.labels[idx])
695 + .attr('dy', '1.1em');
696 +
697 + // debug function to show the modelled x,y coordinates of nodes...
698 + if (debug('showNodeXY')) {
699 + node.select('circle').attr('fill-opacity', 0.5);
700 + node.append('circle')
701 + .attr({
702 + class: 'debug',
703 + cx: 0,
704 + cy: 0,
705 + r: '3px'
706 + });
707 + }
708 + });
571 709
572 // operate on both existing and new nodes, if necessary 710 // operate on both existing and new nodes, if necessary
573 //node .foo() .bar() ... 711 //node .foo() .bar() ...
...@@ -643,7 +781,7 @@ ...@@ -643,7 +781,7 @@
643 if (webSock.ws) { 781 if (webSock.ws) {
644 webSock.ws.send(message); 782 webSock.ws.send(message);
645 } else { 783 } else {
646 - network.view.alert('no web socket open'); 784 + network.view.alert('no web socket open\n\n' + message);
647 } 785 }
648 } 786 }
649 787
...@@ -714,7 +852,7 @@ ...@@ -714,7 +852,7 @@
714 //flyinPane(null); 852 //flyinPane(null);
715 } 853 }
716 854
717 - 855 + // TODO: this click handler does not get unloaded when the view does
718 $('#view').on('click', function(e) { 856 $('#view').on('click', function(e) {
719 if (!$(e.target).closest('.node').length) { 857 if (!$(e.target).closest('.node').length) {
720 if (!e.metaKey) { 858 if (!e.metaKey) {
...@@ -723,6 +861,33 @@ ...@@ -723,6 +861,33 @@
723 } 861 }
724 }); 862 });
725 863
864 +
865 + function prepareScenario(view, ctx, dbg) {
866 + var sc = scenario,
867 + urlSc = sc.evDir + ctx + sc.evScenario;
868 +
869 + if (!ctx) {
870 + view.alert("No scenario specified (null ctx)");
871 + return;
872 + }
873 +
874 + sc.view = view;
875 + sc.ctx = ctx;
876 + sc.debug = dbg;
877 + sc.evNumber = 0;
878 +
879 + d3.json(urlSc, function(err, data) {
880 + var p = data && data.params || {};
881 + if (err) {
882 + view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
883 + } else {
884 + sc.params = p;
885 + view.alert("Scenario loaded: " + ctx + '\n\n' + data.title);
886 + }
887 + });
888 +
889 + }
890 +
726 // ============================== 891 // ==============================
727 // View life-cycle callbacks 892 // View life-cycle callbacks
728 893
...@@ -781,16 +946,13 @@ ...@@ -781,16 +946,13 @@
781 } 946 }
782 947
783 function atDragEnd(d, self) { 948 function atDragEnd(d, self) {
784 - // once we've finished moving, pin the node in position, 949 + // once we've finished moving, pin the node in position
785 - // if it is a device (not a host)
786 - if (d.class === 'device') {
787 d.fixed = true; 950 d.fixed = true;
788 d3.select(self).classed('fixed', true); 951 d3.select(self).classed('fixed', true);
789 if (config.useLiveData) { 952 if (config.useLiveData) {
790 tellServerCoords(d); 953 tellServerCoords(d);
791 } 954 }
792 } 955 }
793 - }
794 956
795 function tellServerCoords(d) { 957 function tellServerCoords(d) {
796 sendMessage('updateMeta', { 958 sendMessage('updateMeta', {
...@@ -814,9 +976,14 @@ ...@@ -814,9 +976,14 @@
814 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd); 976 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
815 } 977 }
816 978
817 - function load(view, ctx) { 979 + function load(view, ctx, flags) {
818 // cache the view token, so network topo functions can access it 980 // cache the view token, so network topo functions can access it
819 network.view = view; 981 network.view = view;
982 + config.useLiveData = !flags.local;
983 +
984 + if (!config.useLiveData) {
985 + prepareScenario(view, ctx, flags.debug);
986 + }
820 987
821 // set our radio buttons and key bindings 988 // set our radio buttons and key bindings
822 view.setRadio(btnSet); 989 view.setRadio(btnSet);
......