diff --git a/luci-app-bandwidth-manager/Makefile b/luci-app-bandwidth-manager/Makefile index d22588c1..9349c9ea 100644 --- a/luci-app-bandwidth-manager/Makefile +++ b/luci-app-bandwidth-manager/Makefile @@ -3,51 +3,14 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-bandwidth-manager PKG_VERSION:=1.0.0 PKG_RELEASE:=1 -PKG_MAINTAINER:=CyberMind -PKG_LICENSE:=MIT +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:=SecuBox Project -include $(INCLUDE_DIR)/package.mk +LUCI_TITLE:=Bandwidth Manager - QoS & Traffic Control +LUCI_DESCRIPTION:=Advanced bandwidth management with QoS rules, client quotas, and SQM integration +LUCI_DEPENDS:=+luci-base +rpcd +tc +kmod-sched-core +kmod-sched-cake +kmod-ifb +sqm-scripts +iptables +iptables-mod-conntrack-extra +ip-full +LUCI_PKGARCH:=all -define Package/luci-app-bandwidth-manager - SECTION:=luci - CATEGORY:=LuCI - SUBMENU:=3. Applications - TITLE:=Bandwidth Manager - QoS, Quotas & Media Detection - DEPENDS:=+luci-base +rpcd +tc +kmod-sched-cake +kmod-sched-fq-codel - PKGARCH:=all -endef +include ../../luci.mk -define Package/luci-app-bandwidth-manager/description - Advanced bandwidth management for OpenWrt with: - - Per-client and per-group quotas (daily/monthly) - - Bandwidth throttling and shaping - - 8-level QoS priority classes - - Automatic media detection (VoIP, Gaming, Streaming) - - Time-based scheduling - - Real-time statistics and graphs -endef - -define Build/Compile -endef - -define Package/luci-app-bandwidth-manager/install - $(INSTALL_DIR) $(1)/usr/libexec/rpcd - $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.bandwidth-manager $(1)/usr/libexec/rpcd/ - $(INSTALL_DIR) $(1)/usr/share/luci/menu.d - $(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json $(1)/usr/share/luci/menu.d/ - $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d - $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json $(1)/usr/share/rpcd/acl.d/ - $(INSTALL_DIR) $(1)/etc/config - $(INSTALL_CONF) ./root/etc/config/bandwidth $(1)/etc/config/ - $(INSTALL_DIR) $(1)/www/luci-static/resources/view/bandwidth-manager - $(INSTALL_DATA) ./htdocs/luci-static/resources/view/bandwidth-manager/*.js $(1)/www/luci-static/resources/view/bandwidth-manager/ - $(INSTALL_DIR) $(1)/www/luci-static/resources/bandwidth-manager - $(INSTALL_DATA) ./htdocs/luci-static/resources/bandwidth-manager/*.js $(1)/www/luci-static/resources/bandwidth-manager/ -endef - -define Package/luci-app-bandwidth-manager/postinst -#!/bin/sh -[ -n "$${IPKG_INSTROOT}" ] || /etc/init.d/rpcd reload -endef - -$(eval $(call BuildPackage,luci-app-bandwidth-manager)) +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-bandwidth-manager/README.md b/luci-app-bandwidth-manager/README.md index 66fbed73..8aca9abf 100644 --- a/luci-app-bandwidth-manager/README.md +++ b/luci-app-bandwidth-manager/README.md @@ -1,50 +1,533 @@ -# Bandwidth Manager for OpenWrt +# Bandwidth Manager - QoS & Traffic Control -Advanced bandwidth management with QoS, quotas, and automatic media detection. +Advanced bandwidth management for OpenWrt with QoS rules, client quotas, and SQM/CAKE integration. ## Features -### 🎯 QoS Priority Classes -- 8 configurable priority levels -- Per-class rate guarantees and ceilings -- DSCP marking support +### QoS Traffic Shaping +- Rule-based traffic control by application, port, IP, or MAC +- Per-rule download/upload limits +- 8-level priority system (1=highest, 8=lowest) +- Time-based scheduling support +- Real-time rule enable/disable -### 📊 Bandwidth Quotas -- Daily and monthly limits -- Per-client or per-group quotas -- Configurable actions (throttle/block) +### Client Quotas +- Monthly data quotas per MAC address +- Usage tracking with iptables counters +- Configurable actions: throttle, block, or notify +- Automatic monthly reset (configurable day) +- Real-time quota usage monitoring -### 🎬 Media Detection -- Automatic VoIP detection (SIP, RTP) -- Gaming traffic prioritization -- Streaming service identification -- Domain-based classification +### SQM/CAKE Integration +- Smart Queue Management with CAKE qdisc +- Automatic bandwidth shaping +- NAT-aware configuration +- Link overhead compensation (Ethernet, PPPoE, VLAN) +- Alternative FQ_CoDel and HTB support -### ⏰ Time-Based Scheduling -- Peak/off-peak configurations -- Day-of-week rules -- Automatic limit adjustments - -### 👥 Client Management -- Per-device statistics -- MAC-based identification -- Real-time monitoring +### Real-time Monitoring +- Live client bandwidth usage (auto-refresh every 5s) +- Per-client RX/TX statistics +- Quota progress visualization +- Historical usage tracking ## Installation ```bash opkg update opkg install luci-app-bandwidth-manager +/etc/init.d/rpcd restart +/etc/init.d/uhttpd restart ``` +## Dependencies + +- **tc**: Traffic control utility +- **kmod-sched-core**: Kernel traffic scheduler +- **kmod-sched-cake**: CAKE qdisc module +- **kmod-ifb**: Intermediate Functional Block device +- **sqm-scripts**: SQM scripts +- **iptables**: For traffic tracking +- **iptables-mod-conntrack-extra**: Connection tracking extensions + ## Configuration -Edit `/etc/config/bandwidth` or use the LuCI interface. +### UCI Configuration -## Demo +Edit `/etc/config/bandwidth`: -Open `demo/index.html` in a browser to see a live preview. +```bash +config global 'global' + option enabled '1' + option interface 'br-lan' + option sqm_enabled '1' + +config sqm 'sqm' + option download_speed '100000' # kbit/s + option upload_speed '50000' # kbit/s + option qdisc 'cake' + option nat '1' + option overhead '22' # PPPoE overhead + +config rule 'rule_youtube' + option name 'Limit YouTube' + option type 'application' + option target 'youtube' + option limit_down '5000' # kbit/s + option limit_up '1000' # kbit/s + option priority '6' + option enabled '1' + +config quota 'quota_phone' + option mac 'AA:BB:CC:DD:EE:FF' + option name 'iPhone Jean' + option limit_mb '10240' # 10 GB + option action 'throttle' + option reset_day '1' + option enabled '1' +``` + +### Configuration Options + +#### Global Section +- `enabled`: Enable/disable bandwidth manager +- `interface`: Network interface to manage (default: br-lan) +- `sqm_enabled`: Enable SQM/CAKE + +#### SQM Section +- `download_speed`: Download speed in kbit/s +- `upload_speed`: Upload speed in kbit/s +- `qdisc`: Queue discipline (cake, fq_codel, htb) +- `nat`: NAT mode (1=enabled, 0=disabled) +- `overhead`: Link overhead in bytes (0, 18, 22, 40) + +#### Rule Section +- `name`: Rule name +- `type`: Rule type (application, port, ip, mac) +- `target`: Target value (app name, port number, IP, or MAC) +- `limit_down`: Download limit in kbit/s (0=unlimited) +- `limit_up`: Upload limit in kbit/s (0=unlimited) +- `priority`: Priority level (1-8) +- `schedule`: Optional time schedule (e.g., "Mon-Fri 08:00-18:00") +- `enabled`: Enable/disable rule + +#### Quota Section +- `mac`: Client MAC address +- `name`: Friendly name +- `limit_mb`: Monthly limit in MB +- `action`: Action when exceeded (throttle, block, notify) +- `reset_day`: Day of month to reset (1-28) +- `enabled`: Enable/disable quota + +## Usage + +### Web Interface + +Navigate to **Network → Bandwidth Manager** in LuCI. + +#### Overview Tab +- System status (QoS active, interface, SQM) +- Traffic statistics (RX/TX bytes and packets) +- Active rules summary +- Client quotas with progress bars + +#### QoS Rules Tab +- Create/edit/delete traffic shaping rules +- Configure type, target, limits, and priority +- Enable/disable rules individually +- Set time-based schedules + +#### Client Quotas Tab +- Manage monthly data quotas per MAC +- Set limits and actions +- Reset quota counters +- View current usage + +#### Real-time Usage Tab +- Live bandwidth usage per client +- Auto-refresh every 5 seconds +- Download/upload breakdown +- Quota progress for monitored clients + +#### Settings Tab +- Global enable/disable +- Interface selection +- SQM/CAKE configuration +- Traffic tracking settings +- Alert configuration + +### Command Line + +#### Get Status + +```bash +ubus call luci.bandwidth-manager status +``` + +#### List QoS Rules + +```bash +ubus call luci.bandwidth-manager list_rules +``` + +#### Add QoS Rule + +```bash +ubus call luci.bandwidth-manager add_rule '{ + "name": "Limit Torrent", + "type": "port", + "target": "6881-6889", + "limit_down": 3000, + "limit_up": 500, + "priority": 7 +}' +``` + +#### Delete Rule + +```bash +ubus call luci.bandwidth-manager delete_rule '{ + "rule_id": "rule_1234567890" +}' +``` + +#### List Client Quotas + +```bash +ubus call luci.bandwidth-manager list_quotas +``` + +#### Set Quota + +```bash +ubus call luci.bandwidth-manager set_quota '{ + "mac": "AA:BB:CC:DD:EE:FF", + "name": "iPhone John", + "limit_mb": 10240, + "action": "throttle", + "reset_day": 1 +}' +``` + +#### Get Quota Details + +```bash +ubus call luci.bandwidth-manager get_quota '{ + "mac": "AA:BB:CC:DD:EE:FF" +}' +``` + +#### Reset Quota Counter + +```bash +ubus call luci.bandwidth-manager reset_quota '{ + "mac": "AA:BB:CC:DD:EE:FF" +}' +``` + +#### Get Real-time Usage + +```bash +ubus call luci.bandwidth-manager get_usage_realtime +``` + +#### Get Usage History + +```bash +ubus call luci.bandwidth-manager get_usage_history '{ + "timeframe": "24h", + "mac": "AA:BB:CC:DD:EE:FF" +}' +``` + +Timeframe options: `1h`, `6h`, `24h`, `7d`, `30d` + +## ubus API Reference + +### status() + +Get system status and global statistics. + +**Returns:** +```json +{ + "enabled": true, + "interface": "br-lan", + "sqm_enabled": true, + "qos_active": true, + "stats": { + "rx_bytes": 1234567890, + "tx_bytes": 987654321, + "rx_packets": 1234567, + "tx_packets": 987654 + }, + "rule_count": 5, + "quota_count": 3 +} +``` + +### list_rules() + +List all QoS rules. + +**Returns:** +```json +{ + "rules": [ + { + "id": "rule_youtube", + "name": "Limit YouTube", + "type": "application", + "target": "youtube", + "limit_down": 5000, + "limit_up": 1000, + "priority": 6, + "enabled": true, + "schedule": "" + } + ] +} +``` + +### add_rule(name, type, target, limit_down, limit_up, priority) + +Add a new QoS rule. + +**Returns:** +```json +{ + "success": true, + "rule_id": "rule_1234567890", + "message": "Rule created successfully" +} +``` + +### delete_rule(rule_id) + +Delete a QoS rule. + +**Returns:** +```json +{ + "success": true, + "message": "Rule deleted successfully" +} +``` + +### list_quotas() + +List all client quotas with current usage. + +**Returns:** +```json +{ + "quotas": [ + { + "id": "quota_phone", + "mac": "AA:BB:CC:DD:EE:FF", + "name": "iPhone Jean", + "limit_mb": 10240, + "used_mb": 7850, + "percent": 76, + "action": "throttle", + "reset_day": 1, + "enabled": true + } + ] +} +``` + +### get_quota(mac) + +Get detailed quota information for a specific MAC. + +**Returns:** +```json +{ + "success": true, + "quota_id": "quota_phone", + "mac": "AA:BB:CC:DD:EE:FF", + "name": "iPhone Jean", + "limit_mb": 10240, + "used_mb": 7850, + "remaining_mb": 2390, + "percent": 76, + "action": "throttle", + "reset_day": 1 +} +``` + +### set_quota(mac, name, limit_mb, action, reset_day) + +Create or update a client quota. + +**Returns:** +```json +{ + "success": true, + "quota_id": "quota_1234567890", + "message": "Quota created successfully" +} +``` + +### reset_quota(mac) + +Reset quota counter for a client. + +**Returns:** +```json +{ + "success": true, + "message": "Quota counter reset for AA:BB:CC:DD:EE:FF" +} +``` + +### get_usage_realtime() + +Get real-time bandwidth usage for all active clients. + +**Returns:** +```json +{ + "clients": [ + { + "mac": "AA:BB:CC:DD:EE:FF", + "ip": "192.168.1.100", + "hostname": "iPhone", + "rx_bytes": 1234567, + "tx_bytes": 987654, + "has_quota": true, + "limit_mb": 10240, + "used_mb": 7850 + } + ] +} +``` + +### get_usage_history(timeframe, mac) + +Get historical usage data. + +**Parameters:** +- `timeframe`: "1h", "6h", "24h", "7d", "30d" +- `mac`: MAC address (optional, empty for all clients) + +**Returns:** +```json +{ + "history": [ + { + "mac": "AA:BB:CC:DD:EE:FF", + "timestamp": 1640000000, + "rx_bytes": 1234567, + "tx_bytes": 987654 + } + ] +} +``` + +## Traffic Tracking + +Bandwidth Manager uses iptables for per-client traffic accounting: + +```bash +# Create tracking chain +iptables -N BW_TRACKING + +# Add rules for each MAC +iptables -A BW_TRACKING -m mac --mac-source AA:BB:CC:DD:EE:FF +iptables -A BW_TRACKING -m mac --mac-source BB:CC:DD:EE:FF:00 + +# Insert into FORWARD chain +iptables -I FORWARD -j BW_TRACKING + +# View counters +iptables -L BW_TRACKING -n -v -x +``` + +Usage data is stored in `/tmp/bandwidth_usage.db` in pipe-delimited format: +``` +MAC|Timestamp|RX_Bytes|TX_Bytes +``` + +## QoS Implementation + +### CAKE (Recommended) + +```bash +tc qdisc add dev br-lan root cake bandwidth 100000kbit +``` + +Benefits: +- Active Queue Management (AQM) +- Flow-based fair queuing +- NAT-aware +- Low latency + +### HTB (Manual Control) + +```bash +tc qdisc add dev br-lan root handle 1: htb default 10 +tc class add dev br-lan parent 1: classid 1:1 htb rate 100mbit +tc class add dev br-lan parent 1:1 classid 1:10 htb rate 50mbit ceil 100mbit prio 5 +``` + +## Troubleshooting + +### QoS Not Working + +Check if QoS is active: +```bash +tc qdisc show dev br-lan +``` + +Check iptables rules: +```bash +iptables -L BW_TRACKING -n -v +``` + +### Quota Tracking Not Accurate + +Reset iptables counters: +```bash +iptables -Z BW_TRACKING +``` + +Check usage database: +```bash +cat /tmp/bandwidth_usage.db +``` + +### High CPU Usage + +Reduce tracking frequency or use hardware flow offloading if available: +```bash +echo 1 > /sys/class/net/br-lan/offload/tx_offload +``` + +## Best Practices + +1. **Set Realistic Limits**: Configure download/upload speeds to 85-95% of your actual connection speed +2. **Use CAKE**: Prefer CAKE qdisc for best performance and lowest latency +3. **Monitor First**: Use real-time usage view to understand traffic patterns before setting quotas +4. **Regular Resets**: Configure monthly resets on quota day 1 to align with ISP billing +5. **Priority Wisely**: Reserve priority 1-2 for VoIP/gaming, use 5 (normal) for most traffic + +## Security Considerations + +- MAC addresses can be spoofed - use in conjunction with other security measures +- Quota tracking requires iptables access - secure your router +- Alert emails may contain sensitive information - use encrypted connections +- Traffic shaping rules are visible to network administrator only ## License -MIT License - CyberMind Security +Apache-2.0 + +## Maintainer + +SecuBox Project + +## Version + +1.0.0 diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/bandwidth-manager/api.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/bandwidth-manager/api.js index 2d58efa9..2cc0d26e 100644 --- a/luci-app-bandwidth-manager/htdocs/luci-static/resources/bandwidth-manager/api.js +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/bandwidth-manager/api.js @@ -1,35 +1,81 @@ 'use strict'; -'require baseclass'; 'require rpc'; -var callStatus = rpc.declare({object:'luci.bandwidth-manager',method:'status',expect:{}}); -var callClasses = rpc.declare({object:'luci.bandwidth-manager',method:'classes',expect:{classes:[]}}); -var callQuotas = rpc.declare({object:'luci.bandwidth-manager',method:'quotas',expect:{quotas:[]}}); -var callMedia = rpc.declare({object:'luci.bandwidth-manager',method:'media',expect:{media:[]}}); -var callClients = rpc.declare({object:'luci.bandwidth-manager',method:'clients',expect:{clients:[]}}); -var callStats = rpc.declare({object:'luci.bandwidth-manager',method:'stats',expect:{}}); -var callApplyQos = rpc.declare({object:'luci.bandwidth-manager',method:'apply_qos'}); - -function formatBytes(bytes) { - if (bytes === 0) return '0 B'; - var k = 1024, sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - var i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -function formatSpeed(kbps) { - if (kbps >= 1000) return (kbps / 1000).toFixed(1) + ' Mbps'; - return kbps + ' Kbps'; -} - -return baseclass.extend({ - getStatus: callStatus, - getClasses: callClasses, - getQuotas: callQuotas, - getMedia: callMedia, - getClients: callClients, - getStats: callStats, - applyQos: callApplyQos, - formatBytes: formatBytes, - formatSpeed: formatSpeed +var callStatus = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'status', + expect: {} }); + +var callListRules = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'list_rules', + expect: { rules: [] } +}); + +var callAddRule = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'add_rule', + params: ['name', 'type', 'target', 'limit_down', 'limit_up', 'priority'], + expect: {} +}); + +var callDeleteRule = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'delete_rule', + params: ['rule_id'], + expect: {} +}); + +var callListQuotas = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'list_quotas', + expect: { quotas: [] } +}); + +var callGetQuota = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'get_quota', + params: ['mac'], + expect: {} +}); + +var callSetQuota = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'set_quota', + params: ['mac', 'name', 'limit_mb', 'action', 'reset_day'], + expect: {} +}); + +var callResetQuota = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'reset_quota', + params: ['mac'], + expect: {} +}); + +var callGetUsageRealtime = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'get_usage_realtime', + expect: { clients: [] } +}); + +var callGetUsageHistory = rpc.declare({ + object: 'luci.bandwidth-manager', + method: 'get_usage_history', + params: ['timeframe', 'mac'], + expect: { history: [] } +}); + +return { + getStatus: callStatus, + listRules: callListRules, + addRule: callAddRule, + deleteRule: callDeleteRule, + listQuotas: callListQuotas, + getQuota: callGetQuota, + setQuota: callSetQuota, + resetQuota: callResetQuota, + getUsageRealtime: callGetUsageRealtime, + getUsageHistory: callGetUsageHistory +}; diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/overview.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/overview.js index f6cd5e3f..7a46c825 100644 --- a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/overview.js +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/overview.js @@ -1,69 +1,170 @@ 'use strict'; 'require view'; -'require bandwidth-manager.api as api'; +'require bandwidth-manager/api as API'; -return view.extend({ - load: function() { - return Promise.all([api.getStatus(), api.getClasses(), api.getClients()]); - }, - render: function(data) { - var status = data[0] || {}; - var classes = data[1].classes || []; - var clients = data[2].clients || []; - - return E('div', {class:'cbi-map'}, [ - E('style', {}, [ - '.bw{font-family:system-ui,sans-serif}', - '.bw-hdr{background:linear-gradient(135deg,#7c3aed,#a855f7);color:#fff;padding:24px;border-radius:12px;margin-bottom:20px}', - '.bw-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:20px}', - '.bw-stat{background:#1e293b;padding:20px;border-radius:10px;text-align:center}', - '.bw-stat-val{font-size:28px;font-weight:700;color:#a855f7}', - '.bw-stat-lbl{font-size:12px;color:#94a3b8;margin-top:4px}', - '.bw-section{background:#1e293b;padding:20px;border-radius:10px;margin-bottom:16px}', - '.bw-section-title{font-size:16px;font-weight:600;color:#f1f5f9;margin-bottom:16px}', - '.bw-class{display:flex;align-items:center;gap:12px;padding:12px;background:#0f172a;border-radius:8px;margin-bottom:8px}', - '.bw-class-bar{height:8px;border-radius:4px;background:#334155;flex:1}', - '.bw-class-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#7c3aed,#a855f7)}', - '.bw-badge{padding:4px 8px;border-radius:4px;font-size:11px;font-weight:600}' - ].join('')), - E('div', {class:'bw'}, [ - E('div', {class:'bw-hdr'}, [ - E('h1', {style:'margin:0 0 8px;font-size:24px'}, '⚡ Bandwidth Manager'), - E('p', {style:'margin:0;opacity:.9'}, 'QoS, Quotas & Media Detection') - ]), - E('div', {class:'bw-stats'}, [ - E('div', {class:'bw-stat'}, [ - E('div', {class:'bw-stat-val'}, status.qos_active ? '✓' : '✗'), - E('div', {class:'bw-stat-lbl'}, 'QoS Status') - ]), - E('div', {class:'bw-stat'}, [ - E('div', {class:'bw-stat-val'}, clients.length), - E('div', {class:'bw-stat-lbl'}, 'Active Clients') - ]), - E('div', {class:'bw-stat'}, [ - E('div', {class:'bw-stat-val'}, api.formatBytes(status.rx_bytes || 0)), - E('div', {class:'bw-stat-lbl'}, 'Downloaded') - ]), - E('div', {class:'bw-stat'}, [ - E('div', {class:'bw-stat-val'}, api.formatBytes(status.tx_bytes || 0)), - E('div', {class:'bw-stat-lbl'}, 'Uploaded') - ]) - ]), - E('div', {class:'bw-section'}, [ - E('div', {class:'bw-section-title'}, '📊 QoS Classes'), - E('div', {}, classes.map(function(c) { - return E('div', {class:'bw-class'}, [ - E('span', {style:'width:100px;font-weight:600;color:#f1f5f9'}, c.name), - E('span', {class:'bw-badge',style:'background:#7c3aed20;color:#a855f7'}, 'P'+c.priority), - E('div', {class:'bw-class-bar'}, [ - E('div', {class:'bw-class-fill',style:'width:'+c.rate+'%'}) - ]), - E('span', {style:'color:#94a3b8;font-size:12px'}, c.rate+'% / '+c.ceil+'%') - ]); - })) - ]) - ]) - ]); - }, - handleSaveApply:null,handleSave:null,handleReset:null +return L.view.extend({ + load: function() { + return Promise.all([ + API.getStatus(), + API.listRules(), + API.listQuotas() + ]); + }, + + render: function(data) { + var status = data[0] || {}; + var rules = data[1] || []; + var quotas = data[2] || []; + + var v = E('div', { 'class': 'cbi-map' }, [ + E('h2', {}, _('Bandwidth Manager - Overview')), + E('div', { 'class': 'cbi-map-descr' }, _('QoS rules, client quotas, and traffic control')) + ]); + + // System status + var statusSection = E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('System Status')), + E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left', 'width': '25%' }, [ + E('strong', {}, _('QoS Engine: ')), + status.qos_active ? + E('span', { 'style': 'color: green' }, '● ' + _('Active')) : + E('span', { 'style': 'color: red' }, '● ' + _('Inactive')) + ]), + E('div', { 'class': 'td left', 'width': '25%' }, [ + E('strong', {}, _('Interface: ')), + E('span', {}, status.interface || 'br-lan') + ]), + E('div', { 'class': 'td left', 'width': '25%' }, [ + E('strong', {}, _('SQM: ')), + status.sqm_enabled ? + E('span', { 'style': 'color: green' }, '✓ ' + _('Enabled')) : + E('span', {}, '✗ ' + _('Disabled')) + ]), + E('div', { 'class': 'td left', 'width': '25%' }, [ + E('strong', {}, _('Rules: ')), + E('span', { 'style': 'font-size: 1.3em; color: #0088cc' }, String(status.rule_count || 0)) + ]) + ]) + ]) + ]); + v.appendChild(statusSection); + + // Traffic statistics + if (status.stats) { + var statsSection = E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Traffic Statistics')), + E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left', 'width': '50%' }, [ + E('strong', {}, '⬇ ' + _('Download: ')), + E('span', {}, this.formatBytes(status.stats.rx_bytes || 0)) + ]), + E('div', { 'class': 'td left', 'width': '50%' }, [ + E('strong', {}, '⬆ ' + _('Upload: ')), + E('span', {}, this.formatBytes(status.stats.tx_bytes || 0)) + ]) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left', 'width': '50%' }, [ + E('strong', {}, _('RX Packets: ')), + E('span', {}, String(status.stats.rx_packets || 0)) + ]), + E('div', { 'class': 'td left', 'width': '50%' }, [ + E('strong', {}, _('TX Packets: ')), + E('span', {}, String(status.stats.tx_packets || 0)) + ]) + ]) + ]) + ]); + v.appendChild(statsSection); + } + + // Active rules summary + if (rules.length > 0) { + var rulesSection = E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Active QoS Rules')) + ]); + + var rulesTable = E('table', { 'class': 'table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, _('Name')), + E('th', { 'class': 'th' }, _('Type')), + E('th', { 'class': 'th' }, _('Target')), + E('th', { 'class': 'th' }, _('Download Limit')), + E('th', { 'class': 'th' }, _('Priority')) + ]) + ]); + + rules.slice(0, 5).forEach(function(rule) { + if (!rule.enabled) + return; + + rulesTable.appendChild(E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, rule.name), + E('td', { 'class': 'td' }, rule.type), + E('td', { 'class': 'td' }, rule.target), + E('td', { 'class': 'td' }, rule.limit_down > 0 ? rule.limit_down + ' kbit/s' : _('Unlimited')), + E('td', { 'class': 'td' }, String(rule.priority)) + ])); + }); + + rulesSection.appendChild(rulesTable); + v.appendChild(rulesSection); + } + + // Client quotas summary + if (quotas.length > 0) { + var quotasSection = E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Client Quotas')) + ]); + + var quotasTable = E('table', { 'class': 'table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, _('Client')), + E('th', { 'class': 'th' }, _('MAC')), + E('th', { 'class': 'th' }, _('Usage')), + E('th', { 'class': 'th' }, _('Limit')), + E('th', { 'class': 'th' }, _('Progress')) + ]) + ]); + + quotas.slice(0, 5).forEach(function(quota) { + var progressColor = quota.percent > 90 ? 'red' : (quota.percent > 75 ? 'orange' : 'green'); + + quotasTable.appendChild(E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, quota.name || quota.mac), + E('td', { 'class': 'td' }, E('code', {}, quota.mac)), + E('td', { 'class': 'td' }, quota.used_mb + ' MB'), + E('td', { 'class': 'td' }, quota.limit_mb + ' MB'), + E('td', { 'class': 'td' }, [ + E('div', { 'style': 'background: #eee; width: 100px; height: 10px; border-radius: 5px;' }, [ + E('div', { + 'style': 'background: ' + progressColor + '; width: ' + Math.min(quota.percent, 100) + '%; height: 100%; border-radius: 5px;' + }) + ]), + E('small', {}, quota.percent + '%') + ]) + ])); + }); + + quotasSection.appendChild(quotasTable); + v.appendChild(quotasSection); + } + + return v; + }, + + formatBytes: function(bytes) { + if (bytes === 0) return '0 B'; + var k = 1024; + var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + var i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null }); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/quotas.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/quotas.js index fda8c412..2364a6c9 100644 --- a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/quotas.js +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/quotas.js @@ -1,34 +1,114 @@ 'use strict'; 'require view'; -'require bandwidth-manager.api as api'; +'require ui'; +'require form'; +'require bandwidth-manager/api as API'; -return view.extend({ - load: function() { return api.getQuotas(); }, - render: function(data) { - var quotas = data.quotas || []; - return E('div', {class:'cbi-map'}, [ - E('h2', {}, '📉 Bandwidth Quotas'), - E('p', {style:'color:#94a3b8'}, 'Set daily/monthly limits and throttle actions.'), - E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;margin-top:20px'}, [ - E('table', {style:'width:100%;color:#f1f5f9'}, [ - E('tr', {style:'border-bottom:1px solid #334155'}, [ - E('th', {style:'padding:12px;text-align:left'}, 'Profile'), - E('th', {style:'padding:12px'}, 'Daily Limit'), - E('th', {style:'padding:12px'}, 'Monthly Limit'), - E('th', {style:'padding:12px'}, 'Throttle Speed'), - E('th', {style:'padding:12px'}, 'Action') - ]) - ].concat(quotas.map(function(q) { - return E('tr', {}, [ - E('td', {style:'padding:12px;font-weight:600'}, q.id), - E('td', {style:'padding:12px;text-align:center'}, q.daily_limit ? api.formatBytes(q.daily_limit * 1024 * 1024) : '∞'), - E('td', {style:'padding:12px;text-align:center'}, q.monthly_limit ? api.formatBytes(q.monthly_limit * 1024 * 1024) : '∞'), - E('td', {style:'padding:12px;text-align:center'}, api.formatSpeed(q.throttle_speed)), - E('td', {style:'padding:12px;text-align:center'}, E('span', {style:'padding:4px 8px;border-radius:4px;background:'+(q.action==='block'?'#ef444420;color:#ef4444':'#f59e0b20;color:#f59e0b')}, q.action)) - ]); - }))) - ]) - ]); - }, - handleSaveApply:null,handleSave:null,handleReset:null +return L.view.extend({ + load: function() { + return API.listQuotas(); + }, + + render: function(quotas) { + var m = new form.Map('bandwidth', _('Client Quotas'), + _('Set monthly data quotas for individual clients by MAC address')); + + var s = m.section(form.GridSection, 'quota', _('Quotas')); + s.anonymous = false; + s.addremove = true; + s.sortable = true; + + s.modaltitle = function(section_id) { + return _('Edit Quota: ') + section_id; + }; + + // Custom render to show usage progress bars + s.addModalOptions = function(s, section_id, ev) { + var mac = this.section.formvalue(section_id, 'mac'); + + if (!mac) { + ui.addNotification(null, E('p', _('MAC address is required')), 'error'); + return; + } + + // Save quota via API + var name = this.section.formvalue(section_id, 'name') || ''; + var limit_mb = parseInt(this.section.formvalue(section_id, 'limit_mb')) || 0; + var action = this.section.formvalue(section_id, 'action') || 'throttle'; + var reset_day = parseInt(this.section.formvalue(section_id, 'reset_day')) || 1; + + API.setQuota(mac, name, limit_mb, action, reset_day).then(function(result) { + if (result.success) { + ui.addNotification(null, E('p', '✓ ' + result.message), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', '✗ ' + result.message), 'error'); + } + }); + }; + + var o; + + o = s.option(form.Value, 'mac', _('MAC Address')); + o.rmempty = false; + o.datatype = 'macaddr'; + o.placeholder = 'AA:BB:CC:DD:EE:FF'; + + o = s.option(form.Value, 'name', _('Client Name')); + o.placeholder = 'iPhone John'; + o.description = _('Friendly name for this client'); + + o = s.option(form.Value, 'limit_mb', _('Monthly Limit (MB)')); + o.rmempty = false; + o.datatype = 'uinteger'; + o.placeholder = '10240'; + o.description = _('Monthly data limit in megabytes (e.g., 10240 = 10GB)'); + + o = s.option(form.ListValue, 'action', _('Action When Exceeded')); + o.value('throttle', _('Throttle bandwidth')); + o.value('block', _('Block all traffic')); + o.value('notify', _('Notify only')); + o.default = 'throttle'; + + o = s.option(form.Value, 'reset_day', _('Reset Day')); + o.datatype = 'range(1,28)'; + o.default = '1'; + o.description = _('Day of month to reset quota (1-28)'); + + o = s.option(form.Flag, 'enabled', _('Enabled')); + o.default = o.enabled; + + // Show current usage + s.renderRowActions = function(section_id) { + var config_name = this.uciconfig || this.map.config; + var mac = this.cfgvalue(section_id, 'mac'); + + var resetBtn = E('button', { + 'class': 'cbi-button cbi-button-action', + 'click': function(ev) { + ev.preventDefault(); + if (confirm(_('Reset quota counter for this client?'))) { + API.resetQuota(mac).then(function(result) { + if (result.success) { + ui.addNotification(null, E('p', '✓ ' + result.message), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', '✗ ' + result.message), 'error'); + } + }); + } + } + }, _('Reset Counter')); + + var actions = form.GridSection.prototype.renderRowActions.call(this, section_id); + actions.appendChild(resetBtn); + return actions; + }; + + return m.render(); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null }); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/rules.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/rules.js new file mode 100644 index 00000000..16a75b4f --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/rules.js @@ -0,0 +1,79 @@ +'use strict'; +'require view'; +'require ui'; +'require form'; +'require bandwidth-manager/api as API'; + +return L.view.extend({ + load: function() { + return API.listRules(); + }, + + render: function(rules) { + var m = new form.Map('bandwidth', _('QoS Rules'), + _('Define traffic shaping rules based on applications, ports, or IP addresses')); + + var s = m.section(form.GridSection, 'rule', _('Rules')); + s.anonymous = false; + s.addremove = true; + s.sortable = true; + + s.modaltitle = function(section_id) { + return _('Edit Rule: ') + section_id; + }; + + var o; + + o = s.option(form.Value, 'name', _('Rule Name')); + o.rmempty = false; + o.placeholder = 'Limit YouTube'; + + o = s.option(form.ListValue, 'type', _('Type')); + o.value('application', _('Application')); + o.value('port', _('Port')); + o.value('ip', _('IP Address')); + o.value('mac', _('MAC Address')); + o.default = 'application'; + + o = s.option(form.Value, 'target', _('Target')); + o.rmempty = false; + o.placeholder = 'youtube / 80,443 / 192.168.1.100 / AA:BB:CC:DD:EE:FF'; + o.description = _('Application name, port(s), IP address, or MAC address'); + + o = s.option(form.Value, 'limit_down', _('Download Limit (kbit/s)')); + o.datatype = 'uinteger'; + o.placeholder = '5000'; + o.description = _('Maximum download speed in kbit/s (0 = unlimited)'); + o.default = '0'; + + o = s.option(form.Value, 'limit_up', _('Upload Limit (kbit/s)')); + o.datatype = 'uinteger'; + o.placeholder = '1000'; + o.description = _('Maximum upload speed in kbit/s (0 = unlimited)'); + o.default = '0'; + + o = s.option(form.ListValue, 'priority', _('Priority')); + o.value('1', '1 (Highest)'); + o.value('2', '2 (High)'); + o.value('3', '3'); + o.value('4', '4'); + o.value('5', '5 (Normal)'); + o.value('6', '6'); + o.value('7', '7 (Low)'); + o.value('8', '8 (Lowest)'); + o.default = '5'; + + o = s.option(form.Value, 'schedule', _('Schedule (Optional)')); + o.placeholder = 'Mon-Fri 08:00-18:00'; + o.description = _('Apply rule only during specific times'); + + o = s.option(form.Flag, 'enabled', _('Enabled')); + o.default = o.enabled; + + return m.render(); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/settings.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/settings.js new file mode 100644 index 00000000..ce6b0d4b --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/settings.js @@ -0,0 +1,109 @@ +'use strict'; +'require view'; +'require form'; +'require network'; + +return L.view.extend({ + load: function() { + return network.getDevices(); + }, + + render: function(devices) { + var m = new form.Map('bandwidth', _('Bandwidth Manager Settings'), + _('Global settings and SQM/CAKE configuration')); + + var s = m.section(form.NamedSection, 'global', 'global', _('Global Settings')); + s.anonymous = false; + s.addremove = false; + + var o; + + o = s.option(form.Flag, 'enabled', _('Enable Bandwidth Manager')); + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.ListValue, 'interface', _('Interface')); + devices.forEach(function(dev) { + var name = dev.getName(); + o.value(name, name); + }); + o.default = 'br-lan'; + o.rmempty = false; + + o = s.option(form.Flag, 'sqm_enabled', _('Enable SQM')); + o.description = _('Smart Queue Management with CAKE qdisc'); + o.default = o.disabled; + + // SQM Configuration + var sqm = m.section(form.NamedSection, 'sqm', 'sqm', _('SQM/CAKE Configuration')); + sqm.anonymous = false; + sqm.addremove = false; + + o = sqm.option(form.Value, 'download_speed', _('Download Speed (kbit/s)')); + o.datatype = 'uinteger'; + o.placeholder = '100000'; + o.description = _('Your internet download speed in kbit/s'); + o.depends('global.sqm_enabled', '1'); + + o = sqm.option(form.Value, 'upload_speed', _('Upload Speed (kbit/s)')); + o.datatype = 'uinteger'; + o.placeholder = '50000'; + o.description = _('Your internet upload speed in kbit/s'); + o.depends('global.sqm_enabled', '1'); + + o = sqm.option(form.ListValue, 'qdisc', _('Queue Discipline')); + o.value('cake', 'CAKE (Recommended)'); + o.value('fq_codel', 'FQ_CoDel'); + o.value('htb', 'HTB'); + o.default = 'cake'; + o.depends('global.sqm_enabled', '1'); + + o = sqm.option(form.Flag, 'nat', _('NAT Mode')); + o.description = _('Enable if router performs NAT'); + o.default = o.enabled; + o.depends('global.sqm_enabled', '1'); + + o = sqm.option(form.ListValue, 'overhead', _('Link Overhead')); + o.value('0', _('None')); + o.value('18', 'Ethernet (18 bytes)'); + o.value('22', 'PPPoE (22 bytes)'); + o.value('40', 'VLAN + PPPoE (40 bytes)'); + o.default = '0'; + o.depends('global.sqm_enabled', '1'); + + // Traffic Tracking + var tracking = m.section(form.NamedSection, 'tracking', 'tracking', _('Traffic Tracking')); + tracking.anonymous = false; + tracking.addremove = false; + + o = tracking.option(form.Flag, 'iptables_tracking', _('Enable iptables Tracking')); + o.description = _('Track per-client bandwidth usage with iptables counters'); + o.default = o.enabled; + + o = tracking.option(form.Value, 'history_retention', _('History Retention (days)')); + o.datatype = 'range(1,90)'; + o.default = '30'; + o.description = _('How long to keep usage history'); + + // Alerts + var alerts = m.section(form.NamedSection, 'alerts', 'alerts', _('Alert Settings')); + alerts.anonymous = false; + alerts.addremove = false; + + o = alerts.option(form.Flag, 'enabled', _('Enable Alerts')); + o.default = o.disabled; + + o = alerts.option(form.Value, 'quota_threshold', _('Quota Alert Threshold (%)')); + o.datatype = 'range(50,100)'; + o.default = '90'; + o.description = _('Send alert when quota usage exceeds this percentage'); + o.depends('enabled', '1'); + + o = alerts.option(form.Value, 'email', _('Alert Email')); + o.datatype = 'email'; + o.placeholder = 'admin@example.com'; + o.depends('enabled', '1'); + + return m.render(); + } +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/usage.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/usage.js new file mode 100644 index 00000000..f8bedbd5 --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/usage.js @@ -0,0 +1,96 @@ +'use strict'; +'require view'; +'require poll'; +'require bandwidth-manager/api as API'; + +return L.view.extend({ + load: function() { + return API.getUsageRealtime(); + }, + + render: function(clients) { + var v = E('div', { 'class': 'cbi-map' }, [ + E('h2', {}, _('Real-time Usage')), + E('div', { 'class': 'cbi-map-descr' }, _('Live bandwidth usage per client (updates every 5 seconds)')) + ]); + + var container = E('div', { 'id': 'usage-container', 'class': 'cbi-section' }); + v.appendChild(container); + + // Initial render + this.renderUsageTable(container, clients); + + // Auto-refresh every 5 seconds + poll.add(L.bind(function() { + return API.getUsageRealtime().then(L.bind(function(data) { + this.renderUsageTable(container, data); + }, this)); + }, this), 5); + + return v; + }, + + renderUsageTable: function(container, clients) { + L.dom.content(container, [ + E('h3', {}, _('Active Clients')), + E('table', { 'class': 'table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, _('Client')), + E('th', { 'class': 'th' }, _('IP')), + E('th', { 'class': 'th' }, _('MAC')), + E('th', { 'class': 'th' }, _('Download')), + E('th', { 'class': 'th' }, _('Upload')), + E('th', { 'class': 'th' }, _('Total')), + E('th', { 'class': 'th' }, _('Quota')) + ]) + ].concat(clients.length > 0 ? clients.map(L.bind(function(client) { + var total = (client.rx_bytes || 0) + (client.tx_bytes || 0); + + var quotaCell = _('None'); + if (client.has_quota) { + var percent = client.limit_mb > 0 ? Math.floor((client.used_mb * 100) / client.limit_mb) : 0; + var color = percent > 90 ? 'red' : (percent > 75 ? 'orange' : 'green'); + quotaCell = [ + E('div', {}, client.used_mb + ' / ' + client.limit_mb + ' MB'), + E('div', { 'style': 'background: #eee; width: 80px; height: 8px; border-radius: 4px;' }, [ + E('div', { + 'style': 'background: ' + color + '; width: ' + Math.min(percent, 100) + '%; height: 100%; border-radius: 4px;' + }) + ]) + ]; + } + + return E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, client.hostname), + E('td', { 'class': 'td' }, client.ip), + E('td', { 'class': 'td' }, E('code', {}, client.mac)), + E('td', { 'class': 'td' }, [ + E('span', { 'style': 'color: #28a745' }, '⬇ ' + this.formatBytes(client.rx_bytes)) + ]), + E('td', { 'class': 'td' }, [ + E('span', { 'style': 'color: #dc3545' }, '⬆ ' + this.formatBytes(client.tx_bytes)) + ]), + E('td', { 'class': 'td' }, E('strong', {}, this.formatBytes(total))), + E('td', { 'class': 'td' }, quotaCell) + ]); + }, this)) : [ + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td center', 'colspan': 7 }, + E('em', {}, _('No active clients'))) + ]) + ])) + ]); + }, + + formatBytes: function(bytes) { + if (bytes === 0) return '0 B'; + var k = 1024; + var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + var i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/luci-app-bandwidth-manager/root/etc/config/bandwidth b/luci-app-bandwidth-manager/root/etc/config/bandwidth index 7c7a4781..d2db8ae5 100644 --- a/luci-app-bandwidth-manager/root/etc/config/bandwidth +++ b/luci-app-bandwidth-manager/root/etc/config/bandwidth @@ -1,89 +1,39 @@ -config bandwidth 'global' - option enabled '1' +config global 'global' + option enabled '0' option interface 'br-lan' - option wan_interface 'wan' - option default_download '100000' - option default_upload '50000' - option quota_period 'monthly' + option sqm_enabled '0' -config class 'realtime' - option name 'Real-time' - option priority '1' - option rate '30' - option ceil '100' - option description 'VoIP, Video calls' +config sqm 'sqm' + option download_speed '100000' + option upload_speed '50000' + option qdisc 'cake' + option nat '1' + option overhead '0' -config class 'interactive' - option name 'Interactive' - option priority '2' - option rate '20' - option ceil '100' - option description 'Gaming, SSH, DNS' +config tracking 'tracking' + option iptables_tracking '1' + option history_retention '30' -config class 'streaming' - option name 'Streaming' - option priority '3' - option rate '20' - option ceil '90' - option description 'Video streaming' +config alerts 'alerts' + option enabled '0' + option quota_threshold '90' + option email '' -config class 'browsing' - option name 'Browsing' - option priority '4' - option rate '15' - option ceil '80' - option description 'Web browsing' +# Example QoS rule +#config rule 'rule_youtube' +# option name 'Limit YouTube' +# option type 'application' +# option target 'youtube' +# option limit_down '5000' +# option limit_up '1000' +# option priority '6' +# option enabled '1' -config class 'download' - option name 'Downloads' - option priority '5' - option rate '10' - option ceil '70' - option description 'File downloads' - -config class 'bulk' - option name 'Bulk' - option priority '6' - option rate '5' - option ceil '50' - option description 'P2P, Backups' - -config media 'voip' - option name 'VoIP' - option class 'realtime' - list port '5060' - list port '5061' - list port '10000-20000' - list protocol 'sip' - list protocol 'rtp' - -config media 'gaming' - option name 'Gaming' - option class 'interactive' - list port '3074' - list port '3478-3480' - list port '27015-27030' - option dscp 'ef' - -config media 'streaming' - option name 'Streaming' - option class 'streaming' - list domain 'netflix.com' - list domain 'youtube.com' - list domain 'twitch.tv' - list domain 'spotify.com' - -config quota 'default' - option daily_limit '0' - option monthly_limit '0' - option throttle_speed '1000' - option action 'throttle' - -config schedule 'peak' - option name 'Peak Hours' - option enabled '1' - option days 'mon tue wed thu fri' - option start '18:00' - option end '23:00' - option download_limit '80' - option upload_limit '80' +# Example client quota +#config quota 'quota_phone' +# option mac 'AA:BB:CC:DD:EE:FF' +# option name 'iPhone Jean' +# option limit_mb '10240' +# option action 'throttle' +# option reset_day '1' +# option enabled '1' diff --git a/luci-app-bandwidth-manager/root/usr/libexec/rpcd/luci.bandwidth-manager b/luci-app-bandwidth-manager/root/usr/libexec/rpcd/luci.bandwidth-manager index 3a8b6faf..6f5a8339 100755 --- a/luci-app-bandwidth-manager/root/usr/libexec/rpcd/luci.bandwidth-manager +++ b/luci-app-bandwidth-manager/root/usr/libexec/rpcd/luci.bandwidth-manager @@ -1,192 +1,556 @@ #!/bin/sh +# Bandwidth Manager RPCD Backend +# Provides QoS rules, client quotas, and traffic statistics + . /lib/functions.sh . /usr/share/libubox/jshn.sh -get_status() { - json_init - - local enabled interface - config_load bandwidth - config_get enabled global enabled "0" - config_get interface global interface "br-lan" - - json_add_boolean "enabled" "$enabled" - json_add_string "interface" "$interface" - - # Get current bandwidth stats - local rx_bytes tx_bytes - rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) - tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) - - json_add_int "rx_bytes" "$rx_bytes" - json_add_int "tx_bytes" "$tx_bytes" - - # Check if QoS is active - local qos_active=0 - tc qdisc show dev $interface 2>/dev/null | grep -qE "(cake|fq_codel|htb)" && qos_active=1 - json_add_boolean "qos_active" "$qos_active" - - json_dump +# Configuration paths +CONFIG_FILE="/etc/config/bandwidth" +USAGE_DB="/tmp/bandwidth_usage.db" +IPTABLES_CHAIN="BW_TRACKING" + +# Initialize usage database +init_usage_db() { + if [ ! -f "$USAGE_DB" ]; then + cat > "$USAGE_DB" << 'EOF' +# MAC|Timestamp|RX_Bytes|TX_Bytes +EOF + fi } -get_classes() { - config_load bandwidth - json_init - json_add_array "classes" - - _add_class() { - local name priority rate ceil desc - config_get name "$1" name "" - config_get priority "$1" priority "5" - config_get rate "$1" rate "10" - config_get ceil "$1" ceil "100" - config_get desc "$1" description "" - - json_add_object "" - json_add_string "id" "$1" - json_add_string "name" "$name" - json_add_int "priority" "$priority" - json_add_int "rate" "$rate" - json_add_int "ceil" "$ceil" - json_add_string "description" "$desc" - json_close_object - } - config_foreach _add_class class - - json_close_array - json_dump +# Get system status and global stats +status() { + json_init + + local enabled interface sqm_enabled + config_load bandwidth + config_get enabled global enabled "0" + config_get interface global interface "br-lan" + config_get sqm_enabled global sqm_enabled "0" + + json_add_boolean "enabled" "$enabled" + json_add_string "interface" "$interface" + json_add_boolean "sqm_enabled" "$sqm_enabled" + + # Check QoS status + local qos_active=0 + tc qdisc show dev "$interface" 2>/dev/null | grep -qE "(cake|htb|fq_codel)" && qos_active=1 + json_add_boolean "qos_active" "$qos_active" + + # Get interface stats + if [ -d "/sys/class/net/$interface" ]; then + local rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) + local tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) + local rx_packets=$(cat /sys/class/net/$interface/statistics/rx_packets 2>/dev/null || echo 0) + local tx_packets=$(cat /sys/class/net/$interface/statistics/tx_packets 2>/dev/null || echo 0) + + json_add_object "stats" + json_add_int "rx_bytes" "$rx_bytes" + json_add_int "tx_bytes" "$tx_bytes" + json_add_int "rx_packets" "$rx_packets" + json_add_int "tx_packets" "$tx_packets" + json_close_object + fi + + # Count rules and quotas + local rule_count=0 + local quota_count=0 + config_foreach count_section rule && rule_count=$? + config_foreach count_section quota && quota_count=$? + + json_add_int "rule_count" "$rule_count" + json_add_int "quota_count" "$quota_count" + + json_dump } -get_quotas() { - config_load bandwidth - json_init - json_add_array "quotas" - - _add_quota() { - local daily monthly throttle action - config_get daily "$1" daily_limit "0" - config_get monthly "$1" monthly_limit "0" - config_get throttle "$1" throttle_speed "1000" - config_get action "$1" action "throttle" - - json_add_object "" - json_add_string "id" "$1" - json_add_int "daily_limit" "$daily" - json_add_int "monthly_limit" "$monthly" - json_add_int "throttle_speed" "$throttle" - json_add_string "action" "$action" - json_close_object - } - config_foreach _add_quota quota - - json_close_array - json_dump +count_section() { + return $(( $? + 1 )) } -get_media() { - config_load bandwidth - json_init - json_add_array "media" - - _add_media() { - local name class - config_get name "$1" name "" - config_get class "$1" class "" - - json_add_object "" - json_add_string "id" "$1" - json_add_string "name" "$name" - json_add_string "class" "$class" - json_close_object - } - config_foreach _add_media media - - json_close_array - json_dump +# List all QoS rules +list_rules() { + config_load bandwidth + json_init + json_add_array "rules" + + _add_rule() { + local name type target limit_down limit_up priority enabled schedule + config_get name "$1" name "" + config_get type "$1" type "application" + config_get target "$1" target "" + config_get limit_down "$1" limit_down "0" + config_get limit_up "$1" limit_up "0" + config_get priority "$1" priority "5" + config_get enabled "$1" enabled "1" + config_get schedule "$1" schedule "" + + json_add_object "" + json_add_string "id" "$1" + json_add_string "name" "$name" + json_add_string "type" "$type" + json_add_string "target" "$target" + json_add_int "limit_down" "$limit_down" + json_add_int "limit_up" "$limit_up" + json_add_int "priority" "$priority" + json_add_boolean "enabled" "$enabled" + json_add_string "schedule" "$schedule" + json_close_object + } + + config_foreach _add_rule rule + + json_close_array + json_dump } -get_clients() { - json_init - json_add_array "clients" - - # Parse DHCP leases - if [ -f /tmp/dhcp.leases ]; then - while read expires mac ip hostname clientid; do - # Get current bandwidth for this client - local rx=0 tx=0 - - json_add_object "" - json_add_string "mac" "$mac" - json_add_string "ip" "$ip" - json_add_string "hostname" "${hostname:-unknown}" - json_add_int "rx_bytes" "$rx" - json_add_int "tx_bytes" "$tx" - json_close_object - done < /tmp/dhcp.leases - fi - - json_close_array - json_dump +# Add new QoS rule +add_rule() { + read -r input + json_load "$input" + + local name type target limit_down limit_up priority + json_get_var name name + json_get_var type type "application" + json_get_var target target + json_get_var limit_down limit_down "0" + json_get_var limit_up limit_up "0" + json_get_var priority priority "5" + + json_cleanup + + if [ -z "$name" ] || [ -z "$target" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "message" "Name and target are required" + json_dump + return 1 + fi + + # Generate unique ID + local rule_id="rule_$(date +%s)" + + # Add to UCI config + uci -q batch << EOF +set bandwidth.$rule_id=rule +set bandwidth.$rule_id.name='$name' +set bandwidth.$rule_id.type='$type' +set bandwidth.$rule_id.target='$target' +set bandwidth.$rule_id.limit_down='$limit_down' +set bandwidth.$rule_id.limit_up='$limit_up' +set bandwidth.$rule_id.priority='$priority' +set bandwidth.$rule_id.enabled='1' +commit bandwidth +EOF + + json_init + json_add_boolean "success" 1 + json_add_string "rule_id" "$rule_id" + json_add_string "message" "Rule created successfully" + json_dump } -get_stats() { - json_init - - local interface - config_load bandwidth - config_get interface global interface "br-lan" - - # TC statistics - json_add_object "tc" - local tc_stats=$(tc -s qdisc show dev $interface 2>/dev/null) - json_add_string "raw" "$tc_stats" - json_close_object - - # Interface statistics - json_add_object "interface" - json_add_int "rx_bytes" "$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0)" - json_add_int "tx_bytes" "$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0)" - json_add_int "rx_packets" "$(cat /sys/class/net/$interface/statistics/rx_packets 2>/dev/null || echo 0)" - json_add_int "tx_packets" "$(cat /sys/class/net/$interface/statistics/tx_packets 2>/dev/null || echo 0)" - json_close_object - - json_dump +# Delete QoS rule +delete_rule() { + read -r input + json_load "$input" + + local rule_id + json_get_var rule_id rule_id + json_cleanup + + if [ -z "$rule_id" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "message" "Rule ID is required" + json_dump + return 1 + fi + + # Check if rule exists + if ! uci -q get bandwidth.$rule_id >/dev/null 2>&1; then + json_init + json_add_boolean "success" 0 + json_add_string "message" "Rule not found" + json_dump + return 1 + fi + + uci -q delete bandwidth.$rule_id + uci -q commit bandwidth + + json_init + json_add_boolean "success" 1 + json_add_string "message" "Rule deleted successfully" + json_dump } -apply_qos() { - local interface download upload - config_load bandwidth - config_get interface global interface "br-lan" - config_get download global default_download "100000" - config_get upload global default_upload "50000" - - # Clear existing - tc qdisc del dev $interface root 2>/dev/null - tc qdisc del dev $interface ingress 2>/dev/null - - # Apply CAKE qdisc - tc qdisc add dev $interface root cake bandwidth ${download}kbit - - json_init - json_add_boolean "success" 1 - json_add_string "message" "QoS applied with ${download}kbit download" - json_dump +# List all client quotas +list_quotas() { + config_load bandwidth + json_init + json_add_array "quotas" + + _add_quota() { + local mac name limit_mb used_mb action reset_day enabled + config_get mac "$1" mac "" + config_get name "$1" name "" + config_get limit_mb "$1" limit_mb "0" + config_get action "$1" action "throttle" + config_get reset_day "$1" reset_day "1" + config_get enabled "$1" enabled "1" + + # Get current usage + used_mb=$(get_mac_usage "$mac") + local percent=0 + if [ "$limit_mb" -gt 0 ]; then + percent=$(( (used_mb * 100) / limit_mb )) + fi + + json_add_object "" + json_add_string "id" "$1" + json_add_string "mac" "$mac" + json_add_string "name" "$name" + json_add_int "limit_mb" "$limit_mb" + json_add_int "used_mb" "$used_mb" + json_add_int "percent" "$percent" + json_add_string "action" "$action" + json_add_int "reset_day" "$reset_day" + json_add_boolean "enabled" "$enabled" + json_close_object + } + + config_foreach _add_quota quota + + json_close_array + json_dump } +# Get usage for a specific MAC address +get_mac_usage() { + local mac="$1" + local total_bytes=0 + + # Get from iptables counters + if iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -qi "$mac"; then + local bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{sum+=$2} END {print sum}') + total_bytes=${bytes:-0} + fi + + # Convert to MB + echo $(( total_bytes / 1024 / 1024 )) +} + +# Get quota details for specific client +get_quota() { + read -r input + json_load "$input" + + local mac + json_get_var mac mac + json_cleanup + + if [ -z "$mac" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "message" "MAC address is required" + json_dump + return 1 + fi + + config_load bandwidth + local found=0 + local quota_id name limit_mb action reset_day + + _find_quota() { + local this_mac + config_get this_mac "$1" mac "" + if [ "$this_mac" = "$mac" ]; then + quota_id="$1" + config_get name "$1" name "" + config_get limit_mb "$1" limit_mb "0" + config_get action "$1" action "throttle" + config_get reset_day "$1" reset_day "1" + found=1 + fi + } + + config_foreach _find_quota quota + + if [ "$found" -eq 0 ]; then + json_init + json_add_boolean "success" 0 + json_add_string "message" "Quota not found for this MAC" + json_dump + return 1 + fi + + local used_mb=$(get_mac_usage "$mac") + local remaining_mb=$(( limit_mb - used_mb )) + [ "$remaining_mb" -lt 0 ] && remaining_mb=0 + + local percent=0 + if [ "$limit_mb" -gt 0 ]; then + percent=$(( (used_mb * 100) / limit_mb )) + fi + + json_init + json_add_boolean "success" 1 + json_add_string "quota_id" "$quota_id" + json_add_string "mac" "$mac" + json_add_string "name" "$name" + json_add_int "limit_mb" "$limit_mb" + json_add_int "used_mb" "$used_mb" + json_add_int "remaining_mb" "$remaining_mb" + json_add_int "percent" "$percent" + json_add_string "action" "$action" + json_add_int "reset_day" "$reset_day" + json_dump +} + +# Set or update quota +set_quota() { + read -r input + json_load "$input" + + local mac name limit_mb action reset_day + json_get_var mac mac + json_get_var name name "" + json_get_var limit_mb limit_mb "0" + json_get_var action action "throttle" + json_get_var reset_day reset_day "1" + json_cleanup + + if [ -z "$mac" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "message" "MAC address is required" + json_dump + return 1 + fi + + # Check if quota exists for this MAC + config_load bandwidth + local quota_id="" + + _find_existing() { + local this_mac + config_get this_mac "$1" mac "" + if [ "$this_mac" = "$mac" ]; then + quota_id="$1" + fi + } + + config_foreach _find_existing quota + + if [ -z "$quota_id" ]; then + # Create new quota + quota_id="quota_$(date +%s)" + uci -q batch << EOF +set bandwidth.$quota_id=quota +set bandwidth.$quota_id.mac='$mac' +set bandwidth.$quota_id.name='$name' +set bandwidth.$quota_id.limit_mb='$limit_mb' +set bandwidth.$quota_id.action='$action' +set bandwidth.$quota_id.reset_day='$reset_day' +set bandwidth.$quota_id.enabled='1' +commit bandwidth +EOF + local msg="Quota created successfully" + else + # Update existing quota + uci -q batch << EOF +set bandwidth.$quota_id.name='$name' +set bandwidth.$quota_id.limit_mb='$limit_mb' +set bandwidth.$quota_id.action='$action' +set bandwidth.$quota_id.reset_day='$reset_day' +commit bandwidth +EOF + local msg="Quota updated successfully" + fi + + json_init + json_add_boolean "success" 1 + json_add_string "quota_id" "$quota_id" + json_add_string "message" "$msg" + json_dump +} + +# Reset quota counter for a client +reset_quota() { + read -r input + json_load "$input" + + local mac + json_get_var mac mac + json_cleanup + + if [ -z "$mac" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "message" "MAC address is required" + json_dump + return 1 + fi + + # Reset iptables counters for this MAC + iptables -Z $IPTABLES_CHAIN 2>/dev/null + + # Remove from usage DB + if [ -f "$USAGE_DB" ]; then + sed -i "/^${mac}|/d" "$USAGE_DB" + fi + + json_init + json_add_boolean "success" 1 + json_add_string "message" "Quota counter reset for $mac" + json_dump +} + +# Get real-time usage for all clients +get_usage_realtime() { + json_init + json_add_array "clients" + + # Parse DHCP leases for active clients + if [ -f /tmp/dhcp.leases ]; then + while read -r expires mac ip hostname clientid; do + local rx_bytes=0 tx_bytes=0 + + # Get current bytes from iptables + if iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -qi "$mac"; then + rx_bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{print $2}' | head -1) + tx_bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{print $2}' | tail -1) + fi + + # Get quota info if exists + config_load bandwidth + local has_quota=0 limit_mb=0 used_mb=0 + + _check_quota() { + local this_mac + config_get this_mac "$1" mac "" + if [ "$this_mac" = "$mac" ]; then + has_quota=1 + config_get limit_mb "$1" limit_mb "0" + used_mb=$(get_mac_usage "$mac") + fi + } + + config_foreach _check_quota quota + + json_add_object "" + json_add_string "mac" "$mac" + json_add_string "ip" "$ip" + json_add_string "hostname" "${hostname:-unknown}" + json_add_int "rx_bytes" "${rx_bytes:-0}" + json_add_int "tx_bytes" "${tx_bytes:-0}" + json_add_boolean "has_quota" "$has_quota" + if [ "$has_quota" -eq 1 ]; then + json_add_int "limit_mb" "$limit_mb" + json_add_int "used_mb" "$used_mb" + fi + json_close_object + done < /tmp/dhcp.leases + fi + + json_close_array + json_dump +} + +# Get usage history +get_usage_history() { + read -r input + json_load "$input" + + local timeframe mac + json_get_var timeframe timeframe "24h" + json_get_var mac mac "" + json_cleanup + + init_usage_db + + json_init + json_add_array "history" + + # Calculate time threshold + local now=$(date +%s) + local threshold=0 + case "$timeframe" in + "1h") threshold=$(( now - 3600 )) ;; + "6h") threshold=$(( now - 21600 )) ;; + "24h") threshold=$(( now - 86400 )) ;; + "7d") threshold=$(( now - 604800 )) ;; + "30d") threshold=$(( now - 2592000 )) ;; + *) threshold=$(( now - 86400 )) ;; + esac + + # Read usage database + if [ -f "$USAGE_DB" ]; then + while IFS='|' read -r db_mac timestamp rx_bytes tx_bytes; do + # Skip header line + [ "$db_mac" = "# MAC" ] && continue + + # Filter by MAC if specified + if [ -n "$mac" ] && [ "$db_mac" != "$mac" ]; then + continue + fi + + # Filter by timeframe + if [ "$timestamp" -lt "$threshold" ]; then + continue + fi + + json_add_object "" + json_add_string "mac" "$db_mac" + json_add_int "timestamp" "$timestamp" + json_add_int "rx_bytes" "$rx_bytes" + json_add_int "tx_bytes" "$tx_bytes" + json_close_object + done < "$USAGE_DB" + fi + + json_close_array + json_dump +} + +# Main dispatcher case "$1" in - list) - echo '{"status":{},"classes":{},"quotas":{},"media":{},"clients":{},"stats":{},"apply_qos":{}}' - ;; - call) - case "$2" in - status) get_status ;; - classes) get_classes ;; - quotas) get_quotas ;; - media) get_media ;; - clients) get_clients ;; - stats) get_stats ;; - apply_qos) apply_qos ;; - *) echo '{"error":"Unknown method"}' ;; - esac - ;; + list) + cat << 'EOF' +{ + "status": {}, + "list_rules": {}, + "add_rule": { "name": "string", "type": "string", "target": "string", "limit_down": 0, "limit_up": 0, "priority": 5 }, + "delete_rule": { "rule_id": "string" }, + "list_quotas": {}, + "get_quota": { "mac": "string" }, + "set_quota": { "mac": "string", "name": "string", "limit_mb": 0, "action": "string", "reset_day": 1 }, + "reset_quota": { "mac": "string" }, + "get_usage_realtime": {}, + "get_usage_history": { "timeframe": "24h", "mac": "" } +} +EOF + ;; + call) + case "$2" in + status) status ;; + list_rules) list_rules ;; + add_rule) add_rule ;; + delete_rule) delete_rule ;; + list_quotas) list_quotas ;; + get_quota) get_quota ;; + set_quota) set_quota ;; + reset_quota) reset_quota ;; + get_usage_realtime) get_usage_realtime ;; + get_usage_history) get_usage_history ;; + *) + json_init + json_add_boolean "success" 0 + json_add_string "error" "Unknown method: $2" + json_dump + ;; + esac + ;; esac diff --git a/luci-app-bandwidth-manager/root/usr/share/luci/menu.d/luci-app-bandwidth-manager.json b/luci-app-bandwidth-manager/root/usr/share/luci/menu.d/luci-app-bandwidth-manager.json index 579ac49a..d00feb56 100644 --- a/luci-app-bandwidth-manager/root/usr/share/luci/menu.d/luci-app-bandwidth-manager.json +++ b/luci-app-bandwidth-manager/root/usr/share/luci/menu.d/luci-app-bandwidth-manager.json @@ -1,37 +1,52 @@ { - "admin/network/bandwidth": { - "title": "Bandwidth Manager", - "order": 80, - "action": {"type": "firstchild"} - }, - "admin/network/bandwidth/overview": { - "title": "Overview", - "order": 10, - "action": {"type": "view", "path": "bandwidth-manager/overview"} - }, - "admin/network/bandwidth/classes": { - "title": "QoS Classes", - "order": 20, - "action": {"type": "view", "path": "bandwidth-manager/classes"} - }, - "admin/network/bandwidth/quotas": { - "title": "Quotas", - "order": 30, - "action": {"type": "view", "path": "bandwidth-manager/quotas"} - }, - "admin/network/bandwidth/media": { - "title": "Media Detection", - "order": 40, - "action": {"type": "view", "path": "bandwidth-manager/media"} - }, - "admin/network/bandwidth/clients": { - "title": "Clients", - "order": 50, - "action": {"type": "view", "path": "bandwidth-manager/clients"} - }, - "admin/network/bandwidth/schedules": { - "title": "Schedules", - "order": 60, - "action": {"type": "view", "path": "bandwidth-manager/schedules"} - } + "admin/network/bandwidth-manager": { + "title": "Bandwidth Manager", + "order": 55, + "action": { + "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-bandwidth-manager"] + } + }, + "admin/network/bandwidth-manager/overview": { + "title": "Overview", + "order": 1, + "action": { + "type": "view", + "path": "bandwidth-manager/overview" + } + }, + "admin/network/bandwidth-manager/rules": { + "title": "QoS Rules", + "order": 2, + "action": { + "type": "view", + "path": "bandwidth-manager/rules" + } + }, + "admin/network/bandwidth-manager/quotas": { + "title": "Client Quotas", + "order": 3, + "action": { + "type": "view", + "path": "bandwidth-manager/quotas" + } + }, + "admin/network/bandwidth-manager/usage": { + "title": "Real-time Usage", + "order": 4, + "action": { + "type": "view", + "path": "bandwidth-manager/usage" + } + }, + "admin/network/bandwidth-manager/settings": { + "title": "Settings", + "order": 5, + "action": { + "type": "view", + "path": "bandwidth-manager/settings" + } + } } diff --git a/luci-app-bandwidth-manager/root/usr/share/rpcd/acl.d/luci-app-bandwidth-manager.json b/luci-app-bandwidth-manager/root/usr/share/rpcd/acl.d/luci-app-bandwidth-manager.json index 1f056e5f..80d2c063 100644 --- a/luci-app-bandwidth-manager/root/usr/share/rpcd/acl.d/luci-app-bandwidth-manager.json +++ b/luci-app-bandwidth-manager/root/usr/share/rpcd/acl.d/luci-app-bandwidth-manager.json @@ -1,17 +1,28 @@ { - "luci-app-bandwidth-manager": { - "description": "Bandwidth Manager", - "read": { - "ubus": { - "luci.bandwidth-manager": ["status", "classes", "quotas", "media", "clients", "stats"] - }, - "uci": ["bandwidth"] - }, - "write": { - "ubus": { - "luci.bandwidth-manager": ["apply_qos"] - }, - "uci": ["bandwidth"] - } - } + "luci-app-bandwidth-manager": { + "description": "Bandwidth Manager - QoS & Traffic Control", + "read": { + "ubus": { + "luci.bandwidth-manager": [ + "status", + "list_rules", + "list_quotas", + "get_quota", + "get_usage_realtime", + "get_usage_history" + ] + } + }, + "write": { + "ubus": { + "luci.bandwidth-manager": [ + "add_rule", + "delete_rule", + "set_quota", + "reset_quota" + ] + }, + "uci": ["bandwidth"] + } + } }