diff options
author | Kristian Lyngstol <kristian@bohemians.org> | 2015-06-23 14:26:30 +0200 |
---|---|---|
committer | Kristian Lyngstol <kristian@bohemians.org> | 2015-06-23 14:26:30 +0200 |
commit | a495b7094845f2f484b8e6e0ea0b7077bbb790a5 (patch) | |
tree | dcdc5ff650850675b11d3919d5c13dda8afb4c9d | |
parent | c8a1ab2f825348969ffe72a20f562b329e271db1 (diff) | |
parent | af9a801eb6c21c1c9087f1f8f910b98c4ad1ecc6 (diff) |
Merge branch 'master' of 192.168.122.1:tgmanage
-rw-r--r-- | README.md | 41 | ||||
-rwxr-xr-x | include/nms.pm | 1 | ||||
-rwxr-xr-x | tools/deplist.sh | 22 | ||||
-rwxr-xr-x | tools/dotnet.sh | 9 | ||||
-rwxr-xr-x | tools/draw-neighbors.pl | 35 | ||||
-rwxr-xr-x | tools/get_mibs.sh | 18 | ||||
-rwxr-xr-x | tools/lldpdiscover.pl | 317 | ||||
-rw-r--r-- | web/nms.gathering.org/index.html | 2 | ||||
-rw-r--r-- | web/nms.gathering.org/nms2/index.html | 46 | ||||
-rw-r--r-- | web/nms.gathering.org/nms2/js/nms-map-handlers.js | 3 | ||||
-rw-r--r-- | web/nms.gathering.org/nms2/js/nms.js | 128 |
11 files changed, 566 insertions, 56 deletions
@@ -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 & 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(); |