diff options
author | Kristian Lyngstol <kristian@bohemians.org> | 2016-03-13 22:04:00 +0100 |
---|---|---|
committer | Kristian Lyngstol <kristian@bohemians.org> | 2016-03-13 22:04:00 +0100 |
commit | a57fe6b9a707222e0ca1b4e18a542b5b179e6b72 (patch) | |
tree | d342d858c8e142dafb76bb5d326ec1ebb3e040b3 | |
parent | 8a89ee32f56e37848ad29be033b095c46b7ce9e3 (diff) | |
parent | fe776bbc6f2e62436acea72f2f3bf027a6138ed8 (diff) |
Merge branch 'master' of github.com:tech-server/tgmanage
-rw-r--r-- | include/nms/util.pm | 33 | ||||
-rwxr-xr-x | include/nms/web.pm | 14 | ||||
-rw-r--r-- | nms/Dockerfile.in | 2 | ||||
-rw-r--r-- | web/etc/varnish/nms.vcl | 1 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/private/comment-add | 11 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/private/comment-change | 10 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/private/port-state | 2 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/private/switch-add | 78 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/private/switches-management | 2 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/public/ping | 2 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/public/switch-state | 2 | ||||
-rwxr-xr-x | web/nms.gathering.org/api/public/switches | 2 | ||||
-rw-r--r-- | web/nms.gathering.org/index.html | 300 | ||||
-rw-r--r-- | web/nms.gathering.org/js/nms-color-util.js | 22 | ||||
-rw-r--r-- | web/nms.gathering.org/js/nms-data.js | 247 | ||||
-rw-r--r-- | web/nms.gathering.org/js/nms-info-box.js | 333 | ||||
-rw-r--r-- | web/nms.gathering.org/js/nms-map-handlers.js | 78 | ||||
-rw-r--r-- | web/nms.gathering.org/js/nms-map.js | 477 | ||||
-rw-r--r-- | web/nms.gathering.org/js/nms.js | 1466 |
19 files changed, 1383 insertions, 1699 deletions
diff --git a/include/nms/util.pm b/include/nms/util.pm index c97572b..79598d7 100644 --- a/include/nms/util.pm +++ b/include/nms/util.pm @@ -18,6 +18,7 @@ sub parse_switch { 'mgtmt4' => "$mgtmt4", 'mgtmt6' => "$mgtmt6", 'lolid' => "$lolid", + 'ip' => "$mgtmt4", 'distro' => "$distro" ); %{$ret{'placement'}} = guess_placement($switch); @@ -62,13 +63,13 @@ sub guess_placement { my ($e, $s) = ($1, $2); $src = "main"; - $x = int(232 + (($e-1)/2) * 31.1); + $x = int(292 + (($e-1)/2) * 31.1); $y = undef; - $x += 14 if ($e >= 17); - $x += 14 if ($e >= 29); - $x += 14 if ($e >= 45); - $x += 14 if ($e >= 63); + $x += 14 if ($e >= 13); + $x += 14 if ($e >= 25); + $x += 14 if ($e >= 41); + $x += 14 if ($e >= 59); if ($s > 2) { $y = 405 - 120 * ($s-2); @@ -83,17 +84,18 @@ sub guess_placement { $y += 45 if $name eq "e1-4"; $y += 20 if $name eq "e3-4"; $y += 15 if $name eq "e5-4"; - $yy -= 25 if $name eq "e11-1"; + $yy -= 25 if $name eq "e7-1"; + $y += 10 if $name eq "e5-2"; + $yy -= 25 if $name eq "e5-2"; + $y += 20 if ($e >= 81 and $s == 2); + $yy -= 20 if ($e >= 79 and $s == 1); + $yy -= 30 if ($e >= 81 and $s == 1); - #$yy -= 14 if $name eq "e77-1"; - #$yy -= 28 if $name eq "e79-1"; - #$yy -= 15 if $name eq "e81-1"; - #$yy -= 56 if $name eq "e83-1"; } elsif ($name =~ /^sw(\d+)-creativia$/) { my ($s) = ($1); $src = "creativia"; $x = 1535; - $y = int(130 + 32.2 * $s); + $y = int(160 + 32.2 * $s); $yy = $y + 20; if ($s == 1) { $xx = $x + 70; @@ -108,14 +110,11 @@ sub guess_placement { } elsif ($name =~ /^crew(\d+)-(\d+)$/) { my ($s, $n) = ($1, $2); $src = "crew"; - $x = 1023 + 45 * $n; - $y = int(329 + 20.5 * $s); - $xx = $x + 45; + $x = 550 + 65 * $n; + $y = int(759 + 20.5 * $s); + $xx = $x + 65; $yy = $y + 14; - if ($s == 1 && $n == 1) { - $xx += 25; - } } else { # Fallback to have _some_ position $src = "random"; diff --git a/include/nms/web.pm b/include/nms/web.pm index 74f40c1..0f37a59 100755 --- a/include/nms/web.pm +++ b/include/nms/web.pm @@ -7,12 +7,15 @@ use DBI; use Data::Dumper; use JSON; use nms; +use Digest::SHA; +use FreezeThaw; +use URI::Escape; package nms::web; use base 'Exporter'; our %get_params; our %json; -our @EXPORT = qw(finalize_output json dbh db_safe_quote %get_params get_input %json); +our @EXPORT = qw(finalize_output json $dbh db_safe_quote %get_params get_input %json); our $dbh; our $now; our $when; @@ -62,10 +65,12 @@ sub setwhen { sub finalize_output { my $query; - $query = $dbh->prepare ('select ' . $now . ' as time;'); + my $hash = Digest::SHA::sha512_base64(FreezeThaw::freeze(%json)); + $query = $dbh->prepare('select to_char(' . $now . ', \'YYYY-MM-DD"T"HH24:MI:SS\') as time;'); $query->execute(); $json{'time'} = $query->fetchrow_hashref()->{'time'}; + $json{'hash'} = $hash; printcc; print "Content-Type: text/jso; charset=utf-8\n\n"; @@ -74,9 +79,10 @@ sub finalize_output { } sub populate_params { - foreach my $hdr (split("&",$ENV{'QUERY_STRING'} || "")) { + my $querystring = $ENV{'QUERY_STRING'} || ""; + foreach my $hdr (split("&",$querystring)) { my ($key, $value) = split("=",$hdr,"2"); - $get_params{$key} = $value; + $get_params{$key} = URI::Escape::uri_unescape($value); } } diff --git a/nms/Dockerfile.in b/nms/Dockerfile.in index 2793882..b1d2140 100644 --- a/nms/Dockerfile.in +++ b/nms/Dockerfile.in @@ -41,7 +41,6 @@ RUN echo . /etc/default/locale >> /root/.bashrc RUN echo export LANG >> /root/.bashrc RUN echo . /etc/bash_completion >> /root/.bashrc ENV TERM=rxvt-unicode -ADD .vimrc /root/.vimrc RUN rm /etc/apt/apt.conf.d/docker-clean RUN systemctl mask dev-hugepages.mount sys-fs-fuse-connections.mount systemd-logind.service RUN git clone https://github.com/tech-server/tgmanage.git /srv/tgmanage @@ -83,6 +82,7 @@ RUN apt-get -y install \ perl-base \ perl-modules \ varnish \ + libfreezethaw-perl \ apache2 RUN cd /srv/tgmanage/ && tools/get_mibs.sh diff --git a/web/etc/varnish/nms.vcl b/web/etc/varnish/nms.vcl index e4b4747..4f1a855 100644 --- a/web/etc/varnish/nms.vcl +++ b/web/etc/varnish/nms.vcl @@ -30,6 +30,7 @@ sub vcl_recv { return (pass); } + return (pass); # Brukes ikke. Cookies er for nubs. unset req.http.Cookie; diff --git a/web/nms.gathering.org/api/private/comment-add b/web/nms.gathering.org/api/private/comment-add index beb7b21..2f8b0b7 100755 --- a/web/nms.gathering.org/api/private/comment-add +++ b/web/nms.gathering.org/api/private/comment-add @@ -2,13 +2,16 @@ # vim:ts=8:sw=8 use lib '../../../../include'; use utf8; -use nms::web; +use nms::web qw($dbh db_safe_quote get_input finalize_output); use strict; use warnings; -my $data = db_safe_quote('comment'); -my $switch = db_safe_quote('switch'); -my $user = $dbh->quote($cgi->remote_user() || "undefined"); +my $in = get_input(); +my %tmp = %{JSON::XS::decode_json($in)}; + +my $data = $dbh->quote($tmp{'comment'}); +my $switch = $dbh->quote($tmp{'switch'}); +my $user = $dbh->quote($ENV{'REMOTE_USER'} || "undefined"); my $q = $nms::web::dbh->prepare("INSERT INTO switch_comments (time,username,switch,comment) values (now(),$user,(select switch from switches where sysname = $switch limit 1),$data)"); $q->execute(); diff --git a/web/nms.gathering.org/api/private/comment-change b/web/nms.gathering.org/api/private/comment-change index ccf336d..fb7da54 100755 --- a/web/nms.gathering.org/api/private/comment-change +++ b/web/nms.gathering.org/api/private/comment-change @@ -3,12 +3,16 @@ use lib '../../../../include'; use utf8; use nms; -use nms::web; +use nms::web qw($dbh db_safe_quote get_input finalize_output); + use strict; use warnings; -my $id = db_safe_quote('comment'); -my $state = db_safe_quote('state'); +my $in = get_input(); +my %tmp = %{JSON::XS::decode_json($in)}; + +my $id = $dbh->quote($tmp{'comment'}); +my $state = $dbh->quote($tmp{'state'}); my $q = $nms::web::dbh->prepare("UPDATE switch_comments SET state = " . $state . " WHERE id = " . $id . ";"); $q->execute(); diff --git a/web/nms.gathering.org/api/private/port-state b/web/nms.gathering.org/api/private/port-state index 1f30181..8c6b64c 100755 --- a/web/nms.gathering.org/api/private/port-state +++ b/web/nms.gathering.org/api/private/port-state @@ -27,5 +27,7 @@ while (my $ref = $q3->fetchrow_hashref()) { $nms::web::json{'switches'}{$ref->{'sysname'}}{'temp'} = $ref->{'temp'}; $nms::web::json{'switches'}{$ref->{'sysname'}}{'temp_time'} = $ref->{'time'}; } +$nms::web::cc{'max-age'} = '2'; +$nms::web::cc{'stale-while-revalidate'} = '120'; finalize_output(); diff --git a/web/nms.gathering.org/api/private/switch-add b/web/nms.gathering.org/api/private/switch-add index af8db38..92479f0 100755 --- a/web/nms.gathering.org/api/private/switch-add +++ b/web/nms.gathering.org/api/private/switch-add @@ -6,6 +6,7 @@ use DBI; use lib '../../../../include'; use nms; use nms::web qw(%get_params %json finalize_output get_input $dbh); +use nms::util qw(guess_placement); use strict; use warnings; use JSON; @@ -21,13 +22,32 @@ my @dups; my $sth = $nms::web::dbh->prepare("SELECT sysname FROM switches WHERE sysname=?"); -my @fields = ('ip', 'sysname', 'switchtype', 'last_updated', 'locked', 'poll_frequency', 'community', 'lldp_chassis_id', 'secondary_ip'); +my @fields = ('ip', 'sysname', 'switchtype', 'last_updated', 'locked', 'poll_frequency', 'community', 'lldp_chassis_id', 'secondary_ip', 'placement'); + +sub convertplace +{ + my %in = %{$_[0]}; + my %out = (); + + if (not defined $in{'x1'} and defined($in{'x'})) { + $out{'x1'} = int($in{'x'}); + $out{'y1'} = int($in{'y'}); + $out{'xx'} = int($in{'x'} + $in{'width'}); + $out{'yy'} = int($in{'y'} + $in{'height'}); + } else { + return \%in; + } + return \%out; +} foreach my $tmp2 (@tmp) { my %switch = %{$tmp2}; my $affected = 0; my %template = (); map { $template{$_} = 'DEFAULT' } @fields; + if (not defined($switch{'sysname'})) { + next; + } $sth->execute( $switch{'sysname'}); while ( my @row = $sth->fetchrow_array ) { @@ -35,27 +55,61 @@ foreach my $tmp2 (@tmp) { } if ($affected == 0) { + my %placement; + if (not defined ($switch{'placement'})) { + %placement = guess_placement($switch{'sysname'}); + } else { + %placement = %{convertplace($switch{'placement'})}; + } + if (not defined($switch{'ip'}) and defined($switch{'mgtmt4'})) { + $switch{'ip'} = $switch{'mgtmt4'}; + } + my ($x1,$x2,$y1,$y2); + $x1 = $placement{'x1'}; + $y1 = $placement{'y1'}; + $x2 = $placement{'xx'}; + $y2 = $placement{'yy'}; + $switch{'placement'} = "(($x1,$y1),($x2,$y2))"; + map { if (defined ($template{$_})) { - $template{$_} = $dbh->quote($switch{$_}); + $template{$_} = $dbh->quote($switch{$_}); } } keys %switch; + + + $nms::web::dbh->do("INSERT INTO SWITCHES (ip, sysname, switchtype, last_updated, locked, poll_frequency, community, lldp_chassis_id, secondary_ip, placement) VALUES ($template{'ip'}, $template{'sysname'}, $template{'switchtype'}, $template{'last_updated'}, $template{'locked'}, $template{'poll_frequency'}, $template{'community'}, $template{'lldp_chassis_id'}, $template{'secondary_ip'}, $template{'placement'});"); + push @added, $switch{'sysname'}; + } else { if (defined($switch{'placement'})) { + my %placement; + if ($switch{'placement'} eq "reset") { + %placement = guess_placement($switch{'sysname'}); + } else { + %placement = %{convertplace($switch{'placement'})}; + } my ($x1,$x2,$y1,$y2); - $x1 = $switch{'placement'}{'x1'}; - $y1 = $switch{'placement'}{'y1'}; - $x2 = $switch{'placement'}{'xx'}; - $y2 = $switch{'placement'}{'yy'}; - my $place = "(($x1,$y1),($x2,$y2))"; - $template{'placement'} = $place; + $x1 = $placement{'x1'}; + $y1 = $placement{'y1'}; + $x2 = $placement{'xx'}; + $y2 = $placement{'yy'}; + $switch{'placement'} = "(($x1,$y1),($x2,$y2))"; + push @dups, "not really, but: " . $switch{'placement'}; } - $nms::web::dbh->do("INSERT INTO SWITCHES (ip, sysname, switchtype, last_updated, locked, poll_frequency, community, lldp_chassis_id, secondary_ip) VALUES ($template{'ip'}, $template{'sysname'}, $template{'switchtype'}, $template{'last_updated'}, $template{'locked'}, $template{'poll_frequency'}, $template{'community'}, $template{'lldp_chassis_id'}, $template{'secondary_ip'});"); - push @added, $switch{'sysname'}; - } else { + if (not defined($switch{'ip'}) and defined($switch{'mgtmt4'})) { + $switch{'ip'} = $switch{'mgtmt4'}; + } + my @set; + map { + if (defined($template{$_})) { + push @set, "$_=" . $dbh->quote($switch{$_}); + } + } keys %switch; + $nms::web::dbh->do("UPDATE SWITCHES SET " . join(", ", @set) . "WHERE sysname=" . $dbh->quote($switch{'sysname'}) . ";"); push @dups, $switch{'sysname'}; } } $json{'switches_addded'} = \@added; -$json{'switches_duplicate'} = \@dups; +$json{'switches_updated'} = \@dups; finalize_output(); diff --git a/web/nms.gathering.org/api/private/switches-management b/web/nms.gathering.org/api/private/switches-management index 474f674..a598824 100755 --- a/web/nms.gathering.org/api/private/switches-management +++ b/web/nms.gathering.org/api/private/switches-management @@ -12,7 +12,7 @@ use Data::Dumper; $nms::web::cc{'max-age'} = "60"; -my $q2 = $nms::web::dbh->prepare('select sysname,ip,switchtype,poll_frequency,community,last_updated from switches '); +my $q2 = $nms::web::dbh->prepare('select sysname,ip,poll_frequency,community,last_updated from switches '); $q2->execute(); while (my $ref = $q2->fetchrow_hashref()) { diff --git a/web/nms.gathering.org/api/public/ping b/web/nms.gathering.org/api/public/ping index f713df1..f13a03b 100755 --- a/web/nms.gathering.org/api/public/ping +++ b/web/nms.gathering.org/api/public/ping @@ -22,4 +22,6 @@ while (my $ref = $lq->fetchrow_hashref()) { $q->execute(); +$nms::web::cc{'max-age'} = "1"; +$nms::web::cc{'stale-while-revalidate'} = "5"; finalize_output(); diff --git a/web/nms.gathering.org/api/public/switch-state b/web/nms.gathering.org/api/public/switch-state index e494e6d..999a1d8 100755 --- a/web/nms.gathering.org/api/public/switch-state +++ b/web/nms.gathering.org/api/public/switch-state @@ -41,4 +41,6 @@ while (my $ref = $qs->fetchrow_hashref()) { $nms::web::json{'switches'}{$ref->{'switch'}}{'latency_secondary'} = $ref->{'latency_ms'}; } +$nms::web::cc{'max-age'} = "5"; +$nms::web::cc{'stale-while-revalidate'} = "30"; finalize_output(); diff --git a/web/nms.gathering.org/api/public/switches b/web/nms.gathering.org/api/public/switches index d62169c..8447b2b 100755 --- a/web/nms.gathering.org/api/public/switches +++ b/web/nms.gathering.org/api/public/switches @@ -12,7 +12,7 @@ use Data::Dumper; $nms::web::cc{'max-age'} = "60"; -my $q2 = $nms::web::dbh->prepare('select switch,sysname,placement,ip,switchtype,poll_frequency,community,last_updated from switches'); +my $q2 = $nms::web::dbh->prepare('select switch,sysname,placement,ip,switchtype,poll_frequency,community,last_updated from switches where placement is not null'); $q2->execute(); while (my $ref = $q2->fetchrow_hashref()) { diff --git a/web/nms.gathering.org/index.html b/web/nms.gathering.org/index.html index 55cd095..9906612 100644 --- a/web/nms.gathering.org/index.html +++ b/web/nms.gathering.org/index.html @@ -70,18 +70,13 @@ <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="dropdown-header">Move switches</li> + <li><a href="#" onclick="nmsMap.moveSet(true);">Enable switch moving</a></li> + <li><a href="#" onclick="nmsMap.moveSet(false);">Disable switch moving</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> @@ -93,6 +88,12 @@ <button class="btn btn-default btn-xs" id="legend-4"></button> <button class="btn btn-default btn-xs" id="legend-5"></button> </div> + <div class="form-group"> + <div class="form-inline"> + <input id="searchbox" type="text" class="form-control" placeholder="Search" onchange="nmsInfoBox._search()" /> + <button class="btn" onclick="nmsInfoBox._search();">Search</button> + </div> + </div> </div> </li> </ul> @@ -107,113 +108,6 @@ <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">×</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">×</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"> @@ -337,177 +231,6 @@ </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">×</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->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">×</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">×</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">×</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> @@ -517,7 +240,7 @@ <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 id="inputCanvas" width="1920" height="1032" style="position: absolute; z-index: 60; cursor: pointer;" onmousedown="nmsMap.canvasClick(event)"> </canvas> <canvas id="hiddenCanvas" width="1000" height="10" style="display: none; position: absolute; z-index: 1000 "></canvas> @@ -526,6 +249,9 @@ </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-data.js"></script> + <script type="text/javascript" src="js/nms-map.js"></script> + <script type="text/javascript" src="js/nms-info-box.js"></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> diff --git a/web/nms.gathering.org/js/nms-color-util.js b/web/nms.gathering.org/js/nms-color-util.js index 965a10e..f50ee04 100644 --- a/web/nms.gathering.org/js/nms-color-util.js +++ b/web/nms.gathering.org/js/nms-color-util.js @@ -49,21 +49,22 @@ function getRandomColor() */ function drawGradient(gradients) { - var gradient = dr.hidden.ctx.createLinearGradient(0,0,1000,0); + var ctx = nmsMap._c.hidden.ctx; // FIXME: Move it away... + var gradient = ctx.createLinearGradient(0,0,1000,0); var stops = gradients.length - 1; nms.gradients = gradients; for (var color in gradients) { var i = color / stops; gradient.addColorStop(i, gradients[color]); } - dr.hidden.ctx.beginPath(); - dr.hidden.ctx.strokeStyle = gradient; - dr.hidden.ctx.moveTo(0,0); - dr.hidden.ctx.lineTo(1000,0); - dr.hidden.ctx.lineWidth = 10; - dr.hidden.ctx.closePath(); - dr.hidden.ctx.stroke(); - dr.hidden.ctx.moveTo(0,0); + ctx.beginPath(); + ctx.strokeStyle = gradient; + ctx.moveTo(0,0); + ctx.lineTo(1000,0); + ctx.lineWidth = 10; + ctx.closePath(); + ctx.stroke(); + ctx.moveTo(0,0); } /* @@ -83,7 +84,8 @@ function getColorStop(x) { * made generic. */ function getColor(x,y) { - var imageData = dr.hidden.ctx.getImageData(x, y, 1, 1); + var ctx = nmsMap._c.hidden.ctx; // FIXME: Move it away... + var imageData = ctx.getImageData(x, y, 1, 1); var data = imageData.data; if (data.length < 4) return false; diff --git a/web/nms.gathering.org/js/nms-data.js b/web/nms.gathering.org/js/nms-data.js new file mode 100644 index 0000000..ef41a3a --- /dev/null +++ b/web/nms.gathering.org/js/nms-data.js @@ -0,0 +1,247 @@ +"use strict"; + +/* + * This file/module/whatever is an attempt to gather all data collection in + * one place. + * + * The basic idea is to have all periodic data updates unified here, with + * stats, tracking of "ajax overflows" and general-purpose error handling + * and callbacks and whatnot, instead of all the custom stuff that we + * started out with. + * + * Sources are identified by a name, which is then available in + * nmsData[name] in full. A copy of the previous data set is kept in + * nmsData.old[name]. You can use getNow / setNow() to append a 'now=' + * string. + * + * nmsData[name] - actual data + * nmsData.old[name] - previous copy of data + * nmsData.registerSource() - add a source, will be polled periodicall + * nmsData.addHandler() + * nmsData.updateSource() - issue a one-off update, outside of whatever + * periodic polling might take place + * nmsData.invalidate() - Invalidate browser-cache. + */ + + +var nmsData = nmsData || { + old: {}, // Single copy of previous data. Automatically populated. + stats: { + identicalFetches:0, + outstandingAjaxRequests:0, + ajaxOverflow:0, + pollClearsEmpty:0, + pollClears:0, + pollSets:0, + newSource:0, + oldSource:0 + }, + /* + * The last time stamp of any data received, regardless of source. + * + * Used as a fallback for blank now, but can also be used to check + * "freshness", I suppose. + */ + _last: undefined, + _now: undefined, + + /* + * These are provided so we can introduce error checking when we + * have time. + * + * now() represents the data, not the intent. That means that if + * you want to check if we are traveling in time you should not + * check nmsData.now. That will always return a value as long as + * we've had a single piece of data. + */ + get now() { return this._now || this._last; }, + set now(val) { + if (val == undefined || !val) { + nmsData._now = undefined; + } else { + // FIXME: Check if now is valid syntax. + nmsData._now = val; + } + }, + /* + * List of sources, name, handler, etc + */ + _sources: {}, + + /* + * Maximum number of AJAX requests in transit before we start + * skipping updates. + * + * A problem right now is that it will typically always hit the + * same thing since everything starts at the same time... + */ + _ajaxThreshold: 10 +}; + + +nmsData._dropData = function (name) { + delete this[name]; + delete this.old[name]; +} + +nmsData.removeSource = function (name) { + if (this._sources[name] == undefined) { + this.stats.pollClearsEmpty++; + return true; + } + if (this._sources[name]['handle']) { + this.stats.pollClears++; + clearInterval(this._sources[name]['handle']); + } + delete this._sources[name]; +} + +/* + * Register a source. + * + * name: "Local" name. Maps to nmsData[name] + * target: URL of the source + * + * This can be called multiple times to add multiple handlers. There's no + * guarantee that they will be run in order, but right now they do. + * + * Update frequency _might_ be adaptive eventually, but since we only + * execute callbacks on change and backend sends cache headers, the browser + * will not issue actual HTTP requests. + * + * FIXME: Should be unified with nmsTimers() somehow. + */ +nmsData.registerSource = function(name, target) { + if (this._sources[name] == undefined) { + this._sources[name] = { target: target, cbs: {}, fresh: true }; + this._sources[name]['handle'] = setInterval(function(){nmsData.updateSource(name)}, 1000); + this.stats.newSource++; + } else { + this.stats.oldSource++; + } + + this.stats.pollSets++; +} + +/* + * Add a handler (callback) for a source, using an id. + * + * This is idempotent: if the id is the same, it will just overwrite the + * old id, not add a copy. + */ +nmsData.addHandler = function(name, id, cb, cbdata) { + var cbob = { + id: id, + name: name, + cb: cb, + fresh: true, + cbdata: cbdata + }; + if (id == undefined) { + return; + } + this._sources[name].cbs[id] = cbob; + this.updateSource(name); +} + +/* + * Unregister all handlers with the "id" for all sources. + * + * Mainly used to avoid fini() functions in the map handlers. E.g.: just + * reuse "mapHandler" as id. + */ +nmsData.unregisterHandlerWildcard = function(id) { + for (var v in nmsData._sources) { + this.unregisterHandler(v, id); + } +} + +nmsData.unregisterHandler = function(name, id) { + delete this._sources[name].cbs[id]; +} + +/* + * Updates a source. + * + * Called on interval, but can also be used to update a source after a + * known action that updates the underlying data (e.g: update comments + * after a comment is posted). + */ +nmsData.updateSource = function(name) { + this._genericUpdater(name, true); +} + +nmsData.invalidate = function(name) { + this._genericUpdater(name, false); +} +/* + * Reset a source, deleting all data, including old. + * + * Useful if traveling in time, for example. + */ +nmsData.resetSource = function(name) { + this[name] = {}; + this.old[name] = {}; + this.updateSource(name); +} + +/* + * Updates nmsData[name] and nmsData.old[name], issuing any callbacks where + * relevant. + * + * Do not use this directly. Use updateSource(). + * + */ +nmsData._genericUpdater = function(name, cacheok) { + if (this.stats.outstandingAjaxRequests++ > this._ajaxThreshold) { + this.stats.outstandingAjaxRequests--; + this.stats.ajaxOverflow++; + return; + } + var now = ""; + if (this._now != undefined) + now = "now=" + this._now; + if (now != "") { + if (this._sources[name].target.match("\\?")) + now = "&" + now; + else + now = "?" + now; + } + var heads = {}; + if (cacheok == false) { + heads['Cache-Control'] = "max-age=0, no-cache, stale-while-revalidate=0"; + } + + $.ajax({ + type: "GET", + headers: heads, + url: this._sources[name].target + now, + dataType: "json", + success: function (data, textStatus, jqXHR) { + if (nmsData[name] == undefined || nmsData[name]['hash'] != data['hash']) { + nmsData._last = data['time']; + nmsData.old[name] = nmsData[name]; + nmsData[name] = data; + nmsMap.drawNow(); + for (var i in nmsData._sources[name].cbs) { + var tmp = nmsData._sources[name].cbs[i]; + if (tmp.cb != undefined) { + tmp.cb(tmp.cbdata); + } + } + } else { + for (var i in nmsData._sources[name].cbs) { + var tmp = nmsData._sources[name].cbs[i]; + if (tmp.cb != undefined && tmp.fresh) { + nmsData._sources[name].cbs[i].fresh = false; + tmp.cb(tmp.cbdata); + } + } + nmsData.stats.identicalFetches++; + } + }, + complete: function(jqXHR, textStatus) { + nmsData.stats.outstandingAjaxRequests--; + } + }); +}; diff --git a/web/nms.gathering.org/js/nms-info-box.js b/web/nms.gathering.org/js/nms-info-box.js new file mode 100644 index 0000000..2ef4c3b --- /dev/null +++ b/web/nms.gathering.org/js/nms-info-box.js @@ -0,0 +1,333 @@ +"use strict"; + +/* + * Handle the info-box for switches (e.g.: what's shown when a switch is + * clicked). + * + * Interfaces: show(switch), hide(), click(switch). + */ + + +var nmsInfoBox = nmsInfoBox || { + stats: {}, + _showing:"" // Which switch we are displaying (if any). +} + +/* + * Show the infobox for a switch. + * + * Just a wrapper for _show, but adds a handler for comments. Could easily + * add a handler for other events too. E.g.: switches. + */ +nmsInfoBox.show = function(x) { + nmsData.addHandler("comments","switchshower",nmsInfoBox._show,x); + nmsData.addHandler("switches","switchshower",nmsInfoBox._show,x); + nmsData.addHandler("smanagement","switchshower",nmsInfoBox._show,x); + nmsInfoBox._show(x); +} + +/* + * Hide switch info-box and remove handler. + */ +nmsInfoBox.hide = function() { + nmsInfoBox._hide(); + nmsData.unregisterHandler("comments","switchshower"); + nmsData.unregisterHandler("switches","switchshower"); + nmsData.unregisterHandler("smanagement","switchshower"); +} + +/* + * Click a switch: If it's currently showing: hide it, otherwise display + * it. + */ +nmsInfoBox.click = function(sw) +{ + if (nmsInfoBox._showing == sw) + nmsInfoBox.hide(); + else + nmsInfoBox.show(sw); +} + +nmsInfoBox._hide = function() +{ + var swtop = document.getElementById("info-switch-parent"); + var switchele = document.getElementById("info-switch-table"); + var comments = document.getElementById("info-switch-comments-table"); + var commentbox; + if (switchele != undefined) + switchele.parentNode.removeChild(switchele); + if (comments != undefined) + comments.parentNode.removeChild(comments); + commentbox = document.getElementById("commentbox"); + if (commentbox != undefined) + commentbox.parentNode.removeChild(commentbox); + swtop.style.display = 'none'; + nmsInfoBox._showing = ""; + nmsInfoBox._editHide(); +} + +/* + * General-purpose table-maker? + * + * Takes an array of arrays as input, and an optional caption. + * + * E.g.: _makeTable([["name","Kjell"],["Age","five"]], "Age list"); + */ +nmsInfoBox._makeTable = function(content, caption) { + var table = document.createElement("table"); + var tr; + var td1; + var td2; + table.className = "table"; + table.classList.add("table"); + table.classList.add("table-condensed"); + if (caption != undefined) { + var cap = document.createElement("caption"); + cap.textContent = caption; + table.appendChild(cap); + } + for (var v in content) { + tr = table.insertRow(-1); + td1 = tr.insertCell(0); + td2 = tr.insertCell(1); + td1.innerHTML = content[v][0]; + td2.innerHTML = content[v][1]; + } + return table; +} + +/* + * Create and return a table for comments. + * + * Input is an array of comments. + */ +nmsInfoBox._makeCommentTable = function(content) { + var table = document.createElement("table"); + table.className = "table"; + table.classList.add("table"); + table.classList.add("table-condensed"); + var cap = document.createElement("caption"); + cap.textContent = "Comments" + table.appendChild(cap); + for (var commentid in content) { + var tr; + var td1; + var td2; + var comment = content[commentid]; + var col; + if (comment["state"] == "active") + col = "danger"; + else if (comment["state"] == "inactive") + col = false; + else + col = "info"; + tr = table.insertRow(-1); + tr.id = "commentRow" + comment["id"]; + tr.className = col; + + td1 = tr.insertCell(0); + td1.style.whiteSpace = "nowrap"; + td1.style.width = "8em"; + td2 = tr.insertCell(1); + var txt = '<div class="btn-group" role="group" aria-label="..."><button type="button" class="btn btn-xs btn-default" data-trigger="focus" data-toggle="popover" title="Info" data-content="Comment added ' + comment["time"] + " by user " + comment["username"] + ' and listed as ' + comment["state"] + '"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></button>'; + txt += '<button type="button" class="btn btn-xs btn-danger" data-trigger="focus" data-toggle="tooltip" title="Mark as deleted" onclick="commentDelete(' + comment["id"] + ');"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>'; + txt += '<button type="button" class="btn btn-xs btn-success" data-trigger="focus" data-toggle="tooltip" title="Mark as inactive/fixed" onclick="commentInactive(' + comment["id"] + ');"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>'; + txt += '<button type="button" class="btn btn-xs btn-info" data-trigger="focus" data-toggle="tooltip" title="Mark as persistent" onclick="commentPersist(' + comment["id"] + ');"><span class="glyphicon glyphicon-star" aria-hidden="true"></span></button></div>'; + td1.innerHTML = txt; + td2.innerHTML = comment["comment"]; + } + return table; +} + +/* + * FIXME: Not sure this belongs here, it's really part of the "Core" ui, + * not just the infobox. + */ +nmsInfoBox._search = function() { + var el = document.getElementById("searchbox"); + var id = false; + if (el) { + id = el.value; + } + if (id && nmsData.switches.switches[id] != undefined) { + nmsMap.setSwitchColor(id, "pink"); + el.parentElement.classList.remove("has-error"); + el.parentElement.classList.add("has-success"); + setTimeout(function(){ + nmsInfoBox.show(id); + var el = document.getElementById("searchbox"); + el.parentElement.classList.remove("has-success"); + },1000); + } else { + el.parentElement.classList.add("has-error"); + } +} + +/* + * Display info on switch "x" in the info-box + * + * Use nmsInfoBox.show(), otherwise changes wont be picked up. + */ +nmsInfoBox._show = function(x) +{ + var sw = nmsData.switches["switches"][x]; + var swm = nmsData.smanagement.switches[x]; + var swtop = document.getElementById("info-switch-parent"); + var swpanel = document.getElementById("info-switch-panel-body"); + var swtitle = document.getElementById("info-switch-title"); + var content = []; + + + nmsInfoBox._hide(); + nmsInfoBox._showing = x; + + swtitle.innerHTML = ' <button type="button" class="edit btn btn-xs btn-warning" onclick="nmsInfoBox._edit(\'' + x + '\');">Edit</button> ' + x + ' <button type="button" class="close" aria-label="Close" onclick="nmsInfoBox.hide();" style="float: right;"><span aria-hidden="true">×</span></button>'; + + for (var v in sw) { + if (v == "placement") { + var place = JSON.stringify(sw[v]); + content.push([v,place]); + continue; + } + content.push([v, sw[v]]); + } + + for (var v in swm) { + content.push([v, swm[v]]); + } + content.sort(); + + var comments = []; + if (nmsData.comments.comments != undefined && nmsData.comments.comments[x] != undefined) { + for (var c in nmsData.comments.comments[x]["comments"]) { + var comment = nmsData.comments.comments[x]["comments"][c]; + if (comment["state"] == "active" || comment["state"] == "persist" || comment["state"] == "inactive") { + comments.push(comment); + } + } + } + + var infotable = nmsInfoBox._makeTable(content); + infotable.id = "info-switch-table"; + swtop.appendChild(infotable); + if (comments.length > 0) { + var commenttable = nmsInfoBox._makeCommentTable(comments); + commenttable.id = "info-switch-comments-table"; + swtop.appendChild(commenttable); + $(function () { $('[data-toggle="popover"]').popover({placement:"top",continer:'body'}) }) + } + var commentbox = document.createElement("div"); + commentbox.id = "commentbox"; + commentbox.className = "panel-body"; + commentbox.style.width = "100%"; + commentbox.innerHTML = '<div class="input-group"><input type="text" class="form-control" placeholder="Comment" id="' + x + '-comment"><span class=\"input-group-btn\"><button class="btn btn-default" onclick="addComment(\'' + x + '\',document.getElementById(\'' + x + '-comment\').value); document.getElementById(\'' + x + '-comment\').value = \'\'; document.getElementById(\'' + x + '-comment\').placeholder = \'Comment added. Wait for next refresh.\';">Add comment</button></span></div>'; + swtop.appendChild(commentbox); + swtop.style.display = 'block'; +} + +nmsInfoBox._nullBlank = function(x) { + if (x == null || x == false || x == undefined) + return ""; + return x; +} + +nmsInfoBox._editHide = function() { + var container = document.getElementById("nmsInfoBox-edit-box"); + if (container != undefined) + container.parentNode.removeChild(container); +} + +nmsInfoBox._edit = function(sw) { + var template = {}; + var place = false; + nmsInfoBox._editHide(); + var container = document.createElement("div"); + container.id = "nmsInfoBox-edit-box"; + + nmsInfoBox._editValues = {}; + if (nmsData.switches.switches[sw] != undefined) { + for (var v in nmsData.switches.switches[sw]) { + if (v == "placement") { + place = JSON.stringify(nmsData.switches.switches[sw][v]); + template[v] = ""; + continue; + } + template[v] = this._nullBlank(nmsData.switches.switches[sw][v]); + } + } + if (nmsData.smanagement.switches[sw] != undefined) { + for (var v in nmsData.smanagement.switches[sw]) { + template[v] = this._nullBlank(nmsData.smanagement.switches[sw][v]); + } + } + var content = []; + for (v in template) { + var tmpsw = '\'' + sw + '\''; + var tmpv = '\'' + v + '\''; + var tmphandler = '"nmsInfoBox._editChange(' + tmpsw + ',' + tmpv + ');"'; + var html = "<input type=\"text\" class=\"form-control\" value=\"" + template[v] + "\" id=\"edit-"+ sw + "-" + v + '" onchange=' + tmphandler + ' oninput=' + tmphandler + '/>'; + content.push([v, html]); + } + var table = nmsInfoBox._makeTable(content, "edit"); + var swtop = document.getElementById("info-switch-parent"); + container.appendChild(table); + var submit = document.createElement("button"); + submit.innerHTML = "Save changes"; + submit.classList.add("btn", "btn-primary"); + submit.id = "edit-submit-" + sw; + submit.onclick = function(e) { nmsInfoBox._editSave(sw, e); }; + container.appendChild(submit); + var output = document.createElement("output"); + output.id = "edit-output"; + container.appendChild(output); + swtop.appendChild(container); + if (place) { + var pval = document.getElementById("edit-" + sw + "-placement"); + if (pval) { + pval.value = place; + } + } +} + +nmsInfoBox._editChange = function(sw, v) { + var el = document.getElementById("edit-" + sw + "-" + v); + var val = el.value; + if (v == "placement") { + try { + val = JSON.parse(val); + el.parentElement.classList.remove("has-error"); + el.parentElement.classList.add("has-success"); + } catch (e) { + el.parentElement.classList.add("has-error"); + return; + } + } + nmsInfoBox._editValues[v] = val; + el.classList.add("has-warning"); + var myData = nmsInfoBox._editStringify(sw); + var out = document.getElementById("edit-output"); + out.value = myData; +} + +nmsInfoBox._editStringify = function(sw) { + for (var key in nmsInfoBox._editValues) { + var val = nmsInfoBox._editValues[key]; + } + nmsInfoBox._editValues['sysname'] = sw; + var myData = JSON.stringify([nmsInfoBox._editValues]); + return myData; +} +nmsInfoBox._editSave = function(sw, e) { + var myData = nmsInfoBox._editStringify(sw); + $.ajax({ + type: "POST", + url: "/api/private/switch-add", + dataType: "text", + data:myData, + success: function (data, textStatus, jqXHR) { + nmsData.invalidate("switches"); + nmsData.invalidate("smanagement"); + } + }); + nmsInfoBox._editHide(); +} diff --git a/web/nms.gathering.org/js/nms-map-handlers.js b/web/nms.gathering.org/js/nms-map-handlers.js index eaa62fd..cb2dc45 100644 --- a/web/nms.gathering.org/js/nms-map-handlers.js +++ b/web/nms.gathering.org/js/nms-map-handlers.js @@ -2,8 +2,7 @@ * Map handlers/updaters for NMS. * * These are functions used to determine how the map should look in NMS. - * They represent vastly different information, but in a uniform way. I - * suppose this is the c++-type of object orientation... + * They represent vastly different information, but in a uniform way. * * The idea is that these updaters only parse information that's fetched by * NMS - they do not request additional information. E.g., ping data is @@ -11,6 +10,10 @@ * displayed. This might seem redundant, but it means any handler can * utilize information from any aspect of NMS, and thus opens NMS up to the * world of intelligent maps base don multiple data sources. + * + * Warning: This paradigm will change. Handlers will be expected to + * register their own callbacks for nmsData. Work in progress. + * */ /* @@ -257,7 +260,6 @@ function tempInit() function pingUpdater() { if (!nms.ping_data || !nms.ping_data["switches"]) { - resetColors(); return; } for (var sw in nms.switches_now["switches"]) { @@ -291,62 +293,74 @@ function commentUpdater() { var realnow = Date.now(); var now = Math.floor(realnow / 1000); - for (var sw in nms.switches_now["switches"]) { + if (nmsData.comments == undefined || nmsData.comments.comments == undefined) { + return + } + for (var sw in nmsData.switches.switches) { var c = "white"; - var s = nms.switches_now["switches"][sw]; - if (s["comments"] && s["comments"].length > 0) { - var then = 0; - var active = 0; - var persist = 0; - c = "yellow"; - for (var v in s["comments"]) { - var then_test = parseInt(s["comments"][v]["time"]); - if (then_test > then && s["comments"][v]["state"] != "inactive") - then = then_test; - if (s["comments"][v]["state"] == "active") { - active++; - } - if (s["comments"][v]["state"] == "persist") - persist++; - } - if (then > (now - (60*15))) { - c = red; - } else if (active > 0) { - c = orange; - } else if (persist > 0) { - c = blue; - } else { - c = green; + if (nmsData.comments.comments[sw] == undefined) { + nmsMap.setSwitchColor(sw,c); + continue; + } + var s = nmsData.comments.comments[sw]; + var then = 0; + var active = 0; + var persist = 0; + c = "yellow"; + for (var v in s["comments"]) { + var then_test = parseInt(s["comments"][v]["time"]); + if (then_test > then && s["comments"][v]["state"] != "inactive") + then = then_test; + if (s["comments"][v]["state"] == "active") { + active++; } + if (s["comments"][v]["state"] == "persist") + persist++; } - setSwitchColor(sw, c); + if (then > (now - (60*15))) { + c = red; + } else if (active > 0) { + c = orange; + } else if (persist > 0) { + c = blue; + } else { + c = green; + } + nmsMap.setSwitchColor(sw, c); } } function commentInit() { + nmsData.addHandler("comments","mapHandler",commentUpdater); setLegend(1,"white","0 comments"); setLegend(2,blue,"Persistent"); setLegend(3,red, "New"); setLegend(4,orange,"Active"); setLegend(5,green ,"Old/inactive only"); } + /* * Testing-function to randomize colors of linknets and switches */ function randomizeColors() { - for (var i in nms.switches_now.linknets) { +/* for (var i in nms.switches_now.linknets) { setLinknetColors(i, getRandomColor(), getRandomColor()); } - for (var sw in nms.switches_now.switches) { - setSwitchColor(sw, getRandomColor()); +*/ + if (nmsData.switches == undefined || nmsData.switches.switches == undefined) { + return; + } + for (var sw in nmsData.switches.switches) { + nmsMap.setSwitchColor(sw, getRandomColor()); } } function discoInit() { + nmsData.addHandler("switches","mapHandler",randomizeColors); setNightMode(true); setLegend(1,blue,"Y"); setLegend(2,red, "M"); diff --git a/web/nms.gathering.org/js/nms-map.js b/web/nms.gathering.org/js/nms-map.js new file mode 100644 index 0000000..b4f10b5 --- /dev/null +++ b/web/nms.gathering.org/js/nms-map.js @@ -0,0 +1,477 @@ +"use strict"; + +/* WORK + * IN + * PROGRESS + * + * Interface: + * + * nmsMap.init() - start things up + * nmsMap.setSwitchColor(switch,color) + * nmsMap.setSwitchInfo(switch,info) + */ + + +var nmsMap = nmsMap || { + _moveInProgress: false, + stats: { + earlyDrawAll:0, + colorChange:0, + colorSame:0, + resizeEvents:0, + switchInfoUpdate:0, + switchInfoSame:0, + nowDups:0, + nows:0 + }, + contexts: ["bg","link","blur","switch","text","textInfo","top","input","hidden"], + _info: {}, + _settings: { + fontLineFactor: 2, + textMargin: 3, + xMargin: 10, + yMargin: 20, + fontSize: 15, + fontFace: "sans-serif" + }, + scale: 1, + _orig: { width:1920, height:1032 }, + _canvas: { + get width() { return nmsMap.scale * nmsMap._orig.width; }, + get height() { return nmsMap.scale * nmsMap._orig.height; } + }, + + _color: { }, + _c: {} +} + +nmsMap.init = function() { + this._initContexts(); + this._drawBG(); + nmsData.addHandler("switches","nmsMap",function(){nmsMap._resizeEvent();}); + window.addEventListener('resize',nmsMap._resizeEvent,true); + document.addEventListener('load',nmsMap._resizeEvent,true); + this._drawAllSwitches(); +} + +nmsMap.setSwitchColor = function(sw, color) { + if (this._color[sw] != color) { + this._color[sw] = color; + this._drawSwitch(sw); + this.stats.colorChange++; + } else { + this.stats.colorSame++; + } +} + +nmsMap.reset = function() { + for (var sw in this._color) { + nmsMap.setSwitchColor(sw, undefined); + } + for (var sw in this._info) { + nmsMap.setSwitchInfo(sw, undefined); + } +} + +nmsMap.setSwitchInfo = function(sw,info) { + if (this._info[sw] != info) { + this._info[sw] = info; + this._drawSwitchInfo(sw); + this.stats.switchInfoUpdate++; + } else { + this.stats.switchInfoSame++; + } +} + +nmsMap._initContext = function(name) { + this._c[name] = {}; + this._c[name].c = document.getElementById(name + "Canvas"); + this._c[name].ctx = this._c[name].c.getContext('2d'); +} + +nmsMap._initContexts = function() { + for (var context in this.contexts) { + this._initContext(this.contexts[context]); + } +} + +nmsMap._resizeEvent = function() { + var width = window.innerWidth - nmsMap._c.bg.c.offsetLeft; + var height = window.innerHeight - nmsMap._c.bg.c.offsetTop; + + var xScale = (width / (nmsMap._orig.width + nmsMap._settings.xMargin)); + var yScale = (height / (nmsMap._orig.height + nmsMap._settings.yMargin)); + + if (xScale > yScale) { + nmsMap.scale = yScale; + } else { + nmsMap.scale = xScale; + } + for (var a in nmsMap._c) { + /* + * Resizing this to a too small size breaks gradients on smaller screens. + */ + if (a == 'hidden') + continue; + nmsMap._c[a].c.height = nmsMap._canvas.height; + nmsMap._c[a].c.width = nmsMap._canvas.width; + } + nmsMap._drawBG(); + nmsMap._drawAllSwitches(); + nmsMap.drawNow(); + nmsMap.stats.resizeEvents++; +} + +/* + * Draw current time-window + * + * FIXME: The math here is just wild approximation and guesswork because + * I'm lazy. + * + * FIXME: 2: Should really just use _drawText() instead somehow. Font size + * being an issue. + */ +nmsMap.drawNow = function () +{ + var now = nmsData.now; + if (nmsMap._lastNow == now) { + nmsMap.stats.nowDups++; + return; + } + nmsMap.stats.nows++; + + var ctx = nmsMap._c.top.ctx; + ctx.save(); + ctx.scale(this.scale, this.scale); + ctx.font = (2 * this._settings.fontSize) + "px " + this._settings.fontFace; + ctx.clearRect(0,0,800,100); + ctx.fillStyle = "white"; + ctx.strokeStyle = "black"; + ctx.lineWidth = nms.fontLineFactor; + ctx.strokeText(now, 0 + this._settings.textMargin, 25); + ctx.fillText(now, 0 + this._settings.textMargin, 25); + ctx.restore(); +} + +nmsMap.setNightMode = function(toggle) { + if (this._nightmode == toggle) + return; + this._nightmode = toggle; + nmsMap._drawBG(); +} + +nmsMap._drawBG = function() { + var imageObj = document.getElementById('source'); + this._c.bg.ctx.drawImage(imageObj, 0, 0, nmsMap._canvas.width, nmsMap._canvas.height); + if(this._nightmode) + nmsMap._invertBG(); +} + +nmsMap._invertBG = function() { + var imageData = this._c.bg.ctx.getImageData(0, 0, nmsMap._canvas.width, nmsMap._canvas.height); + var data = imageData.data; + + for(var i = 0; i < data.length; i += 4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + this._c.bg.ctx.putImageData(imageData, 0, 0); +} + +nmsMap._getBox = function(sw) { + var box = nmsData.switches.switches[sw]['placement']; + box.x = parseInt(box.x); + box.y = parseInt(box.y); + box.width = parseInt(box.width); + box.height = parseInt(box.height); + return box; +} + +nmsMap._drawSwitch = function(sw) +{ + // XXX: If a handler sets a color before switches are loaded... The + // color will get set fine so this isn't a problem. + if (nmsData.switches == undefined || nmsData.switches.switches == undefined) + return; + var box = this._getBox(sw); + var color = nmsMap._color[sw]; + if (color == undefined) { + color = blue; + } + this._c.switch.ctx.fillStyle = color; + this._drawBox(this._c.switch.ctx, box['x'],box['y'],box['width'],box['height']); + this._c.switch.ctx.shadowBlur = 0; + this._drawText(this._c.text.ctx, sw,box); +} + +nmsMap._drawSwitchInfo = function(sw) { + var box = this._getBox(sw); + if (this._info[sw] == undefined) { + this._clearBox(this._c.textInfo.ctx, box); + } else { + this._drawText(this._c.textInfo.ctx, this._info[sw], box, "right"); + } +} + +nmsMap._clearBox = function(ctx,box) { + ctx.save(); + ctx.scale(this.scale,this.scale); + ctx.clearRect(box['x'], box['y'], box['width'], box['height']); + ctx.restore(); +} + +nmsMap._drawText = function(ctx, text, box, align) { + var rotate = false; + + if ((box['width'] + 10 )< box['height']) + rotate = true; + + this._clearBox(ctx,box); + ctx.save(); + ctx.scale(this.scale, this.scale); + ctx.font = "bold " + this._settings.fontSize + "px " + this._settings.fontFace; + ctx.lineWidth = nmsMap._settings.fontLineFactor; + ctx.fillStyle = "white"; + ctx.strokeStyle = "black"; + ctx.translate(box.x + this._settings.textMargin, box.y + box.height - this._settings.textMargin); + + if (rotate) { + ctx.translate(box.width - this._settings.textMargin * 2,0); + ctx.rotate(Math.PI * 3/2); + } + + if (align == "right") { + ctx.textAlign = "right"; + /* + * Margin*2 is to compensate for the margin above. + */ + if (rotate) + ctx.translate(box.height - this._settings.textMargin*2,0); + else + ctx.translate(box.width - this._settings.textMargin*2,0); + } + + ctx.strokeText(text, 0, 0); + ctx.fillText(text, 0, 0); + ctx.restore(); +} + +nmsMap._drawAllSwitches = function() { + if (nmsData.switches == undefined) { + this.stats.earlyDrawAll++; + return; + } + for (var sw in nmsData.switches.switches) { + this._drawSwitch(sw); + } +} + +nmsMap._drawBox = function(ctx, x, y, boxw, boxh) { + ctx.save(); + ctx.scale(this.scale, this.scale); // FIXME + ctx.fillRect(x,y, boxw, boxh); + ctx.lineWidth = 1; + ctx.strokeStyle = "#000000"; + ctx.strokeRect(x,y, boxw, boxh); + ctx.restore(); +} + +nmsMap._connectSwitches = function(sw1, sw2, color1, color2) { + nmsMap._connectBoxes(this._getBox(sw1), this._getBox(sw2), + color1, color2); +} + +/* + * Draw a line between two boxes, with a gradient going from color1 to + * color2. + */ +nmsMap._connectBoxes = function(box1, box2,color1, color2) { + var ctx = nmsMap._c.link.ctx; + if (color1 == undefined) + color1 = blue; + if (color2 == undefined) + color2 = blue; + var x0 = Math.floor(box1.x + box1.width/2); + var y0 = Math.floor(box1.y + box1.height/2); + var x1 = Math.floor(box2.x + box2.width/2); + var y1 = Math.floor(box2.y + box2.height/2); + ctx.save(); + ctx.scale(nmsMap.scale, nmsMap.scale); + var gradient = ctx.createLinearGradient(x1,y1,x0,y0); + gradient.addColorStop(0, color1); + gradient.addColorStop(1, color2); + ctx.strokeStyle = gradient; + ctx.moveTo(x0,y0); + ctx.lineTo(x1,y1); + ctx.lineWidth = 5; + ctx.stroke(); + ctx.restore(); +} + +nmsMap.moveSet = function(toggle) { + nmsMap._moveInProgress = toggle; + if (!toggle) + nmsMap._moveStopListen(); +} + +/* + * onclick handler for the canvas. + * + * Currently just shows info for a switch. + */ +nmsMap.canvasClick = function(e) +{ + var sw = findSwitch(e.pageX - e.target.offsetLeft, e.pageY - e.target.offsetTop); + if (sw != undefined) { + if (nmsMap._moveInProgress) { + nmsMap._moveStart(sw, e); + } else { + nmsInfoBox.click(sw); + } + } +} + +nmsMap._clearOld = function(box) { + if (box) { + nmsMap._c.top.ctx.save(); + nmsMap._c.top.ctx.fillStyle = "#000000"; + nmsMap._c.top.ctx.scale(nmsMap.scale, nmsMap.scale); // FIXME + nmsMap._c.top.ctx.clearRect(box['x'] - 5, box['y'] - 5, box['width'] + 10, box['height'] + 10); + nmsMap._c.top.ctx.restore(); + } +} + +nmsMap._moveMove = function(e) { + nmsMap._moveX = (e.pageX - e.target.offsetLeft) / nmsMap.scale; + nmsMap._moveY = (e.pageY - e.target.offsetTop) / nmsMap.scale; + var diffx = nmsMap._moveX - nmsMap._moveXstart; + var diffy = nmsMap._moveY - nmsMap._moveYstart; + var box = {}; + nmsMap._clearOld(nmsMap._moveOldBox); + box['x'] = nmsMap._moveBox['x'] + diffx; + box['y'] = nmsMap._moveBox['y'] + diffy; + box['height'] = nmsMap._moveBox['height']; + box['width'] = nmsMap._moveBox['width']; + nmsMap._moveOldBox = box; + nmsMap._c.top.ctx.save(); + nmsMap._c.top.ctx.fillStyle = "red"; + nmsMap._drawBox(nmsMap._c.top.ctx, box['x'], box['y'], box['width'], box['height']); + nmsMap._c.top.ctx.restore(); +} + +nmsMap._moveSubmit = function() { + var data = { + sysname: nmsMap._moving, + placement: nmsMap._moveOldBox + } + var myData = JSON.stringify([data]); + $.ajax({ + type: "POST", + url: "/api/private/switch-add", + dataType: "text", + data:myData, + success: function (data, textStatus, jqXHR) { + nmsData.invalidate("switches"); + } + }); +} +nmsMap._moveStopListen = function() { + nmsMap._c.input.c.removeEventListener('mousemove',nmsMap._moveMove, true); + nmsMap._c.input.c.removeEventListener('mouseup',nmsMap._moveDone, true); +} + +nmsMap._moveDone = function(e) { + nmsMap._moveStopListen(); + if(nmsMap._moveOldBox == false) { + return; + } + nmsMap._moveSubmit(); + nmsMap._clearOld(nmsMap._moveOldBox); +} + +nmsMap._moveStart = function(sw, e) +{ + nmsMap._moving = sw; + nmsMap._moveOldBox = false; + nmsMap._moveXstart = (e.pageX - e.target.offsetLeft) / nmsMap.scale; + nmsMap._moveYstart = (e.pageY - e.target.offsetTop) / nmsMap.scale; + nmsMap._moveBox = nmsData.switches.switches[sw].placement; + nmsMap._c.input.c.addEventListener('mousemove',nmsMap._moveMove,true); + nmsMap._c.input.c.addEventListener('mouseup',nmsMap._moveDone,true); +} + + +/* + * STUFF NOT YET INTEGRATED, BUT MOVED AWAY FROM nms.js TO TIDY. + * + * Consider this a TODO list. + */ + + +/* + * Draw the blur for a box. + */ +function drawBoxBlur(x,y,boxw,boxh) +{ + var myX = Math.floor(x * canvas.scale); + var myY = Math.floor(y * canvas.scale); + var myX2 = Math.floor((boxw) * canvas.scale); + var myY2 = Math.floor((boxh) * canvas.scale); + dr.blur.ctx.fillRect(myX,myY, myX2, myY2); +} + +/* + * Draw a linknet with index i. + * + * XXX: Might have to change the index here to match backend + */ +function drawLinknet(i) +{ + var c1 = nms.linknet_color[i] && nms.linknet_color[i].c1 ? nms.linknet_color[i].c1 : blue; + var c2 = nms.linknet_color[i] && nms.linknet_color[i].c2 ? nms.linknet_color[i].c2 : blue; + if (nmsData.switches.switches[nmsData.switches.linknets[i].sysname1] && nmsData.switches.switches[nmsData.switches.linknets[i].sysname2]) { + connectSwitches(nmsData.switches.linknets[i].sysname1,nmsData.switches.linknets[i].sysname2, c1, c2); + } +} + +/* + * Draw all linknets + */ +function drawLinknets() +{ + if (nmsData.switches && nmsData.switches.linknets) { + for (var i in nmsData.switches.linknets) { + drawLinknet(i); + } + } +} + +/* + * Change both colors of a linknet. + * + * XXX: Probably have to change this to better match the backend data + */ +function setLinknetColors(i,c1,c2) +{ + if (!nms.linknet_color[i] || + nms.linknet_color[i].c1 != c1 || + nms.linknet_color[i].c2 != c2) { + if (!nms.linknet_color[i]) + nms.linknet_color[i] = {}; + nms.linknet_color[i]['c1'] = c1; + nms.linknet_color[i]['c2'] = c2; + drawLinknet(i); + } +} + +function applyBlur() +{ + var blur = document.getElementById("shadowBlur"); + var col = document.getElementById("shadowColor"); + nms.shadowBlur = blur.value; + nms.shadowColor = col.value; + resetBlur(); + saveSettings(); +} + diff --git a/web/nms.gathering.org/js/nms.js b/web/nms.gathering.org/js/nms.js index 87f0788..ec7ed06 100644 --- a/web/nms.gathering.org/js/nms.js +++ b/web/nms.gathering.org/js/nms.js @@ -1,73 +1,33 @@ +"use strict"; var nms = { - updater:undefined, // Active updater - update_time:0, // Client side timestamp for last update - switches_management:{switches:{}}, - switches_now:{switches:{}}, // Most recent data - switches_then:{switches:{}}, // 2 minutes old - speed:0, // Current aggregated speed - ping_data:undefined, // JSON data for ping history. - drawn:false, // Set to 'true' when switches are drawn - switch_showing:"", // Which switch we are displaying (if any). - switchInfo:{}, - switchInfoDrawn:{}, - repop_switch:false, // True if we need to repopulate the switch info when port state updates (e.g.: added comments); - repop_time:false, // Timestamp in case we get a cached result - nightMode:false, + stats:{}, // Various internal stats + get nightMode() { return this._nightMode; }, + set nightMode(val) { if (val != this._nightMode) { this._nightMode = val; setNightMode(val); } }, /* - * Switch-specific variables. These are currently separate from - * "switches_now" because switches_now is reset every time we get - * new data. + * FIXME: This should be slightly smarter. */ - nightBlur:{}, // Have we blurred this switch or not? - shadowBlur:10, - shadowColor:"#EEEEEE", - switch_color:{}, // Color for switch - linknet_color:{}, // color for linknet - textDrawn:{}, // Have we drawn text for this switch? - now:false, // Date we are looking at (false for current date). - fontSize:16, // This is scaled too, but 16 seems to make sense. - fontFace:"Verdana", - fontLineFactor:3, - /* - * This is used to track outbound AJAX requests and skip updates if - * we have too many outstanding requests. The ajaxOverflow is a - * counter that tracks how many times this has happened. - * - * It's a cheap way to be nice to the server. - */ - outstandingAjaxRequests:0, - ajaxOverflow:0, - /* - * Set to 'true' after we've done some basic updating. Used to - * bootstrap the map quickly as soon as we have enough data, then - * ignored. - */ - did_update:false, + _now: false, + get now() { return this._now }, + set now(v) { this._now = v; nmsData.now = v; }, /* * Various setInterval() handlers. See nmsTimer() for how they are * used. * - * Cool fact: Adding one here adds it to the 'debug timers' - * drop-down. + * FIXME: Should just stop using these. */ timers: { playback:false, - ports:false, - ping:false }, + menuShowing:true, /* * This is a list of nms[x] variables that we store in our * settings-cookie when altered and restore on load. */ settingsList:[ - 'shadowBlur', - 'shadowColor', 'nightMode', - 'menuShowing', - 'layerVisibility' + 'menuShowing' ], - layerVisibility:{}, keyBindings:{ '?':toggleMenu, 'n':toggleNightMode, @@ -83,19 +43,18 @@ var nms = { 'k':moveTimeFromKey, 'l':moveTimeFromKey, 'p':moveTimeFromKey, - 'r':moveTimeFromKey, - 'not-default':keyDebug + 'r':moveTimeFromKey }, - /* - * Playback controllers and variables - */ - playback:{ - startTime: false, - stopTime: false, - playing: false, - replayTime: 0, - replayIncrement: 60 * 60 - } + /* + * Playback controllers and variables + */ + playback:{ + startTime: false, + stopTime: false, + playing: false, + replayTime: 0, + replayIncrement: 60 * 60 + } }; /* @@ -133,96 +92,12 @@ function nmsTimer(handler, interval, name, description) { /* - * Drawing primitives. - * - * This contains both canvas and context for drawing layers. It's on a - * top-level namespace to reduce SLIGHTLY the ridiculously long names - * (e.g.: dr.bg.ctx.drawImage() is long enough....). - * - * Only initialized once (for now). - */ -var dr = {}; - -/* - * Original scale. This is just used to define the coordinate system. - * 1920x1032 was chosen for tg15 by coincidence: We scaled the underlying - * map down to "full hd" and these are the bounds we got. There's no - * particular reason this couldn't change, except it means re-aligning all - * switches. - */ -var orig = { - width:1920, - height:1032 - }; - -/* - * Canvas dimensions, and scale factor. - * - * We could derive scale factor from canvas.width / orig.width, but it's - * used so frequently that this makes much more sense. - * - * Width and height are rarely used. - */ -var canvas = { - width:0, - height:0, - scale:1 -}; - -/* - * Various margins at the sides. - * - * Not really used much, except for "text", which is really more of a - * padding than margin... - */ -var margin = { - x:10, - y:20, - text:3 -}; - -/* - * Convenience-function to populate the 'dr' structure. - * - * Only run once. - */ -function initDrawing() { - dr['bg'] = {}; - dr['bg']['c'] = document.getElementById("bgCanvas"); - dr['bg']['ctx'] = dr['bg']['c'].getContext('2d'); - dr['link'] = {}; - dr['link']['c'] = document.getElementById("linkCanvas"); - dr['link']['ctx'] = dr['link']['c'].getContext('2d'); - dr['blur'] = {}; - dr['blur']['c'] = document.getElementById("blurCanvas"); - dr['blur']['ctx'] = dr['blur']['c'].getContext('2d'); - dr['switch'] = {}; - dr['switch']['c'] = document.getElementById("switchCanvas"); - dr['switch']['ctx'] = dr['switch']['c'].getContext('2d'); - dr['text'] = {}; - dr['text']['c'] = document.getElementById("textCanvas"); - dr['text']['ctx'] = dr['text']['c'].getContext('2d'); - dr['textInfo'] = {}; - dr['textInfo']['c'] = document.getElementById("textInfoCanvas"); - dr['textInfo']['ctx'] = dr['textInfo']['c'].getContext('2d'); - dr['top'] = {}; - dr['top']['c'] = document.getElementById("topCanvas"); - dr['top']['ctx'] = dr['top']['c'].getContext('2d'); - dr['input'] = {}; - dr['input']['c'] = document.getElementById("inputCanvas"); - dr['input']['ctx'] = dr['top']['c'].getContext('2d'); - dr['hidden'] = {}; - dr['hidden']['c'] = document.getElementById("hiddenCanvas"); - dr['hidden']['ctx'] = dr['hidden']['c'].getContext('2d'); -} - -/* * Convenience function that doesn't support huge numbers, and it's easier * to comment than to fix. But not really, but I'm not fixing it anyway. */ function byteCount(bytes) { var units = ['', 'K', 'M', 'G', 'T', 'P']; - i = 0; + var i = 0; while (bytes > 1024) { bytes = bytes / 1024; i++; @@ -236,7 +111,7 @@ function byteCount(bytes) { */ function toggleNightMode() { - setNightMode(!nms.nightMode); + nms.nightMode = !nms.nightMode; saveSettings(); } @@ -309,76 +184,82 @@ function epochToString(t) return str; } + function localEpochToString(t) { - var d = new Date(parseInt(t) * parseInt(1000)); - var timezoneOffset = d.getTimezoneOffset() * -60; - t = t + timezoneOffset; + var d = new Date(parseInt(t) * parseInt(1000)); + var timezoneOffset = d.getTimezoneOffset() * -60; + t = t + timezoneOffset; - return epochToString(t); + return epochToString(t); } /* * Start replaying historical data. */ nms.playback.startReplay = function(startTime,stopTime) { - if(!startTime || !stopTime) - return false; + if(!startTime || !stopTime) + return false; - nms.playback.pause(); - nms.playback.startTime = stringToEpoch(startTime); - nms.playback.stopTime = stringToEpoch(stopTime); - nms.now = epochToString(nms.playback.startTime); - nms.playback.play(); + nms.playback.pause(); + nms.playback.startTime = stringToEpoch(startTime); + nms.playback.stopTime = stringToEpoch(stopTime); + nms.now = epochToString(nms.playback.startTime); + nms.playback.play(); } + /* * Pause playback */ nms.playback.pause = function() { - nms.timers.playback.stop(); - nms.playback.playing = false; + nms.timers.playback.stop(); + nms.playback.playing = false; } + /* * Start playback */ nms.playback.play = function() { - nms.playback.tick(); - nms.timers.playback.start(); - nms.playback.playing = true; + nms.playback.tick(); + nms.timers.playback.start(); + nms.playback.playing = true; } + /* * Toggle playback */ nms.playback.toggle = function() { - if(nms.playback.playing) { - nms.playback.pause(); - } else { - nms.playback.play(); - } + if(nms.playback.playing) { + nms.playback.pause(); + } else { + nms.playback.play(); + } } + /* * Jump to place in time */ nms.playback.setNow = function(now) { - resetSwitchStates(); - now = parseNow(now); - nms.now = now; + var now = parseNow(now); + nms.now = now; - nms.playback.stopTime = false; - nms.playback.startTime = false; - nms.playback.tick(); + nms.playback.stopTime = false; + nms.playback.startTime = false; + nms.playback.tick(); } + /* * Step forwards or backwards in timer */ nms.playback.stepTime = function(n) { - now = getNowEpoch(); - newtime = parseInt(now) + parseInt(n); - nms.now = epochToString(parseInt(newtime)); + var now = getNowEpoch(); + var newtime = parseInt(now) + parseInt(n); + nms.now = epochToString(parseInt(newtime)); - if(!nms.playback.playing) - nms.playback.tick(); + if(!nms.playback.playing) + nms.playback.tick(); } + /* * Ticker to trigger updates, and advance time if replaying * @@ -386,214 +267,35 @@ nms.playback.stepTime = function(n) */ nms.playback.tick = function() { - nms.playback.replayTime = getNowEpoch(); + nms.playback.replayTime = getNowEpoch(); - // If outside start-/stopTime, remove limits and pause playback - if (nms.playback.stopTime && (nms.playback.replayTime >= nms.playback.stopTime || nms.playback.replayTime < nms.playback.startTime)) { - nms.playback.stopTime = false; - nms.playback.startTime = false; - nms.playback.pause(); - return; - } - - // If past actual datetime, go live - if (nms.playback.replayTime > parseInt(Date.now() / 1000)) { - nms.now = false; - } + // If outside start-/stopTime, remove limits and pause playback + if (nms.playback.stopTime && (nms.playback.replayTime >= nms.playback.stopTime || nms.playback.replayTime < nms.playback.startTime)) { + nms.playback.stopTime = false; + nms.playback.startTime = false; + nms.playback.pause(); + return; + } - // If we are still replaying, advance time - if(nms.now !== false && nms.playback.playing) { - nms.playback.stepTime(nms.playback.replayIncrement); - } + // If past actual datetime, go live + if (nms.playback.replayTime > parseInt(Date.now() / 1000)) { + nms.now = false; + } - // Update data and force redraw - updatePorts(); - updatePing(); - nms.updater.updater(); + // If we are still replaying, advance time + if(nms.now !== false && nms.playback.playing) { + nms.playback.stepTime(nms.playback.replayIncrement); + } } + /* * Helper function for safely getting a valid now-epoch */ function getNowEpoch() { - if (nms.now && nms.now != 0) - return stringToEpoch(nms.now); - else - return parseInt(Date.now() / 1000); -} - - -/* - * Hide switch info-box - */ -function hideSwitch() -{ - var swtop = document.getElementById("info-switch-parent"); - var switchele = document.getElementById("info-switch-table"); - var comments = document.getElementById("info-switch-comments-table"); - if (switchele != undefined) - switchele.parentNode.removeChild(switchele); - if (comments != undefined) - comments.parentNode.removeChild(comments); - commentbox = document.getElementById("commentbox"); - if (commentbox != undefined) - commentbox.parentNode.removeChild(commentbox); - swtop.style.display = 'none'; - nms.switch_showing = ""; -} -/* - * Display info on switch "x" in the info-box - * - * FIXME: THIS IS A MONSTROSITY. - */ -function showSwitch(x) -{ - var sw = nms.switches_now["switches"][x]; - var swm = nms.switches_management["switches"][x]; - var swtop = document.getElementById("info-switch-parent"); - var swpanel = document.getElementById("info-switch-panel-body"); - var swtitle = document.getElementById("info-switch-title"); - var tr; - var td1; - var td2; - - hideSwitch(); - nms.switch_showing = x; - document.getElementById("aboutBox").style.display = "none"; - var switchele = document.createElement("table"); - switchele.id = "info-switch-table"; - switchele.className = "table"; - switchele.classList.add("table"); - switchele.classList.add("table-condensed"); - - swtitle.innerHTML = x + '<button type="button" class="close" aria-labe="Close" onclick="hideSwitch();" style="float: right;"><span aria-hidden="true">×</span></button>'; - var speed = 0; - var speed2 = 0; - for (port in nms.switches_now["switches"][x]["ports"]) { - if (nms.switches_now["switches"][x]["ports"] == undefined || - nms.switches_then["switches"][x]["ports"] == undefined) { - continue; - } - if (/ge-0\/0\/44$/.exec(port) || - /ge-0\/0\/45$/.exec(port) || - /ge-0\/0\/46$/.exec(port) || - /ge-0\/0\/47$/.exec(port)) - { - var t = nms.switches_then["switches"][x]["ports"][port]; - var n = nms.switches_now["switches"][x]["ports"][port]; - speed += (parseInt(t["ifhcoutoctets"]) - parseInt(n["ifhcoutoctets"])) / (parseInt(t["time"] - n["time"])); - speed2 += (parseInt(t["ifhcinoctets"]) - parseInt(n["ifhcinoctets"])) / (parseInt(t["time"] - n["time"])); - } - } - - tr = switchele.insertRow(-1); - td1 = tr.insertCell(0); - td2 = tr.insertCell(1); - td1.innerHTML = "Uplink speed (out , port 44-47)"; - td2.innerHTML = byteCount(8 * speed) + "b/s"; - - tr = switchele.insertRow(-1); - td1 = tr.insertCell(0); - td2 = tr.insertCell(1); - td1.title = "Port 44, 45, 46 and 47 are used as uplinks."; - td1.innerHTML = "Uplink speed (in , port 44-47)"; - td2.innerHTML = byteCount(8 * speed2) + "b/s"; - - speed = 0; - for (port in nms.switches_now["switches"][x]["ports"]) { - if (nms.switches_now["switches"][x]["ports"] == undefined || - nms.switches_then["switches"][x]["ports"] == undefined) { - continue; - } - var t = nms.switches_then["switches"][x]["ports"][port]; - var n = nms.switches_now["switches"][x]["ports"][port]; - speed += (parseInt(t["ifhcinoctets"]) -parseInt(n["ifhcinoctets"])) / (parseInt(t["time"] - n["time"])); - } - - tr = switchele.insertRow(-1); - td1 = tr.insertCell(0); - td2 = tr.insertCell(1); - td1.innerHTML = "Total speed (in)"; - td2.innerHTML = byteCount(8 * speed) + "b/s"; - - speed = 0; - for (port in nms.switches_now["switches"][x]["ports"]) { - if (nms.switches_now["switches"][x]["ports"] == undefined || - nms.switches_then["switches"][x]["ports"] == undefined) { - continue; - } - var t = nms.switches_then["switches"][x]["ports"][port]; - var n = nms.switches_now["switches"][x]["ports"][port]; - speed += (parseInt(t["ifhcoutoctets"]) -parseInt(n["ifhcoutoctets"])) / (parseInt(t["time"] - n["time"])); - } - - tr = switchele.insertRow(-1); - td1 = tr.insertCell(0); - td2 = tr.insertCell(1); - td1.innerHTML = "Total speed (out)"; - td2.innerHTML = byteCount(8 * speed) + "b/s"; - - for (v in sw) { - tr = switchele.insertRow(-1); - td1 = tr.insertCell(0); - td2 = tr.insertCell(1); - td1.innerHTML = v; - td2.innerHTML = sw[v]; - } - for (v in swm) { - tr = switchele.insertRow(-1); - td1 = tr.insertCell(0); - td2 = tr.insertCell(1); - td1.innerHTML = v; - td2.innerHTML = swm[v]; - } - - comments = document.createElement("table"); - comments.id = "info-switch-comments-table"; - comments.className = "table table-condensed"; - var cap = document.createElement("caption"); - cap.textContent = "Comments"; - comments.appendChild(cap); - - var has_comment = false; - for (var c in sw["comments"]) { - var comment = sw["comments"][c]; - has_comment = true; - if (comment["state"] == "active" || comment["state"] == "persist" || comment["state"] == "inactive") { - tr = comments.insertRow(-1); - var col; - if (comment["state"] == "active") - col = "danger"; - else if (comment["state"] == "inactive") - col = false; - else - col = "info"; - tr.className = col; - tr.id = "commentRow" + comment["id"]; - td1 = tr.insertCell(0); - td2 = tr.insertCell(1); - td1.style.whiteSpace = "nowrap"; - td1.style.width = "8em"; - var txt = '<div class="btn-group" role="group" aria-label="..."><button type="button" class="btn btn-xs btn-default" data-trigger="focus" data-toggle="popover" title="Info" data-content="Comment added ' + epochToString(comment["time"]) + " by user " + comment["username"] + ' and listed as ' + comment["state"] + '"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></button>'; - txt += '<button type="button" class="btn btn-xs btn-danger" data-trigger="focus" data-toggle="tooltip" title="Mark as deleted" onclick="commentDelete(' + comment["id"] + ');"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>'; - txt += '<button type="button" class="btn btn-xs btn-success" data-trigger="focus" data-toggle="tooltip" title="Mark as inactive/fixed" onclick="commentInactive(' + comment["id"] + ');"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>'; - txt += '<button type="button" class="btn btn-xs btn-info" data-trigger="focus" data-toggle="tooltip" title="Mark as persistent" onclick="commentPersist(' + comment["id"] + ');"><span class="glyphicon glyphicon-star" aria-hidden="true"></span></button></div>'; - td1.innerHTML = txt; - td2.textContent = comment['comment']; - } - } - - swtop.appendChild(switchele); - if (has_comment) { - swtop.appendChild(comments); - $(function () { $('[data-toggle="popover"]').popover({placement:"top",continer:'body'}) }) - } - var commentbox = document.createElement("div"); - commentbox.id = "commentbox"; - commentbox.className = "panel-body"; - commentbox.style.width = "100%"; - commentbox.innerHTML = '<div class="input-group"><input type="text" class="form-control" placeholder="Comment" id="' + x + '-comment"><span class=\"input-group-btn\"><button class="btn btn-default" onclick="addComment(\'' + x + '\',document.getElementById(\'' + x + '-comment\').value); document.getElementById(\'' + x + '-comment\').value = \'\'; document.getElementById(\'' + x + '-comment\').placeholder = \'Comment added. Wait for next refresh.\';">Add comment</button></span></div>'; - swtop.appendChild(commentbox); - swtop.style.display = 'block'; + if (nms.now && nms.now != 0) + return stringToEpoch(nms.now); + else + return parseInt(Date.now() / 1000); } /* @@ -611,180 +313,25 @@ function setLegend(x,color,name) el.textContent = name; } -function updateAjaxInfo() -{ - var out = document.getElementById('outstandingAJAX'); - var of = document.getElementById('overflowAJAX'); - out.textContent = nms.outstandingAjaxRequests; - of.textContent = nms.ajaxOverflow; -} -/* - * Run periodically to trigger map updates when a handler is active - */ -function updateMap() -{ - /* - * XXX: This a bit hacky: There are a bunch of links that use - * href="#foo" but probably shouldn't. This breaks refresh since we - * base this on the location hash. To circumvent that issue - * somewhat, we just update the location hash if it's not - * "correct". - */ - if (nms.updater) { - if (document.location.hash != ('#' + nms.updater.tag)) { - document.location.hash = nms.updater.tag; - } - } - if (!newerSwitches()) - return; - if (!nms.drawn) - setScale(); - if (!nms.drawn) - return; - if (!nms.ping_data) - return; - nms.update_time = Date.now(); - - if (nms.updater != undefined && nms.switches_now && nms.switches_then) { - nms.updater.updater(); - } - drawNow(); -} - /* * Change map handler (e.g., change from uplink map to ping map) */ function setUpdater(fo) { - nms.updater = undefined; - resetColors(); - resetTextInfo(); + nmsMap.reset(); + nmsData.unregisterHandlerWildcard("mapHandler"); fo.init(); - nms.updater = fo; var foo = document.getElementById("updater_name"); foo.innerHTML = fo.name + " "; document.location.hash = fo.tag; - initialUpdate(); - if (nms.ping_data && nms.switches_then && nms.switches_now) { - nms.updater.updater(); - } -} - -/* - * Helper function for updating switch-data without overwriting existing - * data with non-existent data - */ -function updateSwitches(switchdata,target) { - target['time'] = switchdata['time'] //Assume we always get time - - if(switchdata.switches != undefined) { - for(var sw in switchdata.switches) { - if(switchdata.switches[sw]['management'] != undefined) - updateSwitchProperty(sw,'management',switchdata.switches[sw]['management'],target); - if(switchdata.switches[sw]['ports'] != undefined) - updateSwitchProperty(sw,'ports',switchdata.switches[sw]['ports'],target); - if(switchdata.switches[sw]['temp'] != undefined) - updateSwitchProperty(sw,'temp',switchdata.switches[sw]['temp'],target); - if(switchdata.switches[sw]['temp_time'] != undefined) - updateSwitchProperty(sw,'temp_time',switchdata.switches[sw]['temp_time'],target); - if(switchdata.switches[sw]['placement'] != undefined) - updateSwitchProperty(sw,'placement',switchdata.switches[sw]['placement'],target); - } - } -} -/* - * Helper function for updating a limited subset of switch properties, - * while the current state of the switch data is unknown. - */ -function updateSwitchProperty(sw,property,data,target) { - if(target.switches[sw] == undefined) - target.switches[sw] = {}; - - target.switches[sw][property] = data; -} - -/* - * Helper function for reseting switch state data (and keeping more permanent data) - */ -function resetSwitchStates() { - for (var sw in nms.switches_now.switches) { - for (var property in nms.switches_now.switches[sw]) { - if (['ports','temp','temp_time'].indexOf(property) > -1) { - nms.switches_now.switches[sw][property] = undefined; - } - } - } -} - -/* - * Convenience function to avoid waiting for pollers when data is available - * for the first time. - */ -function initialUpdate() -{ - return; - if (nms.ping_data && nms.switches_then && nms.switches_now && nms.updater != undefined && nms.did_update == false ) { - resizeEvent(); - if (!nms.drawn) { - drawSwitches(); - drawLinknets(); - } - nms.updater.updater(); - nms.did_update = true; - } -} - -function resetBlur() -{ - nms.nightBlur = {}; - dr.blur.ctx.clearRect(0,0,canvas.width,canvas.height); - drawSwitches(); } -function applyBlur() -{ - var blur = document.getElementById("shadowBlur"); - var col = document.getElementById("shadowColor"); - nms.shadowBlur = blur.value; - nms.shadowColor = col.value; - resetBlur(); - saveSettings(); -} - -function showBlurBox() -{ - var blur = document.getElementById("shadowBlur"); - var col = document.getElementById("shadowColor"); - blur.value = nms.shadowBlur; - col.value = nms.shadowColor; - document.getElementById("blurManic").style.display = ''; -} -/* - * Update nms.ping_data - */ -function updatePing() -{ - var now = nms.now ? ("?now=" + nms.now) : ""; - if (nms.outstandingAjaxRequests > 5) { - nms.ajaxOverflow++; - updateAjaxInfo(); - return; - } - nms.outstandingAjaxRequests++; - $.ajax({ - type: "GET", - url: "/api/public/ping" + now, - dataType: "text", - success: function (data, textStatus, jqXHR) { - nms.ping_data = JSON.parse(data); - initialUpdate(); - updateMap(); - }, - complete: function(jqXHR, textStatus) { - nms.outstandingAjaxRequests--; - updateAjaxInfo(); - } - }); +function toggleLayer(layer) { + var l = document.getElementById(layer); + if (l.style.display == 'none') + l.style.display = ''; + else + l.style.display = 'none'; } function commentInactive(id) @@ -815,18 +362,14 @@ function commentChange(id,state) comment:id, state:state }; - var foo = document.getElementById("commentRow" + id); - if (foo) { - foo.className = ''; - foo.style.backgroundColor = "silver"; - } + myData = JSON.stringify(myData); $.ajax({ type: "POST", url: "/api/private/comment-change", dataType: "text", data:myData, success: function (data, textStatus, jqXHR) { - nms.repop_switch = true; + nmsData.invalidate("comments"); } }); } @@ -837,396 +380,19 @@ function addComment(sw,comment) switch:sw, comment:comment }; + myData = JSON.stringify(myData); $.ajax({ type: "POST", url: "/api/private/comment-add", dataType: "text", data:myData, success: function (data, textStatus, jqXHR) { - nms.repop_switch = true; + nmsData.invalidate("comments"); } }); } -/* - * Update nms.switches_management - * - * FIXME: This isn't actually called from anywhere, only console at the - * moment. - */ -function updateSwitchManagement() -{ - if (nms.outstandingAjaxRequests > 5) { - nms.ajaxOverflow++; - updateAjaxInfo(); - return; - } - nms.outstandingAjaxRequests++; - $.ajax({ - type: "GET", - url: "/api/private/switches-management" , - dataType: "text", - success: function (data, textStatus, jqXHR) { - var switchdata = JSON.parse(data); - nms.switches_management = switchdata; - }, - complete: function(jqXHR, textStatus) { - nms.outstandingAjaxRequests--; - updateAjaxInfo(); - } - }); -} - -/* - * Update nms.switches_now and nms.switches_then - */ -function updatePorts() -{ - var now = ""; - if (nms.outstandingAjaxRequests > 5) { - nms.ajaxOverflow++; - updateAjaxInfo(); - return; - } - nms.outstandingAjaxRequests++; - if (nms.now != false) - now = "?now=" + nms.now; - $.ajax({ - type: "GET", - url: "/api/private/port-state"+ now , - dataType: "text", - success: function (data, textStatus, jqXHR) { - var switchdata = JSON.parse(data); - updateSwitches(switchdata,nms.switches_now); - initialUpdate(); - updateSpeed(); - updateMap(); - if (nms.repop_time == false && nms.repop_switch) - nms.repop_time = nms.switches_now.time; - else if (nms.repop_switch && nms.switch_showing && nms.repop_time != nms.switches_now.time) { - showSwitch(nms.switch_showing,true); - nms.repop_switch = false; - nms.repop_time = false; - } - }, - complete: function(jqXHR, textStatus) { - nms.outstandingAjaxRequests--; - updateAjaxInfo(); - } - }); - nms.outstandingAjaxRequests++; - $.ajax({ - type: "GET", - url: "/api/public/switches"+ now , - dataType: "text", - success: function (data, textStatus, jqXHR) { - var switchdata = JSON.parse(data); - updateSwitches(switchdata,nms.switches_now); - parseIntPlacements(); - }, - complete: function(jqXHR, textStatus) { - nms.outstandingAjaxRequests--; - updateAjaxInfo(); - } - }); - now=""; - if (nms.now != false) - now = "?now=" + nms.now; - nms.outstandingAjaxRequests++; - updateAjaxInfo(); - $.ajax({ - type: "GET", - url: "/api/private/port-state" + now, - dataType: "text", - success: function (data, textStatus, jqXHR) { - var switchdata = JSON.parse(data); - updateSwitches(switchdata,nms.switches_then); - initialUpdate(); - updateSpeed(); - updateMap(); - }, - complete: function(jqXHR, textStatus) { - nms.outstandingAjaxRequests--; - updateAjaxInfo(); - } - }) -} - -/* - * Returns true if we have now and then-data for switches and that the - * "now" is actually newer. Useful for basic sanity and avoiding negative - * values when rewinding time. - */ -function newerSwitches() -{ - if (nms.switches_now.time == undefined || nms.switches_then.time == undefined) - return false; - var now_timestamp = stringToEpoch(nms.switches_now.time); - var then_timestamp = stringToEpoch(nms.switches_then.time); - if (now_timestamp == 0 || then_timestamp == 0 || then_timestamp >= now_timestamp) - return false; - return true; -} -/* - * Use nms.switches_now and nms.switches_then to update 'nms.speed'. - * - * nms.speed is a total of ifHCInOctets across all client-interfaces - * nms.speed_full is a total of for /all/ interfaces. - * - * This is run separate of updatePorts mainly for historic reasons, but - * if it was added to the tail end of updatePorts, there'd have to be some - * logic to ensure it was run after both requests. Right now, it's just - * equally wrong for both scenarios, not consistently wrong (or something). - * - * FIXME: Err, yeah, add this to the tail-end of updatePorts instead :D - * - */ - -function updateSpeed() -{ - var speed_in = parseInt(0); - var speed_full = parseInt(0); - var counter=0; - var sw; - var speedele = document.getElementById("speed"); - if (!newerSwitches()) - return; - for (sw in nms.switches_now["switches"]) { - for (port in nms.switches_now["switches"][sw]["ports"]) { - if (!nms.switches_now["switches"][sw]["ports"][port]) { - console.log("ops"); - continue; - } - if (!nms.switches_then || !nms.switches_then["switches"] || !nms.switches_then["switches"][sw] || !nms.switches_then["switches"][sw]["ports"]) { - continue; - } - if (!nms.switches_now || !nms.switches_now["switches"] || !nms.switches_now["switches"][sw] || !nms.switches_now["switches"][sw]["ports"]) { - continue; - } - - if (!nms.switches_then["switches"][sw]["ports"][port]) { - console.log("ops"); - continue; - } - var diff = parseInt(parseInt(nms.switches_now["switches"][sw]["ports"][port]["time"]) - parseInt(nms.switches_then["switches"][sw]["ports"][port]["time"])); - var then = parseInt(nms.switches_then["switches"][sw]["ports"][port]["ifhcinoctets"]) ; - var now = parseInt(nms.switches_now["switches"][sw]["ports"][port]["ifhcinoctets"]) ; - var diffval = (now - then); - if (then == 0 || now == 0 || diffval == 0 || diffval == NaN) { - continue; - } - speed_full += parseInt(diffval/diff); - if (( /e\d-\d/.exec(sw) || /e\d\d-\d/.exec(sw)) && ( /ge-\d\/\d\/\d$/.exec(port) || /ge-\d\/\d\/\d\d$/.exec(port))) { - if (!( - /ge-0\/0\/44$/.exec(port) || - /ge-0\/0\/45$/.exec(port) || - /ge-0\/0\/46$/.exec(port) || - /ge-0\/0\/47$/.exec(port))) { - speed_in += parseInt(diffval/diff) ; - counter++; - } - } - } - } - nms.speed = speed_in; - nms.speed_full = speed_full; - if (speedele) { - speedele.innerHTML = byteCount(8 * parseInt(nms.speed)) + "b/s"; - speedele.innerHTML += " / " + byteCount(8 * parseInt(nms.speed_full)) + "b/s"; - - } -} - -/* - * Draw a linknet with index i. - * - * XXX: Might have to change the index here to match backend - */ -function drawLinknet(i) -{ - var c1 = nms.linknet_color[i] && nms.linknet_color[i].c1 ? nms.linknet_color[i].c1 : blue; - var c2 = nms.linknet_color[i] && nms.linknet_color[i].c2 ? nms.linknet_color[i].c2 : blue; - if (nms.switches_now.switches[nms.switches_now.linknets[i].sysname1] && nms.switches_now.switches[nms.switches_now.linknets[i].sysname2]) { - connectSwitches(nms.switches_now.linknets[i].sysname1,nms.switches_now.linknets[i].sysname2, c1, c2); - } -} - -/* - * Draw all linknets - */ -function drawLinknets() -{ - if (nms.switches_now && nms.switches_now.linknets) { - for (var i in nms.switches_now.linknets) { - drawLinknet(i); - } - } -} - -/* - * Change both colors of a linknet. - * - * XXX: Probably have to change this to better match the backend data - */ -function setLinknetColors(i,c1,c2) -{ - if (!nms.linknet_color[i] || - nms.linknet_color[i].c1 != c1 || - nms.linknet_color[i].c2 != c2) { - if (!nms.linknet_color[i]) - nms.linknet_color[i] = {}; - nms.linknet_color[i]['c1'] = c1; - nms.linknet_color[i]['c2'] = c2; - drawLinknet(i); - } -} - -/* - * (Re)draw a switch 'sw'. - * - * Color defaults to 'blue' if it's not set in the data structure. - */ -function drawSwitch(sw) -{ - var box = nms.switches_now['switches'][sw]['placement']; - var color = nms.switch_color[sw]; - if (color == undefined) { - color = blue; - } - dr.switch.ctx.fillStyle = color; - /* - * XXX: This is a left-over from before NMS did separate - * canvases, and might be done better elsewhere. - */ - if (nms.nightMode && nms.nightBlur[sw] != true) { - dr.blur.ctx.shadowBlur = nms.shadowBlur; - dr.blur.ctx.shadowColor = nms.shadowColor; - drawBoxBlur(box['x'],box['y'],box['width'],box['height']); - nms.nightBlur[sw] = true; - } - drawBox(box['x'],box['y'],box['width'],box['height']); - dr.switch.ctx.shadowBlur = 0; - if (!nms.textDrawn[sw]) { - if ((box['width'] + 10 )< box['height']) { - drawSideways(dr.text.ctx, sw,box['x'],box['y'],box['width'],box['height']); - } else { - drawRegular(dr.text.ctx,sw,box['x'],box['y'],box['width'],box['height']); - } - - nms.textDrawn[sw] = true; - } -} - -/* - * Make sure all placements of switches are parsed as integers so we don't - * have to pollute the code with pasreInt() every time we use it. - */ -function parseIntPlacements() { - for (var sw in nms.switches_now.switches) { - nms.switches_now.switches[sw]['placement']['x'] = - parseInt(nms.switches_now.switches[sw]['placement']['x']); - nms.switches_now.switches[sw]['placement']['y'] = - parseInt(nms.switches_now.switches[sw]['placement']['y']); - nms.switches_now.switches[sw]['placement']['width'] = - parseInt(nms.switches_now.switches[sw]['placement']['width']); - nms.switches_now.switches[sw]['placement']['height'] = - parseInt(nms.switches_now.switches[sw]['placement']['height']); - } -} -/* - * Draw all switches - */ -function drawSwitches() -{ - if (!nms.switches_now || !nms.switches_now.switches) - return; - for (var sw in nms.switches_now.switches) { - drawSwitch(sw); - } - nms.drawn = true; -} -function drawSwitchInfo() -{ - if (!nms.switches_now || !nms.switches_now.switches) - return; - for (var sw in nms.switchInfo) { - switchInfoText(sw, nms.switchInfo[sw]); - } - nms.drawn = true; -} - -/* - * Draw current time-window - * - * FIXME: The math here is just wild approximation and guesswork because - * I'm lazy. - */ -function drawNow() -{ - if (!nms.switches_now) - return; - // XXX: Get rid of microseconds that we get from the backend. - var now = /^[^.]*/.exec(nms.switches_now.time); - dr.top.ctx.font = Math.round(2 * nms.fontSize * canvas.scale) + "px " + nms.fontFace; - dr.top.ctx.clearRect(0,0,Math.floor(800 * canvas.scale),Math.floor(100 * canvas.scale)); - dr.top.ctx.fillStyle = "white"; - dr.top.ctx.strokeStyle = "black"; - dr.top.ctx.lineWidth = Math.floor(nms.fontLineFactor * canvas.scale); - if (dr.top.ctx.lineWidth == 0) { - dr.top.ctx.lineWidth = Math.round(nms.fontLineFactor * canvas.scale); - } - dr.top.ctx.strokeText(now, 0 + margin.text, 25 * canvas.scale); - dr.top.ctx.fillText(now, 0 + margin.text, 25 * canvas.scale); -} -/* - * Draw foreground/scene. - * - * FIXME: Review this! This was made before linknets and switches were - * split apart. - * - * This is used so linknets are drawn before switches. If a switch is all - * that has changed, we just need to re-draw that, but linknets require - * scene-redrawing. - */ -function drawScene() -{ - dr.text.ctx.font = Math.floor(nms.fontSize * canvas.scale) + "px " + nms.fontFace; - dr.textInfo.ctx.font = Math.floor(nms.fontSize * canvas.scale) + "px " + nms.fontFace; - drawLinknets(); - drawSwitches(); - drawSwitchInfo(); -} - -/* - * Set the scale factor and (re)draw the scene and background. - * Uses canvas.scale and updates canvas.height and canvas.width. - */ -function setScale() -{ - canvas.height = orig.height * canvas.scale ; - canvas.width = orig.width * canvas.scale ; - for (var a in dr) { - /* - * Resizing this to a too small size breaks gradients on smaller screens. - */ - if (a == 'hidden') - continue; - dr[a].c.height = canvas.height; - dr[a].c.width = canvas.width; - } - nms.nightBlur = {}; - nms.textDrawn = {}; - nms.switchInfoDrawn = {}; - if (nms.gradients) - drawGradient(nms.gradients); - drawBG(); - drawScene(); - drawNow(); - - document.getElementById("scaler").value = canvas.scale; - document.getElementById("scaler-text").innerHTML = (parseFloat(canvas.scale)).toPrecision(3); -} /* * Returns true if the coordinates (x,y) is inside the box defined by @@ -1246,11 +412,11 @@ function isIn(box, x, y) * if none is found. */ function findSwitch(x,y) { - x = parseInt(parseInt(x) / canvas.scale); - y = parseInt(parseInt(y) / canvas.scale); + x = parseInt(parseInt(x) / nmsMap.scale); + y = parseInt(parseInt(y) / nmsMap.scale); - for (var v in nms.switches_now.switches) { - if(isIn(nms.switches_now.switches[v]['placement'],x,y)) { + for (var v in nmsData.switches.switches) { + if(isIn(nmsData.switches.switches[v]['placement'],x,y)) { return v; } } @@ -1258,118 +424,9 @@ function findSwitch(x,y) { } /* - * Set switch color of 'sw' to 'c', then re-draw the switch. - */ -function setSwitchColor(sw, c) -{ - if(!nms.switch_color || !nms.switch_color[sw] || nms.switch_color[sw] != c) { - nms.switch_color[sw] = c; - drawSwitch(sw); - } -} - -/* - * Event handler for the front-end drag bar to change scale - */ -function scaleChange() -{ - var scaler = document.getElementById("scaler").value; - canvas.scale = scaler; - setScale(); -} - -/* - * Called when a switch is clicked - */ -function switchClick(sw) -{ - if (nms.switch_showing == sw) - hideSwitch(); - else - showSwitch(sw); -} - -/* - * Resets the colors of linknets and switches. - * - * Useful when mode changes so we don't re-use colors from previous modes - * due to lack of data or bugs. - */ -function resetColors() -{ - if (!nms.switches_now) - return; - if (nms.switches_now.linknets) { - for (var i in nms.switches_now.linknets) { - setLinknetColors(i, blue,blue); - } - } - for (var sw in nms.switches_now.switches) { - setSwitchColor(sw, blue); - } -} - -function resetTextInfo() -{ - if (!nms.switches_now) - return; - for (var sw in nms.switches_now.switches) { - switchInfoText(sw, undefined); - } - -} -/* - * onclick handler for the canvas. - * - * Currently just shows info for a switch. - */ -function canvasClick(e) -{ - var sw = findSwitch(e.pageX - e.target.offsetLeft, e.pageY - e.target.offsetTop); - if (sw != undefined) { - switchClick(sw); - } -} - -/* - * Resize event-handler. - * - * Recomputes the scale and applies it. - * - * Has to use c.offset* since we are just scaling the canvas, not - * everything else. - * - */ -function resizeEvent() -{ - var width = window.innerWidth - dr.bg.c.offsetLeft; - var height = window.innerHeight - dr.bg.c.offsetTop; - if (width / (orig.width + margin.x) > height / (orig.height + margin.y)) { - canvas.scale = height / (orig.height + margin.y); - } else { - canvas.scale = width / (orig.width + margin.x); - } - setScale(); -} - -/* - * Draws the background image (scaled). - */ -function drawBG() -{ - if (nms.nightMode) { - invertCanvas(); - } else { - var image = document.getElementById('source'); - dr.bg.ctx.drawImage(image, 0, 0, canvas.width, canvas.height); - } -} - -/* * Set night mode to whatever 'toggle' is. - * - * XXX: setScale() is a bit of a hack, but it really is the same stuff we - * need to do: Redraw "everything" (not really). + * + * Changes background and nav-bar, then leaves the rest to nmsMap. */ function setNightMode(toggle) { nms.nightMode = toggle; @@ -1377,184 +434,34 @@ function setNightMode(toggle) { body.style.background = toggle ? "black" : "white"; var nav = document.getElementsByTagName("nav")[0]; if (toggle) { - dr.blur.c.style.display = ''; nav.classList.add('navbar-inverse'); } else { - dr.blur.c.style.display = 'none'; nav.classList.remove('navbar-inverse'); } - setScale(); -} - -function switchInfoText(sw, text) -{ - var box = nms.switches_now['switches'][sw]['placement']; - var c = canvas.scale; - if (nms.switchInfo[sw] == text && nms.switchInfoDrawn[sw]) { - return; - } - nms.switchInfo[sw] = text; - nms.switchInfoDrawn[sw] = true; - dr.textInfo.ctx.clearRect(c* box['x'], c*box['y'], c*box['width'], c*box['height']); - if (text != undefined && text != "") { - if ((box['width'] + 10 )< box['height']) { - drawSideways(dr.textInfo.ctx, text,box['x'],box['y'],box['width'],box['height'],"end"); - } else { - drawRegular(dr.textInfo.ctx, text,box['x'],box['y'],box['width'],box['height'],"end"); - } - } -} - -/* - * Draw a box (e.g.: switch). - */ -function drawBox(x,y,boxw,boxh) -{ - var myX = Math.floor(x * canvas.scale); - var myY = Math.floor(y * canvas.scale); - var myX2 = Math.floor((boxw) * canvas.scale); - var myY2 = Math.floor((boxh) * canvas.scale); - dr.switch.ctx.fillRect(myX,myY, myX2, myY2); - dr.switch.ctx.lineWidth = Math.floor(1.0 * canvas.scale); - if (canvas.scale < 1.0) { - dr.switch.ctx.lineWidth = 1.0; - } - dr.switch.ctx.strokeStyle = "#000000"; - dr.switch.ctx.strokeRect(myX,myY, myX2, myY2); -} - -/* - * Draw the blur for a box. - */ -function drawBoxBlur(x,y,boxw,boxh) -{ - var myX = Math.floor(x * canvas.scale); - var myY = Math.floor(y * canvas.scale); - var myX2 = Math.floor((boxw) * canvas.scale); - var myY2 = Math.floor((boxh) * canvas.scale); - dr.blur.ctx.fillRect(myX,myY, myX2, myY2); -} -/* - * Draw text on a box - sideways! - * - * XXX: This is pretty nasty and should also probably take a box as input. - */ -function drawSideways(ctx,text,x,y,w,h,align) -{ - ctx.rotate(Math.PI * 3 / 2); - ctx.fillStyle = "white"; - ctx.strokeStyle = "black"; - if (align == "end") { - ctx.textAlign = align; - y = y-h + margin.text*2; - } else { - ctx.textAlign = "start"; - } - ctx.lineWidth = Math.floor(nms.fontLineFactor * canvas.scale); - if (ctx.lineWidth == 0) { - ctx.lineWidth = Math.round(nms.fontLineFactor * canvas.scale); - } - ctx.strokeText(text, - canvas.scale * (y + h - margin.text),canvas.scale * (x + w - margin.text) ); - ctx.fillText(text, - canvas.scale * (y + h - margin.text),canvas.scale * (x + w - margin.text) ); - - ctx.rotate(Math.PI / 2); -} - -/* - * Draw background inverted (wooo) - * - * XXX: This is broken for chromium on local file system (e.g.: file:///) - * Seems like a chromium bug? - */ -function invertCanvas() { - var imageObj = document.getElementById('source'); - dr.bg.ctx.drawImage(imageObj, 0, 0, canvas.width, canvas.height); - - var imageData = dr.bg.ctx.getImageData(0, 0, canvas.width, canvas.height); - var data = imageData.data; - - for(var i = 0; i < data.length; i += 4) { - data[i] = 255 - data[i]; - data[i + 1] = 255 - data[i + 1]; - data[i + 2] = 255 - data[i + 2]; - } - dr.bg.ctx.putImageData(imageData, 0, 0); -} - -/* - * Draw regular text on a box. - * - * Should take the same format as drawSideways() - * - * XXX: Both should be renamed to have 'text' or something in them - */ -function drawRegular(ctx,text,x,y,w,h,align) { - - ctx.fillStyle = "white"; - ctx.strokeStyle = "black"; - ctx.lineWidth = Math.floor(nms.fontLineFactor * canvas.scale); - if (align == "end") { - ctx.textAlign = align; - x = x+w - margin.text*2; - } else { - ctx.textAlign = "start"; - } - if (ctx.lineWidth == 0) { - ctx.lineWidth = Math.round(nms.fontLineFactor * canvas.scale); - } - ctx.strokeText(text, (x + margin.text) * canvas.scale, (y + h - margin.text) * canvas.scale); - ctx.fillText(text, (x + margin.text) * canvas.scale, (y + h - margin.text) * canvas.scale); -} - -/* - * Draw a line between switch "insw1" and "insw2", using a gradiant going - * from color1 to color2. - * - * XXX: beginPath() and closePath() is needed to avoid re-using the - * gradient/color - */ -function connectSwitches(insw1, insw2,color1, color2) { - var sw1 = nms.switches_now.switches[insw1].placement; - var sw2 = nms.switches_now.switches[insw2].placement; - if (color1 == undefined) - color1 = blue; - if (color2 == undefined) - color2 = blue; - var x0 = Math.floor((sw1.x + sw1.width/2) * canvas.scale); - var y0 = Math.floor((sw1.y + sw1.height/2) * canvas.scale); - var x1 = Math.floor((sw2.x + sw2.width/2) * canvas.scale); - var y1 = Math.floor((sw2.y + sw2.height/2) * canvas.scale); - var gradient = dr.link.ctx.createLinearGradient(x1,y1,x0,y0); - gradient.addColorStop(0, color1); - gradient.addColorStop(1, color2); - dr.link.ctx.beginPath(); - dr.link.ctx.strokeStyle = gradient; - dr.link.ctx.moveTo(x0,y0); - dr.link.ctx.lineTo(x1,y1); - dr.link.ctx.lineWidth = Math.floor(5 * canvas.scale); - dr.link.ctx.closePath(); - dr.link.ctx.stroke(); - dr.link.ctx.moveTo(0,0); + nmsMap.setNightMode(toggle); } /* * Boot up "fully fledged" NMS. * - * If you only want parts of the functionality, then re-implement this - * (e.g., just add and start the handlers you want, don't worry about - * drawing, etc). + * This can be re-written to provide different looks and feels but using + * the same framework. Or rather: that's the goal. We're not quite there + * yet. */ function initNMS() { - initDrawing(); - window.addEventListener('resize',resizeEvent,true); - document.addEventListener('load',resizeEvent,true); - - nms.timers.ports = new nmsTimer(updatePorts, 1000, "Port updater", "AJAX request to update port data (traffic, etc)"); - - nms.timers.ping = new nmsTimer(updatePing, 1000, "Ping updater", "AJAX request to update ping data"); - nms.timers.playback = new nmsTimer(nms.playback.tick, 1000, "Playback ticker", "Handler used to advance time"); + // Public + nmsData.registerSource("ping", "/api/public/ping"); + nmsData.registerSource("switches","/api/public/switches"); + nmsData.registerSource("switchstate","/api/public/switch-state"); + + // Private + nmsData.registerSource("portstate","/api/private/port-state"); + nmsData.registerSource("comments", "/api/private/comments"); + nmsData.registerSource("smanagement","/api/private/switches-management"); + + nmsMap.init(); detectHandler(); nms.playback.play(); setupKeyhandler(); @@ -1572,91 +479,12 @@ function detectHandler() { setUpdater(handler_ping); } -/* - * Display and populate the dialog box for debugging timers. - * - * Could probably be cleaned up. - */ -function showTimerDebug() { - var tableTop = document.getElementById('debugTimers'); - var table = document.getElementById('timerTable'); - var tr, td1, td2; - if (table) - tableTop.removeChild(table); - table = document.createElement("table"); - table.id = "timerTable"; - table.style.zIndex = 100; - table.className = "table"; - table.classList.add("table"); - table.classList.add("table-default"); - var header = table.createTHead(); - tr = header.insertRow(0); - td = tr.insertCell(0); - td.innerHTML = "Handler"; - td = tr.insertCell(1); - td.innerHTML = "Interval (ms)"; - td = tr.insertCell(2); - td.innerHTML = "Name"; - td = tr.insertCell(3); - td.innerHTML = "Description"; - for (var v in nms.timers) { - tr = table.insertRow(-1); - td = tr.insertCell(0); - td.textContent = nms.timers[v].handle; - td = tr.insertCell(1); - td.style.width = "15em"; - var tmp = "<div class=\"input-group\"><input type=\"text\" id='handlerValue" + v + "' value='" + nms.timers[v].interval + "' class=\"form-control\"></input>"; - tmp += "<span class=\"input-group-btn\"><button type=\"button\" class=\"btn btn-default\" onclick=\"nms.timers['" + v + "'].setInterval(document.getElementById('handlerValue" + v + "').value);\">Apply</button></span></div>"; - td.innerHTML = tmp; - td = tr.insertCell(2); - td.textContent = nms.timers[v].name; - td = tr.insertCell(3); - td.textContent = nms.timers[v].description; - } - tableTop.appendChild(table); - document.getElementById('debugTimers').style.display = 'block'; -} - -function hideLayer(layer) { - var l = document.getElementById(layer); - l.style.display = "none"; - if (layer != "layerVisibility") - nms.layerVisibility[layer] = false; - saveSettings(); -} - -function showLayer(layer) { - var l = document.getElementById(layer); - l.style.display = ""; - if (layer != "layerVisibility") - nms.layerVisibility[layer] = true; - saveSettings(); -} - -function toggleLayer(layer) { - var l = document.getElementById(layer); - if (l.style.display == 'none') - l.style.display = ''; - else - l.style.display = 'none'; -} - -function applyLayerVis() -{ - for (var l in nms.layerVisibility) { - var layer = document.getElementById(l); - if (layer) - layer.style.display = nms.layerVisibility[l] ? '' : 'none'; - } -} - function setMenu() { var nav = document.getElementsByTagName("nav")[0]; nav.style.display = nms.menuShowing ? '' : 'none'; - resizeEvent(); - } + function toggleMenu() { nms.menuShowing = ! nms.menuShowing; @@ -1718,13 +546,6 @@ function moveTimeFromKey(e,key) return true; } -var debugEvent; -function keyDebug(e) -{ - console.log(e); - debugEvent = e; -} - function keyPressed(e) { if (e.target.nodeName == "INPUT") { @@ -1778,42 +599,33 @@ function restoreSettings() for (var v in retrieve) { nms[v] = retrieve[v]; } - setScale(); setMenu(); - setNightMode(nms.nightMode); - applyLayerVis(); -} - -function forgetSettings() -{ - document.cookie = 'nms=' + btoa('{}'); } /* * Time travel gui */ -var datepicker; function startNowPicker(now) { - $.datetimepicker.setLocale('no'); - $('#nowPicker').datetimepicker('destroy'); - if(!now && nms.now) - now = nms.now; - datepicker = $('#nowPicker').datetimepicker({ - value: now, - mask:false, - inline:true, - todayButton: false, - validateOnBlur:false, - dayOfWeekStart:1, - maxDate:'+1970/01/01', - onSelectDate: function(ct,$i){ - document.getElementById('nowPicker').dataset.iso = localEpochToString(ct.valueOf()/1000); - }, - onSelectTime: function(ct,$i){ - document.getElementById('nowPicker').dataset.iso = localEpochToString(ct.valueOf()/1000); - }, - onGenerate: function(ct,$i){ - document.getElementById('nowPicker').dataset.iso = localEpochToString(ct.valueOf()/1000); - } - }); + $.datetimepicker.setLocale('no'); + $('#nowPicker').datetimepicker('destroy'); + if(!now && nms.now) + now = nms.now; + var datepicker = $('#nowPicker').datetimepicker({ + value: now, + mask:false, + inline:true, + todayButton: false, + validateOnBlur:false, + dayOfWeekStart:1, + maxDate:'+1970/01/01', + onSelectDate: function(ct,$i){ + document.getElementById('nowPicker').dataset.iso = localEpochToString(ct.valueOf()/1000); + }, + onSelectTime: function(ct,$i){ + document.getElementById('nowPicker').dataset.iso = localEpochToString(ct.valueOf()/1000); + }, + onGenerate: function(ct,$i){ + document.getElementById('nowPicker').dataset.iso = localEpochToString(ct.valueOf()/1000); + } + }); } |