diff --git a/ffho/ffho-status-page/Makefile b/ffho/ffho-status-page/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4e44e2b29d96e08bdcd7e20d19af76158b7ab237 --- /dev/null +++ b/ffho/ffho-status-page/Makefile @@ -0,0 +1,37 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=gluon-status-page +PKG_VERSION:=1 +PKG_RELEASE:=1 + +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define Package/gluon-status-page + SECTION:=gluon + CATEGORY:=Gluon + TITLE:=Adds a status page showing information about the node. + DEPENDS:=+gluon-core +gluon-neighbour-info +uhttpd +endef + +define Package/gluon-status-page/description + Adds a status page showing information about the node. + Especially useful in combination with the next-node feature. +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/gluon-status-page/install + $(CP) ./files/* $(1)/ +endef + +$(eval $(call BuildPackage,gluon-status-page)) diff --git a/ffho/ffho-status-page/files/lib/gluon/status-page/www/cgi-bin/status b/ffho/ffho-status-page/files/lib/gluon/status-page/www/cgi-bin/status new file mode 100755 index 0000000000000000000000000000000000000000..0ad25c3ffefcdef2fdd36c180f7cc3d1c655a54c --- /dev/null +++ b/ffho/ffho-status-page/files/lib/gluon/status-page/www/cgi-bin/status @@ -0,0 +1,217 @@ +#!/usr/bin/lua + +local util = require("luci.util") +local fs = require("luci.fs") +local ltn12 = require 'luci.ltn12' +local sys = require("luci.sys") +local json = require("luci.json") +local nixio = require 'nixio' +local platform_info = require("platform_info") +local site = require 'gluon.site_config' +local ip = require 'luci.ip' +local uci = require('luci.model.uci').cursor() + +local hostname = sys.hostname() +local model = platform_info.get_model() +local release = util.trim(fs.readfile("/lib/gluon/release") or "") +local primary_mac = require('gluon.sysconfig').primary_mac +local nodeid = require('gluon.util').node_id() +local latitude = uci:get_first('gluon-node-info', 'location', 'latitude') +local longitude = uci:get_first('gluon-node-info', 'location', 'longitude') +local location = "" +if latitude and longitude then + location = "<a href=https://map.hochstift.freifunk.net/#!v:m;n:" .. nodeid .. ">" .. latitude .. ", " .. longitude .. "</a>" +else + location = "<a href=https://map.hochstift.freifunk.net/#!v:m;n:" .. nodeid .. ">none</a>" +end + +local contact = uci:get_first('gluon-node-info', 'owner', 'contact', '') +if contact == '' then + contact = "none" +end +local autoupdater = uci:get('autoupdater', 'settings', 'branch') +if uci:get_bool('autoupdater', 'settings', 'enabled') == false then + autoupdater = "disabled (" .. autoupdater .. ")" +end + +local addresses = "" +for line in io.lines('/proc/net/if_inet6') do + local matches = { line:match('^' .. string.rep('(%x%x%x%x)', 8) .. string.rep(' %x%x', 4) .. '%s+([^%s]+)$') } + if matches[9] == 'br-client' then + addresses = addresses .. " " .. ip.IPv6(string.format('%s:%s:%s:%s:%s:%s:%s:%s', unpack(matches))):string():lower() .. "\n" + end +end + +local data = io.open('/proc/meminfo'):read('*a') +local fields = {} +for k, v in data:gmatch('([^\n:]+):%s*(%d+) kB') do + fields[k] = tonumber(v) +end + +function escape_html(s) + return (s:gsub('&', '&'):gsub('<', '<'):gsub('>', '>'):gsub('"', '"')) +end + +function neighbours(ifname) + local info = util.exec("gluon-neighbour-info -d ff02::2:1001 -p 1001 -r nodeinfo -t 3 -i " .. ifname) + local macs = {} + for _, line in ipairs(util.split(info)) do + local data = json.decode(line) + if data then + local function add_macs(list) + if list then + for _, mac in ipairs(list) do + macs[mac] = data + end + end + end + + if data["network"] then + add_macs(data["network"]["mesh_interfaces"]) + + if data["network"]["mesh"] and data["network"]["mesh"]["bat0"] and + data["network"]["mesh"]["bat0"]["interfaces"] then + local interfaces = data["network"]["mesh"]["bat0"]["interfaces"] + add_macs(interfaces["other"]) + add_macs(interfaces["wireless"]) + add_macs(interfaces["tunnel"]) + end + end + end + end + + return macs +end + +io.write("Content-type: text/html\n\n") +io.write("<!DOCTYPE html>\n") +io.write("<html>") +io.write("<head>") +io.write("<meta charset=\"utf-8\"/>") +io.write("<script src=\"/status.js\"></script>") +io.write("<title>" .. escape_html(hostname) .. "</title>") +io.write("</head>") +io.write("<body>") + +io.write("<h1>" .. escape_html(hostname) .. "</h1>") +io.write("<pre>") + +io.write("Region: " .. escape_html(site.site_name) .. "\n") +io.write("Model: " .. escape_html(model) .. "\n") +io.write("Firmware: " .. escape_html(release) .. "\n") +io.write("MAC: " .. escape_html(primary_mac) .. "\n") +io.write("Contact: " .. escape_html(contact) .. "\n") +io.write("Uptime: " .. escape_html(util.trim(sys.exec("uptime | sed 's/^ \+//'"))) .. "\n") +io.write("Autoupdater: " .. escape_html(autoupdater) .. "\n") +io.write("Location: " .. location .. "\n") +io.write("IPs: " .. escape_html(util.trim(addresses)) .. "\n") +io.write("Memory: " .. string.format("%.1f %% used, %.1f %% free",(fields.MemTotal-fields.MemFree)/fields.MemTotal*100,fields.MemFree/fields.MemTotal*100) .. "\n") +io.write("</pre>") + +io.write("<h2>Neighbours</h2>") + +local interfaces = util.split(util.trim(util.exec("iw dev | egrep 'type IBSS|type mesh' -B 5 | grep Interface | cut -d' ' -f2"))) + +for _, ifname in ipairs(interfaces) do + io.write("<h3>" .. escape_html(ifname) .. "</h3>") + io.write("<pre>") + + local peer=false + for _, line in ipairs(util.split(util.exec("iw dev " .. ifname .. " station dump"))) do + local mac = line:match("^Station (.*) %(on ") + if mac then + io.write("Station <a id=\"" .. escape_html(ifname) .. "-" .. mac .. "\">" .. mac .. "</a> (on " .. escape_html(ifname) .. ")\n") + peer = true + else + io.write(escape_html(line) .. "\n") + end + end + + if peer == false then + io.write("no peers connected") + end + + io.write("</pre>") +end + +io.write("<h2>VPN status</h2>") +io.write("<pre>") + +if string.len(util.exec("ip -f inet address show dev br-wan | grep global")) >= 2 then + io.write("IPv4 configured\n") +else + io.write("IPv4 not configured\n") +end + +if string.len(util.exec("ip -f inet6 address show dev br-wan | grep global")) >= 2 then + io.write("IPv6 configured\n") +else + io.write("IPv6 not configured\n") +end + +local stat, fastd_status = pcall( + function() + local fastd_sock = nixio.socket('unix', 'stream') + assert(fastd_sock:connect('/var/run/fastd.mesh_vpn.socket')) + + decoder = json.Decoder() + ltn12.pump.all(ltn12.source.file(fastd_sock), decoder:sink()) + return decoder:get() + end +) + +if stat then + io.write(string.format("fastd running for %.3f seconds\n\n", fastd_status.uptime/1000)) + + local peers = 0 + local connections = 0 + + for key, peer in pairs(fastd_status.peers) do + peers = peers+1 + + if peer.connection then + connections = connections+1 + end + end + + io.write(string.format("There are %i peers configured, of which %i are connected:\n", peers, connections)) + + for key, peer in pairs(fastd_status.peers) do + io.write(string.format("%s: ", escape_html(peer.name))) + + if peer.connection then + io.write(string.format("connected for %.3f seconds\n", peer.connection.established/1000)) + else + io.write("not connected\n") + end + end + +else + io.write("fastd not running") +end + +io.write("</pre>") + +io.write("<script>") +for _, ifname in ipairs(interfaces) do + local macs = neighbours(ifname) + for mac, node in pairs(macs) do + local hostname = node["hostname"] + local ip + if node["network"] and node["network"]["addresses"] then + for _, myip in ipairs(node["network"]["addresses"]) do + if ip == nil and myip:sub(1, 5) ~= "fe80:" then + ip = myip + end + end + end + + if ip and hostname then + io.write("update_node(\"" .. escape_html(ifname) .. "-" .. mac .. "\", \"" .. escape_html(ip) .. "\", \"" .. escape_html(hostname) .. "\");") + end + end +end + +io.write("</script>") +io.write("</body>") +io.write("</html>") diff --git a/ffho/ffho-status-page/files/lib/gluon/status-page/www/index.html b/ffho/ffho-status-page/files/lib/gluon/status-page/www/index.html new file mode 100644 index 0000000000000000000000000000000000000000..75700015a4b45b660afba4e55d828b90c1df150c --- /dev/null +++ b/ffho/ffho-status-page/files/lib/gluon/status-page/www/index.html @@ -0,0 +1,12 @@ +<html> + <head> + <meta http-equiv="refresh" content="0; URL=/cgi-bin/status"> + <meta http-equiv="cache-control" content="no-cache"> + <meta http-equiv="expires" content="0"> + <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT"> + <meta http-equiv="pragma" content="no-cache"> + </head> + <body> + <a href="/cgi-bin/status">Redirecting...</a> + </body> +</html> diff --git a/ffho/ffho-status-page/files/lib/gluon/status-page/www/status.js b/ffho/ffho-status-page/files/lib/gluon/status-page/www/status.js new file mode 100644 index 0000000000000000000000000000000000000000..e17102d004a0b69273ba6f2c0f28b09296b08ce0 --- /dev/null +++ b/ffho/ffho-status-page/files/lib/gluon/status-page/www/status.js @@ -0,0 +1,9 @@ +function update_node(id, ip, hostname) { + var el = document.getElementById(id); + + if (!el) + return; + + el.href = "http://[" + ip + "]/"; + el.textContent += " (" + hostname + ")"; +} diff --git a/ffho/ffho-status-page/files/lib/gluon/upgrade/500-status-page b/ffho/ffho-status-page/files/lib/gluon/upgrade/500-status-page new file mode 100755 index 0000000000000000000000000000000000000000..ee7a58c90661b0d2d8848a3482e6af881c1e57da --- /dev/null +++ b/ffho/ffho-status-page/files/lib/gluon/upgrade/500-status-page @@ -0,0 +1,13 @@ +#!/bin/sh + +uci batch <<-EOF + delete uhttpd.main.listen_http + add_list uhttpd.main.listen_http=0.0.0.0:80 + add_list uhttpd.main.listen_http=[::]:80 + + delete uhttpd.main.listen_https + + set uhttpd.main.home=/lib/gluon/status-page/www + + commit uhttpd +EOF