mirror of https://github.com/aredn/aredn.git
250 lines
8.5 KiB
Plaintext
Executable File
250 lines
8.5 KiB
Plaintext
Executable File
#!/usr/bin/lua
|
|
--[[
|
|
|
|
Copyright (C) 2022 Tim Wilkinson
|
|
See Contributors file for additional contributors
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation version 3 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Additional Terms:
|
|
|
|
Additional use restrictions exist on the AREDN(TM) trademark and logo.
|
|
See AREDNLicense.txt for more info.
|
|
|
|
Attributions to the AREDN Project must be retained in the source code.
|
|
If importing this code into a new or existing project attribution
|
|
to the AREDN project must be added to the source code.
|
|
|
|
You must not misrepresent the origin of the material contained within.
|
|
|
|
Modified versions must be modified to attribute to the original source
|
|
and be marked in reasonable ways as differentiate it from the original
|
|
version
|
|
|
|
--]]
|
|
|
|
require("uci")
|
|
require("aredn.http")
|
|
require("aredn.hardware")
|
|
local html = require("aredn.html")
|
|
require("aredn.info")
|
|
|
|
local cursor = uci.cursor()
|
|
|
|
if cursor:get("aredn", "@lqm[0]", "enable") ~= "1" then
|
|
print "Content-type: text/text\r"
|
|
print "Cache-Control: no-store\r"
|
|
print "\r"
|
|
print "Disabled"
|
|
os.exit()
|
|
end
|
|
|
|
|
|
local node = aredn.info.get_nvram("node")
|
|
local node_desc = cursor:get("system", "@system[0]", "description") or ""
|
|
local lat_lon = "<strong>Location Not Available</strong>"
|
|
local lat = cursor:get("aredn", "@location[0]", "lat")
|
|
local lon = cursor:get("aredn", "@location[0]", "lon")
|
|
if lat and lon then
|
|
lat_lon = string.format("<center><strong>Location: </strong> %s %s</center>", lat, lon)
|
|
end
|
|
|
|
http_header()
|
|
html.header(node .. " Neighbor Status")
|
|
html.print("<body>")
|
|
html.alert_banner()
|
|
html.print([[
|
|
<style>
|
|
.lt {
|
|
font-weight: bold;
|
|
padding: 30px 0 4px 0;
|
|
}
|
|
#links {
|
|
padding-bottom: 16px;
|
|
min-height: 300px;
|
|
}
|
|
#links > div {
|
|
padding: 2px 0;
|
|
}
|
|
span {
|
|
display: inline-block;
|
|
}
|
|
.m {
|
|
width: 220px;
|
|
}
|
|
.s {
|
|
width: 90px;
|
|
}
|
|
.p {
|
|
width: 110px;
|
|
}
|
|
.i {
|
|
width: 160px;
|
|
}
|
|
</style>
|
|
<center>
|
|
<h1>]] .. node .. [[ neighbor status</h1>]] .. lat_lon)
|
|
if node_desc ~= "" then
|
|
html.print([[<table id='node_description_display'><tr><td>]] .. node_desc .. [[</td></tr></table>]])
|
|
end
|
|
html.print([[<hr>
|
|
<table>
|
|
<tr><td>
|
|
<center>
|
|
<a href='/help.html#status' target='_blank'>Help</a>
|
|
<button type=button onClick='window.location.reload()' title='Refresh this page'>Refresh</button>
|
|
|
|
<button type=button onClick='window.location="status"' title='Return to the status page'>Quit</button>
|
|
</center>
|
|
</td></tr>
|
|
<tr><td>
|
|
<div class="lt">
|
|
<span class="m">Neighbor</span><span class="s">Link</span><span class="s">SNR</span><span class="p">Distance</span><span class="s">Quality</span><span class="i">Status <a href='/help.html#lqmstatus' target='_blank'><img src='/qmark.png'></a></span>
|
|
</div>
|
|
<div id="links"></div>
|
|
</td></tr>
|
|
</table>
|
|
</center>
|
|
<script>
|
|
const meters_to_miles = 0.000621371;
|
|
const meters_to_km = 0.001;
|
|
const wifi_scale = 0.2;
|
|
const get_status = (track, data) => {
|
|
if (track.pending > data.info.now) {
|
|
return "pending";
|
|
}
|
|
if (track.lastseen < data.info.now) {
|
|
return "disconnected";
|
|
}
|
|
if (track.blocked) {
|
|
if (track.blocks.user) {
|
|
return "blocked - user";
|
|
}
|
|
if (track.blocks.dtd) {
|
|
return "blocked - dtd";
|
|
}
|
|
if (track.blocks.distance) {
|
|
return "blocked - distance";
|
|
}
|
|
|
|
if (track.blocks.signal) {
|
|
return "blocked - signal";
|
|
}
|
|
if (track.blocks.dup) {
|
|
return "blocked - dup";
|
|
}
|
|
if (track.blocks.quality) {
|
|
if (track.tx_quality < track.ping_quality) {
|
|
return "blocked - retries";
|
|
}
|
|
else {
|
|
return "blocked - latency";
|
|
}
|
|
}
|
|
return "blocked";
|
|
}
|
|
if (track.routable) {
|
|
return track.exposed ? "active - exposed" : "active";
|
|
}
|
|
if (track.hidden) {
|
|
return "hidden";
|
|
}
|
|
return track.exposed ? "idle - exposed" : "idle";
|
|
}
|
|
const get_snr = track => {
|
|
return !track.snr ? "-" : track.snr + ("rev_snr" in track ? "/" + track.rev_snr : "");
|
|
}
|
|
const name = track => {
|
|
if (track.hostname) {
|
|
return `<a href="http://${track.hostname}.local.mesh:8080">${track.hostname}</a>`;
|
|
}
|
|
if (track.ip) {
|
|
return `<a href="http://${track.ip}:8080">${track.ip}</a>`;
|
|
}
|
|
return track.mac || "-";
|
|
}
|
|
let convertd = (d) => (meters_to_km * d).toFixed(1) + " km";
|
|
switch (navigator.language.split("-")[1] || "unknown") {
|
|
case "US":
|
|
case "GB":
|
|
convertd = (d) => (meters_to_miles * d).toFixed(1) + " miles";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
const update = data => {
|
|
let links = "";
|
|
const trackers = Object.values(data.info.trackers);
|
|
data.info.hidden_nodes.forEach(hidden => {
|
|
trackers.push({
|
|
hostname: hidden.hostname,
|
|
ip: hidden.ip,
|
|
hidden: true,
|
|
type: "-",
|
|
snr: -1,
|
|
distance: hidden.distance
|
|
});
|
|
});
|
|
trackers.sort((a, b) => name(a).localeCompare(name(b)));
|
|
trackers.forEach(track => {
|
|
let quality = "-";
|
|
let distance = "-";
|
|
let status = get_status(track, data);
|
|
switch (status) {
|
|
case "disconnected":
|
|
break;
|
|
case "active":
|
|
case "idle":
|
|
case "active - exposed":
|
|
case "idle - exposed":
|
|
case "pending":
|
|
case "blocked - retries":
|
|
case "blocked - latency":
|
|
if (typeof track.quality === "number") {
|
|
quality = track.quality + "%";
|
|
}
|
|
// Fall through
|
|
default:
|
|
if (typeof track.distance === "number") {
|
|
distance = convertd(track.distance);
|
|
}
|
|
break;
|
|
}
|
|
links += `<div><span class="m">${name(track)}</span><span class="s">${track.type}</span><span class="s">${get_snr(track)}</span><span class="p">${distance}</span><span class="s">${quality}</span><span class="i">${status}</span></div>`;
|
|
});
|
|
if (links.length) {
|
|
document.getElementById("links").innerHTML = links;
|
|
}
|
|
else {
|
|
document.getElementById("links").innerHTML = `<div><span>Currently no neighbor data available</span></div>`
|
|
}
|
|
}
|
|
const fetchAndUpdate = () => {
|
|
fetch("/cgi-bin/sysinfo.json?lqm=1").then(r => r.json()).then(data => {
|
|
update(data.lqm);
|
|
setTimeout(fetchAndUpdate, 60000);
|
|
}).catch(_ => {
|
|
setTimeout(fetchAndUpdate, 30000);
|
|
});
|
|
}
|
|
setTimeout(fetchAndUpdate, 30000);
|
|
]])
|
|
local ls = nixio.fs.stat("/tmp/lqm.info")
|
|
if ls and ls.size > 0 then
|
|
html.print("update({info:" .. io.open("/tmp/lqm.info"):read("*a") .. "})")
|
|
end
|
|
html.print([[</script>]])
|
|
html.footer()
|
|
html.print("</body></html>")
|
|
http_footer()
|