diff options
-rw-r--r-- | web/nms.gathering.org/nms2/index.html | 23 | ||||
-rw-r--r-- | web/nms.gathering.org/nms2/js/nms-color-util.js | 54 | ||||
-rw-r--r-- | web/nms.gathering.org/nms2/js/nms-map-handlers.js | 283 | ||||
-rw-r--r-- | web/nms.gathering.org/nms2/js/nms.js | 600 | ||||
-rwxr-xr-x | web/nms.gathering.org/ping-json2.pl | 2 | ||||
-rwxr-xr-x | web/nms.gathering.org/port-state.pl | 16 | ||||
-rwxr-xr-x | web/nms.gathering.org/switch-comment.pl | 25 |
7 files changed, 625 insertions, 378 deletions
diff --git a/web/nms.gathering.org/nms2/index.html b/web/nms.gathering.org/nms2/index.html index 211bcf9..aeda611 100644 --- a/web/nms.gathering.org/nms2/index.html +++ b/web/nms.gathering.org/nms2/index.html @@ -57,6 +57,7 @@ <li><a href="#uplink" onclick="setUpdater(handler_uplinks)">Uplink map</a></li> <li><a href="#temp" onclick="setUpdater(handler_temp)">Temperature map</a></li> <li><a href="#traffic" onclick="setUpdater(handler_traffic)">Traffic map</a></li> + <li><a href="#comment" onclick="setUpdater(handler_comment)">Comment spotter</a></li> <li><a href="#disco" onclick="setUpdater(handler_disco)">DISCO</a></li> <li class="divider"> </li> <li><a href="#" onclick="toggleNightMode()" title="Add 'nightMode' anywhere in the url to auto-enable">Toggle Night Mode</a></li> @@ -86,7 +87,7 @@ </li> </ul> <ul class="nav navbar-nav navbar-right"> - <li><p id="speed" class="navbar-text" title="Client port speed"></p></li> + <li><p id="speed" class="navbar-text" title="Client port speed / Total port speed"></p></li> </ul> </div><!--/.nav-collapse --> </div> @@ -135,6 +136,7 @@ <button class="btn" onclick="document.getElementById('nowPickerBox').style.display = 'none';">Cancel</button> </div> </div> + <div style="position: fixed; z-index: 120;"> <div id="info-switch-parent" class="panel panel-default" style="display: none; backgroun:silver; position: fixed; z-index: 120;"> <div class="panel-heading"><h3 class="panel-title" id="info-switch-title"></h3></div> @@ -144,6 +146,7 @@ </div> </div> </div> + </div> <div id="aboutBox" class="panel panel-default" style="display: none; position: fixed; z-index: 100;"> <div class="panel-heading"><h3 class="panel-title">Welcome to NMS <button type="button" class="close" aria-labe="Close" onclick="document.getElementById('aboutBox').style.display = 'none';" style="float: right;"><span aria-hidden="true">×</span></button></h3></div> @@ -170,12 +173,9 @@ not used)</li> <li>Add DHCP map</li> <li>Add magic map (combined map of sorts)</li> - <li>Add better control panel stuff (Review timers, change some, etc)</li> <li>Adjust updatePorts() frequency based on necessity (1sec updates is overkill for regular operation, but needed for time travel)</li> - <li>Fix legend display (There's a legend for all handlers, but - it's currently hidden beneath the canvas)</li> <li>More info on switches: Port state, possibly link time trends)</li> <li>Moving switches around (like ping.html + edit)</li> @@ -185,16 +185,21 @@ </ul> <h3>Todo for backend:</h3> <ul> - <li>Fix horrible SQL :D</li> + <li>IPv6 support</li> <li>Close SQL injections (IT'S WIDE OPEN BECAUSE WHY NOT THAT'S NEVER A PROBLEM)</li> - <li>Fix SNMP-fetcher so it gets ifXTable and at least ifOperStatus from ifTable. Possibly other tweaks.</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>Comments. Allow two type of comments: switch-specific + comments and "global". Possibly ticket-like support. Ooooooooh, + jira support? :P </li> <li>Integrate with FAP</li> <li>Clean up old interfaces</li> <li>Review various agents/tools</li> <li>Improve cache headers</li> - <li>Consider time log of DHCP (right now it just stores the - most recent timestamp, making time travel impossible)</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> @@ -228,6 +233,8 @@ <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/bootstrap.min.js" type="text/javascript"></script> <script type="text/javascript" src="js/nms.js"></script> + <script type="text/javascript" src="js/nms-color-util.js"></script> + <script type="text/javascript" src="js/nms-map-handlers.js"></script> <script type="text/javascript"> initNMS(); </script> diff --git a/web/nms.gathering.org/nms2/js/nms-color-util.js b/web/nms.gathering.org/nms2/js/nms-color-util.js new file mode 100644 index 0000000..28f7e1b --- /dev/null +++ b/web/nms.gathering.org/nms2/js/nms-color-util.js @@ -0,0 +1,54 @@ + +function gradient_from_latency(latency_ms, latency_secondary_ms) +{ + if (latency_secondary_ms === undefined) { + return rgb_from_latency(latency_ms); + } + return 'linear-gradient(' + + rgb_from_latency(latency_ms) + ', ' + + rgb_from_latency(latency_secondary_ms) + ')'; +} + +function rgb_from_latency(latency_ms) +{ + if (latency_ms === null || latency_ms === undefined) { + return '#0000ff'; + } + + var l = latency_ms / 40.0; + if (l >= 2.0) { + return 'rgb(255, 0, 0)'; + } else if (l >= 1.0) { + l = 2.0 - l; + l = Math.pow(l, 1.0/2.2); + l = Math.floor(l * 255.0); + return 'rgb(255, ' + l + ', 0)'; + } else { + l = Math.pow(l, 1.0/2.2); + l = Math.floor(l * 255.0); + return 'rgb(' + l + ', 255, 0)'; + } +} + +/* + * Give us a color from blue (0) to red (100). + */ +function rgb_from_max(x) +{ + x = x/100; + var colorred = 255 * x; + var colorblue = 255 - colorred; + + return 'rgb(' + Math.floor(colorred) + ", 0, " + Math.floor(colorblue) + ')'; +} + +/* + * Return a random-ish color (for testing) + */ +function getRandomColor() +{ + var i = Math.round(Math.random() * 5); + var colors = [ "white", "red", "pink", "yellow", "orange", "green" ]; + return colors[i]; +} + diff --git a/web/nms.gathering.org/nms2/js/nms-map-handlers.js b/web/nms.gathering.org/nms2/js/nms-map-handlers.js new file mode 100644 index 0000000..762788a --- /dev/null +++ b/web/nms.gathering.org/nms2/js/nms-map-handlers.js @@ -0,0 +1,283 @@ +/* + * 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... + * + * 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 + * always present, but until the ping-handler is active, it isn't + * 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. + */ + +/* + * 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, + name:"Uplink map" +}; + +var handler_temp = { + updater:tempUpdater, + init:tempInit, + name:"Temperature map" +}; + +var handler_ping = { + updater:pingUpdater, + init:pingInit, + name:"IPv4 Ping map" +}; + +var handler_traffic = { + updater:trafficUpdater, + init:trafficInit, + name:"Uplink traffic map" +}; + +var handler_disco = { + updater:randomizeColors, + init:discoInit, + name:"Disco fever" +}; + +var handler_comment = { + updater:commentUpdater, + init:commentInit, + name:"Fresh comment spotter" +}; +/* + * Update function for uplink map + * Run periodically when uplink map is active. + */ +function uplinkUpdater() +{ + if (!nms.switches_now["switches"]) + return; + for (sw in nms.switches_now["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 (uplinks == 0) { + setSwitchColor(sw,"blue"); + } else if (uplinks == 1) { + setSwitchColor(sw,"red"); + } else if (uplinks == 2) { + setSwitchColor(sw, "yellow"); + } else if (uplinks == 3) { + setSwitchColor(sw, "green"); + } else if (uplinks > 3) { + setSwitchColor(sw, "white"); + } + } +} + +/* + * Init-function for uplink map + */ +function trafficInit() +{ + setLegend(1,"blue","0 (N/A)"); + setLegend(5,"red", "1000Mb/s or more"); + setLegend(4,"yellow","100Mb/s to 800Mb/s"); + setLegend(3,"green", "5Mb/s to 100Mb/s"); + setLegend(2,"white","0 to 5Mb/s"); +} + +function trafficUpdater() +{ + if (!nms.switches_now["switches"]) + return; + for (sw in nms.switches_now["switches"]) { + var speed = 0; + for (port in nms.switches_now["switches"][sw]["ports"]) { + 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"][sw]["ports"][port]; + var n = nms.switches_now["switches"][sw]["ports"][port]; + speed += (parseInt(t["ifhcoutoctets"]) -parseInt(n["ifhcoutoctets"])) / (parseInt(t["time"] - n["time"])); + speed += (parseInt(t["ifhcinoctets"]) -parseInt(n["ifhcinoctets"])) / (parseInt(t["time"] - n["time"])); + } + } + var m = 1024 * 1024 / 8; + if (speed == 0) { + setSwitchColor(sw,"blue"); + } else if (speed > (1000 * m)) { + setSwitchColor(sw,"red"); + } else if (speed > (800 * m)) { + setSwitchColor(sw, "yellow"); + } else if (speed > (5 * m)) { + setSwitchColor(sw, "green"); + } else { + setSwitchColor(sw, "white"); + } + } +} + +/* + * Init-function for uplink map + */ +function uplinkInit() +{ + setLegend(1,"blue","0 uplinks"); + setLegend(2,"red","1 uplink"); + setLegend(3,"yellow","2 uplinks"); + setLegend(4,"green","3 uplinks"); + setLegend(5,"white","4 uplinks"); +} + + +/* + * Tweaked this to scale from roughly 20C to 35C. Hence the -20 and /15 + * thing (e.g., "0" is 20 and "15" is 35 by the time we pass it to + * rgb_from_max()); + */ +function temp_color(t) +{ + if (t == undefined) { + console.log("Temp_color, but temp is undefined"); + return "blue"; + } + t = parseInt(t) - 20; + t = Math.floor((t / 15) * 100); + return rgb_from_max(t); +} + +function tempUpdater() +{ + for (sw in nms.switches_now["switches"]) { + var t = "white"; + if (nms.switches_now["switches"][sw]["temp"]) { + t = temp_color(nms.switches_now["switches"][sw]["temp"]); + } + + setSwitchColor(sw, t); + } +} + +function tempInit() +{ + setLegend(1,temp_color(20),"20 °C"); + setLegend(2,temp_color(22),"22 °C"); + setLegend(3,temp_color(27),"27 °C"); + setLegend(4,temp_color(31),"31 °C"); + setLegend(5,temp_color(35),"35 °C"); +} + +function pingUpdater() +{ + 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]); + } + setLinknetColors(ln, c1, c2); + } +} + +function pingInit() +{ + setLegend(1,gradient_from_latency(1),"1ms"); + setLegend(2,gradient_from_latency(30),"30ms"); + setLegend(3,gradient_from_latency(60),"60ms"); + setLegend(4,gradient_from_latency(80),"80ms"); + setLegend(5,"#0000ff" ,"No response"); +} + +function commentUpdater() +{ + var realnow = Date.now(); + if (nms.now) { + realnow = Date.parse(nms.now); + } + var now = Math.floor(realnow / 1000); + for (var sw in nms.switches_now["switches"]) { + var c = "green"; + var s = nms.switches_now["switches"][sw]; + if (s["comments"] && s["comments"].length > 0) { + var then = 0; + c = "yellow"; + for (var v in s["comments"]) { + var then_test = parseInt(s["comments"][v]["time"]); + if (then_test > then && then_test <= now) + then = then_test; + } + if (then > (now - (60*15))) { + c = "red"; + } else if (then > (now - (120*60))) { + c = "orange"; + } else if (then < (now - (60*60*24))) { + c = "white"; + } + /* + * Special case during time travel: We have + * comments, but are not showing them yet. + */ + if (then == 0) + c = "green"; + } + setSwitchColor(sw, c); + } +} + +function commentInit() +{ + setLegend(1,"green","0 comments"); + setLegend(2,"white","1d+ old"); + setLegend(3,"red", "0 - 15m old"); + setLegend(4,"orange","15m - 120m old"); + setLegend(5,"yellow" ,"2h - 24h old"); +} +/* + * Testing-function to randomize colors of linknets and switches + */ +function randomizeColors() +{ + for (var i in nms.switches_now.linknets) { + setLinknetColors(i, getRandomColor(), getRandomColor()); + } + for (var sw in nms.switches_now.switches) { + setSwitchColor(sw, getRandomColor()); + } +} + +function discoInit() +{ + setNightMode(true); + setLegend(1,"blue","Y"); + setLegend(2,"red", "M"); + setLegend(3,"yellow","C"); + setLegend(4,"green", "A"); + setLegend(5,"white","!"); +} + diff --git a/web/nms.gathering.org/nms2/js/nms.js b/web/nms.gathering.org/nms2/js/nms.js index 97c0dc1..589f75e 100644 --- a/web/nms.gathering.org/nms2/js/nms.js +++ b/web/nms.gathering.org/nms2/js/nms.js @@ -3,7 +3,6 @@ var nms = { switches_now:undefined, // Most recent data switches_then:undefined, // 2 minutes old speed:0, // Current aggregated speed - full_speed:false, // Set to 'true' to include ALL interfaces 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). @@ -18,8 +17,15 @@ var nms = { 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:14, // This is scaled too, but 14 seems to make sense. + fontSize:16, // This is scaled too, but 16 seems to make sense. fontFace:"Arial Black", + /* + * 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, /* @@ -29,12 +35,15 @@ var nms = { */ did_update:false, /* - * Various setInterval() handlers. + * Various setInterval() handlers. See nmsTimer() for how they are + * used. + * + * Cool fact: Adding one here adds it to the 'debug timers' + * drop-down. */ - handlers: { + timers: { replay:false, ports:false, - info:false, ping:false, map:false, speed:false @@ -124,47 +133,22 @@ var margin = { text:3 }; +/* + * All of these should be moved into nms.* + * + * tgStart/tgEnd are "constants". + * replayTime is the current time as far as the replay-function is. This + * should be merged with nms.now. + * + * replayIncrement is how many seconds to add for each replay timer tick + * (e.g.: 30 minutes added for every 1 second display-time). + */ var tgStart = stringToEpoch('2015-04-01T09:00:00'); var tgEnd = stringToEpoch('2015-04-05T12:00:00'); var replayTime = 0; var replayIncrement = 30 * 60; /* - * 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, - name:"Uplink map" -}; - -var handler_temp = { - updater:tempUpdater, - init:tempInit, - name:"Temperature map" -}; - -var handler_ping = { - updater:pingUpdater, - init:pingInit, - name:"IPv4 Ping map" -}; - -var handler_traffic = { - updater:trafficUpdater, - init:trafficInit, - name:"Uplink traffic map" -}; - -var handler_disco = { - updater:randomizeColors, - init:discoInit, - name:"Disco fever" -}; - -/* * Convenience-function to populate the 'dr' structure. * * Only run once. @@ -190,6 +174,10 @@ function initDrawing() { dr['top']['ctx'] = dr['top']['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; @@ -200,11 +188,20 @@ function byteCount(bytes) { return bytes.toFixed(1) + units[i]; } +/* + * Definitely not a way to toggle night mode. Does something COMPLETELY + * DIFFERENT. + */ function toggleNightMode() { setNightMode(!nms.nightMode); } +/* + * Parse 'now' from user-input. + * + * Should probably just use stringToEpoch() instead, but alas, not yet. + */ function checkNow(now) { if (Date.parse(now)) { @@ -219,41 +216,86 @@ function checkNow(now) return false; } - +/* + * Convert back and forth between epoch. + * + * There's no particular reason why I use seconds instead of javascript + * microseconds, except to leave the mark of a C coder on this javascript + * project. + */ function stringToEpoch(t) { var ret = new Date(Date.parse(t)); return parseInt(parseInt(ret.valueOf()) / 1000); } +/* + * Have to pad with zeroes to avoid "17:5:0" instead of the conventional + * and more readable "17:05:00". I'm sure there's a better way, but this + * works just fine. + */ function epochToString(t) { var d = new Date(parseInt(t) * parseInt(1000)); - var str = d.getFullYear() + "-" + (parseInt(d.getMonth())+1) + "-" + d.getDate() + "T"; - str += d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); + var str = d.getFullYear() + "-"; + if (parseInt(d.getMonth()) < 9) + str += "0"; + str += (parseInt(d.getMonth())+1) + "-"; + if (d.getDate() < 10) + str += "0"; + str += d.getDate() + "T"; + if (d.getHours() < 10) + str += "0"; + str += d.getHours() + ":"; + if (d.getMinutes() < 10) + str += "0"; + str += d.getMinutes() + ":"; + if (d.getSeconds() < 10) + str += "0"; + str += d.getSeconds(); + return str; } - +/* + * Move 'nms.now' forward in time, unless we're at the end of the event. + * + * This is run on a timer (nms.timers.replay) every second when we are + * replaying. + */ function timeReplay() { if (replayTime >= tgEnd) { - nms.handlers.replay.stop(); + nms.timers.replay.stop(); return; } replayTime = parseInt(replayTime) + parseInt(replayIncrement); nms.now = epochToString(replayTime); - drawNow(); } +/* + * Start replaying the event. + * + * I want this to be more generic: + * - Set time + * - Set end-time + * - Start/stop/pause + * - Set speed increment + * + * Once the lib supports this, I can move 'tgStart' and 'tgEnd' to the GUI + * and just provide them as default values or templates. + */ function startReplay() { - nms.handlers.replay.stop(); + nms.timers.replay.stop(); resetColors(); replayTime = tgStart; timeReplay(); - nms.handlers.replay.start();; + nms.timers.replay.start();; } +/* + * Used to move to a specific time, but not replay. + */ function changeNow() { var newnow = checkNow(document.getElementById("nowPicker").value); if (!newnow) { @@ -277,10 +319,15 @@ function changeNow() { function hideSwitch() { var swtop = document.getElementById("info-switch-parent"); - var swpanel = document.getElementById("info-switch-panel-body"); var switchele = document.getElementById("info-switch-table"); + var comments = document.getElementById("info-switch-comments-table"); if (switchele != undefined) - swpanel.removeChild(switchele); + 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 = ""; } @@ -414,145 +461,44 @@ function switchInfo(x) td2.innerHTML = sw["management"]["poll_frequency"]; tr.appendChild(td1); tr.appendChild(td2); switchele.appendChild(tr); - - swpanel.appendChild(switchele); - swtop.style.display = 'block'; -} - -/* - * Update various info elements periodically. - */ -function updateInfo() -{ - if (!nms.drawn && nms.switches_now != undefined) { - drawSwitches(); - nms.drawn = true; - } - var speedele = document.getElementById("speed"); - speedele.innerHTML = (8 * parseInt(nms.speed) / 1024 / 1024 / 1024 ).toPrecision(5) + " Gbit/s"; -} - -/* - * Update function for uplink map - * Run periodically when uplink map is active. - */ -function uplinkUpdater() -{ - if (!nms.switches_now["switches"]) - return; - for (sw in nms.switches_now["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 (uplinks == 0) { - setSwitchColor(sw,"blue"); - } else if (uplinks == 1) { - setSwitchColor(sw,"red"); - } else if (uplinks == 2) { - setSwitchColor(sw, "yellow"); - } else if (uplinks == 3) { - setSwitchColor(sw, "green"); - } else if (uplinks > 3) { - setSwitchColor(sw, "white"); - } - } -} - -/* - * Init-function for uplink map - */ -function trafficInit() -{ - setLegend(1,"blue","0 uplink utilization"); - setLegend(5,"red", "1000Mb/s or more uplink utilization"); - setLegend(4,"yellow","100Mb/s to 800Mb/s uplink utilization"); - setLegend(3,"green", "5Mb/s to 100Mb/s uplink utilization"); - setLegend(2,"white","0 to 5Mb/s uplink utilization"); -} - -function trafficUpdater() -{ - if (!nms.switches_now["switches"]) - return; - for (sw in nms.switches_now["switches"]) { - var speed = 0; - for (port in nms.switches_now["switches"][sw]["ports"]) { - 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"][sw]["ports"][port]; - var n = nms.switches_now["switches"][sw]["ports"][port]; - speed += (parseInt(t["ifhcoutoctets"]) -parseInt(n["ifhcoutoctets"])) / (parseInt(t["time"] - n["time"])); - speed += (parseInt(t["ifhcinoctets"]) -parseInt(n["ifhcinoctets"])) / (parseInt(t["time"] - n["time"])); - } + + comments = document.createElement("table"); + comments.id = "info-switch-comments-table"; + comments.border = "1"; + comments.className = "table col-md-6"; + var cap = document.createElement("caption"); + cap.innerText = "Comments"; + comments.appendChild(cap); + + tr = document.createElement("tr"); td1 = document.createElement("th"); td2 = document.createElement("th"); + td3 = document.createElement("th"); + td1.innerText = "Time"; + td2.innerText = "User"; + td3.innerText = "Comment"; + tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3); comments.appendChild(tr); + var has_comment = false; + for (var c in sw["comments"]) { + var comment = sw["comments"][c]; + has_comment = true; + if (comment["state"] == "active" || comment["state"] == "persist") { + tr = document.createElement("tr"); td1 = document.createElement("td"); td2 = document.createElement("td"); + td3 = document.createElement("td"); + td1.innerText = epochToString(comment["time"]) + " "; + td2.innerText = comment["username"] + " "; + td3.innerText = comment['comment']; + tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3); comments.appendChild(tr); + } } - var m = 1024 * 1024 / 8; - if (speed == 0) { - setSwitchColor(sw,"blue"); - } else if (speed > (1000 * m)) { - setSwitchColor(sw,"red"); - } else if (speed > (800 * m)) { - setSwitchColor(sw, "yellow"); - } else if (speed > (5 * m)) { - setSwitchColor(sw, "green"); - } else { - setSwitchColor(sw, "white"); + + swpanel.appendChild(switchele); + if (has_comment) { + swpanel.appendChild(comments); } - } -} - -/* - * Init-function for uplink map - */ -function uplinkInit() -{ - setLegend(1,"blue","0 uplinks"); - setLegend(2,"red","1 uplink"); - setLegend(3,"yellow","2 uplinks"); - setLegend(4,"green","3 uplinks"); - setLegend(5,"white","4 uplinks"); -} - -/* - * Give us a color from blue (0) to red (100). - */ -function rgb_from_max(x) -{ - x = x/100; - var colorred = 255 * x; - var colorblue = 255 - colorred; - - return 'rgb(' + Math.floor(colorred) + ", 0, " + Math.floor(colorblue) + ')'; -} - -/* - * Tweaked this to scale from roughly 20C to 35C. Hence the -20 and /15 - * thing (e.g., "0" is 20 and "15" is 35 by the time we pass it to - * rgb_from_max()); - */ -function temp_color(t) -{ - if (t == undefined) { - console.log("Temp_color, but temp is undefined"); - return "blue"; - } - t = parseInt(t) - 20; - t = Math.floor((t / 15) * 100); - return rgb_from_max(t); + var commentbox = document.createElement("div"); + commentbox.id = "commentbox"; + commentbox.innerHTML = '<input type="text" placeholder="Comment" id="' + x + '-comment"><button onclick="addComment(\'' + x + '\',document.getElementById(\'' + x + '-comment\').value); document.getElementById(\'' + x + '-comment\').value = \'added. Wait for it....\';">Add comment</button>'; + swpanel.appendChild(commentbox); + swtop.style.display = 'block'; } /* @@ -569,86 +515,6 @@ function setLegend(x,color,name) el.innerHTML = name; } -function tempUpdater() -{ - for (sw in nms.switches_now["switches"]) { - var t = "white"; - if (nms.switches_now["switches"][sw]["temp"]) { - t = temp_color(nms.switches_now["switches"][sw]["temp"]); - } - - setSwitchColor(sw, t); - } -} - -function tempInit() -{ - setLegend(1,temp_color(20),"20 °C"); - setLegend(2,temp_color(22),"22 °C"); - setLegend(3,temp_color(27),"27 °C"); - setLegend(4,temp_color(31),"31 °C"); - setLegend(5,temp_color(35),"35 °C"); -} - -function gradient_from_latency(latency_ms, latency_secondary_ms) -{ - if (latency_secondary_ms === undefined) { - return rgb_from_latency(latency_ms); - } - return 'linear-gradient(' + - rgb_from_latency(latency_ms) + ', ' + - rgb_from_latency(latency_secondary_ms) + ')'; -} - -function rgb_from_latency(latency_ms) -{ - if (latency_ms === null || latency_ms === undefined) { - return '#0000ff'; - } - - var l = latency_ms / 40.0; - if (l >= 2.0) { - return 'rgb(255, 0, 0)'; - } else if (l >= 1.0) { - l = 2.0 - l; - l = Math.pow(l, 1.0/2.2); - l = Math.floor(l * 255.0); - return 'rgb(255, ' + l + ', 0)'; - } else { - l = Math.pow(l, 1.0/2.2); - l = Math.floor(l * 255.0); - return 'rgb(' + l + ', 255, 0)'; - } -} - -function pingUpdater() -{ - 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]); - } - setLinknetColors(ln, c1, c2); - } -} - -function pingInit() -{ - setLegend(1,gradient_from_latency(1),"1ms"); - setLegend(2,gradient_from_latency(30),"30ms"); - setLegend(3,gradient_from_latency(60),"60ms"); - setLegend(4,gradient_from_latency(80),"80ms"); - setLegend(5,"#0000ff" ,"No response"); -} - /* * Run periodically to trigger map updates when a handler is active */ @@ -657,6 +523,7 @@ function updateMap() if (nms.updater != undefined && nms.switches_now && nms.switches_then) { nms.updater(); } + drawNow(); } /* @@ -719,6 +586,18 @@ function updatePing() }); } +function addComment(sw,comment) { + var myData = { + switch:sw, + comment:comment}; + console.log(myData); + $.ajax({ + type: "POST", + url: "/switch-comment.pl", + dataType: "text", + data:myData + }); +} /* * Update nms.switches_now and nms.switches_then */ @@ -768,18 +647,24 @@ function updatePorts() /* * Use nms.switches_now and nms.switches_then to update 'nms.speed'. * - * nms.speed is a total of ifHCInOctets across all interfaces. + * 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 * - * if nms.full_speed is true: Include ALL interfaces - * if nms.full_speed is false: Include only e* switches and exclude - * uplinks. */ function updateSpeed() { var speed_in = parseInt(0); - var speed_kant = parseInt(0); + var speed_full = parseInt(0); var counter=0; var sw; + var speedele = document.getElementById("speed"); for (sw in nms.switches_now["switches"]) { for (port in nms.switches_now["switches"][sw]["ports"]) { if (!nms.switches_now["switches"][sw]["ports"][port]) { @@ -804,8 +689,9 @@ function updateSpeed() if (then == 0 || now == 0 || diffval == 0 || diffval == NaN) { continue; } - if (nms.full_speed || (( /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 (nms.full_speed || !( + 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) || @@ -814,10 +700,15 @@ function updateSpeed() counter++; } } - //speed_in += parseInt(diffval/diff) / 1024 ; } } 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"; + + } } /* @@ -929,21 +820,24 @@ function drawSwitches() /* * Draw current time-window + * + * FIXME: The math here is just wild approximation and guesswork because + * I'm lazy. */ function drawNow() { - if (nms.now != false) { - dr.top.ctx.font = Math.round(2 * nms.fontSize * canvas.scale) + "px " + nms.fontFace; - dr.top.ctx.clearRect(0,0,Math.floor(400 * canvas.scale),Math.floor(60 * canvas.scale)); - dr.top.ctx.fillStyle = "white"; - dr.top.ctx.strokeStyle = "black"; - dr.top.ctx.lineWidth = Math.round(2 * canvas.scale); - if (canvas.scale < 0.7) { - dr.top.ctx.lineWidth = 2; - } - dr.top.ctx.strokeText("Now: " + nms.now, 0 + margin.text, 30 * canvas.scale); - dr.top.ctx.fillText("Now: " + nms.now, 0 + margin.text, 30 * canvas.scale); + // 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(4 * canvas.scale); + if (dr.top.ctx.lineWidth == 0) { + dr.top.ctx.lineWidth = Math.round(4 * canvas.scale); } + dr.top.ctx.strokeText(now, 0 + margin.text, 30 * canvas.scale); + dr.top.ctx.fillText(now, 0 + margin.text, 30 * canvas.scale); } /* * Draw foreground/scene. @@ -1025,16 +919,6 @@ function setSwitchColor(sw, c) } /* - * Return a random-ish color (for testing) - */ -function getRandomColor() -{ - var i = Math.round(Math.random() * 5); - var colors = [ "white", "red", "pink", "yellow", "orange", "green" ]; - return colors[i]; -} - -/* * Event handler for the front-end drag bar to change scale */ function scaleChange() @@ -1053,29 +937,6 @@ function switchClick(sw) } /* - * Testing-function to randomize colors of linknets and switches - */ -function randomizeColors() -{ - for (var i in nms.switches_now.linknets) { - setLinknetColors(i, getRandomColor(), getRandomColor()); - } - for (var sw in nms.switches_now.switches) { - setSwitchColor(sw, getRandomColor()); - } -} - -function discoInit() -{ - setNightMode(true); - setLegend(1,"blue","0"); - setLegend(5,"red", "1"); - setLegend(4,"yellow","2"); - setLegend(3,"green", "3"); - setLegend(2,"white","4"); -} - -/* * Resets the colors of linknets and switches. * * Useful when mode changes so we don't re-use colors from previous modes @@ -1182,9 +1043,9 @@ function drawSideways(text,x,y,w,h) dr.text.ctx.rotate(Math.PI * 3 / 2); dr.text.ctx.fillStyle = "white"; dr.text.ctx.strokeStyle = "black"; - dr.text.ctx.lineWidth = Math.floor(1 * canvas.scale); - if (canvas.scale < 0.7) { - dr.text.ctx.lineWidth = 0.5; + dr.text.ctx.lineWidth = Math.floor(3 * canvas.scale); + if (dr.text.ctx.lineWidth == 0) { + dr.text.ctx.lineWidth = Math.round(3 * 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) ); @@ -1224,9 +1085,9 @@ function drawRegular(text,x,y,w,h) { dr.text.ctx.fillStyle = "white"; dr.text.ctx.strokeStyle = "black"; - dr.text.ctx.lineWidth = Math.floor(1 * canvas.scale); - if (canvas.scale < 0.7) { - dr.text.ctx.lineWidth = 0.5; + dr.text.ctx.lineWidth = Math.floor(3 * canvas.scale); + if (dr.text.ctx.lineWidth == 0) { + dr.text.ctx.lineWidth = Math.round(3 * 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); @@ -1263,33 +1124,38 @@ function connectSwitches(insw1, insw2,color1, color2) { dr.link.ctx.moveTo(0,0); } - +/* + * 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). + */ function initNMS() { - var url; initDrawing(); updatePorts(); updatePing(); window.addEventListener('resize',resizeEvent,true); document.addEventListener('load',resizeEvent,true); - nms.handlers.ports = new nmsTimer(updatePorts, 1000, "Port updater", "AJAX request to update port data (traffic, etc)"); - nms.handlers.ports.start(); - - nms.handlers.info = new nmsTimer(updateInfo, 5000, "Info updater", "Updates info-box about client speed (fast - no backend requests)"); - nms.handlers.info.start(); + nms.timers.ports = new nmsTimer(updatePorts, 1000, "Port updater", "AJAX request to update port data (traffic, etc)"); + nms.timers.ports.start(); - nms.handlers.ping = new nmsTimer(updatePing, 1000, "Ping updater", "AJAX request to update ping data"); - nms.handlers.ping.start(); + nms.timers.ping = new nmsTimer(updatePing, 1000, "Ping updater", "AJAX request to update ping data"); + nms.timers.ping.start(); - nms.handlers.map = new nmsTimer(updateMap, 1000, "Map handler", "Updates the map using the chosen map handler (ping, uplink, traffic, etc)"); - nms.handlers.map.start(); + nms.timers.map = new nmsTimer(updateMap, 1000, "Map handler", "Updates the map using the chosen map handler (ping, uplink, traffic, etc)"); + nms.timers.map.start(); - nms.handlers.speed = new nmsTimer(updateSpeed, 3000, "Speed updater", "Recompute total speed (no backend requests)"); - nms.handlers.speed.start(); + nms.timers.speed = new nmsTimer(updateSpeed, 1000, "Speed updater", "Recompute total speed (no backend requests)"); + nms.timers.speed.start(); - nms.handlers.replay = new nmsTimer(timeReplay, 1000, "Time machine", "Handler used to change time"); + nms.timers.replay = new nmsTimer(timeReplay, 1000, "Time machine", "Handler used to change time"); + detectHandler(); +} - url = document.URL; +function detectHandler() { + var url = document.URL; if (/#ping/.exec(url)) { setUpdater(handler_ping); }else if (/#uplink/.exec(url)) { @@ -1298,6 +1164,8 @@ function initNMS() { setUpdater(handler_temp); } else if (/#traffic/.exec(url)) { setUpdater(handler_traffic); + } else if (/#comment/.exec(url)) { + setUpdater(handler_comment); } else if (/#disco/.exec(url)) { setUpdater(handler_disco); } else { @@ -1308,6 +1176,11 @@ function initNMS() { } } +/* + * Display and populate the dialog box for debugging timers. + * + * Could probably be cleaned up. + */ function showTimerDebug() { var tableTop = document.getElementById('timerTableTop'); var table = document.getElementById('timerTable'); @@ -1321,43 +1194,38 @@ function showTimerDebug() { table.classList.add("table"); table.classList.add("table-default"); table.border = "1"; - tr = document.createElement("tr"); - td = document.createElement("th"); - td.innerHTML = "Handler"; - tr.appendChild(td); - td = document.createElement("th"); - td.innerHTML = "Interval (ms)"; - tr.appendChild(td); - td = document.createElement("th"); - td.innerHTML = "Name"; - tr.appendChild(td); - td = document.createElement("th"); - td.innerHTML = "Description"; - tr.appendChild(td); - table.appendChild(tr); - for (var v in nms.handlers) { + tr = document.createElement("tr"); + td = document.createElement("th"); + td.innerHTML = "Handler"; + tr.appendChild(td); + td = document.createElement("th"); + td.innerHTML = "Interval (ms)"; + tr.appendChild(td); + td = document.createElement("th"); + td.innerHTML = "Name"; + tr.appendChild(td); + td = document.createElement("th"); + td.innerHTML = "Description"; + tr.appendChild(td); + table.appendChild(tr); + for (var v in nms.timers) { + console.log(v); tr = document.createElement("tr"); td = document.createElement("td"); - td.innerHTML = nms.handlers[v].handle; + td.innerHTML = nms.timers[v].handle; tr.appendChild(td); td = document.createElement("td"); - td.innerHTML = "<input type=\"text\" id='handlerValue" + v + "' value='" + nms.handlers[v].interval + "'>"; - td.innerHTML += "<button type=\"button\" class=\"btn btn-default\" onclick=\"nms.handlers['" + v + "'].setInterval(document.getElementById('handlerValue" + v + "').value);\">Apply</button>"; + td.innerHTML = "<input type=\"text\" id='handlerValue" + v + "' value='" + nms.timers[v].interval + "'>"; + td.innerHTML += "<button type=\"button\" class=\"btn btn-default\" onclick=\"nms.timers['" + v + "'].setInterval(document.getElementById('handlerValue" + v + "').value);\">Apply</button>"; tr.appendChild(td); td = document.createElement("td"); - td.innerHTML = nms.handlers[v].name; + td.innerHTML = nms.timers[v].name; tr.appendChild(td); td = document.createElement("td"); - td.innerHTML = nms.handlers[v].description; + td.innerHTML = nms.timers[v].description; tr.appendChild(td); table.appendChild(tr); } tableTop.appendChild(table); document.getElementById('debugTimers').style.display = 'block'; } - -function debugHandlers() { - for (var v in nms.handlers) { - console.log(nms.handlers[v]); - } -} diff --git a/web/nms.gathering.org/ping-json2.pl b/web/nms.gathering.org/ping-json2.pl index e46140a..2ffe650 100755 --- a/web/nms.gathering.org/ping-json2.pl +++ b/web/nms.gathering.org/ping-json2.pl @@ -16,7 +16,7 @@ my $when =" updated > " . $now . " - '15 secs'::interval and updated < " . $now my %json = (); -my $q = $dbh->prepare("SELECT DISTINCT ON (updated, sysname) updated,sysname, latency_ms FROM ping NATURAL JOIN switches WHERE $when ORDER BY updated DESC;"); +my $q = $dbh->prepare("SELECT DISTINCT ON (sysname) updated,sysname, latency_ms FROM ping NATURAL JOIN switches WHERE updated in (select max(updated) from ping where $when group by switch)"); $q->execute(); while (my $ref = $q->fetchrow_hashref()) { $json{'switches'}{$ref->{'sysname'}}{'latency'} = $ref->{'latency_ms'}; diff --git a/web/nms.gathering.org/port-state.pl b/web/nms.gathering.org/port-state.pl index 76dd7d8..83e15f1 100755 --- a/web/nms.gathering.org/port-state.pl +++ b/web/nms.gathering.org/port-state.pl @@ -21,7 +21,7 @@ my $when =" time > " . $now . " - '5m'::interval and time < " . $now . " "; my %json = (); if (defined($cin)) { - $when = " time < " . $now . " - '$cin'::interval and time > ". $now . " - ('$cin'::interval + '15m'::interval) "; + $when = " time < " . $now . " - '$cin'::interval and time > ". $now . " - ('$cin'::interval + '5m'::interval) "; } 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 ' . $when . ' group by switch,ifname);'; @@ -37,7 +37,7 @@ while (my $ref = $q->fetchrow_hashref()) { } #print Dumper(%json); -my $q2 = $dbh->prepare('select switch,sysname,placement,zorder,ip,switchtype,poll_frequency,community,last_updated from switches natural join placements'); +my $q2 = $dbh->prepare('select switch,sysname,placement,ip,switchtype,poll_frequency,community,last_updated from switches natural join placements'); my $q3 = $dbh->prepare('select distinct on (switch) switch,temp,time,sysname from switch_temp natural join switches where ' . $when . ' order by switch,time desc'); $q2->execute(); @@ -54,7 +54,6 @@ while (my $ref = $q2->fetchrow_hashref()) { $json{'switches'}{$ref->{'sysname'}}{'placement'}{'y'} = $y2; $json{'switches'}{$ref->{'sysname'}}{'placement'}{'width'} = $x1 - $x2; $json{'switches'}{$ref->{'sysname'}}{'placement'}{'height'} = $y1 - $y2; - $json{'switches'}{$ref->{'sysname'}}{'placement'}{'zorder'} = $ref->{'zorder'}; } $q3->execute(); while (my $ref = $q3->fetchrow_hashref()) { @@ -70,5 +69,16 @@ while (my $ref = $q4->fetchrow_hashref()) { # push @{$json{'linknets'}}, $ref; } +my $q5 = $dbh->prepare ('select ' . $now . ' as time;'); +$q5->execute(); +$json{'time'} = $q5->fetchrow_hashref()->{'time'}; + +my $q6 = $dbh->prepare('select sysname,extract(epoch from date_trunc(\'second\',time)) as time,state,username,id,comment from switch_comments natural join switches order by time desc'); +$q6->execute(); +while (my $ref = $q6->fetchrow_hashref()) { + push @{$json{'switches'}{$ref->{'sysname'}}{'comments'}},$ref; +} + +$json{'username'} = $cgi->remote_user(); print $cgi->header(-type=>'text/json; charset=utf-8'); print JSON::XS::encode_json(\%json); diff --git a/web/nms.gathering.org/switch-comment.pl b/web/nms.gathering.org/switch-comment.pl new file mode 100755 index 0000000..262d625 --- /dev/null +++ b/web/nms.gathering.org/switch-comment.pl @@ -0,0 +1,25 @@ +#! /usr/bin/perl +use CGI qw(fatalsToBrowser); +use DBI; +use lib '../../include'; +use utf8; +use nms; +use strict; +use warnings; +use Data::Dumper; + +my $cgi = CGI->new; + +my $dbh = nms::db_connect(); + +my $data = $dbh->quote($cgi->param('comment') || die ); +my $switch = $dbh->quote($cgi->param('switch') || die ); +my $user = $dbh->quote($cgi->remote_user() || "undefined"); + + +my $q = $dbh->prepare("INSERT INTO switch_comments (time,username,switch,comment) values (now(),$user,(select switch from switches where sysname = $switch limit 1),$data)"); +$q->execute(); + +print $cgi->header(-type=>'text/json; charset=utf-8'); +print "{ 'state': 'ok' }"; + |