diff --git a/CONTRIBUTED.md b/CONTRIBUTED.md
index 8bd938af44..2af7aa017f 100644
--- a/CONTRIBUTED.md
+++ b/CONTRIBUTED.md
@@ -60,9 +60,7 @@ luci-app-beardropper source: [NateLol/natelol](https://github.com/NateLol/natelo
luci-app-vssr source: [jerrykuku/luci-app-vssr](https://github.com/jerrykuku/luci-app-vssr).
luci-proto-modemmanager source: [nickberry17/luci-proto-modemmanager](https://github.com/nickberry17/luci-proto-modemmanager).
luci-theme-infinityfreedom source: [xiaoqingfengATGH/luci-theme-infinityfreedom](https://github.com/xiaoqingfengATGH/luci-theme-infinityfreedom).
-luci-proto-minieap source: [ysc3839/luci-proto-minieap](https://github.com/ysc3839/luci-proto-minieap).
-wrtbwmon source: [brvphoenix/wrtbwmon](https://github.com/brvphoenix/wrtbwmon).
-luci-app-wrtbwmon source: [brvphoenix/luci-app-wrtbwmon](https://github.com/brvphoenix/luci-app-wrtbwmon).
+luci-proto-minieap source: [ysc3839/luci-proto-minieap](https://github.com/ysc3839/luci-proto-minieap).
## License
### Depend on their own License.
diff --git a/package/ctcgfw/luci-app-wrtbwmon/Makefile b/package/ctcgfw/luci-app-wrtbwmon/Makefile
deleted file mode 100644
index 4f6f2cec83..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/Makefile
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2021 Van Waholtz
-#
-# This is free software, licensed under the Apache License, Version 2.0.
-# See /LICENSE for more information.
-#
-
-include $(TOPDIR)/rules.mk
-
-PKG_NAME:=luci-app-wrtbwmon
-PKG_VERSION:=1.6.3
-PKG_RELEASE:=1
-
-PKG_LICENSE:=Apache-2.0
-PKG_MAINTAINER:=Van Waholtz
-
-LUCI_TITLE:=A Luci module that uses wrtbwmon to track bandwidth usage
-LUCI_DEPENDS:=+@BUSYBOX_CONFIG_IP +iptables
-LUCI_PKGARCH:=all
-
-include $(TOPDIR)/feeds/luci/luci.mk
-
-# call BuildPackage - OpenWrt buildroot signature
diff --git a/package/ctcgfw/luci-app-wrtbwmon/etc/config/wrtbwmon b/package/ctcgfw/luci-app-wrtbwmon/etc/config/wrtbwmon
deleted file mode 100644
index da9470a111..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/etc/config/wrtbwmon
+++ /dev/null
@@ -1,5 +0,0 @@
-
-config wrtbwmon 'general'
- option enabled '1'
- option path '/tmp/usage.db'
-
diff --git a/package/ctcgfw/luci-app-wrtbwmon/etc/hotplug.d/iface/99-wrtbwmon b/package/ctcgfw/luci-app-wrtbwmon/etc/hotplug.d/iface/99-wrtbwmon
deleted file mode 100755
index 41b49a4a20..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/etc/hotplug.d/iface/99-wrtbwmon
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-[ "$ACTION" = ifup -o "$ACTION" = ifupdate ] || exit 0
-[ "$ACTION" = ifupdate -a -z "$IFUPDATE_ADDRESSES" -a -z "$IFUPDATE_DATA" ] && exit 0
-
-/etc/init.d/wrtbwmon restart
-
-logger -t wrtbwmon "Restart for $ACTION of $INTERFACE ($DEVICE)"
-
diff --git a/package/ctcgfw/luci-app-wrtbwmon/etc/init.d/wrtbwmon b/package/ctcgfw/luci-app-wrtbwmon/etc/init.d/wrtbwmon
deleted file mode 100755
index 1c3213ee17..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/etc/init.d/wrtbwmon
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh /etc/rc.common
-# Copyright (C) 2019 OpenWrt.org
-
-START=99
-USE_PROCD=1
-
-NAME=wrtbwmon
-PID_FILE=/var/run/wrtbwmon.pid
-args=/usr/sbin/wrtbwmon
-
-create_instance() {
- procd_open_instance
- procd_set_param command $args
- procd_set_param respawn
- procd_set_param user root
-# procd_set_param pidfile $PID_FILE
- procd_close_instance
-}
-
-service_triggers()
-{
- procd_add_reload_trigger "$NAME"
-}
-
-start_service() {
- local db enabled
- config_load $NAME
-
- config_get db general path
- [ -z "$db" ] && db="/tmp/usage.db"
- append args " -46"
- append args "-f $db"
- append args "-p /tmp/usage.htm"
- append args "-u /etc/wrtbwmon.user"
- append args "-d"
-
- config_get enabled general enabled
- [ "$enabled"0 -eq 0 ] || create_instance
-}
-
-stop_service() {
- procd_kill wrtbwmon
- kill -CONT $(cat $PID_FILE)
-}
diff --git a/package/ctcgfw/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua b/package/ctcgfw/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua
deleted file mode 100644
index 20f254cae6..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua
+++ /dev/null
@@ -1,20 +0,0 @@
-module("luci.controller.wrtbwmon", package.seeall)
-
-function index()
- if not nixio.fs.access("/etc/config/wrtbwmon") then
- return
- end
- entry({"admin", "network", "usage"},
- alias("admin", "network", "usage", "details"),
- _("Traffic Status"), 60)
- entry({"admin", "network", "usage", "details"},
- template("wrtbwmon"),
- _("Details"), 10).leaf=true
- entry({"admin", "network", "usage", "config"},
- arcombine(cbi("wrtbwmon/config")),
- _("Configuration"), 20).leaf=true
- entry({"admin", "network", "usage", "custom"},
- form("wrtbwmon/custom"),
- _("User file"), 30).leaf=true
-end
-
diff --git a/package/ctcgfw/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua b/package/ctcgfw/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua
deleted file mode 100644
index 6b3555e451..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua
+++ /dev/null
@@ -1,48 +0,0 @@
-local lastvalue
-local cursor = luci.model.uci.cursor()
-local m, s, o
-m = Map("wrtbwmon", translate("Usage - Configuration"))
-
-s = m:section(NamedSection, "general", "wrtbwmon", translate("General settings"))
-
-o = s:option(Flag, "enabled", translate("Enabled"))
-o.rmempty= true
-
-o = s:option(Value, "path", translate("Database Path"),
- translate("This box is used to select the Database path, "
- .. "which is /tmp/usage.db by default."))
-o:value("/tmp/usage.db")
-o:value("/etc/usage.db")
-o.rmempty= false
-
-function m.on_parse(self)
- lastvalue = cursor:get("wrtbwmon", "general", "path")
-end
-
-function o.write(self,section,value)
- local fpath = nixio.fs.dirname(value) .. "/"
-
- if not nixio.fs.access(fpath) then
- if not nixio.fs.mkdirr(fpath) then
- return Value.write(self, section, lastvalue)
- end
- end
-
- io.popen("/etc/init.d/wrtbwmon stop")
- io.popen("mv -f " .. fileRename(lastvalue, "*") .. " ".. fpath)
- Value.write(self,section,value)
- io.popen("/etc/init.d/wrtbwmon start")
-
- return true
-end
-
-function fileRename(fileName, tag)
- local idx = fileName:match(".+()%.%w+$")
- if(idx) then
- return fileName:sub(1, idx-1) .. tag .. fileName:sub(idx, -1)
- else
- return fileName .. tag
- end
-end
-
-return m
diff --git a/package/ctcgfw/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm b/package/ctcgfw/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm
deleted file mode 100644
index b78e0e86fc..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm
+++ /dev/null
@@ -1,579 +0,0 @@
-<%#
- Licensed to the public under the Apache License 2.0.
--%>
-
-<%
- local fs = require "nixio.fs"
- local stat = require "luci.tools.status"
- local dba = luci.model.uci.cursor():get("wrtbwmon", "general", "path")
-
--- Function to generate table from string.
- local function strToTable(str)
- local tb = {}
- local cmd = nil
- setmetatable(tb, {__index = table.insert})
- str:gsub("[^%s,]+", tb)
- tb.__index = nil
- return tb
- end
-
--- Function to update the mac-hostname table.
- local function getmactable(family)
- local mactable = {}
- local leases = (family == 4 and {stat.dhcp_leases()} or {stat.dhcp6_leases()})[1]
-
- if fs.access("/etc/wrtbwmon.user") then
- for line in io.lines("/etc/wrtbwmon.user") do
- local macpair = strToTable(line)
- mactable[macpair[1]:lower()] = macpair[2]
- end
- end
-
- for _, line in pairs(leases) do
- if line.macaddr and not mactable[line.macaddr:lower()] then
- mactable[line.macaddr:lower()] = line.hostname
- end
- end
-
- return mactable
- end
-
--- Rename the db file for ipv6.
- local function fileRename(fn, tag)
- local idx = fn:match(".+()%.%w+$")
- if(idx) then
- return fn:sub(1, idx-1) .. tag .. fn:sub(idx, -1)
- else
- return fn .. tag
- end
- end
-
- local function procressData(db, family)
- local dbc = (family == 6 and {fileRename(db, ".6")} or {db})[1]
- local cmd_setup = "/etc/init.d/wrtbwmon restart"
- local cmd_update = "wrtbwmon -" .. family .. " -f " .. db .. " >>/dev/null 2>&1"
- local data, total, mactable, firstline = {}, {0.0, 0.0, 0.0, 0.0, 0.0}, getmactable(family), true
- local isshow = luci.http.formvalue("isShow")
-
--- Setup the background update process.
- if not fs.access("/var/run/wrtbwmon.pid") then
- io.popen(cmd_setup)
- else
- io.popen(cmd_update)
- end
-
--- Process the database.
- for line in io.lines(dbc) do
- if firstline then
- firstline = false
- else
- local tbl = strToTable(line)
- if isshow == "1" or tbl[8] ~= "0" then
- tbl[1] = tbl[1]:lower()
-
- if mactable[tbl[1]] then
- tbl[3] = mactable[tbl[1]]
- else
- tbl[3] = tbl[1]
- end
-
- for i = 1,#total do
- total[i] = total[i] + (tbl[i+3] .. ".0")
- end
-
- data[#data+1] = {tbl[3], tbl[1], unpack(tbl, 4)}
- table.insert(data[#data], tbl[2])
- end
- end
- end
-
--- Transfer the database to js.
- luci.http.prepare_content("application/json")
- luci.http.write_json({data, total})
- return
- end
-
- if luci.http.formvalue("proto") == "ipv4" then
- procressData(dba, 4)
- return
- elseif luci.http.formvalue("proto") == "ipv6" then
- procressData(dba, 6)
- return
- end
-
- if luci.http.formvalue("reset") == "1" then
- os.execute("ip -4 neigh flush dev br-lan && ip -6 neigh flush dev br-lan")
- os.execute("rm -f " .. fileRename(dba, "*") .. " && wrtbwmon -46 -f " .. dba .. " >>/dev/null 2>&1")
- luci.http.status(200, "OK")
- return
- end
--%>
-
-<%+header%>
-
-
-
-
-
<%:Usage - Details%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:downflow:%>
-
<%:upflow:%>
-
-
-
-
-
<%:Clients%>
-
<%:MAC%>
-
<%:Download%>
-
<%:Upload%>
-
<%:Total Down%>
-
<%:Total Up%>
-
<%:Total%>
-
<%:First Seen%>
-
<%:Last Seen%>
-
-
-
<%:Collecting data...%>
-
-
-
-
-
-
-
-
-<%+footer%>
diff --git a/package/ctcgfw/luci-app-wrtbwmon/po/zh-cn/wrtbwmon.po b/package/ctcgfw/luci-app-wrtbwmon/po/zh-cn/wrtbwmon.po
deleted file mode 100644
index 02033745c8..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/po/zh-cn/wrtbwmon.po
+++ /dev/null
@@ -1,152 +0,0 @@
-msgid "Traffic Status"
-msgstr "流量监控"
-
-msgid "Usage - Configuration"
-msgstr "文件设置"
-
-msgid "Details"
-msgstr "流量信息"
-
-msgid "Configuration"
-msgstr "设置"
-
-msgid "User file"
-msgstr "用户文件"
-
-msgid "General settings"
-msgstr "通用设置"
-
-msgid "Wrtbwmon is a mointor of the traffic usage."
-msgstr "Wrtbwmon是一个流量监控器"
-
-msgid "This box is used to select the Database path, which is /tmp/usage.db by default."
-msgstr "该选项用于选择数据存储路径,默认路径为/tmp/usage.db。"
-
-msgid "This box is used to set the total bandwidth (Byte/s), which is 1000000Byte/s by default."
-msgstr "该选项用于选设置总的带宽(Byte/s),默认带宽值1000000Byte/s。"
-
-msgid "Usage - Custom User File"
-msgstr "用户文件配置"
-
-msgid "This file is used to match users with MAC addresses. Each line must have the following format: \"00:aa:bb:cc:ee:ff,username\"."
-msgstr "该文件用来进行MAC和主机进行匹配。每行必须满足\"00:aa:bb:cc:ee:ff,用户名\"的格式"
-
-msgid "Usage - Details"
-msgstr "流量详情"
-
-msgid "downflow:"
-msgstr "下行:"
-
-msgid "upflow:"
-msgstr "上行:"
-
-msgid "protocol:"
-msgstr "协议:"
-
-msgid "Reset Database"
-msgstr "重置"
-
-msgid "Auto update every"
-msgstr "自动刷新"
-
-msgid "Database Path"
-msgstr "数据存储位置"
-
-msgid "Show Zeros:"
-msgstr "显示0流量:"
-
-msgid "Show More:"
-msgstr "显示更多:"
-
-msgid "bandwidth:"
-msgstr "带宽:"
-
-msgid "Calculate per host totals"
-msgstr "单主机总流量统计"
-
-msgid "Disabled"
-msgstr "禁用"
-
-msgid "1 second"
-msgstr "1秒"
-
-msgid "2 seconds"
-msgstr "2秒"
-
-msgid "5 seconds"
-msgstr "5秒"
-
-msgid "10 seconds"
-msgstr "10秒"
-
-msgid "20 seconds"
-msgstr "20秒"
-
-msgid "30 seconds"
-msgstr "30秒"
-
-msgid "40 seconds"
-msgstr "40秒"
-
-msgid "50 seconds"
-msgstr "50秒"
-
-msgid "60 seconds"
-msgstr "60秒"
-
-msgid "2 minutes"
-msgstr "2分"
-
-msgid "3 minutes"
-msgstr "3分"
-
-msgid "Loading..."
-msgstr "数据载入中"
-
-msgid "Clients"
-msgstr "客户端"
-
-msgid "MAC Address"
-msgstr "MAC地址"
-
-msgid "Download"
-msgstr "下载"
-
-msgid "Upload"
-msgstr "上传"
-
-msgid "Total Down"
-msgstr "总下载"
-
-msgid "Total Up"
-msgstr "总上传"
-
-msgid "Total"
-msgstr "总计"
-
-msgid "First Seen"
-msgstr "初次记录"
-
-msgid "Last Seen"
-msgstr "最后记录"
-
-msgid "TOTAL"
-msgstr "总共"
-
-msgid "Last updated"
-msgstr "最后更新于"
-
-msgid "This will delete the database file. Are you sure?"
-msgstr "该操作将删除数据统计文件,确定执行该操作?"
-
-msgid "Updating again in"
-msgstr "下次更新将于"
-
-msgid "seconds."
-msgstr "秒以后。"
-
-msgid "wrtbwmon is not installed!"
-msgstr "wrtbwmon插件尚未安装"
-
-msgid "."
-msgstr "。"
diff --git a/package/ctcgfw/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon b/package/ctcgfw/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon
deleted file mode 100755
index 97ada9bccd..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-
-uci -q batch <<-EOF >/dev/null
- delete ucitrack.@wrtbwmon[-1]
- add ucitrack wrtbwmon
- set ucitrack.@wrtbwmon[-1].init=wrtbwmon
- commit ucitrack
-EOF
-
-rm -f /tmp/luci-indexcache
-exit 0
diff --git a/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/readDB.awk b/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/readDB.awk
deleted file mode 100755
index 2080fe5f79..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/readDB.awk
+++ /dev/null
@@ -1,213 +0,0 @@
-#!/usr/bin/awk
-
-function inInterfaces(host) {
- return(interfaces ~ "(^| )" host "($| )")
-}
-
-function newRule(arp_ip, ipt_cmd) {
- # checking for existing rules shouldn't be necessary if newRule is
- # always called after db is read, arp table is read, and existing
- # iptables rules are read.
- ipt_cmd=iptKey " -t mangle -j RETURN -s " arp_ip
- system(ipt_cmd " -C RRDIPT_FORWARD 2>/dev/null || " ipt_cmd " -A RRDIPT_FORWARD")
- ipt_cmd=iptKey " -t mangle -j RETURN -d " arp_ip
- system(ipt_cmd " -C RRDIPT_FORWARD 2>/dev/null || " ipt_cmd " -A RRDIPT_FORWARD")
-}
-
-function delRule(arp_ip, ipt_cmd) {
- ipt_cmd=iptKey " -t mangle -D RRDIPT_FORWARD -j RETURN "
- system(ipt_cmd "-s " arp_ip " 2>/dev/null")
- system(ipt_cmd "-d " arp_ip " 2>/dev/null")
-}
-
-function total(i) {
- return(bw[i "/in"] + bw[i "/out"])
-}
-
-BEGIN {
- if (ipv6) {
- iptNF = 8
- iptKey = "ip6tables"
- } else {
- iptNF = 9
- iptKey = "iptables"
- }
-}
-
-/^#/ { # get DB filename
- FS = ","
- dbFile = FILENAME
- next
-}
-
-# data from database; first file
-ARGIND==1 { #!@todo this doesn't help if the DB file is empty.
- lb=$1
-
- if (lb !~ "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$") next
-
- if (!(lb in mac)) {
- mac[lb] = $1
- ip[lb] = $2
- inter[lb] = $3
- speed[lb "/in"] = 0
- speed[lb "/out"]= 0
- bw[lb "/in"] = $6
- bw[lb "/out"] = $7
- firstDate[lb] = $9
- lastDate[lb] = $10
- ignore[lb] = 1
- } else {
- if ($9 < firstDate[lb])
- firstDate[lb] = $9
- if ($10 > lastDate[lb]) {
- ip[lb] = $2
- inter[lb] = $3
- lastDate[lb] = $10
- }
- bw[lb "/in"] += $6
- bw[lb "/out"] += $7
- ignore[lb] = 0
- }
- next
-}
-
-# not triggered on the first file
-FNR==1 {
- FS=" "
- if(ARGIND == 2) next
-}
-
-# arp: ip hw flags hw_addr mask device
-ARGIND==2 {
- #!@todo regex match IPs and MACs for sanity
- if (ipv6) {
- statFlag= ($4 != "FAILED" && $4 != "INCOMPLETE")
- macAddr = $5
- hwIF = $3
- } else {
- statFlag= ($3 != "0x0")
- macAddr = $4
- hwIF = $6
- }
-
- lb=$1
- if (hwIF != wanIF && statFlag && macAddr ~ "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$") {
- hosts[lb] = 1
- arp_mac[lb] = macAddr
- arp_ip[lb] = $1
- arp_inter[lb] = hwIF
- arp_bw[lb "/in"] = 0
- arp_bw[lb "/out"] = 0
- arp_firstDate[lb] = systime()
- arp_lastDate[lb] = arp_firstDate[lb]
- arp_ignore[lb] = 1
- }
- next
-}
-
-#!@todo could use mangle chain totals or tailing "unnact" rules to
-# account for data for new hosts from their first presence on the
-# network to rule creation. The "unnact" rules would have to be
-# maintained at the end of the list, and new rules would be inserted
-# at the top.
-ARGIND==3 && NF==iptNF && $1!="pkts" { # iptables input
- if (ipv6) {
- lfn = 5
- tag = "::/0"
- } else {
- lfn = 6
- tag = "0.0.0.0/0"
- }
-
- if ($(lfn) != "*") {
- m = $(lfn)
- n = m "/in"
- } else if ($(++lfn) != "*") {
- m = $(lfn)
- n = m "/out"
- } else if ($(++lfn) != tag) {
- m = $(lfn)
- n = m "/out"
- } else { # $(++lfn) != tag
- m = $(++lfn)
- n = m "/in"
- }
-
- if (mode == "diff" || mode == "noUpdate") print n, $2
- if (mode != "noUpdate") {
- if (inInterfaces(m)) { # if label is an interface
- if (!(m in arp_mac)) {
- cmd = "cat /sys/class/net/" m "/address"
- cmd | getline arp_mac[m]
- close(cmd)
-
- if (length(arp_mac[m]) == 0) arp_mac[m] = "00:00:00:00:00:00"
-
- arp_ip[m] = "NA"
- arp_inter[m] = m
- arp_bw[m "/in"] = 0
- arp_bw[m "/out"] = 0
- arp_firstDate[m] = systime()
- arp_lastDate[m] = arp_firstDate[m]
- arp_ignore[lb] = 1
- }
- } else {
- if (!(m in arp_mac)) hosts[m] = 0
- else delete hosts[m]
- }
-
- if ($2 > 0) {
- arp_bw[n] = $2
- arp_lastDate[m] = systime()
- arp_ignore[m] = 0
- }
- }
-}
-
-END {
- if (mode == "noUpdate") exit
-
- for (i in arp_ip) {
- lb = arp_mac[i]
- if (!arp_ignore[i] || !(lb in mac)) {
- ignore[lb] = 0
-
- if (lb in mac) {
- bw[lb "/in"] += arp_bw[i "/in"]
- bw[lb "/out"] += arp_bw[i "/out"]
- lastDate[lb] = arp_lastDate[i]
- } else {
- bw[lb "/in"] = arp_bw[i "/in"]
- bw[lb "/out"] = arp_bw[i "/out"]
- firstDate[lb] = arp_firstDate[i]
- lastDate[lb] = arp_lastDate[i]
- }
- mac[lb] = arp_mac[i]
- ip[lb] = arp_ip[i]
- inter[lb] = arp_inter[i]
-
- if (interval != 0) {
- speed[lb "/in"] = int(arp_bw[i "/in"] / interval)
- speed[lb "/out"]= int(arp_bw[i "/out"] / interval)
- }
- }
- }
-
- close(dbFile)
- for (i in mac) {
- if (!ignore[i]) {
- print "#mac,ip,iface,speed_in,speed_out,in,out,total,first_date,last_date" > dbFile
- OFS=","
- for (i in mac)
- print mac[i], ip[i], inter[i], speed[i "/in"], speed[i "/out"], bw[i "/in"], bw[i "/out"], total(i), firstDate[i], lastDate[i] > dbFile
- close(dbFile)
- break
- }
- }
-
- # for hosts without rules
- for (i in hosts)
- if (hosts[i]) newRule(i)
- else delRule(i)
-}
diff --git a/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/wrtbwmon b/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/wrtbwmon
deleted file mode 100755
index 91089a1eee..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/wrtbwmon
+++ /dev/null
@@ -1,460 +0,0 @@
-#!/bin/sh
-#
-
-# Default input parameters for wrtbwmon.
-runMode=0
-Monitor46=4
-
-# Some parameters for monitor process.
-for46=
-updatePID=
-logFile=/var/log/wrtbwmon.log
-lockFile=/var/lock/wrtbwmon.lock
-pidFile=/var/run/wrtbwmon.pid
-tmpDir=/var/tmp/wrtbwmon
-interval4=0
-interval6=0
-
-# Debug parameters for readDB.awk.
-mode=
-DEBUG=
-
-# Constant parameter for wrtbwmon.
-binDir=/usr/sbin
-dataDir=/usr/share/wrtbwmon
-
-networkFuncs=/lib/functions/network.sh
-uci=`which uci 2>/dev/null`
-nslookup=`which nslookup 2>/dev/null`
-nvram=`which nvram 2>/dev/null`
-
-chains='INPUT OUTPUT FORWARD'
-interfaces='eth0 tun0 br-lan' # in addition to detected WAN
-
-# DNS server for reverse lookups provided in "DNS".
-# don't perform reverse DNS lookups by default
-DO_RDNS=${DNS-}
-
-header="#mac,ip,iface,speed_in,speed_out,in,out,total,first_date,last_date"
-
-createDbIfMissing() {
- [ ! -f "$DB" ] && echo $header > "$DB"
- [ ! -f "$DB6" ] && echo $header > "$DB6"
-}
-
-checkDbArg() {
- [ -z "$DB" ] && echo "ERROR: Missing argument 2 (database file)" && exit 1
-}
-
-checkDB() {
- [ ! -f "$DB" ] && echo "ERROR: $DB does not exist" && exit 1
- [ ! -w "$DB" ] && echo "ERROR: $DB is not writable" && exit 1
- [ ! -f "$DB6" ] && echo "ERROR: $DB6 does not exist" && exit 1
- [ ! -w "$DB6" ] && echo "ERROR: $DB6 is not writable" && exit 1
-}
-
-checkWAN() {
- [ -z "$1" ] && echo "Warning: failed to detect WAN interface."
-}
-
-lookup() {
- local MAC=$1
- local IP=$2
- local userDB=$3
- local USERSFILE=
- local USER=
- for USERSFILE in $userDB /tmp/dhcp.leases /tmp/dnsmasq.conf /etc/dnsmasq.conf /etc/hosts; do
- [ -e "$USERSFILE" ] || continue
-
- case $USERSFILE in
- /tmp/dhcp.leases )
- USER=$(grep -i "$MAC" $USERSFILE | cut -f4 -s -d' ')
- ;;
- /etc/hosts )
- USER=$(grep "^$IP " $USERSFILE | cut -f2 -s -d' ')
- ;;
- * )
- USER=$(grep -i "$MAC" "$USERSFILE" | cut -f2 -s -d,)
- ;;
- esac
-
- [ "$USER" = "*" ] && USER=
- [ -n "$USER" ] && break
-
- done
-
- if [ -n "$DO_RDNS" -a -z "$USER" -a "$IP" != "NA" -a -n "$nslookup" ]; then
- USER=`$nslookup $IP $DNS | awk '!/server can/{if($4){print $4; exit}}' | sed -re 's/[.]$//'`
- fi
-
- [ -z "$USER" ] && USER=${MAC}
- echo $USER
-}
-
-detectIF() {
- local IF=
- if [ -f "$networkFuncs" ]; then
- IF=`. $networkFuncs; network_get_device netdev $1; echo $netdev`
- [ -n "$IF" ] && echo $IF && return
- fi
-
- if [ -n "$uci" -a -x "$uci" ]; then
- IF=`$uci get network.${1}.ifname 2>/dev/null`
- [ $? -eq 0 -a -n "$IF" ] && echo $IF && return
- fi
-
- if [ -n "$nvram" -a -x "$nvram" ]; then
- IF=`$nvram get ${1}_ifname 2>/dev/null`
- [ $? -eq 0 -a -n "$IF" ] && echo $IF && return
- fi
-}
-
-detectLAN() {
- [ -e /sys/class/net/br-lan ] && echo br-lan && return
- local lan=$(detectIF lan)
- [ -n "$lan" ] && echo $lan && return
-}
-
-detectWAN() {
- local wan=$(detectIF wan)
- [ -n "$wan" ] && echo $wan && return
- wan=$(ip route show 2>/dev/null | grep default | sed -re '/^default/ s/default.*dev +([^ ]+).*/\1/')
- [ -n "$wan" ] && echo $wan && return
- [ -f "$networkFuncs" ] && wan=$(. $networkFuncs; network_find_wan wan; echo $wan)
- [ -n "$wan" ] && echo $wan && return
-}
-
-lockFunc() {
- #Realize the lock function by busybox lock or flock command.
- # if !(lock -n $lockFile) >/dev/null 2>&1; then
- # exit 1
- # fi
- #The following lock method is realized by other's function.
-
- local attempts=0
- local flag=0
-
- while [ "$flag" = 0 ]; do
- local tempfile=$(mktemp $tmpDir/lock.XXXXXX)
- ln $tempfile $lockFile >/dev/null 2>&1 && flag=1
- rm $tempfile
-
- if [ "$flag" = 1 ]; then
- [ -n "$DEBUG" ] && echo ${updatePID} "got lock after $attempts attempts"
- flag=1
- else
- sleep 1
- attempts=$(($attempts+1))
- [ -n "$DEBUG" ] && echo ${updatePID} "The $attempts attempts."
- [ "$attempts" -ge 10 ] && exit
- fi
- done
-}
-
-unlockFunc() {
- #Realize the lock function by busybox lock or flock command.
- # lock -u $lockFile
- # rm -f $lockFile
- # [ -n "$DEBUG" ] && echo ${updatePID} "released lock"
- #The following lock method is realized by other's function.
-
- rm -f $lockFile
- [ -n "$DEBUG" ] && echo ${updatePID} "released lock"
-}
-
-# chain
-newChain() {
- local chain=$1
- local ipt=$2
- # Create the RRDIPT_$chain chain (it doesn't matter if it already exists).
-
- $ipt -t mangle -N RRDIPT_$chain 2> /dev/null
-
- # Add the RRDIPT_$chain CHAIN to the $chain chain if not present
- $ipt -t mangle -C $chain -j RRDIPT_$chain 2>/dev/null
- if [ $? -ne 0 ]; then
- [ -n "$DEBUG" ] && echo "DEBUG: $ipt chain misplaced, recreating it..."
- $ipt -t mangle -I $chain -j RRDIPT_$chain
- fi
-}
-
-# chain tun
-newRuleIF() {
- local chain=$1
- local IF=$2
- local ipt=$3
- local cmd=
-
- if [ "$chain" = "OUTPUT" ]; then
- cmd="$ipt -t mangle -o $IF -j RETURN"
- elif [ "$chain" = "INPUT" ]; then
- cmd="$ipt -t mangle -i $IF -j RETURN"
- fi
- [ -n "$cmd" ] && eval $cmd " -C RRDIPT_$chain 2>/dev/null" || eval $cmd " -A RRDIPT_$chain"
-}
-
-publish() {
- # sort DB
- # busybox sort truncates numbers to 32 bits
- grep -v '^#' $DB | awk -F, '{OFS=","; a=sprintf("%f",$6/1e6); $6=""; print a,$0}' | tr -s ',' | sort -rn | awk -F, '{OFS=",";$1=sprintf("%f",$1*1e6);print}' > $tmpDir/sorted_${updatePID}.tmp
-
- # create HTML page
- local htmPage="$tmpDir/${pb_html##*/}"
- rm -f $htmPage
- cp $dataDir/usage.htm1 $htmPage
-
- while IFS=, read PEAKUSAGE_IN MAC IP IFACE SPEED_IN SPEED_OUT PEAKUSAGE_OUT TOTAL FIRSTSEEN LASTSEEN
- do
- echo "
-new Array(\"$(lookup $MAC $IP $user_def)\",\"$MAC\",\"$IP\",$SPEED_IN,$SPEED_OUT,
-$PEAKUSAGE_IN,$PEAKUSAGE_OUT,$TOTAL,\"$FIRSTSEEN\",\"$LASTSEEN\")," >> $htmPage
- done < $tmpDir/sorted_${updatePID}.tmp
- echo "0);" >> $htmPage
-
- sed "s/(date)/`date`/" < $dataDir/usage.htm2 >> $htmPage
- mv $htmPage "$pb_html"
-}
-
-updatePrepare() {
- checkDbArg
- createDbIfMissing
- checkDB
- [ -e $tmpDir ] || mkdir -p $tmpDir
-
- for46="$Monitor46"
- local timeNow=$(cat /proc/uptime | awk '{print $1}')
-
- if [ -e "$logFile" ]; then
- local timeLast4=$(awk -F'[: ]+' '/ipv4/{print $2}' "$logFile")
- local timeLast6=$(awk -F'[: ]+' '/ipv6/{print $2}' "$logFile")
- interval4=$(awk -v now=$timeNow -v last=$timeLast4 'BEGIN{print (now-last)}');
- interval6=$(awk -v now=$timeNow -v last=$timeLast6 'BEGIN{print (now-last)}');
-
- for ii in 4 6; do
- [[ -n "$(echo $for46 | grep ${ii})" ]] && {
- if [[ "$(eval echo \$interval${ii})" \> "0.9" ]]; then
- sed -i "s/^ipv${ii}: [0-9\.]\{1,\}/ipv${ii}: $timeNow/ig" "$logFile"
- else
- for46=`echo "$for46" | sed "s/${ii}//g"`
- fi
- }
- done
- else
- echo -e "ipv4: $timeNow\nipv6: $timeNow" >"$logFile"
- fi
- return 0
-}
-
-update() {
- updatePID=$( sh -c 'echo $PPID' )
-
- lockFunc
-
- local wan=$(detectWAN)
- checkWAN $wan
- interfaces="$interfaces $wan"
-
- [ "$for46" = 4 ] && IPT='iptables'
- [ "$for46" = 6 ] && IPT='ip6tables'
- [ "$for46" = 46 ] && IPT='iptables ip6tables'
-
- for ii in $IPT ; do
- if [ -z "$( ${ii}-save | grep RRDIPT )" ]; then
-
- for chain in $chains; do
- newChain $chain $ii
- done
-
- # track local data
- for chain in INPUT OUTPUT; do
- for interface in $interfaces; do
- [ -n "$interface" ] && [ -e "/sys/class/net/$interface" ] && newRuleIF $chain $interface $ii
- done
- done
- fi
- # this will add rules for hosts in arp table
- > $tmpDir/${ii}_${updatePID}.tmp
-
- for chain in $chains; do
- $ii -nvxL RRDIPT_$chain -t mangle -Z >> $tmpDir/${ii}_${updatePID}.tmp
- done
- done
-
- [ -f $tmpDir/iptables_${updatePID}.tmp ] && (
- awk -v mode="$mode" -v interfaces="$interfaces" -v wanIF="$wan" -v interval=$interval4 \
- -v ipv6="0" -f $binDir/readDB.awk \
- $DB \
- /proc/net/arp \
- $tmpDir/iptables_${updatePID}.tmp
- )
-
- [ -f $tmpDir/ip6tables_${updatePID}.tmp ] && (
- echo "This file is geneated by 'ip -6 neigh'" > $tmpDir/ip6addr_${updatePID}.tmp
- `ip -6 neigh >> $tmpDir/ip6addr_${updatePID}.tmp`;
-
- awk -v mode="$mode" -v interfaces="$interfaces" -v wanIF="$wan" -v interval=$interval6 \
- -v ipv6="1" -f $binDir/readDB.awk \
- "$DB6" \
- $tmpDir/ip6addr_${updatePID}.tmp \
- $tmpDir/ip6tables_${updatePID}.tmp
- )
-
- [ "$Monitor46" = 46 ] && (
- cp $DB $DB46
- cat $DB6 >> $DB46
- awk -f $binDir/readDB.awk "$DB46"
- )
-
- [ -n "$pb_html" ] && publish
-
- rm -f $tmpDir/*_${updatePID}.tmp
- unlockFunc
-}
-
-renamefile() {
- local base=$(basename -- "$1")
- local ext=$([ -z "${base/*.*/}" ] && echo ".${base##*.}" || echo '')
- local base="${base%.*}"
- echo "$(dirname $1)/${base}$2$ext" && return
-}
-
-ending() {
- iptables-save | grep -v RRDIPT | iptables-restore
- ip6tables-save | grep -v RRDIPT | ip6tables-restore
-
- if checkPid $pidFile; then
- local pid=$( cat $pidFile )
- rm -rf $lockFile $logFile $pidFile $tmpDir/*
- kill -9 $pid >> /dev/null 2>&1
- fi
- echo "exit!!"
-}
-
-checkPid() {
- [ -e "$1" ] && local pid=$(cat $1) || return 1
- [ -d "/proc/$pid" ] && {
- [ -n "$( cat /proc/$pid/cmdline | grep wrtbwmon )" ] && return 0
- }
- return 1
-}
-
-sleepProcess() {
- sleep 1m
- kill -CONT $1 >>/dev/null 2>&1
-}
-
-loop() {
- trap 'ending' INT TERM HUP QUIT
- if checkPid $pidFile; then
- echo "Another wrtbwmon is on running!!!"
- else
- local loopPID=$( sh -c 'echo $PPID' )
- local SPID=
- echo $loopPID > $pidFile
- while true ;do
- [ -n "$SPID" ] && kill -9 $SPID >>/dev/null 2>&1
- sleepProcess $loopPID &
- SPID=$!
- updatePrepare && update
- kill -STOP $loopPID >>/dev/null 2>&1
- done
- fi
- trap INT TERM HUP QUIT
-}
-
-tips() {
- echo \
-"Usage: $0 [options...]
-Options:
- -k Exit the wrtbwmon!
- -f dbfile Set the DB file path
- -u usrfile Set the user_def file path
- -p htmlfile Set the publish htm file path
- -d Enter the foreground mode.
- -D Enter the daemo mode.
- -4 Listen to ipv4 only.
- -6 Listen to ipv6 only.
- -46 Listen to ipv4 and ipv6.
-
-Note: [user_file] is an optional file to match users with MAC addresses.
- Its format is \"00:MA:CA:DD:RE:SS,username\", with one entry per line."
-}
-
-############################################################
-
-while [ $# != 0 ];do
- case $1 in
- "-k" )
- /etc/init.d/wrtbwmon stop
- exit 0
- ;;
- "-f" )
- shift
- if [ $# -gt 0 ];then
- DB=$1
- DB6="$(renamefile $DB .6)"
- DB46="$(renamefile $DB .46)"
- else
- echo "No db file path seted, exit!!"
- exit 1
- fi
- ;;
- "-u")
- shift
- if [ $# -gt 0 ];then
- user_def=$1
- else
- echo "No user define file path seted, exit!!"
- exit 1
- fi
- ;;
-
- "-p")
- shift
- if [ $# -gt 0 ];then
- pb_html=$1
- else
- echo "No publish html file path seted, exit!!"
- exit 1
- fi
- ;;
-
- "-d")
- runMode=1
- ;;
-
- "-D")
- runMode=2
- ;;
-
- "-4")
- Monitor46=4
- ;;
-
- "-6")
- Monitor46=6
- ;;
-
- "-46")
- Monitor46=46
- ;;
-
- "&&" | "||" | ";")
- break
- ;;
-
- "*")
- tips
- ;;
- esac
-
- shift
-done
-
-if [ "$runMode" = '1' ]; then
- loop
-elif [ "$runMode" = '2' ]; then
- loop >>/dev/null 2>&1 &
-else
- updatePrepare && update
-fi
diff --git a/package/ctcgfw/luci-app-wrtbwmon/usr/share/wrtbwmon/usage.htm1 b/package/ctcgfw/luci-app-wrtbwmon/usr/share/wrtbwmon/usage.htm1
deleted file mode 100644
index c469cb7d91..0000000000
--- a/package/ctcgfw/luci-app-wrtbwmon/usr/share/wrtbwmon/usage.htm1
+++ /dev/null
@@ -1,34 +0,0 @@
-Traffic
-
-
Total Usage:
-
-
-
User
-
Down Speed
-
Up Speed
-
Download
-
Upload
-
Total
-
First seen
-
Last seen
-
-
- This page was generated on (date)
-
diff --git a/package/lean/luci-app-wrtbwmon/Makefile b/package/lean/luci-app-wrtbwmon/Makefile
new file mode 100644
index 0000000000..412b35fe52
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/Makefile
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 Openwrt.org
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for Wrtbwmon
+LUCI_DEPENDS:=+luci-app-nlbwmon
+LUCI_PKGARCH:=all
+PKG_VERSION:=1.0
+PKG_RELEASE:=7
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
+
diff --git a/package/lean/luci-app-wrtbwmon/htdocs/luci-static/wrtbwmon.js b/package/lean/luci-app-wrtbwmon/htdocs/luci-static/wrtbwmon.js
new file mode 100644
index 0000000000..36812e723b
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/htdocs/luci-static/wrtbwmon.js
@@ -0,0 +1,562 @@
+var wrt = {
+ // variables for auto-update, interval is in seconds
+ scheduleTimeout: undefined,
+ updateTimeout: undefined,
+ isScheduled: true,
+ interval: 5,
+ // option on whether to show per host sub-totals
+ perHostTotals: false,
+ // variables for sorting
+ sortData: {
+ column: 7,
+ elId: 'thTotal',
+ dir: 'desc',
+ cache: {}
+ }
+};
+
+(function () {
+ var oldDate, oldValues = [];
+
+ // find base path
+ var re = /(.*?admin\/nlbw\/[^/]+)/;
+ var basePath = window.location.pathname.match(re)[1];
+
+ //----------------------
+ // HELPER FUNCTIONS
+ //----------------------
+
+ /**
+ * Human readable text for size
+ * @param size
+ * @returns {string}
+ */
+ function getSize(size) {
+ var prefix = [' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z'];
+ var precision, base = 1000, pos = 0;
+ while (size > base) {
+ size /= base;
+ pos++;
+ }
+ if (pos > 2) precision = 1000; else precision = 1;
+ return (Math.round(size * precision) / precision) + ' ' + prefix[pos] + 'B';
+ }
+
+ /**
+ * Human readable text for date
+ * @param date
+ * @returns {string}
+ */
+ function dateToString(date) {
+ return date.toString().substring(0, 24);
+ }
+
+ /**
+ * Gets the string representation of the date received from BE
+ * @param value
+ * @returns {*}
+ */
+ function getDateString(value) {
+ var tmp = value.split('_'),
+ str = tmp[0].split('-').reverse().join('-') + 'T' + tmp[1];
+ return dateToString(new Date(str));
+ }
+
+ /**
+ * Create a `tr` element with content
+ * @param content
+ * @returns {string}
+ */
+ function createTR(content) {
+ var res = '
';
+ res += content;
+ res += '
';
+
+ return res;
+ }
+
+ /**
+ * Create a `th` element with content and options
+ * @param content
+ * @param opts
+ * @returns {string}
+ */
+ function createTH(content, opts) {
+ opts = opts || {};
+ var res = '
';
+ res += content;
+ res += '
';
+ return res;
+ }
+
+ /**
+ * Create a `td` element with content and options
+ * @param content
+ * @param opts
+ * @returns {string}
+ */
+ function createTD(content, opts) {
+ opts = opts || {};
+ var res = '
';
+ res += content;
+ res += '
';
+ return res;
+ }
+
+ /**
+ * Returns true if obj is instance of Array
+ * @param obj
+ * @returns {boolean}
+ */
+ function isArray(obj) {
+ return obj instanceof Array;
+ }
+
+ //----------------------
+ // END HELPER FUNCTIONS
+ //----------------------
+
+ /**
+ * Handle the error that happened during the call to the BE
+ */
+ function handleError() {
+ // TODO handle errors
+ // var message = 'Something went wrong...';
+ }
+
+ /**
+ * Handle the new `values` that were received from the BE
+ * @param values
+ * @returns {string}
+ */
+ function handleValues(values) {
+ if (!isArray(values)) return '';
+
+ // find data and totals
+ var res = parseValues(values);
+ var data = res[0];
+ var totals = res[1];
+
+ // aggregate (sub-total) by hostname (or MAC address) after the global totals are computed, before sort and display
+ aggregateHostTotals(data);
+
+ // store them in cache for quicker re-rendering
+ wrt.sortData.cache.data = data;
+ wrt.sortData.cache.totals = totals;
+
+ renderTableData(data, totals);
+ }
+
+ /**
+ * Renders the table body
+ * @param data
+ * @param totals
+ */
+ function renderTableData(data, totals) {
+ // sort data
+ data.sort(sortingFunction);
+
+ // display data
+ document.getElementById('tableBody').innerHTML = getDisplayData(data, totals);
+
+ // set sorting arrows
+ var el = document.getElementById(wrt.sortData.elId);
+ if (el) {
+ el.innerHTML = el.innerHTML + (wrt.sortData.dir === 'desc' ? '▼' : '▲');
+ }
+
+ // register table events
+ registerTableEventHandlers();
+ }
+
+ /**
+ * Parses the values and returns a data array, where each element in the data array is an array with two elements,
+ * and a totals array, that holds aggregated values for each column.
+ * The first element of each row in the data array, is the HTML output of the row as a `tr` element
+ * and the second is the actual data:
+ * [ result, data ]
+ * @param values The `values` array
+ * @returns {Array}
+ */
+ function parseValues(values) {
+ var data = [], totals = [0, 0, 0, 0, 0];
+ for (var i = 0; i < values.length; i++) {
+ var d = parseValueRow(values[i]);
+ if (d[1]) {
+ data.push(d);
+ // get totals
+ for (var j = 0; j < totals.length; j++) {
+ totals[j] += d[1][3 + j];
+ }
+ }
+ }
+
+ return [data, totals];
+ }
+
+ /**
+ * Parse each row in the `values` array and return an array with two elements.
+ * The first element is the HTML output of the row as a `tr` element and the second is the actual data
+ * [ result, data ]
+ * @param data A row from the `values` array
+ * @returns {[ string, [] ]}
+ */
+ function parseValueRow(data) {
+ // check if data is array
+ if (!isArray(data)) return [''];
+
+ // find old data
+ var oldData;
+ for (var i = 0; i < oldValues.length; i++) {
+ var cur = oldValues[i];
+ // compare mac addresses and ip addresses
+ if (oldValues[i][1] === data[1] && oldValues[i][2] === data[2]) {
+ oldData = cur;
+ break;
+ }
+ }
+
+ // find download and upload speeds
+ var dlSpeed = 0, upSpeed = 0;
+ if (oldData) {
+ var now = new Date(),
+ seconds = (now - oldDate) / 1000;
+ dlSpeed = (data[3] - oldData[3]) / seconds;
+ upSpeed = (data[4] - oldData[4]) / seconds;
+ }
+
+ // create rowData
+ var rowData = [];
+ for (var j = 0; j < data.length; j++) {
+ rowData.push(data[j]);
+ if (j === 2) {
+ rowData.push(dlSpeed, upSpeed);
+ }
+ }
+
+ // create displayData
+ var displayData = [
+ createTD(data[0] + ' ' + data[2], {title: data[1]}),
+ createTD(getSize(dlSpeed) + '/s', {right: true}),
+ createTD(getSize(upSpeed) + '/s', {right: true}),
+ createTD(getSize(data[3]), {right: true}),
+ createTD(getSize(data[4]), {right: true}),
+ createTD(getSize(data[5]), {right: true}),
+ createTD(getDateString(data[6])),
+ createTD(getDateString(data[7]))
+ ];
+
+ // display row data
+ var result = '';
+ for (var k = 0; k < displayData.length; k++) {
+ result += displayData[k];
+ }
+ result = createTR(result);
+ return [result, rowData];
+ }
+
+ /**
+ * Creates the HTML output based on the `data` and `totals` inputs
+ * @param data
+ * @param totals
+ * @returns {string} HTML output
+ */
+ function getDisplayData(data, totals) {
+ var result =
+ createTH('客户端', {id: 'thClient'}) +
+ createTH('下载带宽', {id: 'thDownload'}) +
+ createTH('上传带宽', {id: 'thUpload'}) +
+ createTH('总下载流量', {id: 'thTotalDown'}) +
+ createTH('总上传流量', {id: 'thTotalUp'}) +
+ createTH('流量合计', {id: 'thTotal'}) +
+ createTH('首次上线时间', {id: 'thFirstSeen'}) +
+ createTH('最后上线时间', {id: 'thLastSeen'});
+ result = createTR(result);
+ for (var k = 0; k < data.length; k++) {
+ result += data[k][0];
+ }
+ var totalsRow = createTH('总计');
+ for (var m = 0; m < totals.length; m++) {
+ var t = totals[m];
+ totalsRow += createTD(getSize(t) + (m < 2 ? '/s' : ''), {right: true});
+ }
+ result += createTR(totalsRow);
+ return result;
+ }
+
+ /**
+ * Calculates per host sub-totals and adds them in the data input
+ * @param data The data input
+ */
+ function aggregateHostTotals(data) {
+ if (!wrt.perHostTotals) return;
+
+ var curHost = 0, insertAt = 1;
+ while (curHost < data.length && insertAt < data.length) {
+ // grab the current hostname/mac, and walk the data looking for rows with the same host/mac
+ var hostName = data[curHost][1][0].toLowerCase();
+ for (var k = curHost + 1; k < data.length; k++) {
+ if (data[k][1][0].toLowerCase() === hostName) {
+ // this is another row for the same host, group it with any other rows for this host
+ data.splice(insertAt, 0, data.splice(k, 1)[0]);
+ insertAt++;
+ }
+ }
+
+ // if we found more than one row for the host, add a subtotal row
+ if (insertAt > curHost + 1) {
+ var hostTotals = [data[curHost][1][0], '', '', 0, 0, 0, 0, 0];
+ for (var i = curHost; i < insertAt && i < data.length; i++) {
+ for (var j = 3; j < hostTotals.length; j++) {
+ hostTotals[j] += data[i][1][j];
+ }
+ }
+ var hostTotalRow = createTH(data[curHost][1][0] + ' (host total)', {title: data[curHost][1][1]});
+ for (var m = 3; m < hostTotals.length; m++) {
+ var t = hostTotals[m];
+ hostTotalRow += createTD(getSize(t) + (m < 5 ? '/s' : ''), {right: true});
+ }
+ hostTotalRow = createTR(hostTotalRow);
+ data.splice(insertAt, 0, [hostTotalRow, hostTotals]);
+ }
+ curHost = insertAt;
+ insertAt = curHost + 1;
+ }
+ }
+
+ /**
+ * Sorting function used to sort the `data`. Uses the global sort settings
+ * @param x first item to compare
+ * @param y second item to compare
+ * @returns {number} 1 for desc, -1 for asc, 0 for equal
+ */
+ function sortingFunction(x, y) {
+ // get data from global variable
+ var sortColumn = wrt.sortData.column, sortDirection = wrt.sortData.dir;
+ var a = x[1][sortColumn];
+ var b = y[1][sortColumn];
+ if (a === b) {
+ return 0;
+ } else if (sortDirection === 'desc') {
+ return a < b ? 1 : -1;
+ } else {
+ return a > b ? 1 : -1;
+ }
+ }
+
+ /**
+ * Sets the relevant global sort variables and re-renders the table to apply the new sorting
+ * @param elId
+ * @param column
+ */
+ function setSortColumn(elId, column) {
+ if (column === wrt.sortData.column) {
+ // same column clicked, switch direction
+ wrt.sortData.dir = wrt.sortData.dir === 'desc' ? 'asc' : 'desc';
+ } else {
+ // change sort column
+ wrt.sortData.column = column;
+ // reset sort direction
+ wrt.sortData.dir = 'desc';
+ }
+ wrt.sortData.elId = elId;
+
+ // render table data from cache
+ renderTableData(wrt.sortData.cache.data, wrt.sortData.cache.totals);
+ }
+
+ /**
+ * Registers the table events handlers for sorting when clicking the column headers
+ */
+ function registerTableEventHandlers() {
+ // note these ordinals are into the data array, not the table output
+ document.getElementById('thClient').addEventListener('click', function () {
+ setSortColumn(this.id, 0); // hostname
+ });
+ document.getElementById('thDownload').addEventListener('click', function () {
+ setSortColumn(this.id, 3); // dl speed
+ });
+ document.getElementById('thUpload').addEventListener('click', function () {
+ setSortColumn(this.id, 4); // ul speed
+ });
+ document.getElementById('thTotalDown').addEventListener('click', function () {
+ setSortColumn(this.id, 5); // total down
+ });
+ document.getElementById('thTotalUp').addEventListener('click', function () {
+ setSortColumn(this.id, 6); // total up
+ });
+ document.getElementById('thTotal').addEventListener('click', function () {
+ setSortColumn(this.id, 7); // total
+ });
+ }
+
+ /**
+ * Fetches and handles the updated `values` from the BE
+ * @param once If set to true, it re-schedules itself for execution based on selected interval
+ */
+ function receiveData(once) {
+ var ajax = new XMLHttpRequest();
+ ajax.onreadystatechange = function () {
+ // noinspection EqualityComparisonWithCoercionJS
+ if (this.readyState == 4 && this.status == 200) {
+ var re = /(var values = new Array[^;]*;)/,
+ match = ajax.responseText.match(re);
+ if (!match) {
+ handleError();
+ } else {
+ // evaluate values
+ eval(match[1]);
+ //noinspection JSUnresolvedVariable
+ var v = values;
+ if (!v) {
+ handleError();
+ } else {
+ handleValues(v);
+ // set old values
+ oldValues = v;
+ // set old date
+ oldDate = new Date();
+ document.getElementById('updated').innerHTML = '数据更新时间 ' + dateToString(oldDate);
+ }
+ }
+ var int = wrt.interval;
+ if (!once && int > 0) reschedule(int);
+ }
+ };
+ ajax.open('GET', basePath + '/usage_data', true);
+ ajax.send();
+ }
+
+ /**
+ * Registers DOM event listeners for user interaction
+ */
+ function addEventListeners() {
+ document.getElementById('intervalSelect').addEventListener('change', function () {
+ var int = wrt.interval = this.value;
+ if (int > 0) {
+ // it is not scheduled, schedule it
+ if (!wrt.isScheduled) {
+ reschedule(int);
+ }
+ } else {
+ // stop the scheduling
+ stopSchedule();
+ }
+ });
+
+ document.getElementById('resetDatabase').addEventListener('click', function () {
+ if (confirm('This will delete the database file. Are you sure?')) {
+ var ajax = new XMLHttpRequest();
+ ajax.onreadystatechange = function () {
+ // noinspection EqualityComparisonWithCoercionJS
+ if (this.readyState == 4 && this.status == 204) {
+ location.reload();
+ }
+ };
+ ajax.open('GET', basePath + '/usage_reset', true);
+ ajax.send();
+ }
+ });
+
+ document.getElementById('perHostTotals').addEventListener('change', function () {
+ wrt.perHostTotals = !wrt.perHostTotals;
+ });
+ }
+
+ //----------------------
+ // AUTO-UPDATE
+ //----------------------
+
+ /**
+ * Stop auto-update schedule
+ */
+ function stopSchedule() {
+ window.clearTimeout(wrt.scheduleTimeout);
+ window.clearTimeout(wrt.updateTimeout);
+ setUpdateMessage('');
+ wrt.isScheduled = false;
+ }
+
+ /**
+ * Start auto-update schedule
+ * @param seconds
+ */
+ function reschedule(seconds) {
+ wrt.isScheduled = true;
+ seconds = seconds || 60;
+ updateSeconds(seconds);
+ wrt.scheduleTimeout = window.setTimeout(receiveData, seconds * 1000);
+ }
+
+ /**
+ * Sets the text of the `#updating` element
+ * @param msg
+ */
+ function setUpdateMessage(msg) {
+ document.getElementById('updating').innerHTML = msg;
+ }
+
+ /**
+ * Updates the 'Updating in X seconds' message
+ * @param start
+ */
+ function updateSeconds(start) {
+ setUpdateMessage('倒数 ' + start + ' 秒后刷新.');
+ if (start > 0) {
+ wrt.updateTimeout = window.setTimeout(function () {
+ updateSeconds(start - 1);
+ }, 1000);
+ }
+ }
+
+ //----------------------
+ // END AUTO-UPDATE
+ //----------------------
+
+ /**
+ * Check for dependency, and if all is well, run callback
+ * @param cb Callback function
+ */
+ function checkForDependency(cb) {
+ var ajax = new XMLHttpRequest();
+ ajax.onreadystatechange = function () {
+ // noinspection EqualityComparisonWithCoercionJS
+ if (this.readyState == 4 && this.status == 200) {
+ // noinspection EqualityComparisonWithCoercionJS
+ if (ajax.responseText == "1") {
+ cb();
+ } else {
+ alert("wrtbwmon is not installed!");
+ }
+ }
+ };
+ ajax.open('GET', basePath + '/check_dependency', true);
+ ajax.send();
+ }
+
+ checkForDependency(function () {
+ // register events
+ addEventListeners();
+ // Main entry point
+ receiveData();
+ });
+
+})();
diff --git a/package/lean/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua b/package/lean/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua
new file mode 100644
index 0000000000..25909c846d
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua
@@ -0,0 +1,43 @@
+module("luci.controller.wrtbwmon", package.seeall)
+
+function index()
+ entry({"admin", "nlbw", "usage"}, alias("admin", "nlbw", "usage", "details"), _("Usage"), 60)
+ entry({"admin", "nlbw", "usage", "details"}, template("wrtbwmon"), _("Details"), 10).leaf=true
+ entry({"admin", "nlbw", "usage", "config"}, cbi("wrtbwmon/config"), _("Configuration"), 20).leaf=true
+ entry({"admin", "nlbw", "usage", "custom"}, form("wrtbwmon/custom"), _("User file"), 30).leaf=true
+ entry({"admin", "nlbw", "usage", "check_dependency"}, call("check_dependency")).dependent=true
+ entry({"admin", "nlbw", "usage", "usage_data"}, call("usage_data")).dependent=true
+ entry({"admin", "nlbw", "usage", "usage_reset"}, call("usage_reset")).dependent=true
+end
+
+function usage_database_path()
+ local cursor = luci.model.uci.cursor()
+ if cursor:get("wrtbwmon", "general", "persist") == "1" then
+ return "/etc/config/usage.db"
+ else
+ return "/tmp/usage.db"
+ end
+end
+
+function check_dependency()
+ local ret = "0"
+ if require("luci.model.ipkg").installed('iptables') then
+ ret = "1"
+ end
+ luci.http.prepare_content("text/plain")
+ luci.http.write(ret)
+end
+
+function usage_data()
+ local db = usage_database_path()
+ local publish_cmd = "wrtbwmon publish " .. db .. " /tmp/usage.htm /etc/config/wrtbwmon.user"
+ local cmd = "wrtbwmon update " .. db .. " && " .. publish_cmd .. " && cat /tmp/usage.htm"
+ luci.http.prepare_content("text/html")
+ luci.http.write(luci.sys.exec(cmd))
+end
+
+function usage_reset()
+ local db = usage_database_path()
+ local ret = luci.sys.call("wrtbwmon update " .. db .. " && rm " .. db)
+ luci.http.status(204)
+end
diff --git a/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua b/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua
new file mode 100644
index 0000000000..469d4f8f2c
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua
@@ -0,0 +1,18 @@
+local m = Map("wrtbwmon", translate("Details"))
+
+local s = m:section(NamedSection, "general", "wrtbwmon", translate("General settings"))
+
+local o = s:option(Flag, "persist", translate("Persist database"),
+ translate("Check this to persist the database file"))
+o.rmempty = false
+
+function o.write(self, section, value)
+ if value == '1' then
+ luci.sys.call("mv /tmp/usage.db /etc/config/usage.db")
+ elseif value == '0' then
+ luci.sys.call("mv /etc/config/usage.db /tmp/usage.db")
+ end
+ return Flag.write(self, section ,value)
+end
+
+return m
diff --git a/package/ctcgfw/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua b/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua
similarity index 69%
rename from package/ctcgfw/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua
rename to package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua
index 46baa33127..671879b4cc 100644
--- a/package/ctcgfw/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua
+++ b/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua
@@ -1,11 +1,10 @@
-local USER_FILE_PATH = "/etc/wrtbwmon.user"
+local USER_FILE_PATH = "/etc/config/wrtbwmon.user"
local fs = require "nixio.fs"
local f = SimpleForm("wrtbwmon",
translate("Usage - Custom User File"),
- translate("This file is used to match users with MAC addresses. "
- .. "Each line must have the following format: \"00:aa:bb:cc:ee:ff,username\"."))
+ translate("This file is used to match users with MAC addresses and it must have the following format: 00:aa:bb:cc:ee:ff,username"))
local o = f:field(Value, "_custom")
@@ -21,6 +20,4 @@ function o.write(self, section, value)
fs.writefile(USER_FILE_PATH, value)
end
-f.submit = translate("Submit")
-
return f
diff --git a/package/lean/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm b/package/lean/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm
new file mode 100644
index 0000000000..68713b1238
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm
@@ -0,0 +1,46 @@
+<%+header%>
+
<%=translate("Usage")%>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<%=translate("Loading...")%>
+
+
+
+<%+footer%>
diff --git a/package/lean/luci-app-wrtbwmon/po/zh-cn/wrtbwmon.po b/package/lean/luci-app-wrtbwmon/po/zh-cn/wrtbwmon.po
new file mode 100644
index 0000000000..3eb050e3f4
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/po/zh-cn/wrtbwmon.po
@@ -0,0 +1,56 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Usage"
+msgstr "实时流量监测"
+
+msgid "Details"
+msgstr "详细信息"
+
+msgid "Configuration"
+msgstr "配置"
+
+msgid "User file"
+msgstr "自定义主机信息"
+
+msgid "Usage - Configuration"
+msgstr "详细设置"
+
+msgid "General settings"
+msgstr "通用设置"
+
+msgid "Persist database"
+msgstr "写入数据库到硬盘"
+
+msgid "Check this to persist the database file"
+msgstr "把统计数据写入 /etc/config 中避免重启或者升级后丢失"
+
+msgid "Usage - Custom User File"
+msgstr "自定义MAC地址对应的主机名"
+
+msgid "This file is used to match users with MAC addresses and it must have the following format: 00:aa:bb:cc:ee:ff,username"
+msgstr "每一行的格式为 00:aa:bb:cc:ee:ff,username"
+
+msgid "Reset Database"
+msgstr "重置数据库"
+
+msgid "Auto Refresh Interval"
+msgstr "自动刷新间隔"
+
+msgid "Disabled"
+msgstr "禁用"
+
+msgid "Second"
+msgstr "秒"
+
+msgid "Seconds"
+msgstr "秒"
+
+msgid "Minutes"
+msgstr "分钟"
+
+msgid "Per-host Totals"
+msgstr "合并每一客户端数据"
+
+msgid "Loading..."
+msgstr "加载中..."
diff --git a/package/lean/luci-app-wrtbwmon/root/etc/config/wrtbwmon b/package/lean/luci-app-wrtbwmon/root/etc/config/wrtbwmon
new file mode 100644
index 0000000000..419270dbab
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/root/etc/config/wrtbwmon
@@ -0,0 +1,4 @@
+
+config wrtbwmon 'general'
+ option persist '0'
+
diff --git a/package/lean/luci-app-wrtbwmon/root/etc/init.d/wrtbwmon b/package/lean/luci-app-wrtbwmon/root/etc/init.d/wrtbwmon
new file mode 100755
index 0000000000..d57c09ae1d
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/root/etc/init.d/wrtbwmon
@@ -0,0 +1,22 @@
+#!/bin/sh /etc/rc.common
+#
+# start/stop wrtbwmon bandwidth monitor
+
+### BEGIN INIT INFO
+# Provides: wrtbwmon
+# Required-Start: $network $local_fs $remote_fs
+# Required-Stop: $local_fs $remote_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: iptables-based bandwidth monitor
+### END INIT INFO
+
+START=91
+
+start(){
+ /usr/sbin/wrtbwmon setup /tmp/usage.db
+}
+
+stop(){
+ /usr/sbin/wrtbwmon remove
+}
diff --git a/package/lean/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon b/package/lean/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon
new file mode 100755
index 0000000000..bcb5b337f3
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+ delete firewall.wrtbwmon
+ set firewall.wrtbwmon=include
+ set firewall.wrtbwmon.type=script
+ set firewall.wrtbwmon.path='/etc/wrtbwmon.include'
+ set firewall.wrtbwmon.reload=1
+ commit firewall
+EOF
+
+/etc/init.d/wrtbwmon enable
+/etc/init.d/wrtbwmon start
+exit 0
diff --git a/package/lean/luci-app-wrtbwmon/root/etc/wrtbwmon.include b/package/lean/luci-app-wrtbwmon/root/etc/wrtbwmon.include
new file mode 100755
index 0000000000..39bcf43a10
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/root/etc/wrtbwmon.include
@@ -0,0 +1 @@
+/etc/init.d/wrtbwmon restart >/dev/null 2>&1
diff --git a/package/lean/luci-app-wrtbwmon/root/usr/sbin/readDB.awk b/package/lean/luci-app-wrtbwmon/root/usr/sbin/readDB.awk
new file mode 100755
index 0000000000..fe67e4ae8b
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/root/usr/sbin/readDB.awk
@@ -0,0 +1,157 @@
+#!/usr/bin/awk
+
+function inInterfaces(host){
+ return(interfaces ~ "(^| )"host"($| )")
+}
+
+function newRule(arp_ip,
+ ipt_cmd){
+ # checking for existing rules shouldn't be necessary if newRule is
+ # always called after db is read, arp table is read, and existing
+ # iptables rules are read.
+ ipt_cmd="iptables -t mangle -j RETURN -s " arp_ip
+ system(ipt_cmd " -C RRDIPT_FORWARD 2>/dev/null || " ipt_cmd " -A RRDIPT_FORWARD")
+ ipt_cmd="iptables -t mangle -j RETURN -d " arp_ip
+ system(ipt_cmd " -C RRDIPT_FORWARD 2>/dev/null || " ipt_cmd " -A RRDIPT_FORWARD")
+}
+
+function total(i){
+ return(bw[i "/in"] + bw[i "/out"])
+}
+
+function date( cmd, d){
+ cmd="date +%d-%m-%Y_%H:%M:%S"
+ cmd | getline d
+ close(cmd)
+ #!@todo could start a process with "while true; do date ...; done"
+ return(d)
+}
+
+BEGIN {
+ od=""
+ fid=1
+ debug=0
+ rrd=0
+}
+
+/^#/ { # get DB filename
+ FS=","
+ dbFile=FILENAME
+ next
+}
+
+# data from database; first file
+FNR==NR { #!@todo this doesn't help if the DB file is empty.
+ if($2 == "NA")
+ #!@todo could get interface IP here
+ n=$1
+ else
+ n=$2
+
+ hosts[n] = "" # add this host/interface to hosts
+ mac[n] = $1
+ ip[n] = $2
+ inter[n] = $3
+ bw[n "/in"] = $4
+ bw[n "/out"] = $5
+ firstDate[n] = $7
+ lastDate[n] = $8
+ next
+}
+
+# not triggered on the first file
+FNR==1 {
+ FS=" "
+ fid++ #!@todo use fid for all files; may be problematic for empty files
+ next
+}
+
+# arp: ip hw flags hw_addr mask device
+fid==2 {
+ #!@todo regex match IPs and MACs for sanity
+ arp_ip = $1
+ arp_flags = $3
+ arp_mac = $4
+ arp_dev = $6
+ if(arp_flags != "0x0" && !(arp_ip in ip)){
+ if(debug)
+ print "new host:", arp_ip, arp_flags > "/dev/stderr"
+ hosts[arp_ip] = ""
+ mac[arp_ip] = arp_mac
+ ip[arp_ip] = arp_ip
+ inter[arp_ip] = arp_dev
+ bw[arp_ip "/in"] = bw[arp_ip "/out"] = 0
+ firstDate[arp_ip] = lastDate[arp_ip] = date()
+ }
+ next
+}
+
+#!@todo could use mangle chain totals or tailing "unnact" rules to
+# account for data for new hosts from their first presence on the
+# network to rule creation. The "unnact" rules would have to be
+# maintained at the end of the list, and new rules would be inserted
+# at the top.
+
+# skip line
+# read the chain name and deal with the data accordingly
+fid==3 && $1 == "Chain"{
+ rrd=$2 ~ /RRDIPT_.*/
+ next
+}
+
+fid==3 && rrd && (NF < 9 || $1=="pkts"){ next }
+
+fid==3 && rrd { # iptables input
+ if($6 != "*"){
+ m=$6
+ n=m "/out"
+ } else if($7 != "*"){
+ m=$7
+ n=m "/in"
+ } else if($8 != "0.0.0.0/0"){
+ m=$8
+ n=m "/out"
+ } else { # $9 != "0.0.0.0/0"
+ m=$9
+ n=m "/in"
+ }
+
+ # remove host from array; any hosts left in array at END get new
+ # iptables rules
+
+ #!@todo this deletes a host if any rule exists; if only one
+ # directional rule is removed, this will not remedy the situation
+ delete hosts[m]
+
+ if($2 > 0){ # counted some bytes
+ if(mode == "diff" || mode == "noUpdate")
+ print n, $2
+ if(mode!="noUpdate"){
+ if(inInterfaces(m)){ # if label is an interface
+ if(!(m in mac)){ # if label was not in db (also not in
+ # arp table, but interfaces won't be
+ # there anyway)
+ firstDate[m] = date()
+ mac[m] = inter[m] = m
+ ip[m] = "NA"
+ bw[m "/in"]=bw[m "/out"]= 0
+ }
+ }
+ bw[n]+=$2
+ lastDate[m] = date()
+ }
+ }
+}
+
+END {
+ if(mode=="noUpdate") exit
+ close(dbFile)
+ system("rm -f " dbFile)
+ print "#mac,ip,iface,in,out,total,first_date,last_date" > dbFile
+ OFS=","
+ for(i in mac)
+ print mac[i], ip[i], inter[i], bw[i "/in"], bw[i "/out"], total(i), firstDate[i], lastDate[i] > dbFile
+ close(dbFile)
+ # for hosts without rules
+ for(host in hosts) if(!inInterfaces(host)) newRule(host)
+}
diff --git a/package/lean/luci-app-wrtbwmon/root/usr/sbin/wrtbwmon b/package/lean/luci-app-wrtbwmon/root/usr/sbin/wrtbwmon
new file mode 100755
index 0000000000..b2c0b9a07a
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/root/usr/sbin/wrtbwmon
@@ -0,0 +1,301 @@
+#!/bin/sh
+#
+# wrtbwmon: traffic logging tool for routers
+#
+# Peter Bailey (peter.eldridge.bailey+wrtbwmon AT gmail.com)
+#
+# Based on work by:
+# Emmanuel Brucy (e.brucy AT qut.edu.au)
+# Fredrik Erlandsson (erlis AT linux.nu)
+# twist - http://wiki.openwrt.org/RrdTrafficWatch
+
+trap "rm -f /tmp/*_$$.tmp; kill $$" INT
+binDir=/usr/sbin
+dataDir=/usr/share/wrtbwmon
+lockDir=/tmp/wrtbwmon.lock
+pidFile=$lockDir/pid
+networkFuncs=/lib/functions/network.sh
+uci=`which uci 2>/dev/null`
+nslookup=`which nslookup 2>/dev/null`
+nvram=`which nvram 2>/dev/null`
+
+chains='INPUT OUTPUT FORWARD'
+DEBUG=
+interfaces='eth0 tun0' # in addition to detected WAN
+DB=$2
+mode=
+
+# DNS server for reverse lookups provided in "DNS".
+# don't perform reverse DNS lookups by default
+DO_RDNS=${DNS-}
+
+header="#mac,ip,iface,in,out,total,first_date,last_date"
+
+createDbIfMissing()
+{
+ [ ! -f "$DB" ] && echo $header > "$DB"
+}
+
+checkDbArg()
+{
+ [ -z "$DB" ] && echo "ERROR: Missing argument 2 (database file)" && exit 1
+}
+
+checkDB()
+{
+ [ ! -f "$DB" ] && echo "ERROR: $DB does not exist" && exit 1
+ [ ! -w "$DB" ] && echo "ERROR: $DB is not writable" && exit 1
+}
+
+checkWAN()
+{
+ [ -z "$wan" ] && echo "Warning: failed to detect WAN interface."
+}
+
+lookup()
+{
+ MAC=$1
+ IP=$2
+ userDB=$3
+ for USERSFILE in $userDB /tmp/dhcp.leases /tmp/dnsmasq.conf /etc/dnsmasq.conf /etc/hosts; do
+ [ -e "$USERSFILE" ] || continue
+ case $USERSFILE in
+ /tmp/dhcp.leases )
+ USER=$(grep -i "$MAC" $USERSFILE | cut -f4 -s -d' ')
+ ;;
+ /etc/hosts )
+ USER=$(grep "^$IP " $USERSFILE | cut -f2 -s -d' ')
+ ;;
+ * )
+ USER=$(grep -i "$MAC" "$USERSFILE" | cut -f2 -s -d,)
+ ;;
+ esac
+ [ "$USER" = "*" ] && USER=
+ [ -n "$USER" ] && break
+ done
+ if [ -n "$DO_RDNS" -a -z "$USER" -a "$IP" != "NA" -a -n "$nslookup" ]; then
+ USER=`$nslookup $IP $DNS | awk '!/server can/{if($4){print $4; exit}}' | sed -re 's/[.]$//'`
+ fi
+ [ -z "$USER" ] && USER=${MAC}
+ echo $USER
+}
+
+detectIF()
+{
+ if [ -f "$networkFuncs" ]; then
+ IF=`. $networkFuncs; network_get_device netdev $1; echo $netdev`
+ [ -n "$IF" ] && echo $IF && return
+ fi
+
+ if [ -n "$uci" -a -x "$uci" ]; then
+ IF=`$uci get network.${1}.ifname 2>/dev/null`
+ [ $? -eq 0 -a -n "$IF" ] && echo $IF && return
+ fi
+
+ if [ -n "$nvram" -a -x "$nvram" ]; then
+ IF=`$nvram get ${1}_ifname 2>/dev/null`
+ [ $? -eq 0 -a -n "$IF" ] && echo $IF && return
+ fi
+}
+
+detectLAN()
+{
+ [ -e /sys/class/net/br-lan ] && echo br-lan && return
+ lan=$(detectIF lan)
+ [ -n "$lan" ] && echo $lan && return
+}
+
+detectWAN()
+{
+ [ -n "$WAN_IF" ] && echo $WAN_IF && return
+ wan=$(detectIF wan)
+ [ -n "$wan" ] && echo $wan && return
+ wan=$(ip route show 2>/dev/null | grep default | sed -re '/^default/ s/default.*dev +([^ ]+).*/\1/')
+ [ -n "$wan" ] && echo $wan && return
+ [ -f "$networkFuncs" ] && wan=$(. $networkFuncs; network_find_wan wan; echo $wan)
+ [ -n "$wan" ] && echo $wan && return
+}
+
+lock()
+{
+ attempts=0
+ while [ $attempts -lt 10 ]; do
+ mkdir $lockDir 2>/dev/null && break
+ attempts=$((attempts+1))
+ pid=`cat $pidFile 2>/dev/null`
+ if [ -n "$pid" ]; then
+ if [ -d "/proc/$pid" ]; then
+ [ -n "$DEBUG" ] && echo "WARNING: Lockfile detected but process $(cat $pidFile) does not exist !"
+ rm -rf $lockDir
+ else
+ sleep 1
+ fi
+ fi
+ done
+ mkdir $lockDir 2>/dev/null
+ echo $$ > $pidFile
+ [ -n "$DEBUG" ] && echo $$ "got lock after $attempts attempts"
+ trap '' INT
+}
+
+unlock()
+{
+ rm -rf $lockDir
+ [ -n "$DEBUG" ] && echo $$ "released lock"
+ trap "rm -f /tmp/*_$$.tmp; kill $$" INT
+}
+
+# chain
+newChain()
+{
+ chain=$1
+ # Create the RRDIPT_$chain chain (it doesn't matter if it already exists).
+ iptables -t mangle -N RRDIPT_$chain 2> /dev/null
+
+ # Add the RRDIPT_$chain CHAIN to the $chain chain if not present
+ iptables -t mangle -C $chain -j RRDIPT_$chain 2>/dev/null
+ if [ $? -ne 0 ]; then
+ [ -n "$DEBUG" ] && echo "DEBUG: iptables chain misplaced, recreating it..."
+ iptables -t mangle -I $chain -j RRDIPT_$chain
+ fi
+}
+
+# chain tun
+newRuleIF()
+{
+ chain=$1
+ IF=$2
+
+ #!@todo test
+ if [ "$chain" = "OUTPUT" ]; then
+ cmd="iptables -t mangle -o $IF -j RETURN"
+ eval $cmd " -C RRDIPT_$chain 2>/dev/null" || eval $cmd " -A RRDIPT_$chain"
+ elif [ "$chain" = "INPUT" ]; then
+ cmd="iptables -t mangle -i $IF -j RETURN"
+ eval $cmd " -C RRDIPT_$chain 2>/dev/null" || eval $cmd " -A RRDIPT_$chain"
+ fi
+}
+
+update()
+{
+ #!@todo could let readDB.awk handle this; that would place header
+ #!info in fewer places
+ createDbIfMissing
+
+ checkDB
+ checkWAN
+
+ > /tmp/iptables_$$.tmp
+ lock
+ # only zero our own chains
+ for chain in $chains; do
+ iptables -nvxL RRDIPT_$chain -t mangle -Z >> /tmp/iptables_$$.tmp
+ done
+ # the iptables and readDB commands have to be separate. Otherwise,
+ # they will fight over iptables locks
+ awk -v mode="$mode" -v interfaces=\""$interfaces"\" -f $binDir/readDB.awk \
+ $DB \
+ /proc/net/arp \
+ /tmp/iptables_$$.tmp
+ unlock
+}
+
+############################################################
+
+case $1 in
+ "dump" )
+ checkDbArg
+ lock
+ tr ',' '\t' < "$DB"
+ unlock
+ ;;
+
+ "update" )
+ checkDbArg
+ wan=$(detectWAN)
+ interfaces="$interfaces $wan"
+ update
+ rm -f /tmp/*_$$.tmp
+ exit
+ ;;
+
+ "publish" )
+ checkDbArg
+ [ -z "$3" ] && echo "ERROR: Missing argument 3 (output html file)" && exit 1
+
+ # sort DB
+ lock
+
+ # busybox sort truncates numbers to 32 bits
+ grep -v '^#' $DB | awk -F, '{OFS=","; a=sprintf("%f",$4/1e6); $4=""; print a,$0}' | tr -s ',' | sort -rn | awk -F, '{OFS=",";$1=sprintf("%f",$1*1e6);print}' > /tmp/sorted_$$.tmp
+
+ # create HTML page
+ rm -f $3.tmp
+ cp $dataDir/usage.htm1 $3.tmp
+
+ #!@todo fix publishing
+ while IFS=, read PEAKUSAGE_IN MAC IP IFACE PEAKUSAGE_OUT TOTAL FIRSTSEEN LASTSEEN
+ do
+ echo "
+new Array(\"$(lookup $MAC $IP $4)\",\"$MAC\",\"$IP\",
+$PEAKUSAGE_IN,$PEAKUSAGE_OUT,$TOTAL,\"$FIRSTSEEN\",\"$LASTSEEN\")," >> $3.tmp
+ done < /tmp/sorted_$$.tmp
+ echo "0);" >> $3.tmp
+
+ sed "s/(date)/`date`/" < $dataDir/usage.htm2 >> $3.tmp
+ mv $3.tmp $3
+
+ unlock
+
+ #Free some memory
+ rm -f /tmp/*_$$.tmp
+ ;;
+
+ "setup" )
+ checkDbArg
+ [ -w "$DB" ] && echo "Warning: using existing $DB"
+ createDbIfMissing
+
+ for chain in $chains; do
+ newChain $chain
+ done
+
+ #lan=$(detectLAN)
+ wan=$(detectWAN)
+ checkWAN
+ interfaces="$interfaces $wan"
+
+ # track local data
+ for chain in INPUT OUTPUT; do
+ for interface in $interfaces; do
+ [ -n "$interface" ] && [ -e "/sys/class/net/$interface" ] && newRuleIF $chain $interface
+ done
+ done
+
+ # this will add rules for hosts in arp table
+ update
+
+ rm -f /tmp/*_$$.tmp
+ ;;
+
+ "remove" )
+ iptables-save | grep -v RRDIPT | iptables-restore
+ rm -rf "$lockDir"
+ ;;
+
+ *)
+ echo \
+"Usage: $0 {setup|update|publish|remove} [options...]
+Options:
+ $0 setup database_file
+ $0 update database_file
+ $0 publish database_file path_of_html_report [user_file]
+Examples:
+ $0 setup /tmp/usage.db
+ $0 update /tmp/usage.db
+ $0 publish /tmp/usage.db /www/user/usage.htm /jffs/users.txt
+ $0 remove
+Note: [user_file] is an optional file to match users with MAC addresses.
+ Its format is \"00:MA:CA:DD:RE:SS,username\", with one entry per line."
+ ;;
+esac
diff --git a/package/lean/luci-app-wrtbwmon/root/usr/share/wrtbwmon/usage.htm1 b/package/lean/luci-app-wrtbwmon/root/usr/share/wrtbwmon/usage.htm1
new file mode 100644
index 0000000000..1f0c342a7c
--- /dev/null
+++ b/package/lean/luci-app-wrtbwmon/root/usr/share/wrtbwmon/usage.htm1
@@ -0,0 +1,23 @@
+Traffic
+
+