Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 0x4A6F-master
  • 0x4A6F-rpi4
  • autinerd/experimental-openwrt-24.10
  • experimental
  • feature/addMikrotikwAP
  • master
  • nrb/airmax-test
  • nrb/ar9344-reset-sequence
  • nrb/ex400-remove-wps
  • nrb/gluon-master-cpe510
  • nrb/test-radv-filter
  • nrbffs/fastd-remove-delay
  • nrbffs/netgear-ex6120
  • v2018.2.2-ffs
  • v2018.2.3-ffs
  • v2019.1-ffs
  • v2019.1.1-ffs
  • v2019.1.2-ffs
  • v2020.1-ffs
  • v2020.1.1-ffs
  • v2020.1.3-ffs
  • v2020.2-ffs
  • v2020.2.1-ffs
  • v2020.2.2-ffs
  • v2020.2.3-ffs
  • v2021.1-ffs
  • v2021.1.1-ffs
  • v2021.1.2-ffs
  • v2022.1.1-ffs
  • v2022.1.3-ffs
  • v2022.1.4-ffs
  • v2023.1-ffs
  • v2023.2-ffs
  • v2023.2.2-ffs
  • v2023.2.3-ffs
  • v2023.2.4-ffs
  • v2023.2.5-ffs
  • experimental-2022-09-24
  • experimental-2022-09-24-base
  • experimental-2023-03-11
  • experimental-2023-03-11-base
  • experimental-2023-03-12
  • experimental-2023-03-12-base
  • experimental-2023-03-16
  • experimental-2023-03-16-base
  • experimental-2023-03-20
  • experimental-2023-03-20-base
  • experimental-2023-03-23
  • experimental-2023-03-23-base
  • experimental-2023-03-25
  • experimental-2023-03-25-base
  • experimental-2023-03-26
  • experimental-2023-03-26-base
  • experimental-2023-03-30
  • experimental-2023-03-30-base
  • experimental-2023-03-31
  • experimental-2023-03-31-base
  • experimental-2023-04-01
  • experimental-2023-04-01-base
  • experimental-2023-04-08
  • experimental-2023-04-08-base
  • experimental-2023-04-10
  • experimental-2023-04-10-base
  • experimental-2023-04-13
  • experimental-2023-04-13-base
  • experimental-2023-04-15
  • experimental-2023-04-15-base
  • experimental-2023-04-16
  • experimental-2023-04-16-base
  • experimental-2023-04-18
  • experimental-2023-04-18-base
  • experimental-2023-04-20
  • experimental-2023-04-20-base
  • experimental-2023-04-26
  • experimental-2023-04-26-base
  • experimental-2023-04-28
  • experimental-2023-04-28-base
  • experimental-2023-04-30
  • experimental-2023-04-30-base
  • experimental-2023-05-02
  • experimental-2023-05-02-base
  • experimental-2023-05-03
  • experimental-2023-05-03-base
  • experimental-2023-05-12
  • experimental-2023-05-12-base
  • experimental-2023-05-21
  • experimental-2023-05-21-base
  • experimental-2023-05-25
  • experimental-2023-05-25-base
  • experimental-2023-07-02
  • experimental-2023-07-02-base
  • experimental-2023-07-04
  • experimental-2023-07-04-base
  • experimental-2023-07-12
  • experimental-2023-07-12-base
  • experimental-2023-07-16
  • experimental-2023-07-16-base
  • experimental-2023-08-04
  • experimental-2023-08-04-base
  • experimental-2023-08-10
  • experimental-2023-08-10-base
  • experimental-2023-09-08
  • experimental-2023-09-08-base
  • experimental-2023-09-09
  • experimental-2023-09-09-base
  • experimental-2023-09-10
  • experimental-2023-09-10-base
  • experimental-2023-09-11
  • experimental-2023-09-11-base
  • experimental-2023-09-12
  • experimental-2023-09-12-base
  • experimental-2023-09-13
  • experimental-2023-09-13-base
  • experimental-2023-09-15
  • experimental-2023-09-15-base
  • experimental-2023-09-16
  • experimental-2023-09-16-base
  • experimental-2023-09-18
  • experimental-2023-09-18-base
  • experimental-2023-09-20
  • experimental-2023-09-20-base
  • experimental-2023-09-27
  • experimental-2023-09-27-base
  • experimental-2023-09-28
  • experimental-2023-09-28-base
  • experimental-2023-09-29
  • experimental-2023-09-29-base
  • experimental-2023-10-02
  • experimental-2023-10-02-base
  • experimental-2023-10-13
  • experimental-2023-10-13-base
  • experimental-2023-10-14
  • experimental-2023-10-14-base
  • experimental-2023-10-16
  • experimental-2023-10-16-base
  • experimental-2023-10-23
  • experimental-2023-10-23-base
137 results

Target

Select target project
  • firmware/gluon
  • 0x4A6F/gluon
  • patrick/gluon
3 results
Select Git revision
  • 0x4A6F-master
  • 0x4A6F-rpi4
  • 2014.3.x
  • 2014.4.x
  • babel
  • experimental
  • hoodselector
  • master
  • nrb/gluon-master-cpe510
  • nrb/test-radv-filter
  • nrbffs/fastd-remove-delay
  • nrbffs/netgear-ex6120
  • radv-filterd
  • v2015.1.x
  • v2016.1.x
  • v2016.2.4-batmanbug
  • v2016.2.x
  • v2018.2.2-ffs
  • v2018.2.3-ffs
  • v2018.2.x
  • v2019.1-ffs
  • v2019.1.1-ffs
  • v2019.1.2-ffs
  • v2020.1-ffs
  • v2020.1.1-ffs
  • v2020.1.3-ffs
  • v2020.2-ffs
  • v2020.2.1-ffs
  • v2020.2.2-ffs
  • v2020.2.3-ffs
  • v2020.2.x
  • v2021.1-ffs
  • v2021.1.1-ffs
  • v2021.1.2-ffs
  • v2014.1
  • v2014.2
  • v2014.3
  • v2014.3.1
  • v2014.4
  • v2015.1
  • v2015.1.1
  • v2015.1.2
  • v2016.1
  • v2016.1.1
  • v2016.1.2
  • v2016.1.3
  • v2016.1.4
  • v2016.1.5
  • v2016.1.6
  • v2016.2
  • v2016.2.1
  • v2016.2.2
  • v2016.2.3
  • v2016.2.4
  • v2016.2.5
  • v2016.2.6
  • v2016.2.7
  • v2017.1
  • v2017.1.1
  • v2017.1.2
  • v2017.1.3
  • v2017.1.4
  • v2017.1.5
  • v2017.1.6
  • v2017.1.7
  • v2017.1.8
  • v2018.1
  • v2018.1.1
  • v2018.1.2
  • v2018.1.3
  • v2018.1.4
  • v2018.2
  • v2018.2-ffs0.1
  • v2018.2.1
  • v2018.2.1-ffs0.1
  • v2018.2.2-ffs0.1
  • v2018.2.3-ffs0.1
  • v2019.1-ffs0.1
  • v2019.1.1-ffs0.1
  • v2019.1.2-ffs0.1
  • v2020.1-ffs0.1
  • v2020.1.1-ffs0.1
  • v2020.1.3-ffs0.1
  • v2020.2
  • v2020.2-ffs0.1
  • v2020.2.1-ffs0.1
  • v2020.2.2-ffs0.1
  • v2020.2.3-ffs0.1
  • v2020.2.3-ffs0.2
  • v2020.2.3-ffs0.3
  • v2020.2.x-ffs0.1
  • v2021.1-ffs0.1
  • v2021.1.1-ffs0.1
  • v2021.1.1-ffs0.2
  • v2021.1.1-ffs0.3
  • v2021.1.1-ffs0.4
  • v2021.1.2-ffs0.1
  • v2021.1.2-ffs0.2
98 results
Show changes
Showing
with 423 additions and 168 deletions
...@@ -3,20 +3,13 @@ ...@@ -3,20 +3,13 @@
local platform = require 'gluon.platform' local platform = require 'gluon.platform'
local sysconfig = require 'gluon.sysconfig' local sysconfig = require 'gluon.sysconfig'
if sysconfig.setup_ifname then
os.exit(0)
end
if platform.is_outdoor_device() or if platform.is_outdoor_device() or
platform.match('ar71xx', 'generic', { platform.match('ath79', 'generic', {
'airgateway', 'ubnt,unifi-ap-pro',
'uap-pro', 'ubnt,unifiac-pro',
'unifiac-pro' })
}) or
platform.match('ar71xx', 'mikrotik')
then then
sysconfig.setup_ifname = sysconfig.wan_ifname or sysconfig.lan_ifname sysconfig.setup_ifname = sysconfig.single_ifname or sysconfig.wan_ifname
else else
sysconfig.setup_ifname = sysconfig.lan_ifname or sysconfig.wan_ifname sysconfig.setup_ifname = sysconfig.single_ifname or sysconfig.lan_ifname
end end
local platform = require 'gluon.platform'
local M = {}
function M.get_status_led()
if platform.match('ath79', 'nand', {
'glinet,gl-xe300',
}) then
return "green:wlan"
end
end
return M
...@@ -3,14 +3,10 @@ include $(TOPDIR)/rules.mk ...@@ -3,14 +3,10 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=gluon-site PKG_NAME:=gluon-site
GLUON_SITEDIR = $(call qstrip,$(CONFIG_GLUON_SITEDIR)) GLUON_SITEDIR = $(call qstrip,$(CONFIG_GLUON_SITEDIR))
GLUON_SITE_VERSION = $(shell ( cd '$(GLUON_SITEDIR)' && git --git-dir=.git describe --always --dirty=+ ) 2>/dev/null || echo unknown)
PKG_VERSION:=$(if $(DUMP),x,$(GLUON_SITE_VERSION))
PKG_CONFIG_DEPENDS := CONFIG_GLUON_RELEASE CONFIG_GLUON_SITEDIR CONFIG_GLUON_MULTIDOMAIN PKG_CONFIG_DEPENDS := CONFIG_GLUON_SITE_VERSION CONFIG_GLUON_RELEASE CONFIG_GLUON_SITEDIR CONFIG_GLUON_MULTIDOMAIN
PKG_FILE_DEPENDS := $(GLUON_SITEDIR)/site.conf $(GLUON_SITEDIR)/domains/ $(GLUON_SITEDIR)/i18n/ PKG_FILE_DEPENDS := $(GLUON_SITEDIR)/site.conf $(GLUON_SITEDIR)/domains/ $(GLUON_SITEDIR)/i18n/
PKG_BUILD_DEPENDS := lua-cjson/host gluon-web/host PKG_BUILD_DEPENDS := lua-jsonc/host gluon-web/host
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include ../gluon.mk include ../gluon.mk
...@@ -21,6 +17,11 @@ define Package/gluon-site ...@@ -21,6 +17,11 @@ define Package/gluon-site
endef endef
define Package/gluon-site/config define Package/gluon-site/config
config GLUON_SITE_VERSION
string "Gluon site version number"
depends on PACKAGE_gluon-site
default ""
config GLUON_RELEASE config GLUON_RELEASE
string "Gluon release number" string "Gluon release number"
depends on PACKAGE_gluon-site depends on PACKAGE_gluon-site
...@@ -38,7 +39,7 @@ endef ...@@ -38,7 +39,7 @@ endef
define GenerateJSON define GenerateJSON
GLUON_SITEDIR='$$(GLUON_SITEDIR)' GLUON_SITE_CONFIG='$(1).conf' \ GLUON_SITEDIR='$$(GLUON_SITEDIR)' GLUON_SITE_CONFIG='$(1).conf' \
lua -e 'print(require("cjson").encode(assert(dofile("../../scripts/site_config.lua")(os.getenv("GLUON_SITE_CONFIG")))))' \ lua -e 'print(require("jsonc").stringify(assert(dofile("../../scripts/site_config.lua")(os.getenv("GLUON_SITE_CONFIG")))))' \
> '$$(PKG_BUILD_DIR)/$(1).json' > '$$(PKG_BUILD_DIR)/$(1).json'
endef endef
...@@ -49,7 +50,14 @@ define Build/Compile ...@@ -49,7 +50,14 @@ define Build/Compile
rm -rf $(PKG_BUILD_DIR)/domains rm -rf $(PKG_BUILD_DIR)/domains
mkdir -p $(PKG_BUILD_DIR)/domains mkdir -p $(PKG_BUILD_DIR)/domains
$(foreach domain,$(patsubst $(GLUON_SITEDIR)/domains/%.conf,%,$(wildcard $(GLUON_SITEDIR)/domains/*.conf)), $(foreach domain,$(patsubst $(GLUON_SITEDIR)/domains/%.conf,%,$(wildcard $(GLUON_SITEDIR)/domains/*.conf)),
[ ! -e '$(PKG_BUILD_DIR)/domains/$(domain).json' ] @if [ -e '$(PKG_BUILD_DIR)/domains/$(domain).json' ]; then \
link='$(PKG_BUILD_DIR)/domains/$(domain).json'; \
other="$$$$(basename $$$$(readlink -f "$$$$link") .json)"; \
if [ "$$$$other" ]; then \
echo >&2 "Error: Failed to install domain '"'$(domain)'"', name already taken by domain '$$$$other'."; \
fi; \
exit 1; \
fi
$(call GenerateJSON,domains/$(domain)) $(call GenerateJSON,domains/$(domain))
@lua ../../scripts/domain_aliases.lua '$(PKG_BUILD_DIR)/domains/$(domain).json' | while read alias; do \ @lua ../../scripts/domain_aliases.lua '$(PKG_BUILD_DIR)/domains/$(domain).json' | while read alias; do \
[ "$$$${alias}" != '$(domain)' ] || continue; \ [ "$$$${alias}" != '$(domain)' ] || continue; \
...@@ -57,7 +65,7 @@ define Build/Compile ...@@ -57,7 +65,7 @@ define Build/Compile
if ! ln -s '$(domain).json' "$$$$link"; then \ if ! ln -s '$(domain).json' "$$$$link"; then \
other="$$$$(basename $$$$(readlink -f "$$$$link") .json)"; \ other="$$$$(basename $$$$(readlink -f "$$$$link") .json)"; \
if [ "$$$$other" ]; then \ if [ "$$$$other" ]; then \
echo >&2 "Failed to alias domain '"'$(domain)'"' as '$$$$alias', name already taken by domain '$$$$other'."; \ echo >&2 "Error: Failed to alias domain '"'$(domain)'"' as '$$$$alias', name already taken by domain '$$$$other'."; \
fi; \ fi; \
exit 1; \ exit 1; \
fi; \ fi; \
...@@ -76,7 +84,7 @@ define Package/gluon-site/install ...@@ -76,7 +84,7 @@ define Package/gluon-site/install
$(CP) $(PKG_BUILD_DIR)/domains $(1)/lib/gluon/ $(CP) $(PKG_BUILD_DIR)/domains $(1)/lib/gluon/
endif endif
echo '$(GLUON_SITE_VERSION)' > $(1)/lib/gluon/site-version echo '$(call qstrip,$(CONFIG_GLUON_SITE_VERSION))' > $(1)/lib/gluon/site-version
echo '$(call qstrip,$(CONFIG_GLUON_RELEASE))' > $(1)/lib/gluon/release echo '$(call qstrip,$(CONFIG_GLUON_RELEASE))' > $(1)/lib/gluon/release
$(GluonInstallI18N) $(GluonInstallI18N)
......
include $(TOPDIR)/rules.mk
PKG_NAME:=gluon-state-check
include ../gluon.mk
define Package/gluon-state-check
TITLE:=Provides info about the routers state
DEPENDS:=+gluon-core +micrond
endef
define Package/gluon-state-check/description
gluon-state-check executes checks in `/lib/gluon/state/check.d/` and provides
a flag file for each check in `/var/gluon/state` depending on the return code
of the check. A flag file is created (or "touched") if the corresponding check
exits cleanly and gets removed otherwise. If the flags are "touched", they
are only accessed, but not modified. In this way, the atime of a flag file
reflects when the last check was performed and the mtime reflects when
when the state was last changed.
This package provides the following checks:
- `has_default_gw6` - check whether the router has a default IPv6-route on br-client.
- `has_ntp_sync` - check whether the last stratum event of busybox ntpd was <16
- `has_lost_ntp_sync` - check whether the last stratum event of busybox was 16
The checks are executed once every minute (by micron.d).
The two NTP checks are hotplug results of ntpd and as a result not available
in the first 11 minutes of uptime.
endef
$(eval $(call BuildPackageGluon,gluon-state-check))
#!/bin/sh
sync_flag="/var/gluon/state/has_ntp_sync"
unsync_flag="/var/gluon/state/has_lost_ntp_sync"
# shellcheck disable=SC2154 # stratum is set by busybox ntpd.
if [ "$stratum" = 16 ]
then
touch -a "$unsync_flag"
rm -f "$sync_flag"
elif [ "$stratum" -lt 16 ]
then
touch -a "$sync_flag"
rm -f "$unsync_flag"
fi
#!/bin/sh
out=$(ip -6 route show default dev br-client 2>/dev/null) && [ -n "$out" ]
#!/bin/busybox sh
# Show the current ntp state using gluon-state-check.
#
# In case of an unsynced ntp session the file has_lost_ntp_sync is created.
# Its atime will be bumped upon further failed synchronizations.
# As long as there is no sync, the file has_ntp_sync is absent.
#
# Vice versa, the file has_ntp_sync is created upon stratum-level <16.
# As long as the sync persists, only the files atime is updated.
# Furthermore, if there is an ntp sync, the file has_lost_ntp_sync is deleted.
#
# This allows the following code to emit the current ntp status and how long its been going on.
# These two variables must not contain spaces in order for the awk splitting to work.
HAS_LOST_NTP_SYNC="/var/gluon/state/has_lost_ntp_sync"
HAS_NTP_SYNC="/var/gluon/state/has_ntp_sync"
get_duration() {
local flag_file
local use_atime
local mtime
local time_epoch
local current_time
flag_file=$1
use_atime=$2
if [ "$use_atime" = true ]; then
ls_option="-u"
fi
# shellcheck disable=SC2012 # stat is unavailable and busybox find does not emit the necessary fields.
mtime=$(ls -l "$ls_option" "$flag_file" --full-time | awk '{print $6, $7}') 2>/dev/null
time_epoch=$(date -d "$mtime" +%s)
current_time=$(date +%s)
echo $((current_time - time_epoch))
}
if [ -f "$HAS_NTP_SYNC" ]; then
duration=$(get_duration "$HAS_NTP_SYNC")
last_checked=$(get_duration "$HAS_NTP_SYNC" true)
echo "NTP has been synced for $duration seconds (last checked $last_checked seconds ago)."
elif [ -f "$HAS_LOST_NTP_SYNC" ]; then
duration=$(get_duration "$HAS_LOST_NTP_SYNC")
last_checked=$(get_duration "$HAS_LOST_NTP_SYNC" true)
echo "NTP has been unsynced for $duration seconds (last checked $last_checked seconds ago)."
else
echo "NTP state is unknown, ntpd hotplugs might not have been invoked, yet."
fi
* * * * * /usr/sbin/gluon-state-check
#!/usr/bin/lua
local util = require 'gluon.util'
local unistd = require 'posix.unistd'
local state_dir = "/var/gluon/state/"
local check_dir = "/lib/gluon/state/check.d/"
local function set_flag(stateflag, state)
if state then
-- this does not modify atime
local flaghandle = io.open(stateflag, "w")
flaghandle:close()
else
os.remove(stateflag)
end
end
local function exec_check(checkpath)
local checkname = string.sub(checkpath, #check_dir+1)
local ret = os.execute(checkpath)
local flagfile = state_dir..checkname
set_flag(flagfile, 0==ret)
end
local function run_executable_checks()
for _, v in ipairs(util.glob(check_dir..'*')) do
if unistd.access(v, 'x') then
exec_check(v)
end
end
end
-- ensure state path exists
if not unistd.access(state_dir) then
os.execute("mkdir -p "..state_dir)
end
run_executable_checks()
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=gluon-status-page-mesh-batman-adv PKG_NAME:=gluon-status-page-mesh-batman-adv
PKG_VERSION:=1
include ../gluon.mk include ../gluon.mk
......
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=gluon-status-page PKG_NAME:=gluon-status-page
PKG_VERSION:=3
include ../gluon.mk include ../gluon.mk
...@@ -18,8 +17,10 @@ define Package/gluon-status-page/install ...@@ -18,8 +17,10 @@ define Package/gluon-status-page/install
$(INSTALL_DIR) $(1)/lib/gluon/status-page/providers $(INSTALL_DIR) $(1)/lib/gluon/status-page/providers
$(INSTALL_BIN) $(PKG_BUILD_DIR)/stations $(1)/lib/gluon/status-page/providers/ $(INSTALL_BIN) $(PKG_BUILD_DIR)/stations $(1)/lib/gluon/status-page/providers/
ifndef CONFIG_GLUON_MINIFY ifdef CONFIG_GLUON_MINIFY
$(CP) ./javascript/status-page.js $(1)/lib/gluon/status-page/www/static/status-page.js $(INSTALL_DATA) ./javascript/status-page.min.js $(1)/lib/gluon/status-page/www/static/status-page.js
else
$(INSTALL_DATA) ./javascript/status-page.js $(1)/lib/gluon/status-page/www/static/status-page.js
endif endif
$(INSTALL_DIR) $(1)/lib/gluon/status-page/view/ $(INSTALL_DIR) $(1)/lib/gluon/status-page/view/
......
<%- <%-
http:prepare_content("application/xhtml+xml") http:prepare_content("text/html")
-%> -%>
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> <html lang="">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no" /> <meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title><%:Error%></title> <title><%:Error%></title>
<link rel="stylesheet" href="/static/status-page.css" type="text/css" /> <link rel="stylesheet" href="/static/status-page.css" type="text/css">
</head> </head>
<body> <body>
<header> <header>
......
<%- <%-
local iwinfo = require 'iwinfo'
local ubus = require 'ubus' local ubus = require 'ubus'
local unistd = require 'posix.unistd' local unistd = require 'posix.unistd'
local util = require 'gluon.util' local util = require 'gluon.util'
local wireless = require 'gluon.wireless'
local uci = require('simple-uci').cursor()
local translations = {} local translations = {}
local site_i18n = i18n 'gluon-site'
local function _(v) local function _(v)
translations[v] = translate(v) translations[v] = translate(v)
...@@ -28,17 +33,31 @@ ...@@ -28,17 +33,31 @@
local mesh = get_mesh() local mesh = get_mesh()
local function get_interfaces() local function get_interfaces(uconn)
local uconn = ubus.connect()
if not uconn then
error('failed to connect to ubus')
end
local interfaces = util.get_mesh_devices(uconn) local interfaces = util.get_mesh_devices(uconn)
ubus.close(uconn)
table.sort(interfaces) table.sort(interfaces)
return interfaces return interfaces
end 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) local function is_wireless(iface)
while true do while true do
local pattern = '/sys/class/net/' .. iface .. '/lower_*' local pattern = '/sys/class/net/' .. iface .. '/lower_*'
...@@ -48,10 +67,19 @@ ...@@ -48,10 +67,19 @@
iface = lower:sub(pattern:len()) iface = lower:sub(pattern:len())
end end
return unistd.access('/sys/class/net/' .. iface .. '/wireless') ~= nil return unistd.access('/sys/class/net/' .. iface .. '/phy80211') ~= nil
end end
local interfaces = get_interfaces() 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) local function sorted(t)
t = {unpack(t)} t = {unpack(t)}
...@@ -63,29 +91,45 @@ ...@@ -63,29 +91,45 @@
return v and translate('enabled') or translate('disabled') return v and translate('enabled') or translate('disabled')
end 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) local function statistics(key, format)
return string.format('<span data-statistics="%s" data-format="%s"></span>', pcdata(key), pcdata(format or 'id')) return string.format('<span data-statistics="%s" data-format="%s"></span>', pcdata(key), pcdata(format or 'id'))
end end
local function statisticsTraffic(key) local function statisticsTraffic(key)
return string.format('%s<br />%s<br />%s', return string.format('%s<br>%s<br>%s',
statistics(key .. '/packets', 'packetsDiff'), statistics(key .. '/packets', 'packetsDiff'),
statistics(key .. '/bytes', 'bytesDiff'), statistics(key .. '/bytes', 'bytesDiff'),
statistics(key .. '/bytes', 'bytes') statistics(key .. '/bytes', 'bytes')
) )
end end
http:prepare_content("application/xhtml+xml") http:prepare_content("text/html")
-%> -%>
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> <html lang="">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no" /> <meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title><%| nodeinfo.hostname %> - <%:Status%></title> <title><%| nodeinfo.hostname %> - <%:Status%></title>
<link rel="stylesheet" href="/static/status-page.css" type="text/css" /> <link rel="stylesheet" href="/static/status-page.css" type="text/css">
</head> </head>
<body data-node-address="<%| http:getenv('SERVER_ADDR') %>"<%= <body data-node-address="<%| http:getenv('SERVER_ADDR') %>"<%=
attr('data-translations', translations) .. attr('data-translations', translations) ..
...@@ -111,10 +155,27 @@ ...@@ -111,10 +155,27 @@
<%- end %> <%- end %>
<dt><%:Model%></dt><dd><%| nodeinfo.hardware.model %></dd> <dt><%:Model%></dt><dd><%| nodeinfo.hardware.model %></dd>
<dt><%:Primary MAC address%></dt><dd><%| nodeinfo.network.mac %></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><%: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> <dt><%:Firmware%></dt><dd><%| nodeinfo.software.firmware.release %></dd>
<% if nodeinfo.software.fastd then -%> <% if nodeinfo.network.mesh_vpn then -%>
<dt><%:Mesh VPN%></dt><dd><%| enabled(nodeinfo.software.fastd.enabled) %></dd> <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 %> <%- end %>
<dt><%:Site%></dt><dd><%| site.site_name() %></dd> <dt><%:Site%></dt><dd><%| site.site_name() %></dd>
<% if nodeinfo.system.domain_code then -%> <% if nodeinfo.system.domain_code then -%>
...@@ -126,35 +187,70 @@ ...@@ -126,35 +187,70 @@
<%- end %> <%- end %>
</dd> </dd>
<%- end %> <%- 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 -%> <% if nodeinfo.software.autoupdater then -%>
<dt><%:Automatic updates%></dt><dd><%| enabled(nodeinfo.software.autoupdater.enabled) %><%| <dt><%:Automatic updates%></dt><dd><%| enabled(nodeinfo.software.autoupdater.enabled) %><%|
nodeinfo.software.autoupdater.enabled and nodeinfo.software.autoupdater.enabled and
string.format(' (%s)', nodeinfo.software.autoupdater.branch) string.format(' (%s)', nodeinfo.software.autoupdater.branch)
%></dd> %></dd>
<%- end %> <%- end %>
<% if nodeinfo.software['batman-adv'] then -%>
<dt><%:Mesh protocol%></dt>
<% if nodeinfo.software['batman-adv'] then -%>
<dd>batman-adv <%| nodeinfo.software['batman-adv'].version %> (compat<%| nodeinfo.software['batman-adv'].compat %>)</dd>
<%- end %>
<%- end %>
</dl> </dl>
</div> </div>
<div class="frame"> <div class="frame">
<h2><%:Monitoring%></h2> <h2><%:Monitoring%></h2>
<table> <table>
<tbody>
<tr><th><%:Uptime%></th><td><%= statistics('uptime', 'time') %></td></tr> <tr><th><%:Uptime%></th><td><%= statistics('uptime', 'time') %></td></tr>
<tr><th><%:Load average%></th><td><%= statistics('loadavg', 'decimal') %></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><%:RAM%></th><td><%= statistics('memory', 'memory') %></td></tr>
<tr><th><%:Filesystem%></th><td><%= statistics('rootfs_usage', 'percent') %></td></tr> <tr><th><%:Filesystem%></th><td><%= statistics('rootfs_usage', 'percent') %></td></tr>
<tr><th><%:Gateway%></th><td><%= statistics('gateway') %><br /><%= statistics('gateway_nexthop', 'neighbour') %></td></tr> <tr><th><%:Gateway%></th><td><%= statistics('gateway') %> (TQ: <%= statistics('gateway_tq', 'tq') %>)<br><%= statistics('gateway_nexthop', 'neighbour') %></td></tr>
<tr><th><%:Clients%></th><td><%= statistics('clients/total') %></td></tr> </tbody>
</table> </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> <h3><%:Traffic%></h3>
<table> <table>
<tbody>
<tr><th><%:Transmitted%></th><td><%= statisticsTraffic('traffic/tx') %></td></tr> <tr><th><%:Transmitted%></th><td><%= statisticsTraffic('traffic/tx') %></td></tr>
<tr><th><%:Received%></th><td><%= statisticsTraffic('traffic/rx') %></td></tr> <tr><th><%:Received%></th><td><%= statisticsTraffic('traffic/rx') %></td></tr>
<tr><th><%:Forwarded%></th><td><%= statisticsTraffic('traffic/forward') %></td></tr> <tr><th><%:Forwarded%></th><td><%= statisticsTraffic('traffic/forward') %></td></tr>
</tbody>
</table> </table>
<div id="mesh-vpn" style="display: none"> <div id="mesh-vpn" style="display: none">
<h3><%:Mesh VPN%></h3> <h3><%:Mesh VPN%></h3>
<table id="mesh-vpn-peers"> <table>
<tbody id="mesh-vpn-peers"></tbody>
</table> </table>
</div> </div>
</div> </div>
...@@ -170,6 +266,7 @@ ...@@ -170,6 +266,7 @@
<h3><%| iface %></h3> <h3><%| iface %></h3>
<div data-interface="<%| iface %>" data-interface-address="<%| util.trim(address) %>"<%= attr('data-interface-wireless', wireless) %>> <div data-interface="<%| iface %>" data-interface-address="<%| util.trim(address) %>"<%= attr('data-interface-wireless', wireless) %>>
<table class="datatable"> <table class="datatable">
<tbody>
<tr> <tr>
<th><%:Node%></th> <th><%:Node%></th>
<% for i, v in ipairs(mesh.attrs or {}) do %> <% for i, v in ipairs(mesh.attrs or {}) do %>
...@@ -181,6 +278,7 @@ ...@@ -181,6 +278,7 @@
<th class="row-inactive"><%:Last seen%></th> <th class="row-inactive"><%:Last seen%></th>
<% end %> <% end %>
</tr> </tr>
</tbody>
</table> </table>
</div> </div>
<% <%
......
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"> <html lang="">
<head> <head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0" /> <meta http-equiv="Expires" content="0">
<meta http-equiv="refresh" content="0; URL=/cgi-bin/status" /> <meta http-equiv="refresh" content="0; URL=/cgi-bin/status">
<meta name="robots" content="noindex,nofollow">
</head> </head>
<body> <body>
</body> </body>
......
User-agent: *
Disallow: /
html,body,div,span,h1,h2,h3,dl,dt,dd,canvas,header,table,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{background:rgba(0,0,0,0.12);font-family:Roboto, Lucida Grande, sans, Arial;color:rgba(0,0,0,0.87);font-size:14px;line-height:1}a{color:rgba(220,0,103,0.87);text-decoration:none;margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}a:hover{text-decoration:underline}h1{font-weight:bold}h2{font-size:16px;margin-bottom:16px;color:rgba(0,0,0,0.54)}h3{font-size:15px;margin-top:16px;margin-bottom:8px;color:rgba(0,0,0,0.54)}header{display:flex;padding:0 14px;background:#dc0067;color:rgba(255,255,255,0.98);position:absolute;top:0;width:100%;box-sizing:border-box;height:20vh;z-index:-1;box-shadow:0px 5px 6px rgba(0,0,0,0.16),0px 1.5px 3px rgba(0,0,0,0.23);white-space:nowrap}header h1{font-size:24px;margin:10px 0;padding:6px 0;text-overflow:ellipsis;overflow:hidden;flex:1}.container{display:flex;max-width:90vw;margin:64px auto 24px auto;background:#fdfdfd;box-shadow:0px 5px 20px rgba(0,0,0,0.19),0px 3px 6px rgba(0,0,0,0.23)}.container>.frame{flex:1;border-style:solid;border-color:rgba(0,0,0,0.12);box-sizing:border-box;padding:16px}.container>.frame+.frame{border-width:0 0 0 1px}.container>.frame-wide{flex:2}dt,th,td::before{font-weight:bold;color:rgba(0,0,0,0.87)}dt{margin-bottom:4px}th,td{text-align:left;padding:4px 16px 4px 0}th:last-child,td:last-child{padding-right:0}dd,td{font-weight:normal;font-size:0.9em;color:rgba(0,0,0,0.54)}dd{margin-bottom:16px}table{border-collapse:collapse;border-spacing:0}table.datatable{width:100%;table-layout:fixed}table.datatable th,table.datatable td{font-size:1em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.datatable th.row-tq{width:42px}table.datatable th.row-signal{width:36px}table.datatable th.row-distance{width:90px}table.datatable th.row-inactive{width:130px}table.datatable tr.inactive{opacity:0.33}table.datatable tr.highlight{background:rgba(255,180,0,0.25)}canvas.signalgraph{margin-top:8px;width:100%}@media only screen and (max-width: 1250px){.container{max-width:none;margin:56px 0 0}header{height:56px;z-index:1;position:fixed}.datatable tr{display:block;margin-bottom:15px}.datatable tr:first-child{margin-bottom:0}.datatable th{display:none}.datatable td{display:block;position:relative;padding-left:150px;max-width:calc(100% - 150px)}.datatable td::before{position:absolute;left:5px;content:attr(data-label)}}@media only screen and (max-width: 700px){.container{display:block}.container>.frame+.frame{border-width:1px 0 0 0}} html,body,div,span,h1,h2,h3,dl,dt,dd,canvas,header,table,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{background:rgba(0,0,0,0.12);font-family:Roboto, Lucida Grande, sans, Arial;color:rgba(0,0,0,0.87);font-size:14px;line-height:1}a{color:rgba(220,0,103,0.87);text-decoration:none;margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}a:hover{text-decoration:underline}h1{font-weight:bold}h2{font-size:16px;margin-bottom:16px;color:rgba(0,0,0,0.54)}h3{font-size:15px;margin-top:16px;margin-bottom:8px;color:rgba(0,0,0,0.54)}header{display:flex;padding:0 14px;background:#dc0067;color:rgba(255,255,255,0.98);position:absolute;top:0;width:100%;box-sizing:border-box;height:20vh;z-index:-1;box-shadow:0px 5px 6px rgba(0,0,0,0.16),0px 1.5px 3px rgba(0,0,0,0.23);white-space:nowrap}header h1{font-size:24px;margin:10px 0;padding:6px 0;text-overflow:ellipsis;overflow:hidden;flex:1}.container{display:flex;max-width:90vw;margin:64px auto 24px auto;background:#fdfdfd;box-shadow:0px 5px 20px rgba(0,0,0,0.19),0px 3px 6px rgba(0,0,0,0.23)}.container>.frame{flex:1;border-style:solid;border-color:rgba(0,0,0,0.12);box-sizing:border-box;padding:16px}.container>.frame+.frame{border-width:0 0 0 1px}.container>.frame-wide{flex:2}dt,th,td::before{font-weight:bold;color:rgba(0,0,0,0.87)}dt{margin-bottom:4px}th,td{text-align:left;padding:4px 16px 4px 0}th:last-child,td:last-child{padding-right:0}dd,td{font-weight:normal;font-size:0.9em;color:rgba(0,0,0,0.54)}dd{margin-bottom:16px}table{border-collapse:collapse;border-spacing:0}table.datatable{width:100%;table-layout:fixed}table.datatable th,table.datatable td{font-size:1em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.datatable th.row-tq{width:45px}table.datatable th.row-signal{width:36px}table.datatable th.row-distance{width:90px}table.datatable th.row-inactive{width:130px}table.datatable tr.inactive{opacity:0.33}table.datatable tr.highlight{background:rgba(255,180,0,0.25)}canvas.signalgraph{margin-top:8px;width:100%}@media only screen and (max-width: 1250px){.container{max-width:none;margin:56px 0 0}header{height:56px;z-index:1;position:fixed}.datatable tr{display:block;margin-bottom:15px}.datatable tr:first-child{margin-bottom:0}.datatable th{display:none}.datatable td{display:block;position:relative;padding-left:150px;max-width:calc(100% - 150px)}.datatable td::before{position:absolute;left:5px;content:attr(data-label)}}@media only screen and (max-width: 700px){.container{display:block}.container>.frame+.frame{border-width:1px 0 0 0}}
"use strict";!function(){var a=JSON.parse(document.body.getAttribute("data-translations"));function i(t,e){return t.toFixed(e).replace(/\./,a["."])}function o(t,e){e--;for(var n=t;10<=n&&0<e;n/=10)e--;return i(t,e)}function r(t){return function(t,e,n){var r=0;if(void 0===n)return"- ";for(;e<n&&r<t.length-1;)n/=e,r++;return(n=o(n,3))+" "+t[r]}(["","K","M","G","T"],1024,t)}String.prototype.sprintf=function(){var t=0,e=arguments;return this.replace(/%s/g,function(){return e[t++]})};var u={id:function(t){return t},decimal:function(t){return i(t,2)},percent:function(t){return a["%s used"].sprintf(o(100*t,3)+"%")},memory:function(t){var e=1-t.available/t.total;return u.percent(e)},time:function(t){var e=Math.round(t/60),n=Math.floor(e/1440),r=Math.floor(e%1440/60);e=Math.floor(e%60);var i="";return 1===n?i+=a["1 day"]+", ":1<n&&(i+=a["%s days"].sprintf(n)+", "),i+=r+":",e<10&&(i+="0"),i+=e},packetsDiff:function(t,e,n){if(0<n)return r=(t-e)/n,a["%s packets/s"].sprintf(i(r,0));var r},bytesDiff:function(t,e,n){if(0<n)return r(8*((t-e)/n))+"bps"},bytes:function(t){return r(t)+"B"},neighbour:function(t){if(!t)return"";for(var e in c){var n=c[e].lookup_neigh(t);if(n)return"via "+n.get_hostname()+" ("+e+")"}return"via "+t+" (unknown iface)"}};function l(e,t){return t.split("/").forEach(function(t){e=e&&e[t]}),e}function h(t,n){var e=new EventSource(t),r={};e.onmessage=function(t){var e=JSON.parse(t.data);n(e,r),r=e},e.onerror=function(){e.close(),window.setTimeout(function(){h(t,n)},3e3)}}var y,w=document.body.getAttribute("data-node-address");try{y=JSON.parse(document.body.getAttribute("data-node-location"))}catch(t){}function t(t){var e=document.getElementById("mesh-vpn");if(t){e.style.display="";for(var i=document.getElementById("mesh-vpn-peers");i.lastChild;)i.removeChild(i.lastChild);var n=function e(n,r){return Object.keys(r.peers||{}).forEach(function(t){n.push([t,r.peers[t]])}),Object.keys(r.groups||{}).forEach(function(t){e(n,r.groups[t])}),n}([],t);n.sort(),n.forEach(function(t){var e=document.createElement("tr"),n=document.createElement("th");n.textContent=t[0],e.appendChild(n);var r=document.createElement("td");t[1]?r.textContent=a.connected+" ("+u.time(t[1].established)+")":r.textContent=a["not connected"],e.appendChild(r),i.appendChild(e)})}else e.style.display="none"}var e=document.querySelectorAll("[data-statistics]");h("/cgi-bin/dyn/statistics",function(o,c){var s=o.uptime-c.uptime;e.forEach(function(t){var e=t.getAttribute("data-statistics"),n=t.getAttribute("data-format"),r=l(c,e),i=l(o,e);try{var a=u[n](i,r,s);void 0!==a&&(t.textContent=a)}catch(t){console.error(t)}});try{t(o.mesh_vpn)}catch(t){console.error(t)}});var c={};function E(a){var o=document.createElement("canvas"),c=o.getContext("2d"),s=null;return{canvas:o,highlight:!1,resize:function(t,e){try{c.getImageData(0,0,t,e)}catch(t){}o.width=t,o.height=e},draw:function(t,e){var n,r,i=e(s);c.clearRect(t,0,5,o.height),i&&(n=t,r=i,c.beginPath(),c.fillStyle=a,c.arc(n,r,1.2,0,2*Math.PI,!1),c.closePath(),c.fill())},set:function(t){s=t}}}function f(){var s=-100,u=0,n=0,r=[],l=document.createElement("canvas");l.className="signalgraph",l.height=200;var h=l.getContext("2d");function t(){l.width=l.clientWidth,r.forEach(function(t){t.resize(l.width,l.height)})}function i(){if(0!==l.clientWidth){l.width!==l.clientWidth&&t(),h.clearRect(0,0,l.width,l.height);var e=!1;r.forEach(function(t){t.highlight&&(e=!0)}),h.save(),r.forEach(function(t){e&&(h.globalAlpha=.2),t.highlight&&(h.globalAlpha=1),t.draw(n,function(t){return e=t,n=s,r=u,i=l.height,(1-(e-n)/(r-n))*i;var e,n,r,i}),h.drawImage(t.canvas,0,0)}),h.restore(),h.save(),h.beginPath(),h.strokeStyle="rgba(255, 180, 0, 0.15)",h.lineWidth=5,h.moveTo(n+2.5,0),h.lineTo(n+2.5,l.height),h.stroke(),function(){var t,e,n,r,i=Math.floor(l.height/40);h.save(),h.lineWidth=.5,h.strokeStyle="rgba(0, 0, 0, 0.25)",h.fillStyle="rgba(0, 0, 0, 0.5)",h.textAlign="end",h.textBaseline="bottom",h.beginPath();for(var a=0;a<i;a++){var o=l.height-40*a;h.moveTo(0,o-.5),h.lineTo(l.width,o-.5);var c=Math.round((t=o,e=s,n=u,r=l.height,(e*t+n*(r-t))/r))+" dBm";h.save(),h.strokeStyle="rgba(255, 255, 255, 0.9)",h.lineWidth=4,h.miterLimit=2,h.strokeText(c,l.width-5,o-2.5),h.fillText(c,l.width-5,o-2.5),h.restore()}h.stroke(),h.strokeStyle="rgba(0, 0, 0, 0.83)",h.lineWidth=1.5,h.strokeRect(.5,.5,l.width-1,l.height-1),h.restore()}()}}t(),window.addEventListener("resize",i);var a=0;return window.requestAnimationFrame(function t(e){40<e-a&&(i(),n=(n+1)%l.width,a=e),window.requestAnimationFrame(t)}),{el:l,addSignal:function(t){r.push(t),t.resize(l.width,l.height)},removeSignal:function(t){r.splice(r.indexOf(t),1)}}}function d(t,e,n,r){var i=t.table.firstElementChild,a=t.table.insertRow(),o=a.insertCell();if(o.setAttribute("data-label",i.children[0].textContent),t.wireless){var c=document.createElement("span");c.textContent="",c.style.color=n,o.appendChild(c)}var f=document.createElement("span");f.textContent=e,o.appendChild(f);var s,d,u,l,h,g={};function v(t){var e=t.getAttribute("data-key");if(e){var n=t.getAttribute("data-suffix")||"",r=a.insertCell();r.textContent="-",r.setAttribute("data-label",t.textContent),g[e]={td:r,suffix:n}}}for(var m=0;m<i.children.length;m++)v(i.children[m]);function p(){h&&window.clearTimeout(h),h=window.setTimeout(function(){l&&t.signalgraph.removeSignal(l),a.parentNode.removeChild(a),r()},6e4)}function b(t){var e=function(t){"::"==t.slice(0,2)&&(t="0"+t),"::"==t.slice(-2)&&(t+="0");var e=t.split(":"),n=e.length,r=[];return e.forEach(function(t,e){if(""===t)for(;n++<=8;)r.push(0);else{if(!/^[a-f0-9]{1,4}$/i.test(t))return;r.push(parseInt(t,16))}}),r}(t);if(e){var n="";return e.forEach(function(t){n+=("0000000000000000"+t.toString(2)).slice(-16)}),n}}function C(t){var r=b(w);if(t&&t[0]){(t=t.map(function(t){var e=b(t);if(!e)return[-1];var n=0;return r&&(n=function(t,e){var n;for(n=0;n<t.length&&n<e.length&&t[n]===e[n];n++);return n}(r,e)),[n,e,t]})).sort(function(t,e){return t[0]<e[0]?1:t[0]>e[0]||t[1]<e[1]?-1:t[1]>e[1]?1:0});var e=t[0][2];return e&&!/^fe80:/i.test(e)?e:void 0}}return t.wireless&&((s=a.insertCell()).textContent="-",s.setAttribute("data-label",i.children[Object.keys(g).length+1].textContent),(d=a.insertCell()).textContent="-",d.setAttribute("data-label",i.children[Object.keys(g).length+2].textContent),(u=a.insertCell()).textContent="-",u.setAttribute("data-label",i.children[Object.keys(g).length+3].textContent),l=E(n),t.signalgraph.addSignal(l)),a.onmouseenter=function(){a.classList.add("highlight"),l&&(l.highlight=!0)},a.onmouseleave=function(){a.classList.remove("highlight"),l&&(l.highlight=!1)},p(),{get_hostname:function(){return f.textContent},update_nodeinfo:function(t){var e,n,r,i,a,o,c,s,u=C(t.network.addresses);if(u){if("span"===f.nodeName.toLowerCase()){var l=f;f=document.createElement("a"),l.parentNode.replaceChild(f,l)}f.href="http://["+u+"]/"}if(f.textContent=t.hostname,y&&t.location){var h=(e=y.latitude,n=y.longitude,r=t.location.latitude,i=t.location.longitude,a=Math.PI/180,o=(r*=a)-(e*=a),c=(i*=a)-(n*=a),s=Math.sin(o/2)*Math.sin(o/2)+Math.sin(c/2)*Math.sin(c/2)*Math.cos(e)*Math.cos(r),2*Math.asin(Math.sqrt(s))*6372.8);d.textContent=Math.round(1e3*h)+" m"}p()},update_mesh:function(n){Object.keys(g).forEach(function(t){var e=g[t];e.td.textContent=n[t]+e.suffix}),p()},update_wifi:function(t){s.textContent=t.signal,u.textContent=Math.round(t.inactive/1e3)+" s",a.classList.toggle("inactive",200<t.inactive),l.set(200<t.inactive?null:t.signal),p()}}}function s(t,e,n){var r,a={};n&&(r=f(),t.appendChild(r.el));var i={table:t.firstElementChild,signalgraph:r,ifname:e,wireless:n},o=!1,c={},s=[];function u(){if(!o){o=!0;var t=new EventSource("/cgi-bin/dyn/neighbours-nodeinfo?"+encodeURIComponent(e));t.addEventListener("neighbour",function(t){try{var n=JSON.parse(t.data);r=[],i=n.network.mesh,Object.keys(i).forEach(function(t){var e=i[t].interfaces;Object.keys(e).forEach(function(t){e[t].forEach(function(t){r.push(t)})})}),r.forEach(function(t){var e=a[t];if(e){delete c[t];try{e.update_nodeinfo(n)}catch(t){console.error(t)}}})}catch(t){console.error(t)}var r,i},!1),t.onerror=function(){t.close(),o=!1,Object.keys(c).forEach(function(t){0<c[t]&&(c[t]--,u())})}}}function l(t){var e=a[t];return e||(c[t]=3,e=a[t]=d(i,t,(s[0]||(s=["#396AB1","#DA7C30","#3E9651","#CC2529","#535154","#6B4C9A","#922428","#948B3D"]),s.shift()),function(){delete c[t],delete a[t]}),u()),e}return n&&h("/cgi-bin/dyn/stations?"+encodeURIComponent(e),function(n){Object.keys(n).forEach(function(t){var e=n[t];l(t).update_wifi(e)})}),{get_neigh:l,lookup_neigh:function(t){return a[t]}}}document.querySelectorAll("[data-interface]").forEach(function(t){var e=t.getAttribute("data-interface"),n=(t.getAttribute("data-interface-address"),!!t.getAttribute("data-interface-wireless"));c[e]=s(t,e,n)});var n=document.body.getAttribute("data-mesh-provider");n&&h(n,function(r){Object.keys(r).forEach(function(t){var e=r[t],n=c[e.ifname];n&&n.get_neigh(t).update_mesh(e)})})}();
\ No newline at end of file