<%- local iwinfo = require 'iwinfo' local ubus = require 'ubus' local unistd = require 'posix.unistd' local util = require 'gluon.util' local wireless = require 'gluon.wireless' local uci = require('simple-uci').cursor() local translations = {} local site_i18n = i18n 'gluon-site' local function _(v) translations[v] = translate(v) end -- i18n strings for JavaScript _('.') -- decimal point _('connected') _('not connected') _('1 day') _('%s days') _('%s used') _('%s packets/s') local function get_mesh() local f = loadfile('/lib/gluon/status-page/mesh.lua') if f then return f() end return {} end local mesh = get_mesh() local function get_interfaces(uconn) local interfaces = util.get_mesh_devices(uconn) table.sort(interfaces) return interfaces end local function get_radios() local ret = {} wireless.foreach_radio(uci, function(radio) local channel = iwinfo.nl80211.channel(wireless.find_phy(radio)) if channel then table.insert(ret, { name = radio['.name'], channel = channel, }) end end) table.sort(ret, function(a, b) return a.name < b.name end) return ret end local function is_wireless(iface) while true do local pattern = '/sys/class/net/' .. iface .. '/lower_*' local lower = util.glob(pattern)[1] if not lower then break end iface = lower:sub(pattern:len()) end return unistd.access('/sys/class/net/' .. iface .. '/phy80211') ~= nil end local uconn = ubus.connect() if not uconn then error('failed to connect to ubus') end local interfaces = get_interfaces(uconn) ubus.close(uconn) local radios = get_radios() local function sorted(t) t = {unpack(t)} table.sort(t) return t end local function enabled(v) return v and translate('enabled') or translate('disabled') end local function formatBits(bits) local units = {[0]='', 'k', 'M', 'G'} local unit = 0 for i = 1, #units do if math.abs(bits) < 1000 then break end unit = i bits = bits / 1000 end return string.format('%g %sbit', bits, units[unit]) end local function statistics(key, format) return string.format('<span data-statistics="%s" data-format="%s"></span>', pcdata(key), pcdata(format or 'id')) end local function statisticsTraffic(key) return string.format('%s<br>%s<br>%s', statistics(key .. '/packets', 'packetsDiff'), statistics(key .. '/bytes', 'bytesDiff'), statistics(key .. '/bytes', 'bytes') ) end http:prepare_content("text/html") -%> <!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="robots" content="noindex,nofollow"> <title><%| nodeinfo.hostname %> - <%:Status%></title> <link rel="stylesheet" href="/static/status-page.css" type="text/css"> </head> <body data-node-address="<%| http:getenv('SERVER_ADDR') %>"<%= attr('data-translations', translations) .. attr('data-node-location', nodeinfo.location) .. attr('data-mesh-provider', mesh.provider) %>> <header> <h1><%| nodeinfo.hostname %></h1> </header> <div class="container"> <div class="frame"> <h2><%:Overview%></h2> <dl> <dt><%:Node name%></dt><dd><%| nodeinfo.hostname %></dd> <% if nodeinfo.owner and nodeinfo.owner.contact then -%> <dt><%:Contact%></dt><dd><%| nodeinfo.owner.contact %></dd> <%- end %> <% if nodeinfo.location then -%> <dt><%:Location%></dt> <dd><a href="geo:<%| nodeinfo.location.latitude %>,<%| nodeinfo.location.longitude %>"> <%| nodeinfo.location.latitude %>, <%| nodeinfo.location.longitude %> </a></dd> <%- end %> <dt><%:Model%></dt><dd><%| nodeinfo.hardware.model %></dd> <dt><%:Primary MAC address%></dt><dd><%| nodeinfo.network.mac %></dd> <dt><%:IP address%></dt><dd><%= pcdata(table.concat(sorted(nodeinfo.network.addresses), '\n')):gsub('\n', '<br>') %></dd> <dt><%:Firmware%></dt><dd><%| nodeinfo.software.firmware.release %></dd> <% if nodeinfo.network.mesh_vpn then -%> <dt><%:Mesh VPN%></dt> <dd> <%| enabled(nodeinfo.network.mesh_vpn.enabled) %> <% if nodeinfo.network.mesh_vpn.provider then -%> <br><%| nodeinfo.network.mesh_vpn.provider %> <%- end %> </dd> <% if nodeinfo.network.mesh_vpn.bandwidth_limit.enabled then -%> <dt><%:Bandwidth limit%></dt> <dd> <% if nodeinfo.network.mesh_vpn.bandwidth_limit.ingress then -%> ▼ <%| formatBits(nodeinfo.network.mesh_vpn.bandwidth_limit.ingress*1000) %>/s <%:downstream%><br> <%- end %> <% if nodeinfo.network.mesh_vpn.bandwidth_limit.egress then -%> ▲ <%| formatBits(nodeinfo.network.mesh_vpn.bandwidth_limit.egress*1000) %>/s <%:upstream%> <%- end %> </dd> <%- end %> <%- end %> <dt><%:Site%></dt><dd><%| site.site_name() %></dd> <% if nodeinfo.system.domain_code then -%> <dt><%:Domain%></dt> <dd> <%| site.domain_names[nodeinfo.system.domain_code]() %> <% if nodeinfo.system.domain_code ~= nodeinfo.system.primary_domain_code then %> (<%| site.domain_names[nodeinfo.system.primary_domain_code]() %>) <%- end %> </dd> <%- end %> <% if nodeinfo.system.role then -%> <dt><%:Role%></dt><dd><%| site_i18n._translate('gluon-web-node-role:role:' .. nodeinfo.system.role) %></dd> <%- end %> <% if nodeinfo.software.autoupdater then -%> <dt><%:Automatic updates%></dt><dd><%| enabled(nodeinfo.software.autoupdater.enabled) %><%| nodeinfo.software.autoupdater.enabled and string.format(' (%s)', nodeinfo.software.autoupdater.branch) %></dd> <%- end %> <% if nodeinfo.software.babeld or nodeinfo.software['batman-adv'] then -%> <dt><%:Mesh protocol%></dt> <% if nodeinfo.software.babeld then -%> <dd>babeld <%| nodeinfo.software.babeld.version %></dd> <%- end %> <% if nodeinfo.software['batman-adv'] then -%> <dd>batman-adv <%| nodeinfo.software['batman-adv'].version %> (compat<%| nodeinfo.software['batman-adv'].compat %>)</dd> <%- end %> <%- end %> </dl> </div> <div class="frame"> <h2><%:Monitoring%></h2> <table> <tbody> <tr><th><%:Uptime%></th><td><%= statistics('uptime', 'time') %></td></tr> <tr><th><%:Load average%></th><td><%= statistics('loadavg', 'decimal') %></td></tr> <tr><th><%:RAM%></th><td><%= statistics('memory', 'memory') %></td></tr> <tr><th><%:Filesystem%></th><td><%= statistics('rootfs_usage', 'percent') %></td></tr> <tr><th><%:Gateway%></th><td><%= statistics('gateway') %> (TQ: <%= statistics('gateway_tq', 'tq') %>)<br><%= statistics('gateway_nexthop', 'neighbour') %></td></tr> </tbody> </table> <h3><%:Clients%></h3> <table> <tbody> <tr><th><%:Total%></th><td><%= statistics('clients/total') %></td></tr> <tr><th><%:Wireless 2.4 GHz%></th><td><%= statistics('clients/wifi24') %></td></tr> <tr><th><%:Wireless 5 GHz%></th><td><%= statistics('clients/wifi5') %></td></tr> </tbody> </table> <% if radios[1] then -%> <h3><%:Radios%></h3> <table> <tbody> <% for _, radio in ipairs(radios) do -%> <tr> <th><%| radio.name %></th> <td><%| translatef('Channel %u', radio.channel) %></td> </tr> <%- end %> </tbody> </table> <%- end %> <h3><%:Traffic%></h3> <table> <tbody> <tr><th><%:Transmitted%></th><td><%= statisticsTraffic('traffic/tx') %></td></tr> <tr><th><%:Received%></th><td><%= statisticsTraffic('traffic/rx') %></td></tr> <tr><th><%:Forwarded%></th><td><%= statisticsTraffic('traffic/forward') %></td></tr> </tbody> </table> <div id="mesh-vpn" style="display: none"> <h3><%:Mesh VPN%></h3> <table> <tbody id="mesh-vpn-peers"></tbody> </table> </div> </div> <div class="frame frame-wide"> <h2><%:Neighbors%></h2> <% for _, iface in ipairs(interfaces) do local wireless = is_wireless(iface) local address = util.readfile('/sys/class/net/' .. iface .. '/address') if address then %> <h3><%| iface %></h3> <div data-interface="<%| iface %>" data-interface-address="<%| util.trim(address) %>"<%= attr('data-interface-wireless', wireless) %>> <table class="datatable"> <tbody> <tr> <th><%:Node%></th> <% for i, v in ipairs(mesh.attrs or {}) do %> <th<%= attr('class', 'row-' .. v[1] ) .. attr('data-key', v[1]) .. attr('data-suffix', v[3]) %>><%| v[2] %></th> <% end %> <% if wireless then %> <th class="row-signal">dBm</th> <th class="row-distance"><%:Distance%></th> <th class="row-inactive"><%:Last seen%></th> <% end %> </tr> </tbody> </table> </div> <% end end %> </div> </div> <script src="/static/status-page.js"></script> </body> </html>