Repository: brvphoenix/luci-app-wrtbwmon Branch: master Commit: f1b59b2309a0 Files: 16 Total size: 55.3 KB Directory structure: gitextract_89cv8w6g/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── CI.yml │ └── stale.yml ├── README.md └── luci-app-wrtbwmon/ ├── Makefile ├── htdocs/ │ └── luci-static/ │ └── resources/ │ └── view/ │ └── wrtbwmon/ │ ├── config.js │ ├── custom.js │ ├── details.js │ └── wrtbwmon.css ├── po/ │ ├── templates/ │ │ └── wrtbwmon.pot │ ├── zh_Hans/ │ │ └── wrtbwmon.po │ └── zh_Hant/ │ └── wrtbwmon.po └── root/ ├── etc/ │ └── luci-wrtbwmon.conf └── usr/ ├── libexec/ │ └── rpcd/ │ └── luci.wrtbwmon └── share/ ├── luci/ │ └── menu.d/ │ └── luci-app-wrtbwmon.json └── rpcd/ └── acl.d/ └── luci-app-wrtbwmon.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] ================================================ FILE: .github/workflows/CI.yml ================================================ name: CI on: push: branches: - master tags: - 'release-*' paths: - 'luci-app-wrtbwmon/**' pull_request: branches: - master paths: - 'luci-app-wrtbwmon/**' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: build: name: Build the IPK runs-on: ubuntu-latest container: image: ghcr.io/openwrt/sdk:x86-64-23.05-SNAPSHOT options: --user root steps: - name: Checkout uses: actions/checkout@v4 - name: Docker Build working-directory: /builder run: | sed \ -e 's,git\.openwrt\.org/feed/,github.com/openwrt/,' \ -e 's,git\.openwrt\.org/openwrt/,github.com/openwrt/,' \ -e 's,git\.openwrt\.org/project/,github.com/openwrt/,' \ feeds.conf.default | grep -Ev "^src-git(-full)? (routing|telephony) .*" > feeds.conf echo "src-cpy local ${GITHUB_WORKSPACE}" >> feeds.conf ./scripts/feeds update -f luci local ./scripts/feeds install -p local luci-app-wrtbwmon make defconfig make package/luci-app-wrtbwmon/compile V=sc -j$(nproc) BUILD_LOG=1 tar -cJf logs.tar.xz logs - name: Release if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/release-') uses: actions/github-script@v7 with: script: | const tag = context.ref.replace("refs/tags/", ""); try { // Get release for this tag const release = await github.rest.repos.getReleaseByTag({ owner: context.repo.owner, repo: context.repo.repo, tag: tag, }); // Delete obsolete release await github.rest.repos.deleteRelease({ owner: context.repo.owner, repo: context.repo.repo, release_id: release.data.id, }); } catch(err) { console.log(err); } finally { // Create release for this tag const release = await github.rest.repos.createRelease({ owner: context.repo.owner, repo: context.repo.repo, tag_name: tag, draft: false, prerelease: true, }); // Upload the release asset const fs = require('fs'); const patterns = ['/builder/bin/packages/x86_64/*/*wrtbwmon*.ipk'] const globber = await glob.create(patterns.join('\n')) for await (const file of globber.globGenerator()) { await github.rest.repos.uploadReleaseAsset({ owner: context.repo.owner, repo: context.repo.repo, release_id: release.data.id, name: file.substr(file.lastIndexOf('/') + 1), data: await fs.readFileSync(file) }); } } - name: Upload app if: ${{ ! (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/release-')) }} uses: actions/upload-artifact@v4 with: name: luci-app-wrtbwmon path: /builder/bin/packages/x86_64/*/*wrtbwmon* if-no-files-found: error - name: Upload Log if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: buildlog path: /builder/logs.tar.xz ================================================ FILE: .github/workflows/stale.yml ================================================ name: "Close stale issues" on: schedule: - cron: "0 0 * * *" jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' stale-pr-message: 'This pr is stale has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' stale-issue-label: 'no-issue-activity' exempt-issue-labels: 'awaiting-approval,work-in-progress' stale-pr-label: 'no-pr-activity' exempt-pr-labels: 'awaiting-approval,work-in-progress' days-before-stale: 120 days-before-close: 5 ================================================ FILE: README.md ================================================ # luci-app-wrtbwmon [![CI](https://github.com/brvphoenix/luci-app-wrtbwmon/workflows/CI/badge.svg)](https://github.com/brvphoenix/luci-app-wrtbwmon/actions) [![GitHub All Releases](https://img.shields.io/github/downloads/brvphoenix/luci-app-wrtbwmon/total)](https://github.com/brvphoenix/luci-app-wrtbwmon/releases) [![Lastest Release](https://img.shields.io/github/v/release/brvphoenix/luci-app-wrtbwmon.svg?logo=github&cacheSeconds=10&label=latest)](https://github.com/brvphoenix/luci-app-wrtbwmon/releases/latest) This repo provides yet another LuCI module for wrtbwmon, which has similar features with [Kiougar's one](https://github.com/Kiougar/luci-wrtbwmon). The differnence is that this one has more features supported: 1. Support IPV6. 1. Identify a host by the unique MAC rather than its IP. 1. Use the progress bar to display the total bandwidth. 1. For brevity, some columns are hidden by default. 1. Convert to client side for rendering just as what the new openwrt release has done. ## Known issues * **Incompatible** with the [pyrovski's wrtbwmon](https://github.com/pyrovski/wrtbwmon). **You must download the compatible one from [here](https://github.com/brvphoenix/wrtbwmon)**. * **Incompatible** with Routing/NAT, Flow Offloading and so on. ## Screenshots ![Screenshots](https://github.com/brvphoenix/luci-app-wrtbwmon/blob/master/screenshot.png?raw=true) ## Downloading Openwrt 19.07 has been fully supported after commit: [ff4909d](https://github.com/brvphoenix/luci-app-wrtbwmon/tree/ff4909d8f5d06fee87f7ec5a365ac5dde6492130). * `openwrt-19.07.3 ... latest`: [latest](https://github.com/brvphoenix/luci-app-wrtbwmon/releases/latest) * `openwrt-19.07.0 ... 19.07.2`: [release-2.0.7](https://github.com/brvphoenix/luci-app-wrtbwmon/releases/tag/release-2.0.7) * `openwrt-18.06`: [release-1.6.3](https://github.com/brvphoenix/luci-app-wrtbwmon/releases/tag/release-1.6.3) After installing, you will see a new `Traffic status` menu item in the `Network` menu list in the LuCI Page. ## Information In principle, the lua version (based on the old openwrt 18.06) has been dropped support since [ff4909d](https://github.com/brvphoenix/luci-app-wrtbwmon/tree/ff4909d8f5d06fee87f7ec5a365ac5dde6492130), and the new features will not backport to the old lua version. However, it is welcomed if someone can implement it and make a pr. If anyone would like to help translate this luci app, just upload the translation files or make a pr. ## Credits Thanks to * [Kiougar](https://github.com/Kiougar/luci-wrtbwmon) for the original codes. * [pyrovski](https://github.com/pyrovski) for creating `wrtbwmon`. ================================================ FILE: luci-app-wrtbwmon/Makefile ================================================ # # 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 ================================================ FILE: luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js ================================================ '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')); } }); ================================================ FILE: luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js ================================================ '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 }); ================================================ FILE: luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js ================================================ '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 }); ================================================ FILE: luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css ================================================ .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; } ================================================ FILE: luci-app-wrtbwmon/po/templates/wrtbwmon.pot ================================================ 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 "" ================================================ FILE: luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po ================================================ 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 "更改将会重置,确定吗?" ================================================ FILE: luci-app-wrtbwmon/po/zh_Hant/wrtbwmon.po ================================================ 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 "顯示更多欄位:" ================================================ FILE: luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf ================================================ { "protocol": "ipv4", "interval": "5", "showMore": false, "showZero": true, "useBits": false, "useMultiple": "1000", "useDSL": false, "upstream": "100", "downstream": "100", "hideMACs": [] } ================================================ FILE: luci-app-wrtbwmon/root/usr/libexec/rpcd/luci.wrtbwmon ================================================ #!/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 ================================================ FILE: luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json ================================================ { "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" } } } ================================================ FILE: luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json ================================================ { "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" ] } } }