aboutsummaryrefslogtreecommitdiffstats
path: root/web/nms.gathering.org/nms2/index.html
blob: 81f68e5be818d47976047e068314a3f742f23e67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../favicon.ico">

    <title>NMS2</title>

    <!-- Bootstrap core CSS -->
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="css/navbar-static-top.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
      <![endif]-->
      <style type="text/css">
	canvas {
	  -webkit-touch-callout: none;
	  -webkit-user-select: none;
	  -khtml-user-select: none;
	  -moz-user-select: none;
	  -ms-user-select: none;
	  user-select: none;
	  outline: none;
	  -webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */
	}   
      </style>
  </head>

  <body id="body">
    <nav class="navbar navbar-default navbar-static-top">
      <div class="container-fluid">
	<div class="navbar-header">
	  <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
	    <span class="sr-only">Toggle navigation</span>
	    <span class="icon-bar"></span>
	    <span class="icon-bar"></span>
	    <span class="icon-bar"></span>
	  </button>
	</div>
	<div id="navbar" class="navbar-collapse collapse">
	  <ul class="nav navbar-nav">
	    <li class="dropdown">
	      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Menu
		<span class="caret"></span>
	      </a>
	      <ul class="dropdown-menu" role="menu">
		<li><a href="#ping" onclick="setUpdater(handler_ping)">Ping map</a></li>
		<li><a href="#uplink" onclick="setUpdater(handler_uplinks)">Uplink map</a></li>
		<li><a href="#temp" onclick="setUpdater(handler_temp)">Temperature map</a></li>
		<li><a href="#traffic" onclick="setUpdater(handler_traffic)">Traffic map</a></li>
		<li><a href="#comment" onclick="setUpdater(handler_comment)">Comment spotter</a></li>
		<li><a href="#traffictot" onclick="setUpdater(handler_traffic_tot)">Total switch traffic</a></li>
		<li><a href="#disco" onclick="setUpdater(handler_disco)">DISCO</a></li>
		<li class="divider"> </li>
		<li><a href="#" onclick="toggleLayer('nowPickerBox');document.getElementById('nowPicker').focus();">Travel in time</a></li>
		<li><a href="#" onclick="startReplay();" title="Replay from opening 120 minutes per second">Replay TG</a></li>
		<li class="divider"> </li>
		<li class="dropdown-header">View</li>
		<li><a href="#" onclick="toggleNightMode()">Toggle Night Mode</a></li>
		<li><a href="#" onclick="showBlurBox()">Tweak Night Mode blur</a></li>
		<li><a href="#" onclick='showLayer("layerVisibility");'>Set layer visibility</a></li>
		<li class="divider"> </li>
		<li class="dropdown-header">Map scale</li>
		<li><a href="#"><label id="scaler-text" for='scaler'></label><input type="range" id="scaler" name="points" min="0.2" max="3" step="0.01" onchange="scaleChange()" /></a></li>
		<li class="divider"> </li>
		<li class="dropdown-header">Help</li>
		<li><a href="#" onclick="toggleLayer('aboutData');">About TG15 data</a></li>
	  	<li><a href="#" onclick="toggleLayer('aboutBox');" >About NMS</a></li>
	  	<li><a href="#" onclick="toggleLayer('aboutPerformance');" >About Performance</a></li>
	  	<li><a href="#" onclick="toggleLayer('aboutKeybindings');" >Keyboard Shortcuts</a></li>
		<li><a onclick="showTimerDebug(); hideSwitch();" style="cursor: pointer;" >Debug timers</a></li>
	      </ul>
	    </li>
	    <li><p id="updater_name" class="navbar-text"></p></li>
	    <div class="navbar-form navbar-left">
	      <div class="form-group">
		<button class="btn btn-default btn-xs" id="legend-1"></button>
		<button class="btn btn-default btn-xs" id="legend-2"></button>
		<button class="btn btn-default btn-xs" id="legend-3"></button>
		<button class="btn btn-default btn-xs" id="legend-4"></button>
		<button class="btn btn-default btn-xs" id="legend-5"></button>
	      </div>
	    </div>
	    </li>
	  </ul>
	  <ul class="nav navbar-nav navbar-right">
	    <li><p id="speed" class="navbar-text" title="Client port speed / Total port speed"></p></li>
	  </ul>
	</div><!--/.nav-collapse -->
      </div>
    </nav>

    <div class="container-fluid">

      <div class="row-fluid">
	<div class="span12">
	  <div id="aboutData" class="col-md-4" style="position: absolute; display:none; z-index: 130;">
	    <div id="abotData" class="panel panel-default">
	      <div class="panel-heading">
		<h3 class="panel-title">About the TG15 data
		  <button type="button" class="close" aria-label="Close" onclick="document.getElementById('aboutData').style.display = 'none';" style="float: right">
		    <span aria-hidden="true">&times;</span>
		  </button>
		</h3>
	      </div>
	      <div class="panel-body">
		<p>The data you see from The Gathering 2015 will seem
		"broken up". This is not because we don't have data from
		the first day, but because the backend was re-written on
		day 1/2 and this web app only uses the new API.</p>
		<p>NMS was set up on March 30th (Monday). Data started
		pouring in on the same day. </p>
		<p>Ping data is available for the entire event with 1
		second resolution. We "lost" data from the 30th because we
		re-inserted the switches (We have the ping data, but not
		the mapping between switch ID number and actual
		switch).</p>
		<p>DHCP data is available only for the last detected DHCP
		ack (no history, except extensive text-based logs)</p>
		<p>Uplink status is available for most of the event, but
		not exposed here. We only expose traffic-based uplink state
		here, which, again, is based on the new API.</p>
		<p>Traffic status was temporarily bugged, but is available
		from late on day 2.</p>
		<p>Temperature data is available from day 2.</p>
		<p>Plans are being made to ensure that we don't have gaps
		like these in the future.</p>
		<p>It is also worth mentioning that things like switch
		positions are not logged historically, so you see the final
		position on the map.</p>
	      </div>
	    </div>
	  </div>
	  <div id="aboutPerformance" class="col-md-4" style="position: absolute; display:none; z-index: 130;">
	    <div class="panel panel-default">
	      <div class="panel-heading">
		<h3 class="panel-title">Performance
		  <button type="button" class="close" aria-label="Close" onclick="document.getElementById('aboutPerformance').style.display = 'none';" style="float: right">
		    <span aria-hidden="true">&times;</span>
		  </button>
		</h3>
	      </div>
	      <table class="table">
		<tr>
		  <td>Outstanding AJAX requests</td>
		  <td id="outstandingAJAX"></td>
		</tr>
		<tr>
		  <td>Overflowed AJAX requests</td>
		  <td id="overflowAJAX"></td>
		</tr>
	      </table>
	      <div class="panel-body">
		<p>NMS performance is surprisingly complex. It's split into
		several parts and dealt with differently.</p>
		<p>Poller performance is a matter of efficiently collecting
		data and is mostly handled in the Perl code (and ensuring
		we use sensible database schemas).</p>
		<p>Backend performance for the GUI is mostly about not
		killing the database server. We do NOT try to protect
		against malicious clients directly, since this is a
		management system not public-facing, but Varnish is used to
		cache requests. To be able to do that properly, we need use
		absolute time when reviewing past events (so "2015-04-02
		17:30:00", not "2 hours ago"). We've also tried to minimize
		the stupidity in the queries. There's still work to be done
		here, though, as we need to split up a few large backend
		requests (port-state.pl).</p>
		<p>Front-end performance is mostly about drawing things
		sensibly and not completely bombing the memory usage. And
		about gracefully handling slow backends  This will affect
		you. For example, if you are reviewing past events and the
		DB is struggling, we'll simply skip a backend request if we
		have too many outstanding requests, that means you may jump
		from "17:00" to "18:30" instead of going through
		"17:30" and "18:00" too. This is working as intended. It
		also means that you can happily spam the forward/backward
		keyboard bindings to jump 18 hours forward: You'll overflow
		the extra AJAX requests for individual requests, but you'll
		land at the right time when you let go. But there could be
		a 1 second delay (or more if the backend really struggles)
		since you'll have to rely on the periodic backend requests
		instead of the explicit ones triggered on hitting a
		button.</p>
		<p>Note that the counters on top are updated on a timer,
		but this timer is set up at the same time as everything
		else, which means that it's likely to update at the same
		time as we fire off AJAX requests, so the 'outstanding ajax
		requests' counter might either show almost constantly 3 or
		0 depending on what timer happens to fire first. This does
		NOT mean that NMS has 3 requests all the time, just that
		we're checking right after we fire off AJAX requests every
		time.</p>
		<p>NMS also tries to handle drawing OK, which is why things
		are split into different HTML5 canvases. Blur and text are
		particularly expensive, but there's no reason to re-paint
		that all the time, etc).</p>
		<p>The basic performance experiments are done on TG15 data
		using a laptop and a VM with 6GB of memory, so it should
		hold up quite well on "proper" hardware.</p>
	      </div>
	    </div>
	  </div>
	  <div id="aboutKeybindings" class="col-md-4" style="position: absolute; display:none; z-index: 130;">
	    <div class="panel panel-default">
	      <div class="panel-heading">
		<h3 class="panel-title">Keyboard Shortcuts
		  <button type="button" class="close" aria-label="Close" onclick="document.getElementById('aboutKeybindings').style.display = 'none';" style="float: right">
		    <span aria-hidden="true">&times;</span>
		  </button>
		</h3>
	      </div>
	      <table class="table table-condensed">
		<tr>
		  <th>Key</th>
		  <th>Description</th>
		</tr>
		<tr>
		  <td>?</td>
		  <td>Toggle navigation bar</td>
		</tr>
		<tr>
		  <td>n</td>
		  <td>Toggle night mode</td>
		</tr>
		<tr>
		  <td>1</td>
		  <td>View Ping map</td>
		</tr>
		<tr>
		  <td>2</td>
		  <td>View uplink map</td>
		</tr>
		<tr>
		  <td>3</td>
		  <td>View temperature map</td>
		</tr>
		<tr>
		  <td>4</td>
		  <td>View uplink traffic map</td>
		</tr>
		<tr>
		  <td>5</td>
		  <td>View comment spotter map</td>
		</tr>
		<tr>
		  <td>6</td>
		  <td>View total switch traffic map</td>
		</tr>
		<tr>
		  <td>7</td>
		  <td>View Disco map</td>
		</tr>
		<tr>
		  <td>h</td>
		  <td>Step 1 hour back in time</td>
		</tr>
		<tr>
		  <td>j</td>
		  <td>Step 5 minutes back in time</td>
		</tr>
		<tr>
		  <td>k</td>
		  <td>Step 5 minutes forward in time</td>
		</tr>
		<tr>
		  <td>l</td>
		  <td>Step 1 hour forward in time</td>
		</tr>
		<tr>
		  <td>p</td>
		  <td>Toggle playback (1 hour per second)</td>
		</tr>
		<tr>
		  <td>r</td>
		  <td>Return to real time</td>
		</tr>
	      </table>
	    </div>
	  </div>
	  <div id="nowPickerBox" style="position: absolute; display: none; z-index: 130;" class="col-md-4">
	    <div class="panel panel-default"> 
	      <div class="panel-heading">
		<h3 class="panel-title">Time travel
		  <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('nowPickerBox').style.display = 'none';" style="float: right;">
		    <span aria-hidden="true">&times;</span>
		  </button>
		</h3>
	      </div>
	      <div class="panel-body">
		<p>Some features do not have time travel support (comment
		spotting and DHCP map at the moment). We also lack
		compatible SNMP data for the first day or so, so you'll
		only have ping data for the first day of TG15.</p>
		<p>It could take some time to load a specific point in time
		for the first time. See "About performance" under the help
		menu for more information.</p>
		<p>You can also step backwards and forwards in time, stop
		and start replay and go back to real time using keyboard
		shortcuts. See the help menu for an overview of keyboard
		shortcuts.</p>
		<div class="input-group">
		  <input type="text" class="form-control" placeholder="YYYY-MM-DD hh:mm:ss" id="nowPicker">
		  <span class="input-group-btn">
		    <button class="btn btn-default" onclick="changeNow();">Travel</button>
		  </span>
		</div>
	      </div>
	    </div>
	  </div>
	  <div style="position: absolute; z-index: 120;" class="col-md-4">
	    <div id="info-switch-parent" class="panel panel-default col-d-6" style="display: none; backgroun:silver; position: absolute; z-index: 120;">
	      <div class="panel-heading">
		<h3 class="panel-title" id="info-switch-title"></h3>
	      </div>
	      <div id="info-switch-panel-body">
		<table class="table" id="info-switch-table"></table>
	      </div>
	    </div>
	  </div>
	  <div id="aboutBox" class="col-md-4" style="display: none; position: absolute; z-index: 100;">
	    <div id="abotBox" class="panel panel-default">
	      <div class="panel-heading">
		<h3 class="panel-title">Welcome to NMS
		  <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('aboutBox').style.display = 'none';" style="float: right;">
		    <span aria-hidden="true">&times;</span>
		  </button>
		</h3>
	      </div>
	      <div class="panel-body">
		<h3>Cool stuff:</h3>
		<ul>
		  <li>Click a switch for more info</li>
		  <li>Rewind: You can check out state at a specific time or
		    replay from the beginning of the event. Only works for
		    data where we keep time-series (so not for
		  comments)</li>
		  <li>Press '?' to toggle the menu.</li>
		  <li>Auto-scaling the viewport/canvas</li>
		  <li>Total client speed (up right)</li>
		  <li>Generic(-ish) map handlers: provide a name, init-function
		    and an update-function and the nms lib does the rest as far as
		    integration goes.</li>
		</ul>
		<h3>Todo list front end:</h3>
		<ul>
		  <li>Polish time travel UI (Allow playing from a given time at a given speed, play/pause buttons, etc)</li>
		  <li>Better "popup" boxes: It's growing out of control.</li>
		  <li>Toggle auto-scale on/off</li>
		  <li>Clean up various global variables</li>
		  <li>Create name spaces in nms.*: It's just barely better
		  than global stuff now.</li>
		  <li>Add DHCP map</li>
		  <li>More info on switches: Port state, possibly link time
		    trends</li>
		  <li>Moving switches around (like ping.html + edit)</li>
		  <li>Split nms.js into multiple components to unclutter the
		    code</li>
		  <li>Comments: Fix UTF8 garbligash caused by $dbh-&gt;quote()</li>
		</ul>
		<h3>Todo for backend:</h3>
		<ul>
		  <li>IPv6 support</li>
		  <li>Provide public API's</li>
		  <li>Investigate a json tree filter/massager</li>
		  <li>Close SQL injections (IT'S WIDE OPEN BECAUSE WHY NOT THAT'S NEVER A PROBLEM)</li>
		  <li>Split port-state.pl into multiple appropriate pieces. Right
		    it mixes heavy time-critical data with less time-critical and
		    cheap computation.</li>
		  <li>Rip comments out of port-state.pl completely so it's not
		    bound by the same cache issues and can be reliably
		    refreshed.</li>
		  <li>Consider time log of DHCP (right now it just stores the
		    most recent timestamp, making time travel impossible)</li>
		  <li>Fix SNMP-fetcher so it gets ifXTable and at least
		    ifOperStatus from ifTable. Don't request the entire
		    ifXTable if we can avoid it. Possibly other
		  tweaks.</li>
		  <li>Support for adding switches through an API, not just pure SQL.</li>
		  <li>Integrate with FAP</li>
		  <li>Clean up old interfaces</li>
		  <li>Review various agents/tools</li>
		  <li>Improve cache headers</li>
		  <li>Cache invalidation of comments? (Probably not needed)</li>
		  <li>Re-test the SQL schema. It's been modified and works fine
		    on my laptop, but I need to dump it, commit it and test it.</li>
		  <li>Munin plugin for ports.</li>
		</ul>
	      </div>
	    </div>
	  </div>
	  <div id="blurManic" class="panel panel-default" style="display: none; position: absolute; z-index: 100;">
	    <div class="panel-heading">
	      <h1 class="panel-title">Blur tweaks
		<button type="button" class="close" aria-labe="Close" onclick="document.getElementById('blurManic').style.display = 'none';" style="float: right;">
		  <span aria-hidden="true">&times;</span>
		</button>
	      </h1>
	    </div>
	    <div class="panel-body">
	      <div class="form">
		<div class="form-group">
		  <label for="shadowBlur">Blur strength</label>
		  <input type="number" id="shadowBlur" class="form-control">
		</div>
		<div class="form-group">
		  <label for="shadowColor">Blur color</label>
		  <input type="color" id="shadowColor" class="form-control">
		</div>
		<button type="button" class="btn btn-default" onclick="applyBlur();">Apply</button>
	      </div>
	    </div>
	  </div>
	</div>
	<div id="debugTimers" class="panel panel-default" style="display: none; position: absolute; z-index: 100;">
	  <div class="panel-heading">
	    <h1 class="panel-title">Debug timers (e.g.: Break stuff! FAST!)
	      <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('debugTimers').style.display = 'none';" style="float: right;">
		<span aria-hidden="true">&times;</span>
	      </button>
	    </h1>
	  </div>
	  <div id="timerTableTop" class="panel-body">
	    <p>These are internal timers for the NMS frontend. They are
	    provided mainly to debug the frontend. Setting AJAX-triggering
	    counters to ridiculous numbers is not advised (mainly because
	    it causes server load).</p>
	  </div>
	  <table id="timerTable"> </table>
	</div>
	<div id="layerVisibility" class="panel panel-default" style="display: none; position: absolute; z-index: 100;">
	  <div class="panel-heading">
	    <h1 class="panel-title">Set layer visibility
	      <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('layerVisibility').style.display = 'none';" style="float: right;">
		<span aria-hidden="true">&times;</span>
	      </button>
	    </h1>
	  </div>
	  <div class="panel-body">
	    <table id="visibilityTable" class="table">
	      <tr>
		<td>Background</td>
		<td>
		  <button onclick='hideLayer("bgCanvas");'>Hide</button>
		  <button onclick='showLayer("bgCanvas");'>Show</button>
		</td>
	      </tr>
	      <tr>
		<td>Linknets</td>
		<td>
		  <button onclick='hideLayer("linkCanvas");'>Hide</button>
		  <button onclick='showLayer("linkCanvas");'>Show</button>
		</td>
	      </tr>
	      <tr>
		<td>Blur</td>
		<td>
		  <button onclick='hideLayer("blurCanvas");'>Hide</button>
		  <button onclick='showLayer("blurCanvas");'>Show</button>
		</td>
	      </tr>
	      <tr>
		<td>Switches</td>
		<td>
		  <button onclick='hideLayer("switchCanvas");'>Hide</button>
		  <button onclick='showLayer("switchCanvas");'>Show</button>
		</td>
	      </tr>
	      <tr>
		<td>Text</td>
		<td>
		  <button onclick='hideLayer("textCanvas");'>Hide</button>
		  <button onclick='showLayer("textCanvas");'>Show</button>
		</td>
	      </tr>
	      <tr>
		<td>TextInfo</td>
		<td>
		  <button onclick='hideLayer("textInfoCanvas");'>Hide</button>
		  <button onclick='showLayer("textInfoCanvas");'>Show</button>
		</td>
	      </tr>
	      <tr>
		<td>Timestamp</td>
		<td>
		  <button onclick='hideLayer("topCanvas");'>Hide</button>
		  <button onclick='showLayer("topCanvas");'>Show</button>
		</td>
	      </tr>
	    </table>
	  </div>
	</div>

	<canvas id="bgCanvas" width="1920" height="1032" style="position: absolute; z-index: 1;"> </canvas>
	<canvas id="linkCanvas" width="1920" height="1032" style="position: absolute; z-index: 10;"> </canvas>
	<canvas id="blurCanvas" width="1920" height="1032" style="position: absolute; z-index: 20;"> </canvas>
	<canvas id="switchCanvas" width="1920" height="1032" style="position: absolute; z-index: 30;"> </canvas>
	<canvas id="textCanvas" width="1920" height="1032" style="position: absolute; z-index: 40;"> </canvas>
	<canvas id="textInfoCanvas" width="1920" height="1032" style="position: absolute; z-index: 45;"> </canvas>
	<canvas id="topCanvas" width="1920" height="1032" style="position: absolute; z-index: 50;"> </canvas>
	<canvas id="inputCanvas" width="1920" height="1032" style="position: absolute; z-index: 60; cursor: pointer;" onclick="canvasClick(event)">
	</canvas>
	<canvas id="hiddenCanvas" width="1000" height="10" style="display: none; position: absolute; z-index: 1000 "></canvas>

	<div style="display:none;"><img id="source" src="img/tg15-salkart-clean-big.png" ></div>
      </div>
    </div><!--/.fluid-container-->
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/bootstrap.min.js" type="text/javascript"></script>
    <script type="text/javascript" src="js/nms.js"></script>
    <script type="text/javascript" src="js/nms-color-util.js"></script>
    <script type="text/javascript" src="js/nms-map-handlers.js"></script>
    <script type="text/javascript">
initNMS();
    </script>
  </body>
</html>