2022-01-19 06:42:31 -07:00
#!/usr/bin/lua
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021 Tim Wilkinson
Original Perl Copyright (C) 2015 Conrad Lara
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("nixio")
require("aredn.hardware")
require("aredn.http")
require("aredn.utils")
require("aredn.html")
require("uci")
2024-04-01 23:14:49 -06:00
require("aredn.info")
require("aredn.olsr")
2022-01-19 06:42:31 -07:00
require("iwinfo")
2024-02-09 21:45:52 -07:00
require('luci.jsonc')
2022-01-19 06:42:31 -07:00
local html = aredn.html
local node = aredn.info.get_nvram("node")
if node == "" then
node = "NOCALL"
end
local tactical = aredn.info.get_nvram("tactical")
local config = aredn.info.get_nvram("config")
if config == "" or nixio.fs.stat("/etc/config.mesh", "type") ~= "dir" then
config = "not set"
end
local wifiif = aredn.hardware.get_iface_name("wifi")
local my_ip = aredn.hardware.get_interface_ip4(wifiif)
if not my_ip then
my_ip = "none"
end
local phy = iwinfo.nl80211.phyname(wifiif)
if not phy then
phy = 0
end
local chanbw = 1
local cb = "/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k/chanbw"
2023-02-13 15:04:48 -07:00
if not nixio.fs.stat(cb) then
cb = "/sys/kernel/debug/ieee80211/" .. phy .. "/ath10k/chanbw"
end
2022-01-19 06:42:31 -07:00
if nixio.fs.stat(cb) then
for line in io.lines(cb)
do
if line == "0x00000005" then
chanbw = 4
elseif line == "0x0000000a" then
chanbw = 2
end
break
end
end
if not nixio.fs.stat("/tmp/web") then
nixio.fs.mkdir("/tmp/web")
end
local cursor = uci.cursor()
local node_desc = cursor:get("system", "@system[0]", "description")
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
local routes = {}
local links = {}
local neighbor = {}
local wangateway = {}
local ipalias = {}
local localhosts = {}
local dtd = {}
local midcount = {}
2024-01-12 23:26:45 -07:00
local xlinkcount = {}
2022-01-19 06:42:31 -07:00
local hosts = {}
local services = {}
local history = {}
for i, node in ipairs(aredn.olsr.getOLSRRoutes())
do
2023-12-06 12:50:53 -07:00
if node.genmask ~= 0 and node.etx <= 50 then
routes[node.destination] = { etx = node.etx }
2022-01-19 06:42:31 -07:00
end
end
2023-12-06 12:50:53 -07:00
2022-01-19 06:42:31 -07:00
-- low memory route reduction
2023-12-06 12:50:53 -07:00
local lowmemory = 1024 * tonumber(cursor:get("aredn", "@meshstatus[0]", "lowmem") or 10000)
local lowroutes = tonumber(cursor:get("aredn", "@meshstatus[0]", "lowroutes") or 1000)
if #routes > lowroutes and nixio.sysinfo().freeram < lowmemory then
2022-01-19 06:42:31 -07:00
local list = {}
for k,v in pairs(routes)
do
list[#list + 1] = { key = k, etx = v.etx }
end
table.sort(list, function (a, b) return a.etx < b.etx end)
2023-12-06 12:50:53 -07:00
for i = lowroutes, #list - 1
2022-01-19 06:42:31 -07:00
do
2022-10-11 21:22:11 -06:00
routes[list[i].key] = nil
2022-01-19 06:42:31 -07:00
end
end
-- load up arpcache
local arpcache = {}
arptable(function(a)
2023-02-13 15:04:48 -07:00
if a["Flags"] ~= "0x0" and a["HW address"] ~= "00:00:00:00:00:00" then
arpcache[a["IP address"]] = a
end
2022-01-19 06:42:31 -07:00
end)
2022-12-22 13:22:49 -07:00
local iwrates
2022-01-19 06:42:31 -07:00
for i, node in ipairs(aredn.olsr.getOLSRLinks())
do
2024-01-12 23:26:45 -07:00
links[node.remoteIP] = { lq = node.linkQuality, nlq = node.neighborLinkQuality, mbps = "", weight = 65536 / node.lossMultiplier }
2022-01-19 06:42:31 -07:00
neighbor[node.remoteIP] = true
local mac = arpcache[node.remoteIP]
if mac then
2023-02-15 21:49:33 -07:00
if not iwrates then
iwrates = {}
2024-01-13 00:25:00 -07:00
if wifiif ~= "br-nomesh" then
local station = {}
for line in io.popen("iw " .. wifiif .. " station dump"):lines()
do
local mac = line:match("^Station (%S+) ")
if mac then
station = {}
iwrates[mac] = station
end
local txbitrate = line:match("tx bitrate:%s+([%d%.]+) MBit/s")
if txbitrate then
station.txbitrate = txbitrate
end
2022-12-22 13:22:49 -07:00
end
end
2023-02-15 21:49:33 -07:00
end
local station = iwrates[mac["HW address"]]
if station then
links[node.remoteIP].mbps = string.format("%.1f", tonumber(station.txbitrate) / chanbw)
2022-01-19 06:42:31 -07:00
end
end
end
-- discard
arpcache = nil
for i, node in ipairs(aredn.olsr.getOLSRHNA())
do
if node.destination == "0.0.0.0" then
wangateway[node.gateway] = true
end
end
for i, node in ipairs(aredn.olsr.getOLSRMid())
do
local ip = node.main.ipAddress
for _, alias in ipairs(node.aliases)
do
local aip = alias.ipAddress
ipalias[aip] = ip
neighbor[aip] = true
if links[aip] then
neighbor[ip] = true
end
end
end
-- load the local hosts file
for line in io.lines("/etc/hosts")
do
if line:match("^10%.") then
local ip, name = line:match("([%d%.]+)%s+(%S+)")
if name then
local name9 = name:sub(1, 9)
if name9 ~= "localhost" and name9 ~= "localnode" then
local name7 = name:sub(1, 7)
if name7 ~= "localap" and name7 ~= "dtdlink" then
if not name:match("%.") then
name = name .. ".local.mesh"
end
2022-03-02 16:27:39 -07:00
local tac = line:match("[%d%.]+%s+%S+%s+(%S+)")
2022-01-19 06:42:31 -07:00
if not tac then
tac = ""
end
if not localhosts[my_ip] then
localhosts[my_ip] = { hosts = {}, noprops = {}, aliases = {}, name = name, tactical = tac }
end
local host = localhosts[my_ip]
if ip == my_ip then
host.tactical = tac
host.name = name
else
host.hosts[#host.hosts + 1] = name
end
if tac == "#NOPROP" then
host.noprops[#host.noprops + 1] = name
end
if tac == "#ALIAS" then
host.aliases[#host.aliases + 1] = name
end
end
end
end
end
end
-- load the olsr hosts file
2024-04-01 23:14:49 -06:00
for line in aredn.olsr.getHostAsLines(2)
do
local ip, name, originator = line:match("^([%d%.]+)%s+(%S+)%s+%S+%s+(%S+)")
if ip and originator and originator ~= "myself" and (routes[ip] or routes[originator]) then
local etx = routes[ip]
if not etx then
etx = routes[originator]
end
etx = etx.etx
if not name:match("%.") or name:match("^mid%.[^%.]*$") then
name = name .. ".local.mesh"
end
if ip == originator then
if not hosts[originator] then
hosts[originator] = { hosts = {} }
2022-01-19 06:42:31 -07:00
end
2024-04-01 23:14:49 -06:00
local host = hosts[originator]
if host.name then
host.tactical = name
else
host.name = name
host.etx = etx
2022-01-19 06:42:31 -07:00
end
2024-04-01 23:14:49 -06:00
elseif name:match("^dtdlink%.") then
dtd[originator] = true
if links[ip] then
links[ip].dtd = true
end
elseif name:match("^xlink%d+%.") then
if not xlinkcount[originator] then
xlinkcount[originator] = 1
2022-01-19 06:42:31 -07:00
else
2024-04-01 23:14:49 -06:00
xlinkcount[originator] = xlinkcount[originator] + 1
end
if links[ip] then
links[ip].xlink = true
end
elseif name:match("^mid%d+%.") then
if not midcount[originator] then
midcount[originator] = 1
else
midcount[originator] = midcount[originator] + 1
2022-01-19 06:42:31 -07:00
end
2024-04-01 23:14:49 -06:00
if links[ip] then
links[ip].tun = true
end
else
if not hosts[originator] then
hosts[originator] = { hosts = {} }
end
local host = hosts[originator]
host.hosts[#host.hosts + 1] = name
2022-01-19 06:42:31 -07:00
end
end
end
-- discard
routes = nil
2024-04-01 23:14:49 -06:00
for line in aredn.olsr.getServicesAsLines()
do
if line:match("^%w") then
local url, name = line:match("^(.*)|.*|(.*)$")
if name then
local protocol, host, port, path = url:match("^([%w][%w%+%-%.]+)%://(.+):(%d+)/(.*)")
if path then
local name, originator = name:match("(.*%S)%s*#(.*)")
if originator == " my own service" or (hosts[originator] and hosts[originator].name) then
if not host:match("%.local%.mesh$") then
host = host .. ".local.mesh"
end
if not services[host] then
services[host] = {}
end
if not services[host][name] then
if port ~= "0" then
services[host][name] = "<a href='" .. protocol .. "://" .. host .. ":" .. port .. "/" .. path .. "' target='_blank'>" .. name .. "</a>"
else
services[host][name] = name
2022-01-19 06:42:31 -07:00
end
end
end
end
end
end
end
-- load the node history
2024-04-01 23:14:49 -06:00
local f = io.open("/tmp/node.history")
if f then
for line in f:lines("/tmp/node.history")
2022-01-19 06:42:31 -07:00
do
2022-04-04 20:16:35 -06:00
local ip, age, host = line:match("^(%S+) (%d+) (%S+)")
2022-03-19 08:21:22 -06:00
if ip and age and host then
2022-01-19 06:42:31 -07:00
history[ip] = { age = age, host = host:gsub("/", " / ") }
end
end
end
2024-02-11 22:04:02 -07:00
function ac(a, b)
if not a or a == "" then
return b
elseif not b or b == "" then
return a
else
return a .. "," .. b
end
end
2022-10-18 20:37:34 -06:00
------------------
-- generate page
------------------
2022-01-19 06:42:31 -07:00
http_header()
html.header(node .. " mesh status", false)
2024-03-25 20:52:16 -06:00
local automesh = string.find((nixio.getenv("QUERY_STRING") or ""):lower(),"automesh=1")
2022-01-19 06:42:31 -07:00
if automesh then
2024-03-25 20:52:16 -06:00
html.print("<script>setTimeout(function(){ window.location.reload(); }, 10000);</script>")
2022-01-19 06:42:31 -07:00
end
2022-10-18 20:37:34 -06:00
html.print([[
<style>
table {
cellspacing:5px;
2022-11-01 08:47:52 -06:00
width:80%;
2022-10-18 20:37:34 -06:00
border-collapse:collapse;
}
2022-11-01 08:47:52 -06:00
td {
padding-left:5px;
2022-10-18 20:37:34 -06:00
}
tr {
vertical-align:top;
text-align:left;
}
tr.spaceUnder>td {
padding-bottom:2em;
}
2024-03-15 09:43:43 -06:00
tbody:hover {
2023-08-29 21:06:37 -06:00
background-color:rgba(128,128,128,0.15);
}
tr.h {
border-top: 1px solid black;
border-bottom: 1px solid black;
}
2022-10-18 20:37:34 -06:00
th {
white-space:nowrap;
vertical-align:middle;
2023-08-29 21:06:37 -06:00
padding:3px 5px;
2022-10-18 20:37:34 -06:00
}
input.search {
width:150px;
}
2023-08-29 21:06:37 -06:00
#nTable {
margin-bottom: 12px;
}
2022-10-26 19:55:36 -06:00
#nTable th:nth-child(1) {
width: 25%;
}
2022-11-01 08:47:52 -06:00
#nTable th:nth-child(2) {
2024-02-09 21:45:52 -07:00
width: 50%;
2022-10-26 19:55:36 -06:00
}
2023-08-29 21:06:37 -06:00
#nTable th:nth-child(3) {
2024-02-09 21:45:52 -07:00
width: 25%;
2022-10-26 19:55:36 -06:00
}
2023-08-29 21:06:37 -06:00
#cTable {
margin-bottom: 12px;
}
2022-10-26 19:55:36 -06:00
#cTable th:nth-child(1), #rTable th:nth-child(1) {
width: 25%;
}
#cTable th:nth-child(2), #rTable th:nth-child(2) {
2024-02-09 21:45:52 -07:00
width: 20%;
2022-10-26 19:55:36 -06:00
}
2024-02-09 21:45:52 -07:00
#cTable th:nth-child(3), #cTable th:nth-child(4), #cTable th:nth-child(5) {
width: 4%;
padding: 0 2px 0 0;
text-align: right;
}
#cTable th:nth-child(6), #cTable th:nth-child(7), #cTable th:nth-child(8) {
2022-11-01 08:47:52 -06:00
width: 6%;
2024-02-09 21:45:52 -07:00
padding: 0 2px 0 0;
text-align: right;
2022-10-26 19:55:36 -06:00
}
2024-02-09 21:45:52 -07:00
#cTable td:nth-child(3), #cTable td:nth-child(4), #cTable td:nth-child(5), #cTable td:nth-child(6), #cTable td:nth-child(7), #cTable td:nth-child(8) {
text-align: right;
padding: 0 2px 0 0;
}
#cTable th:nth-child(9) {
width: 25%;
2022-10-26 19:55:36 -06:00
}
2024-02-11 22:04:02 -07:00
#rTable th:nth-child(3), #rTable td:nth-child(3) {
width: 4%;
padding: 0 2px 0 0;
text-align: right;
2022-10-26 19:55:36 -06:00
}
2022-11-01 08:47:52 -06:00
#rTable th:nth-child(4) {
2024-02-11 22:04:02 -07:00
width: 26%;
}
#rTable th:nth-child(5) {
2024-02-09 21:45:52 -07:00
width: 25%;
2022-10-26 19:55:36 -06:00
}
2023-08-29 21:06:37 -06:00
tr.s.f.nf {
display: table-row;
}
tr.s.nf {
display: none;
}
2024-03-15 09:43:43 -06:00
tr.s.f.nf td, tr.s.f.nf td.s.f.nf {
color: transparent;
}
tr.s.f.nf td.s.f.nf:last-child a {
display: none;
}
tr.s.f.nf td.s.f {
color: inherit;
}
tr.s.f.nf td:first-child, tr.s.f.nf td:nth-child(2), tr.s.f.nf td:nth-child(3) {
color: inherit;
}
#cTable tr.s.f.nf td:nth-child(4), #cTable tr.s.f.nf td:nth-child(5), #cTable tr.s.f.nf td:nth-child(6), #cTable tr.s.f.nf td:nth-child(7), #cTable tr.s.f.nf td:nth-child(8) {
color: inherit;
}
2022-10-18 20:37:34 -06:00
</style>
2023-08-29 21:06:37 -06:00
<script>
let searchPending;
2024-03-22 18:49:59 -06:00
let cFilter = "";
2023-08-29 21:06:37 -06:00
function doSearch() {
clearTimeout(searchPending);
searchPending = setTimeout(function() {
const filter = document.getElementById("srch").value.toUpperCase();
2024-03-15 09:43:43 -06:00
if (filter !== cFilter) {
cFilter = filter;
const rows = document.querySelectorAll("tr.s");
for (let i = 0; i < rows.length; i++) {
rows[i].classList.remove("f", "nf");
2023-08-29 21:06:37 -06:00
}
2024-03-15 09:43:43 -06:00
const cells = document.querySelectorAll("tr.s td.nf");
for (let i = 0; i < cells.length; i++) {
cells[i].classList.remove("nf");
2023-08-29 21:06:37 -06:00
}
2024-03-15 09:43:43 -06:00
const tds = document.querySelectorAll("td.s");
if (filter === "") {
for (let i = 0; i < tds.length; i++) {
const td = tds[i];
td.parentElement.classList.add("f");
td.parentElement.parentElement.firstElementChild.classList.add("f");
}
2023-08-29 21:06:37 -06:00
}
else {
2024-03-15 09:43:43 -06:00
for (let i = 0; i < tds.length; i++) {
const td = tds[i];
2024-03-22 18:49:59 -06:00
let txt = td.dataset.search;
if (txt === undefined) {
txt = (td.textContent || td.innerText).toUpperCase();
td.dataset.search = txt
}
if (txt.indexOf(filter) === -1) {
2024-03-15 09:43:43 -06:00
td.classList.add("nf");
td.parentElement.classList.add("nf");
}
else {
td.parentElement.classList.add("f");
td.parentElement.parentElement.firstElementChild.classList.add("f");
}
}
}
const tables = document.querySelectorAll("table.s");
for (let i = 0; i < tables.length; i++) {
if (!tables[i].querySelector("tr.f")) {
tables[i].style.display = "none";
}
else {
tables[i].style.display = null;
}
2023-08-29 21:06:37 -06:00
}
}
2024-03-15 09:43:43 -06:00
}, 200);
2023-08-29 21:06:37 -06:00
}
</script>
2022-10-18 20:37:34 -06:00
]])
2022-01-19 06:42:31 -07:00
html.print("</head>")
2022-10-18 20:37:34 -06:00
html.print("<body><form method=post action=/cgi-bin/mesh accept-charset=utf-8 enctype='multipart/form-data'>")
2022-01-19 06:42:31 -07:00
html.print("<input type=hidden name=reload value=1>")
html.print("<center>")
html.alert_banner()
html.print("<h1>" .. node .. " mesh status</h1>")
html.print(lat_lon)
if node_desc then
2022-10-23 13:49:03 -06:00
html.print("<table id='node_description_display'><tr><td align=center>" .. node_desc .. "</td></tr></table>")
2022-01-19 06:42:31 -07:00
end
html.print("<hr><nobr>")
2022-10-03 19:49:26 -06:00
html.print("<a href='/help.html#meshstatus' target='_blank'>Help</a> ")
2024-03-25 20:52:16 -06:00
if automesh then
html.print("<input type=button name=stop value=Stop title='Abort continuous status' onclick='window.location = window.location.origin + window.location.pathname'>")
2016-06-29 20:43:43 -06:00
else
2022-12-22 13:22:49 -07:00
html.print("<input type=button name=refresh value=Refresh title='Refresh this page' onclick='window.location.reload();'>")
2022-01-19 06:42:31 -07:00
html.print(" ")
2024-03-25 20:52:16 -06:00
html.print([[<input type=button name=auto value=Auto title='Automatic page refresh' onclick='window.location = window.location.origin + window.location.pathname + "?automesh=1"'>]])
2022-01-19 06:42:31 -07:00
end
2023-09-24 21:40:28 -06:00
if nixio.fs.stat("/tmp/dnsmasq.d/supernode.conf") then
local ip = read_all("/tmp/dnsmasq.d/supernode.conf"):match("^#(%S+)")
if ip then
html.print(" ")
2023-10-10 21:57:19 -06:00
html.print("<a target=_blank href='http://" .. ip .. "/cgi-bin/mesh'><button type=button title='See what is on the whole AREDN mesh'>Cloud Mesh</button></a>")
2023-09-24 21:40:28 -06:00
end
end
2022-01-19 06:42:31 -07:00
html.print(" ")
html.print("<button type=button onClick='window.location=\"status\"' title='Return to the status page'>Quit</button>")
2023-08-29 21:06:37 -06:00
html.print(" ")
2024-02-01 19:01:17 -07:00
html.print("<input id=srch type=search autocorrect=off spellcheck=false placeholder='Search...' onkeyup='doSearch()' onclick='doSearch()' onkeypress='event.keyCode == 13 && event.preventDefault()'>")
2022-01-19 06:42:31 -07:00
html.print("</nobr><br><br>")
if not next(localhosts) and not next(links) then
html.print("No other nodes are available.")
html.print("</center></form>")
html.footer()
html.print("</body></html>")
os.exit(0)
end
2022-11-01 08:47:52 -06:00
-- show local node table
2022-11-09 20:15:25 -07:00
2024-03-15 09:43:43 -06:00
html.print("<table id=nTable class=s><thead>")
2023-08-29 21:06:37 -06:00
html.print("<tr class=h>")
2024-02-09 21:45:52 -07:00
html.print("<th>Node Name</th>")
2023-08-29 21:06:37 -06:00
html.print("<th>LAN Hostname</th>")
html.print("<th>Service Name</th>")
2024-03-15 09:43:43 -06:00
html.print("</tr></thead>")
2022-01-19 06:42:31 -07:00
if next(localhosts) then
local rows = {}
for ip, host in pairs(localhosts)
do
local localpart = host.name:match("([^.]*)%.")
2022-10-20 14:43:55 -06:00
if localpart then
local tactical = ""
if host.tactical ~= "" then
tactical = " / " .. host.tactical
2022-01-19 06:42:31 -07:00
end
2023-02-21 12:39:18 -07:00
local c1 = localpart .. tactical
2022-10-20 14:43:55 -06:00
if wangateway[ip] then
2022-11-09 20:15:25 -07:00
c1 = c1 .. " <small>(wan)</small>"
2022-10-20 14:43:55 -06:00
end
2024-03-15 09:43:43 -06:00
local tbody = "<tbody>"
2022-10-20 14:43:55 -06:00
if services[host.name] then
2024-03-15 09:43:43 -06:00
local first = true;
2022-11-09 20:15:25 -07:00
for _, v in pairs(services[host.name])
2022-10-20 14:43:55 -06:00
do
2024-03-15 09:43:43 -06:00
if first then
first = false
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='f'></td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='f'></td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
end
2022-11-09 20:15:25 -07:00
end
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='f'></td><td class='f'></td></tr>"
2022-01-19 06:42:31 -07:00
end
2022-10-20 14:43:55 -06:00
2023-02-21 12:39:18 -07:00
for _, dmzhost in ipairs(host.hosts)
2022-01-19 06:42:31 -07:00
do
2022-10-20 14:43:55 -06:00
local nopropd = false
local aliased = false
for _, v in ipairs(host.noprops)
do
if v == dmzhost then
nopropd = true;
break
end
2022-01-19 06:42:31 -07:00
end
2022-10-20 14:43:55 -06:00
for _, v in ipairs(host.aliases)
2022-01-19 06:42:31 -07:00
do
2022-10-20 14:43:55 -06:00
if v == dmzhost then
aliased = true;
break
end
end
local localpart = dmzhost:match("(.*)%.local%.mesh")
if localpart then
2024-03-15 09:43:43 -06:00
if aliased then
localpart = "<span class=aliased-hosts>" .. localpart .. "</span>"
elseif nopropd then
localpart = "<span class=hidden-hosts>" .. localpart .. "</span>"
2022-10-20 14:43:55 -06:00
end
if services[dmzhost] then
2024-03-15 09:43:43 -06:00
local first = true
for _, v in pairs(services[dmzhost])
2022-10-20 14:43:55 -06:00
do
2024-03-15 09:43:43 -06:00
if first then
first = false
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='s f'>" .. localpart .. "</td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='f'></td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
end
2022-10-20 14:43:55 -06:00
end
2022-12-21 11:30:37 -07:00
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='s f'>" .. localpart .. "</td><td class='f'></td></tr>"
2022-10-20 14:43:55 -06:00
end
2022-01-19 06:42:31 -07:00
end
end
2024-03-15 09:43:43 -06:00
tbody = tbody .. "</tbody>"
rows[#rows + 1] = { key = host.name, row = tbody }
2022-01-19 06:42:31 -07:00
end
end
table.sort(rows, function(a,b) return a.key < b.key end)
for _, row in ipairs(rows)
do
html.print(row.row)
end
-- discard
rows = nil
2013-11-14 23:11:16 -07:00
else
2024-03-15 09:43:43 -06:00
html.print("<tbody><tr><td class='s f'>none</td></tr></tbody>")
2022-01-19 06:42:31 -07:00
end
-- discard
localhosts = nil
2022-11-01 08:47:52 -06:00
-- end local node table
html.print("</table>")
2022-01-19 06:42:31 -07:00
2022-10-18 20:37:34 -06:00
-- show current neighbors table
2022-11-09 20:15:25 -07:00
2024-03-15 09:43:43 -06:00
html.print("<table id='cTable' class=s><thead><tr class=h>")
2024-02-09 21:45:52 -07:00
html.print("<th>Current Neighbors</th>")
2023-08-29 21:06:37 -06:00
html.print("<th>LAN Hostname</th>")
2024-02-09 21:45:52 -07:00
html.print("<th title='Percent of packets successfully received'>LQ</th><th title='Percent of packets successfully sent'>NLQ</th>")
html.print("<th>SNR</th><th>Quality</th><th>TxMbps</th><th>Distance</th>")
2023-08-29 21:06:37 -06:00
html.print("<th>Service Name</th>")
2024-03-15 09:43:43 -06:00
html.print("</tr></thead>")
2022-01-19 06:42:31 -07:00
2024-02-09 21:45:52 -07:00
local trackers = {}
local l = io.open("/tmp/lqm.info")
if l then
local lqm = luci.jsonc.parse(l:read("*a"))
l:close()
2024-02-11 02:12:58 -07:00
for _, tracker in pairs(lqm.trackers)
do
2024-02-23 22:11:51 -07:00
if tracker.ip then
trackers[tracker.ip] = tracker
end
2024-02-11 02:12:58 -07:00
end
2024-02-09 21:45:52 -07:00
end
local now = nixio.sysinfo().uptime
2024-03-13 19:18:24 -06:00
local metric = true
local language = os.getenv("HTTP_ACCEPT_LANGUAGE") or "en-US"
if language:match("^..%-US") or language:match("^..%-GB") then
metric = false
end
2022-01-19 06:42:31 -07:00
local rows = {}
local neighservices = {}
for ip, link in pairs(links)
do
local ipmain = ipalias[ip]
if not ipmain then
ipmain = ip
end
local name = ipmain
local localpart = ipmain
local tactical = ""
local host = hosts[ipmain]
if host then
if host.name then
name = host.name
localpart = name:match("(.*)%.local%.mesh")
if not localpart then
localpart = name
end
end
if host.tactical then
tactical = " / " .. host.tactical
end
end
if rows[name] then
name = name .. " " -- avoid collision 2 links to same host {rf, dtd}
end
local no_space_host = name:match("(.*%S)%s*$")
2023-02-21 12:39:18 -07:00
local c1 = "<a href='http://" .. no_space_host .. ":8080/'>" .. localpart .. tactical .. "</a>"
2022-01-19 06:42:31 -07:00
local nodeiface
2024-02-11 02:12:58 -07:00
local waniface
local lqmstatus
2022-01-19 06:42:31 -07:00
if ipmain ~= ip then
if links[ip].dtd then
nodeiface = "dtd"
2022-11-14 21:45:58 -07:00
elseif links[ip].xlink then
nodeiface = "xlink"
2022-01-19 06:42:31 -07:00
elseif links[ip].tun then
nodeiface = "tun"
else
nodeiface = "?"
end
end
if wangateway[ip] or wangateway[ipmain] then
2024-02-11 02:12:58 -07:00
waniface = "wan"
2022-01-19 06:42:31 -07:00
end
2024-02-09 21:45:52 -07:00
2023-02-21 12:39:18 -07:00
local c2 = ""
2024-01-12 23:26:45 -07:00
local c3 = string.format("%.0f%%", math.min(100, math.ceil(100 * link.lq * link.weight)))
local c4 = string.format("%.0f%%", math.min(100, math.ceil(100 * link.nlq * link.weight)))
2024-02-09 21:45:52 -07:00
local c4b = ""
local c4c = ""
2023-02-21 12:39:18 -07:00
local c5 = string.format("%s", link.mbps)
2024-02-09 21:45:52 -07:00
local c5b = ""
2023-02-21 12:39:18 -07:00
local c6 = ""
2022-01-19 06:42:31 -07:00
2024-02-09 21:45:52 -07:00
-- lqm info
2024-02-11 02:12:58 -07:00
local track = trackers[ip]
if track and not (track.hidden or track.blocked) then
if not nodeiface and track.type == "RF" then
nodeiface = "rf"
end
if nodeiface == "tun" and track.type == "Wireguard" then
nodeiface = "wg"
end
if track.pending > now then
lqmstatus = "pending"
elseif track.routable then
lqmstatus = "active"
else
lqmstatus = "idle"
end
if track.snr > 0 then
c4b = track.snr
if track.rev_snr then
c4b = c4b .. "/" .. track.rev_snr
2024-02-09 21:45:52 -07:00
end
2024-02-11 02:12:58 -07:00
end
if track.quality then
c4c = track.quality .. "%"
end
if track.distance then
2024-03-13 19:18:24 -06:00
if not metric then
2024-02-11 02:12:58 -07:00
local v = track.distance * 0.000621371
if v > 1 then
c5b = math.ceil(v) .. " miles"
elseif v > 0.5 then
c5b = "1 mile"
2024-03-13 19:18:24 -06:00
else
c5b = "<1 mile"
2024-02-09 21:45:52 -07:00
end
2024-02-11 02:12:58 -07:00
else
c5b = math.ceil(track.distance * 0.001) .. " km"
2024-02-09 21:45:52 -07:00
end
end
end
2024-02-11 02:12:58 -07:00
if nodeiface or waniface or lqmstatus then
2024-02-11 22:04:02 -07:00
c1 = c1 .. " <small>(" .. ac(ac(nodeiface, waniface), lqmstatus) .. ")</small>"
2024-02-09 21:45:52 -07:00
end
2022-11-09 20:15:25 -07:00
-- print node services if any
2024-03-15 09:43:43 -06:00
local tbody = "<tbody>"
2022-01-19 06:42:31 -07:00
if not neighservices[name] then
neighservices[name] = true
2024-03-15 09:43:43 -06:00
2022-01-19 06:42:31 -07:00
if services[name] then
2024-03-15 09:43:43 -06:00
local first = true
2022-01-19 06:42:31 -07:00
for _, v in pairs(services[name])
do
2024-03-15 09:43:43 -06:00
if first then
first = false
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='f'></td><td>" .. c3 .. "</td><td>" .. c4 .. "</td><td>" .. c4b .. "</td><td>" .. c4c .. "</td><td>" .. c5 .. "</td><td>" .. c5b .. "</td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='f'></td><td></td><td></td><td></td><td></td><td></td><td></td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
end
2022-11-09 20:15:25 -07:00
end
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='f'></td><td>" .. c3 .. "</td><td>" .. c4 .. "</td><td>" .. c4b .. "</td><td>" .. c4c .. "</td><td>" .. c5 .. "</td><td>" .. c5b .. "</td><td class='f'></td></tr>"
2022-01-19 06:42:31 -07:00
end
2022-10-18 20:37:34 -06:00
2022-01-19 06:42:31 -07:00
if host then
for _, dmzhost in ipairs(host.hosts)
do
local localpart = dmzhost:match("(.*)%.local%.mesh")
2022-10-20 14:43:55 -06:00
if localpart then
if services[dmzhost] then
2024-03-15 09:43:43 -06:00
local first = true
2022-10-20 14:43:55 -06:00
for _, v in pairs(services[dmzhost])
do
2024-03-15 09:43:43 -06:00
if first then
first = false
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='s f'>" .. localpart .. "</td><td></td><td></td><td></td><td></td><td></td><td></td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='s f'></td><td class='f'></td><td></td><td></td><td></td><td></td><td></td><td></td><td class='s f'>" .. v .. "</td></tr>"
2024-03-15 09:43:43 -06:00
end
2022-11-09 20:15:25 -07:00
end
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='s f'>" .. localpart .. "</td><td></td><td></td><td></td><td></td><td></td><td></td><td class='f'></td></tr>"
2022-01-19 06:42:31 -07:00
end
end
end
end
end
2024-03-15 09:43:43 -06:00
tbody = tbody .. "</tbody>"
rows[#rows + 1] = { key = name:lower(), row = tbody }
2024-02-09 21:45:52 -07:00
end
for _, track in pairs(trackers)
do
2024-02-11 22:04:02 -07:00
if track.blocked then
2024-02-09 21:45:52 -07:00
local name = track.ip
if track.hostname then
name = track.hostname
end
local c1 = "<a href='http://" .. name:match("(.*%S)%s*$") .. ":8080/'>" .. name .. "</a>"
local c4c = ""
local c4b = ""
local c5b = ""
2024-02-11 02:12:58 -07:00
local type = ""
if track.type == "RF" then
type = "rf,"
end
2024-02-11 22:04:02 -07:00
if track.blocks.user then
c1 = c1 .. " <small>(" .. type .. "blocked user)</small>"
elseif track.blocks.dtd then
c1 = c1 .. " <small>(" .. type .. "blocked dtd)</small>"
elseif track.blocks.distance then
c1 = c1 .. " <small>(" .. type .. "blocked distance)</small>"
elseif track.blocks.signal then
c1 = c1 .. " <small>(" .. type .. "blocked signal)</small>"
elseif track.blocks.dup then
c1 = c1 .. " <small>(" .. type .. "blocked dup)</small>"
elseif track.blocks.quality then
if track.tx_quality < track.ping_quality then
c1 = c1 .. " <small>(" .. type .. "blocked retries)</small>"
2024-02-09 21:45:52 -07:00
else
2024-02-11 22:04:02 -07:00
c1 = c1 .. " <small>(" .. type .. "blocked latency)</small>"
2024-02-09 21:45:52 -07:00
end
2024-02-11 22:04:02 -07:00
else
c1 = c1 .. " <small>(" .. type .. "blocked)</small>"
end
if track.snr > 0 then
c4b = track.snr
if track.rev_snr then
c4b = c4b .. "/" .. track.rev_snr
2024-02-09 21:45:52 -07:00
end
2024-02-11 22:04:02 -07:00
end
if track.quality then
c4c = track.quality .. "%"
2024-02-09 21:45:52 -07:00
end
if track.distance then
if true then
local v = track.distance * 0.000621371
if v > 1 then
c5b = math.ceil(v) .. " miles"
elseif v > 0.5 then
c5b = "1 mile"
end
else
c5b = math.ceil(track.distance * 0.001) .. " km"
end
end
2024-03-22 18:49:59 -06:00
local tbody = "<tbody><tr class=s><td class='s f'>" .. c1 .. "</td><td class='f'></td><td></td><td></td><td>" .. c4b .. "</td><td>" .. c4c .. "</td><td></td><td>" .. c5b .. "</td><td class='f'></td></tr></tbody>"
2024-03-15 09:43:43 -06:00
rows[#rows + 1] = { key = name:lower(), row = tbody }
2024-02-09 21:45:52 -07:00
end
2022-01-19 06:42:31 -07:00
end
2024-02-11 22:04:02 -07:00
2024-03-28 16:43:09 -06:00
local cn = {}
2022-01-19 06:42:31 -07:00
if #rows > 0 then
table.sort(rows, function(a,b) return a.key < b.key end)
for _, row in ipairs(rows)
do
2024-03-28 16:43:09 -06:00
cn[row.key] = true
2022-01-19 06:42:31 -07:00
html.print(row.row)
end
-- discard
rows = nil
2013-11-14 23:11:16 -07:00
else
2024-03-15 09:43:43 -06:00
html.print("<tbody><tr><td class='s f'>none</td></tr></tbody>")
2022-01-19 06:42:31 -07:00
end
2022-11-01 08:47:52 -06:00
--add previous neighbors
local rows = {}
local uptime = nixio.sysinfo().uptime
for ip, node in pairs(history)
do
if not (links[ip] or links[ipalias[ip]]) then
local age = uptime - tonumber(node.age)
local host = node.host
if host == "" then
host = ip
else
host = host:gsub("^mid%d+%.", ""):gsub("^dtdlink%.", "")
end
2024-03-28 16:43:09 -06:00
if not cn[host:lower()] then
2024-03-31 15:18:38 -06:00
cn[host:lower()] = true
2024-03-28 16:43:09 -06:00
local row = "<tbody style='color:sienna;'><tr><td>" .. host .. "</td><td colspan=7></td><td style='text-align:left'>"
if age < 3600 then
local val = math.floor(age / 60)
if val == 1 then
row = row .. "1 minute ago</td></tr>"
else
row = row .. val .. " minutes ago</td></tr>"
end
2022-11-01 08:47:52 -06:00
else
2024-03-28 16:43:09 -06:00
local val = string.format("%.1f", age / 3600)
if val == "1.0" then
row = row .. "1 hour ago</td></tr>"
else
row = row .. val .. " hours ago</td></tr>"
end
2022-11-01 08:47:52 -06:00
end
2024-03-28 16:43:09 -06:00
if hosts[ip] and hosts[ip].hosts then
for _, v in ipairs(hosts[ip].hosts)
do
row = row .. "<tr><td></td><td colspan=8>" .. v .. "</td></tr>"
end
end
row = row .. "</tbody>"
rows[#rows + 1] = { key = age, row = row }
2022-11-01 08:47:52 -06:00
end
end
end
if #rows > 0 then
2022-11-09 20:15:25 -07:00
html.print("<tr><td style='color:sienna;font-weight:bold;padding-top:10px;'>Previous Neighbors</td></tr>")
2022-11-01 08:47:52 -06:00
table.sort(rows, function(a,b) return a.key < b.key end)
for _, row in ipairs(rows)
do
html.print(row.row)
end
-- discard
rows = nil
end
2024-03-28 16:43:09 -06:00
cn = nil
2022-11-01 08:47:52 -06:00
-- end current neighbors table
html.print("</table>")
2022-10-18 20:37:34 -06:00
-- show remote node table
2022-11-09 20:15:25 -07:00
2024-03-15 09:43:43 -06:00
html.print("<table id='rTable' class=s><thead><tr class=h>")
2024-02-09 21:45:52 -07:00
html.print("<th>Remote Nodes</th>")
2023-08-29 21:06:37 -06:00
html.print("<th>LAN Hostname</th>")
2024-02-11 22:04:02 -07:00
html.print("<th>ETX</th><th></th>")
2023-08-29 21:06:37 -06:00
html.print("<th>Service Name</th>")
2024-03-15 09:43:43 -06:00
html.print("</tr></thead>")
2022-01-19 06:42:31 -07:00
local rows = {}
2022-10-18 20:37:34 -06:00
for ip, host in pairs(hosts)
2022-01-19 06:42:31 -07:00
do
2022-10-18 20:37:34 -06:00
if not neighbor[ip] and host.name then
local localpart = host.name:match("(.*)%.local%.mesh")
2022-10-20 14:43:55 -06:00
if localpart then
local tactical = ""
if host.tactical then
tactical = " / " .. host.tactical
2022-01-19 06:42:31 -07:00
end
2022-10-20 14:43:55 -06:00
local etx = string.format("%.2f", host.etx)
2024-03-15 09:43:43 -06:00
local c1 = "<a href='http://" .. host.name .. ":8080/'>" .. localpart .. tactical .. "</a>"
2022-10-20 14:43:55 -06:00
local nodeiface
2024-02-11 22:04:02 -07:00
local tuniface
local waniface
local hidden
2022-10-20 14:43:55 -06:00
local mycount = 0
if midcount[ip] then
mycount = midcount[ip]
2024-01-12 23:26:45 -07:00
if xlinkcount[ip] then
mycount = mycount - xlinkcount[ip]
nodeiface = "xlink*" .. xlinkcount[ip]
end
2022-01-19 06:42:31 -07:00
end
2022-10-20 14:43:55 -06:00
if dtd[ip] then
mycount = mycount - 1
end
if hosts[ip].tactical then
mycount = mycount - 1
end
if mycount > 0 then
2024-02-11 22:04:02 -07:00
tuniface = "tun*" .. mycount
2022-10-20 14:43:55 -06:00
end
if wangateway[ip] then
2024-02-11 22:04:02 -07:00
waniface = "wan"
end
local track = trackers[ip]
if track and track.hidden then
hidden = "hidden"
2022-10-20 14:43:55 -06:00
end
2024-02-11 22:04:02 -07:00
if nodeiface or waniface then
c1 = c1 .. " <small>(" .. ac(ac(ac(nodeiface, tuniface), waniface), hidden) .. ")</small>"
2022-10-20 14:43:55 -06:00
end
2024-03-15 09:43:43 -06:00
local c3 = string.format("%s", etx)
2022-11-09 20:15:25 -07:00
2024-03-15 09:43:43 -06:00
local tbody = "<tbody>"
2022-10-20 14:43:55 -06:00
if services[host.name] then
2024-03-15 09:43:43 -06:00
local first = true
2022-10-20 14:43:55 -06:00
for _, v in pairs(services[host.name])
2022-10-18 20:37:34 -06:00
do
2024-03-15 09:43:43 -06:00
if first then
first = false
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='f'></td><td>" .. c3 .. "</td><td></td><td class='s f'>" .. v .. "</td></tr>"
2022-11-09 20:15:25 -07:00
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='f'></td><td></td><td></td><td class='s f'>" .. v .. "</td></tr>"
2022-11-09 20:15:25 -07:00
end
end
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='f'></td><td>" .. c3 .. "</td><td></td><td class='f'></td></tr>"
2022-10-18 20:37:34 -06:00
end
2022-10-20 14:43:55 -06:00
for _, dmzhost in ipairs(host.hosts)
do
local localpart = dmzhost:match("(.*)%.local%.mesh")
if localpart then
if services[dmzhost] then
2024-03-15 09:43:43 -06:00
local first = true
2022-10-20 14:43:55 -06:00
for _, v in pairs(services[dmzhost])
do
2024-03-15 09:43:43 -06:00
if first then
first = false
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='s f'>" .. localpart .. "</td><td></td><td></td><td class='s f'>" .. v .. "</td></tr>"
2022-11-09 20:15:25 -07:00
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='f'></td><td></td><td></td><td class='s f'>" .. v .. "</td></tr>"
2022-11-09 20:15:25 -07:00
end
end
else
2024-03-22 18:49:59 -06:00
tbody = tbody .. "<tr class=s><td class='f'></td><td class='s f'>" .. localpart .. "</td><td></td><td></td><td class='f'></td></tr>"
2022-10-20 14:43:55 -06:00
end
end
end
2024-03-15 09:43:43 -06:00
tbody = tbody .. "</tbody>"
rows[#rows + 1] = { key = string.format("%05d/%s", math.floor(100 * host.etx), host.name:lower()), row = tbody }
2022-10-20 14:43:55 -06:00
end
2022-01-19 06:42:31 -07:00
end
end
2022-10-18 20:37:34 -06:00
2022-01-19 06:42:31 -07:00
if #rows > 0 then
table.sort(rows, function(a,b) return a.key < b.key end)
for _, row in ipairs(rows)
do
html.print(row.row)
end
else
2024-03-15 09:43:43 -06:00
html.print("<tbody><tr><td class='s f'>none</td></tr></tbody>")
2022-01-19 06:42:31 -07:00
end
2024-02-11 22:04:02 -07:00
2022-10-18 20:37:34 -06:00
-- discard
2024-02-11 22:04:02 -07:00
rows = nil
2022-10-18 20:37:34 -06:00
neighbor = nil
dtd = nil
midcount = nil
2024-01-12 23:26:45 -07:00
xlinkcount = nil
2022-10-18 20:37:34 -06:00
wangateway = nil
services = nil
2024-02-11 22:04:02 -07:00
trackers = nil
2022-01-19 06:42:31 -07:00
links = nil
ipalias = nil
hosts = nil
history = nil
2024-02-11 22:04:02 -07:00
2022-11-01 08:47:52 -06:00
-- end remote nodes table
2022-10-18 20:37:34 -06:00
html.print("</table>")
2022-01-19 06:42:31 -07:00
html.print("</center>")
html.print("</form>")
html.footer();
html.print("</body>")
html.print("</html>")