diff --git a/luci-app-bandix/Makefile b/luci-app-bandix/Makefile
index 6480c8a..1903e55 100644
--- a/luci-app-bandix/Makefile
+++ b/luci-app-bandix/Makefile
@@ -10,7 +10,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
PKG_MAINTAINER:=timsaya
-PKG_VERSION:=0.8.3
+PKG_VERSION:=0.9.0
PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk
diff --git a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
index 584c30b..6051ebe 100644
--- a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
+++ b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
@@ -133,13 +133,6 @@ var callStatus = rpc.declare({
expect: {}
});
-var callSetRateLimit = rpc.declare({
- object: 'luci.bandix',
- method: 'setRateLimit',
- params: ['mac', 'wide_tx_rate_limit', 'wide_rx_rate_limit'],
- expect: { success: true }
-});
-
var callSetHostname = rpc.declare({
object: 'luci.bandix',
method: 'setHostname',
@@ -155,6 +148,27 @@ var callGetMetrics = rpc.declare({
expect: {}
});
+// 定时限速 RPC
+var callGetScheduleLimits = rpc.declare({
+ object: 'luci.bandix',
+ method: 'getScheduleLimits',
+ expect: {}
+});
+
+var callSetScheduleLimit = rpc.declare({
+ object: 'luci.bandix',
+ method: 'setScheduleLimit',
+ params: ['mac', 'start_time', 'end_time', 'days', 'wide_tx_rate_limit', 'wide_rx_rate_limit'],
+ expect: { success: true }
+});
+
+var callDeleteScheduleLimit = rpc.declare({
+ object: 'luci.bandix',
+ method: 'deleteScheduleLimit',
+ params: ['mac', 'start_time', 'end_time', 'days'],
+ expect: { success: true }
+});
+
return view.extend({
load: function () {
return Promise.all([
@@ -366,22 +380,28 @@ return view.extend({
.bandix-table th:nth-child(2),
.bandix-table td:nth-child(2) {
- width: 22%;
+ width: 20%;
}
.bandix-table th:nth-child(3),
.bandix-table td:nth-child(3) {
- width: 22%;
+ width: 20%;
}
.bandix-table th:nth-child(4),
.bandix-table td:nth-child(4) {
- width: 15%;
+ width: 25%;
}
.bandix-table th:nth-child(5),
.bandix-table td:nth-child(5) {
- width: 9%;
+ width: 10%;
+ }
+
+ .schedule-rules-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
}
/* 类型联动的高亮与弱化 */
@@ -720,6 +740,206 @@ return view.extend({
outline: none;
}
+ /* Tab 切换样式 */
+ .modal-tabs {
+ display: flex;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ margin-bottom: 20px;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .modal-tabs {
+ border-bottom-color: rgba(255, 255, 255, 0.15);
+ }
+ }
+
+ .modal-tab {
+ flex: 1;
+ padding: 12px 16px;
+ text-align: center;
+ cursor: pointer;
+ border: none;
+ background: transparent;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: rgba(0, 0, 0, 0.6);
+ transition: all 0.2s ease;
+ border-bottom: 2px solid transparent;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .modal-tab {
+ color: rgba(255, 255, 255, 0.6);
+ }
+ }
+
+ .modal-tab:hover {
+ color: rgba(0, 0, 0, 0.8);
+ background-color: rgba(0, 0, 0, 0.02);
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .modal-tab:hover {
+ color: rgba(255, 255, 255, 0.8);
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+ }
+
+ .modal-tab.active {
+ color: #3b82f6;
+ border-bottom-color: #3b82f6;
+ font-weight: 600;
+ }
+
+ .modal-tab-content {
+ display: none;
+ }
+
+ .modal-tab-content.active {
+ display: block;
+ }
+
+ .schedule-time-row {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ margin-bottom: 16px;
+ }
+
+ .schedule-time-input {
+ flex: 1;
+ border-radius: 4px;
+ padding: 8px 12px;
+ font-size: 0.875rem;
+ transition: border-color 0.15s ease;
+ box-sizing: border-box;
+ }
+
+ .schedule-days {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ margin-bottom: 16px;
+ }
+
+ .schedule-day-btn {
+ flex: 1;
+ min-width: 40px;
+ padding: 6px 8px;
+ border-radius: 4px;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ background: transparent;
+ cursor: pointer;
+ font-size: 0.75rem;
+ transition: all 0.15s ease;
+ text-align: center;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .schedule-day-btn {
+ border-color: rgba(255, 255, 255, 0.2);
+ }
+ }
+
+ .schedule-day-btn:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .schedule-day-btn:hover {
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+ }
+
+ .schedule-day-btn.active {
+ background-color: #3b82f6;
+ color: white;
+ border-color: #3b82f6;
+ }
+
+ .schedule-rules-list {
+ min-height: 200px;
+ max-height: 400px;
+ overflow-y: auto;
+ border: 1px dashed rgba(0, 0, 0, 0.2);
+ border-radius: 4px;
+ padding: 16px;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .schedule-rules-list {
+ border-color: rgba(255, 255, 255, 0.2);
+ }
+ }
+
+ .schedule-rules-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 200px;
+ text-align: center;
+ color: rgba(0, 0, 0, 0.5);
+ font-size: 0.875rem;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .schedule-rules-empty {
+ color: rgba(255, 255, 255, 0.5);
+ }
+ }
+
+ .schedule-rule-item {
+ padding: 12px;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ border-radius: 4px;
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .schedule-rule-item {
+ border-color: rgba(255, 255, 255, 0.15);
+ }
+ }
+
+ .schedule-rule-info {
+ flex: 1;
+ }
+
+ .schedule-rule-time {
+ font-weight: 600;
+ margin-bottom: 4px;
+ }
+
+ .schedule-rule-days {
+ font-size: 0.75rem;
+ opacity: 0.7;
+ margin-bottom: 4px;
+ }
+
+ .schedule-rule-limits {
+ font-size: 0.75rem;
+ opacity: 0.7;
+ }
+
+ .schedule-rule-delete {
+ padding: 6px 12px;
+ font-size: 0.75rem;
+ cursor: pointer;
+ border-radius: 4px;
+ border: 1px solid rgba(239, 68, 68, 0.3);
+ background-color: rgba(239, 68, 68, 0.1);
+ color: #ef4444;
+ transition: all 0.15s ease;
+ }
+
+ .schedule-rule-delete:hover {
+ background-color: rgba(239, 68, 68, 0.2);
+ }
+
.device-summary {
border-radius: 4px;
padding: 12px;
@@ -757,6 +977,41 @@ return view.extend({
pointer-events: none;
}
+ /* 确认对话框 */
+ .confirm-dialog {
+ max-width: 400px;
+ width: 90%;
+ }
+
+ .confirm-dialog .modal-body {
+ padding: 24px;
+ }
+
+ .confirm-dialog-title {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin-bottom: 12px;
+ }
+
+ .confirm-dialog-message {
+ font-size: 0.875rem;
+ line-height: 1.5;
+ color: rgba(0, 0, 0, 0.7);
+ margin-bottom: 20px;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .confirm-dialog-message {
+ color: rgba(255, 255, 255, 0.7);
+ }
+ }
+
+ .confirm-dialog-footer {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ }
+
/* 历史趋势 */
.history-header {
display: flex;
@@ -971,45 +1226,80 @@ return view.extend({
font-size: 0.875rem;
}
- .device-card-expandable {
- display: none;
+ /* LAN流量样式(移动端直接显示) */
+ .device-card-lan {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
- .device-card-expandable {
+ .device-card-lan {
border-top-color: rgba(255, 255, 255, 0.15);
}
}
- .device-card.expanded .device-card-expandable {
- display: block;
- }
-
- .device-card-toggle {
- width: 100%;
- margin-top: 8px;
- padding: 6px;
- font-size: 0.75rem;
- text-align: center;
- background: transparent;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 4px;
- cursor: pointer;
- opacity: 0.7;
- transition: opacity 0.2s;
+ /* 规则显示样式 */
+ .device-card-rules {
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
- .device-card-toggle {
- border-color: rgba(255, 255, 255, 0.15);
+ .device-card-rules {
+ border-top-color: rgba(255, 255, 255, 0.15);
}
}
- .device-card-toggle:hover {
- opacity: 1;
+ .device-card-rules-content {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 8px 0;
+ }
+
+ .device-card-rules-empty {
+ font-size: 0.75rem;
+ opacity: 0.6;
+ padding: 4px 0;
+ }
+
+ .device-card-rules-count {
+ font-size: 0.8125rem;
+ font-weight: 600;
+ color: inherit;
+ margin-bottom: 2px;
+ }
+
+ .device-card-rules-active-time {
+ font-size: 0.8125rem;
+ color: #10b981;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px;
+ line-height: 1.4;
+ }
+
+ .device-card-rules-limits {
+ font-size: 0.75rem;
+ opacity: 0.8;
+ margin-top: 2px;
+ word-break: break-word;
+ }
+
+ .device-card-rules-more {
+ font-size: 0.7rem;
+ opacity: 0.6;
+ margin-top: 2px;
+ }
+
+ .device-card-rules-inactive {
+ font-size: 0.8125rem;
+ opacity: 0.5;
+ margin-top: 4px;
}
}
@@ -1183,42 +1473,402 @@ return view.extend({
// 创建限速设置模态框
var modal = E('div', { 'class': 'modal-overlay', 'id': 'rate-limit-modal' }, [
E('div', { 'class': 'modal' }, [
- E('div', { 'class': 'modal-header' }, [
- E('h3', { 'class': 'modal-title' }, _('Device Settings'))
- ]),
+ // E('div', { 'class': 'modal-header' }, [
+ // E('h3', { 'class': 'modal-title' }, _('Device Settings'))
+ // ]),
E('div', { 'class': 'modal-body' }, [
E('div', { 'class': 'device-summary', 'id': 'modal-device-summary' }),
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, _('Hostname')),
- E('input', { 'type': 'text', 'class': 'form-input', 'id': 'device-hostname-input', 'placeholder': _('Please enter hostname') }),
+ E('div', { 'style': 'display: flex; gap: 8px; align-items: center;' }, [
+ E('input', { 'type': 'text', 'class': 'form-input', 'id': 'device-hostname-input', 'placeholder': _('Please enter hostname'), 'style': 'flex: 1;' }),
+ E('button', { 'class': 'cbi-button cbi-button-positive', 'id': 'hostname-save-btn', 'style': 'flex-shrink: 0;' }, _('Save'))
+ ]),
E('div', { 'style': 'font-size: 0.75rem; color: #6b7280; margin-top: 4px;' }, _('Set Hostname'))
]),
- E('div', { 'class': 'form-group' }, [
- E('label', { 'class': 'form-label' }, _('Upload Limit')),
- E('div', { 'style': 'display: flex; gap: 8px;' }, [
- E('input', { 'type': 'number', 'class': 'form-input', 'id': 'upload-limit-value', 'min': '0', 'step': '1', 'placeholder': '0' }),
- E('select', { 'class': 'cbi-select', 'id': 'upload-limit-unit', 'style': 'width: 100px;' })
+ // 定时限速
+ E('div', { 'id': 'schedule-limit-tab' }, [
+ // 描述和添加规则按钮
+ E('div', { 'style': 'display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;' }, [
+ E('span', { 'style': 'font-size: 0.875rem; opacity: 0.7;' }, _('Set rate limit rules for different time periods')),
+ E('button', {
+ 'type': 'button',
+ 'class': 'cbi-button cbi-button-action',
+ 'id': 'schedule-add-rule-btn',
+ 'style': 'display: flex; align-items: center; gap: 4px;'
+ }, [
+ E('span', {}, '+'),
+ _('Add Rule')
+ ])
]),
- E('div', { 'style': 'font-size: 0.75rem; color: #6b7280; margin-top: 4px;' }, _('Tip: Enter 0 for unlimited'))
- ]),
- E('div', { 'class': 'form-group' }, [
- E('label', { 'class': 'form-label' }, _('Download Limit')),
- E('div', { 'style': 'display: flex; gap: 8px;' }, [
- E('input', { 'type': 'number', 'class': 'form-input', 'id': 'download-limit-value', 'min': '0', 'step': '1', 'placeholder': '0' }),
- E('select', { 'class': 'cbi-select', 'id': 'download-limit-unit', 'style': 'width: 100px;' })
- ]),
- E('div', { 'style': 'font-size: 0.75rem; color: #6b7280; margin-top: 4px;' }, _('Tip: Enter 0 for unlimited'))
+ // 规则列表区域
+ E('div', { 'class': 'schedule-rules-list', 'id': 'schedule-rules-list' })
])
]),
E('div', { 'class': 'modal-footer' }, [
- E('button', { 'class': 'cbi-button cbi-button-reset', 'id': 'modal-cancel' }, _('Cancel')),
- E('button', { 'class': 'cbi-button cbi-button-positive', 'id': 'modal-save' }, _('Save'))
+ E('button', { 'class': 'cbi-button cbi-button-reset', 'id': 'modal-close' }, _('Close'))
])
])
]);
document.body.appendChild(modal);
+ // 创建添加规则模态框
+ var addRuleModal = E('div', { 'class': 'modal-overlay', 'id': 'add-rule-modal' }, [
+ E('div', { 'class': 'modal' }, [
+ E('div', { 'class': 'modal-header' }, [
+ E('h3', { 'class': 'modal-title' }, _('Add Schedule Rule'))
+ ]),
+ E('div', { 'class': 'modal-body' }, [
+ E('div', { 'class': 'form-group' }, [
+ E('label', { 'class': 'form-label' }, _('Time Slot')),
+ E('div', { 'class': 'schedule-time-row' }, [
+ E('input', { 'type': 'time', 'class': 'schedule-time-input', 'id': 'add-rule-start-time' }),
+ E('span', {}, ' - '),
+ E('input', { 'type': 'time', 'class': 'schedule-time-input', 'id': 'add-rule-end-time' })
+ ])
+ ]),
+ E('div', { 'class': 'form-group' }, [
+ E('label', { 'class': 'form-label' }, _('Days of Week')),
+ E('div', { 'class': 'schedule-days', 'id': 'add-rule-days' }, [
+ E('button', { 'type': 'button', 'class': 'schedule-day-btn', 'data-day': '1' }, _('Mon')),
+ E('button', { 'type': 'button', 'class': 'schedule-day-btn', 'data-day': '2' }, _('Tue')),
+ E('button', { 'type': 'button', 'class': 'schedule-day-btn', 'data-day': '3' }, _('Wed')),
+ E('button', { 'type': 'button', 'class': 'schedule-day-btn', 'data-day': '4' }, _('Thu')),
+ E('button', { 'type': 'button', 'class': 'schedule-day-btn', 'data-day': '5' }, _('Fri')),
+ E('button', { 'type': 'button', 'class': 'schedule-day-btn', 'data-day': '6' }, _('Sat')),
+ E('button', { 'type': 'button', 'class': 'schedule-day-btn', 'data-day': '7' }, _('Sun'))
+ ])
+ ]),
+ E('div', { 'class': 'form-group' }, [
+ E('label', { 'class': 'form-label' }, _('Upload Limit')),
+ E('div', { 'style': 'display: flex; gap: 8px;' }, [
+ E('input', { 'type': 'number', 'class': 'form-input', 'id': 'add-rule-upload-limit-value', 'min': '0', 'step': '1', 'placeholder': '0' }),
+ E('select', { 'class': 'cbi-select', 'id': 'add-rule-upload-limit-unit', 'style': 'width: 100px;' })
+ ]),
+ E('div', { 'style': 'font-size: 0.75rem; color: #6b7280; margin-top: 4px;' }, _('Tip: Enter 0 for unlimited'))
+ ]),
+ E('div', { 'class': 'form-group', 'style': 'margin-bottom: 0;' }, [
+ E('label', { 'class': 'form-label' }, _('Download Limit')),
+ E('div', { 'style': 'display: flex; gap: 8px;' }, [
+ E('input', { 'type': 'number', 'class': 'form-input', 'id': 'add-rule-download-limit-value', 'min': '0', 'step': '1', 'placeholder': '0' }),
+ E('select', { 'class': 'cbi-select', 'id': 'add-rule-download-limit-unit', 'style': 'width: 100px;' })
+ ]),
+ E('div', { 'style': 'font-size: 0.75rem; color: #6b7280; margin-top: 4px;' }, _('Tip: Enter 0 for unlimited'))
+ ])
+ ]),
+ E('div', { 'class': 'modal-footer' }, [
+ E('button', { 'class': 'cbi-button cbi-button-reset', 'id': 'add-rule-cancel' }, _('Cancel')),
+ E('button', { 'class': 'cbi-button cbi-button-positive', 'id': 'add-rule-save' }, _('Add'))
+ ])
+ ])
+ ]);
+
+ document.body.appendChild(addRuleModal);
+
+ // 创建确认对话框
+ var confirmDialog = E('div', { 'class': 'modal-overlay', 'id': 'confirm-dialog-modal' }, [
+ E('div', { 'class': 'modal confirm-dialog' }, [
+ E('div', { 'class': 'modal-body' }, [
+ E('div', { 'class': 'confirm-dialog-title', 'id': 'confirm-dialog-title' }, _('Confirm')),
+ E('div', { 'class': 'confirm-dialog-message', 'id': 'confirm-dialog-message' }, ''),
+ E('div', { 'class': 'confirm-dialog-footer' }, [
+ E('button', { 'class': 'cbi-button cbi-button-reset', 'id': 'confirm-dialog-cancel' }, _('Cancel')),
+ E('button', { 'class': 'cbi-button cbi-button-negative', 'id': 'confirm-dialog-confirm' }, _('Confirm'))
+ ])
+ ])
+ ])
+ ]);
+
+ document.body.appendChild(confirmDialog);
+
+ // 确认对话框相关变量
+ var confirmDialogCallback = null;
+
+ // 显示确认对话框
+ function showConfirmDialog(title, message, onConfirm) {
+ document.getElementById('confirm-dialog-title').textContent = title || _('Confirm');
+ document.getElementById('confirm-dialog-message').textContent = message || '';
+ confirmDialogCallback = onConfirm;
+
+ // 应用主题颜色
+ try {
+ var cbiSection = document.querySelector('.cbi-section');
+ var targetElement = cbiSection || document.querySelector('.main') || document.body;
+ var computedStyle = window.getComputedStyle(targetElement);
+ var bgColor = computedStyle.backgroundColor;
+ var textColor = computedStyle.color;
+
+ var modalElement = confirmDialog.querySelector('.modal');
+
+ if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
+ var rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
+ if (rgbaMatch) {
+ var r = parseInt(rgbaMatch[1]);
+ var g = parseInt(rgbaMatch[2]);
+ var b = parseInt(rgbaMatch[3]);
+ var alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
+
+ if (alpha < 0.95) {
+ modalElement.style.backgroundColor = 'rgb(' + r + ', ' + g + ', ' + b + ')';
+ } else {
+ modalElement.style.backgroundColor = bgColor;
+ }
+ } else {
+ modalElement.style.backgroundColor = bgColor;
+ }
+ }
+
+ if (textColor && textColor !== 'rgba(0, 0, 0, 0)') {
+ modalElement.style.color = textColor;
+ }
+ } catch(e) {}
+
+ confirmDialog.classList.add('show');
+ }
+
+ // 隐藏确认对话框
+ function hideConfirmDialog() {
+ confirmDialog.classList.remove('show');
+ confirmDialogCallback = null;
+ }
+
+ // 确认对话框事件处理
+ document.getElementById('confirm-dialog-confirm').addEventListener('click', function() {
+ if (confirmDialogCallback) {
+ confirmDialogCallback();
+ }
+ hideConfirmDialog();
+ });
+
+ document.getElementById('confirm-dialog-cancel').addEventListener('click', hideConfirmDialog);
+
+ // 点击确认对话框背景关闭
+ confirmDialog.addEventListener('click', function (e) {
+ if (e.target === this) {
+ hideConfirmDialog();
+ }
+ });
+
+ // 日期选择按钮事件处理(添加规则模态框)
+ var addRuleDayButtons = addRuleModal.querySelectorAll('.schedule-day-btn');
+ addRuleDayButtons.forEach(function(btn) {
+ btn.addEventListener('click', function() {
+ this.classList.toggle('active');
+ });
+ });
+
+ // 显示添加规则模态框
+ function showAddRuleModal() {
+ if (!currentDevice) return;
+
+ var addRuleModalEl = document.getElementById('add-rule-modal');
+ var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
+
+ // 动态填充单位选择器
+ var uploadUnitSelect = document.getElementById('add-rule-upload-limit-unit');
+ var downloadUnitSelect = document.getElementById('add-rule-download-limit-unit');
+
+ uploadUnitSelect.innerHTML = '';
+ downloadUnitSelect.innerHTML = '';
+
+ if (speedUnit === 'bits') {
+ uploadUnitSelect.appendChild(E('option', { 'value': '125' }, 'Kbps'));
+ uploadUnitSelect.appendChild(E('option', { 'value': '125000' }, 'Mbps'));
+ uploadUnitSelect.appendChild(E('option', { 'value': '125000000' }, 'Gbps'));
+
+ downloadUnitSelect.appendChild(E('option', { 'value': '125' }, 'Kbps'));
+ downloadUnitSelect.appendChild(E('option', { 'value': '125000' }, 'Mbps'));
+ downloadUnitSelect.appendChild(E('option', { 'value': '125000000' }, 'Gbps'));
+ } else {
+ uploadUnitSelect.appendChild(E('option', { 'value': '1024' }, 'KB/s'));
+ uploadUnitSelect.appendChild(E('option', { 'value': '1048576' }, 'MB/s'));
+ uploadUnitSelect.appendChild(E('option', { 'value': '1073741824' }, 'GB/s'));
+
+ downloadUnitSelect.appendChild(E('option', { 'value': '1024' }, 'KB/s'));
+ downloadUnitSelect.appendChild(E('option', { 'value': '1048576' }, 'MB/s'));
+ downloadUnitSelect.appendChild(E('option', { 'value': '1073741824' }, 'GB/s'));
+ }
+
+ // 重置表单
+ resetAddRuleForm();
+
+ // 应用主题颜色
+ try {
+ var cbiSection = document.querySelector('.cbi-section');
+ var targetElement = cbiSection || document.querySelector('.main') || document.body;
+ var computedStyle = window.getComputedStyle(targetElement);
+ var bgColor = computedStyle.backgroundColor;
+ var textColor = computedStyle.color;
+
+ var modalElement = addRuleModalEl.querySelector('.modal');
+
+ if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
+ var rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
+ if (rgbaMatch) {
+ var r = parseInt(rgbaMatch[1]);
+ var g = parseInt(rgbaMatch[2]);
+ var b = parseInt(rgbaMatch[3]);
+ var alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
+
+ if (alpha < 0.95) {
+ modalElement.style.backgroundColor = 'rgb(' + r + ', ' + g + ', ' + b + ')';
+ } else {
+ modalElement.style.backgroundColor = bgColor;
+ }
+ } else {
+ modalElement.style.backgroundColor = bgColor;
+ }
+ }
+
+ if (textColor && textColor !== 'rgba(0, 0, 0, 0)') {
+ modalElement.style.color = textColor;
+ }
+ } catch(e) {}
+
+ // 显示模态框
+ addRuleModalEl.classList.add('show');
+ }
+
+ // 隐藏添加规则模态框
+ function hideAddRuleModal() {
+ var addRuleModalEl = document.getElementById('add-rule-modal');
+ addRuleModalEl.classList.remove('show');
+ }
+
+ // 重置添加规则表单
+ function resetAddRuleForm() {
+ document.getElementById('add-rule-start-time').value = '00:00';
+ document.getElementById('add-rule-end-time').value = '23:59';
+ // 默认选中所有7天 - 重新获取按钮引用
+ var dayButtons = addRuleModal.querySelectorAll('.schedule-day-btn');
+ dayButtons.forEach(function(btn) {
+ btn.classList.add('active');
+ });
+ var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
+ document.getElementById('add-rule-upload-limit-value').value = '0';
+ document.getElementById('add-rule-download-limit-value').value = '0';
+ document.getElementById('add-rule-upload-limit-unit').value = speedUnit === 'bits' ? '125' : '1024';
+ document.getElementById('add-rule-download-limit-unit').value = speedUnit === 'bits' ? '125' : '1024';
+ }
+
+ // 添加规则按钮事件处理
+ var scheduleAddRuleBtn = document.getElementById('schedule-add-rule-btn');
+ if (scheduleAddRuleBtn) {
+ scheduleAddRuleBtn.addEventListener('click', function() {
+ showAddRuleModal();
+ });
+ }
+
+ // 添加规则模态框取消按钮
+ document.getElementById('add-rule-cancel').addEventListener('click', hideAddRuleModal);
+
+ // 点击添加规则模态框背景关闭
+ document.getElementById('add-rule-modal').addEventListener('click', function (e) {
+ if (e.target === this) {
+ hideAddRuleModal();
+ }
+ });
+
+ // 保存定时限速规则(从添加规则模态框)
+ document.getElementById('add-rule-save').addEventListener('click', function() {
+ if (!currentDevice) {
+ console.error('No current device selected');
+ return;
+ }
+
+ var saveButton = this;
+ var originalText = saveButton.textContent;
+
+ // 显示加载状态
+ saveButton.innerHTML = '' + _('Adding...');
+ saveButton.classList.add('btn-loading');
+ saveButton.disabled = true;
+
+ var startTime = document.getElementById('add-rule-start-time').value;
+ var endTime = document.getElementById('add-rule-end-time').value;
+ // HTML5 time 输入不支持 24:00,将 23:59 转换为 24:00 表示全天
+ if (endTime === '23:59') {
+ endTime = '24:00';
+ }
+
+ // 重新获取日期按钮引用,确保获取最新状态
+ var addRuleModalEl = document.getElementById('add-rule-modal');
+ var dayButtons = addRuleModalEl.querySelectorAll('.schedule-day-btn');
+ var selectedDays = [];
+ dayButtons.forEach(function(btn) {
+ if (btn.classList.contains('active')) {
+ selectedDays.push(parseInt(btn.getAttribute('data-day')));
+ }
+ });
+
+ if (!startTime || !endTime) {
+ ui.addNotification(null, E('p', {}, _('Please set time slot')), 'error');
+ saveButton.innerHTML = originalText;
+ saveButton.classList.remove('btn-loading');
+ saveButton.disabled = false;
+ return;
+ }
+
+ if (selectedDays.length === 0) {
+ ui.addNotification(null, E('p', {}, _('Please select at least one day')), 'error');
+ saveButton.innerHTML = originalText;
+ saveButton.classList.remove('btn-loading');
+ saveButton.disabled = false;
+ return;
+ }
+
+ var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
+ var scheduleUploadValue = parseInt(document.getElementById('add-rule-upload-limit-value').value) || 0;
+ var scheduleUploadUnit = parseInt(document.getElementById('add-rule-upload-limit-unit').value);
+ var scheduleUploadLimit = scheduleUploadValue > 0 ? scheduleUploadValue * scheduleUploadUnit : 0;
+
+ var scheduleDownloadValue = parseInt(document.getElementById('add-rule-download-limit-value').value) || 0;
+ var scheduleDownloadUnit = parseInt(document.getElementById('add-rule-download-limit-unit').value);
+ var scheduleDownloadLimit = scheduleDownloadValue > 0 ? scheduleDownloadValue * scheduleDownloadUnit : 0;
+
+ console.log('Calling setScheduleLimit:', {
+ mac: currentDevice.mac,
+ startTime: startTime,
+ endTime: endTime,
+ days: selectedDays,
+ uploadLimit: scheduleUploadLimit,
+ downloadLimit: scheduleDownloadLimit
+ });
+
+ callSetScheduleLimit(
+ currentDevice.mac,
+ startTime,
+ endTime,
+ JSON.stringify(selectedDays),
+ scheduleUploadLimit,
+ scheduleDownloadLimit
+ ).then(function(result) {
+ console.log('setScheduleLimit result:', result);
+ // 恢复按钮状态
+ saveButton.innerHTML = originalText;
+ saveButton.classList.remove('btn-loading');
+ saveButton.disabled = false;
+
+ // 隐藏模态框
+ hideAddRuleModal();
+
+ // 重置表单
+ resetAddRuleForm();
+
+ // 刷新规则列表
+ loadScheduleRules();
+ updateDeviceData();
+ }).catch(function(error) {
+ console.error('Failed to add schedule rule:', error);
+ // 恢复按钮状态
+ saveButton.innerHTML = originalText;
+ saveButton.classList.remove('btn-loading');
+ saveButton.disabled = false;
+ ui.addNotification(null, E('p', {}, _('Failed to add schedule rule: ') + (error.message || error)), 'error');
+ });
+ });
+
// 模态框事件处理
var currentDevice = null;
var showRateLimitModal;
@@ -1228,35 +1878,16 @@ return view.extend({
currentDevice = device;
var modal = document.getElementById('rate-limit-modal');
var deviceSummary = document.getElementById('modal-device-summary');
- var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
-
- // 动态填充单位选择器
- var uploadUnitSelect = document.getElementById('upload-limit-unit');
- var downloadUnitSelect = document.getElementById('download-limit-unit');
-
- // 清空现有选项
- uploadUnitSelect.innerHTML = '';
- downloadUnitSelect.innerHTML = '';
-
- if (speedUnit === 'bits') {
- // 比特单位选项 - 直接设置为对应的字节数
- uploadUnitSelect.appendChild(E('option', { 'value': '125' }, 'Kbps')); // 1000 bits/s / 8 = 125 bytes/s
- uploadUnitSelect.appendChild(E('option', { 'value': '125000' }, 'Mbps')); // 1000000 bits/s / 8 = 125000 bytes/s
- uploadUnitSelect.appendChild(E('option', { 'value': '125000000' }, 'Gbps')); // 1000000000 bits/s / 8 = 125000000 bytes/s
-
- downloadUnitSelect.appendChild(E('option', { 'value': '125' }, 'Kbps'));
- downloadUnitSelect.appendChild(E('option', { 'value': '125000' }, 'Mbps'));
- downloadUnitSelect.appendChild(E('option', { 'value': '125000000' }, 'Gbps'));
- } else {
- // 字节单位选项
- uploadUnitSelect.appendChild(E('option', { 'value': '1024' }, 'KB/s'));
- uploadUnitSelect.appendChild(E('option', { 'value': '1048576' }, 'MB/s'));
- uploadUnitSelect.appendChild(E('option', { 'value': '1073741824' }, 'GB/s'));
-
- downloadUnitSelect.appendChild(E('option', { 'value': '1024' }, 'KB/s'));
- downloadUnitSelect.appendChild(E('option', { 'value': '1048576' }, 'MB/s'));
- downloadUnitSelect.appendChild(E('option', { 'value': '1073741824' }, 'GB/s'));
+ // 清空定时限速规则列表并加载
+ var rulesList = document.getElementById('schedule-rules-list');
+ if (rulesList) {
+ rulesList.innerHTML = '
' +
+ _('No scheduled rules yet, click "Add Rule" to start setting') +
+ '
';
}
+
+ // 加载定时限速规则列表
+ loadScheduleRules();
// 更新设备信息
deviceSummary.innerHTML = E('div', {}, [
@@ -1267,84 +1898,6 @@ return view.extend({
// 设置当前hostname值
document.getElementById('device-hostname-input').value = device.hostname || '';
- // 设置当前限速值
- var uploadLimit = device.wide_tx_rate_limit || 0;
- var downloadLimit = device.wide_rx_rate_limit || 0;
-
- // 设置上传限速值
- var uploadValue = uploadLimit;
- var uploadUnit;
- if (uploadValue === 0) {
- document.getElementById('upload-limit-value').value = 0;
- uploadUnit = speedUnit === 'bits' ? '125' : '1024';
- } else {
- if (speedUnit === 'bits') {
- // 转换为比特单位显示
- var uploadBits = uploadValue * 8;
- if (uploadBits >= 1000000000) {
- uploadValue = uploadBits / 1000000000;
- uploadUnit = '125000000'; // Gbps对应的字节倍数
- } else if (uploadBits >= 1000000) {
- uploadValue = uploadBits / 1000000;
- uploadUnit = '125000'; // Mbps对应的字节倍数
- } else {
- uploadValue = uploadBits / 1000;
- uploadUnit = '125'; // Kbps对应的字节倍数
- }
- } else {
- // 字节单位显示
- if (uploadValue >= 1073741824) {
- uploadValue = uploadValue / 1073741824;
- uploadUnit = '1073741824';
- } else if (uploadValue >= 1048576) {
- uploadValue = uploadValue / 1048576;
- uploadUnit = '1048576';
- } else {
- uploadValue = uploadValue / 1024;
- uploadUnit = '1024';
- }
- }
- document.getElementById('upload-limit-value').value = Math.round(uploadValue);
- }
- document.getElementById('upload-limit-unit').value = uploadUnit;
-
- // 设置下载限速值
- var downloadValue = downloadLimit;
- var downloadUnit;
- if (downloadValue === 0) {
- document.getElementById('download-limit-value').value = 0;
- downloadUnit = speedUnit === 'bits' ? '125' : '1024';
- } else {
- if (speedUnit === 'bits') {
- // 转换为比特单位显示
- var downloadBits = downloadValue * 8;
- if (downloadBits >= 1000000000) {
- downloadValue = downloadBits / 1000000000;
- downloadUnit = '125000000'; // Gbps对应的字节倍数
- } else if (downloadBits >= 1000000) {
- downloadValue = downloadBits / 1000000;
- downloadUnit = '125000'; // Mbps对应的字节倍数
- } else {
- downloadValue = downloadBits / 1000;
- downloadUnit = '125'; // Kbps对应的字节倍数
- }
- } else {
- // 字节单位显示
- if (downloadValue >= 1073741824) {
- downloadValue = downloadValue / 1073741824;
- downloadUnit = '1073741824';
- } else if (downloadValue >= 1048576) {
- downloadValue = downloadValue / 1048576;
- downloadUnit = '1048576';
- } else {
- downloadValue = downloadValue / 1024;
- downloadUnit = '1024';
- }
- }
- document.getElementById('download-limit-value').value = Math.round(downloadValue);
- }
- document.getElementById('download-limit-unit').value = downloadUnit;
-
// 应用 cbi-section 的颜色到模态框
try {
// 优先从 cbi-section 获取颜色
@@ -1437,107 +1990,173 @@ return view.extend({
}, 300);
}
- // 保存限速设置
- function saveRateLimit() {
+ // 加载定时限速规则列表
+ function loadScheduleRules() {
+ if (!currentDevice) return;
+
+ var rulesList = document.getElementById('schedule-rules-list');
+ if (!rulesList) return;
+
+ rulesList.innerHTML = '' + _('Loading...') + '
';
+
+ callGetScheduleLimits().then(function(res) {
+ // 检查响应格式
+ if (!res) {
+ rulesList.innerHTML = '' + _('No schedule rules') + '
';
+ return;
+ }
+
+ // 检查是否有错误
+ if (res.success === false || res.error) {
+ var errorMsg = res.error || _('Failed to load schedule rules');
+ rulesList.innerHTML = '' + errorMsg + '
';
+ return;
+ }
+
+ // 检查数据格式
+ var limits = [];
+ if (res.data && res.data.limits && Array.isArray(res.data.limits)) {
+ limits = res.data.limits;
+ } else if (Array.isArray(res.limits)) {
+ // 兼容不同的响应格式
+ limits = res.limits;
+ } else if (Array.isArray(res)) {
+ // 如果直接返回数组
+ limits = res;
+ }
+
+ var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
+
+ // 过滤出当前设备的规则
+ var deviceRules = limits.filter(function(rule) {
+ return rule && rule.mac === currentDevice.mac;
+ });
+
+ // 清空列表
+ rulesList.innerHTML = '';
+
+ if (deviceRules.length === 0) {
+ rulesList.innerHTML = '' +
+ _('No scheduled rules yet, click "Add Rule" to start setting') +
+ '
';
+ return;
+ }
+
+ // 显示所有规则(支持多个规则)
+ deviceRules.forEach(function(rule) {
+ var daysText = '';
+ // days 范围是 1-7 (Monday-Sunday)
+ var dayNames = {
+ 1: _('Mon'),
+ 2: _('Tue'),
+ 3: _('Wed'),
+ 4: _('Thu'),
+ 5: _('Fri'),
+ 6: _('Sat'),
+ 7: _('Sun')
+ };
+ if (rule.time_slot && rule.time_slot.days && Array.isArray(rule.time_slot.days)) {
+ daysText = rule.time_slot.days.map(function(d) { return dayNames[d] || d; }).join(', ');
+ }
+
+ var startTime = rule.time_slot && rule.time_slot.start ? rule.time_slot.start : '';
+ var endTime = rule.time_slot && rule.time_slot.end ? rule.time_slot.end : '';
+ var uploadLimit = rule.wide_tx_rate_limit || 0;
+ var downloadLimit = rule.wide_rx_rate_limit || 0;
+
+ var ruleItem = E('div', { 'class': 'schedule-rule-item' }, [
+ E('div', { 'class': 'schedule-rule-info' }, [
+ E('div', { 'class': 'schedule-rule-time' }, startTime + ' - ' + endTime),
+ E('div', { 'class': 'schedule-rule-days' }, daysText),
+ E('div', { 'class': 'schedule-rule-limits' },
+ '↑ ' + formatByterate(uploadLimit, speedUnit) +
+ ' / ↓ ' + formatByterate(downloadLimit, speedUnit)
+ )
+ ]),
+ E('button', {
+ 'class': 'schedule-rule-delete',
+ 'title': _('Delete')
+ }, _('Delete'))
+ ]);
+
+ ruleItem.querySelector('.schedule-rule-delete').addEventListener('click', function() {
+ showConfirmDialog(
+ _('Delete Schedule Rule'),
+ _('Are you sure you want to delete this schedule rule?'),
+ function() {
+ var days = rule.time_slot && rule.time_slot.days ? JSON.stringify(rule.time_slot.days) : '[]';
+ callDeleteScheduleLimit(
+ rule.mac,
+ startTime,
+ endTime,
+ days
+ ).then(function() {
+ loadScheduleRules();
+ updateDeviceData();
+ }).catch(function(error) {
+ ui.addNotification(null, E('p', {}, _('Failed to delete schedule rule')), 'error');
+ });
+ }
+ );
+ });
+
+ rulesList.appendChild(ruleItem);
+ });
+ }).catch(function(error) {
+ console.error('Failed to load schedule rules:', error);
+ var errorMsg = _('Failed to load schedule rules');
+ if (error && error.message) {
+ errorMsg += ': ' + error.message;
+ }
+ rulesList.innerHTML = '' + errorMsg + '
';
+ });
+ }
+
+ // 保存 hostname
+ function saveHostname() {
if (!currentDevice) return;
- var saveButton = document.getElementById('modal-save');
+ var saveButton = document.getElementById('hostname-save-btn');
var originalText = saveButton.textContent;
- // 显示加载状态
- saveButton.innerHTML = '' + _('Saving...');
- saveButton.classList.add('btn-loading');
-
- var uploadLimit = 0;
- var downloadLimit = 0;
- var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
-
// 获取hostname值
var newHostname = document.getElementById('device-hostname-input').value.trim();
- // 获取上传限速值
- var uploadValue = parseInt(document.getElementById('upload-limit-value').value) || 0;
- var uploadUnit = parseInt(document.getElementById('upload-limit-unit').value);
- if (uploadValue > 0) {
- // 选择器的值已经是正确的字节倍数,直接计算即可
- uploadLimit = uploadValue * uploadUnit;
+ // 如果hostname没有变化,不需要保存
+ if (newHostname === (currentDevice.hostname || '')) {
+ return;
}
- // 获取下载限速值
- var downloadValue = parseInt(document.getElementById('download-limit-value').value) || 0;
- var downloadUnit = parseInt(document.getElementById('download-limit-unit').value);
- if (downloadValue > 0) {
- // 选择器的值已经是正确的字节倍数,直接计算即可
- downloadLimit = downloadValue * downloadUnit;
- }
+ // 显示加载状态
+ saveButton.innerHTML = '' + _('Saving...');
+ saveButton.classList.add('btn-loading');
+ saveButton.disabled = true;
- // console.log("mac", currentDevice.mac)
- // console.log("uploadLimit", uploadLimit)
- // console.log("downloadLimit", downloadLimit)
- // console.log("newHostname", newHostname)
-
- // 创建Promise数组来并行处理hostname和限速设置
- var promises = [];
-
- // 如果hostname有变化,添加hostname设置Promise
- if (newHostname !== (currentDevice.hostname || '')) {
- promises.push(
- callSetHostname(currentDevice.mac, newHostname).catch(function(error) {
- return { hostnameError: error };
- })
- );
- }
-
- // 添加限速设置Promise
- promises.push(
- callSetRateLimit(currentDevice.mac, uploadLimit, downloadLimit).catch(function(error) {
- return { rateLimitError: error };
- })
- );
-
- // 并行执行所有设置
- Promise.all(promises).then(function (results) {
+ callSetHostname(currentDevice.mac, newHostname).then(function(result) {
// 恢复按钮状态
saveButton.innerHTML = originalText;
saveButton.classList.remove('btn-loading');
+ saveButton.disabled = false;
- var hasError = false;
- var errorMessages = [];
-
- // 检查结果
- results.forEach(function(result, index) {
- if (result && result.hostnameError) {
- hasError = true;
- errorMessages.push(_('Failed to set hostname'));
- } else if (result && result.rateLimitError) {
- hasError = true;
- errorMessages.push(_('Failed to save settings'));
- } else if (result !== true && result !== undefined) {
- // 检查是否有其他错误
- if (result && result.error) {
- hasError = true;
- errorMessages.push(result.error);
- }
- }
- });
-
- if (hasError) {
- ui.addNotification(null, E('p', {}, errorMessages.join(', ')), 'error');
- } else {
- // 所有设置都成功
- hideRateLimitModal();
- }
- }).catch(function (error) {
+ // 更新当前设备信息
+ currentDevice.hostname = newHostname;
+
+ // 刷新设备数据
+ updateDeviceData();
+ }).catch(function(error) {
// 恢复按钮状态
saveButton.innerHTML = originalText;
saveButton.classList.remove('btn-loading');
- ui.addNotification(null, E('p', {}, _('Failed to save settings')), 'error');
+ saveButton.disabled = false;
+ ui.addNotification(null, E('p', {}, _('Failed to set hostname')), 'error');
});
}
- // 绑定模态框事件
- document.getElementById('modal-cancel').addEventListener('click', hideRateLimitModal);
- document.getElementById('modal-save').addEventListener('click', saveRateLimit);
+ // 绑定 hostname 保存按钮事件
+ document.getElementById('hostname-save-btn').addEventListener('click', saveHostname);
+
+ // 绑定关闭按钮事件
+ document.getElementById('modal-close').addEventListener('click', hideRateLimitModal);
// 点击模态框背景关闭
document.getElementById('rate-limit-modal').addEventListener('click', function (e) {
@@ -1551,6 +2170,92 @@ return view.extend({
var lastHistoryData = null; // 最近一次拉取的原始 metrics 数据
var isHistoryLoading = false; // 防止轮询重入
+ // 定时限速规则:全局存储
+ var allScheduleRules = []; // 存储所有设备的定时限速规则
+ var isScheduleRulesLoading = false; // 防止轮询重入
+
+ // 获取所有定时限速规则
+ function fetchAllScheduleRules() {
+ if (isScheduleRulesLoading) return Promise.resolve();
+ isScheduleRulesLoading = true;
+
+ return callGetScheduleLimits().then(function(res) {
+ isScheduleRulesLoading = false;
+
+ if (!res) {
+ allScheduleRules = [];
+ return;
+ }
+
+ // 检查是否有错误
+ if (res.success === false || res.error) {
+ allScheduleRules = [];
+ return;
+ }
+
+ // 检查数据格式
+ var limits = [];
+ if (res.data && res.data.limits && Array.isArray(res.data.limits)) {
+ limits = res.data.limits;
+ } else if (Array.isArray(res.limits)) {
+ limits = res.limits;
+ } else if (Array.isArray(res)) {
+ limits = res;
+ }
+
+ allScheduleRules = limits || [];
+ }).catch(function(error) {
+ isScheduleRulesLoading = false;
+ console.error('Failed to fetch schedule rules:', error);
+ allScheduleRules = [];
+ });
+ }
+
+ // 判断规则是否在当前时间生效
+ function isRuleActive(rule) {
+ if (!rule || !rule.time_slot) return false;
+
+ var now = new Date();
+ var currentDay = now.getDay(); // 0=Sunday, 1=Monday, ..., 6=Saturday
+ // 转换为 1-7 (Monday-Sunday)
+ var dayOfWeek = currentDay === 0 ? 7 : currentDay;
+
+ // 检查是否在规则指定的日期中
+ var days = rule.time_slot.days || [];
+ if (!Array.isArray(days) || days.length === 0) return false;
+ if (days.indexOf(dayOfWeek) === -1) return false;
+
+ // 获取当前时间(HH:MM格式)
+ var currentTime = ('0' + now.getHours()).slice(-2) + ':' + ('0' + now.getMinutes()).slice(-2);
+ var startTime = rule.time_slot.start || '';
+ var endTime = rule.time_slot.end || '';
+
+ if (!startTime || !endTime) return false;
+
+ // 处理 24:00 的情况
+ if (endTime === '24:00') {
+ endTime = '23:59';
+ }
+
+ // 比较时间
+ if (startTime <= endTime) {
+ // 正常情况:开始时间 <= 结束时间
+ return currentTime >= startTime && currentTime <= endTime;
+ } else {
+ // 跨天情况:开始时间 > 结束时间(例如 22:00 - 06:00)
+ return currentTime >= startTime || currentTime <= endTime;
+ }
+ }
+
+ // 获取设备当前生效的规则
+ function getActiveRulesForDevice(mac) {
+ if (!allScheduleRules || allScheduleRules.length === 0) return [];
+
+ return allScheduleRules.filter(function(rule) {
+ return rule && rule.mac === mac && isRuleActive(rule);
+ });
+ }
+
// 排序状态管理
var currentSortBy = localStorage.getItem('bandix_sort_by') || 'online'; // 默认按在线状态排序
var currentSortOrder = localStorage.getItem('bandix_sort_order') === 'true'; // false = 降序, true = 升序
@@ -2732,7 +3437,11 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
// 定义更新设备数据的函数
function updateDeviceData() {
- return callStatus().then(function (result) {
+ return Promise.all([
+ callStatus(),
+ fetchAllScheduleRules()
+ ]).then(function (results) {
+ var result = results[0];
var trafficDiv = document.getElementById('traffic-status');
var deviceCountDiv = document.getElementById('device-count');
var statsGrid = document.getElementById('stats-grid');
@@ -2936,7 +3645,7 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
createSortableHeader(_('Device Info'), 'online'),
createSplitHeader(_('LAN Traffic'), 'lan_speed', 'lan_traffic'),
createSplitHeader(_('WAN Traffic'), 'wan_speed', 'wan_traffic'),
- E('th', {}, _('Rate Limit')),
+ E('th', {}, _('Schedule Rules')),
E('th', {}, _('Actions'))
])
]),
@@ -3062,19 +3771,58 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
])
]),
- // 限速设置
- E('td', {}, [
- E('div', { 'class': 'limit-info' }, [
- E('div', { 'class': 'traffic-row' }, [
- E('span', { 'class': 'traffic-icon upload', 'style': 'font-size: 0.75rem;' }, '↑'),
- E('span', { 'style': 'font-size: 0.875rem;' }, formatByterate(device.wide_tx_rate_limit || 0, speedUnit))
- ]),
- E('div', { 'class': 'traffic-row' }, [
- E('span', { 'class': 'traffic-icon download', 'style': 'font-size: 0.75rem;' }, '↓'),
- E('span', { 'style': 'font-size: 0.875rem;' }, formatByterate(device.wide_rx_rate_limit || 0, speedUnit))
- ]),
- ])
- ]),
+ // 定时限速规则
+ (function() {
+ var activeRules = getActiveRulesForDevice(device.mac);
+ var allDeviceRules = allScheduleRules.filter(function(r) { return r && r.mac === device.mac; });
+
+ var rulesInfo = E('div', { 'class': 'schedule-rules-info' }, []);
+
+ if (allDeviceRules.length === 0) {
+ rulesInfo.appendChild(E('div', { 'style': 'font-size: 0.75rem; opacity: 0.6;' }, '-'));
+ } else {
+ // 显示规则数量
+ rulesInfo.appendChild(E('div', {
+ 'style': 'font-size: 0.75rem; font-weight: 600; margin-bottom: 4px;'
+ }, allDeviceRules.length + ' ' + (allDeviceRules.length === 1 ? _('rule') : _('rules'))));
+
+ // 显示当前生效的规则
+ if (activeRules.length > 0) {
+ var activeRule = activeRules[0]; // 显示第一个生效的规则
+ var startTime = activeRule.time_slot && activeRule.time_slot.start ? activeRule.time_slot.start : '';
+ var endTime = activeRule.time_slot && activeRule.time_slot.end ? activeRule.time_slot.end : '';
+
+ var uploadLimit = activeRule.wide_tx_rate_limit || 0;
+ var downloadLimit = activeRule.wide_rx_rate_limit || 0;
+
+ // 时间段和限速值放在同一行
+ var timeSlotText = startTime + '-' + endTime;
+ var limitsText = [];
+ // 即使限速是0也显示
+ limitsText.push('↑' + (uploadLimit > 0 ? formatByterate(uploadLimit, speedUnit) : _('Unlimited')));
+ limitsText.push('↓' + (downloadLimit > 0 ? formatByterate(downloadLimit, speedUnit) : _('Unlimited')));
+
+ rulesInfo.appendChild(E('div', {
+ 'style': 'font-size: 0.75rem; color: #10b981; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;'
+ }, [
+ E('span', {}, '● ' + timeSlotText),
+ E('span', { 'style': 'opacity: 0.8; font-size: 0.7rem;' }, limitsText.join(' '))
+ ]));
+
+ if (activeRules.length > 1) {
+ rulesInfo.appendChild(E('div', {
+ 'style': 'font-size: 0.7rem; opacity: 0.6; margin-top: 2px;'
+ }, '+' + (activeRules.length - 1) + ' ' + _('more')));
+ }
+ } else {
+ rulesInfo.appendChild(E('div', {
+ 'style': 'font-size: 0.75rem; opacity: 0.5;'
+ }, _('No active rule')));
+ }
+ }
+
+ return E('td', {}, rulesInfo);
+ })(),
// 操作
E('td', {}, [
@@ -3108,7 +3856,7 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
})()
])
]),
- // 卡片主要内容(WAN流量和限速)
+ // 卡片主要内容(WAN流量)
E('div', { 'class': 'device-card-content' }, [
// WAN流量
E('div', { 'class': 'device-card-section' }, [
@@ -3125,64 +3873,86 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.wide_rx_bytes || 0) + ')')
])
])
- ]),
- // 限速设置
- E('div', { 'class': 'device-card-section' }, [
- E('div', { 'class': 'device-card-section-label' }, _('Rate Limit')),
- E('div', { 'class': 'device-card-traffic' }, [
- E('div', { 'class': 'device-card-traffic-row' }, [
- E('span', { 'style': 'color: #f97316; font-size: 0.75rem; font-weight: bold;' }, '↑'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.wide_tx_rate_limit || 0, speedUnit))
- ]),
- E('div', { 'class': 'device-card-traffic-row' }, [
- E('span', { 'style': 'color: #06b6d4; font-size: 0.75rem; font-weight: bold;' }, '↓'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.wide_rx_rate_limit || 0, speedUnit))
- ])
- ])
])
]),
- // 可展开的详情(LAN流量)
- E('div', { 'class': 'device-card-expandable' }, [
- E('div', { 'class': 'device-card-section', 'style': 'margin-top: 0;' }, [
- E('div', { 'class': 'device-card-section-label' }, _('LAN Traffic')),
- E('div', { 'class': 'device-card-traffic' }, [
- E('div', { 'class': 'device-card-traffic-row' }, [
- E('span', { 'style': 'color: #f97316; font-size: 0.75rem; font-weight: bold;' }, '↑'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.local_tx_rate || 0, speedUnit)),
- E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.local_tx_bytes || 0) + ')')
- ]),
- E('div', { 'class': 'device-card-traffic-row' }, [
- E('span', { 'style': 'color: #06b6d4; font-size: 0.75rem; font-weight: bold;' }, '↓'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.local_rx_rate || 0, speedUnit)),
- E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.local_rx_bytes || 0) + ')')
- ])
+ // 定时限速规则
+ (function() {
+ var activeRules = getActiveRulesForDevice(device.mac);
+ var allDeviceRules = allScheduleRules.filter(function(r) { return r && r.mac === device.mac; });
+
+ if (allDeviceRules.length === 0) {
+ return E('div', { 'class': 'device-card-section device-card-rules' }, [
+ E('div', { 'class': 'device-card-section-label' }, _('Schedule Rules')),
+ E('div', { 'class': 'device-card-rules-empty' }, '-')
+ ]);
+ }
+
+ var rulesContent = E('div', { 'class': 'device-card-rules-content' });
+
+ // 规则数量
+ rulesContent.appendChild(E('div', {
+ 'class': 'device-card-rules-count'
+ }, allDeviceRules.length + ' ' + (allDeviceRules.length === 1 ? _('rule') : _('rules'))));
+
+ if (activeRules.length > 0) {
+ var activeRule = activeRules[0];
+ var startTime = activeRule.time_slot && activeRule.time_slot.start ? activeRule.time_slot.start : '';
+ var endTime = activeRule.time_slot && activeRule.time_slot.end ? activeRule.time_slot.end : '';
+
+ var uploadLimit = activeRule.wide_tx_rate_limit || 0;
+ var downloadLimit = activeRule.wide_rx_rate_limit || 0;
+
+ // 时间段和限速值放在同一行
+ var timeSlotText = startTime + '-' + endTime;
+ var limitsText = [];
+ // 即使限速是0也显示
+ limitsText.push('↑' + (uploadLimit > 0 ? formatByterate(uploadLimit, speedUnit) : _('Unlimited')));
+ limitsText.push('↓' + (downloadLimit > 0 ? formatByterate(downloadLimit, speedUnit) : _('Unlimited')));
+
+ // 生效规则的时间段和限速值
+ var activeTimeInfo = E('div', {
+ 'class': 'device-card-rules-active-time'
+ }, [
+ E('span', {}, '● ' + timeSlotText),
+ E('span', { 'style': 'opacity: 0.8; font-size: 0.7rem; margin-left: 8px;' }, limitsText.join(' '))
+ ]);
+
+ if (activeRules.length > 1) {
+ activeTimeInfo.appendChild(E('div', {
+ 'class': 'device-card-rules-more'
+ }, '+' + (activeRules.length - 1) + ' ' + _('more')));
+ }
+
+ rulesContent.appendChild(activeTimeInfo);
+ } else {
+ rulesContent.appendChild(E('div', {
+ 'class': 'device-card-rules-inactive'
+ }, _('No active rule')));
+ }
+
+ return E('div', { 'class': 'device-card-section device-card-rules' }, [
+ E('div', { 'class': 'device-card-section-label' }, _('Schedule Rules')),
+ rulesContent
+ ]);
+ })(),
+ // LAN流量(直接显示,不需要展开/收起)
+ E('div', { 'class': 'device-card-section', 'style': 'margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0, 0, 0, 0.1);' }, [
+ E('div', { 'class': 'device-card-section-label' }, _('LAN Traffic')),
+ E('div', { 'class': 'device-card-traffic' }, [
+ E('div', { 'class': 'device-card-traffic-row' }, [
+ E('span', { 'style': 'color: #f97316; font-size: 0.75rem; font-weight: bold;' }, '↑'),
+ E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.local_tx_rate || 0, speedUnit)),
+ E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.local_tx_bytes || 0) + ')')
+ ]),
+ E('div', { 'class': 'device-card-traffic-row' }, [
+ E('span', { 'style': 'color: #06b6d4; font-size: 0.75rem; font-weight: bold;' }, '↓'),
+ E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.local_rx_rate || 0, speedUnit)),
+ E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.local_rx_bytes || 0) + ')')
])
])
- ]),
- // 展开/收起按钮
- E('button', { 'class': 'device-card-toggle' }, _('Show More'))
+ ])
]);
- // 检查并恢复展开状态
- if (expandedDeviceCards.has(device.mac)) {
- card.classList.add('expanded');
- }
-
- // 绑定展开/收起功能
- var toggleBtn = card.querySelector('.device-card-toggle');
- toggleBtn.textContent = card.classList.contains('expanded') ? _('Show Less') : _('Show More');
- toggleBtn.addEventListener('click', function() {
- card.classList.toggle('expanded');
- var isExpanded = card.classList.contains('expanded');
- toggleBtn.textContent = isExpanded ? _('Show Less') : _('Show More');
- // 保存展开状态
- if (isExpanded) {
- expandedDeviceCards.add(device.mac);
- } else {
- expandedDeviceCards.delete(device.mac);
- }
- });
-
cardsContainer.appendChild(card);
});
@@ -3206,9 +3976,20 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
// 轮询获取数据
poll.add(updateDeviceData, 1);
+
+ // 轮询获取定时限速规则(每5秒)
+ poll.add(function() {
+ return fetchAllScheduleRules().then(function() {
+ // 规则更新后,重新渲染表格以显示最新的规则状态
+ if (window.__bandixRenderTable) {
+ window.__bandixRenderTable();
+ }
+ });
+ }, 5000);
// 立即执行一次,不等待轮询
updateDeviceData();
+ fetchAllScheduleRules();
// 自动适应主题背景色和文字颜色的函数(仅应用于弹窗和 tooltip)
function applyThemeColors() {
diff --git a/luci-app-bandix/po/es/bandix.po b/luci-app-bandix/po/es/bandix.po
index 968b2d0..e965242 100644
--- a/luci-app-bandix/po/es/bandix.po
+++ b/luci-app-bandix/po/es/bandix.po
@@ -751,3 +751,93 @@ msgstr "Mostrar más"
msgid "Show Less"
msgstr "Mostrar menos"
+
+msgid "Add Schedule Rule"
+msgstr "Agregar regla de limitación programada"
+
+msgid "Add Rule"
+msgstr "Agregar regla"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "Establecer reglas de limitación para diferentes períodos de tiempo"
+
+msgid "Time Slot"
+msgstr "Franja horaria"
+
+msgid "Days of Week"
+msgstr "Días de la semana"
+
+msgid "Mon"
+msgstr "Lun"
+
+msgid "Tue"
+msgstr "Mar"
+
+msgid "Wed"
+msgstr "Mié"
+
+msgid "Thu"
+msgstr "Jue"
+
+msgid "Fri"
+msgstr "Vie"
+
+msgid "Sat"
+msgstr "Sáb"
+
+msgid "Sun"
+msgstr "Dom"
+
+msgid "Adding..."
+msgstr "Agregando..."
+
+msgid "Failed to add schedule rule: "
+msgstr "Error al agregar regla programada: "
+
+msgid "Please set time slot"
+msgstr "Por favor establezca la franja horaria"
+
+msgid "Please select at least one day"
+msgstr "Por favor seleccione al menos un día"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "Aún no hay reglas programadas, haga clic en \"Agregar regla\" para comenzar"
+
+msgid "Loading..."
+msgstr "Cargando..."
+
+msgid "No schedule rules"
+msgstr "No hay reglas programadas"
+
+msgid "Failed to load schedule rules"
+msgstr "Error al cargar reglas programadas"
+
+msgid "Delete Schedule Rule"
+msgstr "Eliminar regla programada"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "¿Está seguro de que desea eliminar esta regla programada?"
+
+msgid "Failed to delete schedule rule"
+msgstr "Error al eliminar regla programada"
+
+msgid "Confirm"
+msgstr "Confirmar"
+
+msgid "Close"
+msgstr "Cerrar"
+
+msgid "rule"
+msgstr "regla"
+
+msgid "rules"
+msgstr "reglas"
+
+msgid "more"
+msgstr "más"
+
+msgid "No active rule"
+msgstr "Sin regla activa"
+
+msgid "Schedule Rules"
+msgstr "Reglas de limitación programada"
diff --git a/luci-app-bandix/po/fr/bandix.po b/luci-app-bandix/po/fr/bandix.po
index 43b2bb8..658f76c 100644
--- a/luci-app-bandix/po/fr/bandix.po
+++ b/luci-app-bandix/po/fr/bandix.po
@@ -750,4 +750,94 @@ msgid "Show More"
msgstr "Afficher plus"
msgid "Show Less"
-msgstr "Afficher moins"
\ No newline at end of file
+msgstr "Afficher moins"
+
+msgid "Add Schedule Rule"
+msgstr "Ajouter une règle de limitation programmée"
+
+msgid "Add Rule"
+msgstr "Ajouter une règle"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "Définir des règles de limitation pour différentes périodes"
+
+msgid "Time Slot"
+msgstr "Créneau horaire"
+
+msgid "Days of Week"
+msgstr "Jours de la semaine"
+
+msgid "Mon"
+msgstr "Lun"
+
+msgid "Tue"
+msgstr "Mar"
+
+msgid "Wed"
+msgstr "Mer"
+
+msgid "Thu"
+msgstr "Jeu"
+
+msgid "Fri"
+msgstr "Ven"
+
+msgid "Sat"
+msgstr "Sam"
+
+msgid "Sun"
+msgstr "Dim"
+
+msgid "Adding..."
+msgstr "Ajout en cours..."
+
+msgid "Failed to add schedule rule: "
+msgstr "Échec de l'ajout de la règle programmée : "
+
+msgid "Please set time slot"
+msgstr "Veuillez définir un créneau horaire"
+
+msgid "Please select at least one day"
+msgstr "Veuillez sélectionner au moins un jour"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "Aucune règle programmée pour le moment, cliquez sur \"Ajouter une règle\" pour commencer"
+
+msgid "Loading..."
+msgstr "Chargement..."
+
+msgid "No schedule rules"
+msgstr "Aucune règle programmée"
+
+msgid "Failed to load schedule rules"
+msgstr "Échec du chargement des règles programmées"
+
+msgid "Delete Schedule Rule"
+msgstr "Supprimer la règle programmée"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "Êtes-vous sûr de vouloir supprimer cette règle programmée ?"
+
+msgid "Failed to delete schedule rule"
+msgstr "Échec de la suppression de la règle programmée"
+
+msgid "Confirm"
+msgstr "Confirmer"
+
+msgid "Close"
+msgstr "Fermer"
+
+msgid "rule"
+msgstr "règle"
+
+msgid "rules"
+msgstr "règles"
+
+msgid "more"
+msgstr "plus"
+
+msgid "No active rule"
+msgstr "Aucune règle active"
+
+msgid "Schedule Rules"
+msgstr "Règles de limitation programmée"
\ No newline at end of file
diff --git a/luci-app-bandix/po/id/bandix.po b/luci-app-bandix/po/id/bandix.po
index 968b2b3..0c70d35 100644
--- a/luci-app-bandix/po/id/bandix.po
+++ b/luci-app-bandix/po/id/bandix.po
@@ -751,3 +751,93 @@ msgstr "Tampilkan lebih banyak"
msgid "Show Less"
msgstr "Tampilkan lebih sedikit"
+
+msgid "Add Schedule Rule"
+msgstr "Tambah aturan pembatasan terjadwal"
+
+msgid "Add Rule"
+msgstr "Tambah aturan"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "Tetapkan aturan pembatasan untuk periode waktu yang berbeda"
+
+msgid "Time Slot"
+msgstr "Slot waktu"
+
+msgid "Days of Week"
+msgstr "Hari dalam seminggu"
+
+msgid "Mon"
+msgstr "Sen"
+
+msgid "Tue"
+msgstr "Sel"
+
+msgid "Wed"
+msgstr "Rab"
+
+msgid "Thu"
+msgstr "Kam"
+
+msgid "Fri"
+msgstr "Jum"
+
+msgid "Sat"
+msgstr "Sab"
+
+msgid "Sun"
+msgstr "Min"
+
+msgid "Adding..."
+msgstr "Menambahkan..."
+
+msgid "Failed to add schedule rule: "
+msgstr "Gagal menambahkan aturan terjadwal: "
+
+msgid "Please set time slot"
+msgstr "Silakan atur slot waktu"
+
+msgid "Please select at least one day"
+msgstr "Silakan pilih setidaknya satu hari"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "Belum ada aturan terjadwal, klik \"Tambah aturan\" untuk mulai pengaturan"
+
+msgid "Loading..."
+msgstr "Memuat..."
+
+msgid "No schedule rules"
+msgstr "Tidak ada aturan terjadwal"
+
+msgid "Failed to load schedule rules"
+msgstr "Gagal memuat aturan terjadwal"
+
+msgid "Delete Schedule Rule"
+msgstr "Hapus aturan pembatasan terjadwal"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "Apakah Anda yakin ingin menghapus aturan terjadwal ini?"
+
+msgid "Failed to delete schedule rule"
+msgstr "Gagal menghapus aturan terjadwal"
+
+msgid "Confirm"
+msgstr "Konfirmasi"
+
+msgid "Close"
+msgstr "Tutup"
+
+msgid "rule"
+msgstr "aturan"
+
+msgid "rules"
+msgstr "aturan"
+
+msgid "more"
+msgstr "lainnya"
+
+msgid "No active rule"
+msgstr "Tidak ada aturan aktif"
+
+msgid "Schedule Rules"
+msgstr "Aturan pembatasan terjadwal"
diff --git a/luci-app-bandix/po/ja/bandix.po b/luci-app-bandix/po/ja/bandix.po
index c9a056a..7a1c54b 100644
--- a/luci-app-bandix/po/ja/bandix.po
+++ b/luci-app-bandix/po/ja/bandix.po
@@ -751,3 +751,93 @@ msgstr "もっと見る"
msgid "Show Less"
msgstr "閉じる"
+
+msgid "Add Schedule Rule"
+msgstr "スケジュール制限ルールを追加"
+
+msgid "Add Rule"
+msgstr "ルールを追加"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "異なる時間帯にレート制限ルールを設定"
+
+msgid "Time Slot"
+msgstr "時間帯"
+
+msgid "Days of Week"
+msgstr "曜日"
+
+msgid "Mon"
+msgstr "月"
+
+msgid "Tue"
+msgstr "火"
+
+msgid "Wed"
+msgstr "水"
+
+msgid "Thu"
+msgstr "木"
+
+msgid "Fri"
+msgstr "金"
+
+msgid "Sat"
+msgstr "土"
+
+msgid "Sun"
+msgstr "日"
+
+msgid "Adding..."
+msgstr "追加中..."
+
+msgid "Failed to add schedule rule: "
+msgstr "スケジュール制限ルールの追加に失敗しました:"
+
+msgid "Please set time slot"
+msgstr "時間帯を設定してください"
+
+msgid "Please select at least one day"
+msgstr "少なくとも1日を選択してください"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "スケジュール制限ルールがまだありません。「ルールを追加」をクリックして設定を開始してください"
+
+msgid "Loading..."
+msgstr "読み込み中..."
+
+msgid "No schedule rules"
+msgstr "スケジュール制限ルールなし"
+
+msgid "Failed to load schedule rules"
+msgstr "スケジュール制限ルールの読み込みに失敗しました"
+
+msgid "Delete Schedule Rule"
+msgstr "スケジュール制限ルールを削除"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "このスケジュール制限ルールを削除してもよろしいですか?"
+
+msgid "Failed to delete schedule rule"
+msgstr "スケジュール制限ルールの削除に失敗しました"
+
+msgid "Confirm"
+msgstr "確認"
+
+msgid "Close"
+msgstr "閉じる"
+
+msgid "rule"
+msgstr "ルール"
+
+msgid "rules"
+msgstr "ルール"
+
+msgid "more"
+msgstr "その他"
+
+msgid "No active rule"
+msgstr "アクティブなルールなし"
+
+msgid "Schedule Rules"
+msgstr "スケジュール制限ルール"
diff --git a/luci-app-bandix/po/pl/bandix.po b/luci-app-bandix/po/pl/bandix.po
index 8298f37..5abe740 100644
--- a/luci-app-bandix/po/pl/bandix.po
+++ b/luci-app-bandix/po/pl/bandix.po
@@ -752,3 +752,93 @@ msgstr "Pokaż więcej"
msgid "Show Less"
msgstr "Pokaż mniej"
+msgid "Add Schedule Rule"
+msgstr "Dodaj regułę limitu czasu"
+
+msgid "Add Rule"
+msgstr "Dodaj regułę"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "Ustaw reguły limitu dla różnych okresów czasu"
+
+msgid "Time Slot"
+msgstr "Przedział czasowy"
+
+msgid "Days of Week"
+msgstr "Dni tygodnia"
+
+msgid "Mon"
+msgstr "Pon"
+
+msgid "Tue"
+msgstr "Wt"
+
+msgid "Wed"
+msgstr "Śr"
+
+msgid "Thu"
+msgstr "Czw"
+
+msgid "Fri"
+msgstr "Pt"
+
+msgid "Sat"
+msgstr "Sob"
+
+msgid "Sun"
+msgstr "Nie"
+
+msgid "Adding..."
+msgstr "Dodawanie..."
+
+msgid "Failed to add schedule rule: "
+msgstr "Nie udało się dodać reguły limitu czasu: "
+
+msgid "Please set time slot"
+msgstr "Proszę ustawić przedział czasowy"
+
+msgid "Please select at least one day"
+msgstr "Proszę wybrać co najmniej jeden dzień"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "Brak jeszcze reguł limitu czasu, kliknij \"Dodaj regułę\", aby rozpocząć"
+
+msgid "Loading..."
+msgstr "Ładowanie..."
+
+msgid "No schedule rules"
+msgstr "Brak reguł limitu czasu"
+
+msgid "Failed to load schedule rules"
+msgstr "Nie udało się załadować reguł limitu czasu"
+
+msgid "Delete Schedule Rule"
+msgstr "Usuń regułę limitu czasu"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "Czy na pewno chcesz usunąć tę regułę limitu czasu?"
+
+msgid "Failed to delete schedule rule"
+msgstr "Nie udało się usunąć reguły limitu czasu"
+
+msgid "Confirm"
+msgstr "Potwierdź"
+
+msgid "Close"
+msgstr "Zamknij"
+
+msgid "rule"
+msgstr "reguła"
+
+msgid "rules"
+msgstr "reguły"
+
+msgid "more"
+msgstr "więcej"
+
+msgid "No active rule"
+msgstr "Brak aktywnej reguły"
+
+msgid "Schedule Rules"
+msgstr "Reguły limitu czasu"
+
diff --git a/luci-app-bandix/po/ru/bandix.po b/luci-app-bandix/po/ru/bandix.po
index 4714dea..29401e5 100644
--- a/luci-app-bandix/po/ru/bandix.po
+++ b/luci-app-bandix/po/ru/bandix.po
@@ -750,4 +750,94 @@ msgid "Show More"
msgstr "Показать больше"
msgid "Show Less"
-msgstr "Показать меньше"
\ No newline at end of file
+msgstr "Показать меньше"
+
+msgid "Add Schedule Rule"
+msgstr "Добавить правило ограничения по расписанию"
+
+msgid "Add Rule"
+msgstr "Добавить правило"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "Установить правила ограничения скорости для разных периодов времени"
+
+msgid "Time Slot"
+msgstr "Временной интервал"
+
+msgid "Days of Week"
+msgstr "Дни недели"
+
+msgid "Mon"
+msgstr "Пн"
+
+msgid "Tue"
+msgstr "Вт"
+
+msgid "Wed"
+msgstr "Ср"
+
+msgid "Thu"
+msgstr "Чт"
+
+msgid "Fri"
+msgstr "Пт"
+
+msgid "Sat"
+msgstr "Сб"
+
+msgid "Sun"
+msgstr "Вс"
+
+msgid "Adding..."
+msgstr "Добавление..."
+
+msgid "Failed to add schedule rule: "
+msgstr "Не удалось добавить правило расписания: "
+
+msgid "Please set time slot"
+msgstr "Пожалуйста, установите временной интервал"
+
+msgid "Please select at least one day"
+msgstr "Пожалуйста, выберите хотя бы один день"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "Правил расписания пока нет, нажмите \"Добавить правило\", чтобы начать настройку"
+
+msgid "Loading..."
+msgstr "Загрузка..."
+
+msgid "No schedule rules"
+msgstr "Нет правил расписания"
+
+msgid "Failed to load schedule rules"
+msgstr "Не удалось загрузить правила расписания"
+
+msgid "Delete Schedule Rule"
+msgstr "Удалить правило ограничения по расписанию"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "Вы уверены, что хотите удалить это правило расписания?"
+
+msgid "Failed to delete schedule rule"
+msgstr "Не удалось удалить правило расписания"
+
+msgid "Confirm"
+msgstr "Подтвердить"
+
+msgid "Close"
+msgstr "Закрыть"
+
+msgid "rule"
+msgstr "правило"
+
+msgid "rules"
+msgstr "правила"
+
+msgid "more"
+msgstr "еще"
+
+msgid "No active rule"
+msgstr "Нет активных правил"
+
+msgid "Schedule Rules"
+msgstr "Правила ограничения по расписанию"
\ No newline at end of file
diff --git a/luci-app-bandix/po/th/bandix.po b/luci-app-bandix/po/th/bandix.po
index d2d9ea2..aed52d4 100644
--- a/luci-app-bandix/po/th/bandix.po
+++ b/luci-app-bandix/po/th/bandix.po
@@ -751,3 +751,93 @@ msgstr "แสดงเพิ่มเติม"
msgid "Show Less"
msgstr "แสดงน้อยลง"
+
+msgid "Add Schedule Rule"
+msgstr "เพิ่มกฎการจำกัดอัตราแบบกำหนดเวลา"
+
+msgid "Add Rule"
+msgstr "เพิ่มกฎ"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "ตั้งค่ากฎการจำกัดอัตราสำหรับช่วงเวลาต่างๆ"
+
+msgid "Time Slot"
+msgstr "ช่วงเวลา"
+
+msgid "Days of Week"
+msgstr "วันในสัปดาห์"
+
+msgid "Mon"
+msgstr "จ"
+
+msgid "Tue"
+msgstr "อ"
+
+msgid "Wed"
+msgstr "พ"
+
+msgid "Thu"
+msgstr "พฤ"
+
+msgid "Fri"
+msgstr "ศ"
+
+msgid "Sat"
+msgstr "ส"
+
+msgid "Sun"
+msgstr "อา"
+
+msgid "Adding..."
+msgstr "กำลังเพิ่ม..."
+
+msgid "Failed to add schedule rule: "
+msgstr "เพิ่มกฎการจำกัดอัตราแบบกำหนดเวลาล้มเหลว: "
+
+msgid "Please set time slot"
+msgstr "โปรดตั้งค่าช่วงเวลา"
+
+msgid "Please select at least one day"
+msgstr "โปรดเลือกอย่างน้อยหนึ่งวัน"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "ยังไม่มีกฎการจำกัดอัตราแบบกำหนดเวลา คลิก \"เพิ่มกฎ\" เพื่อเริ่มตั้งค่า"
+
+msgid "Loading..."
+msgstr "กำลังโหลด..."
+
+msgid "No schedule rules"
+msgstr "ไม่มีกฎการจำกัดอัตราแบบกำหนดเวลา"
+
+msgid "Failed to load schedule rules"
+msgstr "โหลดกฎการจำกัดอัตราแบบกำหนดเวลาล้มเหลว"
+
+msgid "Delete Schedule Rule"
+msgstr "ลบกฎการจำกัดอัตราแบบกำหนดเวลา"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "คุณแน่ใจหรือไม่ว่าต้องการลบกฎการจำกัดอัตราแบบกำหนดเวลานี้?"
+
+msgid "Failed to delete schedule rule"
+msgstr "ลบกฎการจำกัดอัตราแบบกำหนดเวลาล้มเหลว"
+
+msgid "Confirm"
+msgstr "ยืนยัน"
+
+msgid "Close"
+msgstr "ปิด"
+
+msgid "rule"
+msgstr "กฎ"
+
+msgid "rules"
+msgstr "กฎ"
+
+msgid "more"
+msgstr "เพิ่มเติม"
+
+msgid "No active rule"
+msgstr "ไม่มีกฎที่ใช้งาน"
+
+msgid "Schedule Rules"
+msgstr "กฎการจำกัดอัตราแบบกำหนดเวลา"
diff --git a/luci-app-bandix/po/zh_Hans/bandix.po b/luci-app-bandix/po/zh_Hans/bandix.po
index b89b0cc..42859e0 100644
--- a/luci-app-bandix/po/zh_Hans/bandix.po
+++ b/luci-app-bandix/po/zh_Hans/bandix.po
@@ -750,4 +750,94 @@ msgid "Show More"
msgstr "显示更多"
msgid "Show Less"
-msgstr "显示更少"
\ No newline at end of file
+msgstr "显示更少"
+
+msgid "Add Schedule Rule"
+msgstr "添加定时限速规则"
+
+msgid "Add Rule"
+msgstr "添加规则"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "为不同时间段设置限速规则"
+
+msgid "Time Slot"
+msgstr "时间段"
+
+msgid "Days of Week"
+msgstr "星期"
+
+msgid "Mon"
+msgstr "周一"
+
+msgid "Tue"
+msgstr "周二"
+
+msgid "Wed"
+msgstr "周三"
+
+msgid "Thu"
+msgstr "周四"
+
+msgid "Fri"
+msgstr "周五"
+
+msgid "Sat"
+msgstr "周六"
+
+msgid "Sun"
+msgstr "周日"
+
+msgid "Adding..."
+msgstr "添加中..."
+
+msgid "Failed to add schedule rule: "
+msgstr "添加定时限速规则失败:"
+
+msgid "Please set time slot"
+msgstr "请设置时间段"
+
+msgid "Please select at least one day"
+msgstr "请至少选择一天"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "还没有定时限速规则,点击"添加规则"开始设置"
+
+msgid "Loading..."
+msgstr "加载中..."
+
+msgid "No schedule rules"
+msgstr "没有定时限速规则"
+
+msgid "Failed to load schedule rules"
+msgstr "加载定时限速规则失败"
+
+msgid "Delete Schedule Rule"
+msgstr "删除定时限速规则"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "确定要删除这个定时限速规则吗?"
+
+msgid "Failed to delete schedule rule"
+msgstr "删除定时限速规则失败"
+
+msgid "Confirm"
+msgstr "确认"
+
+msgid "Close"
+msgstr "关闭"
+
+msgid "rule"
+msgstr "规则"
+
+msgid "rules"
+msgstr "规则"
+
+msgid "more"
+msgstr "更多"
+
+msgid "No active rule"
+msgstr "无生效规则"
+
+msgid "Schedule Rules"
+msgstr "定时限速规则"
\ No newline at end of file
diff --git a/luci-app-bandix/po/zh_Hant/bandix.po b/luci-app-bandix/po/zh_Hant/bandix.po
index d2c43d7..ec598d2 100644
--- a/luci-app-bandix/po/zh_Hant/bandix.po
+++ b/luci-app-bandix/po/zh_Hant/bandix.po
@@ -750,4 +750,94 @@ msgid "Show More"
msgstr "顯示更多"
msgid "Show Less"
-msgstr "顯示更少"
\ No newline at end of file
+msgstr "顯示更少"
+
+msgid "Add Schedule Rule"
+msgstr "新增定時限速規則"
+
+msgid "Add Rule"
+msgstr "新增規則"
+
+msgid "Set rate limit rules for different time periods"
+msgstr "為不同時間段設定限速規則"
+
+msgid "Time Slot"
+msgstr "時間段"
+
+msgid "Days of Week"
+msgstr "星期"
+
+msgid "Mon"
+msgstr "週一"
+
+msgid "Tue"
+msgstr "週二"
+
+msgid "Wed"
+msgstr "週三"
+
+msgid "Thu"
+msgstr "週四"
+
+msgid "Fri"
+msgstr "週五"
+
+msgid "Sat"
+msgstr "週六"
+
+msgid "Sun"
+msgstr "週日"
+
+msgid "Adding..."
+msgstr "新增中..."
+
+msgid "Failed to add schedule rule: "
+msgstr "新增定時限速規則失敗:"
+
+msgid "Please set time slot"
+msgstr "請設定時間段"
+
+msgid "Please select at least one day"
+msgstr "請至少選擇一天"
+
+msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
+msgstr "還沒有定時限速規則,點擊"新增規則"開始設定"
+
+msgid "Loading..."
+msgstr "載入中..."
+
+msgid "No schedule rules"
+msgstr "沒有定時限速規則"
+
+msgid "Failed to load schedule rules"
+msgstr "載入定時限速規則失敗"
+
+msgid "Delete Schedule Rule"
+msgstr "刪除定時限速規則"
+
+msgid "Are you sure you want to delete this schedule rule?"
+msgstr "確定要刪除這個定時限速規則嗎?"
+
+msgid "Failed to delete schedule rule"
+msgstr "刪除定時限速規則失敗"
+
+msgid "Confirm"
+msgstr "確認"
+
+msgid "Close"
+msgstr "關閉"
+
+msgid "rule"
+msgstr "規則"
+
+msgid "rules"
+msgstr "規則"
+
+msgid "more"
+msgstr "更多"
+
+msgid "No active rule"
+msgstr "無生效規則"
+
+msgid "Schedule Rules"
+msgstr "定時限速規則"
\ No newline at end of file
diff --git a/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix b/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
index 645db55..4459919 100755
--- a/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
+++ b/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
@@ -12,6 +12,7 @@ readonly BANDIX_METRICS_API="$BANDIX_API_BASE/api/traffic/metrics"
readonly BANDIX_CONNECTION_API="$BANDIX_API_BASE/api/connection/devices"
readonly BANDIX_DNS_QUERIES_API="$BANDIX_API_BASE/api/dns/queries"
readonly BANDIX_DNS_STATS_API="$BANDIX_API_BASE/api/dns/stats"
+readonly BANDIX_SCHEDULE_LIMITS_API="$BANDIX_API_BASE/api/traffic/limits/schedule"
# 通用函数:创建简单的JSON响应
make_value() {
@@ -98,56 +99,6 @@ get_metrics() {
fi
}
-# 设置设备限速
-set_device_rate_limit() {
- local mac="$1"
- local wide_tx_rate_limit="$2"
- local wide_rx_rate_limit="$3"
-
- # logger "luci.bandix: set_device_rate_limit 参数: mac=$mac tx=$wide_tx_rate_limit rx=$wide_rx_rate_limit"
-
- # 验证参数
- if [ -z "$mac" ]; then
- make_error "MAC address is required"
- return
- fi
-
- # 转义MAC地址中的特殊字符
- local mac_escaped=$(escape_json_string "$mac")
-
- # 构建请求数据
- local request_data="{
- \"mac\": \"$mac\",
- \"wide_tx_rate_limit\": $wide_tx_rate_limit,
- \"wide_rx_rate_limit\": $wide_rx_rate_limit
- }"
-
- # 记录请求数据(用于调试)
- # logger "luci.bandix: request_data=$request_data"
-
- # 发送请求到bandix API
- local response=$(curl -s --connect-timeout 3 --max-time 10 -X POST -H "Content-Type: application/json" -d "$request_data" "$BANDIX_LIMITS_API" 2>/dev/null)
-
- # logger "luci.bandix: API response=$response"
-
- if [ $? -eq 0 ] && [ -n "$response" ]; then
- # 检查API响应是否包含success状态
- if echo "$response" | grep -q '"status":\s*"success"'; then
- make_success "Rate limit set successfully"
- else
- # API返回了响应但不是success状态
- json_init
- json_add_boolean "success" 0
- json_add_string "error" "API returned error response"
- json_add_string "response" "$response"
- json_dump
- json_cleanup
- fi
- else
- make_error "Failed to set rate limit"
- fi
-}
-
# 设置设备主机名绑定
set_device_hostname() {
local mac="$1"
@@ -293,6 +244,129 @@ get_dns_stats() {
echo "$response"
}
+# 获取定时限速规则
+get_schedule_limits() {
+ # 从 Bandix API 获取定时限速规则
+ local response=$(curl -s --connect-timeout 2 --max-time 5 -X GET "$BANDIX_SCHEDULE_LIMITS_API" 2>/dev/null)
+
+ # 检查API调用是否成功
+ if [ $? -ne 0 ] || [ -z "$response" ]; then
+ make_error "Failed to connect to Bandix service"
+ return
+ fi
+
+ # 直接返回API结果
+ echo "$response"
+}
+
+# 设置定时限速规则
+set_schedule_limit() {
+ local mac="$1"
+ local start_time="$2"
+ local end_time="$3"
+ local days="$4"
+ local wide_tx_rate_limit="$5"
+ local wide_rx_rate_limit="$6"
+
+ # 验证参数
+ if [ -z "$mac" ]; then
+ make_error "MAC address is required"
+ return
+ fi
+
+ if [ -z "$start_time" ] || [ -z "$end_time" ] || [ -z "$days" ]; then
+ make_error "Time slot parameters are required"
+ return
+ fi
+
+ # 转义MAC地址中的特殊字符
+ local mac_escaped=$(escape_json_string "$mac")
+
+ # 构建请求数据
+ local request_data="{
+ \"mac\": \"$mac\",
+ \"time_slot\": {
+ \"start\": \"$start_time\",
+ \"end\": \"$end_time\",
+ \"days\": $days
+ },
+ \"wide_tx_rate_limit\": $wide_tx_rate_limit,
+ \"wide_rx_rate_limit\": $wide_rx_rate_limit
+ }"
+
+ # 发送请求到bandix API
+ local response=$(curl -s --connect-timeout 3 --max-time 10 -X POST -H "Content-Type: application/json" -d "$request_data" "$BANDIX_SCHEDULE_LIMITS_API" 2>/dev/null)
+
+ if [ $? -eq 0 ] && [ -n "$response" ]; then
+ # 检查API响应是否包含success状态
+ if echo "$response" | grep -q '"status":\s*"success"'; then
+ make_success "Schedule limit set successfully"
+ else
+ # API返回了响应但不是success状态
+ json_init
+ json_add_boolean "success" 0
+ json_add_string "error" "API returned error response"
+ json_add_string "response" "$response"
+ json_dump
+ json_cleanup
+ fi
+ else
+ make_error "Failed to set schedule limit"
+ fi
+}
+
+# 删除定时限速规则
+delete_schedule_limit() {
+ local mac="$1"
+ local start_time="$2"
+ local end_time="$3"
+ local days="$4"
+
+ # 验证参数
+ if [ -z "$mac" ]; then
+ make_error "MAC address is required"
+ return
+ fi
+
+ if [ -z "$start_time" ] || [ -z "$end_time" ] || [ -z "$days" ]; then
+ make_error "Time slot parameters are required"
+ return
+ fi
+
+ # 转义MAC地址中的特殊字符
+ local mac_escaped=$(escape_json_string "$mac")
+
+ # 构建请求数据
+ local request_data="{
+ \"mac\": \"$mac\",
+ \"time_slot\": {
+ \"start\": \"$start_time\",
+ \"end\": \"$end_time\",
+ \"days\": $days
+ }
+ }"
+
+ # 发送请求到bandix API
+ local response=$(curl -s --connect-timeout 3 --max-time 10 -X DELETE -H "Content-Type: application/json" -d "$request_data" "$BANDIX_SCHEDULE_LIMITS_API" 2>/dev/null)
+
+ if [ $? -eq 0 ] && [ -n "$response" ]; then
+ # 检查API响应是否包含success状态
+ if echo "$response" | grep -q '"status":\s*"success"'; then
+ make_success "Schedule limit deleted successfully"
+ else
+ # API返回了响应但不是success状态
+ json_init
+ json_add_boolean "success" 0
+ json_add_string "error" "API returned error response"
+ json_add_string "response" "$response"
+ json_dump
+ json_cleanup
+ fi
+ else
+ make_error "Failed to delete schedule limit"
+ fi
+}
+
case "$1" in
list)
json_init
@@ -303,12 +377,6 @@ case "$1" in
json_add_string "mac"
json_close_object
- json_add_object "setRateLimit"
- json_add_string "mac"
- json_add_int "wide_rx_rate_limit"
- json_add_int "wide_tx_rate_limit"
- json_close_object
-
json_add_object "setHostname"
json_add_string "mac"
json_add_string "hostname"
@@ -330,6 +398,25 @@ case "$1" in
json_add_object "getDnsStats"
json_close_object
+ json_add_object "getScheduleLimits"
+ json_close_object
+
+ json_add_object "setScheduleLimit"
+ json_add_string "mac"
+ json_add_string "start_time"
+ json_add_string "end_time"
+ json_add_string "days"
+ json_add_int "wide_rx_rate_limit"
+ json_add_int "wide_tx_rate_limit"
+ json_close_object
+
+ json_add_object "deleteScheduleLimit"
+ json_add_string "mac"
+ json_add_string "start_time"
+ json_add_string "end_time"
+ json_add_string "days"
+ json_close_object
+
json_dump
json_cleanup
;;
@@ -360,42 +447,6 @@ case "$1" in
fi
get_metrics "$mac"
;;
- setRateLimit)
- # logger "luci.bandix: setRateLimit called"
-
- # 从 stdin 读取 JSON 参数
- read input
- # logger "luci.bandix: received input: $input"
-
- if [ -n "$input" ]; then
- # 尝试解析数组格式的 JSON 参数 (LuCI RPC 通常使用数组格式)
- # 参数格式: ["mac", wide_tx_rate_limit, wide_rx_rate_limit]
- mac=$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)
- wide_tx_rate_limit=$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)
- wide_rx_rate_limit=$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)
-
- # 如果数组格式解析失败,尝试对象格式
- if [ -z "$mac" ]; then
- mac=$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)
- wide_tx_rate_limit=$(echo "$input" | jsonfilter -e '$.wide_tx_rate_limit' 2>/dev/null)
- wide_rx_rate_limit=$(echo "$input" | jsonfilter -e '$.wide_rx_rate_limit' 2>/dev/null)
- fi
-
- # logger "luci.bandix: parsed - mac=$mac tx=$wide_tx_rate_limit rx=$wide_rx_rate_limit"
-
- # 验证参数
- if [ -n "$mac" ] && [ -n "$wide_tx_rate_limit" ] && [ -n "$wide_rx_rate_limit" ]; then
- # 调用限速设置函数
- set_device_rate_limit "$mac" "$wide_tx_rate_limit" "$wide_rx_rate_limit"
- else
- # logger "luci.bandix: setRateLimit 参数缺失或无效"
- make_error "Missing or invalid parameters"
- fi
- else
- # logger "luci.bandix: setRateLimit 没有接收到输入"
- make_error "No input received"
- fi
- ;;
setHostname)
# logger "luci.bandix: setHostname called"
@@ -471,6 +522,122 @@ case "$1" in
# logger "luci.bandix: getDnsStats called"
get_dns_stats
;;
+ getScheduleLimits)
+ # logger "luci.bandix: getScheduleLimits called"
+ get_schedule_limits
+ ;;
+ setScheduleLimit)
+ # logger "luci.bandix: setScheduleLimit called"
+
+ # 从 stdin 读取 JSON 参数
+ read input
+ # logger "luci.bandix: received input: $input"
+
+ if [ -n "$input" ]; then
+ # 尝试解析数组格式的 JSON 参数 (LuCI RPC 通常使用数组格式)
+ # 参数格式: ["mac", "start_time", "end_time", "days", wide_tx_rate_limit, wide_rx_rate_limit]
+ mac=$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)
+ start_time=$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)
+ end_time=$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)
+ # days 可能是 JSON 数组字符串,需要特殊处理
+ days=$(echo "$input" | jsonfilter -e '$[3]' 2>/dev/null)
+ # 如果提取失败,尝试作为字符串提取
+ if [ -z "$days" ]; then
+ days=$(echo "$input" | jsonfilter -e '$[3]' -s 2>/dev/null)
+ fi
+ wide_tx_rate_limit=$(echo "$input" | jsonfilter -e '$[4]' 2>/dev/null)
+ wide_rx_rate_limit=$(echo "$input" | jsonfilter -e '$[5]' 2>/dev/null)
+
+ # 如果数组格式解析失败,尝试对象格式
+ if [ -z "$mac" ]; then
+ mac=$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)
+ start_time=$(echo "$input" | jsonfilter -e '$.start_time' 2>/dev/null)
+ end_time=$(echo "$input" | jsonfilter -e '$.end_time' 2>/dev/null)
+ # days 可能是 JSON 数组字符串
+ days=$(echo "$input" | jsonfilter -e '$.days' 2>/dev/null)
+ # 如果提取失败,尝试提取为 JSON 数组
+ if [ -z "$days" ]; then
+ days=$(echo "$input" | jsonfilter -e '$.days' -s 2>/dev/null)
+ fi
+ wide_tx_rate_limit=$(echo "$input" | jsonfilter -e '$.wide_tx_rate_limit' 2>/dev/null)
+ wide_rx_rate_limit=$(echo "$input" | jsonfilter -e '$.wide_rx_rate_limit' 2>/dev/null)
+ fi
+
+ # 如果 days 还不是有效的 JSON 数组格式,尝试从原始输入中提取
+ if [ -z "$days" ] || ! echo "$days" | grep -q '^\['; then
+ # 尝试直接提取 days 字段的值
+ days=$(echo "$input" | sed -n 's/.*"days"[[:space:]]*:[[:space:]]*\(\[.*\]\).*/\1/p' 2>/dev/null)
+ fi
+
+ # logger "luci.bandix: parsed - mac=$mac start=$start_time end=$end_time days=$days tx=$wide_tx_rate_limit rx=$wide_rx_rate_limit"
+
+ # 验证参数
+ if [ -n "$mac" ] && [ -n "$start_time" ] && [ -n "$end_time" ] && [ -n "$days" ] && [ -n "$wide_tx_rate_limit" ] && [ -n "$wide_rx_rate_limit" ]; then
+ # 调用定时限速设置函数
+ set_schedule_limit "$mac" "$start_time" "$end_time" "$days" "$wide_tx_rate_limit" "$wide_rx_rate_limit"
+ else
+ # logger "luci.bandix: setScheduleLimit 参数缺失或无效"
+ make_error "Missing or invalid parameters"
+ fi
+ else
+ # logger "luci.bandix: setScheduleLimit 没有接收到输入"
+ make_error "No input received"
+ fi
+ ;;
+ deleteScheduleLimit)
+ # logger "luci.bandix: deleteScheduleLimit called"
+
+ # 从 stdin 读取 JSON 参数
+ read input
+ # logger "luci.bandix: received input: $input"
+
+ if [ -n "$input" ]; then
+ # 尝试解析数组格式的 JSON 参数 (LuCI RPC 通常使用数组格式)
+ # 参数格式: ["mac", "start_time", "end_time", "days"]
+ mac=$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)
+ start_time=$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)
+ end_time=$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)
+ # days 可能是 JSON 数组字符串,需要特殊处理
+ days=$(echo "$input" | jsonfilter -e '$[3]' 2>/dev/null)
+ # 如果提取失败,尝试作为字符串提取
+ if [ -z "$days" ]; then
+ days=$(echo "$input" | jsonfilter -e '$[3]' -s 2>/dev/null)
+ fi
+
+ # 如果数组格式解析失败,尝试对象格式
+ if [ -z "$mac" ]; then
+ mac=$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)
+ start_time=$(echo "$input" | jsonfilter -e '$.start_time' 2>/dev/null)
+ end_time=$(echo "$input" | jsonfilter -e '$.end_time' 2>/dev/null)
+ # days 可能是 JSON 数组字符串
+ days=$(echo "$input" | jsonfilter -e '$.days' 2>/dev/null)
+ # 如果提取失败,尝试提取为 JSON 数组
+ if [ -z "$days" ]; then
+ days=$(echo "$input" | jsonfilter -e '$.days' -s 2>/dev/null)
+ fi
+ fi
+
+ # 如果 days 还不是有效的 JSON 数组格式,尝试从原始输入中提取
+ if [ -z "$days" ] || ! echo "$days" | grep -q '^\['; then
+ # 尝试直接提取 days 字段的值
+ days=$(echo "$input" | sed -n 's/.*"days"[[:space:]]*:[[:space:]]*\(\[.*\]\).*/\1/p' 2>/dev/null)
+ fi
+
+ # logger "luci.bandix: parsed - mac=$mac start=$start_time end=$end_time days=$days"
+
+ # 验证参数
+ if [ -n "$mac" ] && [ -n "$start_time" ] && [ -n "$end_time" ] && [ -n "$days" ]; then
+ # 调用定时限速删除函数
+ delete_schedule_limit "$mac" "$start_time" "$end_time" "$days"
+ else
+ # logger "luci.bandix: deleteScheduleLimit 参数缺失或无效"
+ make_error "Missing or invalid parameters"
+ fi
+ else
+ # logger "luci.bandix: deleteScheduleLimit 没有接收到输入"
+ make_error "No input received"
+ fi
+ ;;
esac
;;
esac
diff --git a/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json b/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json
index 4821a00..7cb6cd8 100644
--- a/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json
+++ b/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json
@@ -6,11 +6,13 @@
"luci.bandix": [
"getStatus",
"getMetrics",
- "setRateLimit",
"getConnection",
"setHostname",
"getDnsQueries",
- "getDnsStats"
+ "getDnsStats",
+ "getScheduleLimits",
+ "setScheduleLimit",
+ "deleteScheduleLimit"
]
},
"uci": [
@@ -20,13 +22,15 @@
"write": {
"ubus": {
"luci.bandix": [
- "setRateLimit",
"getStatus",
"getMetrics",
"getConnection",
"setHostname",
"getDnsQueries",
- "getDnsStats"
+ "getDnsStats",
+ "getScheduleLimits",
+ "setScheduleLimit",
+ "deleteScheduleLimit"
]
},
"uci": [
diff --git a/openlist2/Makefile b/openlist2/Makefile
index d74dc3f..b7c3087 100644
--- a/openlist2/Makefile
+++ b/openlist2/Makefile
@@ -7,13 +7,13 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=openlist2
-PKG_VERSION:=4.1.7
-PKG_WEB_VERSION:=4.1.7
+PKG_VERSION:=4.1.8
+PKG_WEB_VERSION:=4.1.8
PKG_RELEASE:=1
PKG_SOURCE:=openlist-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/OpenListTeam/OpenList/tar.gz/v$(PKG_VERSION)?
-PKG_HASH:=f1b92628be09ba181decc46423c3e0624b78aedfcd28590990a46ba03d75e5e4
+PKG_HASH:=8ba861db0ad29e60fcfb4544ff3744dfa235ab1744ea0c998e1c6f80a630996d
PKG_BUILD_DIR:=$(BUILD_DIR)/OpenList-$(PKG_VERSION)
@@ -24,7 +24,7 @@ PKG_MAINTAINER:=sbwml
define Download/openlist-frontend
FILE:=openlist-frontend-dist-lite-v$(PKG_WEB_VERSION).tar.gz
URL:=https://github.com/OpenListTeam/OpenList-Frontend/releases/download/v$(PKG_WEB_VERSION)/
- HASH:=2a365c1ce17904926cf275540680f0f24c0c8c3620fcbb98149d7faf781235aa
+ HASH:=00c8cf73fa98ba3ff56de9a8fd02d545ef87236170e03ca6f9a76c04d4cf957e
endef
PKG_BUILD_DEPENDS:=golang/host
diff --git a/openwrt-bandix/Makefile b/openwrt-bandix/Makefile
index 725f200..2a1b626 100644
--- a/openwrt-bandix/Makefile
+++ b/openwrt-bandix/Makefile
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=bandix
-PKG_VERSION:=0.8.3
+PKG_VERSION:=0.9.0
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
@@ -13,7 +13,7 @@ include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-values.mk
# 二进制文件的文件名和URL
-RUST_BANDIX_VERSION:=0.8.3
+RUST_BANDIX_VERSION:=0.9.0
RUST_BINARY_FILENAME:=bandix-$(RUST_BANDIX_VERSION)-$(RUSTC_TARGET_ARCH).tar.gz