aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xclients/ping.pl34
-rwxr-xr-xclients/snmpfetchng.pl143
-rwxr-xr-xinclude/config.pm.dist28
-rwxr-xr-xinclude/nms.pm2
-rw-r--r--include/nms/util.pm33
-rwxr-xr-xinclude/nms/web.pm33
-rw-r--r--nms/Dockerfile.in2
-rw-r--r--web/etc/varnish/nms.vcl16
-rw-r--r--web/nms.gathering.org/api/API.rst15
-rwxr-xr-xweb/nms.gathering.org/api/private/comment-add1
-rwxr-xr-xweb/nms.gathering.org/api/private/comment-change10
-rwxr-xr-xweb/nms.gathering.org/api/private/port-state33
-rwxr-xr-xweb/nms.gathering.org/api/private/snmp29
-rwxr-xr-xweb/nms.gathering.org/api/private/switch-add72
-rwxr-xr-xweb/nms.gathering.org/api/private/switches-management2
-rwxr-xr-xweb/nms.gathering.org/api/public/ping12
-rwxr-xr-xweb/nms.gathering.org/api/public/switch-state56
-rw-r--r--web/nms.gathering.org/index.html322
-rw-r--r--web/nms.gathering.org/js/nms-color-util.js22
-rw-r--r--web/nms.gathering.org/js/nms-data.js255
-rw-r--r--web/nms.gathering.org/js/nms-info-box.js369
-rw-r--r--web/nms.gathering.org/js/nms-map-handlers.js177
-rw-r--r--web/nms.gathering.org/js/nms-map.js496
-rw-r--r--web/nms.gathering.org/js/nms.js1529
-rw-r--r--web/nms.gathering.org/test.html9
25 files changed, 1621 insertions, 2079 deletions
diff --git a/clients/ping.pl b/clients/ping.pl
index 8d216ef..47119f1 100755
--- a/clients/ping.pl
+++ b/clients/ping.pl
@@ -5,15 +5,17 @@ use Time::HiRes;
use Net::Oping;
use strict;
use warnings;
+use Data::Dumper;
use lib '../include';
use nms;
+$|++;
my $dbh = nms::db_connect();
$dbh->{AutoCommit} = 0;
$dbh->{RaiseError} = 1;
-my $q = $dbh->prepare("SELECT switch,ip,secondary_ip FROM switches WHERE ip<>'127.0.0.1'");
+my $q = $dbh->prepare("SELECT switch,ip,secondary_ip FROM switches WHERE ip is not null ORDER BY random()");
my $lq = $dbh->prepare("SELECT linknet,addr1,addr2 FROM linknets");
while (1) {
@@ -36,19 +38,26 @@ while (1) {
$ping->host_add($secondary_ip);
$secondary_ip_to_switch{$secondary_ip} = $switch;
}
- print "ip: $ip\n";
}
my $result = $ping->ping();
+ my %dropped = %{$ping->get_dropped()};
die $ping->get_error if (!defined($result));
$dbh->do('COPY ping (switch, latency_ms) FROM STDIN'); # date is implicitly now.
+ my $drops = 0;
while (my ($ip, $latency) = each %$result) {
my $switch = $ip_to_switch{$ip};
next if (!defined($switch));
+ if (!defined($latency)) {
+ $drops += $dropped{$ip};
+ }
$latency //= "\\N";
$dbh->pg_putcopydata("$switch\t$latency\n");
}
+ if ($drops > 0) {
+ print "$drops ";
+ }
$dbh->pg_putcopyend();
$dbh->do('COPY ping_secondary_ip (switch, latency_ms) FROM STDIN'); # date is implicitly now.
@@ -73,18 +82,19 @@ while (1) {
$ping->host_add($ref->{'addr1'});
$ping->host_add($ref->{'addr2'});
}
- $result = $ping->ping();
- die $ping->get_error if (!defined($result));
+ if (@linknets) {
+ $result = $ping->ping();
+ die $ping->get_error if (!defined($result));
- $dbh->do('COPY linknet_ping (linknet, latency1_ms, latency2_ms) FROM STDIN'); # date is implicitly now.
- for my $linknet (@linknets) {
- my $id = $linknet->{'linknet'};
- my $latency1 = $result->{$linknet->{'addr1'}} // '\N';
- my $latency2 = $result->{$linknet->{'addr2'}} // '\N';
- $dbh->pg_putcopydata("$id\t$latency1\t$latency2\n");
+ $dbh->do('COPY linknet_ping (linknet, latency1_ms, latency2_ms) FROM STDIN'); # date is implicitly now.
+ for my $linknet (@linknets) {
+ my $id = $linknet->{'linknet'};
+ my $latency1 = $result->{$linknet->{'addr1'}} // '\N';
+ my $latency2 = $result->{$linknet->{'addr2'}} // '\N';
+ $dbh->pg_putcopydata("$id\t$latency1\t$latency2\n");
+ }
+ $dbh->pg_putcopyend();
}
- $dbh->pg_putcopyend();
$dbh->commit;
-
}
diff --git a/clients/snmpfetchng.pl b/clients/snmpfetchng.pl
index fa7c227..68baddd 100755
--- a/clients/snmpfetchng.pl
+++ b/clients/snmpfetchng.pl
@@ -3,21 +3,26 @@ use strict;
use warnings;
use DBI;
use POSIX;
-use Time::HiRes;
+#use Time::HiRes qw(time);
use SNMP;
use Data::Dumper;
use lib '../include';
use nms;
+SNMP::addMibDirs("/tmp/tmp.esQYrkg9MW/v2");
+SNMP::loadModules('SNMPv2-MIB');
+SNMP::loadModules('ENTITY-MIB');
+SNMP::loadModules('IF-MIB');
+SNMP::loadModules('LLDP-MIB');
+SNMP::loadModules('IP-MIB');
+SNMP::loadModules('IP-FORWARD-MIB');
our $dbh = nms::db_connect();
$dbh->{AutoCommit} = 0;
$dbh->{RaiseError} = 1;
-#my $qualification = 'sysname LIKE \'e71%\'';
-
my $qualification = <<"EOF";
(last_updated IS NULL OR now() - last_updated > poll_frequency)
-AND (locked='f' OR now() - last_updated > '5 minutes'::interval)
+AND (locked='f' OR now() - last_updated > '15 minutes'::interval)
AND ip is not null
EOF
@@ -28,13 +33,11 @@ SELECT
DATE_TRUNC('second', now() - last_updated - poll_frequency) AS overdue
FROM
switches
- NATURAL LEFT JOIN switchtypes
WHERE
$qualification
ORDER BY
- priority DESC,
overdue DESC
-LIMIT 10
+LIMIT ?
FOR UPDATE OF switches
EOF
or die "Couldn't prepare qswitch";
@@ -44,8 +47,10 @@ our $qunlock = $dbh->prepare("UPDATE switches SET locked='f', last_updated=now()
or die "Couldn't prepare qunlock";
my @switches = ();
-our $temppoll = $dbh->prepare("INSERT INTO switch_temp (switch,temp,time) VALUES((select switch from switches where sysname = ?),?,now())")
- or die "Couldn't prepare temppoll";
+my $sth = $dbh->prepare("INSERT INTO snmp (switch,data) VALUES((select switch from switches where sysname=?), ?)");
+
+our $outstanding = 0;
+
sub mylog
{
my $msg = shift;
@@ -57,7 +62,15 @@ sub mylog
sub populate_switches
{
@switches = ();
- $qswitch->execute()
+ my $limit = $nms::config::snmp_max - $outstanding;
+ if ($limit < 0) {
+ mylog("Something wrong. Too many outstanding polls going.");
+ $limit = 1;
+ }
+ if ($outstanding > 0) {
+ mylog("Outstanding polls: $outstanding . Current limit: $limit");
+ }
+ $qswitch->execute($limit)
or die "Couldn't get switch";
while (my $ref = $qswitch->fetchrow_hashref()) {
@@ -68,103 +81,75 @@ sub populate_switches
'community' => $ref->{'community'}
};
}
- $dbh->commit;
}
sub inner_loop
{
- mylog("Starting run");
populate_switches();
+ my $poll_todo = "";
for my $refswitch (@switches) {
+ $outstanding++;
my %switch = %{$refswitch};
- mylog( "START: Polling $switch{'sysname'} ($switch{'mgtip'}) ");
+ $poll_todo .= "$switch{'sysname'} ";
$switch{'start'} = time;
$qlock->execute($switch{'id'})
or die "Couldn't lock switch";
- $dbh->commit;
- my $s = new SNMP::Session(DestHost => $switch{'mgtip'},
+ my $s = SNMP::Session->new(DestHost => $switch{'mgtip'},
Community => $switch{'community'},
+ UseEnums => 1,
Version => '2');
- my @vars = ();
- push @vars, [ "sysName", 0];
- push @vars, [ "sysDescr", 0];
- push @vars, [ "1.3.6.1.4.1.2636.3.1.13.1.7.7.1.0", 0];
- my $varlist = SNMP::VarList->new(@vars);
- $s->get($varlist, [ \&ckcall, \%switch ]);
- $s->gettable('ifXTable',callback => [\&callback, \%switch]);
- }
- mylog( "Added " . @switches . " ");
- SNMP::MainLoop(5);
-}
-
-sub ckcall
-{
- my %switch = %{$_[0]};
-
- my $vars = $_[1];
- my ($sysname,$sysdescr,$temp) = (undef,undef,undef);
- for my $var (@$vars) {
- if ($var->[0] eq "sysName") {
- $sysname = $var->[2];
- } elsif ($var->[0] eq "sysDescr") {
- $sysdescr = $var->[2];
- } elsif ($var->[0] eq "enterprises.2636.3.1.13.1.7.7.1.0.0") {
- $temp = $var->[2];
+ my $ret = $s->bulkwalk(0, 10, @nms::config::snmp_objects, sub{ callback(\%switch, @_); });
+ if (!defined($ret)) {
+ mylog("Fudge: ". $s->{'ErrorStr'});
+ $outstanding--;
}
}
- if (defined $temp && $temp =~ /^\d+$/) {
- $temppoll->execute($switch{'sysname'},$temp);
- } else {
- warn "Couldn't read temp for " . $switch{'sysname'} . ", got " . (defined $temp ? $temp : "undef");
- }
$dbh->commit;
+ mylog( "Polling " . @switches . " switches: $poll_todo");
+ SNMP::MainLoop(5);
}
-my @values = ('ifName','ifHighSpeed','ifHCOutOctets','ifHCInOctets');
-my $query = "INSERT INTO polls (switch,time";
-foreach my $val (@values) {
- $query .= ",$val";
-}
-$query .= ") VALUES(?,timeofday()::timestamp";
-foreach my $val (@values) {
- $query .= ",?";
-}
-$query .= ");";
-our $qpoll = $dbh->prepare($query)
- or die "Couldn't prepare qpoll";
-sub callback
-{
+sub callback{
+ my @top = $_[1];
my %switch = %{$_[0]};
- my $table = $_[1];
-
- my %ifs = ();
-
- foreach my $key (keys %{$table}) {
- my $descr = $table->{$key}->{'ifName'};
-
- if ($descr =~ m/(ge|xe|et)-/ && $descr !~ m/\./) {
- $ifs{$descr} = $table->{$key};
+ my %tree;
+ my %ttop;
+ my %nics;
+ my @nicids;
+
+ for my $ret (@top) {
+ for my $var (@{$ret}) {
+ for my $inner (@{$var}) {
+ my ($tag,$type,$name,$iid, $val) = ( $inner->tag ,$inner->type , $inner->name, $inner->iid, $inner->val);
+ if ($tag eq "ifPhysAddress") {
+ next;
+ }
+ $tree{$iid}{$tag} = $val;
+ if ($tag eq "ifIndex") {
+ push @nicids, $iid;
+ }
+ }
}
}
-
- foreach my $key (keys %ifs) {
- my @vals = ();
- foreach my $val (@values) {
- if (!defined($ifs{$key}{$val})) {
- die "Missing data";
- }
- push @vals, $ifs{$key}{$val};
+ my %tree2;
+ for my $nic (@nicids) {
+ $tree2{'ports'}{$tree{$nic}{'ifName'}} = $tree{$nic};
+ delete $tree{$nic};
+ }
+ for my $iid (keys %tree) {
+ for my $key (keys %{$tree{$iid}}) {
+ $tree2{'misc'}{$key}{$iid} = $tree{$iid}{$key};
}
- $qpoll->execute($switch{'id'},@vals) || die "ops";
}
- mylog( "STOP: Polling $switch{'sysname'} took " . (time - $switch{'start'}) . "s");
+ $sth->execute($switch{'sysname'}, JSON::XS::encode_json(\%tree2));
$qunlock->execute($switch{'id'})
or die "Couldn't unlock switch";
$dbh->commit;
+ $outstanding--;
+ mylog( "Polled $switch{'sysname'} in " . (time - $switch{'start'}) . "s. ($outstanding outstanding polls)");
}
-print $query . "\n";
while (1) {
inner_loop();
}
diff --git a/include/config.pm.dist b/include/config.pm.dist
index cce82df..a1db01a 100755
--- a/include/config.pm.dist
+++ b/include/config.pm.dist
@@ -10,8 +10,32 @@ our $db_host = "gerald.tg15.gathering.org";
our $db_username = "nms";
our $db_password = "<removed>";
-# NMS hash used for public NMS obfuscation of interface names
-our $nms_hash = "<removed>";
+# NMS: What SNMP objects to fetch.
+# Some basics
+our @snmp_objects = [
+['ifIndex'],
+['sysName'],
+['sysDescr'],
+['ifHighSpeed'],
+['ifType'],
+['ifName'],
+['ifDescr'],
+['ifAlias'],
+['ifOperStatus'],
+['ifAdminStatus'],
+['ifLastChange'],
+['ifHCInOctets'],
+['ifHCOutOctets'],
+['ifInDiscards'],
+['ifOutDiscards'],
+['ifInErrors'],
+['ifOutErrors'],
+['ifInUnknownProtos'],
+['ifOutQLen'],
+['1.3.6.1.4.1.2636.3.1.13.1.7.7.1.0']
+];
+# Max SNMP polls to fire off at the same time.
+our $snmp_max = 20;
# DHCP-servers
our $dhcp_server1 = "185.12.59.66"; # primary
diff --git a/include/nms.pm b/include/nms.pm
index 6a9598e..2ec922b 100755
--- a/include/nms.pm
+++ b/include/nms.pm
@@ -26,7 +26,7 @@ sub db_connect {
my $dbh = DBI->connect($connstr,
$nms::config::db_username,
- $nms::config::db_password)
+ $nms::config::db_password, {AutoCommit => 0})
or die "Couldn't connect to database";
return $dbh;
}
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 ddc1be0..2a5e132 100755
--- a/include/nms/web.pm
+++ b/include/nms/web.pm
@@ -7,8 +7,9 @@ use DBI;
use Data::Dumper;
use JSON;
use nms;
-use Digest::SHA qw(sha512_base64);
-use FreezeThaw qw(freeze);
+use Digest::SHA;
+use FreezeThaw;
+use URI::Escape;
package nms::web;
use base 'Exporter';
@@ -51,39 +52,45 @@ sub db_safe_quote {
# returns a valid $when statement
# Also sets cache-control headers if time is overridden
+# This can be called explicitly to override the window of time we evaluate.
+# Normally up to 15 minutes old data will be returned, but for some API
+# endpoints it is better to return no data than old data (e.g.: ping).
sub setwhen {
- my $when;
$now = "now()";
+ my $window = '15m';
+ if (@_ == 1) {
+ $window = $_[0];
+ }
if (defined($get_params{'now'})) {
$now = db_safe_quote('now') . "::timestamp ";
$cc{'max-age'} = "3600";
}
- if (defined($get_params{'offset'})) {
- $now = "(" . $now . " - " . db_safe_quote('offset') . "::interval)";
- }
- $when = " time > " . $now . " - '5m'::interval and time < " . $now . " ";
- return $when;
+ $when = " time > " . $now . " - '".$window."'::interval and time < " . $now . " ";
}
sub finalize_output {
my $query;
my $hash = Digest::SHA::sha512_base64(FreezeThaw::freeze(%json));
- $query = $dbh->prepare ('select ' . $now . ' as time;');
+ $dbh->commit;
+ $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 "Etag: $hash\n";
print "Content-Type: text/jso; charset=utf-8\n\n";
print JSON::XS::encode_json(\%json);
print "\n";
}
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);
}
}
@@ -93,6 +100,6 @@ BEGIN {
$dbh = nms::db_connect();
populate_params();
- $when = setwhen();
+ setwhen();
}
1;
diff --git a/nms/Dockerfile.in b/nms/Dockerfile.in
index a0d048a..b1d2140 100644
--- a/nms/Dockerfile.in
+++ b/nms/Dockerfile.in
@@ -28,6 +28,7 @@ RUN apt-get update && apt-get -y install \
httpie \
locales \
screen \
+ openssh-server \
pkg-config
VOLUME [ "/sys/fs/cgroup" ]
@@ -40,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
diff --git a/web/etc/varnish/nms.vcl b/web/etc/varnish/nms.vcl
index e4b4747..6349238 100644
--- a/web/etc/varnish/nms.vcl
+++ b/web/etc/varnish/nms.vcl
@@ -46,18 +46,8 @@ sub vcl_hash {
# Mauve magi. Hva nĂ¥ enn det er.
# Dette er WIP - Skal flyttes til backend
sub vcl_backend_response {
- if (beresp.status == 200) {
- set beresp.ttl = 2s;
- } else {
- # Vi cacher feilmeldinger, fordi vi er kule.
- set beresp.ttl = 1s;
- }
-
- if(bereq.url ~ "port-state.pl" && beresp.status == 200) {
- set beresp.ttl = 1s;
- }
- if (beresp.status == 200 && bereq.url ~ "now=") {
- # Historisk data kan vi cache cirka evig
- set beresp.ttl = 60m;
+ set beresp.http.x-url = bereq.url;
+ if (beresp.http.x-ban) {
+ ban("obj.http.x-url ~ " + beresp.http.x-ban);
}
}
diff --git a/web/nms.gathering.org/api/API.rst b/web/nms.gathering.org/api/API.rst
index 4751ee5..61fbb5a 100644
--- a/web/nms.gathering.org/api/API.rst
+++ b/web/nms.gathering.org/api/API.rst
@@ -46,15 +46,15 @@ Update frequency: on user input
Lists comments.
-/api/private/port-state
------------------------
+/api/private/snmp
+-----------------
Methods: GET
Update frequency: Every few seconds, based on SNMP data.
-Returns detailed per-port statistics. Being somewhat reorganized but will
-remain highly relevant.
+Returns full SNMP-data, split into two trees. 'misc' and 'ports'.
+
/api/private/switches-management
--------------------------------
@@ -70,10 +70,15 @@ List management information for switches.
Methods: POST
-Add switches, supports same format as tools/add_switches.txt.pl
+Add or update switches, supports same format as tools/add_switches.txt.pl
Accepts an array of switches.
+Magic: If you set placement to be "reset", it will re-calculate placement
+based on sysname. For new switches, this is redundant as an empty
+placement-field will trigger the same behavior.
+
+
Public
......
diff --git a/web/nms.gathering.org/api/private/comment-add b/web/nms.gathering.org/api/private/comment-add
index 2f8b0b7..26ff734 100755
--- a/web/nms.gathering.org/api/private/comment-add
+++ b/web/nms.gathering.org/api/private/comment-add
@@ -20,4 +20,5 @@ $nms::web::cc{'max-age'} = '0';
$nms::web::cc{'stale-while-revalidate'} = '0';
$nms::web::json{'state'} = 'ok';
+print "X-ban: /api/private/comments\n";
finalize_output();
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
deleted file mode 100755
index 8c6b64c..0000000
--- a/web/nms.gathering.org/api/private/port-state
+++ /dev/null
@@ -1,33 +0,0 @@
-#! /usr/bin/perl
-# vim:ts=8:sw=8
-
-use lib '../../../../include';
-use nms::web;
-use strict;
-use warnings;
-
-my $query = 'select sysname,extract(epoch from date_trunc(\'second\',time)) as time, ifname,ifhighspeed,ifhcinoctets,ifhcoutoctets from polls natural join switches where time in (select max(time) from polls where ' . $nms::web::when . ' group by switch,ifname);';
-my $q = $nms::web::dbh->prepare($query);
-$q->execute();
-
-while (my $ref = $q->fetchrow_hashref()) {
- my @fields = ('ifhighspeed','ifhcoutoctets','ifhcinoctets');
- foreach my $val (@fields) {
- $nms::web::json{'switches'}{$ref->{'sysname'}}{'ports'}{$ref->{'ifname'}}{$val} = $ref->{$val};
- }
- $nms::web::json{'switches'}{$ref->{'sysname'}}{'ports'}{$ref->{'ifname'}}{'time'} = $ref->{'time'};
-}
-
-my $q3 = $nms::web::dbh->prepare('select distinct on (switch) switch,temp,time,sysname from switch_temp natural join switches where ' . $nms::web::when . ' order by switch,time desc');
-
-
-$q3->execute();
-while (my $ref = $q3->fetchrow_hashref()) {
- my $sysname = $ref->{'sysname'};
- $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/snmp b/web/nms.gathering.org/api/private/snmp
new file mode 100755
index 0000000..4779659
--- /dev/null
+++ b/web/nms.gathering.org/api/private/snmp
@@ -0,0 +1,29 @@
+#! /usr/bin/perl
+# vim:ts=8:sw=8
+
+use CGI qw(fatalsToBrowser);
+use DBI;
+use lib '../../../../include';
+use nms;
+use nms::web;
+use strict;
+use warnings;
+use JSON;
+use Data::Dumper;
+
+$nms::web::cc{'max-age'} = "10";
+
+my $q = $nms::web::dbh->prepare('select sysname,data from snmp natural join switches where id in (select max(id) from snmp where ' . $nms::web::when . 'group by switch);');
+
+$q->execute();
+while (my $ref = $q->fetchrow_hashref()) {
+ my $sysname = $ref->{'sysname'};
+
+ # This is, strictly speaking, redundant. But by doing this, we can
+ # re-use the standard methods of finalize_output() and whatnot.
+ my $data = JSON::XS::decode_json($ref->{'data'});
+
+ $nms::web::json{'snmp'}{$ref->{'sysname'}} = $data;
+}
+
+finalize_output();
diff --git a/web/nms.gathering.org/api/private/switch-add b/web/nms.gathering.org/api/private/switch-add
index 826b65b..979a1d8 100755
--- a/web/nms.gathering.org/api/private/switch-add
+++ b/web/nms.gathering.org/api/private/switch-add
@@ -22,7 +22,23 @@ 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', 'placement');
+my @fields = ('ip', 'sysname', 'switchtype', 'last_updated', 'locked', 'poll_frequency', 'community', 'lldp_chassis_id', 'secondary_ip', 'subnet4', 'subnet6', '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};
@@ -39,38 +55,55 @@ 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;
- if (not defined($switch{'placement'})) {
- %{$switch{'placement'}} = guess_placement($switch{'sysname'});
- }
- 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'} = $dbh->quote($place);
$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))";
- $switch{'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'};
+ }
+ if (not defined($switch{'ip'}) and defined($switch{'mgtmt4'})) {
+ $switch{'ip'} = $switch{'mgtmt4'};
}
my @set;
map {
- push @set, "$_=" . $dbh->quote($switch{$_});
+ 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'};
@@ -79,4 +112,5 @@ foreach my $tmp2 (@tmp) {
$json{'switches_addded'} = \@added;
$json{'switches_updated'} = \@dups;
+print "X-ban: /api/.*switches.*\n";
finalize_output();
diff --git a/web/nms.gathering.org/api/private/switches-management b/web/nms.gathering.org/api/private/switches-management
index 474f674..543d08d 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,subnet4,subnet6,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 f13a03b..bf92440 100755
--- a/web/nms.gathering.org/api/public/ping
+++ b/web/nms.gathering.org/api/public/ping
@@ -2,10 +2,18 @@
use lib '../../../../include';
use nms::web;
-my $q = $nms::web::dbh->prepare("SELECT DISTINCT ON (sysname) time,sysname, latency_ms FROM ping NATURAL JOIN switches WHERE time in (select max(time) from ping where " . $nms::web::when . " group by switch)");
+#nms::web::setwhen('1s');
+
+my $q = $nms::web::dbh->prepare("SELECT DISTINCT ON (sysname) (now() - time) as age,sysname, latency_ms FROM ping NATURAL JOIN switches WHERE time in (select max(time) from ping where " . $nms::web::when . " group by switch)");
$q->execute();
while (my $ref = $q->fetchrow_hashref()) {
$nms::web::json{'switches'}{$ref->{'sysname'}}{'latency'} = $ref->{'latency_ms'};
+ # This isn't pretty, feel free to fix, but I want age == seconds
+ # without decimals.
+ my ($h,$m,$ss) = split(':', $ref->{'age'});
+ my ($s,undef) = split('\.', "$ss");
+
+ $nms::web::json{'switches'}{$ref->{'sysname'}}{'age'} = ($h*60*60) + ($m*60) + $s;# $$ref->{'age'};
}
my $qs = $nms::web::dbh->prepare("SELECT DISTINCT ON (switch) switch, latency_ms FROM ping_secondary_ip WHERE " . $nms::web::when . " ORDER BY switch, time DESC;");
@@ -20,8 +28,6 @@ while (my $ref = $lq->fetchrow_hashref()) {
$nms::web::json{'linknets'}{$ref->{'linknet'}} = [ $ref->{'latency1_ms'}, $ref->{'latency2_ms'} ];
}
-$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 999a1d8..38f4c57 100755
--- a/web/nms.gathering.org/api/public/switch-state
+++ b/web/nms.gathering.org/api/public/switch-state
@@ -2,45 +2,41 @@
# vim:ts=8:sw=8
use lib '../../../../include';
-use nms::web;
+use nms::web qw (%json finalize_output);
use strict;
use warnings;
+use Data::Dumper;
-my $query = 'select sysname,extract(epoch from date_trunc(\'second\',time)) as time, ifname,ifhighspeed,ifhcinoctets,ifhcoutoctets from polls natural join switches where time in (select max(time) from polls where ' . $nms::web::when . ' group by switch,ifname);';
-my $q = $nms::web::dbh->prepare($query);
-$q->execute();
+my $q = $nms::web::dbh->prepare('select sysname,extract(epoch from date_trunc(\'second\',time)) as time,data from snmp natural join switches where id in (select max(id) from snmp where ' . $nms::web::when . 'group by switch);');
+$q->execute();
while (my $ref = $q->fetchrow_hashref()) {
- my @fields = ('ifhcoutoctets','ifhcinoctets');
- foreach my $val (@fields) {
- if ($ref->{'ifname'} =~ /ge-0\/0\/4[4-7]/) {
- $nms::web::json{'switches'}{$ref->{'sysname'}}{'uplinks'}{$val} += $ref->{$val};
- }
- $nms::web::json{'switches'}{$ref->{'sysname'}}{'total'}{$val} += $ref->{$val};
- }
- $nms::web::json{'switches'}{$ref->{'sysname'}}{'time'} += $ref->{'time'};
-}
-
-my $q3 = $nms::web::dbh->prepare('select distinct on (switch) switch,temp,time,sysname from switch_temp natural join switches where ' . $nms::web::when . ' order by switch,time desc');
-
-$q3->execute();
-while (my $ref = $q3->fetchrow_hashref()) {
my $sysname = $ref->{'sysname'};
- $nms::web::json{'switches'}{$ref->{'sysname'}}{'temp'} = $ref->{'temp'};
-}
-my $q2 = $nms::web::dbh->prepare("SELECT DISTINCT ON (sysname) time,sysname, latency_ms FROM ping NATURAL JOIN switches WHERE time in (select max(time) from ping where " . $nms::web::when . " group by switch)");
-$q2->execute();
-while (my $ref = $q2->fetchrow_hashref()) {
- $nms::web::json{'switches'}{$ref->{'sysname'}}{'latency'} = $ref->{'latency_ms'};
-}
+ my %data = %{JSON::XS::decode_json($ref->{'data'})};
+
+ for my $porti (keys %{$data{'ports'}}) {
+ my %port = %{$data{'ports'}{$porti}};
+ my $smallport = $porti;
+ $smallport =~ s/[0-9-].*$//;
+ if ($porti =~ /ge-0\/0\/4[4-7]/ or $porti eq 'eth0') {
+ $json{'switches'}{$sysname}{'uplinks'}{'ifHCInOctets'} += $port{'ifHCInOctets'};
+ $json{'switches'}{$sysname}{'uplinks'}{'ifHCOutOctets'} += $port{'ifHCOutOctets'};
+ if ($port{'ifOperStatus'} eq "up") {
+ $json{'switches'}{$sysname}{'uplinks'}{'live'} += 1;
+ }
+ $json{'switches'}{$sysname}{'uplinks'}{'total'} += 1;
+ }
-my $qs = $nms::web::dbh->prepare("SELECT DISTINCT ON (switch) switch, latency_ms FROM ping_secondary_ip WHERE " . $nms::web::when . " ORDER BY switch, time DESC;");
-$qs->execute();
-while (my $ref = $qs->fetchrow_hashref()) {
- $nms::web::json{'switches'}{$ref->{'switch'}}{'latency_secondary'} = $ref->{'latency_ms'};
+ $json{'switches'}{$sysname}{$smallport}{'ifHCInOctets'} += $port{'ifHCInOctets'};
+ $json{'switches'}{$sysname}{$smallport}{'ifHCOutOctets'} += $port{'ifHCOutOctets'};
+ if ($port{'ifOperStatus'} eq "up") {
+ $json{'switches'}{$sysname}{$smallport}{'live'} += 1;
+ }
+ $json{'switches'}{$sysname}{$smallport}{'total'} += 1;
+ }
+ $json{'switches'}{$sysname}{'time'} = $ref->{'time'};
}
-
$nms::web::cc{'max-age'} = "5";
$nms::web::cc{'stale-while-revalidate'} = "30";
finalize_output();
diff --git a/web/nms.gathering.org/index.html b/web/nms.gathering.org/index.html
index 55cd095..3452b9a 100644
--- a/web/nms.gathering.org/index.html
+++ b/web/nms.gathering.org/index.html
@@ -70,28 +70,29 @@
<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>
<div class="navbar-form navbar-left">
<div class="form-group">
- <button class="btn btn-default btn-xs" id="legend-1"></button>
- <button class="btn btn-default btn-xs" id="legend-2"></button>
- <button class="btn btn-default btn-xs" id="legend-3"></button>
- <button class="btn btn-default btn-xs" id="legend-4"></button>
- <button class="btn btn-default btn-xs" id="legend-5"></button>
+ <button class="btn btn-default btn-sm" id="legend-1"></button>
+ <button class="btn btn-default btn-sm" id="legend-2"></button>
+ <button class="btn btn-default btn-sm" id="legend-3"></button>
+ <button class="btn btn-default btn-sm" id="legend-4"></button>
+ <button class="btn btn-default btn-sm" id="legend-5"></button>
+ </div>
+ <div class="input-group input-group-sm">
+ <input id="searchbox" type="text" class="form-control" placeholder="Search" oninput="nmsInfoBox._search()" />
+ <span class="input-group-btn">
+ <button class="btn btn-default" type="button" onclick="nmsInfoBox.show(document.getElementById('searchbox').value);">Go!</button>
+ </span>
</div>
</div>
</li>
@@ -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">&times;</span>
- </button>
- </h3>
- </div>
- <div class="panel-body">
- <p>The data you see from The Gathering 2015 will seem
- "broken up". This is not because we don't have data from
- the first day, but because the backend was re-written on
- day 1/2 and this web app only uses the new API.</p>
- <p>NMS was set up on March 30th (Monday). Data started
- pouring in on the same day. </p>
- <p>Ping data is available for the entire event with 1
- second resolution. We "lost" data from the 30th because we
- re-inserted the switches (We have the ping data, but not
- the mapping between switch ID number and actual
- switch).</p>
- <p>DHCP data is available only for the last detected DHCP
- ack (no history, except extensive text-based logs)</p>
- <p>Uplink status is available for most of the event, but
- not exposed here. We only expose traffic-based uplink state
- here, which, again, is based on the new API.</p>
- <p>Traffic status was temporarily bugged, but is available
- from late on day 2.</p>
- <p>Temperature data is available from day 2.</p>
- <p>Plans are being made to ensure that we don't have gaps
- like these in the future.</p>
- <p>It is also worth mentioning that things like switch
- positions are not logged historically, so you see the final
- position on the map.</p>
- </div>
- </div>
- </div>
- <div id="aboutPerformance" class="col-md-4" style="position: absolute; display:none; z-index: 130;">
- <div class="panel panel-default">
- <div class="panel-heading">
- <h3 class="panel-title">Performance
- <button type="button" class="close" aria-label="Close" onclick="document.getElementById('aboutPerformance').style.display = 'none';" style="float: right">
- <span aria-hidden="true">&times;</span>
- </button>
- </h3>
- </div>
- <table class="table">
- <tr>
- <td>Outstanding AJAX requests</td>
- <td id="outstandingAJAX"></td>
- </tr>
- <tr>
- <td>Overflowed AJAX requests</td>
- <td id="overflowAJAX"></td>
- </tr>
- </table>
- <div class="panel-body">
- <p>NMS performance is surprisingly complex. It's split into
- several parts and dealt with differently.</p>
- <p>Poller performance is a matter of efficiently collecting
- data and is mostly handled in the Perl code (and ensuring
- we use sensible database schemas).</p>
- <p>Backend performance for the GUI is mostly about not
- killing the database server. We do NOT try to protect
- against malicious clients directly, since this is a
- management system not public-facing, but Varnish is used to
- cache requests. To be able to do that properly, we need use
- absolute time when reviewing past events (so "2015-04-02
- 17:30:00", not "2 hours ago"). We've also tried to minimize
- the stupidity in the queries. There's still work to be done
- here, though, as we need to split up a few large backend
- requests (port-state.pl).</p>
- <p>Front-end performance is mostly about drawing things
- sensibly and not completely bombing the memory usage. And
- about gracefully handling slow backends This will affect
- you. For example, if you are reviewing past events and the
- DB is struggling, we'll simply skip a backend request if we
- have too many outstanding requests, that means you may jump
- from "17:00" to "18:30" instead of going through
- "17:30" and "18:00" too. This is working as intended. It
- also means that you can happily spam the forward/backward
- keyboard bindings to jump 18 hours forward: You'll overflow
- the extra AJAX requests for individual requests, but you'll
- land at the right time when you let go. But there could be
- a 1 second delay (or more if the backend really struggles)
- since you'll have to rely on the periodic backend requests
- instead of the explicit ones triggered on hitting a
- button.</p>
- <p>Note that the counters on top are updated on a timer,
- but this timer is set up at the same time as everything
- else, which means that it's likely to update at the same
- time as we fire off AJAX requests, so the 'outstanding ajax
- requests' counter might either show almost constantly 3 or
- 0 depending on what timer happens to fire first. This does
- NOT mean that NMS has 3 requests all the time, just that
- we're checking right after we fire off AJAX requests every
- time.</p>
- <p>NMS also tries to handle drawing OK, which is why things
- are split into different HTML5 canvases. Blur and text are
- particularly expensive, but there's no reason to re-paint
- that all the time, etc).</p>
- <p>The basic performance experiments are done on TG15 data
- using a laptop and a VM with 6GB of memory, so it should
- hold up quite well on "proper" hardware.</p>
- </div>
- </div>
- </div>
<div id="aboutKeybindings" class="col-md-4" style="position: absolute; display:none; z-index: 130;">
<div class="panel panel-default">
<div class="panel-heading">
@@ -327,186 +221,9 @@
</div>
</div>
</div>
- <div style="position: absolute; z-index: 120;" class="col-md-4">
- <div id="info-switch-parent" class="panel panel-default col-d-6" style="display: none; backgroun:silver; position: absolute; z-index: 120;">
- <div class="panel-heading">
- <h3 class="panel-title" id="info-switch-title"></h3>
- </div>
- <div id="info-switch-panel-body">
- <table class="table" id="info-switch-table"></table>
- </div>
- </div>
- </div>
- <div id="aboutBox" class="col-md-4" style="display: none; position: absolute; z-index: 100;">
- <div id="abotBox" class="panel panel-default">
- <div class="panel-heading">
- <h3 class="panel-title">Welcome to NMS
- <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('aboutBox').style.display = 'none';" style="float: right;">
- <span aria-hidden="true">&times;</span>
- </button>
- </h3>
- </div>
- <div class="panel-body">
- <h3>Cool stuff:</h3>
- <ul>
- <li>Click a switch for more info</li>
- <li>Rewind: You can check out state at a specific time or
- replay from the beginning of the event. Only works for
- data where we keep time-series (so not for
- comments)</li>
- <li>Press '?' to toggle the menu.</li>
- <li>Auto-scaling the viewport/canvas</li>
- <li>Total client speed (up right)</li>
- <li>Generic(-ish) map handlers: provide a name, init-function
- and an update-function and the nms lib does the rest as far as
- integration goes.</li>
- </ul>
- <h3>Todo list front end:</h3>
- <ul>
- <li>Polish time travel UI (Allow playing from a given time at a given speed, play/pause buttons, etc)</li>
- <li>Better "popup" boxes: It's growing out of control.</li>
- <li>Toggle auto-scale on/off</li>
- <li>Clean up various global variables</li>
- <li>Create name spaces in nms.*: It's just barely better
- than global stuff now.</li>
- <li>Add DHCP map</li>
- <li>More info on switches: Port state, possibly link time
- trends</li>
- <li>Moving switches around (like ping.html + edit)</li>
- <li>Split nms.js into multiple components to unclutter the
- code</li>
- <li>Comments: Fix UTF8 garbligash caused by $dbh-&gt;quote()</li>
- </ul>
- <h3>Todo for backend:</h3>
- <ul>
- <li>IPv6 support</li>
- <li>Provide public API's</li>
- <li>Investigate a json tree filter/massager</li>
- <li>Close SQL injections (IT'S WIDE OPEN BECAUSE WHY NOT THAT'S NEVER A PROBLEM)</li>
- <li>Split port-state.pl into multiple appropriate pieces. Right
- it mixes heavy time-critical data with less time-critical and
- cheap computation.</li>
- <li>Rip comments out of port-state.pl completely so it's not
- bound by the same cache issues and can be reliably
- refreshed.</li>
- <li>Consider time log of DHCP (right now it just stores the
- most recent timestamp, making time travel impossible)</li>
- <li>Fix SNMP-fetcher so it gets ifXTable and at least
- ifOperStatus from ifTable. Don't request the entire
- ifXTable if we can avoid it. Possibly other
- tweaks.</li>
- <li>Support for adding switches through an API, not just pure SQL.</li>
- <li>Integrate with FAP</li>
- <li>Clean up old interfaces</li>
- <li>Review various agents/tools</li>
- <li>Improve cache headers</li>
- <li>Cache invalidation of comments? (Probably not needed)</li>
- <li>Re-test the SQL schema. It's been modified and works fine
- on my laptop, but I need to dump it, commit it and test it.</li>
- <li>Munin plugin for ports.</li>
- </ul>
- </div>
- </div>
- </div>
- <div id="blurManic" class="panel panel-default" style="display: none; position: absolute; z-index: 100;">
- <div class="panel-heading">
- <h1 class="panel-title">Blur tweaks
- <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('blurManic').style.display = 'none';" style="float: right;">
- <span aria-hidden="true">&times;</span>
- </button>
- </h1>
- </div>
- <div class="panel-body">
- <div class="form">
- <div class="form-group">
- <label for="shadowBlur">Blur strength</label>
- <input type="number" id="shadowBlur" class="form-control">
- </div>
- <div class="form-group">
- <label for="shadowColor">Blur color</label>
- <input type="color" id="shadowColor" class="form-control">
- </div>
- <button type="button" class="btn btn-default" onclick="applyBlur();">Apply</button>
- </div>
- </div>
- </div>
- </div>
- <div id="debugTimers" class="panel panel-default" style="display: none; position: absolute; z-index: 100;">
- <div class="panel-heading">
- <h1 class="panel-title">Debug timers (e.g.: Break stuff! FAST!)
- <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('debugTimers').style.display = 'none';" style="float: right;">
- <span aria-hidden="true">&times;</span>
- </button>
- </h1>
- </div>
- <div id="timerTableTop" class="panel-body">
- <p>These are internal timers for the NMS frontend. They are
- provided mainly to debug the frontend. Setting AJAX-triggering
- counters to ridiculous numbers is not advised (mainly because
- it causes server load).</p>
- </div>
- <table id="timerTable"> </table>
- </div>
- <div id="layerVisibility" class="panel panel-default" style="display: none; position: absolute; z-index: 100;">
- <div class="panel-heading">
- <h1 class="panel-title">Set layer visibility
- <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('layerVisibility').style.display = 'none';" style="float: right;">
- <span aria-hidden="true">&times;</span>
- </button>
- </h1>
- </div>
- <div class="panel-body">
- <table id="visibilityTable" class="table">
- <tr>
- <td>Background</td>
- <td>
- <button onclick='hideLayer("bgCanvas");'>Hide</button>
- <button onclick='showLayer("bgCanvas");'>Show</button>
- </td>
- </tr>
- <tr>
- <td>Linknets</td>
- <td>
- <button onclick='hideLayer("linkCanvas");'>Hide</button>
- <button onclick='showLayer("linkCanvas");'>Show</button>
- </td>
- </tr>
- <tr>
- <td>Blur</td>
- <td>
- <button onclick='hideLayer("blurCanvas");'>Hide</button>
- <button onclick='showLayer("blurCanvas");'>Show</button>
- </td>
- </tr>
- <tr>
- <td>Switches</td>
- <td>
- <button onclick='hideLayer("switchCanvas");'>Hide</button>
- <button onclick='showLayer("switchCanvas");'>Show</button>
- </td>
- </tr>
- <tr>
- <td>Text</td>
- <td>
- <button onclick='hideLayer("textCanvas");'>Hide</button>
- <button onclick='showLayer("textCanvas");'>Show</button>
- </td>
- </tr>
- <tr>
- <td>TextInfo</td>
- <td>
- <button onclick='hideLayer("textInfoCanvas");'>Hide</button>
- <button onclick='showLayer("textInfoCanvas");'>Show</button>
- </td>
- </tr>
- <tr>
- <td>Timestamp</td>
- <td>
- <button onclick='hideLayer("topCanvas");'>Hide</button>
- <button onclick='showLayer("topCanvas");'>Show</button>
- </td>
- </tr>
- </table>
+ <div id="info-switch-parent" class="panel panel-default col-md-5" style="display: none; position: absolute; z-index: 120;">
+ <div class="panel-heading"> <h3 class="panel-title" id="info-switch-title"></h3> </div>
+ <div id="info-switch-panel-body"> <table class="table" id="info-switch-table"></table> </div>
</div>
</div>
@@ -517,7 +234,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 +243,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
index 4dda8e4..155b5a8 100644
--- a/web/nms.gathering.org/js/nms-data.js
+++ b/web/nms.gathering.org/js/nms-data.js
@@ -1,81 +1,166 @@
"use strict";
-/**************************************************************************
- * *
- * THIS IS WORK IN PROGRESS, NOT CURRENTLY USED! *
- * *
- * It WILL eventually replace large chunks of nms.js. But we're not there *
- * yet. *
- * *
- **************************************************************************/
-
-
/*
* This file/module/whatever is an attempt to gather all data collection in
* one place.
*
- * It is work in progress.
- *
* 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.
*
- * Public interfaces:
- * nmsData.data[name] - actual data
+ * 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 || {
- data: {}, // Actual data
- sources: {},
-
- // Tracks metdata (hashes/timestamps)
- poller: {
- hashes:{},
- time:{}
- },
- // setInterval handlers (and more?)
- pollers: {
-
- },
+ old: {}, // Single copy of previous data. Automatically populated.
stats: {
identicalFetches:0,
outstandingAjaxRequests:0,
ajaxOverflow:0,
+ pollClearsEmpty:0,
pollClears:0,
- pollSets: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.data[name]
- * remotename: The primary attribute to get from the remote source.
+ * name: "Local" name. Maps to nmsData[name]
* target: URL of the source
- * cb: Optional callback
- * cbdata: Optional callback data
*
- * Update frequency will (eventually) be handled by parsing max-age from
- * the source. Right now it's hardcoded.
+ * 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, remotename, target, cb, cbdata) {
- if(this.pollers[name]) {
- clearInterval(this.pollers[name]);
- this.stats.pollClears++;
+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.sources[name] = { remotename: remotename, target: target, cb: cb, cbdata: cbdata };
- this.pollers[name] = setInterval(function(){nmsData.updateSource(name)}, 1000);
+
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
@@ -83,60 +168,92 @@ nmsData.registerSource = function(name, remotename, target, cb, cbdata) {
* after a comment is posted).
*/
nmsData.updateSource = function(name) {
- nmsData.genericUpdater(name,
- this.sources[name].remotename,
- this.sources[name].target,
- this.sources[name].cb,
- this.sources[name].cbdata);
+ /*
+ * See comment in nms.js nmsINIT();
+ */
+ if (name == "ticker" ) {
+ for (var i in nmsData._sources[name].cbs) {
+ var tmp = nmsData._sources[name].cbs[i];
+ if (tmp.cb != undefined) {
+ tmp.cb(tmp.cbdata);
+ }
+ }
+ return;
+ }
+ 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.data[name] with data fetched from remote target in
- * variable "remotename". If a callback is provided, it is called with
- * argument meh.
+ * Updates nmsData[name] and nmsData.old[name], issuing any callbacks where
+ * relevant.
*
- * This also populates nms.pollers[name] with the server-provided hash.
- * Only if a change is detected is the callback issued.
+ * Do not use this directly. Use updateSource().
*
- * Used by registerSource.
*/
-nmsData.genericUpdater = function(name, remotename, target, cb, meh) {
- if (this.stats.outstandingAjaxRequests > 5) {
+nmsData._genericUpdater = function(name, cacheok) {
+ if (this.stats.outstandingAjaxRequests++ > this._ajaxThreshold) {
+ this.stats.outstandingAjaxRequests--;
this.stats.ajaxOverflow++;
return;
}
- this.stats.outstandingAjaxRequests++;
var now = "";
- /*
- if (nms.now != false)
- now = "now=" + nms.now;
+ if (this._now != undefined)
+ now = "now=" + this._now;
if (now != "") {
- if (target.match("\\?"))
+ 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",
- url: target + now,
- dataType: "text",
+ headers: heads,
+ url: this._sources[name].target + now,
+ dataType: "json",
success: function (data, textStatus, jqXHR) {
- var indata = JSON.parse(data);
- if (nmsData.poller.hashes[name] != indata['hash']) {
- nmsData.data[name] = indata[remotename];
- nmsData.poller.hashes[name] = indata['hash'];
- nmsData.poller.time[name] = indata['time'];
- if (cb != undefined) {
- cb(meh);
+ 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--;
- //updateAjaxInfo();
}
});
};
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..b559ba2
--- /dev/null
+++ b/web/nms.gathering.org/js/nms-info-box.js
@@ -0,0 +1,369 @@
+"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();
+ nmsInfoBox._snmpHide();
+}
+
+/*
+ * 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, "red");
+ window.setTimeout(function(){
+ nmsMap.setSwitchColor(id, "pink");
+ window.setTimeout(function(){
+ nmsMap.setSwitchColor(id, "red");
+ window.setTimeout(function(){
+ nmsMap.setSwitchColor(id, "pink");
+ window.setTimeout(function(){
+ nmsMap.setSwitchColor(id, "red");
+ window.setTimeout(function(){
+ nmsMap.setSwitchColor(id, "pink");
+ },300);
+ },300);
+ },300);
+ },300);
+ },300);
+ el.parentElement.classList.remove("has-error");
+ el.parentElement.classList.add("has-success");
+ } else {
+ el.parentElement.classList.add("has-error");
+ }
+}
+
+nmsInfoBox._snmp = function(x,tree)
+{
+
+ nmsInfoBox._snmpHide();
+ var container = document.createElement("div");
+ container.id = "nmsInfoBox-snmp-show";
+
+ var swtop = document.getElementById("info-switch-parent");
+ var output = document.createElement("output");
+ output.id = "edit-output";
+ output.style = "white-space: pre;";
+ try {
+ output.value = JSON.stringify(nmsData.snmp.snmp[x][tree],null,4);
+ } catch(e) {
+ output.value = "(no recent data (yet)?)";
+ }
+ container.appendChild(output);
+ swtop.appendChild(container);
+}
+/*
+ * 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> <button type="button" class="edit btn btn-xs btn-default" onclick="nmsInfoBox._snmp(\'' + x + '\',\'ports\');">Ports</button> <button type="button" class="edit btn btn-xs btn-default" onclick="nmsInfoBox._snmp(\'' + x + '\',\'misc\');">Misc</button> ' + x + ' <button type="button" class="close" aria-label="Close" onclick="nmsInfoBox.hide();" style="float: right;"><span aria-hidden="true">&times;</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._snmpHide = function() {
+ var container = document.getElementById("nmsInfoBox-snmp-show");
+ if (container != undefined)
+ container.parentNode.removeChild(container);
+}
+
+nmsInfoBox._edit = function(sw) {
+ var template = {};
+ var place = false;
+ nmsInfoBox._editHide();
+ nmsInfoBox._snmpHide();
+ 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..fa33bad 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,57 +10,52 @@
* 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.
+ *
*/
/*
- * Handlers. "updater" is run periodically when the handler is active, and
- * "init" is run once when it's activated.
*/
var handler_uplinks = {
- updater:uplinkUpdater,
init:uplinkInit,
tag:"uplink",
name:"Uplink map"
};
var handler_temp = {
- updater:tempUpdater,
init:tempInit,
tag:"temp",
name:"Temperature map"
};
var handler_ping = {
- updater:pingUpdater,
init:pingInit,
tag:"ping",
name:"IPv4 Ping map"
};
var handler_traffic = {
- updater:trafficUpdater,
init:trafficInit,
tag:"traffic",
name:"Uplink traffic map"
};
var handler_traffic_tot = {
- updater:trafficTotUpdater,
init:trafficTotInit,
tag:"traffictot",
name:"Switch traffic map"
};
var handler_disco = {
- updater:randomizeColors,
init:discoInit,
tag:"disco",
name:"Disco fever"
};
var handler_comment = {
- updater:commentUpdater,
init:commentInit,
tag:"comment",
name:"Fresh comment spotter"
@@ -79,38 +73,36 @@ var handlers = [
/*
* Update function for uplink map
- * Run periodically when uplink map is active.
*/
function uplinkUpdater()
{
- if (!nms.switches_now["switches"])
+ if (!nmsData.switches)
return;
- for (sw in nms.switches_now["switches"]) {
+ if (!nmsData.switches.switches)
+ return;
+ if (!nmsData.switchstate)
+ return;
+ if (!nmsData.switchstate.switches)
+ return;
+ for (sw in nmsData.switches.switches) {
var uplinks=0;
- for (port in nms.switches_now["switches"][sw]["ports"]) {
- if (!nms.switches_then["switches"][sw]["ports"] ||
- !nms.switches_now["switches"][sw]["ports"])
- 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))
- {
- if (parseInt(nms.switches_then["switches"][sw]["ports"][port]["ifhcoutoctets"]) != parseInt(nms.switches_now["switches"][sw]["ports"][port]["ifhcoutoctets"])) {
- uplinks += 1;
- }
- }
+ if (nmsData.switchstate.switches[sw] == undefined || nmsData.switchstate.switches[sw].uplinks == undefined) {
+ uplinks=0;
+ } else {
+ uplinks = nmsData.switchstate.switches[sw].uplinks.live;
+ nuplinks = nmsData.switchstate.switches[sw].uplinks.total;
}
+
if (uplinks == 0) {
- setSwitchColor(sw,"white");
- } else if (uplinks == 1) {
- setSwitchColor(sw,red);
- } else if (uplinks == 2) {
- setSwitchColor(sw, orange);
- } else if (uplinks == 3) {
- setSwitchColor(sw, green);
+ nmsMap.setSwitchColor(sw,"white");
+ } else if (nuplinks == uplinks) {
+ nmsMap.setSwitchColor(sw,green);
+ } else if (nuplinks - uplinks == 1) {
+ nmsMap.setSwitchColor(sw, orange);
+ } else if (nuplinks - uplinks == 2) {
+ nmsMap. setSwitchColor(sw, red);
} else if (uplinks > 3) {
- setSwitchColor(sw, blue);
+ nmsMap.setSwitchColor(sw, blue);
}
}
}
@@ -120,10 +112,12 @@ function uplinkUpdater()
*/
function uplinkInit()
{
+ nmsData.addHandler("switches","mapHandler",uplinkUpdater);
+ nmsData.addHandler("switchstate","mapHandler",uplinkUpdater);
setLegend(1,"white","0 uplinks");
- setLegend(2,red,"1 uplink");
- setLegend(3,orange,"2 uplinks");
- setLegend(4,green,"3 uplinks");
+ setLegend(2,red,"2 missing");
+ setLegend(3,orange,"1 missing");
+ setLegend(4,green,"0 missing");
setLegend(5,blue,"4 uplinks");
}
@@ -168,9 +162,6 @@ function trafficUpdater()
}
}
-/*
- * Init-function for uplink map
- */
function trafficTotInit()
{
var m = 1024 * 1024 / 8;
@@ -256,24 +247,20 @@ function tempInit()
function pingUpdater()
{
- if (!nms.ping_data || !nms.ping_data["switches"]) {
- resetColors();
+ if (nmsData.switches == undefined || nmsData.switches.switches == undefined) {
return;
}
- for (var sw in nms.switches_now["switches"]) {
- var c = blue;
- if (nms.ping_data['switches'] && nms.ping_data['switches'][sw])
- c = gradient_from_latency(nms.ping_data["switches"][sw]["latency"]);
- setSwitchColor(sw, c);
- }
- for (var ln in nms.switches_now["linknets"]) {
- var c1 = blue;
- var c2 = c1;
- if (nms.ping_data['linknets'] && nms.ping_data['linknets'][ln]) {
- c1 = gradient_from_latency(nms.ping_data["linknets"][ln][0]);
- c2 = gradient_from_latency(nms.ping_data["linknets"][ln][1]);
+ for (var sw in nmsData.switches.switches) {
+ try {
+ if (nmsData.ping.switches[sw].age > 0) {
+ c = red;
+ } else {
+ c = gradient_from_latency(nmsData.ping.switches[sw].latency);
+ }
+ nmsMap.setSwitchColor(sw, c);
+ } catch (e) {
+ nmsMap.setSwitchColor(sw, blue);
}
- setLinknetColors(ln, c1, c2);
}
}
@@ -285,68 +272,88 @@ function pingInit()
setLegend(3,gradient_from_latency(60),"60ms");
setLegend(4,gradient_from_latency(100),"100ms");
setLegend(5,gradient_from_latency(undefined) ,"No response");
+ nmsData.addHandler("ping","mapHandler",pingUpdater);
+ nmsData.addHandler("switches","mapHandler",pingUpdater);
+ nmsData.addHandler("ticker", "mapHandler", pingUpdater);
}
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++;
+ }
+ if (then > (now - (60*15))) {
+ c = red;
+ } else if (active > 0) {
+ c = orange;
+ } else if (persist > 0) {
+ c = blue;
+ } else {
+ c = green;
}
- setSwitchColor(sw, c);
+ 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 discoDo() {
+ randomizeColors();
+ setTimeout(randomizeColors,500);
+}
function discoInit()
{
+ nmsData.addHandler("ticker", "mapHandler", discoDo);
+
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..d855320
--- /dev/null
+++ b/web/nms.gathering.org/js/nms-map.js
@@ -0,0 +1,496 @@
+"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,
+ _init: true,
+ _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._loadEvent = function(e) {
+ nmsMap._init = false;
+ nmsMap._drawAllSwitches();
+}
+nmsMap.init = function() {
+ this._initContexts();
+ this._init = true;
+ nmsData.addHandler("switches","nmsMap",function(){nmsMap._resizeEvent();});
+ window.addEventListener('resize',nmsMap._resizeEvent,true);
+ document.addEventListener('load',nmsMap._loadEvent,true);
+}
+
+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' && a != 'blur')
+ continue;
+ nmsMap._c[a].c.height = nmsMap._canvas.height;
+ nmsMap._c[a].c.width = nmsMap._canvas.width;
+ }
+ if (nmsMap._init != true) {
+ console.log("Drawing shit");
+ nmsMap._blurDrawn = false;
+ 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;
+ if (this._init == true) {
+ return;
+ }
+ if (!toggle)
+ this._c.blur.c.style.display = "none";
+ else {
+ this._drawAllBlur();
+ this._c.blur.c.style.display = "";
+ }
+ 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._drawSwitchBlur = function(sw)
+{
+ if (nmsData.switches == undefined || nmsData.switches.switches == undefined)
+ return;
+ var box = this._getBox(sw);
+ this._c.blur.ctx.save();
+ this._c.blur.ctx.fillStyle = "red";
+ this._c.blur.ctx.shadowBlur = 30;
+ this._c.blur.ctx.shadowColor = "white";
+ this._c.blur.ctx.scale(this.scale, this.scale); // FIXME
+ this._c.blur.ctx.fillRect(box['x'],box['y'],box['width'],box['height']);
+ this._c.blur.ctx.restore();
+}
+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);
+ }
+ if (this._nightmode)
+ this._drawAllBlur();
+}
+
+nmsMap._drawAllBlur = function() {
+ if (nmsMap._blurDrawn == true)
+ return;
+ nmsMap._blurDrawn = true;
+ for (var sw in nmsData.switches.switches) {
+ nmsMap._drawSwitchBlur(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 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);
+ }
+}
+
+
diff --git a/web/nms.gathering.org/js/nms.js b/web/nms.gathering.org/js/nms.js
index 6061a0f..1270a7f 100644
--- a/web/nms.gathering.org/js/nms.js
+++ b/web/nms.gathering.org/js/nms.js
@@ -1,76 +1,33 @@
+"use strict";
var nms = {
stats:{}, // Various internal stats
- 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
- comments:{}, // Switch comments
- poller:{hashes:{},time:{}}, // Tracks generic poller hashes/timestamps
- 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,
+ 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,
@@ -86,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
+ }
};
/*
@@ -136,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++;
@@ -239,7 +111,7 @@ function byteCount(bytes) {
*/
function toggleNightMode()
{
- setNightMode(!nms.nightMode);
+ nms.nightMode = !nms.nightMode;
saveSettings();
}
@@ -312,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
*
@@ -389,218 +267,35 @@ nms.playback.stepTime = function(n)
*/
nms.playback.tick = function()
{
- 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;
- }
+ nms.playback.replayTime = getNowEpoch();
- // 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[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">&times;</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;
- if (nms.comments[x] == undefined) {
- nms.comments[x] = {};
- nms.comments[x]["comments"] = [];
- }
- for (var c in nms.comments[x]["comments"]) {
- var comment = nms.comments[x]["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);
}
/*
@@ -618,180 +313,35 @@ 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();
- fo.init();
- nms.updater = fo;
+ nmsMap.reset();
+ nmsData.unregisterHandlerWildcard("mapHandler");
+ try {
+ fo.init();
+ } catch (e) {
+ /*
+ * This can happen typically on initial load where the data
+ * hasn't been retrieved yet. Instead of breaking the
+ * entire init-process, just bail out here.
+ */
+ console.log("Possibly broken handler: " + fo.name);
+ console.log(e);
+ }
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)
@@ -822,18 +372,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");
}
});
}
@@ -851,428 +397,12 @@ function addComment(sw,comment)
dataType: "text",
data:myData,
success: function (data, textStatus, jqXHR) {
- nms.repop_switch = true;
- }
- });
-}
-
-/*
- * FIXME: Not at all done.
- *
- * genericUpdater() should be something one registers for, then it
- * automatically picks up intervals based on max-age from the backend, and
- * allows forced updates (E.g.: force polling comments after a comment is
- * added, force polling switches after switches has been changed).
- *
- */
-function doMiscUpdates() {
- genericUpdater("comments","comments", "/api/private/comments");
- genericUpdater("switches_management", "switches", "/api/private/switches-management");
-}
-
-function nmsStatsInc(stat) {
- if (nms.stats[stat] == undefined)
- nms.stats[stat] = 0;
- nms.stats[stat]++;
-}
-/*
- * Updates nms[name] with data fetched from remote target in variable
- * "remotename". If a callback is provided, it is called with argument meh.
- *
- * This also populates nms.pollers[name] with the server-provided hash.
- * Only if a change is detected is the callback issued.
- */
-function genericUpdater(name, remotename, target, cb, meh) {
- if (nms.outstandingAjaxRequests > 5) {
- nms.ajaxOverflow++;
- updateAjaxInfo();
- return;
- }
- nms.outstandingAjaxRequests++;
- var now = "";
- if (nms.now != false)
- now = "now=" + nms.now;
- if (now != "") {
- if (target.match("\\?"))
- now = "&" + now;
- else
- now = "?" + now;
- }
-
- $.ajax({
- type: "GET",
- url: target + now,
- dataType: "text",
- success: function (data, textStatus, jqXHR) {
- var indata = JSON.parse(data);
- if (nms.poller.hashes[name] != indata['hash']) {
- nms[name] = indata[remotename];
- nms.poller.hashes[name] = indata['hash'];
- nms.poller.time[name] = indata['time'];
- if (cb != undefined) {
- cb(meh);
- }
- } else {
- nmsStatsInc("identicalFetches");
- }
- },
- 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();
+ nmsData.invalidate("comments");
}
});
- 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
@@ -1292,11 +422,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;
}
}
@@ -1304,118 +434,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;
@@ -1423,188 +444,45 @@ 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");
+
+ // This is a magic dummy-source, it's purpose is to give a unified
+ // way to get ticks every second. It is mainly meant to allow map
+ // handlers to register for ticks so they will execute without data
+ // (and thus notice stale data instead of showing a green ping-map
+ // despite no pings)
+ nmsData.registerSource("ticker","bananabananbanana");
+
+ // Private
+ nmsData.registerSource("snmp","/api/private/snmp");
+ nmsData.registerSource("comments", "/api/private/comments");
+ nmsData.registerSource("smanagement","/api/private/switches-management");
+
+ restoreSettings();
+ nmsMap.init();
detectHandler();
nms.playback.play();
setupKeyhandler();
- restoreSettings();
}
function detectHandler() {
@@ -1618,91 +496,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;
@@ -1764,13 +563,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") {
@@ -1824,42 +616,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);
+ }
+ });
}
diff --git a/web/nms.gathering.org/test.html b/web/nms.gathering.org/test.html
deleted file mode 100644
index 49b240b..0000000
--- a/web/nms.gathering.org/test.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<html>
-<body>
-this is just used for prototyping js/nms-data.js and similar.
-<script src="js/jquery.min.js" >
-</script>
-<script src="js/nms-data.js" >
-</script>
-</body>
-</html>