mirror of https://github.com/aredn/aredn.git
919 lines
28 KiB
Lua
Executable File
919 lines
28 KiB
Lua
Executable File
#!/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")
|
|
aredn.info = require("aredn.info")
|
|
aredn.olsr = require("aredn.olsr")
|
|
require("iwinfo")
|
|
|
|
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"
|
|
if not nixio.fs.stat(cb) then
|
|
cb = "/sys/kernel/debug/ieee80211/" .. phy .. "/ath10k/chanbw"
|
|
end
|
|
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
|
|
|
|
-- post data
|
|
|
|
if os.getenv("REQUEST_METHOD") == "POST" then
|
|
require('luci.ohttp')
|
|
local request = luci.http.Request(nixio.getenv(),
|
|
function()
|
|
local v = io.read(1024)
|
|
if not v then
|
|
io.close()
|
|
end
|
|
return v
|
|
end
|
|
)
|
|
if request:formvalue("auto") then
|
|
io.open("/tmp/web/automesh", "w"):close()
|
|
end
|
|
if request:formvalue("stop") then
|
|
os.remove("/tmp/web/automesh")
|
|
end
|
|
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 = {}
|
|
local hosts = {}
|
|
local services = {}
|
|
local history = {}
|
|
|
|
for i, node in ipairs(aredn.olsr.getOLSRRoutes())
|
|
do
|
|
if node.genmask ~= 0 and node.etx <= 50 then
|
|
routes[node.destination] = { etx = node.etx }
|
|
end
|
|
end
|
|
|
|
-- low memory route reduction
|
|
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
|
|
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)
|
|
for i = lowroutes, #list - 1
|
|
do
|
|
routes[list[i].key] = nil
|
|
end
|
|
end
|
|
|
|
-- load up arpcache
|
|
local arpcache = {}
|
|
arptable(function(a)
|
|
if a["Flags"] ~= "0x0" and a["HW address"] ~= "00:00:00:00:00:00" then
|
|
arpcache[a["IP address"]] = a
|
|
end
|
|
end)
|
|
|
|
local iwrates
|
|
local prefix = "/sys/kernel/debug/ieee80211/" .. phy .. "/netdev:" .. wifiif .. "/stations/"
|
|
for i, node in ipairs(aredn.olsr.getOLSRLinks())
|
|
do
|
|
links[node.remoteIP] = { lq = node.linkQuality, nlq = node.neighborLinkQuality, mbps = "" }
|
|
neighbor[node.remoteIP] = true
|
|
local mac = arpcache[node.remoteIP]
|
|
if mac then
|
|
if not iwrates then
|
|
iwrates = {}
|
|
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
|
|
end
|
|
end
|
|
local station = iwrates[mac["HW address"]]
|
|
if station then
|
|
links[node.remoteIP].mbps = string.format("%.1f", tonumber(station.txbitrate) / chanbw)
|
|
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
|
|
local tac = line:match("[%d%.]+%s+%S+%s+(%S+)")
|
|
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
|
|
if nixio.fs.stat("/var/run/hosts_olsr.stable") then
|
|
for line in io.lines("/var/run/hosts_olsr.stable")
|
|
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 = {} }
|
|
end
|
|
local host = hosts[originator]
|
|
if host.name then
|
|
host.tactical = name
|
|
else
|
|
host.name = name
|
|
host.etx = etx
|
|
end
|
|
elseif name:match("^dtdlink%.") then
|
|
dtd[originator] = true
|
|
if links[ip] then
|
|
links[ip].dtd = true
|
|
end
|
|
elseif name:match("^xlink%d+%.") then
|
|
dtd[originator] = true
|
|
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
|
|
end
|
|
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
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- discard
|
|
routes = nil
|
|
|
|
if nixio.fs.stat("/var/run/services_olsr") then
|
|
for line in io.lines("/var/run/services_olsr")
|
|
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
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- load the node history
|
|
if nixio.fs.stat("/tmp/node.history") then
|
|
for line in io.lines("/tmp/node.history")
|
|
do
|
|
local ip, age, host = line:match("^(%S+) (%d+) (%S+)")
|
|
if ip and age and host then
|
|
history[ip] = { age = age, host = host:gsub("/", " / ") }
|
|
end
|
|
end
|
|
end
|
|
|
|
------------------
|
|
-- generate page
|
|
------------------
|
|
http_header()
|
|
html.header(node .. " mesh status", false)
|
|
local automesh = nixio.fs.stat("/tmp/web/automesh");
|
|
if automesh then
|
|
html.print("<meta http-equiv='refresh' content='10;url=/cgi-bin/mesh'>")
|
|
end
|
|
html.print([[
|
|
<style>
|
|
table {
|
|
cellspacing:5px;
|
|
width:80%;
|
|
border-collapse:collapse;
|
|
}
|
|
td {
|
|
padding-left:5px;
|
|
}
|
|
tr {
|
|
vertical-align:top;
|
|
text-align:left;
|
|
}
|
|
tr.spaceUnder>td {
|
|
padding-bottom:2em;
|
|
}
|
|
tr:hover {
|
|
background-color:rgba(128,128,128,0.15);
|
|
}
|
|
tr.h {
|
|
border-top: 1px solid black;
|
|
border-bottom: 1px solid black;
|
|
}
|
|
tr.h:hover {
|
|
background-color:inherit;
|
|
}
|
|
th {
|
|
white-space:nowrap;
|
|
vertical-align:middle;
|
|
padding:3px 5px;
|
|
}
|
|
input.search {
|
|
width:150px;
|
|
}
|
|
#nTable {
|
|
margin-bottom: 12px;
|
|
}
|
|
#nTable th:nth-child(1) {
|
|
width: 25%;
|
|
}
|
|
#nTable th:nth-child(2) {
|
|
width: 45%;
|
|
}
|
|
#nTable th:nth-child(3) {
|
|
width: 30%;
|
|
}
|
|
#cTable {
|
|
margin-bottom: 12px;
|
|
}
|
|
#cTable th:nth-child(1), #rTable th:nth-child(1) {
|
|
width: 25%;
|
|
}
|
|
#cTable th:nth-child(2), #rTable th:nth-child(2) {
|
|
width: 25%;
|
|
}
|
|
#cTable th:nth-child(3), #cTable th:nth-child(4) {
|
|
width: 6%;
|
|
}
|
|
#cTable th:nth-child(5) {
|
|
width: 8%;
|
|
}
|
|
#rTable th:nth-child(3) {
|
|
width: 20%;
|
|
}
|
|
#rTable th:nth-child(4) {
|
|
width: 30%;
|
|
}
|
|
tr.s.f.nf {
|
|
display: table-row;
|
|
}
|
|
tr.s.nf {
|
|
display: none;
|
|
}
|
|
</style>
|
|
<script>
|
|
let searchPending;
|
|
function doSearch() {
|
|
clearTimeout(searchPending);
|
|
searchPending = setTimeout(function() {
|
|
const filter = document.getElementById("srch").value.toUpperCase();
|
|
const rows = document.querySelectorAll("tr.s");
|
|
for (let i = 0; i < rows.length; i++) {
|
|
rows[i].classList.remove("f", "nf");
|
|
}
|
|
const tds = document.querySelectorAll("td.s");
|
|
for (let i = 0; i < tds.length; i++) {
|
|
const td = tds[i];
|
|
const txt = td.textContent || td.innerText;
|
|
if (txt.toUpperCase().indexOf(filter) === -1) {
|
|
td.parentElement.classList.add("nf")
|
|
}
|
|
else {
|
|
td.parentElement.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;
|
|
}
|
|
}
|
|
}, 300);
|
|
}
|
|
</script>
|
|
]])
|
|
html.print("</head>")
|
|
|
|
html.print("<body><form method=post action=/cgi-bin/mesh accept-charset=utf-8 enctype='multipart/form-data'>")
|
|
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
|
|
html.print("<table id='node_description_display'><tr><td align=center>" .. node_desc .. "</td></tr></table>")
|
|
end
|
|
html.print("<hr><nobr>")
|
|
|
|
html.print("<a href='/help.html#meshstatus' target='_blank'>Help</a> ")
|
|
|
|
if nixio.fs.stat("/tmp/web/automesh") then
|
|
html.print("<input type=submit name=stop value=Stop title='Abort continuous status'>")
|
|
else
|
|
html.print("<input type=button name=refresh value=Refresh title='Refresh this page' onclick='window.location.reload();'>")
|
|
html.print(" ")
|
|
html.print("<input type=submit name=auto value=Auto title='Automatic page refresh'>")
|
|
end
|
|
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(" ")
|
|
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>")
|
|
end
|
|
end
|
|
html.print(" ")
|
|
html.print("<button type=button onClick='window.location=\"status\"' title='Return to the status page'>Quit</button>")
|
|
html.print(" ")
|
|
html.print("<input id=srch type=search autocorrect=off spellcheck=false placeholder='Search...' onkeyup='doSearch()' onsearch='doSearch()' onkeypress='event.keyCode == 13 && event.preventDefault()'>")
|
|
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
|
|
|
|
-- show local node table
|
|
|
|
html.print("<table id=nTable class=s>")
|
|
html.print("<tr class=h>")
|
|
html.print("<th width=25%>Node Name</th>")
|
|
html.print("<th>LAN Hostname</th>")
|
|
html.print("<th>Service Name</th>")
|
|
html.print("</tr>")
|
|
|
|
if next(localhosts) then
|
|
local rows = {}
|
|
for ip, host in pairs(localhosts)
|
|
do
|
|
local localpart = host.name:match("([^.]*)%.")
|
|
if localpart then
|
|
local tactical = ""
|
|
if host.tactical ~= "" then
|
|
tactical = " / " .. host.tactical
|
|
end
|
|
local c1 = localpart .. tactical
|
|
if wangateway[ip] then
|
|
c1 = c1 .. " <small>(wan)</small>"
|
|
end
|
|
local c2 = ""
|
|
local c3 = ""
|
|
if services[host.name] then
|
|
for _, v in pairs(services[host.name])
|
|
do
|
|
c3 = c3 .. v .. "<br>"
|
|
c2 = c2 .. "<br>"
|
|
end
|
|
else
|
|
c2 = c2 .. "<br>"
|
|
c3 = c3 .. "<br>"
|
|
end
|
|
|
|
-- add locally advertised dmz hosts
|
|
for _, dmzhost in ipairs(host.hosts)
|
|
do
|
|
local nopropd = false
|
|
local aliased = false
|
|
for _, v in ipairs(host.noprops)
|
|
do
|
|
if v == dmzhost then
|
|
nopropd = true;
|
|
break
|
|
end
|
|
end
|
|
for _, v in ipairs(host.aliases)
|
|
do
|
|
if v == dmzhost then
|
|
aliased = true;
|
|
break
|
|
end
|
|
end
|
|
local localpart = dmzhost:match("(.*)%.local%.mesh")
|
|
if localpart then
|
|
if not nopropd and not aliased then
|
|
c2 = c2 .. localpart
|
|
elseif aliased then
|
|
c2 = c2 .. "<span class=aliased-hosts>" .. localpart .. "</span>"
|
|
else
|
|
c2 = c2 .. "<span class=hidden-hosts>" .. localpart .. "</span>"
|
|
end
|
|
if services[dmzhost] then
|
|
for n, v in pairs(services[dmzhost])
|
|
do
|
|
c3 = c3 .. v .. "<br>"
|
|
c2 = c2 .. "<br>"
|
|
end
|
|
else
|
|
c2 = c2 .. "<br>"
|
|
c3 = c3 .. "<br>"
|
|
end
|
|
end
|
|
end
|
|
-- Build this row
|
|
local row = "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='s f'>" .. c2 .. "</td><td class='s f'>" .. c3 .. "</td></tr>"
|
|
rows[#rows + 1] = { key = host.name, row = row }
|
|
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
|
|
else
|
|
html.print("<tr><td class='s f'>none</td></tr>")
|
|
end
|
|
|
|
-- discard
|
|
localhosts = nil
|
|
-- end local node table
|
|
html.print("</table>")
|
|
|
|
-- show current neighbors table
|
|
|
|
html.print("<table id='cTable' class=s><tr class=h>")
|
|
html.print("<th width=25%>Current Neighbors</th>")
|
|
html.print("<th>LAN Hostname</th>")
|
|
html.print("<th>LQ</th><th>NLQ</th><th>TxMbps</th>")
|
|
html.print("<th>Service Name</th>")
|
|
html.print("</tr>")
|
|
|
|
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*$")
|
|
local c1 = "<a href='http://" .. no_space_host .. ":8080/'>" .. localpart .. tactical .. "</a>"
|
|
local nodeiface
|
|
if ipmain ~= ip then
|
|
if links[ip].dtd then
|
|
nodeiface = "dtd"
|
|
elseif links[ip].xlink then
|
|
nodeiface = "xlink"
|
|
elseif links[ip].tun then
|
|
nodeiface = "tun"
|
|
else
|
|
nodeiface = "?"
|
|
end
|
|
end
|
|
if wangateway[ip] or wangateway[ipmain] then
|
|
if nodeiface then
|
|
nodeiface = nodeiface .. ",wan"
|
|
else
|
|
nodeiface = "wan"
|
|
end
|
|
end
|
|
if nodeiface then
|
|
c1 = c1 .. " <small>(" .. nodeiface .. ")</small>"
|
|
end
|
|
local c2 = ""
|
|
local c3 = string.format("%.0f%%", 100 * link.lq)
|
|
local c4 = string.format("%.0f%%", 100 * link.nlq)
|
|
local c5 = string.format("%s", link.mbps)
|
|
local c6 = ""
|
|
|
|
-- print node services if any
|
|
if not neighservices[name] then
|
|
neighservices[name] = true
|
|
if services[name] then
|
|
for _, v in pairs(services[name])
|
|
do
|
|
c6 = c6 .. v .. "<br>"
|
|
c2 = c2 .. "<br>"
|
|
end
|
|
else
|
|
c2 = c2 .. "<br>"
|
|
c6 = c6 .. "<br>"
|
|
end
|
|
|
|
-- add advertised dmz hosts
|
|
if host then
|
|
for _, dmzhost in ipairs(host.hosts)
|
|
do
|
|
local localpart = dmzhost:match("(.*)%.local%.mesh")
|
|
if localpart then
|
|
c2 = c2 .. localpart
|
|
if services[dmzhost] then
|
|
for _, v in pairs(services[dmzhost])
|
|
do
|
|
c6 = c6 .. v .. "<br>"
|
|
c2 = c2 .. "<br>"
|
|
end
|
|
else
|
|
c2 = c2 .. "<br>"
|
|
c6 = c6 .. "<br>"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Build this row
|
|
local row = "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='s f'>" .. c2 .. "</td><td>" .. c3 .. "</td><td>" .. c4 .. "</td><td>" .. c5 .. "</td><td class='s f'>" .. c6 .. "</td></tr>"
|
|
rows[#rows + 1] = { key = name, row = row }
|
|
end
|
|
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
|
|
-- discard
|
|
rows = nil
|
|
else
|
|
html.print("<tr><td class='s f'>none</td></tr>")
|
|
end
|
|
|
|
--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
|
|
local row = "<tr><td style='color:sienna;'>" .. host
|
|
if hosts[ip] and hosts[ip].hosts then
|
|
for _, v in ipairs(hosts[ip].hosts)
|
|
do
|
|
row = row .. "<br>" .. v
|
|
end
|
|
end
|
|
row = row .. "</td><td style='color:sienna;'>"
|
|
if age < 3600 then
|
|
local val = math.floor(age / 60)
|
|
if val == 1 then
|
|
row = row .. "1 minute ago"
|
|
else
|
|
row = row .. val .. " minutes ago"
|
|
end
|
|
else
|
|
local val = string.format("%.1f", age / 3600)
|
|
if val == "1.0" then
|
|
row = row .. "1 hour ago"
|
|
else
|
|
row = row .. val .. " hours ago"
|
|
end
|
|
end
|
|
row = row .. "</td></tr>"
|
|
rows[#rows + 1] = { key = age, row = row }
|
|
end
|
|
end
|
|
if #rows > 0 then
|
|
html.print("<tr><td style='color:sienna;font-weight:bold;padding-top:10px;'>Previous Neighbors</td></tr>")
|
|
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
|
|
-- end current neighbors table
|
|
html.print("</table>")
|
|
|
|
-- show remote node table
|
|
|
|
html.print("<table id='rTable' class=s><tr class=h>")
|
|
html.print("<th width=25%>Remote Nodes</th>")
|
|
html.print("<th>LAN Hostname</th>")
|
|
html.print("<th>ETX</th>")
|
|
html.print("<th>Service Name</th>")
|
|
html.print("</tr>")
|
|
|
|
local rows = {}
|
|
for ip, host in pairs(hosts)
|
|
do
|
|
local c1,c2,c3,c4
|
|
if not neighbor[ip] and host.name then
|
|
local localpart = host.name:match("(.*)%.local%.mesh")
|
|
if localpart then
|
|
local tactical = ""
|
|
if host.tactical then
|
|
tactical = " / " .. host.tactical
|
|
end
|
|
local etx = string.format("%.2f", host.etx)
|
|
c1 = "<a href='http://" .. host.name .. ":8080/'>" .. localpart .. tactical .. "</a>"
|
|
local nodeiface
|
|
local mycount = 0
|
|
if midcount[ip] then
|
|
mycount = midcount[ip]
|
|
end
|
|
if dtd[ip] then
|
|
mycount = mycount - 1
|
|
end
|
|
if hosts[ip].tactical then
|
|
mycount = mycount - 1
|
|
end
|
|
if mycount > 0 then
|
|
nodeiface = "tun*" .. mycount
|
|
end
|
|
if wangateway[ip] then
|
|
if nodeiface then
|
|
nodeiface = nodeiface .. ",wan"
|
|
else
|
|
nodeiface = "wan"
|
|
end
|
|
end
|
|
if nodeiface then
|
|
c1 = c1 .. " <small>(" .. nodeiface .. ")</small>"
|
|
end
|
|
c2 = "<br>"
|
|
c3 = string.format("%s", etx)
|
|
|
|
-- print node services if any
|
|
if services[host.name] then
|
|
local i=1
|
|
for _, v in pairs(services[host.name])
|
|
do
|
|
if c4 then
|
|
c4 = c4 .. v .. "<br>"
|
|
else
|
|
c4 = v .. "<br>"
|
|
end
|
|
if i > 1 then c2 = c2 .. "<br>" end
|
|
i=i+1
|
|
end
|
|
else
|
|
if c4 then
|
|
c4 = c4 .. "<br>"
|
|
else
|
|
c4 = "<br>"
|
|
end
|
|
end
|
|
|
|
-- add locally advertised dmz hosts
|
|
for _, dmzhost in ipairs(host.hosts)
|
|
do
|
|
local localpart = dmzhost:match("(.*)%.local%.mesh")
|
|
if localpart then
|
|
c2 = c2 .. localpart .. "<br>"
|
|
if services[dmzhost] then
|
|
local i=1
|
|
for _, v in pairs(services[dmzhost])
|
|
do
|
|
if c4 then
|
|
c4 = c4 .. v .. "<br>"
|
|
else
|
|
c4 = v .. "<br>"
|
|
end
|
|
if i > 1 then c2 = c2 .. "<br>" end
|
|
i=i+1
|
|
end
|
|
else
|
|
if c4 then
|
|
c4 = c4 .. "<br>"
|
|
else
|
|
c4 = "<br>"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Build this row
|
|
local row = "<tr class=s><td class='s f'>" .. c1 .. "</td><td class='s f'>" .. c2 .. "</td><td>" .. c3 .. "</td><td class='s f'>" .. c4 .. "</td></tr>"
|
|
rows[#rows + 1] = { key = host.etx, row = row }
|
|
end
|
|
end
|
|
end
|
|
|
|
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
|
|
-- discard
|
|
rows = nil
|
|
else
|
|
html.print("<tr><td class='s f'>none</td></tr>")
|
|
end
|
|
-- discard
|
|
neighbor = nil
|
|
dtd = nil
|
|
midcount = nil
|
|
wangateway = nil
|
|
services = nil
|
|
|
|
-- discard
|
|
links = nil
|
|
ipalias = nil
|
|
hosts = nil
|
|
history = nil
|
|
-- end remote nodes table
|
|
html.print("</table>")
|
|
|
|
html.print("</center>")
|
|
html.print("</form>")
|
|
html.footer();
|
|
html.print("</body>")
|
|
html.print("</html>")
|