├── .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 └── screenshot.png /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "*" 14 | update-types: ["version-update:semver-minor", "version-update:semver-patch"] 15 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - 'release-*' 9 | paths: 10 | - 'luci-app-wrtbwmon/**' 11 | pull_request: 12 | branches: 13 | - master 14 | paths: 15 | - 'luci-app-wrtbwmon/**' 16 | workflow_dispatch: 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | build: 24 | name: Build the IPK 25 | runs-on: ubuntu-latest 26 | container: 27 | image: ghcr.io/openwrt/sdk:x86-64-23.05-SNAPSHOT 28 | options: --user root 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | - name: Docker Build 33 | working-directory: /builder 34 | run: | 35 | sed \ 36 | -e 's,git\.openwrt\.org/feed/,github.com/openwrt/,' \ 37 | -e 's,git\.openwrt\.org/openwrt/,github.com/openwrt/,' \ 38 | -e 's,git\.openwrt\.org/project/,github.com/openwrt/,' \ 39 | feeds.conf.default | grep -Ev "^src-git(-full)? (routing|telephony) .*" > feeds.conf 40 | 41 | echo "src-cpy local ${GITHUB_WORKSPACE}" >> feeds.conf 42 | 43 | ./scripts/feeds update -f luci local 44 | ./scripts/feeds install -p local luci-app-wrtbwmon 45 | 46 | make defconfig 47 | make package/luci-app-wrtbwmon/compile V=sc -j$(nproc) BUILD_LOG=1 48 | tar -cJf logs.tar.xz logs 49 | - name: Release 50 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/release-') 51 | uses: actions/github-script@v7 52 | with: 53 | script: | 54 | const tag = context.ref.replace("refs/tags/", ""); 55 | try { 56 | // Get release for this tag 57 | const release = await github.rest.repos.getReleaseByTag({ 58 | owner: context.repo.owner, 59 | repo: context.repo.repo, 60 | tag: tag, 61 | }); 62 | // Delete obsolete release 63 | await github.rest.repos.deleteRelease({ 64 | owner: context.repo.owner, 65 | repo: context.repo.repo, 66 | release_id: release.data.id, 67 | }); 68 | } 69 | catch(err) { 70 | console.log(err); 71 | } 72 | finally { 73 | // Create release for this tag 74 | const release = await github.rest.repos.createRelease({ 75 | owner: context.repo.owner, 76 | repo: context.repo.repo, 77 | tag_name: tag, 78 | draft: false, 79 | prerelease: true, 80 | }); 81 | // Upload the release asset 82 | const fs = require('fs'); 83 | const patterns = ['/builder/bin/packages/x86_64/*/*wrtbwmon*.ipk'] 84 | const globber = await glob.create(patterns.join('\n')) 85 | for await (const file of globber.globGenerator()) { 86 | await github.rest.repos.uploadReleaseAsset({ 87 | owner: context.repo.owner, 88 | repo: context.repo.repo, 89 | release_id: release.data.id, 90 | name: file.substr(file.lastIndexOf('/') + 1), 91 | data: await fs.readFileSync(file) 92 | }); 93 | } 94 | } 95 | - name: Upload app 96 | if: ${{ ! (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/release-')) }} 97 | uses: actions/upload-artifact@v4 98 | with: 99 | name: luci-app-wrtbwmon 100 | path: /builder/bin/packages/x86_64/*/*wrtbwmon* 101 | if-no-files-found: error 102 | - name: Upload Log 103 | if: ${{ always() }} 104 | uses: actions/upload-artifact@v4 105 | with: 106 | name: buildlog 107 | path: /builder/logs.tar.xz 108 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | 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' 14 | 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' 15 | stale-issue-label: 'no-issue-activity' 16 | exempt-issue-labels: 'awaiting-approval,work-in-progress' 17 | stale-pr-label: 'no-pr-activity' 18 | exempt-pr-labels: 'awaiting-approval,work-in-progress' 19 | days-before-stale: 120 20 | days-before-close: 5 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luci-app-wrtbwmon 2 | 3 | [![CI](https://github.com/brvphoenix/luci-app-wrtbwmon/workflows/CI/badge.svg)](https://github.com/brvphoenix/luci-app-wrtbwmon/actions) 4 | [![GitHub All Releases](https://img.shields.io/github/downloads/brvphoenix/luci-app-wrtbwmon/total)](https://github.com/brvphoenix/luci-app-wrtbwmon/releases) 5 | [![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) 6 | 7 | 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: 8 | 1. Support IPV6. 9 | 1. Identify a host by the unique MAC rather than its IP. 10 | 1. Use the progress bar to display the total bandwidth. 11 | 1. For brevity, some columns are hidden by default. 12 | 1. Convert to client side for rendering just as what the new openwrt release has done. 13 | 14 | ## Known issues 15 | * **Incompatible** with the [pyrovski's wrtbwmon](https://github.com/pyrovski/wrtbwmon). **You must download the compatible one from [here](https://github.com/brvphoenix/wrtbwmon)**. 16 | * **Incompatible** with Routing/NAT, Flow Offloading and so on. 17 | 18 | ## Screenshots 19 | ![Screenshots](https://github.com/brvphoenix/luci-app-wrtbwmon/blob/master/screenshot.png?raw=true) 20 | 21 | ## Downloading 22 | Openwrt 19.07 has been fully supported after commit: [ff4909d](https://github.com/brvphoenix/luci-app-wrtbwmon/tree/ff4909d8f5d06fee87f7ec5a365ac5dde6492130). 23 | * `openwrt-19.07.3 ... latest`: [latest](https://github.com/brvphoenix/luci-app-wrtbwmon/releases/latest) 24 | * `openwrt-19.07.0 ... 19.07.2`: [release-2.0.7](https://github.com/brvphoenix/luci-app-wrtbwmon/releases/tag/release-2.0.7) 25 | * `openwrt-18.06`: [release-1.6.3](https://github.com/brvphoenix/luci-app-wrtbwmon/releases/tag/release-1.6.3) 26 | 27 | After installing, you will see a new `Traffic status` menu item in the `Network` menu list in the LuCI Page. 28 | 29 | ## Information 30 | 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. 31 | 32 | If anyone would like to help translate this luci app, just upload the translation files or make a pr. 33 | 34 | ## Credits 35 | Thanks to 36 | * [Kiougar](https://github.com/Kiougar/luci-wrtbwmon) for the original codes. 37 | * [pyrovski](https://github.com/pyrovski) for creating `wrtbwmon`. 38 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 xxx 3 | # 4 | # This is free software, licensed under the Apache License, Version 2.0 . 5 | # 6 | 7 | include $(TOPDIR)/rules.mk 8 | 9 | PKG_NAME:=luci-app-wrtbwmon 10 | PKG_VERSION:=2.0.13 11 | 12 | PKG_LICENSE:=Apache-2.0 13 | PKG_MAINTAINER:= 14 | 15 | LUCI_TITLE:=A Luci module that uses wrtbwmon to track bandwidth usage 16 | LUCI_DEPENDS:=+wrtbwmon 17 | LUCI_PKGARCH:=all 18 | 19 | include $(TOPDIR)/feeds/luci/luci.mk 20 | 21 | # call BuildPackage - OpenWrt buildroot signature 22 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require form'; 3 | 'require rpc'; 4 | 'require view'; 5 | 6 | var callChangeDatabasePath = rpc.declare({ 7 | object: 'luci.wrtbwmon', 8 | method: 'change_db_path', 9 | params: [ 'state' ] 10 | }); 11 | 12 | return view.extend({ 13 | render: function() { 14 | var m, s, o; 15 | 16 | m = new form.Map('wrtbwmon', _('Usage - Configuration')); 17 | 18 | s = m.section(form.NamedSection, 'general', 'wrtbwmon', _('General settings')); 19 | s.addremove = false; 20 | 21 | o = s.option(form.Flag, 'enabled', _('Keep running in the background')); 22 | o.rmempty = true; 23 | 24 | o = s.option(form.Value, 'path', _('Database path'), _('This box is used to select the Database path, which is /tmp/usage.db by default.')); 25 | o.value('/tmp/usage.db'); 26 | o.value('/etc/usage.db'); 27 | o.default = '/tmp/usage.db'; 28 | o.rmempty = false; 29 | 30 | return m.render(); 31 | }, 32 | 33 | handleSaveApply: function(ev, mode) { 34 | return callChangeDatabasePath('before') 35 | .then(this.super.bind(this, 'handleSaveApply', arguments)) 36 | .then(callChangeDatabasePath.bind(this, 'after')); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/custom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require fs'; 3 | 'require ui'; 4 | 'require view'; 5 | 6 | return view.extend({ 7 | load: function() { 8 | return fs.trimmed('/etc/wrtbwmon.user').catch(function(err) { 9 | ui.addNotification(null, E('p', {}, _('Unable to load the customized hostname file: ' + err.message))); 10 | return ''; 11 | }); 12 | }, 13 | 14 | render: function(data) { 15 | return E('div', {'class': 'cbi-map'}, [ 16 | E('h2', {'name': 'content'}, [ _('Usage - Custom User File') ]), 17 | E('div', {'class': 'cbi-map-descr'}, [ 18 | _('Each line must have the following format:'), 19 | E('em', {'style': 'color:red'}, '00:aa:bb:cc:ee:ff,hostname') 20 | ]), 21 | E('div', {'class': 'cbi-section'}, [ 22 | E('textarea', { 23 | 'id': 'custom_hosts', 24 | 'style': 'width: 100%;padding: .5em;', 25 | 'rows': 20 26 | }, data) 27 | ]) 28 | ]); 29 | }, 30 | 31 | handleSave: function(ev) { 32 | var map = document.querySelector('#custom_hosts'); 33 | return fs.write('/etc/wrtbwmon.user', map.value.trim().replace(/\r\n/g, '\n') + '\n'); 34 | }, 35 | handleSaveApply: null, 36 | handleReset: null 37 | }); 38 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/details.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require dom'; 3 | 'require fs'; 4 | 'require poll'; 5 | 'require rpc'; 6 | 'require ui'; 7 | 'require validation'; 8 | 'require view'; 9 | 10 | var cachedData = []; 11 | var luciConfig = '/etc/luci-wrtbwmon.conf'; 12 | var hostNameFile = '/etc/wrtbwmon.user'; 13 | var columns = { 14 | thClient: _('Clients'), 15 | thMAC: _('MAC'), 16 | thDownload: _('Download'), 17 | thUpload: _('Upload'), 18 | thTotalDown: _('Total Down'), 19 | thTotalUp: _('Total Up'), 20 | thTotal: _('Total'), 21 | thFirstSeen: _('First Seen'), 22 | thLastSeen: _('Last Seen') 23 | }; 24 | 25 | var callLuciDHCPLeases = rpc.declare({ 26 | object: 'luci-rpc', 27 | method: 'getDHCPLeases', 28 | expect: { '': {} } 29 | }); 30 | 31 | var callLuciDSLStatus = rpc.declare({ 32 | object: 'luci-rpc', 33 | method: 'getDSLStatus', 34 | expect: { '': {} } 35 | }); 36 | 37 | var callGetDatabaseRaw = rpc.declare({ 38 | object: 'luci.wrtbwmon', 39 | method: 'get_db_raw', 40 | params: [ 'protocol' ] 41 | }); 42 | 43 | var callGetDatabasePath = rpc.declare({ 44 | object: 'luci.wrtbwmon', 45 | method: 'get_db_path', 46 | params: [ 'protocol' ] 47 | }); 48 | 49 | var callRemoveDatabase = rpc.declare({ 50 | object: 'luci.wrtbwmon', 51 | method: 'remove_db', 52 | params: [ 'protocol' ] 53 | }); 54 | 55 | function $(tid) { 56 | return document.getElementById(tid); 57 | } 58 | 59 | function clickToResetDatabase(settings, table, updated, updating, ev) { 60 | if (confirm(_('This will delete the database file. Are you sure?'))) { 61 | return callRemoveDatabase(settings.protocol) 62 | .then(function() { 63 | updateData(settings, table, updated, updating, true); 64 | }); 65 | } 66 | } 67 | 68 | function clickToSaveConfig(keylist, cstrs) { 69 | var data = {}; 70 | 71 | for (var i = 0; i < keylist.length; i++) { 72 | data[keylist[i]] = cstrs[keylist[i]].getValue(); 73 | } 74 | 75 | ui.showModal(_('Configuration'), [ 76 | E('p', { 'class': 'spinning' }, _('Saving configuration data...')) 77 | ]); 78 | 79 | return fs.write(luciConfig, JSON.stringify(data, undefined, '\t') + '\n') 80 | .catch(function(err) { 81 | ui.addNotification(null, E('p', {}, [ _('Unable to save %s: %s').format(luciConfig, err) ])); 82 | }) 83 | .then(ui.hideModal) 84 | .then(function() { document.location.reload(); }); 85 | } 86 | 87 | function clickToSelectInterval(settings, updating, ev) { 88 | if (ev.target.value > 0) { 89 | settings.interval = parseInt(ev.target.value); 90 | if (!poll.active()) poll.start(); 91 | } 92 | else { 93 | poll.stop(); 94 | setUpdateMessage(updating, -1); 95 | } 96 | } 97 | 98 | function clickToSelectProtocol(settings, table, updated, updating, ev) { 99 | settings.protocol = ev.target.value; 100 | updateData(settings, table, updated, updating, true); 101 | } 102 | 103 | function createOption(args, val) { 104 | var cstr = args[0], title = args[1], desc = args.slice(-1), widget, frame; 105 | widget = args.length == 4 ? new cstr(val, args[2]) : new cstr(val, args[2], args[3]); 106 | 107 | frame = E('div', {'class': 'cbi-value'}, [ 108 | E('label', {'class': 'cbi-value-title'}, title), 109 | E('div', {'class': 'cbi-value-field'}, E('div', {}, widget.render())) 110 | ]); 111 | 112 | if (desc && desc != '') 113 | dom.append(frame.lastChild, E('div', { 'class': 'cbi-value-description' }, desc)); 114 | 115 | return [widget, frame]; 116 | } 117 | 118 | function displayTable(tb, settings) { 119 | var elm, elmID, col, sortedBy, flag, IPVer; 120 | 121 | elm = tb.querySelector('.th.sorted'); 122 | elmID = elm ? elm.id : 'thTotal'; 123 | sortedBy = elm && elm.classList.contains('ascent') ? 'asc' : 'desc'; 124 | 125 | col = Object.keys(columns).indexOf(elmID); 126 | IPVer = col == 0 ? settings.protocol : null; 127 | flag = sortedBy == 'desc' ? 1 : -1; 128 | 129 | cachedData[0].sort(sortTable.bind(this, col, IPVer, flag)); 130 | 131 | //console.time('show'); 132 | updateTable(tb, cachedData, '%s'.format(_('Collecting data...')), settings); 133 | //console.timeEnd('show'); 134 | progressbar('downstream', cachedData[1][0], settings.downstream, settings.useBits, settings.useMultiple); 135 | progressbar('upstream', cachedData[1][1], settings.upstream, settings.useBits, settings.useMultiple); 136 | } 137 | 138 | function formatSize(size, useBits, useMultiple) { 139 | // String.format automatically adds the i for KiB if the multiple is 1024 140 | return String.format('%%%s.2m%s'.format(useMultiple, (useBits ? 'bit' : 'B')), useBits ? size * 8 : size); 141 | } 142 | 143 | function formatSpeed(speed, useBits, useMultiple) { 144 | return formatSize(speed, useBits, useMultiple) + '/s'; 145 | } 146 | 147 | function formatDate(d) { 148 | var Y = d.getFullYear(), M = d.getMonth() + 1, D = d.getDate(), 149 | hh = d.getHours(), mm = d.getMinutes(), ss = d.getSeconds(); 150 | return '%04d/%02d/%02d %02d:%02d:%02d'.format(Y, M, D, hh, mm, ss); 151 | } 152 | 153 | function getDSLBandwidth() { 154 | return callLuciDSLStatus().then(function(res) { 155 | return { 156 | upstream : res.max_data_rate_up || null, 157 | downstream : res.max_data_rate_down || null 158 | }; 159 | }); 160 | } 161 | 162 | function handleConfig(ev) { 163 | ui.showModal(_('Configuration'), [ 164 | E('p', { 'class': 'spinning' }, _('Loading configuration data...')) 165 | ]); 166 | 167 | parseDefaultSettings(luciConfig) 168 | .then(function(settings) { 169 | var arglist, keylist = Object.keys(settings), res, cstrs = {}, node = [], body; 170 | 171 | arglist = [ 172 | [ui.Select, _('Default Protocol'), {'ipv4': _('ipv4'), 'ipv6': _('ipv6')}, {}, ''], 173 | [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']}, ''], 174 | [ui.Dropdown, _('Default Columns'), columns, {multiple: true, sort: false, custom_placeholder: '', dropdown_items: 3}, ''], 175 | [ui.Checkbox, _('Show Zeros'), {value_enabled: true, value_disabled: false}, ''], 176 | [ui.Checkbox, _('Transfer Speed in Bits'), {value_enabled: true, value_disabled: false}, ''], 177 | [ui.Select, _('Multiple of Unit'), {'1000': _('SI - 1000'), '1024': _('IEC - 1024')}, {}, ''], 178 | [ui.Checkbox, _('Use DSL Bandwidth'), {value_enabled: true, value_disabled: false}, ''], 179 | [ui.Textfield, _('Upstream Bandwidth'), {datatype: 'ufloat'}, 'Mbps'], 180 | [ui.Textfield, _('Downstream Bandwidth'), {datatype: 'ufloat'}, 'Mbps'], 181 | [ui.DynamicList, _('Hide MAC Addresses'), '', {datatype: 'macaddr'}, ''] 182 | ]; // [constructor, label(, all_choices), options, description] 183 | 184 | for (var i = 0; i < keylist.length; i++) { 185 | res = createOption(arglist[i], settings[keylist[i]]); 186 | cstrs[keylist[i]] = res[0]; 187 | node.push(res[1]); 188 | } 189 | 190 | body = [ 191 | E('p', {}, _('Configure the default values for luci-app-wrtbwmon.')), 192 | E('div', {}, node), 193 | E('div', { 'class': 'right' }, [ 194 | E('div', { 195 | 'class': 'btn cbi-button-neutral', 196 | 'click': ui.hideModal 197 | }, _('Cancel')), 198 | ' ', 199 | E('div', { 200 | 'class': 'btn cbi-button-positive', 201 | 'click': clickToSaveConfig.bind(this, keylist, cstrs), 202 | 'disabled': (L.hasViewPermission ? !L.hasViewPermission() : null) || null 203 | }, _('Save')) 204 | ]) 205 | ]; 206 | ui.showModal(_('Configuration'), body); 207 | }) 208 | } 209 | 210 | function loadCss(path) { 211 | var head = document.head || document.getElementsByTagName('head')[0]; 212 | var link = E('link', { 213 | 'rel': 'stylesheet', 214 | 'href': path, 215 | 'type': 'text/css' 216 | }); 217 | 218 | head.appendChild(link); 219 | } 220 | 221 | function parseDatabase(raw, hosts, showZero, hideMACs) { 222 | var values = [], 223 | totals = [0, 0, 0, 0, 0], 224 | rows = raw.trim().split(/\r?\n|\r/g), 225 | rowIndex = [1, 0, 3, 4, 5, 6, 7, 8, 9, 0]; 226 | 227 | rows.shift(); 228 | 229 | for (var i = 0; i < rows.length; i++) { 230 | var row = rows[i].split(','); 231 | if ((!showZero && row[7] == 0) || hideMACs.indexOf(row[0]) >= 0) continue; 232 | 233 | for (var j = 0; j < totals.length; j++) { 234 | totals[j] += parseInt(row[3 + j]); 235 | } 236 | 237 | var newRow = rowIndex.map(function(i) { return row[i] }); 238 | if (newRow[1].toLowerCase() in hosts) { 239 | newRow[9] = hosts[newRow[1].toLowerCase()]; 240 | } 241 | values.push(newRow); 242 | } 243 | 244 | return [values, totals]; 245 | } 246 | 247 | function parseDefaultSettings(file) { 248 | var defaultColumns = ['thClient', 'thDownload', 'thUpload', 'thTotalDown', 'thTotalUp', 'thTotal'], 249 | keylist = ['protocol', 'interval', 'showColumns', 'showZero', 'useBits', 'useMultiple', 'useDSL', 'upstream', 'downstream', 'hideMACs'], 250 | valuelist = ['ipv4', '5', defaultColumns, true, false, '1000', false, '100', '100', []]; 251 | 252 | return fs.read_direct(file, 'json').then(function(oldSettings) { 253 | var settings = {}; 254 | for (var i = 0; i < keylist.length; i++) { 255 | if (!(keylist[i] in oldSettings)) 256 | settings[keylist[i]] = valuelist[i]; 257 | else 258 | settings[keylist[i]] = oldSettings[keylist[i]]; 259 | } 260 | 261 | if (settings.useDSL) { 262 | return getDSLBandwidth().then(function(dsl) { 263 | for (var s in dsl) 264 | settings[s] = dsl[s]; 265 | return settings; 266 | }); 267 | } 268 | else { 269 | return settings; 270 | } 271 | }) 272 | .catch(function() { return {} }); 273 | } 274 | 275 | function progressbar(query, v, m, useBits, useMultiple) { 276 | // v = B/s, m = Mb/s 277 | var pg = $(query), 278 | vn = (v * 8) || 0, 279 | mn = (m || 100) * Math.pow(1000, 2), 280 | fv = formatSpeed(v, useBits, useMultiple), 281 | pc = '%.2f'.format((100 / mn) * vn), 282 | wt = Math.floor(pc > 100 ? 100 : pc), 283 | bgc = (pc >= 95 ? 'red' : (pc >= 80 ? 'darkorange' : (pc >= 60 ? 'yellow' : 'lime'))); 284 | if (pg) { 285 | pg.firstElementChild.style.width = wt + '%'; 286 | pg.firstElementChild.style.background = bgc; 287 | pg.setAttribute('title', '%s (%f%%)'.format(fv, pc)); 288 | } 289 | } 290 | 291 | function setupThisDOM(settings, table) { 292 | document.addEventListener('poll-stop', function() { 293 | $('selectInterval').value = -1; 294 | }); 295 | 296 | document.addEventListener('poll-start', function() { 297 | $('selectInterval').value = settings.interval; 298 | }); 299 | 300 | table.querySelectorAll('.th').forEach(function(e) { 301 | if (e) { 302 | e.addEventListener('click', function (ev) { 303 | setSortedColumn(ev.target); 304 | displayTable(table, settings); 305 | }); 306 | 307 | if (settings.showColumns.indexOf(e.id) >= 0) 308 | e.classList.remove('hide'); 309 | else 310 | e.classList.add('hide'); 311 | 312 | } 313 | }); 314 | } 315 | 316 | function renameFile(str, tag) { 317 | var n = str.lastIndexOf('/'), fn = n > -1 ? str.slice(n + 1) : str, dir = n > -1 ? str.slice(0, n + 1) : ''; 318 | var n = fn.lastIndexOf('.'), bn = n > -1 ? fn.slice(0, n) : fn; 319 | var n = fn.lastIndexOf('.'), en = n > -1 ? fn.slice(n + 1) : ''; 320 | return dir + bn + '.' + tag + (en ? '.' + en : ''); 321 | } 322 | 323 | function resolveCustomizedHostName() { 324 | return fs.stat(hostNameFile).then(function() { 325 | return fs.read_direct(hostNameFile).then(function(raw) { 326 | var arr = raw.trim().split(/\r?\n/), hosts = {}, row; 327 | for (var i = 0; i < arr.length; i++) { 328 | row = arr[i].split(','); 329 | if (row.length == 2 && row[0]) 330 | hosts[row[0].toLowerCase()] = row[1]; 331 | } 332 | return hosts; 333 | }) 334 | }) 335 | .catch(function() { return []; }); 336 | } 337 | 338 | function resolveHostNameByMACAddr() { 339 | return Promise.all([ 340 | resolveCustomizedHostName(), 341 | callLuciDHCPLeases() 342 | ]).then(function(res) { 343 | var hosts = res[0]; 344 | for (var key in res[1]) { 345 | var leases = Array.isArray(res[1][key]) ? res[1][key] : []; 346 | for (var i = 0; i < leases.length; i++) { 347 | if(leases[i].macaddr) { 348 | var macaddr = leases[i].macaddr.toLowerCase(); 349 | if (!(macaddr in hosts) && Boolean(leases[i].hostname)) 350 | hosts[macaddr] = leases[i].hostname; 351 | } 352 | } 353 | } 354 | return hosts; 355 | }); 356 | } 357 | 358 | function setSortedColumn(sorting) { 359 | var sorted = document.querySelector('.th.sorted') || $('thTotal'); 360 | 361 | if (sorting.isSameNode(sorted)) { 362 | sorting.classList.toggle('ascent'); 363 | } 364 | else { 365 | sorting.classList.add('sorted'); 366 | sorted.classList.remove('sorted', 'ascent'); 367 | } 368 | } 369 | 370 | function setUpdateMessage(e, sec) { 371 | e.innerHTML = sec < 0 ? '' : _('Updating again in %s second(s).').format('' + sec + ''); 372 | } 373 | 374 | function sortTable(col, IPVer, flag, x, y) { 375 | var byCol = x[col] == y[col] ? 1 : col; 376 | var a = x[byCol], b = y[byCol]; 377 | 378 | if (!IPVer || byCol != 0) { 379 | if (!(a.match(/\D/g) || b.match(/\D/g))) 380 | a = parseInt(a), b = parseInt(b); 381 | } 382 | else { 383 | IPVer == 'ipv4' 384 | ? (a = validation.parseIPv4(a) || [0, 0, 0, 0], b = validation.parseIPv4(b) || [0, 0, 0, 0]) 385 | : (a = validation.parseIPv6(a) || [0, 0, 0, 0, 0, 0, 0, 0], b = validation.parseIPv6(b) || [0, 0, 0, 0, 0, 0, 0, 0]); 386 | } 387 | 388 | if (Array.isArray(a) && Array.isArray(b)) { 389 | for (var i = 0; i < a.length; i++) { 390 | if (a[i] != b[i]) { 391 | return (b[i] - a[i]) * flag; 392 | } 393 | } 394 | return 0; 395 | } 396 | 397 | return a == b ? 0 : (a < b ? 1 : -1) * flag; 398 | } 399 | 400 | function updateData(settings, table, updated, updating, once) { 401 | var tick = poll.tick, 402 | interval = settings.interval, 403 | sec = (interval - tick % interval) % interval; 404 | if (!sec || once) { 405 | callGetDatabasePath() 406 | .then(function(res) { 407 | var params = settings.protocol == 'ipv4' ? '-4' : '-6'; 408 | return fs.exec_direct('/usr/sbin/wrtbwmon', [params, '-f', res.file_4]) 409 | }) 410 | .then(function() { 411 | return Promise.all([ 412 | callGetDatabaseRaw(settings.protocol), 413 | resolveHostNameByMACAddr() 414 | ]); 415 | }) 416 | .then(function(res) { 417 | //console.time('start'); 418 | cachedData = parseDatabase(res[0].data || '', res[1], settings.showZero, settings.hideMACs); 419 | displayTable(table, settings); 420 | updated.textContent = _('Last updated at %s.').format(formatDate(new Date(document.lastModified))); 421 | //console.timeEnd('start'); 422 | }); 423 | } 424 | 425 | setUpdateMessage(updating, sec); 426 | if (!sec) 427 | setTimeout(setUpdateMessage.bind(this, updating, interval), 100); 428 | } 429 | 430 | function updateTable(tb, values, placeholder, settings) { 431 | var fragment = document.createDocumentFragment(), nodeLen = tb.childElementCount - 2; 432 | var formData = values[0], tbTitle = tb.firstElementChild, newNode, childTD; 433 | 434 | // Update the table data. 435 | for (var i = 0; i < formData.length; i++) { 436 | if (i < nodeLen) { 437 | newNode = tbTitle.nextElementSibling; 438 | } 439 | else { 440 | if (nodeLen > 0) { 441 | newNode = fragment.firstChild.cloneNode(true); 442 | } 443 | else { 444 | newNode = document.createElement('tr'); 445 | childTD = document.createElement('td'); 446 | for (var j = 0; j < tbTitle.children.length; j++) { 447 | childTD.className = 'td' + (settings.showColumns.indexOf(tbTitle.children[j].id) >= 0 ? '' : ' hide'); 448 | childTD.setAttribute('data-title', tbTitle.children[j].textContent); 449 | newNode.appendChild(childTD.cloneNode(true)); 450 | } 451 | } 452 | newNode.className = 'tr cbi-rowstyle-%d'.format(i % 2 ? 2 : 1); 453 | } 454 | 455 | childTD = newNode.firstElementChild; 456 | childTD.title = formData[i].slice(-1); 457 | for (var j = 0; j < tbTitle.childElementCount; j++, childTD = childTD.nextElementSibling) { 458 | switch (j) { 459 | case 2: 460 | case 3: 461 | childTD.textContent = formatSpeed(formData[i][j], settings.useBits, settings.useMultiple); 462 | break; 463 | case 4: 464 | case 5: 465 | case 6: 466 | childTD.textContent = formatSize(formData[i][j], settings.useBits, settings.useMultiple); 467 | break; 468 | case 7: 469 | case 8: 470 | childTD.textContent = formatDate(new Date(formData[i][j] * 1000)); 471 | break; 472 | default: 473 | childTD.textContent = formData[i][j]; 474 | } 475 | } 476 | fragment.appendChild(newNode); 477 | } 478 | 479 | // Remove the table data which has been deleted from the database. 480 | while (tb.childElementCount > 1) { 481 | tb.removeChild(tbTitle.nextElementSibling); 482 | } 483 | 484 | //Append the totals or placeholder row. 485 | if (formData.length == 0) { 486 | newNode = document.createElement('tr'); 487 | newNode.className = 'tr placeholder'; 488 | childTD = document.createElement('td'); 489 | childTD.className = 'td'; 490 | childTD.innerHTML = placeholder; 491 | newNode.appendChild(childTD); 492 | } 493 | else{ 494 | newNode = fragment.firstChild.cloneNode(true); 495 | newNode.className = 'tr table-totals'; 496 | 497 | newNode.children[0].textContent = _('TOTAL') + (settings.showColumns.indexOf('thMAC') >= 0 ? '' : ': ' + formData.length); 498 | newNode.children[1].textContent = formData.length + ' ' + _('Clients'); 499 | 500 | for (var j = 0; j < tbTitle.childElementCount; j++) { 501 | switch(j) { 502 | case 0: 503 | case 1: 504 | newNode.children[j].removeAttribute('title'); 505 | newNode.children[j].style.fontWeight = 'bold'; 506 | break; 507 | case 2: 508 | case 3: 509 | newNode.children[j].textContent = formatSpeed(values[1][j - 2], settings.useBits, settings.useMultiple); 510 | break; 511 | case 4: 512 | case 5: 513 | case 6: 514 | newNode.children[j].textContent = formatSize(values[1][j - 2], settings.useBits, settings.useMultiple); 515 | break; 516 | default: 517 | newNode.children[j].textContent = ''; 518 | newNode.children[j].removeAttribute('data-title'); 519 | } 520 | } 521 | } 522 | 523 | fragment.appendChild(newNode); 524 | tb.appendChild(fragment); 525 | } 526 | 527 | function initOption(options, selected) { 528 | var res = [], attr = {}; 529 | for (var idx in options) { 530 | attr.value = idx; 531 | attr.selected = idx == selected ? '' : null; 532 | res.push(E('option', attr, options[idx])); 533 | } 534 | return res; 535 | } 536 | 537 | return view.extend({ 538 | load: function() { 539 | return Promise.all([ 540 | parseDefaultSettings(luciConfig), 541 | loadCss(L.resource('view/wrtbwmon/wrtbwmon.css')) 542 | ]); 543 | }, 544 | 545 | render: function(data) { 546 | var settings = data[0], 547 | labelUpdated = E('label'), 548 | labelUpdating = E('label'), 549 | table = E('table', { 'class': 'table', 'id': 'traffic' }, [ 550 | E('tr', { 'class': 'tr table-titles' }, [ 551 | E('th', { 'class': 'th', 'id': 'thClient' }, _('Clients')), 552 | E('th', { 'class': 'th hide', 'id': 'thMAC' }, _('MAC')), 553 | E('th', { 'class': 'th', 'id': 'thDownload' }, _('Download')), 554 | E('th', { 'class': 'th', 'id': 'thUpload' }, _('Upload')), 555 | E('th', { 'class': 'th', 'id': 'thTotalDown' }, _('Total Down')), 556 | E('th', { 'class': 'th', 'id': 'thTotalUp' }, _('Total Up')), 557 | E('th', { 'class': 'th sorted', 'id': 'thTotal' }, _('Total')), 558 | E('th', { 'class': 'th hide', 'id': 'thFirstSeen' }, _('First Seen')), 559 | E('th', { 'class': 'th hide', 'id': 'thLastSeen' }, _('Last Seen')) 560 | ]), 561 | E('tr', {'class': 'tr placeholder'}, [ 562 | E('td', { 'class': 'td' }, E('em', {}, _('Collecting data...'))) 563 | ]) 564 | ]); 565 | 566 | poll.add(updateData.bind(this, settings, table, labelUpdated, labelUpdating, false), 1); 567 | setupThisDOM(settings, table); 568 | return E('div', { 'class': 'cbi-map' }, [ 569 | E('h2', {}, _('Usage - Details')), 570 | E('div', { 'class': 'cbi-section' }, [ 571 | E('div', { 'id': 'control_panel' }, [ 572 | E('div', {}, [ 573 | E('label', {}, _('Protocol:')), 574 | E('select', { 575 | 'id': 'selectProtocol', 576 | 'change': clickToSelectProtocol.bind(this, settings, table, labelUpdated, labelUpdating) 577 | }, initOption({ 578 | 'ipv4': 'ipv4', 579 | 'ipv6': 'ipv6' 580 | }, settings.protocol)) 581 | ]), 582 | E('div', {}, [ 583 | E('button', { 584 | 'class': 'btn cbi-button cbi-button-reset important', 585 | 'id': 'resetDatabase', 586 | 'click': clickToResetDatabase.bind(this, settings, table, labelUpdated, labelUpdating) 587 | }, _('Reset Database')), 588 | ' ', 589 | E('button', { 590 | 'class': 'btn cbi-button cbi-button-neutral', 591 | 'click': handleConfig 592 | }, _('Configure Options')) 593 | ]) 594 | ]), 595 | E('div', {}, [ 596 | E('div', {}, [ labelUpdated, labelUpdating ]), 597 | E('div', {}, [ 598 | E('label', { 'for': 'selectInterval' }, _('Auto update every:')), 599 | E('select', { 600 | 'id': 'selectInterval', 601 | 'change': clickToSelectInterval.bind(this, settings, labelUpdating) 602 | }, initOption({ 603 | '-1': _('Disabled'), 604 | '2': _('2 seconds'), 605 | '5': _('5 seconds'), 606 | '10': _('10 seconds'), 607 | '30': _('30 seconds') 608 | }, settings.interval)) 609 | ]) 610 | ]), 611 | E('div', { 'id': 'progressbar_panel' }, [ 612 | E('div', {}, [ 613 | E('label', {}, _('Downstream:')), 614 | E('div', { 615 | 'id': 'downstream', 616 | 'class': 'cbi-progressbar', 617 | 'title': '-' 618 | }, E('div') 619 | ) 620 | ]), 621 | E('div', {}, [ 622 | E('label', {}, _('Upstream:')), 623 | E('div', { 624 | 'id': 'upstream', 625 | 'class': 'cbi-progressbar', 626 | 'title': '-' 627 | }, E('div') 628 | ) 629 | ]), 630 | ]), 631 | table 632 | ]) 633 | ]); 634 | }, 635 | 636 | handleSaveApply: null, 637 | handleSave: null, 638 | handleReset: null 639 | }); 640 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/htdocs/luci-static/resources/view/wrtbwmon/wrtbwmon.css: -------------------------------------------------------------------------------- 1 | .th.sorted::after { 2 | content: '\25bc'; 3 | } 4 | .th.sorted.ascent::after { 5 | content: '\25b2'; 6 | } 7 | .hide { 8 | display: none !important; 9 | } 10 | #control_panel { 11 | display: flex; 12 | margin-bottom: 1rem; 13 | padding: .5rem; 14 | line-height: 2rem; 15 | } 16 | #control_panel > :nth-child(1) { 17 | display: inline-block; 18 | flex: 1 1 50%; 19 | } 20 | #control_panel > :nth-child(2) { 21 | flex: 1 1 50%; 22 | text-align: right; 23 | } 24 | #control_panel > * > * { 25 | vertical-align: middle; 26 | } 27 | #control_panel > div > label { 28 | margin-right: .5rem; 29 | } 30 | div > label + select { 31 | min-width: unset; 32 | width: auto; 33 | } 34 | #control_panel + div { 35 | display: flex; 36 | margin-bottom: .5rem; 37 | } 38 | #control_panel + div > div:nth-of-type(1) { 39 | flex: 1 1 65%; 40 | } 41 | #control_panel + div > div:nth-of-type(2) { 42 | flex: 1 1 35%; 43 | text-align: right; 44 | } 45 | #thClient { 46 | width: 17%; 47 | } 48 | #thMAC { 49 | width: 10%; 50 | } 51 | #thDownload, #thUpload { 52 | width: 8%; 53 | } 54 | #thTotalDown, #thTotalUp, #thTotal { 55 | width: 9%; 56 | } 57 | #thFirstSeen, #thLastSeen { 58 | width: 15%; 59 | } 60 | .tr.table-totals { 61 | font-weight: bold; 62 | } 63 | #traffic .tr:not(.table-totals):not(.placeholder) > .td:not(.th):first-child::before { 64 | content: attr(title)'\a'; 65 | white-space: pre-line; 66 | } 67 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/po/templates/wrtbwmon.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "Content-Type: text/plain; charset=UTF-8" 3 | 4 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 5 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:611 6 | msgid "10 seconds" 7 | msgstr "" 8 | 9 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 10 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:609 11 | msgid "2 seconds" 12 | msgstr "" 13 | 14 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 15 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 16 | msgid "30 seconds" 17 | msgstr "" 18 | 19 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 20 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:610 21 | msgid "5 seconds" 22 | msgstr "" 23 | 24 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:603 25 | msgid "Auto update every:" 26 | msgstr "" 27 | 28 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:201 29 | msgid "Cancel" 30 | msgstr "" 31 | 32 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:14 33 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:503 34 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:556 35 | msgid "Clients" 36 | msgstr "" 37 | 38 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:132 39 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:567 40 | msgid "Collecting data..." 41 | msgstr "" 42 | 43 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:75 44 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:167 45 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:210 46 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:25 47 | msgid "Configuration" 48 | msgstr "" 49 | 50 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:597 51 | msgid "Configure Options" 52 | msgstr "" 53 | 54 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:195 55 | msgid "Configure the default values for luci-app-wrtbwmon." 56 | msgstr "" 57 | 58 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 59 | msgid "Database path" 60 | msgstr "" 61 | 62 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 63 | msgid "Default Columns" 64 | msgstr "" 65 | 66 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 67 | msgid "Default Protocol" 68 | msgstr "" 69 | 70 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 71 | msgid "Default Refresh Interval" 72 | msgstr "" 73 | 74 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:16 75 | msgid "Details" 76 | msgstr "" 77 | 78 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 79 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:608 80 | msgid "Disabled" 81 | msgstr "" 82 | 83 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:16 84 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:558 85 | msgid "Download" 86 | msgstr "" 87 | 88 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:184 89 | msgid "Downstream Bandwidth" 90 | msgstr "" 91 | 92 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 93 | msgid "Downstream:" 94 | msgstr "" 95 | 96 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:18 97 | msgid "Each line must have the following format:" 98 | msgstr "" 99 | 100 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:21 101 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 102 | msgid "First Seen" 103 | msgstr "" 104 | 105 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:18 106 | msgid "General settings" 107 | msgstr "" 108 | 109 | #: root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json:3 110 | msgid "Grant access to LuCI app wrtbwmon" 111 | msgstr "" 112 | 113 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:185 114 | msgid "Hide MAC Addresses" 115 | msgstr "" 116 | 117 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 118 | msgid "IEC - 1024" 119 | msgstr "" 120 | 121 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:21 122 | msgid "Keep running in the background" 123 | msgstr "" 124 | 125 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:22 126 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:564 127 | msgid "Last Seen" 128 | msgstr "" 129 | 130 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:425 131 | msgid "Last updated at %s." 132 | msgstr "" 133 | 134 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:168 135 | msgid "Loading configuration data..." 136 | msgstr "" 137 | 138 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:15 139 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:557 140 | msgid "MAC" 141 | msgstr "" 142 | 143 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 144 | msgid "Multiple of Unit" 145 | msgstr "" 146 | 147 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:578 148 | msgid "Protocol:" 149 | msgstr "" 150 | 151 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 152 | msgid "Reset Database" 153 | msgstr "" 154 | 155 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 156 | msgid "SI - 1000" 157 | msgstr "" 158 | 159 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:207 160 | msgid "Save" 161 | msgstr "" 162 | 163 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:76 164 | msgid "Saving configuration data..." 165 | msgstr "" 166 | 167 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 168 | msgid "Show Zeros" 169 | msgstr "" 170 | 171 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:502 172 | msgid "TOTAL" 173 | msgstr "" 174 | 175 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 176 | msgid "" 177 | "This box is used to select the Database path, which is /tmp/usage.db by " 178 | "default." 179 | msgstr "" 180 | 181 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:60 182 | msgid "This will delete the database file. Are you sure?" 183 | msgstr "" 184 | 185 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:20 186 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:562 187 | msgid "Total" 188 | msgstr "" 189 | 190 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:18 191 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:560 192 | msgid "Total Down" 193 | msgstr "" 194 | 195 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:19 196 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:561 197 | msgid "Total Up" 198 | msgstr "" 199 | 200 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:3 201 | msgid "Traffic Status" 202 | msgstr "" 203 | 204 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 205 | msgid "Transfer Speed in Bits" 206 | msgstr "" 207 | 208 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:9 209 | msgid "Unable to load the customized hostname file:" 210 | msgstr "" 211 | 212 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:81 213 | msgid "Unable to save %s: %s" 214 | msgstr "" 215 | 216 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:376 217 | msgid "Updating again in %s second(s)." 218 | msgstr "" 219 | 220 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:17 221 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:559 222 | msgid "Upload" 223 | msgstr "" 224 | 225 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 226 | msgid "Upstream Bandwidth" 227 | msgstr "" 228 | 229 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:627 230 | msgid "Upstream:" 231 | msgstr "" 232 | 233 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:16 234 | msgid "Usage - Configuration" 235 | msgstr "" 236 | 237 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:16 238 | msgid "Usage - Custom User File" 239 | msgstr "" 240 | 241 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:574 242 | msgid "Usage - Details" 243 | msgstr "" 244 | 245 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 246 | msgid "Use DSL Bandwidth" 247 | msgstr "" 248 | 249 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:34 250 | msgid "User file" 251 | msgstr "" 252 | 253 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 254 | msgid "ipv4" 255 | msgstr "" 256 | 257 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 258 | msgid "ipv6" 259 | msgstr "" 260 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/po/zh_Hans/wrtbwmon.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "Content-Type: text/plain; charset=UTF-8\n" 3 | 4 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 5 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:611 6 | msgid "10 seconds" 7 | msgstr "10秒" 8 | 9 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 10 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:609 11 | msgid "2 seconds" 12 | msgstr "2秒" 13 | 14 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 15 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 16 | msgid "30 seconds" 17 | msgstr "30秒" 18 | 19 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 20 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:610 21 | msgid "5 seconds" 22 | msgstr "5秒" 23 | 24 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:603 25 | msgid "Auto update every:" 26 | msgstr "自动刷新:" 27 | 28 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:201 29 | msgid "Cancel" 30 | msgstr "取消" 31 | 32 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:14 33 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:503 34 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:556 35 | msgid "Clients" 36 | msgstr "客户端" 37 | 38 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:132 39 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:567 40 | msgid "Collecting data..." 41 | msgstr "收集数据中..." 42 | 43 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:75 44 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:167 45 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:210 46 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:25 47 | msgid "Configuration" 48 | msgstr "配置" 49 | 50 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:597 51 | msgid "Configure Options" 52 | msgstr "配置选项" 53 | 54 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:195 55 | msgid "Configure the default values for luci-app-wrtbwmon." 56 | msgstr "配置luci-app-wrtbwmon的默认值。" 57 | 58 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 59 | msgid "Database path" 60 | msgstr "数据路径" 61 | 62 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 63 | msgid "Default Columns" 64 | msgstr "默认显示的列" 65 | 66 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 67 | msgid "Default Protocol" 68 | msgstr "默认协议" 69 | 70 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 71 | msgid "Default Refresh Interval" 72 | msgstr "默认刷新间隔" 73 | 74 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:16 75 | msgid "Details" 76 | msgstr "流量信息" 77 | 78 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 79 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:608 80 | msgid "Disabled" 81 | msgstr "禁用" 82 | 83 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:16 84 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:558 85 | msgid "Download" 86 | msgstr "下载" 87 | 88 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:184 89 | msgid "Downstream Bandwidth" 90 | msgstr "下行带宽" 91 | 92 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 93 | msgid "Downstream:" 94 | msgstr "下行:" 95 | 96 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:18 97 | msgid "Each line must have the following format:" 98 | msgstr "每行需要满足以下格式:" 99 | 100 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:21 101 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 102 | msgid "First Seen" 103 | msgstr "初次记录" 104 | 105 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:18 106 | msgid "General settings" 107 | msgstr "通用设置" 108 | 109 | #: root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json:3 110 | msgid "Grant access to LuCI app wrtbwmon" 111 | msgstr "授予访问LuCI应用程序wrtbwmon的权限" 112 | 113 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:185 114 | msgid "Hide MAC Addresses" 115 | msgstr "隐藏MAC地址" 116 | 117 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 118 | msgid "IEC - 1024" 119 | msgstr "" 120 | 121 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:21 122 | msgid "Keep running in the background" 123 | msgstr "保持后台运行" 124 | 125 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:22 126 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:564 127 | msgid "Last Seen" 128 | msgstr "最后记录" 129 | 130 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:425 131 | msgid "Last updated at %s." 132 | msgstr "最后更新于%s。" 133 | 134 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:168 135 | msgid "Loading configuration data..." 136 | msgstr "载入配置数据..." 137 | 138 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:15 139 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:557 140 | msgid "MAC" 141 | msgstr "" 142 | 143 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 144 | msgid "Multiple of Unit" 145 | msgstr "单位之间倍数" 146 | 147 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:578 148 | msgid "Protocol:" 149 | msgstr "协议:" 150 | 151 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 152 | msgid "Reset Database" 153 | msgstr "重置" 154 | 155 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 156 | msgid "SI - 1000" 157 | msgstr "" 158 | 159 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:207 160 | msgid "Save" 161 | msgstr "保存" 162 | 163 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:76 164 | msgid "Saving configuration data..." 165 | msgstr "保存配置数据..." 166 | 167 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 168 | msgid "Show Zeros" 169 | msgstr "显示0流量" 170 | 171 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:502 172 | msgid "TOTAL" 173 | msgstr "总共" 174 | 175 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 176 | msgid "" 177 | "This box is used to select the Database path, which is /tmp/usage.db by " 178 | "default." 179 | msgstr "该选项用于选择数据存储路径,默认路径为/tmp/usage.db。" 180 | 181 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:60 182 | msgid "This will delete the database file. Are you sure?" 183 | msgstr "该操作将删除数据统计文件,确定执行该操作?" 184 | 185 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:20 186 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:562 187 | msgid "Total" 188 | msgstr "总计" 189 | 190 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:18 191 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:560 192 | msgid "Total Down" 193 | msgstr "总下载" 194 | 195 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:19 196 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:561 197 | msgid "Total Up" 198 | msgstr "总上传" 199 | 200 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:3 201 | msgid "Traffic Status" 202 | msgstr "流量监控" 203 | 204 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 205 | msgid "Transfer Speed in Bits" 206 | msgstr "以bits显示传输速度" 207 | 208 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:9 209 | msgid "Unable to load the customized hostname file:" 210 | msgstr "不能载入自定义用户名文件:" 211 | 212 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:81 213 | msgid "Unable to save %s: %s" 214 | msgstr "不能保存%s: %s" 215 | 216 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:376 217 | msgid "Updating again in %s second(s)." 218 | msgstr "下次更新将于%s秒之后。" 219 | 220 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:17 221 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:559 222 | msgid "Upload" 223 | msgstr "上传" 224 | 225 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 226 | msgid "Upstream Bandwidth" 227 | msgstr "上传带宽" 228 | 229 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:627 230 | msgid "Upstream:" 231 | msgstr "上行:" 232 | 233 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:16 234 | msgid "Usage - Configuration" 235 | msgstr "文件设置" 236 | 237 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:16 238 | msgid "Usage - Custom User File" 239 | msgstr "用户文件配置" 240 | 241 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:574 242 | msgid "Usage - Details" 243 | msgstr "流量详情" 244 | 245 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 246 | msgid "Use DSL Bandwidth" 247 | msgstr "使用DSL带宽" 248 | 249 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:34 250 | msgid "User file" 251 | msgstr "用户文件" 252 | 253 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 254 | msgid "ipv4" 255 | msgstr "" 256 | 257 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 258 | msgid "ipv6" 259 | msgstr "" 260 | 261 | #~ msgid "Show More Columns:" 262 | #~ msgstr "显示更多列:" 263 | 264 | #~ msgid "This will revert the changes. Are you sure?" 265 | #~ msgstr "更改将会重置,确定吗?" 266 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/po/zh_Hant/wrtbwmon.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: 2023-04-26 18:31+0800\n" 6 | "Last-Translator: Victor Tseng (palatis@gmail.com)\n" 7 | "Language-Team: \n" 8 | "Language: zh_TW\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Generator: Poedit 3.2.2\n" 14 | 15 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 16 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:611 17 | msgid "10 seconds" 18 | msgstr "10 秒" 19 | 20 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 21 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:609 22 | msgid "2 seconds" 23 | msgstr "2 秒" 24 | 25 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 26 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:612 27 | msgid "30 seconds" 28 | msgstr "30 秒" 29 | 30 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 31 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:610 32 | msgid "5 seconds" 33 | msgstr "5 秒" 34 | 35 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:603 36 | msgid "Auto update every:" 37 | msgstr "重新整理間隔:" 38 | 39 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:201 40 | msgid "Cancel" 41 | msgstr "取消" 42 | 43 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:14 44 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:503 45 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:556 46 | msgid "Clients" 47 | msgstr "用戶端" 48 | 49 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:132 50 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:567 51 | msgid "Collecting data..." 52 | msgstr "蒐集資料中…" 53 | 54 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:75 55 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:167 56 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:210 57 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:25 58 | msgid "Configuration" 59 | msgstr "設定" 60 | 61 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:597 62 | msgid "Configure Options" 63 | msgstr "設定選項" 64 | 65 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:195 66 | msgid "Configure the default values for luci-app-wrtbwmon." 67 | msgstr "設定 luci-app-wrtbwmon 的預設值。" 68 | 69 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 70 | msgid "Database path" 71 | msgstr "資料庫路徑" 72 | 73 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:178 74 | msgid "Default Columns" 75 | msgstr "預設顯示的欄位" 76 | 77 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 78 | msgid "Default Protocol" 79 | msgstr "預設協定" 80 | 81 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 82 | msgid "Default Refresh Interval" 83 | msgstr "預設重新整理間隔" 84 | 85 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:16 86 | msgid "Details" 87 | msgstr "詳細" 88 | 89 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:177 90 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:608 91 | msgid "Disabled" 92 | msgstr "已停用" 93 | 94 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:16 95 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:558 96 | msgid "Download" 97 | msgstr "下載" 98 | 99 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:184 100 | msgid "Downstream Bandwidth" 101 | msgstr "下行頻寬" 102 | 103 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:618 104 | msgid "Downstream:" 105 | msgstr "下行:" 106 | 107 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:18 108 | msgid "Each line must have the following format:" 109 | msgstr "每一行皆必須使用以下格式:" 110 | 111 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:21 112 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:563 113 | msgid "First Seen" 114 | msgstr "初次發現" 115 | 116 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:18 117 | msgid "General settings" 118 | msgstr "通用設定" 119 | 120 | #: root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json:3 121 | msgid "Grant access to LuCI app wrtbwmon" 122 | msgstr "授權給 LuCI app wrtbwmon" 123 | 124 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:185 125 | msgid "Hide MAC Addresses" 126 | msgstr "隱藏 MAC 地址" 127 | 128 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 129 | msgid "IEC - 1024" 130 | msgstr "IEC - 1024" 131 | 132 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:21 133 | msgid "Keep running in the background" 134 | msgstr "保持背景執行" 135 | 136 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:22 137 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:564 138 | msgid "Last Seen" 139 | msgstr "最後發現" 140 | 141 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:425 142 | msgid "Last updated at %s." 143 | msgstr "最後於 %s 發現。" 144 | 145 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:168 146 | msgid "Loading configuration data..." 147 | msgstr "載入設定資料…" 148 | 149 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:15 150 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:557 151 | msgid "MAC" 152 | msgstr "MAC" 153 | 154 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 155 | msgid "Multiple of Unit" 156 | msgstr "速率計量單位格式" 157 | 158 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:578 159 | msgid "Protocol:" 160 | msgstr "協定:" 161 | 162 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:592 163 | msgid "Reset Database" 164 | msgstr "重置資料庫" 165 | 166 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:181 167 | msgid "SI - 1000" 168 | msgstr "SI - 1000" 169 | 170 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:207 171 | msgid "Save" 172 | msgstr "儲存" 173 | 174 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:76 175 | msgid "Saving configuration data..." 176 | msgstr "正在保存設定資料…" 177 | 178 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:179 179 | msgid "Show Zeros" 180 | msgstr "顯示無流量的紀錄" 181 | 182 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:502 183 | msgid "TOTAL" 184 | msgstr "總計" 185 | 186 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:24 187 | msgid "" 188 | "This box is used to select the Database path, which is /tmp/usage.db by " 189 | "default." 190 | msgstr "選擇資料庫路徑,預設為 /tmp/usage.db。" 191 | 192 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:60 193 | msgid "This will delete the database file. Are you sure?" 194 | msgstr "這會刪除目前的資料庫檔案,您確定嗎?" 195 | 196 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:20 197 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:562 198 | msgid "Total" 199 | msgstr "總頻寬" 200 | 201 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:18 202 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:560 203 | msgid "Total Down" 204 | msgstr "總下載" 205 | 206 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:19 207 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:561 208 | msgid "Total Up" 209 | msgstr "總上傳" 210 | 211 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:3 212 | msgid "Traffic Status" 213 | msgstr "頻寬使用狀況" 214 | 215 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:180 216 | msgid "Transfer Speed in Bits" 217 | msgstr "以位元表示傳輸速率" 218 | 219 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:9 220 | msgid "Unable to load the customized hostname file:" 221 | msgstr "無法載入自訂的主機名稱檔案:" 222 | 223 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:81 224 | msgid "Unable to save %s: %s" 225 | msgstr "無法保存 %s:%s" 226 | 227 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:376 228 | msgid "Updating again in %s second(s)." 229 | msgstr "於 %s 秒後更新。" 230 | 231 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:17 232 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:559 233 | msgid "Upload" 234 | msgstr "上傳" 235 | 236 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:183 237 | msgid "Upstream Bandwidth" 238 | msgstr "上行頻寬" 239 | 240 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:627 241 | msgid "Upstream:" 242 | msgstr "上行:" 243 | 244 | #: htdocs/luci-static/resources/view/wrtbwmon/config.js:16 245 | msgid "Usage - Configuration" 246 | msgstr "頻寬使用狀況 - 設定" 247 | 248 | #: htdocs/luci-static/resources/view/wrtbwmon/custom.js:16 249 | msgid "Usage - Custom User File" 250 | msgstr "頻寬使用狀況 - 自訂主機名稱列表" 251 | 252 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:574 253 | msgid "Usage - Details" 254 | msgstr "頻寬使用狀況 - 詳細資料" 255 | 256 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:182 257 | msgid "Use DSL Bandwidth" 258 | msgstr "使用 DSL 頻寬" 259 | 260 | #: root/usr/share/luci/menu.d/luci-app-wrtbwmon.json:34 261 | msgid "User file" 262 | msgstr "使用者檔案" 263 | 264 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 265 | msgid "ipv4" 266 | msgstr "ipv4" 267 | 268 | #: htdocs/luci-static/resources/view/wrtbwmon/details.js:176 269 | msgid "ipv6" 270 | msgstr "ipv6" 271 | 272 | #~ msgid "Default More Columns" 273 | #~ msgstr "預設顯示更多欄位" 274 | 275 | #~ msgid "Show More Columns:" 276 | #~ msgstr "顯示更多欄位:" 277 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/root/etc/luci-wrtbwmon.conf: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "ipv4", 3 | "interval": "5", 4 | "showMore": false, 5 | "showZero": true, 6 | "useBits": false, 7 | "useMultiple": "1000", 8 | "useDSL": false, 9 | "upstream": "100", 10 | "downstream": "100", 11 | "hideMACs": [] 12 | } 13 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/root/usr/libexec/rpcd/luci.wrtbwmon: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . "$IPKG_INSTROOT/usr/share/libubox/jshn.sh" 4 | 5 | renamefile() { 6 | local base=$(basename -- "$1") 7 | local ext=$([ -z "${base/*.*/}" ] && echo ".${base##*.}" || echo '') 8 | local base="${base%.*}" 9 | echo "$(dirname $1)/${base}$2$ext" && return 10 | } 11 | 12 | _get_db_path() { 13 | local db db_4 db_6 db_46 14 | db="$(uci -q get wrtbwmon.general.path)" 15 | db_4=$(renamefile "${db:-/tmp/usage.db}" "") 16 | db_6=$(renamefile "${db:-/tmp/usage.db}" ".6") 17 | db_46=$(renamefile "${db:-/tmp/usage.db}" ".46") 18 | json_init 19 | json_add_string file_4 "$db_4" 20 | json_add_string file_6 "$db_6" 21 | json_add_string file_46 "$db_46" 22 | json_dump 23 | json_cleanup 24 | } 25 | 26 | _get_db_raw() { 27 | json_init 28 | json_add_string file "$1" 29 | json_add_string data "$(cat "$1")" 30 | json_dump 31 | json_cleanup 32 | } 33 | 34 | _remove_db() { 35 | local result 36 | rm "$1" && result=1 || result=0 37 | json_init 38 | json_add_boolean result "$result" 39 | json_dump 40 | json_cleanup 41 | } 42 | 43 | _change_db_path() { 44 | json_init 45 | json_load "$(_get_db_path)" 46 | json_get_var db_4 'file_4' 47 | json_get_var db_6 'file_6' 48 | json_get_var db_46 'file_46' 49 | json_cleanup 50 | if [ "$1" = "before" ]; then 51 | mv "$db_4" /tmp/usage.db.tmp 52 | mv "$db_6" /tmp/usage.6.db.tmp 53 | mv "$db_46" /tmp/usage.46.db.tmp 54 | elif [ "$1" = "after" ]; then 55 | mv -f /tmp/usage.db.tmp "$db_4" 56 | mv -f /tmp/usage.6.db.tmp "$db_6" 57 | mv -f /tmp/usage.46.db.tmp "$db_46" 58 | fi 59 | json_init 60 | json_add_boolean result $([ "$?" = 0 ] && echo 1 || echo 0 ) 61 | json_dump 62 | json_cleanup 63 | } 64 | 65 | case "$1" in 66 | list) 67 | json_init 68 | json_add_object remove_db 69 | json_add_string protocol "protocol" 70 | json_close_object 71 | json_add_object get_db_raw 72 | json_add_string protocol "protocol" 73 | json_close_object 74 | json_add_object get_db_path 75 | json_close_object 76 | json_add_object change_db_path 77 | json_add_string state "state" 78 | json_close_object 79 | json_dump 80 | json_cleanup 81 | ;; 82 | call) 83 | case "$2" in 84 | remove_db) 85 | read -r input 86 | json_init 87 | json_load "$input" 88 | json_get_var protocol 'protocol' 89 | json_cleanup 90 | json_load "$(_get_db_path)" 91 | json_get_var db_s $([ "$protocol" = "ipv4" ] && echo file_4 || echo file_6) 92 | json_cleanup 93 | _remove_db "$db_s" 94 | ;; 95 | get_db_raw) 96 | read -r input 97 | json_init 98 | json_load "$input" 99 | json_get_var protocol 'protocol' 100 | json_cleanup 101 | json_load "$(_get_db_path)" 102 | json_get_var db_s $([ "$protocol" = "ipv4" ] && echo file_4 || echo file_6) 103 | json_cleanup 104 | _get_db_raw "$db_s" 105 | ;; 106 | get_db_path) 107 | read -r input 108 | json_init 109 | json_load "$input" 110 | json_get_var protocol 'protocol' 111 | json_cleanup 112 | _get_db_path 113 | ;; 114 | change_db_path) 115 | read -r input 116 | json_init 117 | json_load "$input" 118 | json_get_var state 'state' 119 | json_cleanup 120 | _change_db_path "$state" 121 | ;; 122 | esac 123 | ;; 124 | esac 125 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/root/usr/share/luci/menu.d/luci-app-wrtbwmon.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/network/usage": { 3 | "title": "Traffic Status", 4 | "order": 60, 5 | "action": { 6 | "type": "alias", 7 | "path": "admin/network/usage/details" 8 | }, 9 | "depends": { 10 | "acl": [ "luci-app-wrtbwmon" ], 11 | "uci": { "wrtbwmon": true } 12 | } 13 | }, 14 | 15 | "admin/network/usage/details": { 16 | "title": "Details", 17 | "order": 10, 18 | "action": { 19 | "type": "view", 20 | "path": "wrtbwmon/details" 21 | } 22 | }, 23 | 24 | "admin/network/usage/config": { 25 | "title": "Configuration", 26 | "order": 20, 27 | "action": { 28 | "type": "view", 29 | "path": "wrtbwmon/config" 30 | } 31 | }, 32 | 33 | "admin/network/usage/custom": { 34 | "title": "User file", 35 | "order": 30, 36 | "action": { 37 | "type": "view", 38 | "path": "wrtbwmon/custom" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /luci-app-wrtbwmon/root/usr/share/rpcd/acl.d/luci-app-wrtbwmon.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-wrtbwmon": { 3 | "description": "Grant access to LuCI app wrtbwmon", 4 | "read": { 5 | "ubus": { 6 | "luci.wrtbwmon": [ 7 | "get_db_raw", 8 | "get_db_path" 9 | ], 10 | "luci-rpc": [ 11 | "getDHCPLeases", 12 | "getDSLStatus" 13 | ] 14 | }, 15 | "file": { 16 | "/etc/wrtbwmon.user": [ "read" ], 17 | "/usr/sbin/wrtbwmon": [ "exec" ], 18 | "/etc/luci-wrtbwmon.conf": [ "read" ] 19 | }, 20 | "uci": [ "wrtbwmon" ] 21 | }, 22 | "write": { 23 | "ubus": { 24 | "luci.wrtbwmon": [ 25 | "remove_db", 26 | "change_db_path" 27 | ] 28 | }, 29 | "file": { 30 | "/etc/luci-wrtbwmon.conf": [ "write" ], 31 | "/etc/wrtbwmon.user": [ "write" ] 32 | }, 33 | "uci": [ "wrtbwmon" ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brvphoenix/luci-app-wrtbwmon/f1b59b2309a0bc45511a1e7432c6c72f080a47d7/screenshot.png --------------------------------------------------------------------------------