diff options
Diffstat (limited to 'modules/luci-mod-status')
19 files changed, 3237 insertions, 0 deletions
diff --git a/modules/luci-mod-status/Makefile b/modules/luci-mod-status/Makefile new file mode 100644 index 0000000000..cf8c8ddf83 --- /dev/null +++ b/modules/luci-mod-status/Makefile @@ -0,0 +1,18 @@ +# +# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org> +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI Status Pages +LUCI_DEPENDS:=+luci-base +libiwinfo +libiwinfo-lua + +PKG_BUILD_DEPENDS:=iwinfo +PKG_LICENSE:=Apache-2.0 + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature + diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/bandwidth.svg b/modules/luci-mod-status/htdocs/luci-static/resources/bandwidth.svg new file mode 100644 index 0000000000..5a121b85c6 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/bandwidth.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <polyline id="rx" points="" style="fill:blue;fill-opacity:0.4;stroke:blue;stroke-width:1" /> + <polyline id="tx" points="" style="fill:green;fill-opacity:0.4;stroke:green;stroke-width:1" /> + + <line x1="0" y1="25%" x2="100%" y2="25%" style="stroke:black;stroke-width:0.1" /> + <text id="label_75" x="20" y="24%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="50%" x2="100%" y2="50%" style="stroke:black;stroke-width:0.1" /> + <text id="label_50" x="20" y="49%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="75%" x2="100%" y2="75%" style="stroke:black;stroke-width:0.1" /> + <text id="label_25" x="20" y="74%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> +</svg> diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/connections.svg b/modules/luci-mod-status/htdocs/luci-static/resources/connections.svg new file mode 100644 index 0000000000..5794e79426 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/connections.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <polyline id="tcp" points="" style="fill:green;fill-opacity:0.4;stroke:green;stroke-width:1" /> + <polyline id="udp" points="" style="fill:blue;fill-opacity:0.4;stroke:blue;stroke-width:1" /> + <polyline id="other" points="" style="fill:red;fill-opacity:0.4;stroke:red;stroke-width:1" /> + + <line x1="0" y1="25%" x2="100%" y2="25%" style="stroke:black;stroke-width:0.1" /> + <text id="label_75" x="20" y="24%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="50%" x2="100%" y2="50%" style="stroke:black;stroke-width:0.1" /> + <text id="label_50" x="20" y="49%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="75%" x2="100%" y2="75%" style="stroke:black;stroke-width:0.1" /> + <text id="label_25" x="20" y="74%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> +</svg> diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/load.svg b/modules/luci-mod-status/htdocs/luci-static/resources/load.svg new file mode 100644 index 0000000000..716d37617f --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/load.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <polyline id="load01" points="" style="fill:#ff0000;fill-opacity:0.4;stroke:#ff0000;stroke-width:1" /> + <polyline id="load05" points="" style="fill:#ff6600;fill-opacity:0.4;stroke:#ff6600;stroke-width:1" /> + <polyline id="load15" points="" style="fill:#ffaa00;fill-opacity:0.4;stroke:#ffaa00;stroke-width:1" /> + + <line x1="0" y1="25%" x2="100%" y2="25%" style="stroke:black;stroke-width:0.1" /> + <text id="label_75" x="20" y="24%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="50%" x2="100%" y2="50%" style="stroke:black;stroke-width:0.1" /> + <text id="label_50" x="20" y="49%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="75%" x2="100%" y2="75%" style="stroke:black;stroke-width:0.1" /> + <text id="label_25" x="20" y="74%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> +</svg> diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/wifirate.svg b/modules/luci-mod-status/htdocs/luci-static/resources/wifirate.svg new file mode 100644 index 0000000000..e75ea614c9 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/wifirate.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <polyline id="rate" points="" style="fill:green;fill-opacity:0.4;stroke:green;stroke-width:1" /> + + <line x1="0" y1="25%" x2="100%" y2="25%" style="stroke:black;stroke-width:0.1" /> + <text id="label_75" x="20" y="24%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="50%" x2="100%" y2="50%" style="stroke:black;stroke-width:0.1" /> + <text id="label_50" x="20" y="49%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="75%" x2="100%" y2="75%" style="stroke:black;stroke-width:0.1" /> + <text id="label_25" x="20" y="74%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> +</svg> diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/wireless.svg b/modules/luci-mod-status/htdocs/luci-static/resources/wireless.svg new file mode 100644 index 0000000000..00cc2a12f1 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/wireless.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <polyline id="rssi" points="" style="fill:blue;fill-opacity:0.4;stroke:blue;stroke-width:1" /> + <polyline id="noise" points="" style="fill:red;fill-opacity:0.4;stroke:red;stroke-width:1" /> + + <line x1="0" y1="25%" x2="100%" y2="25%" style="stroke:black;stroke-width:0.1" /> + <text id="label_75" x="20" y="24%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="50%" x2="100%" y2="50%" style="stroke:black;stroke-width:0.1" /> + <text id="label_50" x="20" y="49%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> + + <line x1="0" y1="75%" x2="100%" y2="75%" style="stroke:black;stroke-width:0.1" /> + <text id="label_25" x="20" y="74%" style="fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000"> </text> +</svg> diff --git a/modules/luci-mod-status/luasrc/controller/admin/status.lua b/modules/luci-mod-status/luasrc/controller/admin/status.lua new file mode 100644 index 0000000000..4f04cce545 --- /dev/null +++ b/modules/luci-mod-status/luasrc/controller/admin/status.lua @@ -0,0 +1,153 @@ +-- Copyright 2008 Steven Barth <steven@midlink.org> +-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org> +-- Licensed to the public under the Apache License 2.0. + +module("luci.controller.admin.status", package.seeall) + +function index() + entry({"admin", "status", "overview"}, template("admin_status/index"), _("Overview"), 1) + + entry({"admin", "status", "iptables"}, template("admin_status/iptables"), _("Firewall"), 2).leaf = true + entry({"admin", "status", "iptables_action"}, post("action_iptables")).leaf = true + + entry({"admin", "status", "routes"}, template("admin_status/routes"), _("Routes"), 3) + entry({"admin", "status", "syslog"}, call("action_syslog"), _("System Log"), 4) + entry({"admin", "status", "dmesg"}, call("action_dmesg"), _("Kernel Log"), 5) + entry({"admin", "status", "processes"}, form("admin_status/processes"), _("Processes"), 6) + + entry({"admin", "status", "realtime"}, alias("admin", "status", "realtime", "load"), _("Realtime Graphs"), 7) + + entry({"admin", "status", "realtime", "load"}, template("admin_status/load"), _("Load"), 1).leaf = true + entry({"admin", "status", "realtime", "load_status"}, call("action_load")).leaf = true + + entry({"admin", "status", "realtime", "bandwidth"}, template("admin_status/bandwidth"), _("Traffic"), 2).leaf = true + entry({"admin", "status", "realtime", "bandwidth_status"}, call("action_bandwidth")).leaf = true + + if nixio.fs.access("/etc/config/wireless") then + entry({"admin", "status", "realtime", "wireless"}, template("admin_status/wireless"), _("Wireless"), 3).leaf = true + entry({"admin", "status", "realtime", "wireless_status"}, call("action_wireless")).leaf = true + end + + entry({"admin", "status", "realtime", "connections"}, template("admin_status/connections"), _("Connections"), 4).leaf = true + entry({"admin", "status", "realtime", "connections_status"}, call("action_connections")).leaf = true + + entry({"admin", "status", "nameinfo"}, call("action_nameinfo")).leaf = true +end + +function action_syslog() + local syslog = luci.sys.syslog() + luci.template.render("admin_status/syslog", {syslog=syslog}) +end + +function action_dmesg() + local dmesg = luci.sys.dmesg() + luci.template.render("admin_status/dmesg", {dmesg=dmesg}) +end + +function action_iptables() + if luci.http.formvalue("zero") then + if luci.http.formvalue("family") == "6" then + luci.util.exec("/usr/sbin/ip6tables -Z") + else + luci.util.exec("/usr/sbin/iptables -Z") + end + elseif luci.http.formvalue("restart") then + luci.util.exec("/etc/init.d/firewall restart") + end + + luci.http.redirect(luci.dispatcher.build_url("admin/status/iptables")) +end + +function action_bandwidth(iface) + luci.http.prepare_content("application/json") + + local bwc = io.popen("luci-bwc -i %s 2>/dev/null" + % luci.util.shellquote(iface)) + + if bwc then + luci.http.write("[") + + while true do + local ln = bwc:read("*l") + if not ln then break end + luci.http.write(ln) + end + + luci.http.write("]") + bwc:close() + end +end + +function action_wireless(iface) + luci.http.prepare_content("application/json") + + local bwc = io.popen("luci-bwc -r %s 2>/dev/null" + % luci.util.shellquote(iface)) + + if bwc then + luci.http.write("[") + + while true do + local ln = bwc:read("*l") + if not ln then break end + luci.http.write(ln) + end + + luci.http.write("]") + bwc:close() + end +end + +function action_load() + luci.http.prepare_content("application/json") + + local bwc = io.popen("luci-bwc -l 2>/dev/null") + if bwc then + luci.http.write("[") + + while true do + local ln = bwc:read("*l") + if not ln then break end + luci.http.write(ln) + end + + luci.http.write("]") + bwc:close() + end +end + +function action_connections() + local sys = require "luci.sys" + + luci.http.prepare_content("application/json") + + luci.http.write('{ "connections": ') + luci.http.write_json(sys.net.conntrack()) + + local bwc = io.popen("luci-bwc -c 2>/dev/null") + if bwc then + luci.http.write(', "statistics": [') + + while true do + local ln = bwc:read("*l") + if not ln then break end + luci.http.write(ln) + end + + luci.http.write("]") + bwc:close() + end + + luci.http.write(" }") +end + +function action_nameinfo(...) + local util = require "luci.util" + + luci.http.prepare_content("application/json") + luci.http.write_json(util.ubus("network.rrdns", "lookup", { + addrs = { ... }, + timeout = 5000, + limit = 1000 + }) or { }) +end diff --git a/modules/luci-mod-status/luasrc/model/cbi/admin_status/processes.lua b/modules/luci-mod-status/luasrc/model/cbi/admin_status/processes.lua new file mode 100644 index 0000000000..0a6e48fd8e --- /dev/null +++ b/modules/luci-mod-status/luasrc/model/cbi/admin_status/processes.lua @@ -0,0 +1,34 @@ +-- Copyright 2008 Steven Barth <steven@midlink.org> +-- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org> +-- Licensed to the public under the Apache License 2.0. + +f = SimpleForm("processes", translate("Processes"), translate("This list gives an overview over currently running system processes and their status.")) +f.reset = false +f.submit = false + +t = f:section(Table, luci.sys.process.list()) +t:option(DummyValue, "PID", translate("PID")) +t:option(DummyValue, "USER", translate("Owner")) +t:option(DummyValue, "COMMAND", translate("Command")) +t:option(DummyValue, "%CPU", translate("CPU usage (%)")) +t:option(DummyValue, "%MEM", translate("Memory usage (%)")) + +hup = t:option(Button, "_hup", translate("Hang Up")) +hup.inputstyle = "reload" +function hup.write(self, section) + null, self.tag_error[section] = luci.sys.process.signal(section, 1) +end + +term = t:option(Button, "_term", translate("Terminate")) +term.inputstyle = "remove" +function term.write(self, section) + null, self.tag_error[section] = luci.sys.process.signal(section, 15) +end + +kill = t:option(Button, "_kill", translate("Kill")) +kill.inputstyle = "reset" +function kill.write(self, section) + null, self.tag_error[section] = luci.sys.process.signal(section, 9) +end + +return f
\ No newline at end of file diff --git a/modules/luci-mod-status/luasrc/view/admin_status/bandwidth.htm b/modules/luci-mod-status/luasrc/view/admin_status/bandwidth.htm new file mode 100644 index 0000000000..3bb55f9054 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/bandwidth.htm @@ -0,0 +1,305 @@ +<%# + Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%- + local ntm = require "luci.model.network".init() + + local dev + local devices = { } + for _, dev in luci.util.vspairs(luci.sys.net.devices()) do + if dev ~= "lo" and not ntm:ignore_interface(dev) then + devices[#devices+1] = dev + end + end + + local curdev = luci.http.formvalue("dev") or devices[1] +-%> + +<%+header%> + +<script type="text/javascript">//<![CDATA[ + var bwxhr = new XHR(); + + var G; + var TIME = 0; + var RXB = 1; + var RXP = 2; + var TXB = 3; + var TXP = 4; + + var width = 760; + var height = 300; + var step = 5; + + var data_wanted = Math.floor(width / step); + var data_fill = 0; + var data_stamp = 0; + + var data_rx = [ ]; + var data_tx = [ ]; + + var line_rx; + var line_tx; + + var label_25; + var label_50; + var label_75; + + var label_rx_cur; + var label_rx_avg; + var label_rx_peak; + + var label_tx_cur; + var label_tx_avg; + var label_tx_peak; + + var label_scale; + + + Math.log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; }; + + function bandwidth_label(bytes, br) + { + var uby = '<%:kB/s%>'; + var kby = (bytes / 1024); + + if (kby >= 1024) + { + uby = '<%:MB/s%>'; + kby = kby / 1024; + } + + var ubi = '<%:kbit/s%>'; + var kbi = (bytes * 8 / 1024); + + if (kbi >= 1024) + { + ubi = '<%:Mbit/s%>'; + kbi = kbi / 1024; + } + + return String.format("%f %s%s(%f %s)", + kbi.toFixed(2), ubi, + br ? '<br />' : ' ', + kby.toFixed(2), uby + ); + } + + /* wait for SVG */ + window.setTimeout( + function() { + var svg = document.getElementById('bwsvg'); + + try { + G = svg.getSVGDocument + ? svg.getSVGDocument() : svg.contentDocument; + } + catch(e) { + G = document.embeds['bwsvg'].getSVGDocument(); + } + + if (!G) + { + window.setTimeout(arguments.callee, 1000); + } + else + { + /* find sizes */ + width = svg.offsetWidth - 2; + height = svg.offsetHeight - 2; + data_wanted = Math.ceil(width / step); + + /* prefill datasets */ + for (var i = 0; i < data_wanted; i++) + { + data_rx[i] = 0; + data_tx[i] = 0; + } + + /* find svg elements */ + line_rx = G.getElementById('rx'); + line_tx = G.getElementById('tx'); + + label_25 = G.getElementById('label_25'); + label_50 = G.getElementById('label_50'); + label_75 = G.getElementById('label_75'); + + label_rx_cur = document.getElementById('rx_bw_cur'); + label_rx_avg = document.getElementById('rx_bw_avg'); + label_rx_peak = document.getElementById('rx_bw_peak'); + + label_tx_cur = document.getElementById('tx_bw_cur'); + label_tx_avg = document.getElementById('tx_bw_avg'); + label_tx_peak = document.getElementById('tx_bw_peak'); + + label_scale = document.getElementById('scale'); + + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) + { + var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + label_25.parentNode.appendChild(line); + label_25.parentNode.appendChild(text); + } + + label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); + + /* render datasets, start update interval */ + XHR.poll(3, '<%=build_url("admin/status/realtime/bandwidth_status", curdev)%>', null, + function(x, data) + { + var data_max = 0; + var data_scale = 0; + + var data_rx_avg = 0; + var data_tx_avg = 0; + + var data_rx_peak = 0; + var data_tx_peak = 0; + + for (var i = data_stamp ? 0 : 1; i < data.length; i++) + { + /* skip overlapping entries */ + if (data[i][TIME] <= data_stamp) + continue; + + /* normalize difference against time interval */ + if (i > 0) + { + var time_delta = data[i][TIME] - data[i-1][TIME]; + if (time_delta) + { + data_rx.push((data[i][RXB] - data[i-1][RXB]) / time_delta); + data_tx.push((data[i][TXB] - data[i-1][TXB]) / time_delta); + } + } + } + + /* cut off outdated entries */ + data_rx = data_rx.slice(data_rx.length - data_wanted, data_rx.length); + data_tx = data_tx.slice(data_tx.length - data_wanted, data_tx.length); + + /* find peak */ + for (var i = 0; i < data_rx.length; i++) + { + data_max = Math.max(data_max, data_rx[i]); + data_max = Math.max(data_max, data_tx[i]); + + data_rx_peak = Math.max(data_rx_peak, data_rx[i]); + data_tx_peak = Math.max(data_tx_peak, data_tx[i]); + + data_rx_avg += data_rx[i]; + data_tx_avg += data_tx[i]; + } + + data_rx_avg = (data_rx_avg / Math.max(data_rx.length, 1)); + data_tx_avg = (data_tx_avg / Math.max(data_tx.length, 1)); + + var size = Math.floor(Math.log2(data_max)), + div = Math.pow(2, size - (size % 10)), + mult = data_max / div, + mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000)); + + data_max = data_max + (mult * div) - (data_max % (mult * div)); + + /* remember current timestamp, calculate horizontal scale */ + data_stamp = data[data.length-1][TIME]; + data_scale = height / data_max; + + /* plot data */ + var pt_rx = '0,' + height; + var pt_tx = '0,' + height; + + var y_rx = 0; + var y_tx = 0; + + for (var i = 0; i < data_rx.length; i++) + { + var x = i * step; + + y_rx = height - Math.floor(data_rx[i] * data_scale); + y_tx = height - Math.floor(data_tx[i] * data_scale); + + pt_rx += ' ' + x + ',' + y_rx; + pt_tx += ' ' + x + ',' + y_tx; + } + + pt_rx += ' ' + width + ',' + y_rx + ' ' + width + ',' + height; + pt_tx += ' ' + width + ',' + y_tx + ' ' + width + ',' + height; + + + line_rx.setAttribute('points', pt_rx); + line_tx.setAttribute('points', pt_tx); + + label_25.firstChild.data = bandwidth_label(0.25 * data_max); + label_50.firstChild.data = bandwidth_label(0.50 * data_max); + label_75.firstChild.data = bandwidth_label(0.75 * data_max); + + label_rx_cur.innerHTML = bandwidth_label(data_rx[data_rx.length-1], true); + label_tx_cur.innerHTML = bandwidth_label(data_tx[data_tx.length-1], true); + + label_rx_avg.innerHTML = bandwidth_label(data_rx_avg, true); + label_tx_avg.innerHTML = bandwidth_label(data_tx_avg, true); + + label_rx_peak.innerHTML = bandwidth_label(data_rx_peak, true); + label_tx_peak.innerHTML = bandwidth_label(data_tx_peak, true); + } + ); + + XHR.run(); + } + }, 1000 + ); +//]]></script> + +<h2 name="content"><%:Realtime Traffic%></h2> + +<ul class="cbi-tabmenu"> + <% for _, dev in ipairs(devices) do %> + <li class="cbi-tab<%= dev == curdev and "" or "-disabled" %>"><a href="?dev=<%=pcdata(dev)%>"><%=pcdata(dev)%></a></li> + <% end %> +</ul> + +<embed id="bwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/bandwidth.svg" /> +<div style="text-align:right"><small id="scale">-</small></div> +<br /> + +<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid blue"><%:Inbound:%></strong></div> + <div class="td" id="rx_bw_cur">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="rx_bw_avg">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="rx_bw_peak">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> + </div> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid green"><%:Outbound:%></strong></div> + <div class="td" id="tx_bw_cur">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="tx_bw_avg">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="tx_bw_peak">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> + </div> +</div> + +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/connections.htm b/modules/luci-mod-status/luasrc/view/admin_status/connections.htm new file mode 100644 index 0000000000..0a0db3be78 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/connections.htm @@ -0,0 +1,376 @@ +<%# + Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +<script type="text/javascript">//<![CDATA[ + var bwxhr = new XHR(); + + var G; + var TIME = 0; + var UDP = 1; + var TCP = 2; + var OTHER = 3; + + var width = 760; + var height = 300; + var step = 5; + + var data_wanted = Math.floor(width / step); + var data_fill = 0; + var data_stamp = 0; + + var data_udp = [ ]; + var data_tcp = [ ]; + var data_otr = [ ]; + + var line_udp; + var line_tcp; + + var label_25; + var label_50; + var label_75; + + var label_udp_cur; + var label_udp_avg; + var label_udp_peak; + + var label_tcp_cur; + var label_tcp_avg; + var label_tcp_peak; + + var label_otr_cur; + var label_otr_avg; + var label_otr_peak; + + var label_scale; + + var conn_table; + + var dns_cache = { }; + + + /* wait for SVG */ + window.setTimeout( + function() { + var svg = document.getElementById('bwsvg'); + + try { + G = svg.getSVGDocument + ? svg.getSVGDocument() : svg.contentDocument; + } + catch(e) { + G = document.embeds['bwsvg'].getSVGDocument(); + } + + if (!G) + { + window.setTimeout(arguments.callee, 1000); + } + else + { + /* find sizes */ + width = svg.offsetWidth - 2; + height = svg.offsetHeight - 2; + data_wanted = Math.ceil(width / step); + + /* prefill datasets */ + for (var i = 0; i < data_wanted; i++) + { + data_udp[i] = 0; + data_tcp[i] = 0; + data_otr[i] = 0; + } + + /* find svg elements */ + line_udp = G.getElementById('udp'); + line_tcp = G.getElementById('tcp'); + line_otr = G.getElementById('other'); + + label_25 = G.getElementById('label_25'); + label_50 = G.getElementById('label_50'); + label_75 = G.getElementById('label_75'); + + label_udp_cur = document.getElementById('lb_udp_cur'); + label_udp_avg = document.getElementById('lb_udp_avg'); + label_udp_peak = document.getElementById('lb_udp_peak'); + + label_tcp_cur = document.getElementById('lb_tcp_cur'); + label_tcp_avg = document.getElementById('lb_tcp_avg'); + label_tcp_peak = document.getElementById('lb_tcp_peak'); + + label_otr_cur = document.getElementById('lb_otr_cur'); + label_otr_avg = document.getElementById('lb_otr_avg'); + label_otr_peak = document.getElementById('lb_otr_peak'); + + label_scale = document.getElementById('scale'); + + conn_table = document.getElementById('connections'); + + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) + { + var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + label_25.parentNode.appendChild(line); + label_25.parentNode.appendChild(text); + } + + label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); + + /* render datasets, start update interval */ + XHR.poll(3, '<%=build_url("admin/status/realtime/connections_status")%>', null, + function(x, json) + { + var rows = []; + var conn = json.connections; + + var lookup_queue = [ ]; + + conn.sort(function(a, b) { + return b.bytes - a.bytes; + }); + + for (var i = 0; i < conn.length; i++) + { + var c = conn[i]; + + if ((c.src == '127.0.0.1' && c.dst == '127.0.0.1') || + (c.src == '::1' && c.dst == '::1')) + continue; + + if (!dns_cache[c.src]) + lookup_queue.push(c.src); + + if (!dns_cache[c.dst]) + lookup_queue.push(c.dst); + + var src = dns_cache[c.src] || (c.layer3 == 'ipv6' ? '[' + c.src + ']' : c.src); + var dst = dns_cache[c.dst] || (c.layer3 == 'ipv6' ? '[' + c.dst + ']' : c.dst); + + rows.push([ + c.layer3.toUpperCase(), + c.layer4.toUpperCase(), + src + ':' + c.sport, + dst + ':' + c.dport, + '%1024.2mB (%d <%:Pkts.%>)'.format(c.bytes, c.packets) + ]); + } + + cbi_update_table(conn_table, rows, '<em><%:No information available%></em>'); + + if (lookup_queue.length > 0) + XHR.get('<%=build_url("admin/status/nameinfo")%>/' + lookup_queue.slice(0, 100).join('/'), null, + function(x, json) + { + for (var addr in json) + dns_cache[addr] = json[addr]; + } + ); + + + var data = json.statistics; + + var data_max = 0; + var data_scale = 0; + + var data_udp_avg = 0; + var data_tcp_avg = 0; + var data_otr_avg = 0; + + var data_udp_peak = 0; + var data_tcp_peak = 0; + var data_otr_peak = 0; + + for (var i = data_stamp ? 0 : 1; i < data.length; i++) + { + /* skip overlapping entries */ + if (data[i][TIME] <= data_stamp) + continue; + + data_udp.push(data[i][UDP]); + data_tcp.push(data[i][TCP]); + data_otr.push(data[i][OTHER]); + } + + /* cut off outdated entries */ + data_udp = data_udp.slice(data_udp.length - data_wanted, data_udp.length); + data_tcp = data_tcp.slice(data_tcp.length - data_wanted, data_tcp.length); + data_otr = data_otr.slice(data_otr.length - data_wanted, data_otr.length); + + /* find peak */ + for (var i = 0; i < data_udp.length; i++) + { + data_max = Math.max(data_max, data_udp[i]); + data_max = Math.max(data_max, data_tcp[i]); + data_max = Math.max(data_max, data_otr[i]); + + data_udp_peak = Math.max(data_udp_peak, data_udp[i]); + data_tcp_peak = Math.max(data_tcp_peak, data_tcp[i]); + data_otr_peak = Math.max(data_otr_peak, data_otr[i]); + + if (i > 0) + { + data_udp_avg = (data_udp_avg + data_udp[i]) / 2; + data_tcp_avg = (data_tcp_avg + data_tcp[i]) / 2; + data_otr_avg = (data_otr_avg + data_otr[i]) / 2; + } + else + { + data_udp_avg = data_udp[i]; + data_tcp_avg = data_tcp[i]; + data_otr_avg = data_otr[i]; + } + } + + /* remember current timestamp, calculate horizontal scale */ + data_stamp = data[data.length-1][TIME]; + data_scale = height / (data_max * 1.1); + + + /* plot data */ + var pt_udp = '0,' + height; + var pt_tcp = '0,' + height; + var pt_otr = '0,' + height; + + var y_udp = 0; + var y_tcp = 0; + var y_otr = 0; + + for (var i = 0; i < data_udp.length; i++) + { + var x = i * step; + + y_udp = height - Math.floor(data_udp[i] * data_scale); + y_tcp = height - Math.floor(data_tcp[i] * data_scale); + y_otr = height - Math.floor(data_otr[i] * data_scale); + + pt_udp += ' ' + x + ',' + y_udp; + pt_tcp += ' ' + x + ',' + y_tcp; + pt_otr += ' ' + x + ',' + y_otr; + } + + pt_udp += ' ' + width + ',' + y_udp + ' ' + width + ',' + height; + pt_tcp += ' ' + width + ',' + y_tcp + ' ' + width + ',' + height; + pt_otr += ' ' + width + ',' + y_otr + ' ' + width + ',' + height; + + + var order = [ + [ line_udp, data_udp[data_udp.length-1] ], + [ line_tcp, data_tcp[data_tcp.length-1] ], + [ line_otr, data_otr[data_otr.length-1] ] + ]; + + order.sort(function(a, b) { return b[1] - a[1] }); + + for (var i = 0; i < order.length; i++) + order[i][0].parentNode.appendChild(order[i][0]); + + + line_udp.setAttribute('points', pt_udp); + line_tcp.setAttribute('points', pt_tcp); + line_otr.setAttribute('points', pt_otr); + + label_25.firstChild.data = Math.floor(1.1 * 0.25 * data_max); + label_50.firstChild.data = Math.floor(1.1 * 0.50 * data_max); + label_75.firstChild.data = Math.floor(1.1 * 0.75 * data_max); + + label_udp_cur.innerHTML = Math.floor(data_udp[data_udp.length-1]); + label_tcp_cur.innerHTML = Math.floor(data_tcp[data_tcp.length-1]); + label_otr_cur.innerHTML = Math.floor(data_otr[data_otr.length-1]); + + label_udp_avg.innerHTML = Math.floor(data_udp_avg); + label_tcp_avg.innerHTML = Math.floor(data_tcp_avg); + label_otr_avg.innerHTML = Math.floor(data_otr_avg); + + label_udp_peak.innerHTML = Math.floor(data_udp_peak); + label_tcp_peak.innerHTML = Math.floor(data_tcp_peak); + label_otr_peak.innerHTML = Math.floor(data_otr_peak); + } + ); + + XHR.run(); + } + }, 1000 + ); +//]]></script> + +<h2 name="content"><%:Realtime Connections%></h2> + +<div class="cbi-map-descr"><%:This page gives an overview over currently active network connections.%></div> + +<fieldset class="cbi-section" id="cbi-table-table"> + <legend><%:Active Connections%></legend> + + <embed id="bwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/connections.svg" /> + <div style="text-align:right"><small id="scale">-</small></div> + <br /> + + <div class="table"> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid blue"><%:UDP:%></strong></div> + <div class="td" id="lb_udp_cur">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="lb_udp_avg">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="lb_udp_peak">0</div> + </div> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid green"><%:TCP:%></strong></div> + <div class="td" id="lb_tcp_cur">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="lb_tcp_avg">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="lb_tcp_peak">0</div> + </div> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid red"><%:Other:%></strong></div> + <div class="td" id="lb_otr_cur">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="lb_otr_avg">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="lb_otr_peak">0</div> + </div> + </div> + <br /> + + <div class="cbi-section-node"> + <div class="table" id="connections"> + <div class="tr table-titles"> + <div class="th col-2 hide-xs"><%:Network%></div> + <div class="th col-2"><%:Protocol%></div> + <div class="th col-7"><%:Source%></div> + <div class="th col-7"><%:Destination%></div> + <div class="th col-4"><%:Transfer%></div> + </div> + + <div class="tr placeholder"> + <div class="td"> + <em><%:Collecting data...%></em> + </div> + </div> + </div> + </div> +</fieldset> + +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/dmesg.htm b/modules/luci-mod-status/luasrc/view/admin_status/dmesg.htm new file mode 100644 index 0000000000..1a8770ef88 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/dmesg.htm @@ -0,0 +1,12 @@ +<%# + Copyright 2008 Steven Barth <steven@midlink.org> + Copyright 2008 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> +<h2 name="content"><%:Kernel Log%></h2> +<div id="content_syslog"> +<textarea style="font-size: 12px;" readonly="readonly" wrap="off" rows="<%=dmesg:cmatch("\n")+2%>" id="syslog"><%=dmesg:pcdata()%></textarea> +</div> +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/index.htm b/modules/luci-mod-status/luasrc/view/admin_status/index.htm new file mode 100644 index 0000000000..3edfd92047 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/index.htm @@ -0,0 +1,487 @@ +<%# + Copyright 2008 Steven Barth <steven@midlink.org> + Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<% + local fs = require "nixio.fs" + local ipc = require "luci.ip" + local util = require "luci.util" + local stat = require "luci.tools.status" + local ver = require "luci.version" + + local has_ipv6 = fs.access("/proc/net/ipv6_route") + local has_dhcp = fs.access("/etc/config/dhcp") + local has_wifi = ((fs.stat("/etc/config/wireless", "size") or 0) > 0) + + local sysinfo = luci.util.ubus("system", "info") or { } + local boardinfo = luci.util.ubus("system", "board") or { } + local unameinfo = nixio.uname() or { } + + local meminfo = sysinfo.memory or { + total = 0, + free = 0, + buffered = 0, + shared = 0 + } + + local swapinfo = sysinfo.swap or { + total = 0, + free = 0 + } + + local has_dsl = fs.access("/etc/init.d/dsl_control") + + if luci.http.formvalue("status") == "1" then + local ntm = require "luci.model.network".init() + local wan_nets = ntm:get_wan_networks() + local wan6_nets = ntm:get_wan6_networks() + + local conn_count = tonumber( + fs.readfile("/proc/sys/net/netfilter/nf_conntrack_count") or "") or 0 + + local conn_max = tonumber(luci.sys.exec( + "sysctl -n -e net.nf_conntrack_max net.ipv4.netfilter.ip_conntrack_max" + ):match("%d+")) or 4096 + + local rv = { + uptime = sysinfo.uptime or 0, + localtime = os.date(), + loadavg = sysinfo.load or { 0, 0, 0 }, + memory = meminfo, + swap = swapinfo, + connmax = conn_max, + conncount = conn_count, + wifinets = stat.wifi_networks() + } + + if #wan_nets > 0 then + local k, v + + rv.wan = { } + + for k, v in pairs(wan_nets) do + local dev = v:get_interface() + local link = dev and ipc.link(dev:name()) + + local wan_info = { + ipaddr = v:ipaddr(), + gwaddr = v:gwaddr(), + netmask = v:netmask(), + dns = v:dnsaddrs(), + expires = v:expires(), + uptime = v:uptime(), + proto = v:proto(), + i18n = v:get_i18n(), + ifname = v:ifname(), + link = v:adminlink(), + mac = dev and dev:mac(), + type = dev and dev:type(), + name = dev and dev:get_i18n(), + ether = link and link.type == 1 + } + + rv.wan[#rv.wan+1] = wan_info + end + end + + if #wan6_nets > 0 then + local k, v + + rv.wan6 = { } + + for k, v in pairs(wan6_nets) do + local dev = v:get_interface() + local link = dev and ipc.link(dev:name()) + local wan6_info = { + ip6addr = v:ip6addr(), + gw6addr = v:gw6addr(), + dns = v:dns6addrs(), + ip6prefix = v:ip6prefix(), + uptime = v:uptime(), + proto = v:proto(), + i18n = v:get_i18n(), + ifname = v:ifname(), + link = v:adminlink(), + mac = dev and dev:mac(), + type = dev and dev:type(), + name = dev and dev:get_i18n(), + ether = link and link.type == 1 + } + + rv.wan6[#rv.wan6+1] = wan6_info + end + end + + if has_dsl then + local dsl_stat = luci.sys.exec("/etc/init.d/dsl_control lucistat") + local dsl_func = loadstring(dsl_stat) + if dsl_func then + rv.dsl = dsl_func() + end + end + + luci.http.prepare_content("application/json") + luci.http.write_json(rv) + + return + end +-%> + +<%+header%> + +<script type="text/javascript">//<![CDATA[ + function progressbar(v, m) + { + var vn = parseInt(v) || 0; + var mn = parseInt(m) || 100; + var pc = Math.floor((100 / mn) * vn); + + return String.format( + '<div style="width:100%%; max-width:200px; position:relative; border:1px solid #999999">' + + '<div style="background-color:#CCCCCC; width:%d%%; height:15px">' + + '<div style="position:absolute; left:0; top:0; text-align:center; width:100%%; color:#000000">' + + '<small>%s / %s (%d%%)</small>' + + '</div>' + + '</div>' + + '</div>', pc, v, m, pc + ); + } + + function labelList(items, offset) { + var rv = [ ]; + + for (var i = offset || 0; i < items.length; i += 2) { + var label = items[i], + value = items[i+1]; + + if (value === undefined || value === null) + continue; + + if (label) + rv.push(E('strong', [label, ': '])); + + rv.push(value, E('br')); + } + + return rv; + } + + function renderBox(title, active, childs) { + childs = childs || []; + childs.unshift(E('span', labelList(arguments, 3))); + + return E('div', { class: 'ifacebox' }, [ + E('div', { class: 'ifacebox-head center ' + (active ? 'active' : '') }, + E('strong', title)), + E('div', { class: 'ifacebox-body left' }, childs) + ]); + } + + function renderBadge(icon, title) { + return E('span', { class: 'ifacebadge' }, [ + E('img', { src: icon, title: title || '' }), + E('span', labelList(arguments, 2)) + ]); + } + + XHR.poll(5, '<%=REQUEST_URI%>', { status: 1 }, + function(x, info) + { + var us = document.getElementById('upstream_status_table'); + + while (us.lastElementChild) + us.removeChild(us.lastElementChild); + + var wan_list = info.wan || []; + + for (var i = 0; i < wan_list.length; i++) { + var ifc = wan_list[i]; + + us.appendChild(renderBox( + '<%:IPv4 Upstream%>', + (ifc.ifname && ifc.proto != 'none'), + [ E('div', {}, renderBadge( + '<%=resource%>' + '/icons/%s.png'.format((ifc && ifc.type) ? ifc.type : 'ethernet_disabled'), null, + '<%:Device%>', ifc ? (ifc.name || ifc.ifname || '-') : '-', + '<%:MAC-Address%>', (ifc && ifc.ether) ? ifc.mac : null)) ], + '<%:Protocol%>', ifc.i18n || E('em', '<%:Not connected%>'), + '<%:Address%>', (ifc.ipaddr) ? ifc.ipaddr : '0.0.0.0', + '<%:Netmask%>', (ifc.netmask && ifc.netmask != ifc.ipaddr) ? ifc.netmask : '255.255.255.255', + '<%:Gateway%>', (ifc.gwaddr) ? ifc.gwaddr : '0.0.0.0', + '<%:DNS%> 1', (ifc.dns) ? ifc.dns[0] : null, + '<%:DNS%> 2', (ifc.dns) ? ifc.dns[1] : null, + '<%:DNS%> 3', (ifc.dns) ? ifc.dns[2] : null, + '<%:DNS%> 4', (ifc.dns) ? ifc.dns[3] : null, + '<%:DNS%> 5', (ifc.dns) ? ifc.dns[4] : null, + '<%:Expires%>', (ifc.expires > -1) ? '%t'.format(ifc.expires) : null, + '<%:Connected%>', (ifc.uptime > 0) ? '%t'.format(ifc.uptime) : null)); + } + + <% if has_ipv6 then %> + var wan6_list = info.wan6 || []; + + for (var i = 0; i < wan6_list.length; i++) { + var ifc6 = wan6_list[i]; + + us.appendChild(renderBox( + '<%:IPv6 Upstream%>', + (ifc6.ifname && ifc6.proto != 'none'), + [ E('div', {}, renderBadge( + '<%=resource%>/icons/%s.png'.format(ifc6.type || 'ethernet_disabled'), null, + '<%:Device%>', ifc6 ? (ifc6.name || ifc6.ifname || '-') : '-', + '<%:MAC-Address%>', (ifc6 && ifc6.ether) ? ifc6.mac : null)) ], + '<%:Protocol%>', ifc6.i18n ? (ifc6.i18n + (ifc6.proto === 'dhcp' && ifc6.ip6prefix ? '-PD' : '')) : E('em', '<%:Not connected%>'), + '<%:Prefix Delegated%>', ifc6.ip6prefix, + '<%:Address%>', (ifc6.ip6prefix) ? (ifc6.ip6addr || null) : (ifc6.ipaddr || '::'), + '<%:Gateway%>', (ifc6.gw6addr) ? ifc6.gw6addr : '::', + '<%:DNS%> 1', (ifc6.dns) ? ifc6.dns[0] : null, + '<%:DNS%> 2', (ifc6.dns) ? ifc6.dns[1] : null, + '<%:DNS%> 3', (ifc6.dns) ? ifc6.dns[2] : null, + '<%:DNS%> 4', (ifc6.dns) ? ifc6.dns[3] : null, + '<%:DNS%> 5', (ifc6.dns) ? ifc6.dns[4] : null, + '<%:Connected%>', (ifc6.uptime > 0) ? '%t'.format(ifc6.uptime) : null)); + } + <% end %> + + <% if has_dsl then %> + var ds = document.getElementById('dsl_status_table'); + + while (ds.lastElementChild) + ds.removeChild(ds.lastElementChild); + + ds.appendChild(renderBox( + '<%:DSL Status%>', + (info.dsl.line_state === 'UP'), [ ], + '<%:Line State%>', '%s [0x%x]'.format(info.dsl.line_state, info.dsl.line_state_detail), + '<%:Line Mode%>', info.dsl.line_mode_s || '-', + '<%:Line Uptime%>', info.dsl.line_uptime_s || '-', + '<%:Annex%>', info.dsl.annex_s || '-', + '<%:Profile%>', info.dsl.profile_s || '-', + '<%:Data Rate%>', '%s/s / %s/s'.format(info.dsl.data_rate_down_s, info.dsl.data_rate_up_s), + '<%:Max. Attainable Data Rate (ATTNDR)%>', '%s/s / %s/s'.format(info.dsl.max_data_rate_down_s, info.dsl.max_data_rate_up_s), + '<%:Latency%>', '%s / %s'.format(info.dsl.latency_num_down, info.dsl.latency_num_up), + '<%:Line Attenuation (LATN)%>', '%.1f dB / %.1f dB'.format(info.dsl.line_attenuation_down, info.dsl.line_attenuation_up), + '<%:Signal Attenuation (SATN)%>', '%.1f dB / %.1f dB'.format(info.dsl.signal_attenuation_down, info.dsl.signal_attenuation_up), + '<%:Noise Margin (SNR)%>', '%.1f dB / %.1f dB'.format(info.dsl.noise_margin_down, info.dsl.noise_margin_up), + '<%:Aggregate Transmit Power(ACTATP)%>', '%.1f dB / %.1f dB'.format(info.dsl.actatp_down, info.dsl.actatp_up), + '<%:Forward Error Correction Seconds (FECS)%>', '%d / %d'.format(info.dsl.errors_fec_near, info.dsl.errors_fec_far), + '<%:Errored seconds (ES)%>', '%d / %d'.format(info.dsl.errors_es_near, info.dsl.errors_es_far), + '<%:Severely Errored Seconds (SES)%>', '%d / %d'.format(info.dsl.errors_ses_near, info.dsl.errors_ses_far), + '<%:Loss of Signal Seconds (LOSS)%>', '%d / %d'.format(info.dsl.errors_loss_near, info.dsl.errors_loss_far), + '<%:Unavailable Seconds (UAS)%>', '%d / %d'.format(info.dsl.errors_uas_near, info.dsl.errors_uas_far), + '<%:Header Error Code Errors (HEC)%>', '%d / %d'.format(info.dsl.errors_hec_near, info.dsl.errors_hec_far), + '<%:Non Pre-emtive CRC errors (CRC_P)%>', '%d / %d'.format(info.dsl.errors_crc_p_near, info.dsl.errors_crc_p_far), + '<%:Pre-emtive CRC errors (CRCP_P)%>', '%d / %d'.format(info.dsl.errors_crcp_p_near, info.dsl.errors_crcp_p_far), + '<%:ATU-C System Vendor ID%>', info.dsl.atuc_vendor_id, + '<%:Power Management Mode%>', info.dsl.power_mode_s)); + <% end %> + + <% if has_wifi then %> + var ws = document.getElementById('wifi_status_table'); + if (ws) + { + while (ws.lastElementChild) + ws.removeChild(ws.lastElementChild); + + for (var didx = 0; didx < info.wifinets.length; didx++) + { + var dev = info.wifinets[didx]; + var net0 = (dev.networks && dev.networks[0]) ? dev.networks[0] : {}; + var vifs = []; + + for (var nidx = 0; nidx < dev.networks.length; nidx++) + { + var net = dev.networks[nidx]; + var is_assoc = (net.bssid != '00:00:00:00:00:00' && net.channel && !net.disabled); + + var icon; + if (net.disabled) + icon = "<%=resource%>/icons/signal-none.png"; + else if (net.quality <= 0) + icon = "<%=resource%>/icons/signal-0.png"; + else if (net.quality < 25) + icon = "<%=resource%>/icons/signal-0-25.png"; + else if (net.quality < 50) + icon = "<%=resource%>/icons/signal-25-50.png"; + else if (net.quality < 75) + icon = "<%=resource%>/icons/signal-50-75.png"; + else + icon = "<%=resource%>/icons/signal-75-100.png"; + + vifs.push(renderBadge( + icon, + '<%:Signal%>: %d dBm / <%:Quality%>: %d%%'.format(net.signal, net.quality), + '<%:SSID%>', E('a', { href: net.link }, [ net.ssid || '?' ]), + '<%:Mode%>', net.mode, + '<%:BSSID%>', is_assoc ? (net.bssid || '-') : null, + '<%:Encryption%>', is_assoc ? net.encryption : null, + '<%:Associations%>', is_assoc ? (net.num_assoc || '-') : null, + null, is_assoc ? null : E('em', net.disabled ? '<%:Wireless is disabled%>' : '<%:Wireless is not associated%>'))); + } + + ws.appendChild(renderBox( + dev.device, dev.up || net0.up, + [ E('div', vifs) ], + '<%:Type%>', dev.name.replace(/^Generic | Wireless Controller .+$/g, ''), + '<%:Channel%>', net0.channel ? '%d (%.3f <%:GHz%>)'.format(net0.channel, net0.frequency) : '-', + '<%:Bitrate%>', net0.bitrate ? '%d <%:Mbit/s%>'.format(net0.bitrate) : '-')); + } + + if (!ws.lastElementChild) + ws.appendChild(E('<em><%:No information available%></em>')); + } + <% end %> + + var e; + + if (e = document.getElementById('localtime')) + e.innerHTML = info.localtime; + + if (e = document.getElementById('uptime')) + e.innerHTML = String.format('%t', info.uptime); + + if (e = document.getElementById('loadavg')) + e.innerHTML = String.format( + '%.02f, %.02f, %.02f', + info.loadavg[0] / 65535.0, + info.loadavg[1] / 65535.0, + info.loadavg[2] / 65535.0 + ); + + if (e = document.getElementById('memtotal')) + e.innerHTML = progressbar( + ((info.memory.free + info.memory.buffered) / 1024) + " <%:kB%>", + (info.memory.total / 1024) + " <%:kB%>" + ); + + if (e = document.getElementById('memfree')) + e.innerHTML = progressbar( + (info.memory.free / 1024) + " <%:kB%>", + (info.memory.total / 1024) + " <%:kB%>" + ); + + if (e = document.getElementById('membuff')) + e.innerHTML = progressbar( + (info.memory.buffered / 1024) + " <%:kB%>", + (info.memory.total / 1024) + " <%:kB%>" + ); + + if (e = document.getElementById('swaptotal')) + e.innerHTML = progressbar( + (info.swap.free / 1024) + " <%:kB%>", + (info.swap.total / 1024) + " <%:kB%>" + ); + + if (e = document.getElementById('swapfree')) + e.innerHTML = progressbar( + (info.swap.free / 1024) + " <%:kB%>", + (info.swap.total / 1024) + " <%:kB%>" + ); + + if (e = document.getElementById('conns')) + e.innerHTML = progressbar(info.conncount, info.connmax); + + } + ); +//]]></script> + +<h2 name="content"><%:Status%></h2> + +<div class="cbi-section"> + <h3><%:System%></h3> + + <div class="table" width="100%"> + <div class="tr"><div class="td left" width="33%"><%:Hostname%></div><div class="td left"><%=luci.sys.hostname() or "?"%></div></div> + <div class="tr"><div class="td left" width="33%"><%:Model%></div><div class="td left"><%=pcdata(boardinfo.model or "?")%></div></div> + <div class="tr"><div class="td left" width="33%"><%:Architecture%></div><div class="td left"><%=pcdata(boardinfo.system or "?")%></div></div> + <div class="tr"><div class="td left" width="33%"><%:Firmware Version%></div><div class="td left"> + <%=pcdata(ver.distname)%> <%=pcdata(ver.distversion)%> / + <%=pcdata(ver.luciname)%> (<%=pcdata(ver.luciversion)%>) + </div></div> + <div class="tr"><div class="td left" width="33%"><%:Kernel Version%></div><div class="td left"><%=unameinfo.release or "?"%></div></div> + <div class="tr"><div class="td left" width="33%"><%:Local Time%></div><div class="td left" id="localtime">-</div></div> + <div class="tr"><div class="td left" width="33%"><%:Uptime%></div><div class="td left" id="uptime">-</div></div> + <div class="tr"><div class="td left" width="33%"><%:Load Average%></div><div class="td left" id="loadavg">-</div></div> + </div> +</div> + +<div class="cbi-section"> + <h3><%:Memory%></h3> + + <div class="table" width="100%"> + <div class="tr"><div class="td left" width="33%"><%:Total Available%></div><div class="td left" id="memtotal">-</div></div> + <div class="tr"><div class="td left" width="33%"><%:Free%></div><div class="td left" id="memfree">-</div></div> + <div class="tr"><div class="td left" width="33%"><%:Buffered%></div><div class="td left" id="membuff">-</div></div> + </div> +</div> + +<% if swapinfo.total > 0 then %> +<div class="cbi-section"> + <h3><%:Swap%></h3> + + <div class="table" width="100%"> + <div class="tr"><div class="td left" width="33%"><%:Total Available%></div><div class="td left" id="swaptotal">-</div></div> + <div class="tr"><div class="td left" width="33%"><%:Free%></div><div class="td left" id="swapfree">-</div></div> + </div> +</div> +<% end %> + +<div class="cbi-section"> + <h3><%:Network%></h3> + + <div id="upstream_status_table" class="network-status-table"> + <p><em><%:Collecting data...%></em></p> + </div> + + <div class="table" width="100%"> + <div class="tr"><div class="td left" width="33%"><%:Active Connections%></div><div class="td left" id="conns">-</div></div> + </div> +</div> + +<% + if has_dhcp then + include("lease_status") + end +%> + +<% if has_dsl then %> +<div class="cbi-section"> + <h3><%:DSL%></h3> + + <div id="dsl_status_table" class="network-status-table"> + <p><em><%:Collecting data...%></em></p> + </div> +</div> +<% end %> + +<% if has_wifi then %> +<div class="cbi-section"> + <h3><%:Wireless%></h3> + + <div id="wifi_status_table" class="network-status-table"> + <p><em><%:Collecting data...%></em></p> + </div> +</div> + +<div class="cbi-section"> + <h3><%:Associated Stations%></h3> + + <%+wifi_assoclist%> +</div> +<% end %> + +<%- + local incdir = util.libpath() .. "/view/admin_status/index/" + if fs.access(incdir) then + local inc + for inc in fs.dir(incdir) do + if inc:match("%.htm$") then + include("admin_status/index/" .. inc:gsub("%.htm$", "")) + end + end + end +-%> + +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/iptables.htm b/modules/luci-mod-status/luasrc/view/admin_status/iptables.htm new file mode 100644 index 0000000000..51e428e40e --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/iptables.htm @@ -0,0 +1,155 @@ +<%# + Copyright 2008-2009 Steven Barth <steven@midlink.org> + Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%- + + require "luci.sys.iptparser" + local wba = require "luci.tools.webadmin" + local fs = require "nixio.fs" + local io = require "io" + + local has_ip6tables = fs.access("/usr/sbin/ip6tables") + local mode = 4 + + if has_ip6tables then + mode = luci.dispatcher.context.requestpath + mode = tonumber(mode[#mode] ~= "iptables" and mode[#mode]) or 4 + end + + local ipt = luci.sys.iptparser.IptParser(mode) + + local rowcnt = 1 + function rowstyle() + rowcnt = rowcnt + 1 + return (rowcnt % 2) + 1 + end + + function link_target(t,c) + if ipt:is_custom_target(c) then + return '<a href="#rule_%s_%s">%s</a>' %{ t:lower(), c, c } + end + return c + end + + function link_iface(i) + local net = wba.iface_get_network(i) + if net and i ~= "lo" then + return '<a href="%s">%s</a>' %{ + url("admin/network/network", net), i + } + + end + return i + end + + local tables = { "Filter", "NAT", "Mangle", "Raw" } + if mode == 6 then + tables = { "Filter", "Mangle", "Raw" } + local ok, lines = pcall(io.lines, "/proc/net/ip6_tables_names") + if ok and lines then + local line + for line in lines do + if line == "nat" then + tables = { "Filter", "NAT", "Mangle", "Raw" } + end + end + end + end +-%> + +<%+header%> + +<style type="text/css"> + span:target { + color: blue; + text-decoration: underline; + } +</style> + +<h2 name="content"><%:Firewall Status%></h2> + +<% if has_ip6tables then %> +<ul class="cbi-tabmenu"> + <li class="cbi-tab<%= mode ~= 4 and "-disabled" %>"><a href="<%=url("admin/status/iptables/4")%>"><%:IPv4 Firewall%></a></li> + <li class="cbi-tab<%= mode ~= 6 and "-disabled" %>"><a href="<%=url("admin/status/iptables/6")%>"><%:IPv6 Firewall%></a></li> +</ul> +<% end %> + +<div class="cbi-map" style="position: relative"> + + <form method="post" action="<%=url("admin/status/iptables_action")%>" style="position: absolute; right: 0"> + <input type="hidden" name="token" value="<%=token%>" /> + <input type="hidden" name="family" value="<%=mode%>" /> + <input type="submit" class="cbi-button" name="zero" value="<%:Reset Counters%>" /> + <input type="submit" class="cbi-button" name="restart" value="<%:Restart Firewall%>" /> + </form> + + <div class="cbi-section"> + + <% for _, tbl in ipairs(tables) do chaincnt = 0 %> + <h3><%:Table%>: <%=tbl%></h3> + + <% for _, chain in ipairs(ipt:chains(tbl)) do + rowcnt = 0 + chaincnt = chaincnt + 1 + chaininfo = ipt:chain(tbl, chain) + %> + <h4 id="rule_<%=tbl:lower()%>_<%=chain%>"> + <%:Chain%> <em><%=chain%></em> + (<%- if chaininfo.policy then -%> + <%:Policy%>: <em><%=chaininfo.policy%></em>, <%:Packets%>: <%=chaininfo.packets%>, <%:Traffic%>: <%=wba.byte_format(chaininfo.bytes)-%> + <%- else -%> + <%:References%>: <%=chaininfo.references-%> + <%- end -%>) + </h4> + + <div class="cbi-section-node"> + <div class="table" style="font-size:90%"> + <div class="tr table-titles cbi-rowstyle-<%=rowstyle()%>"> + <div class="th hide-xs"><%:Pkts.%></div> + <div class="th nowrap"><%:Traffic%></div> + <div class="th col-5"><%:Target%></div> + <div class="th"><%:Prot.%></div> + <div class="th"><%:In%></div> + <div class="th"><%:Out%></div> + <div class="th"><%:Source%></div> + <div class="th"><%:Destination%></div> + <div class="th col-9 hide-xs"><%:Options%></div> + </div> + + <% for _, rule in ipairs(ipt:find({table=tbl, chain=chain})) do %> + <div class="tr cbi-rowstyle-<%=rowstyle()%>"> + <div class="td"><%=rule.packets%></div> + <div class="td nowrap"><%=wba.byte_format(rule.bytes)%></div> + <div class="td col-5"><%=rule.target and link_target(tbl, rule.target) or "-"%></div> + <div class="td"><%=rule.protocol%></div> + <div class="td"><%=link_iface(rule.inputif)%></div> + <div class="td"><%=link_iface(rule.outputif)%></div> + <div class="td"><%=rule.source%></div> + <div class="td"><%=rule.destination%></div> + <div class="td col-9 hide-xs"><%=#rule.options > 0 and luci.util.pcdata(table.concat(rule.options, " ")) or "-"%></div> + </div> + <% end %> + + <% if rowcnt == 1 then %> + <div class="tr cbi-rowstyle-<%=rowstyle()%>"> + <div class="td" colspan="9"><em><%:No rules in this chain%></em></div> + </div> + <% end %> + </div> + </div> + <% end %> + + <% if chaincnt == 0 then %> + <em><%:No chains in this table%></em> + <% end %> + + <br /><br /> + <% end %> + </div> +</div> + +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/load.htm b/modules/luci-mod-status/luasrc/view/admin_status/load.htm new file mode 100644 index 0000000000..bced06fa22 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/load.htm @@ -0,0 +1,285 @@ +<%# + Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +<script type="text/javascript">//<![CDATA[ + var bwxhr = new XHR(); + + var G; + var TIME = 0; + var L01 = 1; + var L05 = 2; + var L15 = 3; + + var width = 760; + var height = 300; + var step = 5; + + var data_wanted = Math.floor(width / step); + var data_fill = 0; + var data_stamp = 0; + + var data_01 = [ ]; + var data_05 = [ ]; + var data_15 = [ ]; + + var line_01; + var line_05; + var line_15; + + var label_25; + var label_050; + var label_75; + + var label_01_cur; + var label_01_avg; + var label_01_peak; + + var label_05_cur; + var label_05_avg; + var label_05_peak; + + var label_15_cur; + var label_15_avg; + var label_15_peak; + + var label_scale; + + + /* wait for SVG */ + window.setTimeout( + function() { + var svg = document.getElementById('bwsvg'); + + try { + G = svg.getSVGDocument + ? svg.getSVGDocument() : svg.contentDocument; + } + catch(e) { + G = document.embeds['bwsvg'].getSVGDocument(); + } + + if (!G) + { + window.setTimeout(arguments.callee, 1000); + } + else + { + /* find sizes */ + width = svg.offsetWidth - 2; + height = svg.offsetHeight - 2; + data_wanted = Math.ceil(width / step); + + /* prefill datasets */ + for (var i = 0; i < data_wanted; i++) + { + data_01[i] = 0; + data_05[i] = 0; + data_15[i] = 0; + } + + /* find svg elements */ + line_01 = G.getElementById('load01'); + line_05 = G.getElementById('load05'); + line_15 = G.getElementById('load15'); + + label_25 = G.getElementById('label_25'); + label_50 = G.getElementById('label_50'); + label_75 = G.getElementById('label_75'); + + label_01_cur = document.getElementById('lb_load01_cur'); + label_01_avg = document.getElementById('lb_load01_avg'); + label_01_peak = document.getElementById('lb_load01_peak'); + + label_05_cur = document.getElementById('lb_load05_cur'); + label_05_avg = document.getElementById('lb_load05_avg'); + label_05_peak = document.getElementById('lb_load05_peak'); + + label_15_cur = document.getElementById('lb_load15_cur'); + label_15_avg = document.getElementById('lb_load15_avg'); + label_15_peak = document.getElementById('lb_load15_peak'); + + label_scale = document.getElementById('scale'); + + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) + { + var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + label_25.parentNode.appendChild(line); + label_25.parentNode.appendChild(text); + } + + label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); + + /* render datasets, start update interval */ + XHR.poll(3, '<%=build_url("admin/status/realtime/load_status")%>', null, + function(x, data) + { + var data_max = 0; + var data_scale = 0; + + var data_01_avg = 0; + var data_05_avg = 0; + var data_15_avg = 0; + + var data_01_peak = 0; + var data_05_peak = 0; + var data_15_peak = 0; + + for (var i = data_stamp ? 0 : 1; i < data.length; i++) + { + /* skip overlapping entries */ + if (data[i][TIME] <= data_stamp) + continue; + + data_01.push(data[i][L01]); + data_05.push(data[i][L05]); + data_15.push(data[i][L15]); + } + + /* cut off outdated entries */ + data_01 = data_01.slice(data_01.length - data_wanted, data_01.length); + data_05 = data_05.slice(data_05.length - data_wanted, data_05.length); + data_15 = data_15.slice(data_15.length - data_wanted, data_15.length); + + /* find peak */ + for (var i = 0; i < data_01.length; i++) + { + data_max = Math.max(data_max, data_01[i]); + data_max = Math.max(data_max, data_05[i]); + data_max = Math.max(data_max, data_15[i]); + + data_01_peak = Math.max(data_01_peak, data_01[i]); + data_05_peak = Math.max(data_05_peak, data_05[i]); + data_15_peak = Math.max(data_15_peak, data_15[i]); + + if (i > 0) + { + data_01_avg = (data_01_avg + data_01[i]) / 2; + data_05_avg = (data_05_avg + data_05[i]) / 2; + data_15_avg = (data_15_avg + data_15[i]) / 2; + } + else + { + data_01_avg = data_01[i]; + data_05_avg = data_05[i]; + data_15_avg = data_15[i]; + } + } + + /* remember current timestamp, calculate horizontal scale */ + data_stamp = data[data.length-1][TIME]; + data_scale = height / (data_max * 1.1); + + + /* plot data */ + var pt_01 = '0,' + height; + var pt_05 = '0,' + height; + var pt_15 = '0,' + height; + + var y_01 = 0; + var y_05 = 0; + var y_15 = 0; + + for (var i = 0; i < data_01.length; i++) + { + var x = i * step; + + y_01 = height - Math.floor(data_01[i] * data_scale); + y_05 = height - Math.floor(data_05[i] * data_scale); + y_15 = height - Math.floor(data_15[i] * data_scale); + + pt_01 += ' ' + x + ',' + y_01; + pt_05 += ' ' + x + ',' + y_05; + pt_15 += ' ' + x + ',' + y_15; + } + + pt_01 += ' ' + width + ',' + y_01 + ' ' + width + ',' + height; + pt_05 += ' ' + width + ',' + y_05 + ' ' + width + ',' + height; + pt_15 += ' ' + width + ',' + y_15 + ' ' + width + ',' + height; + + + line_01.setAttribute('points', pt_01); + line_05.setAttribute('points', pt_05); + line_15.setAttribute('points', pt_15); + + label_25.firstChild.data = (1.1 * 0.25 * data_max / 100).toFixed(2); + label_50.firstChild.data = (1.1 * 0.50 * data_max / 100).toFixed(2); + label_75.firstChild.data = (1.1 * 0.75 * data_max / 100).toFixed(2); + + label_01_cur.innerHTML = (data_01[data_01.length-1] / 100).toFixed(2); + label_05_cur.innerHTML = (data_05[data_05.length-1] / 100).toFixed(2); + label_15_cur.innerHTML = (data_15[data_15.length-1] / 100).toFixed(2); + + label_01_avg.innerHTML = (data_01_avg / 100).toFixed(2); + label_05_avg.innerHTML = (data_05_avg / 100).toFixed(2); + label_15_avg.innerHTML = (data_15_avg / 100).toFixed(2); + + label_01_peak.innerHTML = (data_01_peak / 100).toFixed(2); + label_05_peak.innerHTML = (data_05_peak / 100).toFixed(2); + label_15_peak.innerHTML = (data_15_peak / 100).toFixed(2); + } + ); + + XHR.run(); + } + }, 1000 + ); +//]]></script> + +<h2 name="content"><%:Realtime Load%></h2> + +<embed id="bwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/load.svg" /> +<div style="text-align:right"><small id="scale">-</small></div> +<br /> + +<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid #ff0000; white-space:nowrap"><%:1 Minute Load:%></strong></div> + <div class="td" id="lb_load01_cur">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="lb_load01_avg">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="lb_load01_peak">0</div> + </div> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid #ff6600; white-space:nowrap"><%:5 Minute Load:%></strong></div> + <div class="td" id="lb_load05_cur">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="lb_load05_avg">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="lb_load05_peak">0</div> + </div> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid #ffaa00; white-space:nowrap"><%:15 Minute Load:%></strong></div> + <div class="td" id="lb_load15_cur">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="lb_load15_avg">0</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="lb_load15_peak">0</div> + </div> +</div> + +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/routes.htm b/modules/luci-mod-status/luasrc/view/admin_status/routes.htm new file mode 100644 index 0000000000..74779f6ad8 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/routes.htm @@ -0,0 +1,156 @@ +<%# + Copyright 2008-2009 Steven Barth <steven@midlink.org> + Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%- + require "luci.tools.webadmin" + require "nixio.fs" + + local ip = require "luci.ip" + local style = true + local _, v + + local rtn = { + [255] = "local", + [254] = "main", + [253] = "default", + [0] = "unspec" + } + + if nixio.fs.access("/etc/iproute2/rt_tables") then + local ln + for ln in io.lines("/etc/iproute2/rt_tables") do + local i, n = ln:match("^(%d+)%s+(%S+)") + if i and n then + rtn[tonumber(i)] = n + end + end + end +-%> + +<%+header%> + + +<div class="cbi-map" id="cbi-network"> + <h2 name="content"><%:Routes%></h2> + <div class="cbi-map-descr"><%:The following rules are currently active on this system.%></div> + + <div class="cbi-section"> + <legend>ARP</legend> + <div class="cbi-section-node"> + <div class="table"> + <div class="tr table-titles"> + <div class="th"><%_<abbr title="Internet Protocol Version 4">IPv4</abbr>-Address%></div> + <div class="th"><%_<abbr title="Media Access Control">MAC</abbr>-Address%></div> + <div class="th"><%:Interface%></div> + </div> + + <% + for _, v in ipairs(ip.neighbors({ family = 4 })) do + if v.mac then + %> + <div class="tr cbi-rowstyle-<%=(style and 1 or 2)%>"> + <div class="td"><%=v.dest%></div> + <div class="td"><%=v.mac%></div> + <div class="td"><%=luci.tools.webadmin.iface_get_network(v.dev) or '(' .. v.dev .. ')'%></div> + </div> + <% + style = not style + end + end + %> + </div> + </div> + </div> + + <div class="cbi-section"> + <legend><%_Active <abbr title="Internet Protocol Version 4">IPv4</abbr>-Routes%></legend> + <div class="cbi-section-node"> + <div class="table"> + <div class="tr table-titles"> + <div class="th"><%:Network%></div> + <div class="th"><%:Target%></div> + <div class="th"><%_<abbr title="Internet Protocol Version 4">IPv4</abbr>-Gateway%></div> + <div class="th"><%:Metric%></div> + <div class="th"><%:Table%></div> + </div> + <% for _, v in ipairs(ip.routes({ family = 4, type = 1 })) do %> + <div class="tr cbi-rowstyle-<%=(style and 1 or 2)%>"> + <div class="td"><%=luci.tools.webadmin.iface_get_network(v.dev) or v.dev%></div> + <div class="td"><%=v.dest%></div> + <div class="td"><%=v.gw or "-"%></div> + <div class="td"><%=v.metric or 0%></div> + <div class="td"><%=rtn[v.table] or v.table%></div> + </div> + <% style = not style end %> + </div> + </div> + </div> + + <% + if nixio.fs.access("/proc/net/ipv6_route") then + style = true + %> + <div class="cbi-section"> + <legend><%_Active <abbr title="Internet Protocol Version 6">IPv6</abbr>-Routes%></legend> + <div class="cbi-section-node"> + <div class="table"> + <div class="tr table-titles"> + <div class="th"><%:Network%></div> + <div class="th"><%:Target%></div> + <div class="th"><%:Source%></div> + <div class="th"><%:Metric%></div> + <div class="th"><%:Table%></div> + </div> + <% + for _, v in ipairs(ip.routes({ family = 6, type = 1 })) do + if v.dest and not v.dest:is6linklocal() then + %> + <div class="tr cbi-rowstyle-<%=(style and 1 or 2)%>"> + <div class="td"><%=luci.tools.webadmin.iface_get_network(v.dev) or '(' .. v.dev .. ')'%></div> + <div class="td"><%=v.dest%></div> + <div class="td"><%=v.from%></div> + <div class="td"><%=v.metric or 0%></div> + <div class="td"><%=rtn[v.table] or v.table%></div> + </div> + <% + style = not style + end + end + %> + </div> + </div> + </div> + + <div class="cbi-section"> + <legend><%:IPv6 Neighbours%></legend> + <div class="cbi-section-node"> + <div class="table"> + <div class="tr table-titles"> + <div class="th"><%:IPv6-Address%></div> + <div class="th"><%:MAC-Address%></div> + <div class="th"><%:Interface%></div> + </div> + <% + for _, v in ipairs(ip.neighbors({ family = 6 })) do + if v.dest and not v.dest:is6linklocal() and v.mac then + %> + <div class="tr cbi-rowstyle-<%=(style and 1 or 2)%>"> + <div class="td"><%=v.dest%></div> + <div class="td"><%=v.mac%></div> + <div class="td"><%=luci.tools.webadmin.iface_get_network(v.dev) or '(' .. v.dev .. ')'%></div> + </div> + <% + style = not style + end + end + %> + </div> + </div> + </div> + <% end %> +</div> + +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/syslog.htm b/modules/luci-mod-status/luasrc/view/admin_status/syslog.htm new file mode 100644 index 0000000000..fb734a76d9 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/syslog.htm @@ -0,0 +1,12 @@ +<%# + Copyright 2008 Steven Barth <steven@midlink.org> + Copyright 2008 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> +<h2 name="content"><%:System Log%></h2> +<div id="content_syslog"> +<textarea style="font-size: 12px;" readonly="readonly" wrap="off" rows="<%=syslog:cmatch("\n")+2%>" id="syslog"><%=syslog:pcdata()%></textarea> +</div> +<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/wireless.htm b/modules/luci-mod-status/luasrc/view/admin_status/wireless.htm new file mode 100644 index 0000000000..8ec43cb0e6 --- /dev/null +++ b/modules/luci-mod-status/luasrc/view/admin_status/wireless.htm @@ -0,0 +1,371 @@ +<%# + Copyright 2011 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%- + local ntm = require "luci.model.network".init() + + local dev + local devices = { } + for _, dev in luci.util.vspairs(luci.sys.net.devices()) do + if dev:match("^wlan%d") or dev:match("^ath%d") or dev:match("^wl%d") then + devices[#devices+1] = dev + end + end + + local curdev = luci.http.formvalue("dev") or devices[1] +-%> + +<%+header%> + +<script type="text/javascript">//<![CDATA[ + var bwxhr = new XHR(); + + var G, G2; + var TIME = 0; + var RATE = 1; + var RSSI = 2; + var NOISE = 3; + + var width = 760; + var height = 300; + var step = 5; + + var data_wanted = Math.floor(width / step); + var data_fill = 0; + var data_stamp = 0; + + var data_rssi = [ ]; + var data_noise = [ ]; + var data_rate = [ ]; + + var line_rssi; + var line_noise; + var line_rate; + + var label_25, label_25_2; + var label_50, label_50_2; + var label_75, label_75_2; + + var label_rssi_cur; + var label_rssi_avg; + var label_rssi_peak; + + var label_noise_cur; + var label_noise_avg; + var label_noise_peak; + + var label_rate_cur; + var label_rate_avg; + var label_rate_peak; + + var label_scale; + + + /* wait for SVG */ + window.setTimeout( + function() { + var svg = document.getElementById('iwsvg'); + var svg2 = document.getElementById('iwsvg2'); + + try { + G = svg.getSVGDocument + ? svg.getSVGDocument() : svg.contentDocument; + G2 = svg2.getSVGDocument + ? svg2.getSVGDocument() : svg2.contentDocument; + } + catch(e) { + G = document.embeds['iwsvg'].getSVGDocument(); + G2 = document.embeds['iwsvg2'].getSVGDocument(); + } + + if (!G || !G2) + { + window.setTimeout(arguments.callee, 1000); + } + else + { + /* find sizes */ + width = svg.offsetWidth - 2; + height = svg.offsetHeight - 2; + data_wanted = Math.ceil(width / step); + + /* prefill datasets */ + for (var i = 0; i < data_wanted; i++) + { + data_rssi[i] = 0; + data_noise[i] = 0; + data_rate[i] = 0; + } + + /* find svg elements */ + line_rssi = G.getElementById('rssi'); + line_noise = G.getElementById('noise'); + line_rate = G2.getElementById('rate'); + + label_25 = G.getElementById('label_25'); + label_50 = G.getElementById('label_50'); + label_75 = G.getElementById('label_75'); + label_25_2 = G2.getElementById('label_25'); + label_50_2 = G2.getElementById('label_50'); + label_75_2 = G2.getElementById('label_75'); + + label_rssi_cur = document.getElementById('rssi_bw_cur'); + label_rssi_avg = document.getElementById('rssi_bw_avg'); + label_rssi_peak = document.getElementById('rssi_bw_peak'); + + label_noise_cur = document.getElementById('noise_bw_cur'); + label_noise_avg = document.getElementById('noise_bw_avg'); + label_noise_peak = document.getElementById('noise_bw_peak'); + + label_rate_cur = document.getElementById('rate_bw_cur'); + label_rate_avg = document.getElementById('rate_bw_avg'); + label_rate_peak = document.getElementById('rate_bw_peak'); + + label_scale = document.getElementById('scale'); + label_scale_2 = document.getElementById('scale2'); + + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) + { + var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + label_25.parentNode.appendChild(line); + label_25.parentNode.appendChild(text); + + + var line2 = G2.createElementNS('http://www.w3.org/2000/svg', 'line'); + line2.setAttribute('x1', i); + line2.setAttribute('y1', 0); + line2.setAttribute('x2', i); + line2.setAttribute('y2', '100%'); + line2.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text2 = G2.createElementNS('http://www.w3.org/2000/svg', 'text'); + text2.setAttribute('x', i + 5); + text2.setAttribute('y', 15); + text2.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text2.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + label_25_2.parentNode.appendChild(line2); + label_25_2.parentNode.appendChild(text2); + } + + label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); + label_scale_2.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); + + /* render datasets, start update interval */ + XHR.poll(3, '<%=build_url("admin/status/realtime/wireless_status", curdev)%>', null, + function(x, data) + { + var noise_floor = 255; + var rate_floor = 60000; + + for (var i = 0; i < data.length; i++) { + noise_floor = Math.min(noise_floor, data[i][NOISE]); + rate_floor = Math.min(rate_floor, data[i][RATE]); + } + + noise_floor -= 5; + + var data_max = 0; + var data_scale = 0; + var data_max_2 = 0; + var data_scale_2 = 0; + + var data_rssi_avg = 0; + var data_noise_avg = 0; + var data_rate_avg = 0; + + var data_rssi_peak = 0; + var data_noise_peak = 0; + var data_rate_peak = 0; + + for (var i = data_stamp ? 0 : 1; i < data.length; i++) + { + /* skip overlapping entries */ + if (data[i][TIME] <= data_stamp) + continue; + + data_rssi.push(data[i][RSSI] - noise_floor); + data_noise.push(data[i][NOISE] - noise_floor); + data_rate.push(Math.floor(data[i][RATE] / 1000)); + } + + /* cut off outdated entries */ + data_rssi = data_rssi.slice(data_rssi.length - data_wanted, data_rssi.length); + data_noise = data_noise.slice(data_noise.length - data_wanted, data_noise.length); + data_rate = data_rate.slice(data_rate.length - data_wanted, data_rate.length); + + /* find peak */ + for (var i = 0; i < data_rssi.length; i++) + { + data_max = Math.max(data_max, data_rssi[i]); + data_max_2 = Math.max(data_max_2, data_rate[i]); + + data_rssi_peak = Math.max(data_rssi_peak, data_rssi[i]); + data_noise_peak = Math.max(data_noise_peak, data_noise[i]); + data_rate_peak = Math.max(data_rate_peak, data_rate[i]); + + if (i > 0) + { + data_rssi_avg = (data_rssi_avg + data_rssi[i]) / 2; + data_noise_avg = (data_noise_avg + data_noise[i]) / 2; + data_rate_avg = (data_rate_avg + data_rate[i]) / 2; + } + else + { + data_rssi_avg = data_rssi[i]; + data_noise_avg = data_noise[i]; + data_rate_avg = data_rate[i]; + } + } + + /* remember current timestamp, calculate horizontal scale */ + data_stamp = data[data.length-1][TIME]; + data_scale = (height / (data_max * 1.1)).toFixed(1); + data_scale_2 = (height / (data_max_2 * 1.1)).toFixed(1); + + /* plot data */ + var pt_rssi = '0,' + height; + var pt_noise = '0,' + height; + var pt_rate = '0,' + height; + + var y_rssi = 0; + var y_noise = 0; + var y_rate = 0; + + for (var i = 0; i < data_rssi.length; i++) + { + var x = i * step; + + y_rssi = height - Math.floor(data_rssi[i] * data_scale); + y_noise = height - Math.floor(data_noise[i] * data_scale); + y_rate = height - Math.floor(data_rate[i] * data_scale_2); + + y_rssi -= Math.floor(y_rssi % (1/data_scale)); + y_noise -= Math.floor(y_noise % (1/data_scale)); + + pt_rssi += ' ' + x + ',' + y_rssi; + pt_noise += ' ' + x + ',' + y_noise; + pt_rate += ' ' + x + ',' + y_rate; + } + + pt_rssi += ' ' + width + ',' + y_rssi + ' ' + width + ',' + height; + pt_noise += ' ' + width + ',' + y_noise + ' ' + width + ',' + height; + pt_rate += ' ' + width + ',' + y_rate + ' ' + width + ',' + height; + + line_rssi.setAttribute('points', pt_rssi); + line_noise.setAttribute('points', pt_noise); + line_rate.setAttribute('points', pt_rate); + + function wireless_label(dbm, noise) + { + if (noise) + return String.format("%d <%:dBm%> (SNR %d <%:dB%>)", noise_floor + dbm - 255, dbm - noise); + else + return String.format("%d <%:dBm%>", noise_floor + dbm - 255); + } + + function rate_label(mbit) + { + return String.format("%d <%:Mbit/s%>", mbit); + } + + label_25.firstChild.data = wireless_label(1.1 * 0.25 * data_max); + label_50.firstChild.data = wireless_label(1.1 * 0.50 * data_max); + label_75.firstChild.data = wireless_label(1.1 * 0.75 * data_max); + + label_25_2.firstChild.data = rate_label(1.1 * 0.25 * data_max_2); + label_50_2.firstChild.data = rate_label(1.1 * 0.50 * data_max_2); + label_75_2.firstChild.data = rate_label(1.1 * 0.75 * data_max_2); + + label_rssi_cur.innerHTML = wireless_label(data_rssi[data_rssi.length-1], data_noise[data_noise.length-1]).nobr(); + label_noise_cur.innerHTML = wireless_label(data_noise[data_noise.length-1]).nobr(); + + label_rssi_avg.innerHTML = wireless_label(data_rssi_avg, data_noise_avg).nobr(); + label_noise_avg.innerHTML = wireless_label(data_noise_avg).nobr(); + + label_rssi_peak.innerHTML = wireless_label(data_rssi_peak, data_noise_peak).nobr(); + label_noise_peak.innerHTML = wireless_label(data_noise_peak).nobr(); + + label_rate_cur.innerHTML = rate_label(data_rate[data_rate.length-1]); + label_rate_avg.innerHTML = rate_label(data_rate_avg); + label_rate_peak.innerHTML = rate_label(data_rate_peak); + } + ); + + XHR.run(); + } + }, 1000 + ); +//]]></script> + +<h2 name="content"><%:Realtime Wireless%></h2> + +<ul class="cbi-tabmenu"> + <% for _, dev in ipairs(devices) do %> + <li class="cbi-tab<%= dev == curdev and "" or "-disabled" %>"><a href="?dev=<%=pcdata(dev)%>"><%=pcdata(dev)%></a></li> + <% end %> +</ul> + +<embed id="iwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/wireless.svg" /> +<div style="text-align:right"><small id="scale">-</small></div> +<br /> + +<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid blue"><%:Signal:%></strong></div> + <div class="td" id="rssi_bw_cur">0 <%:dBm%></div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="rssi_bw_avg">0 <%:dBm%></div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="rssi_bw_peak">0 <%:dBm%></div> + </div> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid red"><%:Noise:%></strong></div> + <div class="td" id="noise_bw_cur">0 <%:dBm%></div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="noise_bw_avg">0 <%:dBm%></div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="noise_bw_peak">0 <%:dBm%></div> + </div> +</div> + +<br /> + +<embed id="iwsvg2" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/wifirate.svg" /> +<div style="text-align:right"><small id="scale2">-</small></div> +<br /> + +<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> + <div class="tr"> + <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid green"><%:Phy Rate:%></strong></div> + <div class="td" id="rate_bw_cur">0 MBit/s</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> + <div class="td" id="rate_bw_avg">0 MBit/s</div> + + <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> + <div class="td" id="rate_bw_peak">0 MBit/s</div> + </div> +</div> + +<%+footer%> diff --git a/modules/luci-mod-status/src/Makefile b/modules/luci-mod-status/src/Makefile new file mode 100644 index 0000000000..d6ed8c6e46 --- /dev/null +++ b/modules/luci-mod-status/src/Makefile @@ -0,0 +1,14 @@ +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(FPIC) -c -o $@ $< + +clean: + rm -f luci-bwc *.o + +luci-bwc: luci-bwc.o + $(CC) $(LDFLAGS) -o $@ $^ -ldl + +compile: luci-bwc + +install: compile + mkdir -p $(DESTDIR)/usr/bin + cp luci-bwc $(DESTDIR)/usr/bin/luci-bwc diff --git a/modules/luci-mod-status/src/luci-bwc.c b/modules/luci-mod-status/src/luci-bwc.c new file mode 100644 index 0000000000..8ddd91727a --- /dev/null +++ b/modules/luci-mod-status/src/luci-bwc.c @@ -0,0 +1,778 @@ +/* + * luci-bwc - Very simple bandwidth collector cache for LuCI realtime graphs + * + * Copyright (C) 2010 Jo-Philipp Wich <jow@openwrt.org> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <inttypes.h> +#include <fcntl.h> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> + +#include <sys/stat.h> +#include <sys/mman.h> +#include <arpa/inet.h> + +#include <dlfcn.h> +#include <iwinfo.h> + +#define STEP_COUNT 60 +#define STEP_TIME 1 +#define TIMEOUT 10 + +#define PID_PATH "/var/run/luci-bwc.pid" + +#define DB_PATH "/var/lib/luci-bwc" +#define DB_IF_FILE DB_PATH "/if/%s" +#define DB_RD_FILE DB_PATH "/radio/%s" +#define DB_CN_FILE DB_PATH "/connections" +#define DB_LD_FILE DB_PATH "/load" + +#define IF_SCAN_PATTERN \ + " %[^ :]:%u %u" \ + " %*d %*d %*d %*d %*d %*d" \ + " %u %u" + +#define LD_SCAN_PATTERN \ + "%f %f %f" + + +struct file_map { + int fd; + int size; + char *mmap; +}; + +struct traffic_entry { + uint32_t time; + uint32_t rxb; + uint32_t rxp; + uint32_t txb; + uint32_t txp; +}; + +struct conn_entry { + uint32_t time; + uint32_t udp; + uint32_t tcp; + uint32_t other; +}; + +struct load_entry { + uint32_t time; + uint16_t load1; + uint16_t load5; + uint16_t load15; +}; + +struct radio_entry { + uint32_t time; + uint16_t rate; + uint8_t rssi; + uint8_t noise; +}; + +static int readpid(void) +{ + int fd; + int pid = -1; + char buf[9] = { 0 }; + + if ((fd = open(PID_PATH, O_RDONLY)) > -1) + { + if (read(fd, buf, sizeof(buf))) + { + buf[8] = 0; + pid = atoi(buf); + } + + close(fd); + } + + return pid; +} + +static int writepid(void) +{ + int fd; + int wlen; + char buf[9] = { 0 }; + + if ((fd = open(PID_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0600)) > -1) + { + wlen = snprintf(buf, sizeof(buf), "%i", getpid()); + write(fd, buf, wlen); + close(fd); + + return 0; + } + + return -1; +} + +static int timeout = TIMEOUT; +static int countdown = -1; + +static void reset_countdown(int sig) +{ + countdown = timeout; + +} + + +static char *progname; +static int prognamelen; + +static struct iwinfo_ops *backend = NULL; + + +static int init_directory(char *path) +{ + char *p = path; + + for (p = &path[1]; *p; p++) + { + if (*p == '/') + { + *p = 0; + + if (mkdir(path, 0700) && (errno != EEXIST)) + return -1; + + *p = '/'; + } + } + + return 0; +} + +static int init_file(char *path, int esize) +{ + int i, file; + char buf[sizeof(struct traffic_entry)] = { 0 }; + + if (init_directory(path)) + return -1; + + if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0) + { + for (i = 0; i < STEP_COUNT; i++) + { + if (write(file, buf, esize) < 0) + break; + } + + close(file); + + return 0; + } + + return -1; +} + +static inline uint32_t timeof(void *entry) +{ + return ntohl(((struct traffic_entry *)entry)->time); +} + +static int update_file(const char *path, void *entry, int esize) +{ + int rv = -1; + int file; + char *map; + + if ((file = open(path, O_RDWR)) >= 0) + { + map = mmap(NULL, esize * STEP_COUNT, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_LOCKED, file, 0); + + if ((map != NULL) && (map != MAP_FAILED)) + { + if (timeof(entry) > timeof(map + esize * (STEP_COUNT-1))) + { + memmove(map, map + esize, esize * (STEP_COUNT-1)); + memcpy(map + esize * (STEP_COUNT-1), entry, esize); + } + + munmap(map, esize * STEP_COUNT); + + rv = 0; + } + + close(file); + } + + return rv; +} + +static int mmap_file(const char *path, int esize, struct file_map *m) +{ + m->fd = -1; + m->size = -1; + m->mmap = NULL; + + if ((m->fd = open(path, O_RDONLY)) >= 0) + { + m->size = STEP_COUNT * esize; + m->mmap = mmap(NULL, m->size, PROT_READ, + MAP_SHARED | MAP_LOCKED, m->fd, 0); + + if ((m->mmap != NULL) && (m->mmap != MAP_FAILED)) + return 0; + } + + return -1; +} + +static void umap_file(struct file_map *m) +{ + if ((m->mmap != NULL) && (m->mmap != MAP_FAILED)) + munmap(m->mmap, m->size); + + if (m->fd > -1) + close(m->fd); +} + +static void * iw_open(void) +{ + return dlopen("/usr/lib/libiwinfo.so", RTLD_LAZY); +} + +static int iw_update( + void *iw, const char *ifname, uint16_t *rate, uint8_t *rssi, uint8_t *noise +) { + struct iwinfo_ops *(*probe)(const char *); + int val; + + if (!backend) + { + probe = dlsym(iw, "iwinfo_backend"); + + if (!probe) + return 0; + + backend = probe(ifname); + + if (!backend) + return 0; + } + + *rate = (backend->bitrate && !backend->bitrate(ifname, &val)) ? val : 0; + *rssi = (backend->signal && !backend->signal(ifname, &val)) ? val : 0; + *noise = (backend->noise && !backend->noise(ifname, &val)) ? val : 0; + + return 1; +} + +static void iw_close(void *iw) +{ + void (*finish)(void); + + finish = dlsym(iw, "iwinfo_finish"); + + if (finish) + finish(); + + dlclose(iw); +} + + +static int update_ifstat( + const char *ifname, uint32_t rxb, uint32_t rxp, uint32_t txb, uint32_t txp +) { + char path[1024]; + + struct stat s; + struct traffic_entry e; + + snprintf(path, sizeof(path), DB_IF_FILE, ifname); + + if (stat(path, &s)) + { + if (init_file(path, sizeof(struct traffic_entry))) + { + fprintf(stderr, "Failed to init %s: %s\n", + path, strerror(errno)); + + return -1; + } + } + + e.time = htonl(time(NULL)); + e.rxb = htonl(rxb); + e.rxp = htonl(rxp); + e.txb = htonl(txb); + e.txp = htonl(txp); + + return update_file(path, &e, sizeof(struct traffic_entry)); +} + +static int update_radiostat( + const char *ifname, uint16_t rate, uint8_t rssi, uint8_t noise +) { + char path[1024]; + + struct stat s; + struct radio_entry e; + + snprintf(path, sizeof(path), DB_RD_FILE, ifname); + + if (stat(path, &s)) + { + if (init_file(path, sizeof(struct radio_entry))) + { + fprintf(stderr, "Failed to init %s: %s\n", + path, strerror(errno)); + + return -1; + } + } + + e.time = htonl(time(NULL)); + e.rate = htons(rate); + e.rssi = rssi; + e.noise = noise; + + return update_file(path, &e, sizeof(struct radio_entry)); +} + +static int update_cnstat(uint32_t udp, uint32_t tcp, uint32_t other) +{ + char path[1024]; + + struct stat s; + struct conn_entry e; + + snprintf(path, sizeof(path), DB_CN_FILE); + + if (stat(path, &s)) + { + if (init_file(path, sizeof(struct conn_entry))) + { + fprintf(stderr, "Failed to init %s: %s\n", + path, strerror(errno)); + + return -1; + } + } + + e.time = htonl(time(NULL)); + e.udp = htonl(udp); + e.tcp = htonl(tcp); + e.other = htonl(other); + + return update_file(path, &e, sizeof(struct conn_entry)); +} + +static int update_ldstat(uint16_t load1, uint16_t load5, uint16_t load15) +{ + char path[1024]; + + struct stat s; + struct load_entry e; + + snprintf(path, sizeof(path), DB_LD_FILE); + + if (stat(path, &s)) + { + if (init_file(path, sizeof(struct load_entry))) + { + fprintf(stderr, "Failed to init %s: %s\n", + path, strerror(errno)); + + return -1; + } + } + + e.time = htonl(time(NULL)); + e.load1 = htons(load1); + e.load5 = htons(load5); + e.load15 = htons(load15); + + return update_file(path, &e, sizeof(struct load_entry)); +} + +static int run_daemon(void) +{ + FILE *info; + uint32_t rxb, txb, rxp, txp; + uint32_t udp, tcp, other; + uint16_t rate; + uint8_t rssi, noise; + float lf1, lf5, lf15; + char line[1024]; + char ifname[16]; + int i; + void *iw; + struct sigaction sa; + + struct stat s; + const char *ipc = stat("/proc/net/nf_conntrack", &s) + ? "/proc/net/ip_conntrack" : "/proc/net/nf_conntrack"; + + switch (fork()) + { + case -1: + perror("fork()"); + return -1; + + case 0: + if (chdir("/") < 0) + { + perror("chdir()"); + exit(1); + } + + close(0); + close(1); + close(2); + break; + + default: + return 0; + } + + /* setup USR1 signal handler to reset timer */ + sa.sa_handler = reset_countdown; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + sigaction(SIGUSR1, &sa, NULL); + + /* write pid */ + if (writepid()) + { + fprintf(stderr, "Failed to write pid file: %s\n", strerror(errno)); + return 1; + } + + /* initialize iwinfo */ + iw = iw_open(); + + /* go */ + for (reset_countdown(0); countdown >= 0; countdown--) + { + /* alter progname for ps, top */ + memset(progname, 0, prognamelen); + snprintf(progname, prognamelen, "luci-bwc %d", countdown); + + if ((info = fopen("/proc/net/dev", "r")) != NULL) + { + while (fgets(line, sizeof(line), info)) + { + if (strchr(line, '|')) + continue; + + if (sscanf(line, IF_SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp)) + { + if (strncmp(ifname, "lo", sizeof(ifname))) + update_ifstat(ifname, rxb, rxp, txb, txp); + } + } + + fclose(info); + } + + if (iw) + { + for (i = 0; i < 5; i++) + { +#define iw_checkif(pattern) \ + do { \ + snprintf(ifname, sizeof(ifname), pattern, i); \ + if (iw_update(iw, ifname, &rate, &rssi, &noise)) \ + { \ + update_radiostat(ifname, rate, rssi, noise); \ + continue; \ + } \ + } while(0) + + iw_checkif("wlan%d"); + iw_checkif("ath%d"); + iw_checkif("wl%d"); + } + } + + if ((info = fopen(ipc, "r")) != NULL) + { + udp = 0; + tcp = 0; + other = 0; + + while (fgets(line, sizeof(line), info)) + { + if (strstr(line, "TIME_WAIT")) + continue; + + if ((strstr(line, "src=127.0.0.1 ") && strstr(line, "dst=127.0.0.1 ")) + || (strstr(line, "src=::1 ") && strstr(line, "dst=::1 "))) + continue; + + if (sscanf(line, "%*s %*d %s", ifname) || sscanf(line, "%s %*d", ifname)) + { + if (!strcmp(ifname, "tcp")) + tcp++; + else if (!strcmp(ifname, "udp")) + udp++; + else + other++; + } + } + + update_cnstat(udp, tcp, other); + + fclose(info); + } + + if ((info = fopen("/proc/loadavg", "r")) != NULL) + { + if (fscanf(info, LD_SCAN_PATTERN, &lf1, &lf5, &lf15)) + { + update_ldstat((uint16_t)(lf1 * 100), + (uint16_t)(lf5 * 100), + (uint16_t)(lf15 * 100)); + } + + fclose(info); + } + + sleep(STEP_TIME); + } + + unlink(PID_PATH); + + if (iw) + iw_close(iw); + + return 0; +} + +static void check_daemon(void) +{ + int pid; + + if ((pid = readpid()) < 0 || kill(pid, 0) < 0) + { + /* daemon ping failed, try to start it up */ + if (run_daemon()) + { + fprintf(stderr, + "Failed to ping daemon and unable to start it up: %s\n", + strerror(errno)); + + exit(1); + } + } + else if (kill(pid, SIGUSR1)) + { + fprintf(stderr, "Failed to send signal: %s\n", strerror(errno)); + exit(2); + } +} + +static int run_dump_ifname(const char *ifname) +{ + int i; + char path[1024]; + struct file_map m; + struct traffic_entry *e; + + check_daemon(); + snprintf(path, sizeof(path), DB_IF_FILE, ifname); + + if (mmap_file(path, sizeof(struct traffic_entry), &m)) + { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + return 1; + } + + for (i = 0; i < m.size; i += sizeof(struct traffic_entry)) + { + e = (struct traffic_entry *) &m.mmap[i]; + + if (!e->time) + continue; + + printf("[ %u, %u, %" PRIu32 + ", %u, %u ]%s\n", + ntohl(e->time), + ntohl(e->rxb), ntohl(e->rxp), + ntohl(e->txb), ntohl(e->txp), + ((i + sizeof(struct traffic_entry)) < m.size) ? "," : ""); + } + + umap_file(&m); + + return 0; +} + +static int run_dump_radio(const char *ifname) +{ + int i; + char path[1024]; + struct file_map m; + struct radio_entry *e; + + check_daemon(); + snprintf(path, sizeof(path), DB_RD_FILE, ifname); + + if (mmap_file(path, sizeof(struct radio_entry), &m)) + { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + return 1; + } + + for (i = 0; i < m.size; i += sizeof(struct radio_entry)) + { + e = (struct radio_entry *) &m.mmap[i]; + + if (!e->time) + continue; + + printf("[ %u, %d, %d, %d ]%s\n", + ntohl(e->time), + e->rate, e->rssi, e->noise, + ((i + sizeof(struct radio_entry)) < m.size) ? "," : ""); + } + + umap_file(&m); + + return 0; +} + +static int run_dump_conns(void) +{ + int i; + char path[1024]; + struct file_map m; + struct conn_entry *e; + + check_daemon(); + snprintf(path, sizeof(path), DB_CN_FILE); + + if (mmap_file(path, sizeof(struct conn_entry), &m)) + { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + return 1; + } + + for (i = 0; i < m.size; i += sizeof(struct conn_entry)) + { + e = (struct conn_entry *) &m.mmap[i]; + + if (!e->time) + continue; + + printf("[ %u, %u, %u, %u ]%s\n", + ntohl(e->time), ntohl(e->udp), + ntohl(e->tcp), ntohl(e->other), + ((i + sizeof(struct conn_entry)) < m.size) ? "," : ""); + } + + umap_file(&m); + + return 0; +} + +static int run_dump_load(void) +{ + int i; + char path[1024]; + struct file_map m; + struct load_entry *e; + + check_daemon(); + snprintf(path, sizeof(path), DB_LD_FILE); + + if (mmap_file(path, sizeof(struct load_entry), &m)) + { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + return 1; + } + + for (i = 0; i < m.size; i += sizeof(struct load_entry)) + { + e = (struct load_entry *) &m.mmap[i]; + + if (!e->time) + continue; + + printf("[ %u, %u, %u, %u ]%s\n", + ntohl(e->time), + ntohs(e->load1), ntohs(e->load5), ntohs(e->load15), + ((i + sizeof(struct load_entry)) < m.size) ? "," : ""); + } + + umap_file(&m); + + return 0; +} + + +int main(int argc, char *argv[]) +{ + int opt; + + progname = argv[0]; + prognamelen = -1; + + for (opt = 0; opt < argc; opt++) + prognamelen += 1 + strlen(argv[opt]); + + while ((opt = getopt(argc, argv, "t:i:r:cl")) > -1) + { + switch (opt) + { + case 't': + timeout = atoi(optarg); + break; + + case 'i': + if (optarg) + return run_dump_ifname(optarg); + break; + + case 'r': + if (optarg) + return run_dump_radio(optarg); + break; + + case 'c': + return run_dump_conns(); + + case 'l': + return run_dump_load(); + + default: + break; + } + } + + fprintf(stderr, + "Usage:\n" + " %s [-t timeout] -i ifname\n" + " %s [-t timeout] -r radiodev\n" + " %s [-t timeout] -c\n" + " %s [-t timeout] -l\n", + argv[0], argv[0], argv[0], argv[0] + ); + + return 1; +} |