From 7d207fd4feff2b74d5f6a5d2fce60119eea15faa Mon Sep 17 00:00:00 2001 From: CN_SZTL Date: Thu, 18 Feb 2021 20:09:40 +0800 Subject: [PATCH] luci-app-wrtbwmon: sync with upstream source Signed-off-by: CN_SZTL --- CONTRIBUTED.md | 4 +- package/ctcgfw/luci-app-wrtbwmon/Makefile | 22 + .../luci-app-wrtbwmon/etc/config/wrtbwmon | 5 + .../etc/hotplug.d/iface/99-wrtbwmon | 9 + .../luci-app-wrtbwmon/etc/init.d/wrtbwmon | 44 ++ .../resources/view/wrtbwmon/config.js | 95 +++ .../resources/view/wrtbwmon/custom.js | 43 ++ .../resources/view/wrtbwmon/details.js | 651 ++++++++++++++++++ .../resources/view/wrtbwmon/wrtbwmon.css | 73 ++ .../po/templates/wrtbwmon.pot | 260 +++++++ .../luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po | 260 +++++++ .../root/etc/luci-wrtbwmon.conf | 12 + .../share/luci/menu.d/luci-app-wrtbwmon.json | 41 ++ .../share/rpcd/acl.d/luci-app-wrtbwmon.json | 25 + .../luci-app-wrtbwmon/usr/sbin/readDB.awk | 213 ++++++ .../luci-app-wrtbwmon/usr/sbin/wrtbwmon | 460 +++++++++++++ .../usr/share/wrtbwmon/usage.htm1 | 34 + .../usr/share/wrtbwmon/usage.htm2 | 18 + package/lean/luci-app-wrtbwmon/Makefile | 17 - .../htdocs/luci-static/wrtbwmon.js | 562 --------------- .../luasrc/controller/wrtbwmon.lua | 43 -- .../luasrc/model/cbi/wrtbwmon/config.lua | 18 - .../luasrc/model/cbi/wrtbwmon/custom.lua | 23 - .../luasrc/view/wrtbwmon.htm | 46 -- .../luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po | 56 -- .../root/etc/config/wrtbwmon | 4 - .../root/etc/init.d/wrtbwmon | 22 - .../root/etc/uci-defaults/luci-wrtbwmon | 14 - .../root/etc/wrtbwmon.include | 1 - .../root/usr/sbin/readDB.awk | 157 ----- .../luci-app-wrtbwmon/root/usr/sbin/wrtbwmon | 301 -------- .../share/rpcd/acl.d/luci-app-wrtbwmon.json | 11 - .../root/usr/share/wrtbwmon/usage.htm1 | 23 - .../root/usr/share/wrtbwmon/usage.htm2 | 14 - 34 files changed, 2268 insertions(+), 1313 deletions(-) create mode 100644 package/ctcgfw/luci-app-wrtbwmon/Makefile create mode 100644 package/ctcgfw/luci-app-wrtbwmon/etc/config/wrtbwmon create mode 100755 package/ctcgfw/luci-app-wrtbwmon/etc/hotplug.d/iface/99-wrtbwmon create mode 100755 package/ctcgfw/luci-app-wrtbwmon/etc/init.d/wrtbwmon create mode 100644 package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js create mode 100644 package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js create mode 100644 package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js create mode 100644 package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css create mode 100644 package/ctcgfw/luci-app-wrtbwmon/po/templates/wrtbwmon.pot create mode 100644 package/ctcgfw/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po create mode 100644 package/ctcgfw/luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf create mode 100644 package/ctcgfw/luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json create mode 100644 package/ctcgfw/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json create mode 100755 package/ctcgfw/luci-app-wrtbwmon/usr/sbin/readDB.awk create mode 100755 package/ctcgfw/luci-app-wrtbwmon/usr/sbin/wrtbwmon create mode 100644 package/ctcgfw/luci-app-wrtbwmon/usr/share/wrtbwmon/usage.htm1 create mode 100644 package/ctcgfw/luci-app-wrtbwmon/usr/share/wrtbwmon/usage.htm2 delete mode 100644 package/lean/luci-app-wrtbwmon/Makefile delete mode 100644 package/lean/luci-app-wrtbwmon/htdocs/luci-static/wrtbwmon.js delete mode 100644 package/lean/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua delete mode 100644 package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua delete mode 100644 package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua delete mode 100644 package/lean/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm delete mode 100644 package/lean/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po delete mode 100644 package/lean/luci-app-wrtbwmon/root/etc/config/wrtbwmon delete mode 100755 package/lean/luci-app-wrtbwmon/root/etc/init.d/wrtbwmon delete mode 100755 package/lean/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon delete mode 100755 package/lean/luci-app-wrtbwmon/root/etc/wrtbwmon.include delete mode 100755 package/lean/luci-app-wrtbwmon/root/usr/sbin/readDB.awk delete mode 100755 package/lean/luci-app-wrtbwmon/root/usr/sbin/wrtbwmon delete mode 100644 package/lean/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json delete mode 100644 package/lean/luci-app-wrtbwmon/root/usr/share/wrtbwmon/usage.htm1 delete mode 100644 package/lean/luci-app-wrtbwmon/root/usr/share/wrtbwmon/usage.htm2 diff --git a/CONTRIBUTED.md b/CONTRIBUTED.md index 5bdd7df5df..e9dc534fc9 100644 --- a/CONTRIBUTED.md +++ b/CONTRIBUTED.md @@ -45,7 +45,9 @@ luci-app-oled source: [NateLol/luci-app-oled](https://github.com/NateLol/luci-ap luci-app-beardropper source: [NateLol/natelol](https://github.com/NateLol/natelol).
luci-app-vssr source: [jerrykuku/luci-app-vssr](https://github.com/jerrykuku/luci-app-vssr).
luci-theme-edge source: [garypang13/luci-theme-edge](https://github.com/garypang13/luci-theme-edge).
-luci-proto-minieap source: [ysc3839/luci-proto-minieap](https://github.com/ysc3839/luci-proto-minieap). +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). ## License ### Depend on their own License. diff --git a/package/ctcgfw/luci-app-wrtbwmon/Makefile b/package/ctcgfw/luci-app-wrtbwmon/Makefile new file mode 100644 index 0000000000..316891525c --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/Makefile @@ -0,0 +1,22 @@ +# +# Copyright (C) 2020 xxx +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-wrtbwmon +PKG_VERSION:=2.0.8 +PKG_RELEASE:=1 + +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:= + +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 new file mode 100644 index 0000000000..da9470a111 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/etc/config/wrtbwmon @@ -0,0 +1,5 @@ + +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 new file mode 100755 index 0000000000..41b49a4a20 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/etc/hotplug.d/iface/99-wrtbwmon @@ -0,0 +1,9 @@ +#!/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 new file mode 100755 index 0000000000..1c3213ee17 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/etc/init.d/wrtbwmon @@ -0,0 +1,44 @@ +#!/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/htdocs/luci-static/resources/view/wrtbwmon/config.js b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js new file mode 100644 index 0000000000..693ab5630a --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js @@ -0,0 +1,95 @@ +'use strict'; +'require form'; +'require fs'; +'require uci'; + +function renameFile(str, tag) { + var dir = dirName(str); + var n = str.lastIndexOf('/'), fn = n > -1 ? str.slice(n + 1) : str; + var n = fn.lastIndexOf('.'), bn = n > -1 ? fn.slice(0, n) : fn; + var n = fn.lastIndexOf('.'), en = n > -1 ? fn.slice(n + 1) : ''; + return dir + bn + '.' + tag + (en ? '.' + en : ''); +} + +function dirName(str) { + var n = str.lastIndexOf('/'); + return n > -1 ? str.slice(0, n + 1) : ''; +} + +return L.view.extend({ + lastPath: null, + + load: function() { + return uci.load('wrtbwmon').then(L.bind(function() { + this.lastPath = uci.get_first('wrtbwmon', 'wrtbwmon', 'path') || null; + }, this)); + }, + + render: function() { + var m, s, o; + + m = new form.Map('wrtbwmon', _('Usage - Configuration')); + + s = m.section(form.NamedSection, 'general', 'wrtbwmon', _('General settings')); + s.addremove = false; + + o = s.option(form.Flag, 'enabled', _('Keep running in the background')); + o.rmempty = true; + + o = s.option(form.Value, 'path', _('Database path'), _('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.default = '/tmp/usage.db'; + o.rmempty = false; + + return m.render(); + }, + + changePath: function() { + return uci.changes().then(L.bind(function(res) { + if (res.wrtbwmon && this.lastPath) { + for (var i = 0; i < res.wrtbwmon.length; i++) { + if (res.wrtbwmon[i][2] == "path") { + var newPath = res.wrtbwmon[i][3]; + return fs.stat(dirName(newPath)).then(L.bind(function(res) { + if (res.type == 'directory') { + Promise.all([ + fs.exec('/bin/cp', ['-fp', this.lastPath, newPath]), + fs.exec('/bin/cp', ['-fp', renameFile(this.lastPath, '6'), renameFile(newPath, '6')]), + fs.exec('/bin/cp', ['-fp', renameFile(this.lastPath, '46'), renameFile(newPath, '46')]) + ]); + return true; + } + else { + var err = new Error('Can\'t move files to non-directory path.'); + err.name = 'NotDirectoryError'; + throw err; + } + }, this)).catch(function(err) { + throw err; + }) + } + } + } + return false; + }, this)); + }, + + handleSaveApply: function(ev, mode) { + return this.handleSave(ev).then(L.bind(this.changePath, this)).then(L.bind(function(data) { + L.resolveDefault(L.ui.changes.apply(mode == '0')).then(L.bind(function() { + if (data) { + Promise.all([ + fs.exec('/bin/rm', ['-f', this.lastPath]), + fs.exec('/bin/rm', ['-f', renameFile(this.lastPath, '6')]), + fs.exec('/bin/rm', ['-f', renameFile(this.lastPath, '46')]) + ]); + } + }, this)); + }, this)).catch(function(err) { + if (confirm(err + '\n\n' + _('This will revert the changes. Are you sure?'))) { + L.bind(L.ui.changes.revert, L.ui.changes)(); + } + }); + } +}); diff --git a/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js new file mode 100644 index 0000000000..74d01cbdd5 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js @@ -0,0 +1,43 @@ +'use strict'; +'require fs'; +'require ui'; + +return L.view.extend({ + load: function() { + return fs.trimmed('/etc/wrtbwmon.user').catch(function(err) { + ui.addNotification(null, E('p', {}, _('Unable to load the customized hostname file: ' + err.message))); + return ''; + }); + }, + + render: function(data) { + return E('div', {'class': 'cbi-map'}, [ + E('h2', {'name': 'content'}, [ _('Usage - Custom User File') ]), + E('div', {'class': 'cbi-map-descr'}, [ + _('Each line must have the following format:'), + E('em', {}, E('font', {'color': 'red'}, '00:aa:bb:cc:ee:ff,hostname')) + ]), + E('div', {'class': 'cbi-section'}, [ + E('textarea', { + 'id': 'custom_hosts', + 'style': 'width: 100%;padding: .5em;', + 'rows': 20 + }, data) + ]) + ]); + }, + + handleSave: function(ev) { + var map = document.querySelector('#custom_hosts'); + return fs.write('/etc/wrtbwmon.user', map.value.trim().replace(/\r\n/g, '\n') + '\n'); + }, + + addFooter: function() { + return E('div', { 'class': 'cbi-page-actions' }, [ + E('button', { + 'class': 'cbi-button cbi-button-save', + 'click': L.ui.createHandlerFn(this, 'handleSave') + }, [ _('Save') ]) + ]); + } +}); diff --git a/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js new file mode 100644 index 0000000000..796a51b856 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js @@ -0,0 +1,651 @@ +'use strict'; +'require fs'; +'require rpc'; +'require uci'; +'require ui'; +'require validation'; + +var cachedData = []; +var luciConfig = '/etc/luci-wrtbwmon.conf'; +var hostNameFile = '/etc/wrtbwmon.user'; + +var callLuciDHCPLeases = rpc.declare({ + object: 'luci-rpc', + method: 'getDHCPLeases', + expect: { '': {} } +}); + +var callLuciDSLStatus = rpc.declare({ + object: 'luci-rpc', + method: 'getDSLStatus', + expect: { '': {} } +}); + +function $(tid) { + return document.getElementById(tid); +} + +function clickToResetDatabase(settings) { + if (confirm(_('This will delete the database file. Are you sure?'))) { + getPath().then(function(res) { + var db = settings.protocol == 'ipv4' ? res : renameFile(res, '6'); + fs.exec('/bin/rm', [db]).then(function() { + updateData($('traffic'), $('updated'), $('updating'), settings, true); + }); + }) + } +} + +function clickToSaveConfig(keylist, cstrs) { + var data = {}; + + for(var i = 0; i < keylist.length; i++) { + data[keylist[i]] = cstrs[keylist[i]].getValue(); + } + + ui.showModal(_('Configuration'), [ + E('p', { 'class': 'spinning' }, _('Saving configuration data...')) + ]); + + return fs.write(luciConfig, JSON.stringify(data, undefined, '\t') + '\n') + .catch(function(err) { + ui.addNotification(null, E('p', {}, [ _('Unable to save %s: %s').format(luciConfig, err) ])); + }) + .then(ui.hideModal) + .then(function() { document.location.reload(); }); +} + +function clickToSelectInterval(settings, ev) { + if (ev.target.value > 0) { + settings.interval = parseInt(ev.target.value); + if (!L.Request.poll.active()) L.Request.poll.start(); + } + else { + L.Request.poll.stop(); + setUpdateMessage($('updating'), -1); + } +} + +function clickToSelectProtocol(settings, ev) { + settings.protocol = ev.target.value; + updateData($('traffic'), $('updated'), $('updating'), settings, true); +} + +function clickToShowMore(settings, ev) { + var table = $('traffic'); + var t = table.querySelector('.tr.table-totals'); + settings.showMore = ev.target.checked + + if (t && t.firstElementChild) + t.firstElementChild.textContent = _('TOTAL') + (settings.showMore ? '' : ': ' + (table.childElementCount - 2)); + + table.querySelectorAll('.showMore').forEach(function(e) { + e.classList.toggle('hide'); + }); + + if (!settings.showMore && table.querySelector('.th.sorted').classList.contains('hide')) { + table.querySelector('[id="thTotal"]').click(); + } +} + +function createOption(args, val) { + var cstr = args[0], title = args[1], desc = args.slice(-1), widget, frame; + val = val != null ? val : args[2]; + widget = args.length == 5 ? new cstr(val, args[3]) : new cstr(val, args[3], args[4]); + + frame = E('div', {'class': 'cbi-value'}, [ + E('label', {'class': 'cbi-value-title'}, title), + E('div', {'class': 'cbi-value-field'}, E('div', {}, widget.render())) + ]); + + if (desc && desc != '') + L.dom.append(frame.lastChild, E('div', { 'class': 'cbi-value-description' }, desc)); + + return [widget, frame]; +} + +function displayTable(tb, settings) { + var elm, elmID, col, sortedBy, flag, IPVer; + var thID = ['thClient', 'thMAC', 'thDownload', 'thUpload', 'thTotalDown', 'thTotalUp', 'thTotal', 'thFirstSeen', 'thLastSeen', '']; + + elm = tb.querySelector('.th.sorted'); + elmID = elm ? elm.id : 'thTotal'; + sortedBy = elm && elm.classList.contains('ascent') ? 'asc' : 'desc'; + + col = thID.indexOf(elmID); + IPVer = col == 0 ? settings.protocol : null; + flag = sortedBy == 'desc' ? 1 : -1; + + cachedData[0].sort(sortTable.bind(this, col, IPVer, flag)); + + //console.time('show'); + updateTable(tb, cachedData, '%s'.format(_('Collecting data...')), settings); + //console.timeEnd('show'); + progressbar('downstream', cachedData[1][0], settings.downstream, settings.useBits, settings.useMultiple); + progressbar('upstream', cachedData[1][1], settings.upstream, settings.useBits, settings.useMultiple); +} + +function formatBandWidth(bdw, useBits) { + return bdw * Math.pow(1000, 2) / (useBits ? 1 : 8); +} + +function formatSize(size, useBits, useMultiple) { + var res = String.format('%%%s.2m%s'.format(useMultiple, (useBits ? 'bit' : 'B')), useBits ? size * 8 : size); + return useMultiple == '1024' ? res.replace(/([KMGTPEZ])/, '$&i') : res; +} + +function formatSpeed(speed, useBits, useMultiple) { + return formatSize(speed, useBits, useMultiple) + '/s'; +} + +function formatDate(d) { + var Y = d.getFullYear(), M = d.getMonth() + 1, D = d.getDate(); + var hh = d.getHours(), mm = d.getMinutes(), ss = d.getSeconds(); + return '%04d/%02d/%02d %02d:%02d:%02d'.format(Y, M, D, hh, mm, ss); +} + +function getDSLBandwidth() { + return callLuciDSLStatus().then(function(res) { + return { + upstream : res.max_data_rate_up || null, + downstream : res.max_data_rate_down || null + }; + }); +} + +function getPath() { + return uci.load('wrtbwmon').then(function() { + var res = uci.get_first('wrtbwmon', 'wrtbwmon', 'path') || '/tmp/usage.db'; + uci.unload('wrtbwmon'); + return res; + }); +} + +function handleConfig(ev) { + ui.showModal(_('Configuration'), [ + E('p', { 'class': 'spinning' }, _('Loading configuration data...')) + ]); + + parseDefaultSettings(luciConfig) + .then(function(settings) { + var arglist, keylist = Object.keys(settings), res, cstrs = {}, node = [], body; + + arglist = [ + [ui.Select, _('Default Protocol'), 'ipv4', {'ipv4': _('ipv4'), 'ipv6': _('ipv6')}, {}, ''], + [ui.Select, _('Default Refresh Interval'), '5', {'-1': _('Disabled'), '2': _('2 seconds'), '5': _('5 seconds'), '10': _('10 seconds'), '30': _('30 seconds')}, {sort: ['-1', '2', '5', '10', '30']}, ''], + [ui.Checkbox, _('Default More Columns'), false, {value_enabled: true, value_disabled: false}, ''], + [ui.Checkbox, _('Show Zeros'), true, {value_enabled: true, value_disabled: false}, ''], + [ui.Checkbox, _('Transfer Speed in Bits'), false, {value_enabled: true, value_disabled: false}, ''], + [ui.Select, _('Multiple of Unit'), '1000', {'1000': _('SI - 1000'), '1024': _('IEC - 1024')}, {}, ''], + [ui.Checkbox, _('Use DSL Bandwidth'), false, {value_enabled: true, value_disabled: false}, ''], + [ui.Textfield, _('Upstream Bandwidth'), '100', {datatype: 'ufloat'}, 'Mbps'], + [ui.Textfield, _('Downstream Bandwidth'), '100', {datatype: 'ufloat'}, 'Mbps'], + [ui.DynamicList, _('Hide MAC Addresses'), [], '', {datatype: 'macaddr'}, ''] + ]; // [constructor, lable, default_value(, all_values), options, description] + + for (var i = 0; i < keylist.length; i++) { + res = createOption(arglist[i], settings[keylist[i]]); + cstrs[keylist[i]] = res[0]; + node.push(res[1]); + } + + body = [ + E('p', {}, _('Configure the default values for luci-app-wrtbwmon.')), + E('div', {}, node), + E('div', { 'class': 'right' }, [ + E('div', { + 'class': 'btn cbi-button-neutral', + 'click': ui.hideModal + }, _('Cancel')), + ' ', + E('div', { + 'class': 'btn cbi-button-positive', + 'click': clickToSaveConfig.bind(this, keylist, cstrs), + 'disabled': (L.hasViewPermission ? !L.hasViewPermission() : null) || null + }, _('Save')) + ]) + ]; + ui.showModal(_('Configuration'), body); + }) +} + +function loadCss(path) { + var head = document.head || document.getElementsByTagName('head')[0]; + var link = E('link', { + 'rel': 'stylesheet', + 'href': path, + 'type': 'text/css' + }); + + head.appendChild(link); +} + +function parseDatabase(values, hosts, showZero, hideMACs) { + var valArr = [], totals = [0, 0, 0, 0, 0], valToRows, row; + + valToRows = values.replace(/(^\s*)|(\s*$)/g, '').split(/\r?\n|\r/g); + valToRows.shift(); + + for (var i = 0; i < valToRows.length; i++) { + row = valToRows[i].split(','); + if ((!showZero && row[7] == 0) || hideMACs.indexOf(row[0]) >= 0) continue; + + for (var j = 0; j < totals.length; j++) { + totals[j] += parseInt(row[3 + j]); + } + + row = Array.prototype.concat(row.slice(0, 2).reverse(), row.slice(3), row.slice(0, 1)); + if (row[1] in hosts && hosts[row[1]] != '-') { + row[9] = hosts[row[1]]; + } + valArr.push(row); + } + + return [valArr, totals]; +} + +function parseDefaultSettings(file) { + var keylist = ['protocol', 'interval', 'showMore', 'showZero', 'useBits', 'useMultiple', 'useDSL', 'upstream', 'downstream', 'hideMACs']; + var valuelist = ['ipv4', '5', false, true, false, '1000', false, '100', '100', []]; + + return fs.read(file).then(function(json) { + var settings; + try { + settings = JSON.parse(json); + } + catch(err) { + settings = {}; + } + + for (var i = 0; i < keylist.length; i++) { + if(!(keylist[i] in settings)) + settings[keylist[i]] = valuelist[i]; + } + + if (settings.useDSL) { + return getDSLBandwidth().then(function(dsl) { + settings.upstream = dsl.upstream || settings.upstream; + settings.downstream = dsl.downstream || settings.downstream; + return settings; + }); + } + else { + return settings; + } + }); +} + +function progressbar(query, v, m, useBits, useMultiple) { + var pg = $(query), + vn = v || 0, + mn = formatBandWidth(m, useBits) || 100, + fv = formatSpeed(v, useBits, useMultiple), + pc = '%.2f'.format((100 / mn) * vn), + wt = Math.floor(pc > 100 ? 100 : pc), + bgc = (pc >= 95 ? 'red' : (pc >= 80 ? 'darkorange' : (pc >= 60 ? 'yellow' : 'lime'))), + tc = (pc >= 80 ? 'white' : '#404040'); + if (pg) { + pg.firstElementChild.style.width = wt + '%'; + pg.firstElementChild.style.background = bgc; + pg.style.color = tc; + pg.setAttribute('title', '%s (%f%%)'.format(fv, pc)); + } +} + +function registerTableEventHandlers(settings, table) { + var indicators = $('xhr_poll_status') || $('indicators').getElementsByTagName('span')[0]; + indicators.addEventListener('click', function() { + $('selectInterval').value = L.Request.poll.active() ? settings.interval : -1; + }); + + table.querySelectorAll('.th').forEach(function(e) { + if (e) { + e.addEventListener('click', function (ev) { + setSortedColumn(ev.target); + displayTable(table, settings); + }); + } + }); +} + +function renameFile(str, tag) { + var n = str.lastIndexOf('/'), fn = n > -1 ? str.slice(n + 1) : str, dir = n > -1 ? str.slice(0, n + 1) : ''; + var n = fn.lastIndexOf('.'), bn = n > -1 ? fn.slice(0, n) : fn; + var n = fn.lastIndexOf('.'), en = n > -1 ? fn.slice(n + 1) : ''; + return dir + bn + '.' + tag + (en ? '.' + en : ''); +} + +function resolveCustomizedHostName() { + return fs.stat(hostNameFile).then(function() { + return fs.read(hostNameFile).then(function(rawStr) { + var hostNames = [], arr = rawStr.split(/\r?\n|\r/g), row; + for (var i = 0; i < arr.length; i++) { + row = arr[i].split(','); + if (row.length == 2 && row[0]) + hostNames.push({ macaddr: row[0], hostname: row[1] }); + } + return hostNames; + }) + }).catch(function() { return []; }); +} + +function resolveHostNameByMACAddr() { + return Promise.all([ + resolveCustomizedHostName(), + callLuciDHCPLeases() + ]).then(function(res) { + var leaseNames, macaddr, hostNames = {}; + leaseNames = [ + res[0], + Array.isArray(res[1].dhcp_leases) ? res[1].dhcp_leases : [], + Array.isArray(res[1].dhcp6_leases) ? res[1].dhcp6_leases : [] + ]; + for (var i = 0; i < leaseNames.length; i++) { + for (var j = 0; j < leaseNames[i].length; j++) { + if (leaseNames[i][j].macaddr) { + macaddr = leaseNames[i][j].macaddr.toLowerCase(); + if (!(macaddr in hostNames) || hostNames[macaddr] == '-') { + hostNames[macaddr] = leaseNames[i][j].hostname || '-'; + } + } + } + } + return hostNames; + }); +} + +function setSortedColumn(sorting) { + var sorted = document.querySelector('.th.sorted') || $('thTotal'); + + if (sorting.isSameNode(sorted)) { + sorting.classList.toggle('ascent'); + } + else { + sorting.classList.add('sorted'); + sorted.classList.remove('sorted', 'ascent'); + } +} + +function setUpdateMessage(e, sec) { + e.innerHTML = sec < 0 ? '' : ' ' + _('Updating again in %s second(s).').format('' + sec + ''); +} + +function sortTable(col, IPVer, flag, x, y) { + var byCol = x[col] == y[col] ? 1 : col; + var a = x[byCol], b = y[byCol]; + + if (!IPVer || byCol != 0) { + if (!(a.match(/\D/g) || b.match(/\D/g))) + a = parseInt(a), b = parseInt(b); + } + else { + IPVer == 'ipv4' + ? (a = validation.parseIPv4(a) || [0, 0, 0, 0], b = validation.parseIPv4(b) || [0, 0, 0, 0]) + : (a = validation.parseIPv6(a) || [0, 0, 0, 0, 0, 0, 0, 0], b = validation.parseIPv6(b) || [0, 0, 0, 0, 0, 0, 0, 0]); + } + + if(Array.isArray(a) && Array.isArray(b)) { + for(var i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return (b[i] - a[i]) * flag; + } + } + return 0; + } + + return a == b ? 0 : (a < b ? 1 : -1) * flag; +} + +function updateData(table, updated, updating, settings, once) { + if (!(L.Poll.tick % settings.interval) || once) { + getPath().then(function(res) { + var params = settings.protocol == 'ipv4' ? '-4' : '-6'; + fs.exec('/usr/sbin/wrtbwmon', [params, '-f', res]); + return params == '-4' ? res : renameFile(res, '6'); + }).then(function(data) { + Promise.all([ + fs.exec('/bin/cat', [ data ]), + resolveHostNameByMACAddr() + ]).then(function(res) { + //console.time('start'); + cachedData = parseDatabase(res[0].stdout || '', res[1], settings.showZero, settings.hideMACs); + displayTable(table, settings); + updated.innerHTML = _('Last updated at %s.').format(formatDate(new Date(document.lastModified))); + //console.timeEnd('start'); + }); + }); + } + updatePerSec(updating, settings.interval); +} + +function updatePerSec(e, interval) { + var tick = L.Poll.tick; + var sec = tick % interval ? interval - tick % interval : 0; + + setUpdateMessage(e, sec); + if(sec == 0) { + setTimeout(setUpdateMessage.bind(this, e, interval), 100); + } +} + +function updateTable(tb, values, placeholder, settings) { + var fragment = document.createDocumentFragment(), nodeLen = tb.childElementCount - 2; + var formData = values[0], tbTitle = tb.firstElementChild, newNode, childTD; + + // Update the table data. + for (var i = 0; i < formData.length; i++) { + if (i < nodeLen) { + newNode = tbTitle.nextElementSibling; + } + else { + if (nodeLen > 0) { + newNode = fragment.firstChild.cloneNode(true); + } + else { + newNode = document.createElement('div'); + childTD = document.createElement('div'); + for (var j = 0; j < tbTitle.children.length; j++) { + childTD.className = 'td' + ('178'.indexOf(j) != -1 ? ' showMore' + (settings.showMore ? '' : ' hide') : ''); + childTD.setAttribute('data-title', tbTitle.children[j].textContent); + newNode.appendChild(childTD.cloneNode(true)); + } + } + newNode.className = 'tr cbi-rowstyle-%d'.format(i % 2 ? 2 : 1); + } + + childTD = newNode.firstElementChild; + childTD.title = formData[i].slice(-1); + for (var j = 0; j < tbTitle.childElementCount; j++, childTD = childTD.nextElementSibling) { + switch (j) { + case 2: + case 3: + childTD.textContent = formatSpeed(formData[i][j], settings.useBits, settings.useMultiple); + break; + case 4: + case 5: + case 6: + childTD.textContent = formatSize(formData[i][j], settings.useBits, settings.useMultiple); + break; + case 7: + case 8: + childTD.textContent = formatDate(new Date(formData[i][j] * 1000)); + break; + default: + childTD.textContent = formData[i][j]; + } + } + fragment.appendChild(newNode); + } + + // Remove the table data which has been deleted from the database. + while (tb.childElementCount > 1) { + tb.removeChild(tbTitle.nextElementSibling); + } + + //Append the totals or placeholder row. + if(formData.length == 0) { + newNode = document.createElement('div'); + newNode.className = 'tr placeholder'; + childTD = document.createElement('div'); + childTD.className = 'td'; + childTD.innerHTML = placeholder; + newNode.appendChild(childTD); + } + else{ + newNode = fragment.firstChild.cloneNode(true); + newNode.className = 'tr table-totals'; + + newNode.children[0].textContent = _('TOTAL') + (settings.showMore ? '' : ': ' + formData.length); + newNode.children[1].textContent = formData.length + ' ' + _('Clients'); + + for (var j = 0; j < tbTitle.childElementCount; j++) { + switch(j) { + case 0: + case 1: + newNode.children[j].removeAttribute('title'); + newNode.children[j].style.fontWeight = 'bold'; + break; + case 2: + case 3: + newNode.children[j].textContent = formatSpeed(values[1][j - 2], settings.useBits, settings.useMultiple); + break; + case 4: + case 5: + case 6: + newNode.children[j].textContent = formatSize(values[1][j - 2], settings.useBits, settings.useMultiple); + break; + default: + newNode.children[j].textContent = ''; + newNode.children[j].removeAttribute('data-title'); + } + } + } + + fragment.appendChild(newNode); + tb.appendChild(fragment); +} + +return L.view.extend({ + load: function() { + return Promise.all([ + parseDefaultSettings(luciConfig), + loadCss(L.resource('view/wrtbwmon/wrtbwmon.css')) + ]); + }, + + render: function(data) { + var settings = data[0]; + var node = E('div', { 'class': 'cbi-map' }, [ + E('h2', {}, _('Usage - Details')), + E('div', { 'class': 'cbi-section' }, [ + E('div', { 'id': 'control_panel' }, [ + E('div', {}, [ + E('label', {}, _('Protocol:')), + E('select', { + 'id': 'selectProtocol', + 'change': clickToSelectProtocol.bind(this, settings) + }, [ + E('option', { 'value': 'ipv4' }, 'ipv4'), + E('option', { 'value': 'ipv6' }, 'ipv6') + ]) + ]), + E('div', {}, [ + E('label', { 'for': 'showMore' }, _('Show More Columns:')), + E('input', { + 'id': 'showMore', + 'type': 'checkbox', + 'click': clickToShowMore.bind(this, settings) + }), + ]), + E('div', {}, [ + E('button', { + 'class': 'btn cbi-button cbi-button-reset important', + 'id': 'resetDatabase', + 'click': clickToResetDatabase.bind(this, settings) + }, _('Reset Database')), + ' ', + E('button', { + 'class': 'btn cbi-button cbi-button-neutral', + 'click': handleConfig + }, _('Configure Options')) + ]) + ]), + E('div', {}, [ + E('div', {}, [ + E('div', { 'id': 'updated' }), + E('div', { 'id': 'updating' }) + ]), + E('div', {}, [ + E('label', { 'for': 'selectInterval' }, _('Auto update every:')), + E('select', { + 'id': 'selectInterval', + 'change': clickToSelectInterval.bind(this, settings) + }, [ + E('option', { 'value': '-1' }, _('Disabled')), + E('option', { 'value': '2' }, _('2 seconds')), + E('option', { 'value': '5' }, _('5 seconds')), + E('option', { 'value': '10' }, _('10 seconds')), + E('option', { 'value': '30' }, _('30 seconds')) + ]) + ]) + ]), + E('div', { 'id': 'progressbar_panel', 'class': 'table' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td' }, E('div', {}, _('Downstream:'))), + E('div', { 'class': 'td' }, E('div', { + 'id': 'downstream', + 'class': 'cbi-progressbar', + 'title': '-' + }, E('div') + )) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td' }, E('div', {}, _('Upstream:'))), + E('div', { 'class': 'td' }, E('div', { + 'id': 'upstream', + 'class': 'cbi-progressbar', + 'title': '-' + }, E('div') + )) + ]), + ]), + E('div', { 'class': 'table', 'id': 'traffic' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th', 'id': 'thClient' }, _('Clients')), + E('div', { 'class': 'th showMore hide', 'id': 'thMAC' }, _('MAC')), + E('div', { 'class': 'th', 'id': 'thDownload' }, _('Download')), + E('div', { 'class': 'th', 'id': 'thUpload' }, _('Upload')), + E('div', { 'class': 'th', 'id': 'thTotalDown' }, _('Total Down')), + E('div', { 'class': 'th', 'id': 'thTotalUp' }, _('Total Up')), + E('div', { 'class': 'th sorted', 'id': 'thTotal' }, _('Total')), + E('div', { 'class': 'th showMore hide', 'id': 'thFirstSeen' }, _('First Seen')), + E('div', { 'class': 'th showMore hide', 'id': 'thLastSeen' }, _('Last Seen')) + ]), + E('div', {'class': 'tr placeholder'}, [ + E('div', { 'class': 'td' }, E('em', {}, _('Collecting data...'))) + ]) + ]) + ]) + ]); + + return Promise.all([ + node.querySelector('[id="traffic"]'), + node.querySelector('[id="updated"]'), + node.querySelector('[id="updating"]'), + node.querySelector('[id="selectInterval"]').value = settings.interval, + node.querySelector('[id="selectProtocol"]').value = settings.protocol, + node.querySelector('[id="showMore"]').checked = settings.showMore, + node.querySelectorAll('.showMore').forEach(function(e) { settings.showMore ? e.classList.remove('hide') : e.classList.add('hide'); }) + ]) + .then(function(data) { + L.Poll.add(updateData.bind(this, data[0], data[1], data[2], settings, false), 1); + return data[0]; + }) + .then(registerTableEventHandlers.bind(this, settings)) + .then(function() { return node; }); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css new file mode 100644 index 0000000000..c4793bc4c9 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css @@ -0,0 +1,73 @@ +.th.sorted::after { + content: '\25bc'; +} +.th.sorted.ascent::after { + content: '\25b2'; +} +.hide { + display: none !important; +} +#control_panel { + display: flex; + margin-bottom: 1rem; + padding: .5rem; + line-height: 2rem; + background-color: #fff; +} +#control_panel > :nth-child(1), #control_panel > :nth-child(2) { + display: inline-block; + flex: 1 1 25%; +} +#control_panel > :nth-child(3) { + flex: 1 1 50%; + text-align: right; +} +#control_panel > * > * { + vertical-align: middle; +} +div > label { + margin-right: .5rem; +} +div > label + select { + min-width: unset; + width: auto; +} +#control_panel + div { + display: flex; + margin-bottom: .5rem; +} +#control_panel + div > div:nth-of-type(1) { + flex: 1 1 65%; +} +#control_panel + div > div:nth-of-type(2) { + flex: 1 1 35%; + text-align: right; +} +#updated, #updating { + display: inline; +} +#thClient { + width: 17%; +} +#thMAC { + width: 10%; +} +#thDownload, #thUpload { + width: 8%; +} +#thTotalDown, #thTotalUp, #thTotal { + width: 9%; +} +#thFirstSeen, #thLastSeen { + width: 15%; +} +#progressbar_panel > .tr > .td:nth-child(1) { + width: 10%; +} +.tr.table-totals { + background: #e0e0e0 !important; +} +#traffic .tr:not(.table-totals):not(.placeholder) > .td:not(.th):first-child::before { + content: attr(title)'\a'; + white-space: pre-line; +} diff --git a/package/ctcgfw/luci-app-wrtbwmon/po/templates/wrtbwmon.pot b/package/ctcgfw/luci-app-wrtbwmon/po/templates/wrtbwmon.pot new file mode 100644 index 0000000000..184af4b18c --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/po/templates/wrtbwmon.pot @@ -0,0 +1,260 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:585 +msgid "10 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:583 +msgid "2 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:586 +msgid "30 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:584 +msgid "5 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:577 +msgid "Auto update every:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:199 +msgid "Cancel" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:497 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 +msgid "Clients" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:122 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:623 +msgid "Collecting data..." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:46 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:165 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:208 +#: luasrc/controller/wrtbwmon.lua:16 +msgid "Configuration" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:568 +msgid "Configure Options" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:193 +msgid "Configure the default values for luci-app-wrtbwmon." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:39 +msgid "Database path" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "Default More Columns" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:174 +msgid "Default Protocol" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +msgid "Default Refresh Interval" +msgstr "" + +#: luasrc/controller/wrtbwmon.lua:13 +msgid "Details" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:582 +msgid "Disabled" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:614 +msgid "Download" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 +msgid "Downstream Bandwidth" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 +msgid "Downstream:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:17 +msgid "Each line must have the following format:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:619 +msgid "First Seen" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:33 +msgid "General settings" +msgstr "" + +#: root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json:3 +msgid "Grant access to LuCI app wrtbwmon" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 +msgid "Hide MAC Addresses" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "IEC - 1024" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:36 +msgid "Keep running in the background" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:620 +msgid "Last Seen" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:411 +msgid "Last updated at %s." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:166 +msgid "Loading configuration data..." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:613 +msgid "MAC" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "Multiple of Unit" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:541 +msgid "Protocol:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 +msgid "Reset Database" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "SI - 1000" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:40 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:205 +msgid "Save" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:47 +msgid "Saving configuration data..." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:551 +msgid "Show More Columns:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +msgid "Show Zeros" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:80 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:496 +msgid "TOTAL" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:39 +msgid "" +"This box is used to select the Database path, which is /tmp/usage.db by " +"default." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:29 +msgid "This will delete the database file. Are you sure?" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:90 +msgid "This will revert the changes. Are you sure?" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 +msgid "Total" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:616 +msgid "Total Down" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:617 +msgid "Total Up" +msgstr "" + +#: luasrc/controller/wrtbwmon.lua:10 +msgid "Traffic Status" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 +msgid "Transfer Speed in Bits" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:8 +msgid "Unable to load the customized hostname file:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:52 +msgid "Unable to save %s: %s" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:368 +msgid "Updating again in %s second(s)." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:615 +msgid "Upload" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "Upstream Bandwidth" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:601 +msgid "Upstream:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:31 +msgid "Usage - Configuration" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:15 +msgid "Usage - Custom User File" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:537 +msgid "Usage - Details" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 +msgid "Use DSL Bandwidth" +msgstr "" + +#: luasrc/controller/wrtbwmon.lua:19 +msgid "User file" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:174 +msgid "ipv4" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:174 +msgid "ipv6" +msgstr "" diff --git a/package/ctcgfw/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po b/package/ctcgfw/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po new file mode 100644 index 0000000000..eafcf530fa --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po @@ -0,0 +1,260 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:585 +msgid "10 seconds" +msgstr "10秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:583 +msgid "2 seconds" +msgstr "2秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:586 +msgid "30 seconds" +msgstr "30秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:584 +msgid "5 seconds" +msgstr "5秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:577 +msgid "Auto update every:" +msgstr "自动刷新:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:199 +msgid "Cancel" +msgstr "取消" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:497 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 +msgid "Clients" +msgstr "客户端" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:122 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:623 +msgid "Collecting data..." +msgstr "收集数据中..." + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:46 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:165 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:208 +#: luasrc/controller/wrtbwmon.lua:16 +msgid "Configuration" +msgstr "配置" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:568 +msgid "Configure Options" +msgstr "配置选项" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:193 +msgid "Configure the default values for luci-app-wrtbwmon." +msgstr "配置luci-app-wrtbwmon的默认值。" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:39 +msgid "Database path" +msgstr "数据路径" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "Default More Columns" +msgstr "默认显示更多列" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:174 +msgid "Default Protocol" +msgstr "默认协议" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +msgid "Default Refresh Interval" +msgstr "默认刷新间隔" + +#: luasrc/controller/wrtbwmon.lua:13 +msgid "Details" +msgstr "流量信息" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:175 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:582 +msgid "Disabled" +msgstr "禁用" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:614 +msgid "Download" +msgstr "下载" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 +msgid "Downstream Bandwidth" +msgstr "下行带宽" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 +msgid "Downstream:" +msgstr "下行:" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:17 +msgid "Each line must have the following format:" +msgstr "每行需要满足以下格式:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:619 +msgid "First Seen" +msgstr "初次记录" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:33 +msgid "General settings" +msgstr "通用设置" + +#: root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json:3 +msgid "Grant access to LuCI app wrtbwmon" +msgstr "授予访问LuCI应用程序wrtbwmon的权限" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 +msgid "Hide MAC Addresses" +msgstr "隐藏MAC地址" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "IEC - 1024" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:36 +msgid "Keep running in the background" +msgstr "保持后台运行" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:620 +msgid "Last Seen" +msgstr "最后记录" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:411 +msgid "Last updated at %s." +msgstr "最后更新于%s。" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:166 +msgid "Loading configuration data..." +msgstr "载入配置数据..." + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:613 +msgid "MAC" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "Multiple of Unit" +msgstr "单位之间倍数" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:541 +msgid "Protocol:" +msgstr "协议:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 +msgid "Reset Database" +msgstr "重置" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "SI - 1000" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:40 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:205 +msgid "Save" +msgstr "保存" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:47 +msgid "Saving configuration data..." +msgstr "保存配置数据..." + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:551 +msgid "Show More Columns:" +msgstr "显示更多列:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +msgid "Show Zeros" +msgstr "显示0流量" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:80 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:496 +msgid "TOTAL" +msgstr "总共" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:39 +msgid "" +"This box is used to select the Database path, which is /tmp/usage.db by " +"default." +msgstr "该选项用于选择数据存储路径,默认路径为/tmp/usage.db。" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:29 +msgid "This will delete the database file. Are you sure?" +msgstr "该操作将删除数据统计文件,确定执行该操作?" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:90 +msgid "This will revert the changes. Are you sure?" +msgstr "更改将会重置,确定吗?" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 +msgid "Total" +msgstr "总计" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:616 +msgid "Total Down" +msgstr "总下载" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:617 +msgid "Total Up" +msgstr "总上传" + +#: luasrc/controller/wrtbwmon.lua:10 +msgid "Traffic Status" +msgstr "流量监控" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 +msgid "Transfer Speed in Bits" +msgstr "以bits显示传输速度" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:8 +msgid "Unable to load the customized hostname file:" +msgstr "不能载入自定义用户名文件:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:52 +msgid "Unable to save %s: %s" +msgstr "不能保存%s: %s" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:368 +msgid "Updating again in %s second(s)." +msgstr "下次更新将于%s秒之后。" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:615 +msgid "Upload" +msgstr "上传" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "Upstream Bandwidth" +msgstr "上传带宽" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:601 +msgid "Upstream:" +msgstr "上行" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:31 +msgid "Usage - Configuration" +msgstr "文件设置" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:15 +msgid "Usage - Custom User File" +msgstr "用户文件配置" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:537 +msgid "Usage - Details" +msgstr "流量详情" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 +msgid "Use DSL Bandwidth" +msgstr "使用DSL带宽" + +#: luasrc/controller/wrtbwmon.lua:19 +msgid "User file" +msgstr "用户文件" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:174 +msgid "ipv4" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:174 +msgid "ipv6" +msgstr "" diff --git a/package/ctcgfw/luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf b/package/ctcgfw/luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf new file mode 100644 index 0000000000..7b368df4fc --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf @@ -0,0 +1,12 @@ +{ + "protocol": "ipv4", + "interval": "5", + "showMore": false, + "showZero": true, + "useBits": false, + "useMultiple": "1000", + "useDSL": false, + "upstream": "100", + "downstream": "100", + "hideMACs": [] +} diff --git a/package/ctcgfw/luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json b/package/ctcgfw/luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json new file mode 100644 index 0000000000..904ef60068 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json @@ -0,0 +1,41 @@ +{ + "admin/network/usage": { + "title": "Traffic Status", + "order": 60, + "action": { + "type": "alias", + "path": "admin/network/usage/details" + }, + "depends": { + "acl": [ "luci-app-wrtbwmon" ], + "uci": { "wrtbwmon": true } + } + }, + + "admin/network/usage/details": { + "title": "Details", + "order": 10, + "action": { + "type": "view", + "path": "wrtbwmon/details" + } + }, + + "admin/network/usage/config": { + "title": "Configuration", + "order": 20, + "action": { + "type": "view", + "path": "wrtbwmon/config" + } + }, + + "admin/network/usage/custom": { + "title": "User file", + "order": 30, + "action": { + "type": "view", + "path": "wrtbwmon/custom" + } + } +} diff --git a/package/ctcgfw/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json b/package/ctcgfw/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json new file mode 100644 index 0000000000..8ec2e66045 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json @@ -0,0 +1,25 @@ +{ + "luci-app-wrtbwmon": { + "description": "Grant access to LuCI app wrtbwmon", + "read": { + "file": { + "/tmp/usage.db": [ "read" ], + "/etc/wrtbwmon.user": [ "read" ], + "/usr/sbin/wrtbwmon": [ "exec" ], + "/bin/cp": [ "exec" ], + "/etc/luci-wrtbwmon.conf": [ "read" ] + }, + "uci": [ "wrtbwmon", "luci-app-wrtbwmon" ] + }, + "write": { + "file": { + "/bin/cat": [ "exec" ], + "/bin/rm": [ "exec" ], + "/bin/cp": [ "exec" ], + "/etc/luci-wrtbwmon.conf": [ "write" ], + "/etc/wrtbwmon.user": [ "write" ] + }, + "uci": [ "wrtbwmon", "luci-app-wrtbwmon" ] + } + } +} diff --git a/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/readDB.awk b/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/readDB.awk new file mode 100755 index 0000000000..2080fe5f79 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/readDB.awk @@ -0,0 +1,213 @@ +#!/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 new file mode 100755 index 0000000000..91089a1eee --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/usr/sbin/wrtbwmon @@ -0,0 +1,460 @@ +#!/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 new file mode 100644 index 0000000000..c469cb7d91 --- /dev/null +++ b/package/ctcgfw/luci-app-wrtbwmon/usr/share/wrtbwmon/usage.htm1 @@ -0,0 +1,34 @@ +Traffic + +

Total Usage:

+ + + + + + + + + + + +
UserDown SpeedUp SpeedDownloadUploadTotalFirst seenLast seen
+
This page was generated on (date) + diff --git a/package/lean/luci-app-wrtbwmon/Makefile b/package/lean/luci-app-wrtbwmon/Makefile deleted file mode 100644 index 412b35fe52..0000000000 --- a/package/lean/luci-app-wrtbwmon/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index 36812e723b..0000000000 --- a/package/lean/luci-app-wrtbwmon/htdocs/luci-static/wrtbwmon.js +++ /dev/null @@ -1,562 +0,0 @@ -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 = '' + 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 deleted file mode 100644 index 25909c846d..0000000000 --- a/package/lean/luci-app-wrtbwmon/luasrc/controller/wrtbwmon.lua +++ /dev/null @@ -1,43 +0,0 @@ -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 deleted file mode 100644 index 469d4f8f2c..0000000000 --- a/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/config.lua +++ /dev/null @@ -1,18 +0,0 @@ -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/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua b/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua deleted file mode 100644 index 671879b4cc..0000000000 --- a/package/lean/luci-app-wrtbwmon/luasrc/model/cbi/wrtbwmon/custom.lua +++ /dev/null @@ -1,23 +0,0 @@ -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 and it must have the following format: 00:aa:bb:cc:ee:ff,username")) - -local o = f:field(Value, "_custom") - -o.template = "cbi/tvalue" -o.rows = 20 - -function o.cfgvalue(self, section) - return fs.readfile(USER_FILE_PATH) -end - -function o.write(self, section, value) - value = value:gsub("\r\n?", "\n") - fs.writefile(USER_FILE_PATH, value) -end - -return f diff --git a/package/lean/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm b/package/lean/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm deleted file mode 100644 index 68713b1238..0000000000 --- a/package/lean/luci-app-wrtbwmon/luasrc/view/wrtbwmon.htm +++ /dev/null @@ -1,46 +0,0 @@ -<%+header%> -

<%=translate("Usage")%>

-

- -

-

- - - -
- - -
-

- - -
<%=translate("Loading...")%>
- - -<%+footer%> diff --git a/package/lean/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po b/package/lean/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po deleted file mode 100644 index 3eb050e3f4..0000000000 --- a/package/lean/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po +++ /dev/null @@ -1,56 +0,0 @@ -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 deleted file mode 100644 index 419270dbab..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/etc/config/wrtbwmon +++ /dev/null @@ -1,4 +0,0 @@ - -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 deleted file mode 100755 index d57c09ae1d..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/etc/init.d/wrtbwmon +++ /dev/null @@ -1,22 +0,0 @@ -#!/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 deleted file mode 100755 index bcb5b337f3..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/etc/uci-defaults/luci-wrtbwmon +++ /dev/null @@ -1,14 +0,0 @@ -#!/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 deleted file mode 100755 index 39bcf43a10..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/etc/wrtbwmon.include +++ /dev/null @@ -1 +0,0 @@ -/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 deleted file mode 100755 index fe67e4ae8b..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/usr/sbin/readDB.awk +++ /dev/null @@ -1,157 +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="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 deleted file mode 100755 index b2c0b9a07a..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/usr/sbin/wrtbwmon +++ /dev/null @@ -1,301 +0,0 @@ -#!/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/rpcd/acl.d/luci-app-wrtbwmon.json b/package/lean/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json deleted file mode 100644 index 8290672dc8..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "luci-app-wrtbwmon": { - "description": "Grant UCI access for luci-app-wrtbwmon", - "read": { - "uci": [ "wrtbwmon" ] - }, - "write": { - "uci": [ "wrtbwmon" ] - } - } -} 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 deleted file mode 100644 index 1f0c342a7c..0000000000 --- a/package/lean/luci-app-wrtbwmon/root/usr/share/wrtbwmon/usage.htm1 +++ /dev/null @@ -1,23 +0,0 @@ -Traffic - -

Total Usage:

- - - - - - - - - -
UserDownloadUploadTotalFirst seenLast seen
-
This page was generated on (date) -