aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKristian Lyngstol <kristian@bohemians.org>2015-06-23 14:26:30 +0200
committerKristian Lyngstol <kristian@bohemians.org>2015-06-23 14:26:30 +0200
commita495b7094845f2f484b8e6e0ea0b7077bbb790a5 (patch)
treedcdc5ff650850675b11d3919d5c13dda8afb4c9d
parentc8a1ab2f825348969ffe72a20f562b329e271db1 (diff)
parentaf9a801eb6c21c1c9087f1f8f910b98c4ad1ecc6 (diff)
Merge branch 'master' of 192.168.122.1:tgmanage
-rw-r--r--README.md41
-rwxr-xr-xinclude/nms.pm1
-rwxr-xr-xtools/deplist.sh22
-rwxr-xr-xtools/dotnet.sh9
-rwxr-xr-xtools/draw-neighbors.pl35
-rwxr-xr-xtools/get_mibs.sh18
-rwxr-xr-xtools/lldpdiscover.pl317
-rw-r--r--web/nms.gathering.org/index.html2
-rw-r--r--web/nms.gathering.org/nms2/index.html46
-rw-r--r--web/nms.gathering.org/nms2/js/nms-map-handlers.js3
-rw-r--r--web/nms.gathering.org/nms2/js/nms.js128
11 files changed, 566 insertions, 56 deletions
diff --git a/README.md b/README.md
index cde5b19..2cd7bb5 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,44 @@
Tools, hacks, scripts and other things used by Tech:Server to keep things running smoothly during The Gathering.
Licensed under the GNU GPL, version 2. See the included COPYING file.
+
+# Dependencies
+
+For the perl stuff, you may need the following Debian packages:
+
+- libcapture-tiny-perl
+- libcgi-pm-perl
+- libcommon-sense-perl
+- libdata-dumper-simple-perl
+- libdbi-perl
+- libdigest-perl
+- libgd-perl
+- libgeo-ip-perl
+- libhtml-parser-perl
+- libhtml-template-perl
+- libimage-magick-perl
+- libimage-magick-q16-perl
+- libjson-perl
+- libjson-xs-perl
+- libnetaddr-ip-perl
+- libnet-cidr-perl
+- libnet-ip-perl
+- libnet-openssh-perl
+- libnet-oping-perl
+- libnet-rawip-perl
+- libnet-telnet-cisco-perl
+- libnet-telnet-perl
+- libsnmp-perl
+- libsocket6-perl
+- libsocket-perl
+- libswitch-perl
+- libtimedate-perl
+- perl
+- perl-base
+- perl-modules
+
+`apt-get install libcapture-tiny-perl libcgi-pm-perl libcommon-sense-perl libdata-dumper-simple-perl libdbi-perl libdigest-perl libgd-perl libgeo-ip-perl libhtml-parser-perl libhtml-template-perl libimage-magick-perl libimage-magick-q16-perl libjson-perl libjson-xs-perl libnetaddr-ip-perl libnet-cidr-perl libnet-ip-perl libnet-openssh-perl libnet-oping-perl libnet-rawip-perl libnet-telnet-cisco-perl libnet-telnet-perl libsnmp-perl libsocket6-perl libsocket-perl libswitch-perl libtimedate-perl perl perl-base perl-modules`
+
+You will also need SNMP mibs. For conveneince:
+
+`tools/get_mibs.sh`
diff --git a/include/nms.pm b/include/nms.pm
index c61cec7..4012cc6 100755
--- a/include/nms.pm
+++ b/include/nms.pm
@@ -25,6 +25,7 @@ BEGIN {
# cd /usr/share/mibs/site
# wget -O- ftp://ftp.cisco.com/pub/mibs/v2/v2.tar.gz | sudo tar --strip-components=3 -zxvvf -
SNMP::initMib();
+ SNMP::addMibDirs("../mibs");
SNMP::loadModules('SNMPv2-MIB');
SNMP::loadModules('ENTITY-MIB');
SNMP::loadModules('IF-MIB');
diff --git a/tools/deplist.sh b/tools/deplist.sh
new file mode 100755
index 0000000..cd2ecc0
--- /dev/null
+++ b/tools/deplist.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# Generate a dependency list for debian packages needed to work
+#
+# This is ... somewhat extensive. And a good incentive for people to clean
+# up their mess.
+
+(
+cat <<_EOF_
+use lib '../include';
+use lib '../web/streamlib';
+_EOF_
+find ../ -name '*pl' -exec egrep '^use ' {} \; | sort | uniq
+cat <<_EOF_
+foreach my \$key (keys %INC) {
+ if (\$INC{\$key} =~ m/^\./) {
+ next;
+ }
+ print \$INC{\$key} . "\n";
+}
+_EOF_
+) | perl 2>/dev/null | xargs realpath | xargs dpkg -S | awk '{print $1}' | sed 's/:$//' | sort | uniq
diff --git a/tools/dotnet.sh b/tools/dotnet.sh
new file mode 100755
index 0000000..5c1b369
--- /dev/null
+++ b/tools/dotnet.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+DATE="$(date +%s)"
+if [ -z "$1" ] || [ -z "$2" ]; then
+ echo "Usage: $0 <ip> <community>"
+ exit 1;
+fi
+./lldpdiscover.pl $1 $2 | ./draw-neighbors.pl | dot -Tpng > dotnet-${DATE}.png
+echo File name: dotnet-${DATE}.png
diff --git a/tools/draw-neighbors.pl b/tools/draw-neighbors.pl
new file mode 100755
index 0000000..323e676
--- /dev/null
+++ b/tools/draw-neighbors.pl
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+use strict;
+use JSON;
+
+my $in;
+while (<STDIN>) {
+ $in .= $_;
+}
+
+my %assets = %{JSON::XS::decode_json($in)};
+
+print "strict graph network {\n";
+while (my ($key, $value) = each %assets) {
+ print_tree ($key,0,undef);
+}
+print "}\n";
+
+sub print_tree
+{
+ my ($chassis_id,$indent,$parent,$max) = @_;
+ if (!defined($parent)) {
+ $parent = "";
+ }
+ if ($indent > 50) {
+ die "Possible loop detected.";
+ }
+ print " \"$assets{$chassis_id}{sysName}\" -- {";
+ my @n;
+ while (my ($key, $value) = each %{$assets{$chassis_id}{neighbors}}) {
+ push @n, "\"$assets{$key}{sysName}\"";
+ }
+ print join(",",@n) . "};\n";
+}
+
diff --git a/tools/get_mibs.sh b/tools/get_mibs.sh
new file mode 100755
index 0000000..cab9fa3
--- /dev/null
+++ b/tools/get_mibs.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+MIBS="SNMPv2 ENTITY IF LLDP IP IP-FORWARD"
+ORIGPWD=$PWD
+TMP=$(mktemp -d)
+set -x
+set -e
+cd $TMP
+wget ftp://ftp.cisco.com/pub/mibs/v2/v2.tar.gz
+tar xvzf v2.tar.gz --strip-components=2
+mkdir -p mibs
+
+for a in $MIBS; do
+ cp v2/$a-MIB.my mibs/
+done
+mv mibs ${ORIGPWD}/
+cd ${ORIGPWD}
+rm -rf ${TMP}
diff --git a/tools/lldpdiscover.pl b/tools/lldpdiscover.pl
new file mode 100755
index 0000000..f3df093
--- /dev/null
+++ b/tools/lldpdiscover.pl
@@ -0,0 +1,317 @@
+#! /usr/bin/perl
+#
+# Basic tool to discover your neighbourhood systems, using LLDP, as seen
+# through SNMP.
+#
+# Usage: ./lldpdiscover.pl <ip> <community>
+#
+# This will connect to <ip> and poll it for SNMP-data, then add that to an
+# asset database. After that's done, we parse the LLDP neighbor table
+# provided over SNMP and add those systems to assets, then try to probe
+# THEM with SNMP, using the same community, and so on.
+#
+# If the entire internet exposed LLDP and SNMP in a public domain, we could
+# theoretically map the whole shebang.
+#
+# Note that leaf nodes do NOT need to reply to SNMP to be added, but
+# without SNMP, there'll obviously be some missing data.
+#
+# The output is a JSON blob of all assets, indexed by chassis id. It also
+# includes a neighbor table for each asset which can be used to generate a
+# map (See dotnet.sh or draw-neighbors.pl for examples). It can also be
+# used to add the assets to NMS.
+#
+# A sensible approach might be to run this periodically, store the results
+# to disk, then have multiple tools parse the results.
+use POSIX;
+use Time::HiRes;
+use strict;
+use warnings;
+use Data::Dumper;
+
+use lib '../include';
+use nms;
+
+# Actual assets detected, indexed by chassis ID
+my %assets;
+
+# Tracking arrays. Continue scanning until they are of the same length.
+my @chassis_ids_checked;
+my @chassis_ids_to_check;
+
+# If we are given one switch on the command line, add that and then exit.
+my ($cmdline_ip, $cmdline_community) = @ARGV;
+if (defined($cmdline_ip) && defined($cmdline_community)) {
+ my $chassis_id;
+ eval {
+ # Special-case for the first switch is to fetch chassis id
+ # directly. Everything else is fetched from a neighbour
+ # table.
+ my $session = nms::snmp_open_session($cmdline_ip, $cmdline_community);
+ $chassis_id = get_lldp_chassis_id($session);
+ $assets{$chassis_id}{'community'} = $cmdline_community;
+ @{$assets{$chassis_id}{'v4mgmt'}} = ($cmdline_ip);
+ @{$assets{$chassis_id}{'v6mgmt'}} = ();
+ push @chassis_ids_to_check, $chassis_id;
+ };
+ if ($@) {
+ mylog("Error during SNMP : $@");
+ exit 1;
+ }
+
+ # Welcome to the main loop!
+ while (scalar @chassis_ids_to_check > scalar @chassis_ids_checked) {
+ # As long as you call it something else, it's not really a
+ # goto-statement, right!?
+ OUTER: for my $id (@chassis_ids_to_check) {
+ for my $id2 (@chassis_ids_checked) {
+ if ($id2 eq $id) {
+ next OUTER;
+ }
+ }
+ mylog("Adding $id");
+ add_switch($id);
+ mylog("Discovering neighbors for $id");
+ discover_lldp_neighbors($id);
+ push @chassis_ids_checked,$id;
+ }
+ }
+ print JSON::XS::encode_json(\%assets);
+ # Creates corrupt output, hooray.
+# print JSON::XS->new->pretty(1)->encode(\%assets);
+ exit;
+} else {
+ print "RTFSC\n";
+}
+# Filter out stuff we don't scan. Return true if we care about it.
+# XXX: Several of these things are temporary to test (e.g.: AP).
+sub filter {
+ my %sys = %{$_[0]};
+ my %caps = %{$sys{'lldpRemSysCapEnabled'}};
+ my $sysdesc = $sys{'lldpRemSysDesc'};
+ my $sysname = $sys{'lldpRemSysName'};
+
+ if ($caps{'cap_enabled_ap'}) {
+ return 1;
+ }
+ if ($caps{'cap_enabled_telephone'}) {
+ return 0;
+ }
+ if (!defined($sysdesc)) {
+ return 1;
+ }
+ if ($sysdesc =~ /\b(C1530|C3600|C3700)\b/) {
+ return 0;
+ }
+ if (!$caps{'cap_enabled_bridge'} && !$caps{'cap_enabled_router'}) {
+ return 1;
+ }
+ if ($sysname =~ /BCS-OSL/) {
+ return 1;
+ }
+ return 1;
+}
+
+# Discover neighbours of a switch. The data needed is already present int
+# %assets , so this shouldn't cause any extra SNMP requests. It will add
+# new devices as it finds them.
+sub discover_lldp_neighbors {
+ my $local_id = $_[0];
+ my $ip = $assets{$local_id}{mgmt};
+ my $local_sysname = $assets{$local_id}{snmp}{sysName};
+ my $community = $assets{$local_id}{community};
+ my $addrtable;
+ while (my ($key, $value) = each %{$assets{$local_id}{snmp_parsed}{lldpRemTable}}) {
+ my $chassis_id = $value->{'lldpRemChassisId'};
+ my $sysname = $value->{'lldpRemSysName'};
+ if (!defined($sysname)) {
+ $sysname = $chassis_id;
+ }
+
+ # Do not try to poll servers.
+ if (!filter(\%{$value})) {
+ mylog("Filtered out $sysname ($local_sysname -> $sysname)");
+ next;
+ }
+
+ # Pull in the management address table lazily.
+ $addrtable = $assets{$local_id}{snmp_parsed}{lldpRemManAddrTable}{$key};
+
+ # Search for this key in the address table.
+ my @v4addrs = ();
+ my @v6addrs = ();
+ while (my ($addrkey, $addrvalue) = each %$addrtable) {
+ my $addr = $addrvalue->{'lldpRemManAddr'};
+ my $addrtype = $addrvalue->{'lldpRemManAddrSubtype'};
+ if ($addrtype == 1) {
+ push @v4addrs, $addr;
+ } elsif ($addrtype == 2) {
+ my $v6addr = $addr;
+ next if $v6addr =~ /^fe80:/; # Ignore link-local.
+ push @v6addrs, $v6addr;
+ } else {
+ die "Unknown address type $addr";
+ }
+ }
+ my $addr;
+ if (scalar @v6addrs > 0) {
+ $addr = $v6addrs[0];
+ } elsif (scalar @v4addrs > 0) {
+ $addr = $v4addrs[0];
+ } else {
+ mylog( "Could not find a management address for chassis ID $chassis_id (sysname=$sysname, lldpRemIndex=$key)");
+ # We still want to add these weirdo-things, but
+ # they wont do much good except fill the map.
+ }
+
+ mylog("Found $sysname ($local_sysname -> $sysname )");
+ $sysname =~ s/\..*$//;
+ if (defined($assets{$chassis_id}{'sysName'})) {
+ mylog("Duplicate $sysname: \"$sysname\" vs \"$assets{$chassis_id}{'sysName'}\"");
+ if ($assets{$chassis_id}{'sysName'} eq "") {
+ $assets{$chassis_id}{'sysName'} = $sysname;
+ }
+ } else {
+ $assets{$chassis_id}{'sysName'} = $sysname;
+ }
+
+ # FIXME: We should handle duplicates better and for more
+ # than just sysname. These happen every time we are at
+ # least one tier down (given A->{B,C,D,E}, switch B, C, D
+ # and E will all know about A, thus trigger this). We also
+ # want to _add_ information only, since two nodes might
+ # know about the same switch, but one might have incomplete
+ # information (as is the case when things start up).
+
+ # We simply guess that the community is the same as ours.
+ $assets{$chassis_id}{'community'} = $community;
+ @{$assets{$chassis_id}{'v4mgmt'}} = @v4addrs;
+ @{$assets{$chassis_id}{'v6mgmt'}} = @v6addrs;
+
+ $assets{$chassis_id}{'neighbors'}{$local_id} = 1;
+ $assets{$local_id}{'neighbors'}{$chassis_id} = 1;
+ check_neigh($chassis_id);
+ }
+}
+
+sub mylog {
+ my $msg = shift;
+ my $time = POSIX::ctime(time);
+ $time =~ s/\n.*$//;
+ printf STDERR "[%s] %s\n", $time, $msg;
+}
+
+# Get raw SNMP data for an ip/community.
+# FIXME: This should be seriously improved. Three get()'s and four
+# gettables could definitely be streamlined, but then again, I doubt it
+# matters much unless we start running this tool constantly.
+sub get_snmp_data {
+ my ($ip, $community) = @_;
+ my %ret = ();
+ eval {
+ my $session = nms::snmp_open_session($ip, $community);
+ $ret{'sysName'} = $session->get('sysName.0');
+ $ret{'sysDescr'} = $session->get('sysDescr.0');
+ $ret{'lldpRemManAddrTable'} = $session->gettable("lldpRemManAddrTable");
+ $ret{'lldpRemTable'} = $session->gettable("lldpRemTable");
+ $ret{'ifTable'} = $session->gettable('ifTable', columns => [ 'ifType', 'ifDescr' ]);
+ $ret{'ifXTable'} = $session->gettable('ifXTable', columns => [ 'ifHighSpeed', 'ifName' ]);
+ $ret{'lldpLocChassisId'} = $session->get('lldpLocChassisId.0');
+ };
+ if ($@) {
+ mylog("Error during SNMP to $ip : $@");
+ return undef;
+ }
+ return \%ret;
+}
+
+# Filter raw SNMP data over to something more legible.
+# This is the place to add all post-processed results so all parts of the
+# tool can use them.
+sub parse_snmp
+{
+ my $snmp = $_[0];
+ my %result = ();
+ while (my ($key, $value) = each %{$snmp}) {
+ $result{$key} = $value;
+ }
+ $result{lldpLocChassisId} = nms::convert_mac($snmp->{'lldpLocChassisId'});
+ while (my ($key, $value) = each %{$snmp->{lldpRemTable}}) {
+ my $id = $key;
+ my $chassis_id = nms::convert_mac($value->{'lldpRemChassisId'});
+ my $sysname = $value->{'lldpRemSysName'};
+ foreach my $key2 (keys %$value) {
+ $result{lldpRemTable}{$id}{$key2} = $value->{$key2};
+ }
+ $result{lldpRemTable}{$id}{'lldpRemChassisId'} = $chassis_id;
+ my %caps = ();
+ nms::convert_lldp_caps($value->{'lldpRemSysCapEnabled'}, \%caps);
+ $result{lldpRemTable}{$id}{'lldpRemSysCapEnabled'} = \%caps;
+ }
+ $result{lldpRemManAddrTable} = ();
+ while (my ($key, $value) = each %{$snmp->{lldpRemManAddrTable}}) {
+ my %tmp = ();
+ foreach my $key2 (keys %$value) {
+ $tmp{$key2} = $value->{$key2};
+ }
+ my $addr = $value->{'lldpRemManAddr'};
+ my $addrtype = $value->{'lldpRemManAddrSubtype'};
+ if ($addrtype == 1) {
+ $tmp{lldpRemManAddr} = nms::convert_ipv4($addr);
+ } elsif ($addrtype == 2) {
+ $tmp{lldpRemManAddr} = nms::convert_ipv6($addr);
+ }
+ my $id = $value->{lldpRemTimeMark} . "." . $value->{lldpRemLocalPortNum} . "." . $value->{lldpRemIndex};
+ my $id2 = $tmp{lldpRemManAddr};
+ $result{lldpRemManAddrTable}{$id}{$id2} = \%tmp;
+ }
+ return \%result;
+}
+
+# Add a chassis_id to the list to be checked, but only if it isn't there.
+# I'm sure there's some better way to do this, but meh, perl. Doesn't even
+# have half-decent prototypes.
+sub check_neigh {
+ my $n = $_[0];
+ for my $v (@chassis_ids_to_check) {
+ if ($v eq $n) {
+ return 0;
+ }
+ }
+ push @chassis_ids_to_check,$n;
+ return 1;
+}
+
+# We've got a switch. Populate it with SNMP data (if we can).
+sub add_switch {
+ my $chassis_id = shift;
+ my @addrs;
+ push @addrs, @{$assets{$chassis_id}{'v4mgmt'}};
+ push @addrs, @{$assets{$chassis_id}{'v6mgmt'}};
+ my $addr;
+ my $snmp = undef;
+ while (my $key = each @addrs ) {
+ $addr = $addrs[$key];
+ mylog("Probing $addr");
+ $snmp = get_snmp_data($addr, $assets{$chassis_id}{'community'});
+ if (defined($snmp)) {
+ last;
+ }
+
+ }
+ return if (!defined($snmp));
+ my $sysname = $snmp->{sysName};
+ $sysname =~ s/\..*$//;
+ $assets{$chassis_id}{'sysName'} = $sysname;
+ $assets{$chassis_id}{'mgmt'} = $addr;
+ $assets{$chassis_id}{'snmp'} = $snmp;
+ $assets{$chassis_id}{'snmp_parsed'} = parse_snmp($snmp);
+ $assets{$chassis_id}{'chassis_id_x'} = nms::convert_mac($snmp->{'lldpLocChassisId'});
+ return;
+}
+
+sub get_lldp_chassis_id {
+ my ($session) = @_;
+ my $response = $session->get('lldpLocChassisId.0');
+ return nms::convert_mac($response);
+}
diff --git a/web/nms.gathering.org/index.html b/web/nms.gathering.org/index.html
index ffcb09b..8043c7d 100644
--- a/web/nms.gathering.org/index.html
+++ b/web/nms.gathering.org/index.html
@@ -6,7 +6,7 @@
<p>tg light &amp; magic. :-)</p>
<ul>
- <li><a href="nms2/map.html">NMS 2 NG (Ping, Temp, Uplink, Trafikk, Info)</a>
+ <li><a href="nms2/">NMS 2 NG (Ping, Temp, Uplink, Trafikk, Info)</a>
<br /><i>Trykk på svitsjene (Work in progress)</i>
</li>
<br />
diff --git a/web/nms.gathering.org/nms2/index.html b/web/nms.gathering.org/nms2/index.html
index 304cece..81f68e5 100644
--- a/web/nms.gathering.org/nms2/index.html
+++ b/web/nms.gathering.org/nms2/index.html
@@ -46,12 +46,11 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
- <a class="navbar-brand" onclick="toggleLayer('aboutBox'); //document.getElementById('aboutBox').style.display = 'block'; hideSwitch();" style="cursor: pointer;" >NMS</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Map mode
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Menu
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
@@ -66,45 +65,36 @@
<li><a href="#" onclick="toggleLayer('nowPickerBox');document.getElementById('nowPicker').focus();">Travel in time</a></li>
<li><a href="#" onclick="startReplay();" title="Replay from opening 120 minutes per second">Replay TG</a></li>
<li class="divider"> </li>
- <li><a onclick="showTimerDebug(); hideSwitch();" style="cursor: pointer;" >Debug timers</a></li>
- </ul>
- </li>
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">View<span class="caret"></span></a>
- <ul class="dropdown-menu" role="menu">
+ <li class="dropdown-header">View</li>
<li><a href="#" onclick="toggleNightMode()">Toggle Night Mode</a></li>
<li><a href="#" onclick="showBlurBox()">Tweak Night Mode blur</a></li>
<li><a href="#" onclick='showLayer("layerVisibility");'>Set layer visibility</a></li>
<li class="divider"> </li>
<li class="dropdown-header">Map scale</li>
<li><a href="#"><label id="scaler-text" for='scaler'></label><input type="range" id="scaler" name="points" min="0.2" max="3" step="0.01" onchange="scaleChange()" /></a></li>
+ <li class="divider"> </li>
+ <li class="dropdown-header">Help</li>
+ <li><a href="#" onclick="toggleLayer('aboutData');">About TG15 data</a></li>
+ <li><a href="#" onclick="toggleLayer('aboutBox');" >About NMS</a></li>
+ <li><a href="#" onclick="toggleLayer('aboutPerformance');" >About Performance</a></li>
+ <li><a href="#" onclick="toggleLayer('aboutKeybindings');" >Keyboard Shortcuts</a></li>
+ <li><a onclick="showTimerDebug(); hideSwitch();" style="cursor: pointer;" >Debug timers</a></li>
</ul>
</li>
<li><p id="updater_name" class="navbar-text"></p></li>
<div class="navbar-form navbar-left">
<div class="form-group">
- <button class="btn btn-default" id="legend-1"></button>
- <button class="btn btn-default" id="legend-2"></button>
- <button class="btn btn-default" id="legend-3"></button>
- <button class="btn btn-default" id="legend-4"></button>
- <button class="btn btn-default" id="legend-5"></button>
+ <button class="btn btn-default btn-xs" id="legend-1"></button>
+ <button class="btn btn-default btn-xs" id="legend-2"></button>
+ <button class="btn btn-default btn-xs" id="legend-3"></button>
+ <button class="btn btn-default btn-xs" id="legend-4"></button>
+ <button class="btn btn-default btn-xs" id="legend-5"></button>
</div>
</div>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><p id="speed" class="navbar-text" title="Client port speed / Total port speed"></p></li>
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Help
- <span class="caret"></span>
- </a>
- <ul class="dropdown-menu" role="menu">
- <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>
- </ul>
- </li>
</ul>
</div><!--/.nav-collapse -->
</div>
@@ -494,6 +484,13 @@
</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>
@@ -509,6 +506,7 @@
<canvas id="blurCanvas" width="1920" height="1032" style="position: absolute; z-index: 20;"> </canvas>
<canvas id="switchCanvas" width="1920" height="1032" style="position: absolute; z-index: 30;"> </canvas>
<canvas id="textCanvas" width="1920" height="1032" style="position: absolute; z-index: 40;"> </canvas>
+ <canvas id="textInfoCanvas" width="1920" height="1032" style="position: absolute; z-index: 45;"> </canvas>
<canvas id="topCanvas" width="1920" height="1032" style="position: absolute; z-index: 50;"> </canvas>
<canvas id="inputCanvas" width="1920" height="1032" style="position: absolute; z-index: 60; cursor: pointer;" onclick="canvasClick(event)">
</canvas>
diff --git a/web/nms.gathering.org/nms2/js/nms-map-handlers.js b/web/nms.gathering.org/nms2/js/nms-map-handlers.js
index 9b2d44c..ddb50a1 100644
--- a/web/nms.gathering.org/nms2/js/nms-map-handlers.js
+++ b/web/nms.gathering.org/nms2/js/nms-map-handlers.js
@@ -232,11 +232,14 @@ function tempUpdater()
{
for (sw in nms.switches_now["switches"]) {
var t = "white";
+ var temp = "";
if (nms.switches_now["switches"][sw]["temp"]) {
t = temp_color(nms.switches_now["switches"][sw]["temp"]);
+ temp = nms.switches_now["switches"][sw]["temp"] + "°C";
}
setSwitchColor(sw, t);
+ switchInfoText(sw, temp);
}
}
diff --git a/web/nms.gathering.org/nms2/js/nms.js b/web/nms.gathering.org/nms2/js/nms.js
index d4cf4e8..30d6ba8 100644
--- a/web/nms.gathering.org/nms2/js/nms.js
+++ b/web/nms.gathering.org/nms2/js/nms.js
@@ -7,6 +7,8 @@ var nms = {
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,
@@ -23,7 +25,8 @@ var nms = {
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:"Arial Black",
+ 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
@@ -203,6 +206,9 @@ function initDrawing() {
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');
@@ -649,6 +655,7 @@ function setUpdater(fo)
{
nms.updater = undefined;
resetColors();
+ resetTextInfo();
fo.init();
nms.updater = fo;
var foo = document.getElementById("updater_name");
@@ -932,8 +939,8 @@ function updateSpeed()
nms.speed = speed_in;
nms.speed_full = speed_full;
if (speedele) {
- speedele.innerHTML = byteCount(8 * parseInt(nms.speed)) + "bit/s";
- speedele.innerHTML += " / " + byteCount(8 * parseInt(nms.speed_full)) + "bit/s";
+ speedele.innerHTML = byteCount(8 * parseInt(nms.speed)) + "b/s";
+ speedele.innerHTML += " / " + byteCount(8 * parseInt(nms.speed_full)) + "b/s";
}
}
@@ -1008,10 +1015,11 @@ function drawSwitch(sw)
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(sw,box['x'],box['y'],box['width'],box['height']);
- else
- drawRegular(sw,box['x'],box['y'],box['width'],box['height']);
+ 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;
}
@@ -1046,6 +1054,15 @@ function drawSwitches()
}
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
@@ -1063,9 +1080,9 @@ function drawNow()
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(2 * canvas.scale);
+ dr.top.ctx.lineWidth = Math.floor(nms.fontLineFactor * canvas.scale);
if (dr.top.ctx.lineWidth == 0) {
- dr.top.ctx.lineWidth = Math.round(2 * canvas.scale);
+ 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);
@@ -1083,8 +1100,10 @@ function drawNow()
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();
}
/*
@@ -1096,11 +1115,17 @@ 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();
@@ -1192,6 +1217,15 @@ function resetColors()
}
}
+function resetTextInfo()
+{
+ if (!nms.switches_now)
+ return;
+ for (var sw in nms.switches_now.switches) {
+ switchInfoText(sw, undefined);
+ }
+
+}
/*
* onclick handler for the canvas.
*
@@ -1259,6 +1293,26 @@ function setNightMode(toggle) {
}
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).
*/
@@ -1269,9 +1323,9 @@ function drawBox(x,y,boxw,boxh)
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(0.5 * canvas.scale);
+ dr.switch.ctx.lineWidth = Math.floor(1.0 * canvas.scale);
if (canvas.scale < 1.0) {
- dr.switch.ctx.lineWidth = 0.5;
+ dr.switch.ctx.lineWidth = 1.0;
}
dr.switch.ctx.strokeStyle = "#000000";
dr.switch.ctx.strokeRect(myX,myY, myX2, myY2);
@@ -1293,19 +1347,25 @@ function drawBoxBlur(x,y,boxw,boxh)
*
* XXX: This is pretty nasty and should also probably take a box as input.
*/
-function drawSideways(text,x,y,w,h)
+function drawSideways(ctx,text,x,y,w,h,align)
{
- dr.text.ctx.rotate(Math.PI * 3 / 2);
- dr.text.ctx.fillStyle = "white";
- dr.text.ctx.strokeStyle = "black";
- dr.text.ctx.lineWidth = Math.floor(3 * canvas.scale);
- if (dr.text.ctx.lineWidth == 0) {
- dr.text.ctx.lineWidth = Math.round(3 * canvas.scale);
+ 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);
}
- dr.text.ctx.strokeText(text, - canvas.scale * (y + h - margin.text),canvas.scale * (x + w - margin.text) );
- dr.text.ctx.fillText(text, - canvas.scale * (y + h - margin.text),canvas.scale * (x + w - margin.text) );
+ 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) );
- dr.text.ctx.rotate(Math.PI / 2);
+ ctx.rotate(Math.PI / 2);
}
/*
@@ -1336,16 +1396,22 @@ function invertCanvas() {
*
* XXX: Both should be renamed to have 'text' or something in them
*/
-function drawRegular(text,x,y,w,h) {
-
- dr.text.ctx.fillStyle = "white";
- dr.text.ctx.strokeStyle = "black";
- dr.text.ctx.lineWidth = Math.floor(3 * canvas.scale);
- if (dr.text.ctx.lineWidth == 0) {
- dr.text.ctx.lineWidth = Math.round(3 * canvas.scale);
+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);
}
- dr.text.ctx.strokeText(text, (x + margin.text) * canvas.scale, (y + h - margin.text) * canvas.scale);
- dr.text.ctx.fillText(text, (x + margin.text) * canvas.scale, (y + h - margin.text) * 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);
}
/*
@@ -1397,7 +1463,7 @@ function initNMS() {
nms.timers.ping = new nmsTimer(updatePing, 1000, "Ping updater", "AJAX request to update ping data");
nms.timers.ping.start();
- nms.timers.replay = new nmsTimer(timeReplay, 500, "Time machine", "Handler used to change time");
+ nms.timers.replay = new nmsTimer(timeReplay, 1000, "Time machine", "Handler used to change time");
detectHandler();
updatePorts();
updatePing();