diff --git a/package/mtk/applications/luci-app-wrtbwmon/Makefile b/package/mtk/applications/luci-app-wrtbwmon/Makefile new file mode 100644 index 0000000000..510235fec9 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/Makefile @@ -0,0 +1,21 @@ +# +# 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.13 + +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:= + +LUCI_TITLE:=A Luci module that uses wrtbwmon to track bandwidth usage +LUCI_DEPENDS:=+wrtbwmon +LUCI_PKGARCH:=all + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js new file mode 100644 index 0000000000..c8756b9087 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js @@ -0,0 +1,38 @@ +'use strict'; +'require form'; +'require rpc'; +'require view'; + +var callChangeDatabasePath = rpc.declare({ + object: 'luci.wrtbwmon', + method: 'change_db_path', + params: [ 'state' ] +}); + +return view.extend({ + 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(); + }, + + handleSaveApply: function(ev, mode) { + return callChangeDatabasePath('before') + .then(this.super.bind(this, 'handleSaveApply', arguments)) + .then(callChangeDatabasePath.bind(this, 'after')); + } +}); diff --git a/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js new file mode 100644 index 0000000000..07ff99fa16 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js @@ -0,0 +1,37 @@ +'use strict'; +'require fs'; +'require ui'; +'require view'; + +return 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', {'style': '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'); + }, + handleSaveApply: null, + handleReset: null +}); diff --git a/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js new file mode 100644 index 0000000000..e7e8859479 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js @@ -0,0 +1,639 @@ +'use strict'; +'require dom'; +'require fs'; +'require poll'; +'require rpc'; +'require ui'; +'require validation'; +'require view'; + +var cachedData = []; +var luciConfig = '/etc/luci-wrtbwmon.conf'; +var hostNameFile = '/etc/wrtbwmon.user'; +var columns = { + thClient: _('Clients'), + thMAC: _('MAC'), + thDownload: _('Download'), + thUpload: _('Upload'), + thTotalDown: _('Total Down'), + thTotalUp: _('Total Up'), + thTotal: _('Total'), + thFirstSeen: _('First Seen'), + thLastSeen: _('Last Seen') +}; + +var callLuciDHCPLeases = rpc.declare({ + object: 'luci-rpc', + method: 'getDHCPLeases', + expect: { '': {} } +}); + +var callLuciDSLStatus = rpc.declare({ + object: 'luci-rpc', + method: 'getDSLStatus', + expect: { '': {} } +}); + +var callGetDatabaseRaw = rpc.declare({ + object: 'luci.wrtbwmon', + method: 'get_db_raw', + params: [ 'protocol' ] +}); + +var callGetDatabasePath = rpc.declare({ + object: 'luci.wrtbwmon', + method: 'get_db_path', + params: [ 'protocol' ] +}); + +var callRemoveDatabase = rpc.declare({ + object: 'luci.wrtbwmon', + method: 'remove_db', + params: [ 'protocol' ] +}); + +function $(tid) { + return document.getElementById(tid); +} + +function clickToResetDatabase(settings, table, updated, updating, ev) { + if (confirm(_('This will delete the database file. Are you sure?'))) { + return callRemoveDatabase(settings.protocol) + .then(function() { + updateData(settings, table, updated, updating, 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, updating, ev) { + if (ev.target.value > 0) { + settings.interval = parseInt(ev.target.value); + if (!poll.active()) poll.start(); + } + else { + poll.stop(); + setUpdateMessage(updating, -1); + } +} + +function clickToSelectProtocol(settings, table, updated, updating, ev) { + settings.protocol = ev.target.value; + updateData(settings, table, updated, updating, true); +} + +function createOption(args, val) { + var cstr = args[0], title = args[1], desc = args.slice(-1), widget, frame; + widget = args.length == 4 ? new cstr(val, args[2]) : new cstr(val, args[2], args[3]); + + 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 != '') + 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; + + elm = tb.querySelector('.th.sorted'); + elmID = elm ? elm.id : 'thTotal'; + sortedBy = elm && elm.classList.contains('ascent') ? 'asc' : 'desc'; + + col = Object.keys(columns).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 formatSize(size, useBits, useMultiple) { + // String.format automatically adds the i for KiB if the multiple is 1024 + return String.format('%%%s.2m%s'.format(useMultiple, (useBits ? 'bit' : 'B')), useBits ? size * 8 : size); +} + +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(), + 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 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'), 'ipv6': _('ipv6')}, {}, ''], + [ui.Select, _('Default Refresh Interval'), {'-1': _('Disabled'), '2': _('2 seconds'), '5': _('5 seconds'), '10': _('10 seconds'), '30': _('30 seconds')}, {sort: ['-1', '2', '5', '10', '30']}, ''], + [ui.Dropdown, _('Default Columns'), columns, {multiple: true, sort: false, custom_placeholder: '', dropdown_items: 3}, ''], + [ui.Checkbox, _('Show Zeros'), {value_enabled: true, value_disabled: false}, ''], + [ui.Checkbox, _('Transfer Speed in Bits'), {value_enabled: true, value_disabled: false}, ''], + [ui.Select, _('Multiple of Unit'), {'1000': _('SI - 1000'), '1024': _('IEC - 1024')}, {}, ''], + [ui.Checkbox, _('Use DSL Bandwidth'), {value_enabled: true, value_disabled: false}, ''], + [ui.Textfield, _('Upstream Bandwidth'), {datatype: 'ufloat'}, 'Mbps'], + [ui.Textfield, _('Downstream Bandwidth'), {datatype: 'ufloat'}, 'Mbps'], + [ui.DynamicList, _('Hide MAC Addresses'), '', {datatype: 'macaddr'}, ''] + ]; // [constructor, label(, all_choices), 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(raw, hosts, showZero, hideMACs) { + var values = [], + totals = [0, 0, 0, 0, 0], + rows = raw.trim().split(/\r?\n|\r/g), + rowIndex = [1, 0, 3, 4, 5, 6, 7, 8, 9, 0]; + + rows.shift(); + + for (var i = 0; i < rows.length; i++) { + var row = rows[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]); + } + + var newRow = rowIndex.map(function(i) { return row[i] }); + if (newRow[1].toLowerCase() in hosts) { + newRow[9] = hosts[newRow[1].toLowerCase()]; + } + values.push(newRow); + } + + return [values, totals]; +} + +function parseDefaultSettings(file) { + var defaultColumns = ['thClient', 'thDownload', 'thUpload', 'thTotalDown', 'thTotalUp', 'thTotal'], + keylist = ['protocol', 'interval', 'showColumns', 'showZero', 'useBits', 'useMultiple', 'useDSL', 'upstream', 'downstream', 'hideMACs'], + valuelist = ['ipv4', '5', defaultColumns, true, false, '1000', false, '100', '100', []]; + + return fs.read_direct(file, 'json').then(function(oldSettings) { + var settings = {}; + for (var i = 0; i < keylist.length; i++) { + if (!(keylist[i] in oldSettings)) + settings[keylist[i]] = valuelist[i]; + else + settings[keylist[i]] = oldSettings[keylist[i]]; + } + + if (settings.useDSL) { + return getDSLBandwidth().then(function(dsl) { + for (var s in dsl) + settings[s] = dsl[s]; + return settings; + }); + } + else { + return settings; + } + }) + .catch(function() { return {} }); +} + +function progressbar(query, v, m, useBits, useMultiple) { + // v = B/s, m = Mb/s + var pg = $(query), + vn = (v * 8) || 0, + mn = (m || 100) * Math.pow(1000, 2), + 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'))); + if (pg) { + pg.firstElementChild.style.width = wt + '%'; + pg.firstElementChild.style.background = bgc; + pg.setAttribute('title', '%s (%f%%)'.format(fv, pc)); + } +} + +function setupThisDOM(settings, table) { + document.addEventListener('poll-stop', function() { + $('selectInterval').value = -1; + }); + + document.addEventListener('poll-start', function() { + $('selectInterval').value = settings.interval; + }); + + table.querySelectorAll('.th').forEach(function(e) { + if (e) { + e.addEventListener('click', function (ev) { + setSortedColumn(ev.target); + displayTable(table, settings); + }); + + if (settings.showColumns.indexOf(e.id) >= 0) + e.classList.remove('hide'); + else + e.classList.add('hide'); + + } + }); +} + +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_direct(hostNameFile).then(function(raw) { + var arr = raw.trim().split(/\r?\n/), hosts = {}, row; + for (var i = 0; i < arr.length; i++) { + row = arr[i].split(','); + if (row.length == 2 && row[0]) + hosts[row[0].toLowerCase()] = row[1]; + } + return hosts; + }) + }) + .catch(function() { return []; }); +} + +function resolveHostNameByMACAddr() { + return Promise.all([ + resolveCustomizedHostName(), + callLuciDHCPLeases() + ]).then(function(res) { + var hosts = res[0]; + for (var key in res[1]) { + var leases = Array.isArray(res[1][key]) ? res[1][key] : []; + for (var i = 0; i < leases.length; i++) { + if(leases[i].macaddr) { + var macaddr = leases[i].macaddr.toLowerCase(); + if (!(macaddr in hosts) && Boolean(leases[i].hostname)) + hosts[macaddr] = leases[i].hostname; + } + } + } + return hosts; + }); +} + +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(settings, table, updated, updating, once) { + var tick = poll.tick, + interval = settings.interval, + sec = (interval - tick % interval) % interval; + if (!sec || once) { + callGetDatabasePath() + .then(function(res) { + var params = settings.protocol == 'ipv4' ? '-4' : '-6'; + return fs.exec_direct('/usr/sbin/wrtbwmon', [params, '-f', res.file_4]) + }) + .then(function() { + return Promise.all([ + callGetDatabaseRaw(settings.protocol), + resolveHostNameByMACAddr() + ]); + }) + .then(function(res) { + //console.time('start'); + cachedData = parseDatabase(res[0].data || '', res[1], settings.showZero, settings.hideMACs); + displayTable(table, settings); + updated.textContent = _('Last updated at %s.').format(formatDate(new Date(document.lastModified))); + //console.timeEnd('start'); + }); + } + + setUpdateMessage(updating, sec); + if (!sec) + setTimeout(setUpdateMessage.bind(this, updating, 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('tr'); + childTD = document.createElement('td'); + for (var j = 0; j < tbTitle.children.length; j++) { + childTD.className = 'td' + (settings.showColumns.indexOf(tbTitle.children[j].id) >= 0 ? '' : ' 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('tr'); + newNode.className = 'tr placeholder'; + childTD = document.createElement('td'); + 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.showColumns.indexOf('thMAC') >= 0 ? '' : ': ' + 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); +} + +function initOption(options, selected) { + var res = [], attr = {}; + for (var idx in options) { + attr.value = idx; + attr.selected = idx == selected ? '' : null; + res.push(E('option', attr, options[idx])); + } + return res; +} + +return view.extend({ + load: function() { + return Promise.all([ + parseDefaultSettings(luciConfig), + loadCss(L.resource('view/wrtbwmon/wrtbwmon.css')) + ]); + }, + + render: function(data) { + var settings = data[0], + labelUpdated = E('label'), + labelUpdating = E('label'), + table = E('table', { 'class': 'table', 'id': 'traffic' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th', 'id': 'thClient' }, _('Clients')), + E('th', { 'class': 'th hide', 'id': 'thMAC' }, _('MAC')), + E('th', { 'class': 'th', 'id': 'thDownload' }, _('Download')), + E('th', { 'class': 'th', 'id': 'thUpload' }, _('Upload')), + E('th', { 'class': 'th', 'id': 'thTotalDown' }, _('Total Down')), + E('th', { 'class': 'th', 'id': 'thTotalUp' }, _('Total Up')), + E('th', { 'class': 'th sorted', 'id': 'thTotal' }, _('Total')), + E('th', { 'class': 'th hide', 'id': 'thFirstSeen' }, _('First Seen')), + E('th', { 'class': 'th hide', 'id': 'thLastSeen' }, _('Last Seen')) + ]), + E('tr', {'class': 'tr placeholder'}, [ + E('td', { 'class': 'td' }, E('em', {}, _('Collecting data...'))) + ]) + ]); + + poll.add(updateData.bind(this, settings, table, labelUpdated, labelUpdating, false), 1); + setupThisDOM(settings, table); + return 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, table, labelUpdated, labelUpdating) + }, initOption({ + 'ipv4': 'ipv4', + 'ipv6': 'ipv6' + }, settings.protocol)) + ]), + E('div', {}, [ + E('button', { + 'class': 'btn cbi-button cbi-button-reset important', + 'id': 'resetDatabase', + 'click': clickToResetDatabase.bind(this, settings, table, labelUpdated, labelUpdating) + }, _('Reset Database')), + ' ', + E('button', { + 'class': 'btn cbi-button cbi-button-neutral', + 'click': handleConfig + }, _('Configure Options')) + ]) + ]), + E('div', {}, [ + E('div', {}, [ labelUpdated, labelUpdating ]), + E('div', {}, [ + E('label', { 'for': 'selectInterval' }, _('Auto update every:')), + E('select', { + 'id': 'selectInterval', + 'change': clickToSelectInterval.bind(this, settings, labelUpdating) + }, initOption({ + '-1': _('Disabled'), + '2': _('2 seconds'), + '5': _('5 seconds'), + '10': _('10 seconds'), + '30': _('30 seconds') + }, settings.interval)) + ]) + ]), + E('div', { 'id': 'progressbar_panel' }, [ + E('div', {}, [ + E('label', {}, _('Downstream:')), + E('div', { + 'id': 'downstream', + 'class': 'cbi-progressbar', + 'title': '-' + }, E('div') + ) + ]), + E('div', {}, [ + E('label', {}, _('Upstream:')), + E('div', { + 'id': 'upstream', + 'class': 'cbi-progressbar', + 'title': '-' + }, E('div') + ) + ]), + ]), + table + ]) + ]); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css new file mode 100644 index 0000000000..8c9bbf6340 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css @@ -0,0 +1,66 @@ +.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; +} +#control_panel > :nth-child(1) { + display: inline-block; + flex: 1 1 50%; +} +#control_panel > :nth-child(2) { + flex: 1 1 50%; + text-align: right; +} +#control_panel > * > * { + vertical-align: middle; +} +#control_panel > 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; +} +#thClient { + width: 17%; +} +#thMAC { + width: 10%; +} +#thDownload, #thUpload { + width: 8%; +} +#thTotalDown, #thTotalUp, #thTotal { + width: 9%; +} +#thFirstSeen, #thLastSeen { + width: 15%; +} +.tr.table-totals { + font-weight: bold; +} +#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/mtk/applications/luci-app-wrtbwmon/po/templates/wrtbwmon.pot b/package/mtk/applications/luci-app-wrtbwmon/po/templates/wrtbwmon.pot new file mode 100644 index 0000000000..56e92a13ee --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/po/templates/wrtbwmon.pot @@ -0,0 +1,259 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:611 +msgid "10 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:609 +msgid "2 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 +msgid "30 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:610 +msgid "5 seconds" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:603 +msgid "Auto update every:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:201 +msgid "Cancel" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:14 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:503 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:556 +msgid "Clients" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:132 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:567 +msgid "Collecting data..." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:75 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:167 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:210 +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:25 +msgid "Configuration" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:597 +msgid "Configure Options" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:195 +msgid "Configure the default values for luci-app-wrtbwmon." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 +msgid "Database path" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 +msgid "Default Columns" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "Default Protocol" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +msgid "Default Refresh Interval" +msgstr "" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:16 +msgid "Details" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:608 +msgid "Disabled" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:16 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:558 +msgid "Download" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:184 +msgid "Downstream Bandwidth" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 +msgid "Downstream:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:18 +msgid "Each line must have the following format:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:21 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 +msgid "First Seen" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:18 +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:185 +msgid "Hide MAC Addresses" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "IEC - 1024" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:21 +msgid "Keep running in the background" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:22 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:564 +msgid "Last Seen" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:425 +msgid "Last updated at %s." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:168 +msgid "Loading configuration data..." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:15 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:557 +msgid "MAC" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "Multiple of Unit" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:578 +msgid "Protocol:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 +msgid "Reset Database" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "SI - 1000" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:207 +msgid "Save" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:76 +msgid "Saving configuration data..." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "Show Zeros" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:502 +msgid "TOTAL" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 +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:60 +msgid "This will delete the database file. Are you sure?" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:20 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:562 +msgid "Total" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:18 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:560 +msgid "Total Down" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:19 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:561 +msgid "Total Up" +msgstr "" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:3 +msgid "Traffic Status" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 +msgid "Transfer Speed in Bits" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:9 +msgid "Unable to load the customized hostname file:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:81 +msgid "Unable to save %s: %s" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:376 +msgid "Updating again in %s second(s)." +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:17 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:559 +msgid "Upload" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 +msgid "Upstream Bandwidth" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:627 +msgid "Upstream:" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:16 +msgid "Usage - Configuration" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:16 +msgid "Usage - Custom User File" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:574 +msgid "Usage - Details" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 +msgid "Use DSL Bandwidth" +msgstr "" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:34 +msgid "User file" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "ipv4" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "ipv6" +msgstr "" diff --git a/package/mtk/applications/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po b/package/mtk/applications/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po new file mode 100644 index 0000000000..ff41fe94fc --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po @@ -0,0 +1,265 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:611 +msgid "10 seconds" +msgstr "10秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:609 +msgid "2 seconds" +msgstr "2秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 +msgid "30 seconds" +msgstr "30秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:610 +msgid "5 seconds" +msgstr "5秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:603 +msgid "Auto update every:" +msgstr "自动刷新:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:201 +msgid "Cancel" +msgstr "取消" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:14 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:503 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:556 +msgid "Clients" +msgstr "客户端" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:132 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:567 +msgid "Collecting data..." +msgstr "收集数据中..." + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:75 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:167 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:210 +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:25 +msgid "Configuration" +msgstr "配置" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:597 +msgid "Configure Options" +msgstr "配置选项" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:195 +msgid "Configure the default values for luci-app-wrtbwmon." +msgstr "配置luci-app-wrtbwmon的默认值。" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 +msgid "Database path" +msgstr "数据路径" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 +msgid "Default Columns" +msgstr "默认显示的列" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "Default Protocol" +msgstr "默认协议" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +msgid "Default Refresh Interval" +msgstr "默认刷新间隔" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:16 +msgid "Details" +msgstr "流量信息" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:608 +msgid "Disabled" +msgstr "禁用" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:16 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:558 +msgid "Download" +msgstr "下载" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:184 +msgid "Downstream Bandwidth" +msgstr "下行带宽" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 +msgid "Downstream:" +msgstr "下行:" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:18 +msgid "Each line must have the following format:" +msgstr "每行需要满足以下格式:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:21 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 +msgid "First Seen" +msgstr "初次记录" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:18 +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:185 +msgid "Hide MAC Addresses" +msgstr "隐藏MAC地址" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "IEC - 1024" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:21 +msgid "Keep running in the background" +msgstr "保持后台运行" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:22 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:564 +msgid "Last Seen" +msgstr "最后记录" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:425 +msgid "Last updated at %s." +msgstr "最后更新于%s。" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:168 +msgid "Loading configuration data..." +msgstr "载入配置数据..." + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:15 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:557 +msgid "MAC" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "Multiple of Unit" +msgstr "单位之间倍数" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:578 +msgid "Protocol:" +msgstr "协议:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 +msgid "Reset Database" +msgstr "重置" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "SI - 1000" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:207 +msgid "Save" +msgstr "保存" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:76 +msgid "Saving configuration data..." +msgstr "保存配置数据..." + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "Show Zeros" +msgstr "显示0流量" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:502 +msgid "TOTAL" +msgstr "总共" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 +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:60 +msgid "This will delete the database file. Are you sure?" +msgstr "该操作将删除数据统计文件,确定执行该操作?" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:20 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:562 +msgid "Total" +msgstr "总计" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:18 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:560 +msgid "Total Down" +msgstr "总下载" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:19 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:561 +msgid "Total Up" +msgstr "总上传" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:3 +msgid "Traffic Status" +msgstr "流量监控" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 +msgid "Transfer Speed in Bits" +msgstr "以bits显示传输速度" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:9 +msgid "Unable to load the customized hostname file:" +msgstr "不能载入自定义用户名文件:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:81 +msgid "Unable to save %s: %s" +msgstr "不能保存%s: %s" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:376 +msgid "Updating again in %s second(s)." +msgstr "下次更新将于%s秒之后。" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:17 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:559 +msgid "Upload" +msgstr "上传" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 +msgid "Upstream Bandwidth" +msgstr "上传带宽" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:627 +msgid "Upstream:" +msgstr "上行:" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:16 +msgid "Usage - Configuration" +msgstr "文件设置" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:16 +msgid "Usage - Custom User File" +msgstr "用户文件配置" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:574 +msgid "Usage - Details" +msgstr "流量详情" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 +msgid "Use DSL Bandwidth" +msgstr "使用DSL带宽" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:34 +msgid "User file" +msgstr "用户文件" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "ipv4" +msgstr "" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "ipv6" +msgstr "" + +#~ msgid "Show More Columns:" +#~ msgstr "显示更多列:" + +#~ msgid "This will revert the changes. Are you sure?" +#~ msgstr "更改将会重置,确定吗?" diff --git a/package/mtk/applications/luci-app-wrtbwmon/po/zh_Hant/wrtbwmon.po b/package/mtk/applications/luci-app-wrtbwmon/po/zh_Hant/wrtbwmon.po new file mode 100644 index 0000000000..8e4f57cd32 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/po/zh_Hant/wrtbwmon.po @@ -0,0 +1,276 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2023-04-26 18:31+0800\n" +"Last-Translator: Victor Tseng (palatis@gmail.com)\n" +"Language-Team: \n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 3.2.2\n" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:611 +msgid "10 seconds" +msgstr "10 秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:609 +msgid "2 seconds" +msgstr "2 秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 +msgid "30 seconds" +msgstr "30 秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:610 +msgid "5 seconds" +msgstr "5 秒" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:603 +msgid "Auto update every:" +msgstr "重新整理間隔:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:201 +msgid "Cancel" +msgstr "取消" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:14 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:503 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:556 +msgid "Clients" +msgstr "用戶端" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:132 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:567 +msgid "Collecting data..." +msgstr "蒐集資料中…" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:75 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:167 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:210 +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:25 +msgid "Configuration" +msgstr "設定" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:597 +msgid "Configure Options" +msgstr "設定選項" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:195 +msgid "Configure the default values for luci-app-wrtbwmon." +msgstr "設定 luci-app-wrtbwmon 的預設值。" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 +msgid "Database path" +msgstr "資料庫路徑" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 +msgid "Default Columns" +msgstr "預設顯示的欄位" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "Default Protocol" +msgstr "預設協定" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +msgid "Default Refresh Interval" +msgstr "預設重新整理間隔" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:16 +msgid "Details" +msgstr "詳細" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:608 +msgid "Disabled" +msgstr "已停用" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:16 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:558 +msgid "Download" +msgstr "下載" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:184 +msgid "Downstream Bandwidth" +msgstr "下行頻寬" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 +msgid "Downstream:" +msgstr "下行:" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:18 +msgid "Each line must have the following format:" +msgstr "每一行皆必須使用以下格式:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:21 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 +msgid "First Seen" +msgstr "初次發現" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:18 +msgid "General settings" +msgstr "通用設定" + +#: root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json:3 +msgid "Grant access to LuCI app wrtbwmon" +msgstr "授權給 LuCI app wrtbwmon" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:185 +msgid "Hide MAC Addresses" +msgstr "隱藏 MAC 地址" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "IEC - 1024" +msgstr "IEC - 1024" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:21 +msgid "Keep running in the background" +msgstr "保持背景執行" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:22 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:564 +msgid "Last Seen" +msgstr "最後發現" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:425 +msgid "Last updated at %s." +msgstr "最後於 %s 發現。" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:168 +msgid "Loading configuration data..." +msgstr "載入設定資料…" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:15 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:557 +msgid "MAC" +msgstr "MAC" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "Multiple of Unit" +msgstr "速率計量單位格式" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:578 +msgid "Protocol:" +msgstr "協定:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 +msgid "Reset Database" +msgstr "重置資料庫" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 +msgid "SI - 1000" +msgstr "SI - 1000" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:207 +msgid "Save" +msgstr "儲存" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:76 +msgid "Saving configuration data..." +msgstr "正在保存設定資料…" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 +msgid "Show Zeros" +msgstr "顯示無流量的紀錄" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:502 +msgid "TOTAL" +msgstr "總計" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 +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:60 +msgid "This will delete the database file. Are you sure?" +msgstr "這會刪除目前的資料庫檔案,您確定嗎?" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:20 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:562 +msgid "Total" +msgstr "總頻寬" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:18 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:560 +msgid "Total Down" +msgstr "總下載" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:19 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:561 +msgid "Total Up" +msgstr "總上傳" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:3 +msgid "Traffic Status" +msgstr "頻寬使用狀況" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 +msgid "Transfer Speed in Bits" +msgstr "以位元表示傳輸速率" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:9 +msgid "Unable to load the customized hostname file:" +msgstr "無法載入自訂的主機名稱檔案:" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:81 +msgid "Unable to save %s: %s" +msgstr "無法保存 %s:%s" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:376 +msgid "Updating again in %s second(s)." +msgstr "於 %s 秒後更新。" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:17 +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:559 +msgid "Upload" +msgstr "上傳" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 +msgid "Upstream Bandwidth" +msgstr "上行頻寬" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:627 +msgid "Upstream:" +msgstr "上行:" + +#: htdocs/luci-static/resources/view/wrtbwmon/config.js:16 +msgid "Usage - Configuration" +msgstr "頻寬使用狀況 - 設定" + +#: htdocs/luci-static/resources/view/wrtbwmon/custom.js:16 +msgid "Usage - Custom User File" +msgstr "頻寬使用狀況 - 自訂主機名稱列表" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:574 +msgid "Usage - Details" +msgstr "頻寬使用狀況 - 詳細資料" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 +msgid "Use DSL Bandwidth" +msgstr "使用 DSL 頻寬" + +#: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:34 +msgid "User file" +msgstr "使用者檔案" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "ipv4" +msgstr "ipv4" + +#: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 +msgid "ipv6" +msgstr "ipv6" + +#~ msgid "Default More Columns" +#~ msgstr "預設顯示更多欄位" + +#~ msgid "Show More Columns:" +#~ msgstr "顯示更多欄位:" diff --git a/package/mtk/applications/luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf b/package/mtk/applications/luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf new file mode 100644 index 0000000000..cb5e748390 --- /dev/null +++ b/package/mtk/applications/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": "1000", + "hideMACs": [] +} diff --git a/package/mtk/applications/luci-app-wrtbwmon/root/usr/libexec/rpcd/luci.wrtbwmon b/package/mtk/applications/luci-app-wrtbwmon/root/usr/libexec/rpcd/luci.wrtbwmon new file mode 100755 index 0000000000..793c033015 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/root/usr/libexec/rpcd/luci.wrtbwmon @@ -0,0 +1,124 @@ +#!/bin/sh + +. "$IPKG_INSTROOT/usr/share/libubox/jshn.sh" + +renamefile() { + local base=$(basename -- "$1") + local ext=$([ -z "${base/*.*/}" ] && echo ".${base##*.}" || echo '') + local base="${base%.*}" + echo "$(dirname $1)/${base}$2$ext" && return +} + +_get_db_path() { + local db db_4 db_6 db_46 + db="$(uci -q get wrtbwmon.general.path)" + db_4=$(renamefile "${db:-/tmp/usage.db}" "") + db_6=$(renamefile "${db:-/tmp/usage.db}" ".6") + db_46=$(renamefile "${db:-/tmp/usage.db}" ".46") + json_init + json_add_string file_4 "$db_4" + json_add_string file_6 "$db_6" + json_add_string file_46 "$db_46" + json_dump + json_cleanup +} + +_get_db_raw() { + json_init + json_add_string file "$1" + json_add_string data "$(cat "$1")" + json_dump + json_cleanup +} + +_remove_db() { + local result + rm "$1" && result=1 || result=0 + json_init + json_add_boolean result "$result" + json_dump + json_cleanup +} + +_change_db_path() { + json_init + json_load "$(_get_db_path)" + json_get_var db_4 'file_4' + json_get_var db_6 'file_6' + json_get_var db_46 'file_46' + json_cleanup + if [ "$1" = "before" ]; then + mv "$db_4" /tmp/usage.db.tmp + mv "$db_6" /tmp/usage.6.db.tmp + mv "$db_46" /tmp/usage.46.db.tmp + elif [ "$1" = "after" ]; then + mv -f /tmp/usage.db.tmp "$db_4" + mv -f /tmp/usage.6.db.tmp "$db_6" + mv -f /tmp/usage.46.db.tmp "$db_46" + fi + json_init + json_add_boolean result $([ "$?" = 0 ] && echo 1 || echo 0 ) + json_dump + json_cleanup +} + +case "$1" in + list) + json_init + json_add_object remove_db + json_add_string protocol "protocol" + json_close_object + json_add_object get_db_raw + json_add_string protocol "protocol" + json_close_object + json_add_object get_db_path + json_close_object + json_add_object change_db_path + json_add_string state "state" + json_close_object + json_dump + json_cleanup + ;; + call) + case "$2" in + remove_db) + read -r input + json_init + json_load "$input" + json_get_var protocol 'protocol' + json_cleanup + json_load "$(_get_db_path)" + json_get_var db_s $([ "$protocol" = "ipv4" ] && echo file_4 || echo file_6) + json_cleanup + _remove_db "$db_s" + ;; + get_db_raw) + read -r input + json_init + json_load "$input" + json_get_var protocol 'protocol' + json_cleanup + json_load "$(_get_db_path)" + json_get_var db_s $([ "$protocol" = "ipv4" ] && echo file_4 || echo file_6) + json_cleanup + _get_db_raw "$db_s" + ;; + get_db_path) + read -r input + json_init + json_load "$input" + json_get_var protocol 'protocol' + json_cleanup + _get_db_path + ;; + change_db_path) + read -r input + json_init + json_load "$input" + json_get_var state 'state' + json_cleanup + _change_db_path "$state" + ;; + esac + ;; +esac diff --git a/package/mtk/applications/luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json b/package/mtk/applications/luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json new file mode 100644 index 0000000000..904ef60068 --- /dev/null +++ b/package/mtk/applications/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/mtk/applications/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json b/package/mtk/applications/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json new file mode 100644 index 0000000000..127358fc61 --- /dev/null +++ b/package/mtk/applications/luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json @@ -0,0 +1,36 @@ +{ + "luci-app-wrtbwmon": { + "description": "Grant access to LuCI app wrtbwmon", + "read": { + "ubus": { + "luci.wrtbwmon": [ + "get_db_raw", + "get_db_path" + ], + "luci-rpc": [ + "getDHCPLeases", + "getDSLStatus" + ] + }, + "file": { + "/etc/wrtbwmon.user": [ "read" ], + "/usr/sbin/wrtbwmon": [ "exec" ], + "/etc/luci-wrtbwmon.conf": [ "read" ] + }, + "uci": [ "wrtbwmon" ] + }, + "write": { + "ubus": { + "luci.wrtbwmon": [ + "remove_db", + "change_db_path" + ] + }, + "file": { + "/etc/luci-wrtbwmon.conf": [ "write" ], + "/etc/wrtbwmon.user": [ "write" ] + }, + "uci": [ "wrtbwmon" ] + } + } +} diff --git a/package/mtk/applications/wrtbwmon/Makefile b/package/mtk/applications/wrtbwmon/Makefile new file mode 100755 index 0000000000..e1bbfeaf5d --- /dev/null +++ b/package/mtk/applications/wrtbwmon/Makefile @@ -0,0 +1,50 @@ +# +# Copyright (C) 2006-2011 Xmlad.com +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=wrtbwmon +PKG_VERSION:=1.2.1 +PKG_RELEASE:=3 + +include $(INCLUDE_DIR)/package.mk + +define Package/wrtbwmon + SECTION:=net + CATEGORY:=Network + DEPENDS:=+iptables +@BUSYBOX_CONFIG_IP + TITLE:=A Traffic Usage Monitor + SUBMENU:=Tools + PKGARCH:=all +endef + +define Package/wrtbwmon/description +An CERNET client daemon, +Most usually used in China collages. +endef + +define Build/Prepare +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/wrtbwmon/install + $(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/wrtbwmon + $(INSTALL_BIN) ./net/usr/sbin/* $(1)/usr/sbin/ + $(INSTALL_BIN) ./net/usr/share/wrtbwmon/* $(1)/usr/share/wrtbwmon/ + + $(INSTALL_DIR) $(1)/etc/config $(1)/etc/init.d $(1)/etc/hotplug.d/iface + $(INSTALL_BIN) ./net/etc/init.d/wrtbwmon $(1)/etc/init.d/wrtbwmon + $(INSTALL_BIN) ./net/etc/config/wrtbwmon $(1)/etc/config/wrtbwmon + $(INSTALL_BIN) ./net/etc/hotplug.d/iface/99-wrtbwmon $(1)/etc/hotplug.d/iface/99-wrtbwmon +endef + +$(eval $(call BuildPackage,wrtbwmon)) diff --git a/package/mtk/applications/wrtbwmon/net/etc/config/wrtbwmon b/package/mtk/applications/wrtbwmon/net/etc/config/wrtbwmon new file mode 100755 index 0000000000..da9470a111 --- /dev/null +++ b/package/mtk/applications/wrtbwmon/net/etc/config/wrtbwmon @@ -0,0 +1,5 @@ + +config wrtbwmon 'general' + option enabled '1' + option path '/tmp/usage.db' + diff --git a/package/mtk/applications/wrtbwmon/net/etc/hotplug.d/iface/99-wrtbwmon b/package/mtk/applications/wrtbwmon/net/etc/hotplug.d/iface/99-wrtbwmon new file mode 100644 index 0000000000..41b49a4a20 --- /dev/null +++ b/package/mtk/applications/wrtbwmon/net/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/mtk/applications/wrtbwmon/net/etc/init.d/wrtbwmon b/package/mtk/applications/wrtbwmon/net/etc/init.d/wrtbwmon new file mode 100644 index 0000000000..1c3213ee17 --- /dev/null +++ b/package/mtk/applications/wrtbwmon/net/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/mtk/applications/wrtbwmon/net/usr/sbin/readDB.awk b/package/mtk/applications/wrtbwmon/net/usr/sbin/readDB.awk new file mode 100644 index 0000000000..2080fe5f79 --- /dev/null +++ b/package/mtk/applications/wrtbwmon/net/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/mtk/applications/wrtbwmon/net/usr/sbin/wrtbwmon b/package/mtk/applications/wrtbwmon/net/usr/sbin/wrtbwmon new file mode 100644 index 0000000000..91089a1eee --- /dev/null +++ b/package/mtk/applications/wrtbwmon/net/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/mtk/applications/wrtbwmon/net/usr/share/wrtbwmon/usage.htm1 b/package/mtk/applications/wrtbwmon/net/usr/share/wrtbwmon/usage.htm1 new file mode 100644 index 0000000000..c469cb7d91 --- /dev/null +++ b/package/mtk/applications/wrtbwmon/net/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/target/linux/mediatek/Makefile b/target/linux/mediatek/Makefile index 4472c4c961..3fc7f23f7e 100644 --- a/target/linux/mediatek/Makefile +++ b/target/linux/mediatek/Makefile @@ -14,6 +14,6 @@ KERNEL_TESTING_PATCHVER:=5.4 include $(INCLUDE_DIR)/target.mk DEFAULT_PACKAGES += \ kmod-leds-gpio kmod-gpio-button-hotplug \ - autocore-arm uboot-envtools luci-app-quickstart luci-app-rtbwmon + autocore-arm uboot-envtools luci-app-quickstart luci-app-wrtbwmon $(eval $(call BuildTarget)) diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c index 3a02a05ee1..4e9e854080 100644 --- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c +++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c @@ -554,6 +554,7 @@ unsigned int do_hnat_ge_to_ext(struct sk_buff *skb, const char *func) return -1; } +struct hnat_accounting diffglobal[2][32769]; static void mtk_hnat_nf_update_ipt(struct sk_buff *skb) { @@ -564,8 +565,6 @@ static void mtk_hnat_nf_update_ipt(struct sk_buff *skb) enum ip_conntrack_info ctinfo; struct hnat_accounting diff; - if (hnat_priv->data->per_flow_accounting && hnat_priv->nf_stat_en) - return ; if (skb->protocol == htons(ETH_P_IPV6) && !hnat_priv->ipv6_en) { return ; } @@ -588,14 +587,15 @@ static void mtk_hnat_nf_update_ipt(struct sk_buff *skb) if (ct) { if (!hnat_get_count(hnat_priv, skb_hnat_ppe(skb), skb_hnat_entry(skb), &diff)) return; + + if ((skb_hnat_entry(skb) <= hnat_priv->foe_etry_num) && (skb_hnat_entry(skb) >0)) + diffglobal[skb_hnat_ppe(skb)][skb_hnat_entry(skb)]= diff; acct = nf_conn_acct_find(ct); if (acct) { counter = acct->counter; atomic64_set(&counter[CTINFO2DIR(ctinfo)].diff_packets, diff.packets); atomic64_set(&counter[CTINFO2DIR(ctinfo)].diff_bytes, diff.bytes); - atomic64_add(diff.packets, &counter[CTINFO2DIR(ctinfo)].packets); - atomic64_add(diff.bytes, &counter[CTINFO2DIR(ctinfo)].bytes); } } @@ -850,7 +850,6 @@ static unsigned int is_ppe_support_type(struct sk_buff *skb) return 0; } -/* update hnat count to nf_conntrack and iptables by keepalive */ static unsigned int mtk_hnat_nf_conntrack(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) @@ -2147,6 +2146,29 @@ static void mtk_hnat_dscp_update(struct sk_buff *skb, struct foe_entry *entry) +static void mtk_hnat_nf_update(struct sk_buff *skb) +{ + struct nf_conn *ct; + struct nf_conn_acct *acct; + struct nf_conn_counter *counter; + enum ip_conntrack_info ctinfo; + struct hnat_accounting diff; + + ct = nf_ct_get(skb, &ctinfo); + if (ct) { + if ((skb_hnat_entry(skb) <= hnat_priv->foe_etry_num) && (skb_hnat_entry(skb) >0)) + diff= diffglobal[skb_hnat_ppe(skb)][skb_hnat_entry(skb)]; + else return; + + + acct = nf_conn_acct_find(ct); + if (acct) { + counter = acct->counter; + atomic64_add(diff.packets, &counter[CTINFO2DIR(ctinfo)].packets); + atomic64_add(diff.bytes, &counter[CTINFO2DIR(ctinfo)].bytes); + } + } +} static unsigned int mtk_hnat_nf_post_routing( struct sk_buff *skb, const struct net_device *out, @@ -2205,6 +2227,10 @@ static unsigned int mtk_hnat_nf_post_routing( skb_to_hnat_info(skb, out, entry, &hw_path); break; case HIT_BIND_KEEPALIVE_DUP_OLD_HDR: + /* update hnat count to nf_conntrack by keepalive */ + if (hnat_priv->data->per_flow_accounting && hnat_priv->nf_stat_en) + mtk_hnat_nf_update(skb); + if (fn && !mtk_hnat_accel_type(skb)) break;