🤞 Sync 2025-12-01 00:10:48

This commit is contained in:
actions-user
2025-12-01 00:10:48 +08:00
parent 8bbccc72e1
commit a08dd0ccf0
22 changed files with 1542 additions and 284 deletions

View File

@@ -10,7 +10,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
PKG_MAINTAINER:=timsaya
PKG_VERSION:=0.9.1
PKG_VERSION:=0.10.0
PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk

View File

@@ -148,6 +148,27 @@ var callGetMetrics = rpc.declare({
expect: {}
});
var callGetMetricsDay = rpc.declare({
object: 'luci.bandix',
method: 'getMetricsDay',
params: ['mac'],
expect: {}
});
var callGetMetricsWeek = rpc.declare({
object: 'luci.bandix',
method: 'getMetricsWeek',
params: ['mac'],
expect: {}
});
var callGetMetricsMonth = rpc.declare({
object: 'luci.bandix',
method: 'getMetricsMonth',
params: ['mac'],
expect: {}
});
// 定时限速 RPC
var callGetScheduleLimits = rpc.declare({
object: 'luci.bandix',
@@ -1017,6 +1038,94 @@ return view.extend({
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.history-header-left {
display: flex;
align-items: center;
gap: 16px;
}
.history-tabs {
display: inline-flex;
background-color: rgba(0, 0, 0, 0.04);
border-radius: 8px;
padding: 3px;
gap: 2px;
}
@media (prefers-color-scheme: dark) {
.history-tabs {
background-color: rgba(255, 255, 255, 0.08);
}
}
.history-tab {
padding: 6px 16px;
text-align: center;
cursor: pointer;
border: none;
background: transparent;
font-size: 0.8125rem;
font-weight: 500;
color: rgba(0, 0, 0, 0.65);
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 6px;
white-space: nowrap;
position: relative;
}
@media (prefers-color-scheme: dark) {
.history-tab {
color: rgba(255, 255, 255, 0.65);
}
}
.history-tab:hover:not(.active) {
color: rgba(0, 0, 0, 0.85);
background-color: rgba(0, 0, 0, 0.06);
}
@media (prefers-color-scheme: dark) {
.history-tab:hover:not(.active) {
color: rgba(255, 255, 255, 0.85);
background-color: rgba(255, 255, 255, 0.12);
}
}
.history-tab.active {
background-color: #ffffff;
color: #3b82f6;
font-weight: 600;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.08);
}
@media (prefers-color-scheme: dark) {
.history-tab.active {
background-color: rgba(59, 130, 246, 0.15);
color: #60a5fa;
box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.3);
}
}
@media (max-width: 768px) {
.history-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.history-header-left {
width: 100%;
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.history-tabs {
width: 100%;
padding: 2px;
}
.history-tab {
flex: 1;
padding: 8px 6px;
font-size: 0.75rem;
}
/* 移动端只显示 Realtime tab */
.history-tab[data-range="day"],
.history-tab[data-range="week"],
.history-tab[data-range="month"] {
display: none !important;
}
}
.history-controls {
display: flex;
@@ -1034,7 +1143,6 @@ return view.extend({
position: relative;
}
.history-legend {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
@@ -1452,10 +1560,19 @@ return view.extend({
// 统计卡片
E('div', { 'class': 'stats-grid', 'id': 'stats-grid' }),
// 历史趋势卡片(时间范围筛选
// 历史趋势卡片(时间范围 tab 切换
E('div', { 'class': 'cbi-section', 'id': 'history-card' }, [
E('h3', { 'class': 'history-header', 'style': 'display: flex; align-items: center; justify-content: space-between;' }, [
E('span', {}, _('Traffic History')),
E('div', { 'class': 'history-header' }, [
E('div', { 'class': 'history-header-left' }, [
// E('h3', { 'style': 'margin: 0; font-size: 1rem; font-weight: 600;' }, _('Traffic History')),
// 时间范围 Tab 切换
E('div', { 'class': 'history-tabs' }, [
E('button', { 'class': 'history-tab active', 'data-range': 'realtime', 'id': 'history-tab-realtime' }, _('Realtime')),
E('button', { 'class': 'history-tab', 'data-range': 'day', 'id': 'history-tab-day' }, _('Day')),
E('button', { 'class': 'history-tab', 'data-range': 'week', 'id': 'history-tab-week' }, _('Week')),
E('button', { 'class': 'history-tab', 'data-range': 'month', 'id': 'history-tab-month' }, _('Month'))
])
]),
E('div', { 'class': 'history-legend' }, [
E('div', { 'class': 'legend-item' }, [
E('span', { 'class': 'legend-dot legend-up' }),
@@ -2525,14 +2642,109 @@ return view.extend({
}
function getTypeKeys(type) {
// 对于非实时时间范围day/week/month只有 WAN 数据
if (currentTimeRange !== 'realtime') {
// 所有类型都使用 WAN 数据
return { up: 'wide_tx_rate', down: 'wide_rx_rate' };
}
if (type === 'lan') return { up: 'local_tx_rate', down: 'local_rx_rate' };
if (type === 'wan') return { up: 'wide_tx_rate', down: 'wide_rx_rate' };
return { up: 'total_tx_rate', down: 'total_rx_rate' };
}
// 当前选择的时间范围
var currentTimeRange = localStorage.getItem('bandix_time_range') || 'realtime';
function fetchMetricsData(mac) {
// 根据选择的时间范围调用不同的接口
var range = currentTimeRange;
var callFunction;
switch (range) {
case 'day':
callFunction = callGetMetricsDay;
break;
case 'week':
callFunction = callGetMetricsWeek;
break;
case 'month':
callFunction = callGetMetricsMonth;
break;
case 'realtime':
default:
callFunction = callGetMetrics;
break;
}
// 通过 ubus RPC 获取,避免跨域与鉴权问题
return callGetMetrics(mac || '').then(function (res) { return res || { metrics: [] }; });
return callFunction(mac || '').then(function (res) { return res || { metrics: [] }; });
}
// 将数组数组格式转换为对象数组格式实时数据格式13个字段
// 输入格式: [[ts_ms, total_rx_rate, total_tx_rate, local_rx_rate, local_tx_rate, wide_rx_rate, wide_tx_rate, total_rx_bytes, total_tx_bytes, local_rx_bytes, local_tx_bytes, wide_rx_bytes, wide_tx_bytes], ...]
// 输出格式: [{ts_ms, total_rx_rate, total_tx_rate, ...}, ...]
function convertMetricsArrayToObjects(metricsArray) {
if (!Array.isArray(metricsArray)) {
return [];
}
return metricsArray.map(function(arr) {
// 检查数据格式如果是15个字段说明是 day/week/month 格式
if (arr.length >= 15) {
// day/week/month 格式:使用 P95 作为主要显示值
return {
ts_ms: arr[0] || 0,
// 使用 P95 作为主要速率显示(最有价值的指标)
wide_rx_rate: arr[5] || 0, // wide_rx_rate_p95
wide_tx_rate: arr[11] || 0, // wide_tx_rate_p95
// 保存所有统计信息供 tooltip 使用
wide_rx_rate_avg: arr[1] || 0,
wide_rx_rate_max: arr[2] || 0,
wide_rx_rate_min: arr[3] || 0,
wide_rx_rate_p90: arr[4] || 0,
wide_rx_rate_p95: arr[5] || 0,
wide_rx_rate_p99: arr[6] || 0,
wide_tx_rate_avg: arr[7] || 0,
wide_tx_rate_max: arr[8] || 0,
wide_tx_rate_min: arr[9] || 0,
wide_tx_rate_p90: arr[10] || 0,
wide_tx_rate_p95: arr[11] || 0,
wide_tx_rate_p99: arr[12] || 0,
wide_rx_bytes: arr[13] || 0,
wide_tx_bytes: arr[14] || 0,
// 标记这是聚合数据
is_aggregated: true,
// 为了兼容性设置其他字段day/week/month 只有 WAN 数据)
total_rx_rate: arr[5] || 0, // 使用 P95
total_tx_rate: arr[11] || 0, // 使用 P95
local_rx_rate: 0,
local_tx_rate: 0,
total_rx_bytes: arr[13] || 0,
total_tx_bytes: arr[14] || 0,
local_rx_bytes: 0,
local_tx_bytes: 0
};
} else {
// 实时数据格式13个字段
return {
ts_ms: arr[0] || 0,
total_rx_rate: arr[1] || 0,
total_tx_rate: arr[2] || 0,
local_rx_rate: arr[3] || 0,
local_tx_rate: arr[4] || 0,
wide_rx_rate: arr[5] || 0,
wide_tx_rate: arr[6] || 0,
total_rx_bytes: arr[7] || 0,
total_tx_bytes: arr[8] || 0,
local_rx_bytes: arr[9] || 0,
local_tx_bytes: arr[10] || 0,
wide_rx_bytes: arr[11] || 0,
wide_tx_bytes: arr[12] || 0,
is_aggregated: false
};
}
}).filter(function(item) { return item !== null; });
}
// 辅助函数:使用当前缩放设置绘制图表
@@ -2777,6 +2989,18 @@ return view.extend({
var ss = ('' + d.getSeconds()).padStart(2, '0');
return hh + ':' + mm + ':' + ss;
}
// 完整日期时间格式(用于聚合数据)
function msToFullDateTimeLabel(ts) {
var d = new Date(ts);
var year = d.getFullYear();
var month = ('' + (d.getMonth() + 1)).padStart(2, '0');
var day = ('' + d.getDate()).padStart(2, '0');
var hh = ('' + d.getHours()).padStart(2, '0');
var mm = ('' + d.getMinutes()).padStart(2, '0');
var ss = ('' + d.getSeconds()).padStart(2, '0');
return year + '-' + month + '-' + day + ' ' + hh + ':' + mm + ':' + ss;
}
function buildTooltipHtml(point) {
if (!point) return '';
@@ -2784,6 +3008,7 @@ return view.extend({
var typeSel = (typeof document !== 'undefined' ? document.getElementById('history-type-select') : null);
var selType = (typeSel && typeSel.value) ? typeSel.value : 'total';
var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
var isAggregated = point.is_aggregated || false;
function row(label, val) {
lines.push('<div class="ht-row"><span class="ht-key">' + label + '</span><span class="ht-val">' + val + '</span></div>');
@@ -2815,64 +3040,99 @@ return view.extend({
return { up: 'total_tx_bytes', down: 'total_rx_bytes' };
}
lines.push('<div class="ht-title">' + msToTimeLabel(point.ts_ms) + '</div>');
// 若选择了设备,显示设备信息
try {
var macSel = (typeof document !== 'undefined' ? document.getElementById('history-device-select') : null);
var macVal = (macSel && macSel.value) ? macSel.value : '';
if (macVal && Array.isArray(latestDevices)) {
var dev = latestDevices.find(function(d){ return d.mac === macVal; });
if (dev) {
var ipv6Info = '';
var lanIPv6 = filterLanIPv6(dev.ipv6_addresses);
if (lanIPv6.length > 0) {
ipv6Info = ' | IPv6: ' + lanIPv6.join(', ');
}
var devLabel = (dev.hostname || '-') + (dev.ip ? ' (' + dev.ip + ')' : '') + (dev.mac ? ' [' + dev.mac + ']' : '') + ipv6Info;
lines.push('<div class="ht-device">' + _('Device') + ': ' + devLabel + '</div>');
}
// 标题:聚合数据显示完整日期时间,实时数据只显示时间
if (isAggregated) {
lines.push('<div class="ht-title">' + msToFullDateTimeLabel(point.ts_ms) + '</div>');
var rangeLabel = currentTimeRange === 'day' ? _('Daily') :
currentTimeRange === 'week' ? _('Weekly') :
currentTimeRange === 'month' ? _('Monthly') : '';
if (rangeLabel) {
lines.push('<div style="font-size: 0.75rem; opacity: 0.6; margin-bottom: 4px;">' + rangeLabel + ' ' + _('Statistics') + '</div>');
}
} catch (e) {}
} else {
lines.push('<div class="ht-title">' + msToTimeLabel(point.ts_ms) + '</div>');
}
// 关键信息:选中类型的上下行速率(大号显示)
var kpiLabels = labelsFor(selType);
var kpiRateKeys = rateKeysFor(selType);
lines.push(
'<div class="ht-kpis">' +
'<div class="ht-kpi up">' +
'<div class="ht-k-label">' + kpiLabels.up + '</div>' +
'<div class="ht-k-value">' + rateValue(kpiRateKeys.up) + '</div>' +
'</div>' +
'<div class="ht-kpi down">' +
'<div class="ht-k-label">' + kpiLabels.down + '</div>' +
'<div class="ht-k-value">' + rateValue(kpiRateKeys.down) + '</div>' +
'</div>' +
'</div>'
);
if (isAggregated) {
// 聚合数据:显示 P95 值(主要指标)
lines.push(
'<div class="ht-kpis">' +
'<div class="ht-kpi up">' +
'<div class="ht-k-label">' + _('WAN Upload') + ' (P95)</div>' +
'<div class="ht-k-value">' + formatByterate(point.wide_tx_rate_p95 || 0, speedUnit) + '</div>' +
'</div>' +
'<div class="ht-kpi down">' +
'<div class="ht-k-label">' + _('WAN Download') + ' (P95)</div>' +
'<div class="ht-k-value">' + formatByterate(point.wide_rx_rate_p95 || 0, speedUnit) + '</div>' +
'</div>' +
'</div>'
);
// 详细统计信息
lines.push('<div class="ht-divider"></div>');
lines.push('<div class="ht-section-title">' + _('Upload Statistics') + '</div>');
row(_('Average'), formatByterate(point.wide_tx_rate_avg || 0, speedUnit));
row(_('Maximum'), formatByterate(point.wide_tx_rate_max || 0, speedUnit));
row(_('Minimum'), formatByterate(point.wide_tx_rate_min || 0, speedUnit));
row('P90', formatByterate(point.wide_tx_rate_p90 || 0, speedUnit));
row('P95', formatByterate(point.wide_tx_rate_p95 || 0, speedUnit));
row('P99', formatByterate(point.wide_tx_rate_p99 || 0, speedUnit));
lines.push('<div class="ht-section-title" style="margin-top: 8px;">' + _('Download Statistics') + '</div>');
row(_('Average'), formatByterate(point.wide_rx_rate_avg || 0, speedUnit));
row(_('Maximum'), formatByterate(point.wide_rx_rate_max || 0, speedUnit));
row(_('Minimum'), formatByterate(point.wide_rx_rate_min || 0, speedUnit));
row('P90', formatByterate(point.wide_rx_rate_p90 || 0, speedUnit));
row('P95', formatByterate(point.wide_rx_rate_p95 || 0, speedUnit));
row('P99', formatByterate(point.wide_rx_rate_p99 || 0, speedUnit));
// 累计流量(只显示 WAN
lines.push('<div class="ht-divider"></div>');
lines.push('<div class="ht-section-title">' + _('Cumulative Traffic') + '</div>');
row(_('WAN Uploaded'), bytesValue('wide_tx_bytes'));
row(_('WAN Downloaded'), bytesValue('wide_rx_bytes'));
} else {
// 实时数据:显示实时速率
lines.push(
'<div class="ht-kpis">' +
'<div class="ht-kpi up">' +
'<div class="ht-k-label">' + kpiLabels.up + '</div>' +
'<div class="ht-k-value">' + rateValue(kpiRateKeys.up) + '</div>' +
'</div>' +
'<div class="ht-kpi down">' +
'<div class="ht-k-label">' + kpiLabels.down + '</div>' +
'<div class="ht-k-value">' + rateValue(kpiRateKeys.down) + '</div>' +
'</div>' +
'</div>'
);
// 次要信息:其余类型的速率(精简展示)
var otherTypes = ['total', 'lan', 'wan'].filter(function (t) { return t !== selType; });
if (otherTypes.length) {
lines.push('<div class="ht-section-title">' + _('Other Rates') + '</div>');
otherTypes.forEach(function (t) {
var lbs = labelsFor(t);
var ks = rateKeysFor(t);
row(lbs.up, rateValue(ks.up));
row(lbs.down, rateValue(ks.down));
});
// 次要信息:其余类型的速率(精简展示)
var otherTypes = ['total', 'lan', 'wan'].filter(function (t) { return t !== selType; });
if (otherTypes.length) {
lines.push('<div class="ht-section-title">' + _('Other Rates') + '</div>');
otherTypes.forEach(function (t) {
var lbs = labelsFor(t);
var ks = rateKeysFor(t);
row(lbs.up, rateValue(ks.up));
row(lbs.down, rateValue(ks.down));
});
}
// 累计区分LAN 流量与公网
lines.push('<div class="ht-divider"></div>');
lines.push('<div class="ht-section-title">' + _('Cumulative') + '</div>');
row(_('Total Uploaded'), bytesValue('total_tx_bytes'));
row(_('Total Downloaded'), bytesValue('total_rx_bytes'));
row(_('LAN Uploaded'), bytesValue('local_tx_bytes'));
row(_('LAN Downloaded'), bytesValue('local_rx_bytes'));
row(_('WAN Uploaded'), bytesValue('wide_tx_bytes'));
row(_('WAN Downloaded'), bytesValue('wide_rx_bytes'));
}
// 累计区分LAN 流量与公网
lines.push('<div class="ht-divider"></div>');
lines.push('<div class="ht-section-title">' + _('Cumulative') + '</div>');
row(_('Total Uploaded'), bytesValue('total_tx_bytes'));
row(_('Total Downloaded'), bytesValue('total_rx_bytes'));
row(_('LAN Uploaded'), bytesValue('local_tx_bytes'));
row(_('LAN Downloaded'), bytesValue('local_rx_bytes'));
row(_('WAN Uploaded'), bytesValue('wide_tx_bytes'));
row(_('WAN Downloaded'), bytesValue('wide_rx_bytes'));
return lines.join('');
}
@@ -3236,7 +3496,9 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
return fetchMetricsData(mac).then(function (res) {
var data = Array.isArray(res && res.metrics) ? res.metrics.slice() : [];
// 将数组数组格式转换为对象数组格式
var rawMetrics = res && res.metrics ? res.metrics : [];
var data = convertMetricsArrayToObjects(rawMetrics);
lastHistoryData = data;
var retentionBadge = document.getElementById('history-retention');
@@ -3603,14 +3865,99 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
});
}
// 历史趋势:事件绑定
(function initHistoryControls() {
// 历史趋势:事件绑定(延迟执行以确保 DOM 已加载)
function initHistoryControls() {
var typeSel = document.getElementById('history-type-select');
var devSel = document.getElementById('history-device-select');
if (typeSel) typeSel.value = 'total';
// 初始化缩放倍率显示
updateZoomLevelDisplay();
// Tab 切换事件处理
var tabButtons = document.querySelectorAll('.history-tab');
// 确保找到了 tab 按钮
if (tabButtons.length === 0) {
console.warn('History tab buttons not found, retrying...');
setTimeout(initHistoryControls, 100);
return;
}
tabButtons.forEach(function(btn) {
btn.addEventListener('click', function() {
var range = this.getAttribute('data-range');
// 更新当前选择的时间范围
currentTimeRange = range;
localStorage.setItem('bandix_time_range', range);
// 更新 tab 状态
tabButtons.forEach(function(b) {
b.classList.remove('active');
});
this.classList.add('active');
// 对于非实时时间范围,禁用 LAN 和 Total 选项(因为只有 WAN 数据)
if (range !== 'realtime') {
if (typeSel) {
// 如果当前选择的是 LAN 或 Total切换到 WAN
if (typeSel.value === 'lan' || typeSel.value === 'total') {
typeSel.value = 'wan';
}
// 禁用 LAN 和 Total 选项
var lanOption = typeSel.querySelector('option[value="lan"]');
var totalOption = typeSel.querySelector('option[value="total"]');
if (lanOption) lanOption.disabled = true;
if (totalOption) totalOption.disabled = true;
}
} else {
// 实时模式下,启用所有选项
if (typeSel) {
var lanOption = typeSel.querySelector('option[value="lan"]');
var totalOption = typeSel.querySelector('option[value="total"]');
if (lanOption) lanOption.disabled = false;
if (totalOption) totalOption.disabled = false;
}
}
// 刷新历史数据
refreshHistory();
});
});
// 恢复之前选择的时间范围
var savedRange = localStorage.getItem('bandix_time_range') || 'realtime';
// 移动端强制使用 realtime
var screenWidth = window.innerWidth || document.documentElement.clientWidth;
var isMobileScreen = screenWidth <= 768;
if (isMobileScreen) {
savedRange = 'realtime';
currentTimeRange = 'realtime';
localStorage.setItem('bandix_time_range', 'realtime');
} else {
currentTimeRange = savedRange;
}
tabButtons.forEach(function(btn) {
if (btn.getAttribute('data-range') === savedRange) {
btn.classList.add('active');
// 触发一次点击以应用选项禁用逻辑
if (savedRange !== 'realtime' && typeSel) {
var lanOption = typeSel.querySelector('option[value="lan"]');
var totalOption = typeSel.querySelector('option[value="total"]');
if (lanOption) lanOption.disabled = true;
if (totalOption) totalOption.disabled = true;
if (typeSel.value === 'lan' || typeSel.value === 'total') {
typeSel.value = 'wan';
}
}
} else {
btn.classList.remove('active');
}
});
function onFilterChange() {
refreshHistory();
// 同步刷新表格(立即生效,不等轮询)
@@ -3626,12 +3973,16 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
// 首次加载
refreshHistory();
})();
}
// 延迟执行以确保 DOM 已加载
setTimeout(initHistoryControls, 0);
// 历史趋势轮询(每1秒)
// 历史趋势轮询(实时数据每1秒其他时间范围每30秒)
// 使用 poll.add 但根据时间范围动态调整
poll.add(function () {
return refreshHistory();
},1);
}, 1);

View File

@@ -3,11 +3,24 @@
'require form';
'require ui';
'require uci';
'require fs';
'require rpc';
// 暗色模式检测已改为使用 CSS 媒体查询 @media (prefers-color-scheme: dark)
// 声明 RPC 调用方法
var callClearData = rpc.declare({
object: 'luci.bandix',
method: 'clearData',
expect: { }
});
var callRestartService = rpc.declare({
object: 'luci.bandix',
method: 'restartService',
expect: { }
});
return view.extend({
load: function () {
return Promise.all([
@@ -108,14 +121,78 @@ return view.extend({
return uci.get('bandix', section_id, 'data_dir') || '/usr/share/bandix';
};
// 添加意见反馈信息
o = s.option(form.DummyValue, 'feedback_info', _('Feedback'));
o.href = 'https://github.com/timsaya';
o.cfgvalue = function () {
return 'https://github.com/timsaya';
};
// 添加意见反馈信息
o = s.option(form.DummyValue, 'feedback_info', _('Feedback'));
o.href = 'https://github.com/timsaya';
o.cfgvalue = function () {
return 'https://github.com/timsaya';
};
// 2. 流量监控设置部分 (traffic)
// 添加清空数据按钮
o = s.option(form.Button, 'clear_data', _('Clear Traffic Data'));
o.inputtitle = _('Clear Traffic Data');
o.inputstyle = 'reset';
o.onclick = function () {
return ui.showModal(_('Clear Traffic Data'), [
E('p', _('Are you sure you want to clear all traffic data? This action cannot be undone.')),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
' ',
E('button', {
'class': 'btn cbi-button-negative',
'click': function () {
ui.hideModal();
return callClearData()
.then(function (result) {
if (result && !result.success) {
ui.addNotification(null, E('p', _('Failed to clear traffic data: ') + (result.error || 'Unknown error')), 'error');
}
})
.catch(function (err) {
ui.addNotification(null, E('p', _('Failed to clear traffic data: ') + err.message), 'error');
});
}
}, _('Confirm'))
])
]);
};
// 添加重启服务按钮
o = s.option(form.Button, 'restart_service', _('Restart Service'));
o.inputtitle = _('Restart Bandix Service');
o.inputstyle = 'apply';
o.onclick = function () {
return ui.showModal(_('Restart Service'), [
E('p', _('Are you sure you want to restart the Bandix service?')),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
' ',
E('button', {
'class': 'btn cbi-button-action',
'click': function () {
ui.hideModal();
return callRestartService()
.then(function (result) {
if (result && !result.success) {
ui.addNotification(null, E('p', _('Failed to restart service: ') + (result.error || 'Unknown error')), 'error');
}
})
.catch(function (err) {
ui.addNotification(null, E('p', _('Failed to restart service: ') + err.message), 'error');
});
}
}, _('Confirm'))
])
]);
};
// 2. 流量监控设置部分 (traffic)
s = m.section(form.NamedSection, 'traffic', 'traffic', _('Traffic Monitor Settings'));
s.description = _('Configure traffic monitoring related parameters');
s.addremove = false;
@@ -147,6 +224,7 @@ return view.extend({
o.default = '0';
o.rmempty = false;
// 添加数据 flush 间隔(秒)
o = s.option(form.ListValue, 'traffic_flush_interval_seconds', _('Data Flush Interval'),
_('Set the interval for flushing data to disk'));
@@ -166,13 +244,10 @@ return view.extend({
// 添加历史流量周期(秒)
o = s.option(form.ListValue, 'traffic_retention_seconds', _('Traffic History Period'),
_('10 minutes interval uses about 60 KB per device'));
o.value('60', _('1 minute'));
o.value('300', _('5 minutes'));
o.value('600', _('10 minutes'));
o.value('900', _('15 minutes'));
o.value('1200', _('20 minutes'));
o.value('1500', _('25 minutes'));
o.value('1800', _('30 minutes'));
o.value('3600', _('1 hour'));
o.default = '600';
o.rmempty = false;

View File

@@ -841,3 +841,66 @@ msgstr "Sin regla activa"
msgid "Schedule Rules"
msgstr "Reglas de limitación programada"
msgid "Upload Statistics"
msgstr "Estadísticas de subida"
msgid "Download Statistics"
msgstr "Estadísticas de descarga"
msgid "Cumulative Traffic"
msgstr "Tráfico acumulado"
msgid "Daily"
msgstr "Diario"
msgid "Weekly"
msgstr "Semanal"
msgid "Monthly"
msgstr "Mensual"
msgid "Statistics"
msgstr "Estadísticas"
msgid "Average"
msgstr "Promedio"
msgid "Maximum"
msgstr "Máximo"
msgid "Minimum"
msgstr "Mínimo"
msgid "WAN Upload"
msgstr "Subida WAN"
msgid "WAN Download"
msgstr "Descarga WAN"
msgid "WAN Uploaded"
msgstr "Subido WAN"
msgid "WAN Downloaded"
msgstr "Descargado WAN"
msgid "Clear Traffic Data"
msgstr "Borrar datos de tráfico"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "¿Está seguro de que desea borrar todos los datos de tráfico? Esta acción no se puede deshacer."
msgid "Failed to clear traffic data: "
msgstr "Error al borrar los datos de tráfico: "
msgid "Restart Service"
msgstr "Reiniciar servicio"
msgid "Restart Bandix Service"
msgstr "Reiniciar servicio Bandix"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "¿Está seguro de que desea reiniciar el servicio Bandix?"
msgid "Failed to restart service: "
msgstr "Error al reiniciar el servicio: "

View File

@@ -840,4 +840,67 @@ msgid "No active rule"
msgstr "Aucune règle active"
msgid "Schedule Rules"
msgstr "Règles de limitation programmée"
msgstr "Règles de limitation programmée"
msgid "Upload Statistics"
msgstr "Statistiques d'envoi"
msgid "Download Statistics"
msgstr "Statistiques de téléchargement"
msgid "Cumulative Traffic"
msgstr "Trafic cumulé"
msgid "Daily"
msgstr "Quotidien"
msgid "Weekly"
msgstr "Hebdomadaire"
msgid "Monthly"
msgstr "Mensuel"
msgid "Statistics"
msgstr "Statistiques"
msgid "Average"
msgstr "Moyenne"
msgid "Maximum"
msgstr "Maximum"
msgid "Minimum"
msgstr "Minimum"
msgid "WAN Upload"
msgstr "Envoi WAN"
msgid "WAN Download"
msgstr "Téléchargement WAN"
msgid "WAN Uploaded"
msgstr "Envoyé WAN"
msgid "WAN Downloaded"
msgstr "Téléchargé WAN"
msgid "Clear Traffic Data"
msgstr "Effacer les données de trafic"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "Êtes-vous sûr de vouloir effacer toutes les données de trafic ? Cette action est irréversible."
msgid "Failed to clear traffic data: "
msgstr "Échec de l'effacement des données de trafic : "
msgid "Restart Service"
msgstr "Redémarrer le service"
msgid "Restart Bandix Service"
msgstr "Redémarrer le service Bandix"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "Êtes-vous sûr de vouloir redémarrer le service Bandix ?"
msgid "Failed to restart service: "
msgstr "Échec du redémarrage du service : "

View File

@@ -841,3 +841,66 @@ msgstr "Tidak ada aturan aktif"
msgid "Schedule Rules"
msgstr "Aturan pembatasan terjadwal"
msgid "Upload Statistics"
msgstr "Statistik unggahan"
msgid "Download Statistics"
msgstr "Statistik unduhan"
msgid "Cumulative Traffic"
msgstr "Lalu lintas kumulatif"
msgid "Daily"
msgstr "Harian"
msgid "Weekly"
msgstr "Mingguan"
msgid "Monthly"
msgstr "Bulanan"
msgid "Statistics"
msgstr "Statistik"
msgid "Average"
msgstr "Rata-rata"
msgid "Maximum"
msgstr "Maksimum"
msgid "Minimum"
msgstr "Minimum"
msgid "WAN Upload"
msgstr "Unggahan WAN"
msgid "WAN Download"
msgstr "Unduhan WAN"
msgid "WAN Uploaded"
msgstr "Diunggah WAN"
msgid "WAN Downloaded"
msgstr "Diunduh WAN"
msgid "Clear Traffic Data"
msgstr "Hapus Data Lalu Lintas"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "Apakah Anda yakin ingin menghapus semua data lalu lintas? Tindakan ini tidak dapat dibatalkan."
msgid "Failed to clear traffic data: "
msgstr "Gagal menghapus data lalu lintas: "
msgid "Restart Service"
msgstr "Mulai Ulang Layanan"
msgid "Restart Bandix Service"
msgstr "Mulai Ulang Layanan Bandix"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "Apakah Anda yakin ingin memulai ulang layanan Bandix?"
msgid "Failed to restart service: "
msgstr "Gagal memulai ulang layanan: "

View File

@@ -841,3 +841,66 @@ msgstr "アクティブなルールなし"
msgid "Schedule Rules"
msgstr "スケジュール制限ルール"
msgid "Upload Statistics"
msgstr "アップロード統計"
msgid "Download Statistics"
msgstr "ダウンロード統計"
msgid "Cumulative Traffic"
msgstr "累積トラフィック"
msgid "Daily"
msgstr "日次"
msgid "Weekly"
msgstr "週次"
msgid "Monthly"
msgstr "月次"
msgid "Statistics"
msgstr "統計"
msgid "Average"
msgstr "平均"
msgid "Maximum"
msgstr "最大"
msgid "Minimum"
msgstr "最小"
msgid "WAN Upload"
msgstr "WAN アップロード"
msgid "WAN Download"
msgstr "WAN ダウンロード"
msgid "WAN Uploaded"
msgstr "WAN アップロード量"
msgid "WAN Downloaded"
msgstr "WAN ダウンロード量"
msgid "Clear Traffic Data"
msgstr "トラフィックデータを削除"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "すべてのトラフィックデータを削除してもよろしいですか?この操作は元に戻せません。"
msgid "Failed to clear traffic data: "
msgstr "トラフィックデータの削除に失敗しました:"
msgid "Restart Service"
msgstr "サービス再起動"
msgid "Restart Bandix Service"
msgstr "Bandix サービスを再起動"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "Bandix サービスを再起動してもよろしいですか?"
msgid "Failed to restart service: "
msgstr "サービスの再起動に失敗しました:"

View File

@@ -842,3 +842,66 @@ msgstr "Brak aktywnej reguły"
msgid "Schedule Rules"
msgstr "Reguły limitu czasu"
msgid "Upload Statistics"
msgstr "Statystyki wysyłania"
msgid "Download Statistics"
msgstr "Statystyki pobierania"
msgid "Cumulative Traffic"
msgstr "Skumulowany ruch"
msgid "Daily"
msgstr "Dzienny"
msgid "Weekly"
msgstr "Tygodniowy"
msgid "Monthly"
msgstr "Miesięczny"
msgid "Statistics"
msgstr "Statystyki"
msgid "Average"
msgstr "Średnia"
msgid "Maximum"
msgstr "Maksimum"
msgid "Minimum"
msgstr "Minimum"
msgid "WAN Upload"
msgstr "Wysyłanie WAN"
msgid "WAN Download"
msgstr "Pobieranie WAN"
msgid "WAN Uploaded"
msgstr "Wysłano WAN"
msgid "WAN Downloaded"
msgstr "Pobrano WAN"
msgid "Clear Traffic Data"
msgstr "Wyczyść dane ruchu"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "Czy na pewno chcesz wyczyścić wszystkie dane ruchu? Tej operacji nie można cofnąć."
msgid "Failed to clear traffic data: "
msgstr "Nie udało się wyczyścić danych ruchu: "
msgid "Restart Service"
msgstr "Uruchom ponownie usługę"
msgid "Restart Bandix Service"
msgstr "Uruchom ponownie usługę Bandix"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "Czy na pewno chcesz uruchomić ponownie usługę Bandix?"
msgid "Failed to restart service: "
msgstr "Nie udało się uruchomić ponownie usługi: "

View File

@@ -840,4 +840,67 @@ msgid "No active rule"
msgstr "Нет активных правил"
msgid "Schedule Rules"
msgstr "Правила ограничения по расписанию"
msgstr "Правила ограничения по расписанию"
msgid "Upload Statistics"
msgstr "Статистика отправки"
msgid "Download Statistics"
msgstr "Статистика загрузки"
msgid "Cumulative Traffic"
msgstr "Накопительный трафик"
msgid "Daily"
msgstr "Ежедневно"
msgid "Weekly"
msgstr "Еженедельно"
msgid "Monthly"
msgstr "Ежемесячно"
msgid "Statistics"
msgstr "Статистика"
msgid "Average"
msgstr "Среднее"
msgid "Maximum"
msgstr "Максимум"
msgid "Minimum"
msgstr "Минимум"
msgid "WAN Upload"
msgstr "Отправка WAN"
msgid "WAN Download"
msgstr "Загрузка WAN"
msgid "WAN Uploaded"
msgstr "Отправлено WAN"
msgid "WAN Downloaded"
msgstr "Загружено WAN"
msgid "Clear Traffic Data"
msgstr "Очистить данные трафика"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "Вы уверены, что хотите очистить все данные трафика? Это действие нельзя отменить."
msgid "Failed to clear traffic data: "
msgstr "Не удалось очистить данные трафика: "
msgid "Restart Service"
msgstr "Перезапустить службу"
msgid "Restart Bandix Service"
msgstr "Перезапустить службу Bandix"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "Вы уверены, что хотите перезапустить службу Bandix?"
msgid "Failed to restart service: "
msgstr "Не удалось перезапустить службу: "

View File

@@ -841,3 +841,66 @@ msgstr "ไม่มีกฎที่ใช้งาน"
msgid "Schedule Rules"
msgstr "กฎการจำกัดอัตราแบบกำหนดเวลา"
msgid "Upload Statistics"
msgstr "สถิติการอัปโหลด"
msgid "Download Statistics"
msgstr "สถิติการดาวน์โหลด"
msgid "Cumulative Traffic"
msgstr "ปริมาณการใช้งานสะสม"
msgid "Daily"
msgstr "รายวัน"
msgid "Weekly"
msgstr "รายสัปดาห์"
msgid "Monthly"
msgstr "รายเดือน"
msgid "Statistics"
msgstr "สถิติ"
msgid "Average"
msgstr "ค่าเฉลี่ย"
msgid "Maximum"
msgstr "สูงสุด"
msgid "Minimum"
msgstr "ต่ำสุด"
msgid "WAN Upload"
msgstr "อัปโหลด WAN"
msgid "WAN Download"
msgstr "ดาวน์โหลด WAN"
msgid "WAN Uploaded"
msgstr "อัปโหลด WAN แล้ว"
msgid "WAN Downloaded"
msgstr "ดาวน์โหลด WAN แล้ว"
msgid "Clear Traffic Data"
msgstr "ลบข้อมูลทราฟฟิก"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "คุณแน่ใจหรือไม่ว่าต้องการลบข้อมูลทราฟฟิกทั้งหมด? การกระทำนี้ไม่สามารถยกเลิกได้"
msgid "Failed to clear traffic data: "
msgstr "ล้มเหลวในการลบข้อมูลทราฟฟิก: "
msgid "Restart Service"
msgstr "รีสตาร์ทบริการ"
msgid "Restart Bandix Service"
msgstr "รีสตาร์ทบริการ Bandix"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "คุณแน่ใจหรือไม่ว่าต้องการรีสตาร์ทบริการ Bandix?"
msgid "Failed to restart service: "
msgstr "ล้มเหลวในการรีสตาร์ทบริการ: "

View File

@@ -840,4 +840,67 @@ msgid "No active rule"
msgstr "无生效规则"
msgid "Schedule Rules"
msgstr "定时限速规则"
msgstr "定时限速规则"
msgid "Upload Statistics"
msgstr "上传统计"
msgid "Download Statistics"
msgstr "下载统计"
msgid "Cumulative Traffic"
msgstr "累计流量"
msgid "Daily"
msgstr "每日"
msgid "Weekly"
msgstr "每周"
msgid "Monthly"
msgstr "每月"
msgid "Statistics"
msgstr "统计"
msgid "Average"
msgstr "平均值"
msgid "Maximum"
msgstr "最大值"
msgid "Minimum"
msgstr "最小值"
msgid "WAN Upload"
msgstr "WAN 上传"
msgid "WAN Download"
msgstr "WAN 下载"
msgid "WAN Uploaded"
msgstr "WAN 上传量"
msgid "WAN Downloaded"
msgstr "WAN 下载量"
msgid "Clear Traffic Data"
msgstr "删除流量数据"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "确定要清除所有流量数据吗?此操作无法撤销。"
msgid "Failed to clear traffic data: "
msgstr "清除流量数据失败:"
msgid "Restart Service"
msgstr "重启服务"
msgid "Restart Bandix Service"
msgstr "重启 Bandix 服务"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "确定要重启 Bandix 服务吗?"
msgid "Failed to restart service: "
msgstr "重启服务失败:"

View File

@@ -840,4 +840,67 @@ msgid "No active rule"
msgstr "無生效規則"
msgid "Schedule Rules"
msgstr "定時限速規則"
msgstr "定時限速規則"
msgid "Upload Statistics"
msgstr "上傳統計"
msgid "Download Statistics"
msgstr "下載統計"
msgid "Cumulative Traffic"
msgstr "累計流量"
msgid "Daily"
msgstr "每日"
msgid "Weekly"
msgstr "每週"
msgid "Monthly"
msgstr "每月"
msgid "Statistics"
msgstr "統計"
msgid "Average"
msgstr "平均值"
msgid "Maximum"
msgstr "最大值"
msgid "Minimum"
msgstr "最小值"
msgid "WAN Upload"
msgstr "WAN 上傳"
msgid "WAN Download"
msgstr "WAN 下載"
msgid "WAN Uploaded"
msgstr "WAN 上傳量"
msgid "WAN Downloaded"
msgstr "WAN 下載量"
msgid "Clear Traffic Data"
msgstr "刪除流量數據"
msgid "Are you sure you want to clear all traffic data? This action cannot be undone."
msgstr "確定要清除所有流量數據嗎?此操作無法撤銷。"
msgid "Failed to clear traffic data: "
msgstr "清除流量數據失敗:"
msgid "Restart Service"
msgstr "重啟服務"
msgid "Restart Bandix Service"
msgstr "重啟 Bandix 服務"
msgid "Are you sure you want to restart the Bandix service?"
msgstr "確定要重啟 Bandix 服務嗎?"
msgid "Failed to restart service: "
msgstr "重啟服務失敗:"

View File

@@ -9,6 +9,9 @@ readonly BANDIX_API_BASE="http://127.0.0.1:$BANDIX_PORT"
readonly BANDIX_DEVICES_API="$BANDIX_API_BASE/api/traffic/devices"
readonly BANDIX_LIMITS_API="$BANDIX_API_BASE/api/traffic/limits"
readonly BANDIX_METRICS_API="$BANDIX_API_BASE/api/traffic/metrics"
readonly BANDIX_METRICS_DAY_API="$BANDIX_API_BASE/api/traffic/metrics/day"
readonly BANDIX_METRICS_WEEK_API="$BANDIX_API_BASE/api/traffic/metrics/week"
readonly BANDIX_METRICS_MONTH_API="$BANDIX_API_BASE/api/traffic/metrics/month"
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"
@@ -99,6 +102,90 @@ get_metrics() {
fi
}
# 获取日指标可选MAC
get_metrics_day() {
local mac="$1"
local url="$BANDIX_METRICS_DAY_API"
if [ -n "$mac" ]; then
# 转义MAC
local mac_escaped=$(echo "$mac" | sed 's/\//\\\//g')
url="$url?mac=$mac_escaped"
fi
local api_result=$(curl -s --connect-timeout 2 --max-time 10 "$url" 2>/dev/null)
local curl_exit_code=$?
if [ $curl_exit_code -ne 0 ] || [ -z "$api_result" ]; then
echo '{"retention_seconds":86400,"mac":"","metrics":[]}'
return
fi
# 使用 jsonfilter 提取 data 部分
local data_part=$(echo "$api_result" | jsonfilter -e '$.data' 2>/dev/null)
if [ -n "$data_part" ]; then
echo "$data_part"
return
else
echo '{"retention_seconds":86400,"mac":"","metrics":[]}'
fi
}
# 获取周指标可选MAC
get_metrics_week() {
local mac="$1"
local url="$BANDIX_METRICS_WEEK_API"
if [ -n "$mac" ]; then
# 转义MAC
local mac_escaped=$(echo "$mac" | sed 's/\//\\\//g')
url="$url?mac=$mac_escaped"
fi
local api_result=$(curl -s --connect-timeout 2 --max-time 10 "$url" 2>/dev/null)
local curl_exit_code=$?
if [ $curl_exit_code -ne 0 ] || [ -z "$api_result" ]; then
echo '{"retention_seconds":604800,"mac":"","metrics":[]}'
return
fi
# 使用 jsonfilter 提取 data 部分
local data_part=$(echo "$api_result" | jsonfilter -e '$.data' 2>/dev/null)
if [ -n "$data_part" ]; then
echo "$data_part"
return
else
echo '{"retention_seconds":604800,"mac":"","metrics":[]}'
fi
}
# 获取月指标可选MAC
get_metrics_month() {
local mac="$1"
local url="$BANDIX_METRICS_MONTH_API"
if [ -n "$mac" ]; then
# 转义MAC
local mac_escaped=$(echo "$mac" | sed 's/\//\\\//g')
url="$url?mac=$mac_escaped"
fi
local api_result=$(curl -s --connect-timeout 2 --max-time 10 "$url" 2>/dev/null)
local curl_exit_code=$?
if [ $curl_exit_code -ne 0 ] || [ -z "$api_result" ]; then
echo '{"retention_seconds":2592000,"mac":"","metrics":[]}'
return
fi
# 使用 jsonfilter 提取 data 部分
local data_part=$(echo "$api_result" | jsonfilter -e '$.data' 2>/dev/null)
if [ -n "$data_part" ]; then
echo "$data_part"
return
else
echo '{"retention_seconds":2592000,"mac":"","metrics":[]}'
fi
}
# 设置设备主机名绑定
set_device_hostname() {
local mac="$1"
@@ -367,6 +454,39 @@ delete_schedule_limit() {
fi
}
# 清空数据
clear_data() {
local data_dir=$(uci get bandix.general.data_dir 2>/dev/null || echo "/usr/share/bandix")
local metrics_dir="$data_dir/metrics"
# 检查目录是否存在
if [ ! -d "$metrics_dir" ]; then
make_error "Metrics directory does not exist"
return
fi
# 删除 metrics 目录下的所有文件
rm -rf "$metrics_dir"/* 2>/dev/null
if [ $? -eq 0 ]; then
make_success "Data cleared successfully"
else
make_error "Failed to clear data"
fi
}
# 重启服务
restart_service() {
# 执行服务重启
/etc/init.d/bandix restart >/dev/null 2>&1
if [ $? -eq 0 ]; then
make_success "Service restarted successfully"
else
make_error "Failed to restart service"
fi
}
case "$1" in
list)
json_init
@@ -377,6 +497,18 @@ case "$1" in
json_add_string "mac"
json_close_object
json_add_object "getMetricsDay"
json_add_string "mac"
json_close_object
json_add_object "getMetricsWeek"
json_add_string "mac"
json_close_object
json_add_object "getMetricsMonth"
json_add_string "mac"
json_close_object
json_add_object "setHostname"
json_add_string "mac"
json_add_string "hostname"
@@ -410,16 +542,22 @@ case "$1" in
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
;;
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_add_object "clearData"
json_close_object
json_add_object "restartService"
json_close_object
json_dump
json_cleanup
;;
call)
case "$2" in
getStatus)
@@ -447,6 +585,48 @@ case "$1" in
fi
get_metrics "$mac"
;;
getMetricsDay)
mac=""
input=""
if read -t 1 -r input; then
:
fi
if [ -n "$input" ]; then
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
else
[ -n "$3" ] && mac="$3"
fi
get_metrics_day "$mac"
;;
getMetricsWeek)
mac=""
input=""
if read -t 1 -r input; then
:
fi
if [ -n "$input" ]; then
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
else
[ -n "$3" ] && mac="$3"
fi
get_metrics_week "$mac"
;;
getMetricsMonth)
mac=""
input=""
if read -t 1 -r input; then
:
fi
if [ -n "$input" ]; then
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
else
[ -n "$3" ] && mac="$3"
fi
get_metrics_month "$mac"
;;
setHostname)
# logger "luci.bandix: setHostname called"
@@ -638,6 +818,14 @@ case "$1" in
make_error "No input received"
fi
;;
clearData)
# logger "luci.bandix: clearData called"
clear_data
;;
restartService)
# logger "luci.bandix: restartService called"
restart_service
;;
esac
;;
esac

View File

@@ -6,13 +6,18 @@
"luci.bandix": [
"getStatus",
"getMetrics",
"getMetricsDay",
"getMetricsWeek",
"getMetricsMonth",
"getConnection",
"setHostname",
"getDnsQueries",
"getDnsStats",
"getScheduleLimits",
"setScheduleLimit",
"deleteScheduleLimit"
"deleteScheduleLimit",
"clearData",
"restartService"
]
},
"uci": [
@@ -24,13 +29,18 @@
"luci.bandix": [
"getStatus",
"getMetrics",
"getMetricsDay",
"getMetricsWeek",
"getMetricsMonth",
"getConnection",
"setHostname",
"getDnsQueries",
"getDnsStats",
"getScheduleLimits",
"setScheduleLimit",
"deleteScheduleLimit"
"deleteScheduleLimit",
"clearData",
"restartService"
]
},
"uci": [

View File

@@ -1,7 +1,7 @@
<%
local api = require "luci.passwall.api"
-%>
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js?v=25.11.27"></script>
<style>
table th, .table .th {
@@ -54,12 +54,6 @@ table td, .table .td {
gap: 4px;
}
.node-wrapper .cbi-input-checkbox {
flex-grow: 0 !important;
flex-shrink: 0;
flex-basis: auto;
}
.cbi-tabmenu > li {
margin-right: 2px !important;
}
@@ -82,6 +76,76 @@ table td, .table .td {
align-self: stretch;
}
#cbi-passwall-nodes .pw-checkbox, #cbi-passwall-nodes th:nth-child(1) {
padding-right: 0px;
}
#select_all_btn {
display: none;
}
/* enable flex for small screens*/
@media screen and (max-width: 1152px) {
.cbi-section-table-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
#cbi-passwall-nodes-default-fieldset {
margin: 0;
}
.cbi-section-table-titles {
display: none !important;
}
/* meticulously control how each component occupies the limited space we have */
#cbi-passwall-nodes .pw-checkbox, #cbi-passwall-nodes th:nth-child(1) {
flex: 0 0 40px;
min-width: 0;
}
#cbi-passwall-nodes .pw-remark {
flex: 1 1 30%;
min-width: 0;
}
#cbi-passwall-nodes .pw-ping, #cbi-passwall-nodes .pw-tcping, #cbi-passwall-nodes .pw-urltest {
flex: 0 0 50px;
white-space: nowrap;
min-width: 0;
}
.pw-actions {
padding-top: 0 !important;
border-top-width: 0 !important;
flex: 1 1 350px;
}
#select_all_btn {
display: inline-block !important;
}
}
/* shrink actionbar even further for mobile devices */
@media screen and (max-width: 500px) {
.node-wrapper {
gap: 0;
}
.cbi-button {
margin-left: 0 !important;
margin-right: 1px !important;
}
.pw-actions {
padding-left: 5px!important;
padding-right: 5px !important;
}
}
.sortable-chosen {
background-color: rgba(220, 235, 245, 0.4) !important;
opacity: 0.7;
@@ -114,11 +178,7 @@ table td, .table .td {
function cbi_t_switch(section, tab) {
if( cbi_t[section] && cbi_t[section][tab] ) {
//在切换选项卡之前,先取消当前激活选项卡的全选状态
var btn = document.getElementById("select_all_btn");
if (btn) {
dechecked_all_node(btn);
}
dechecked_all_node();
var o = cbi_t[section][tab];
var h = document.getElementById('tab.' + section);
for( var tid in cbi_t[section] ) {
@@ -143,10 +203,7 @@ table td, .table .td {
if (typeof(cbi_t_switch) === "function") {
var old_switch = cbi_t_switch;
cbi_t_switch = function(section, tab) {
var btn = document.getElementById("select_all_btn");
if (btn) {
dechecked_all_node(btn);
}
dechecked_all_node();
return old_switch(section, tab);
};
}
@@ -249,30 +306,59 @@ table td, .table .td {
}
}
function checked_all_node(btn) {
function set_select_all_state(sectionChecked) {
var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
if (!visibleContainer) return;
var doms = visibleContainer.getElementsByClassName("nodes_select");
if (doms && doms.length > 0) {
for (var i = 0 ; i < doms.length; i++) {
doms[i].checked = true;
}
btn.value = "<%:DeSelect all%>";
btn.setAttribute("onclick", "dechecked_all_node(this)");
var nodes = visibleContainer.getElementsByClassName("nodes_select");
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
var selectAllBtn = document.getElementById("select_all_btn");
for (var i = 0; i < nodes.length; i++) {
nodes[i].checked = sectionChecked;
}
if (selectAllChk) {
selectAllChk.checked = sectionChecked;
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
selectAllChk.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)");
}
if (selectAllBtn) {
selectAllBtn.value = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
selectAllBtn.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)");
}
}
function checked_all_node(btn) {
set_select_all_state(true);
}
function dechecked_all_node(btn) {
set_select_all_state(false);
}
function update_select_state() {
var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
if (!visibleContainer) return;
var doms = visibleContainer.getElementsByClassName("nodes_select");
if (doms && doms.length > 0) {
for (var i = 0 ; i < doms.length; i++) {
doms[i].checked = false;
}
btn.value = "<%:Select all%>";
btn.setAttribute("onclick", "checked_all_node(this)");
var nodes = visibleContainer.getElementsByClassName("nodes_select");
if (!nodes.length) return;
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
var selectAllBtn = document.getElementById("select_all_btn");
var checkedCount = 0;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].checked) checkedCount++;
}
var allChecked = checkedCount === nodes.length;
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
function updateElement(el) {
if (!el) return;
if ("checked" in el) el.checked = allChecked;
if ("title" in el) el.title = title;
if ("value" in el) el.value = title;
el.setAttribute("onclick", onclickFunc);
}
updateElement(selectAllChk);
updateElement(selectAllBtn);
}
function delete_select_nodes() {
@@ -622,6 +708,9 @@ table td, .table .td {
<fieldset class="cbi-section cbi-tblsection" id="cbi-passwall-nodes-{{group}}-fieldset">
<table class="table cbi-section-table" id="cbi-passwall-nodes-{{group}}-table" style="">
<tr class="tr cbi-section-table-titles anonymous">
<th class="th cbi-section-table-cell" style="width:20px">
<input class="cbi-input-checkbox nodes_select_all" type="checkbox" onclick="checked_all_node(this)" title="<%:Select all%>"/>
</th>
<th class="th cbi-section-table-cell" style="width:40%"><%:Remarks%></th>
<th class="th cbi-section-table-cell" style="width:8%">Ping</th>
<th class="th cbi-section-table-cell" style="width:8%">TCPing</th>
@@ -642,13 +731,15 @@ table td, .table .td {
<input class="hidden" id="cbid.passwall.{{id}}.remarks" value="{{remarks_val}}"/>
<input class="hidden" id="cbid.passwall.{{id}}.address" value="{{address_val}}"/>
<input class="hidden" id="cbid.passwall.{{id}}.port" value="{{port_val}}"/>
<td class="td cbi-value-field">{{remarks}}</td>
<td class="td cbi-value-field">{{ping}}</td>
<td class="td cbi-value-field">{{tcping}}</td>
<td class="td cbi-value-field">{{url_test}}</td>
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
<td class="td cbi-value-field pw-checkbox">
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" onclick="update_select_state()"/>
</td>
<td class="td cbi-value-field pw-remark">{{remarks}}</td>
<td class="td cbi-value-field pw-ping">{{ping}}</td>
<td class="td cbi-value-field pw-tcping">{{tcping}}</td>
<td class="td cbi-value-field pw-urltest">{{url_test}}</td>
<td class="td cbi-section-table-cell nowrap cbi-section-actions pw-actions">
<div class="node-wrapper">
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" />
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this)" title="<%:To Top%>"/>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="open_set_node_div('{{id}}')"/>
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/>

View File

@@ -17,7 +17,8 @@
--background-start: oklch(0.984 0.003 247.858);
--background-mid: oklch(0.984 0.003 247.858);
--background-end: oklch(0.984 0.003 247.858);
--progress-start: oklch(0.68 0.11 233);
--progress-end: oklch(0.7535 0.1034 198.37);
--primary: oklch(0.68 0.11 233);
--primary-text: oklch(0.6656 0.1055 234.61);
--primary-hover: oklch(0.64 0.1055 234.61);
@@ -100,6 +101,8 @@
--background-start: oklch(0.2077 0.0398 265.75);
--background-mid: oklch(0.3861 0.059 188.42);
--background-end: oklch(0.4318 0.0865 166.91);
--progress-start: oklch(0.4318 0.0865 166.91);
--progress-end: oklch(62.1% 0.145 189.632);
--primary: oklch(0.534 0.118 190.485);
--primary-text: oklch(0.779 0.168 188.745);
--primary-hover: oklch(0.58 0.13 189.632);
@@ -177,85 +180,6 @@
--spacing: 0.25rem;
}
@theme {
--color-aurora-green: #6ee7b7;
--color-aurora-green-50: oklch(97.2% 0.019 158.743);
--color-aurora-green-100: oklch(94.8% 0.045 159.652);
--color-aurora-green-200: oklch(90.5% 0.089 160.125);
--color-aurora-green-300: oklch(84.7% 0.148 160.742);
--color-aurora-green-400: oklch(89.6% 0.143 162.4);
--color-aurora-green-500: oklch(77.1% 0.198 161.205);
--color-aurora-green-600: oklch(64.3% 0.175 161.428);
--color-aurora-green-700: oklch(55.2% 0.142 162.113);
--color-aurora-green-800: oklch(47.1% 0.112 162.895);
--color-aurora-green-900: oklch(40.8% 0.089 163.742);
--color-aurora-green-950: oklch(27.9% 0.061 165.128);
--color-aurora-teal: #4fc3c7;
--color-aurora-teal-50: oklch(97.8% 0.016 185.234);
--color-aurora-teal-100: oklch(95.1% 0.041 186.152);
--color-aurora-teal-200: oklch(90.8% 0.079 187.063);
--color-aurora-teal-300: oklch(85.2% 0.128 187.894);
--color-aurora-teal-500: oklch(75.8% 0.145 188.2);
--color-aurora-teal-600: oklch(62.1% 0.145 189.632);
--color-aurora-teal-700: oklch(53.4% 0.118 190.485);
--color-aurora-teal-800: oklch(45.8% 0.094 191.328);
--color-aurora-teal-900: oklch(39.7% 0.074 192.156);
--color-aurora-teal-950: oklch(28.2% 0.052 193.864);
--color-aurora-sky: #499ecb;
--color-aurora-sky-50: oklch(97.8% 0.012 214.3);
--color-aurora-sky-100: oklch(95.2% 0.025 214.3);
--color-aurora-sky-200: oklch(90.1% 0.051 214.3);
--color-aurora-sky-300: oklch(82.8% 0.085 214.3);
--color-aurora-sky-400: oklch(73.5% 0.128 214.3);
--color-aurora-sky-500: oklch(68.5% 0.142 214.3);
--color-aurora-sky-600: oklch(58.2% 0.135 214.3);
--color-aurora-sky-700: oklch(49.8% 0.118 214.3);
--color-aurora-sky-800: oklch(42.6% 0.095 214.3);
--color-aurora-sky-900: oklch(36.1% 0.072 214.3);
--color-aurora-sky-950: oklch(25.8% 0.048 214.3);
--color-aurora-night-slate: #0f172a;
--color-aurora-night-slate-50: oklch(96.8% 0.008 245.123);
--color-aurora-night-slate-100: oklch(93.2% 0.018 246.084);
--color-aurora-night-slate-200: oklch(86.4% 0.035 247.159);
--color-aurora-night-slate-300: oklch(76.8% 0.062 248.347);
--color-aurora-night-slate-400: oklch(64.2% 0.098 249.638);
--color-aurora-night-slate-500: oklch(52.1% 0.128 250.942);
--color-aurora-night-slate-600: oklch(41.8% 0.145 252.258);
--color-aurora-night-slate-700: oklch(33.6% 0.142 253.586);
--color-aurora-night-slate-800: oklch(26.8% 0.128 254.926);
--color-aurora-night-slate-900: oklch(11.8% 0.042 264.7);
--color-aurora-night-slate-950: oklch(11.2% 0.089 257.847);
--color-aurora-night-teal: #134e4a;
--color-aurora-night-teal-50: oklch(96.9% 0.012 168.245);
--color-aurora-night-teal-100: oklch(92.8% 0.028 169.137);
--color-aurora-night-teal-200: oklch(85.6% 0.055 170.029);
--color-aurora-night-teal-300: oklch(75.4% 0.089 170.921);
--color-aurora-night-teal-400: oklch(62.8% 0.128 171.813);
--color-aurora-night-teal-500: oklch(51.2% 0.156 172.705);
--color-aurora-night-teal-600: oklch(41.5% 0.172 173.597);
--color-aurora-night-teal-700: oklch(33.8% 0.168 174.489);
--color-aurora-night-teal-800: oklch(27.6% 0.148 175.381);
--color-aurora-night-teal-900: oklch(29.5% 0.074 175.4);
--color-aurora-night-teal-950: oklch(18.9% 0.089 177.165);
--color-aurora-night-emerald: #065f46;
--color-aurora-night-emerald-50: oklch(97.1% 0.015 155.743);
--color-aurora-night-emerald-100: oklch(94.2% 0.035 156.652);
--color-aurora-night-emerald-200: oklch(88.9% 0.068 157.561);
--color-aurora-night-emerald-300: oklch(81.3% 0.112 158.47);
--color-aurora-night-emerald-400: oklch(71.8% 0.158 159.379);
--color-aurora-night-emerald-500: oklch(61.2% 0.189 160.288);
--color-aurora-night-emerald-600: oklch(51.8% 0.198 161.197);
--color-aurora-night-emerald-700: oklch(43.6% 0.185 162.106);
--color-aurora-night-emerald-800: oklch(36.8% 0.156 163.015);
--color-aurora-night-emerald-900: oklch(34.2% 0.118 163.2);
--color-aurora-night-emerald-950: oklch(22.1% 0.089 164.833);
}
@theme inline {
--color-background: var(--background);
--color-text: var(--text);
@@ -263,6 +187,9 @@
--color-aurora-mid: var(--background-mid);
--color-aurora-end: var(--background-end);
--color-progress-start: var(--progress-start);
--color-progress-end: var(--progress-end);
--color-primary: var(--primary);
--color-primary-text: var(--primary-text);
--color-primary-hover: var(--primary-hover);
@@ -355,9 +282,8 @@
html {
@apply scrollbar-thumb-rounded-full scrollbar-thin scrollbar-track-slate-100 scrollbar-thumb-slate-500/70 dark:scrollbar-track-slate-900 dark:scrollbar-thumb-slate-300/65;
@apply max-md:dark:bg-aurora-night-teal relative h-full font-sans max-md:bg-slate-100;
@apply md:before:pointer-events-none md:before:fixed md:before:inset-0 md:before:-z-10;
@apply md:before:from-aurora-start md:before:via-aurora-mid md:before:to-aurora-end md:before:bg-gradient-to-br;
@apply relative h-full font-sans md:before:pointer-events-none md:before:fixed md:before:inset-0 md:before:-z-10;
@apply md:before:from-aurora-start md:before:via-aurora-mid md:before:to-aurora-end max-md:bg-aurora-mid md:before:bg-gradient-to-br;
body {
@apply flex flex-col bg-transparent text-sm leading-relaxed font-normal text-slate-900 transition-all duration-300 dark:text-slate-100;
@@ -624,7 +550,7 @@
}
#syslog {
@apply bg-aurora-night-slate rounded-3xl border border-slate-400/25 p-6 text-slate-200 shadow-lg max-md:p-3 dark:border-gray-600/40 dark:bg-gray-900 dark:text-gray-100 dark:shadow-black/50;
@apply rounded-3xl border border-slate-400/25 bg-slate-900 p-6 text-slate-100 shadow-lg max-md:p-3 dark:border-slate-600/40;
}
}
@@ -729,7 +655,7 @@
@layer button {
button,
.btn {
@apply inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-2xl border px-3 py-1.5 text-sm font-medium antialiased transition-all duration-200 max-md:text-base max-md:font-medium;
@apply inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-2xl border px-3 py-1.5 text-sm font-medium antialiased shadow-sm transition-all duration-200 max-md:text-base max-md:font-medium;
&[disabled] {
@apply cursor-not-allowed opacity-40 dark:opacity-30;
}
@@ -783,7 +709,7 @@
input[type="password"],
.cbi-input-text,
.cbi-input {
@apply focus:border-aurora-sky focus:ring-aurora-sky/20 dark:focus:border-primary dark:focus:ring-primary/30 relative rounded-2xl border border-slate-300/70 bg-white px-3 py-1.5 text-sm font-normal text-slate-900 placeholder-slate-400 shadow-sm transition-all duration-200 focus:ring-2 focus:outline-none dark:border-slate-600/60 dark:bg-slate-900 dark:text-slate-100 dark:placeholder-slate-400;
@apply focus:border-primary focus:ring-primary/20 relative rounded-2xl border border-slate-300/70 bg-white px-3 py-1.5 text-sm font-normal text-slate-900 placeholder-slate-400 shadow-sm transition-all duration-200 focus:ring-2 focus:outline-none dark:border-slate-600/60 dark:bg-slate-900 dark:text-slate-100 dark:placeholder-slate-400;
.table.cbi-section-table & {
@apply w-full;
@@ -800,7 +726,7 @@
input[type="radio"],
input[type="checkbox"] {
@apply focus:before:border-aurora-sky focus:before:ring-aurora-sky/20 checked:before:border-aurora-sky checked:before:bg-aurora-sky dark:focus:before:border-primary dark:focus:before:ring-primary/30 dark:checked:before:border-aurora-teal-600 dark:checked:before:bg-aurora-teal-600 relative mr-3 inline-block h-4 w-4 cursor-pointer appearance-none before:absolute before:top-0 before:left-0 before:h-4 before:w-4 before:border before:border-slate-300/70 before:bg-white before:transition-all before:duration-200 after:absolute after:top-0.5 after:left-0.5 after:h-3 after:w-3 after:bg-white after:opacity-0 after:transition-opacity after:duration-200 checked:after:opacity-100 hover:before:border-slate-400/60 focus:before:ring-2 focus:before:outline-none disabled:cursor-not-allowed dark:before:border-slate-600/60 dark:before:bg-slate-900 dark:hover:before:border-slate-500/60;
@apply focus:before:border-primary focus:before:ring-primary/20 checked:before:border-primary checked:before:bg-primary relative mr-3 inline-block h-4 w-4 cursor-pointer appearance-none before:absolute before:top-0 before:left-0 before:h-4 before:w-4 before:border before:border-slate-300/70 before:bg-white before:transition-all before:duration-200 after:absolute after:top-0.5 after:left-0.5 after:h-3 after:w-3 after:bg-white after:opacity-0 after:transition-opacity after:duration-200 checked:after:opacity-100 hover:before:border-slate-400/60 focus:before:ring-2 focus:before:outline-none disabled:cursor-not-allowed dark:before:border-slate-600/60 dark:before:bg-slate-900 dark:hover:before:border-slate-500/60;
}
input[type="radio"] {
@@ -818,7 +744,7 @@
@layer textarea {
textarea {
@apply focus:border-aurora-sky focus:ring-aurora-sky/20 dark:focus:border-primary dark:focus:ring-primary/30 min-h-24 w-full resize-y rounded-2xl border border-slate-300/70 bg-white px-3 py-2 text-sm font-normal text-slate-900 placeholder-slate-400 shadow-sm transition-all duration-200 focus:ring-2 focus:outline-none dark:border-slate-600/60 dark:bg-slate-900 dark:text-slate-100 dark:placeholder-slate-400;
@apply focus:border-primary focus:ring-primary/20 min-h-24 w-full resize-y rounded-2xl border border-slate-300/70 bg-white px-3 py-2 text-sm font-normal text-slate-900 placeholder-slate-400 shadow-sm transition-all duration-200 focus:ring-2 focus:outline-none dark:border-slate-600/60 dark:bg-slate-900 dark:text-slate-100 dark:placeholder-slate-400;
&[disabled] {
@apply cursor-not-allowed opacity-40 dark:opacity-30;
}
@@ -827,7 +753,7 @@
@layer select {
select {
@apply focus:border-aurora-sky focus:ring-aurora-sky/20 dark:focus:border-primary dark:focus:ring-primary/30 appearance-none rounded-2xl border border-slate-300/70 bg-white px-3 py-1.5 pr-10 text-sm font-normal text-slate-900 shadow-sm transition-all duration-200 focus:ring-2 focus:outline-none dark:border-slate-600/60 dark:bg-slate-900 dark:text-slate-100;
@apply focus:border-primary focus:ring-primary/20 appearance-none rounded-2xl border border-slate-300/70 bg-white px-3 py-1.5 pr-10 text-sm font-normal text-slate-900 shadow-sm transition-all duration-200 focus:ring-2 focus:outline-none dark:border-slate-600/60 dark:bg-slate-900 dark:text-slate-100;
@apply bg-[url('@assets/icons/arrow-down.svg')] bg-[length:16px] bg-[right_0.75rem_center] bg-no-repeat dark:bg-[url('@assets/icons/arrow-down-dark.svg')];
&[disabled] {
@apply cursor-not-allowed opacity-40 dark:opacity-30;
@@ -891,7 +817,7 @@
@apply absolute left-0 z-60 w-fit min-w-full overflow-y-auto rounded-lg border border-slate-200/60 bg-white/95 shadow-xl backdrop-blur-sm dark:border-slate-600/40 dark:bg-slate-800/95;
& > li {
@apply hover:bg-aurora-sky/8 dark:hover:bg-aurora-teal-600/15 min-h-9 w-full cursor-pointer px-3 py-1.5 font-medium text-slate-600 dark:text-slate-300;
@apply hover:bg-primary/15 min-h-9 w-full cursor-pointer px-3 py-1.5 font-medium text-slate-600 dark:text-slate-300;
}
}
@@ -915,7 +841,7 @@
}
&[open] {
@apply border-aurora-sky ring-aurora-sky/20 dark:border-primary dark:ring-primary/30 relative ring-2;
@apply border-primary ring-primary/20 relative ring-2;
& > ul.dropdown {
@apply block w-auto max-w-none;
@@ -924,7 +850,7 @@
@apply flex;
&[selected] {
@apply bg-aurora-sky/20 text-aurora-sky-800 dark:bg-aurora-teal-600/25 dark:text-aurora-teal-200;
@apply bg-primary/20 text-primary-text;
}
&[unselectable] {
@@ -1064,10 +990,10 @@
@layer progress {
.cbi-progressbar {
@apply relative h-3.5 w-full cursor-help overflow-hidden rounded-full bg-slate-300 before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-2xl before:text-xs/normal before:whitespace-nowrap before:text-slate-900 before:shadow-lg before:content-[attr(title)] max-md:h-4 max-md:rounded-2xl max-md:before:text-xs max-md:before:leading-normal dark:bg-slate-700 before:dark:border-slate-600 before:dark:text-slate-100 dark:before:text-slate-100 before:dark:shadow-black/40;
@apply relative h-3.5 w-full cursor-help overflow-hidden rounded-full bg-slate-300 before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-2xl before:text-xs/normal before:whitespace-nowrap before:text-slate-900 before:content-[attr(title)] max-md:h-4 max-md:rounded-2xl max-md:before:text-xs max-md:before:leading-normal dark:bg-slate-700 before:dark:border-slate-600 before:dark:text-slate-100 dark:before:text-slate-100;
& > div {
@apply from-aurora-sky to-aurora-teal dark:from-aurora-night-emerald dark:to-aurora-teal-600 dark:text-shadow-aurora-night-slate-800 h-full bg-gradient-to-r transition-all duration-300;
@apply from-progress-start to-progress-end h-full bg-gradient-to-r transition-all duration-300;
}
}
}
@@ -1132,7 +1058,7 @@
}
&.ifacebadge-active {
@apply border-aurora-sky dark:border-primary font-semibold;
@apply border-primary font-semibold;
}
&.large {
@@ -1229,7 +1155,7 @@
}
&.active {
@apply border-aurora-sky bg-aurora-sky dark:border-aurora-teal-600 dark:bg-aurora-teal-600 text-white;
@apply border-primary bg-primary text-white;
}
}
@@ -1828,7 +1754,7 @@
}
&.drag-over {
@apply border-aurora-sky/60 bg-aurora-sky/5 ring-aurora-sky/30 dark:border-primary/60 dark:bg-aurora-teal-600/10 dark:ring-primary/30 scale-105 shadow-lg ring-2;
@apply border-primary/60 bg-primary/5 ring-primary/30 scale-105 shadow-lg ring-2;
}
& > span,

View File

@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
LUCI_DEPENDS:=+luci-base
PKG_VERSION:=0.8.6_beta
PKG_RELEASE:=20251128
PKG_VERSION:=0.8.7s_beta
PKG_RELEASE:=20251130
PKG_LICENSE:=Apache-2.0
LUCI_MINIFY_CSS:=

File diff suppressed because one or more lines are too long

View File

@@ -10,8 +10,8 @@ THEME_TITLE:=Kucat Theme
PKG_NAME:=luci-theme-$(THEME_NAME)
LUCI_TITLE:=Kucat Theme by sirpdboy
LUCI_DEPENDS:=+wget +curl +jsonfilter
PKG_VERSION:=3.1.4
PKG_RELEASE:=20251126
PKG_VERSION:=3.1.5
PKG_RELEASE:=20251130
define Package/luci-theme-$(THEME_NAME)/conffiles
/www/luci-static/resources/background/

View File

@@ -294,20 +294,43 @@ body {
.td,
.th {
line-height: normal;
display: table-cell;
font-size: var(--font-x);
color: var(--body-color);
text-align: center;
vertical-align: middle;
white-space: nowrap;
padding: .78rem
padding: .7rem
}
.th {
padding: .6rem
}
.tr.placeholder {
height: 2rem
height: 1.9rem
}
#cbi-firewall .tr[data-title]::before, #cbi-firewall .tr.cbi-section-table-titles.named::before {
font-weight: bold;
display: table-cell;
align-self: center;
flex: 1 1 5%;
padding: .5rem;
content: attr(data-title) "\20";
text-align: center;
vertical-align: middle;
white-space: normal;
word-wrap: break-word;
font-size: var(--font-z);
}
#cbi-firewall .tr.cbi-section-table-titles.named::before {
background-color: var(--menu-item-titlebg-color) !important;
}
.cbi-section-table-row {
text-align: center !important;
}
.tr.placeholder>.td {
position: absolute;
right: 0;
@@ -486,11 +509,6 @@ a:hover {
text-decoration: underline
}
em {
font-style: normal !important;
line-height: 1.5;
padding-left: 10px
}
code {
font-size: var(--font-x);
@@ -509,7 +527,7 @@ abbr {
hr {
opacity: .1;
border-color: #eee;
border-color: var(--input-boxcolor);
margin: 1rem 0
}
@@ -544,7 +562,7 @@ footer a {
#view {
display: flex;
flex-direction: column;
gap: 1rem;
/* gap: 1rem; */
min-width: inherit;
overflow-x: auto;
overflow-y: hidden
@@ -566,6 +584,13 @@ footer a {
padding: 1rem 3rem !important
}
.container .cbi-map>div:not(.cbi-button),
.container .cbi-map>fieldset.cbi-section:not(.cbi-button),
#view>#content_syslog,
#view>div>div:not(.cbi-button) {
padding: 0 1%;
}
.main {
position: relative;
top: 0;
@@ -610,7 +635,7 @@ footer a {
flex-direction: column
}
.main-right>#maincontent>.container {
#maincontent>.container {
flex-grow: 1;
display: flex;
flex-direction: column
@@ -1115,7 +1140,7 @@ li {
min-width: inherit;
overflow: unset;
border-radius: var(--radius1);
background-color: rgba(var(--primary-rgbbody), var(--primary-rgbm-ts));
background-color: rgba(var(--primary-rgbbody), 0.9);
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 var(--input-boxcolor);
padding: 1rem
}
@@ -1260,7 +1285,7 @@ select,
}
select option {
background-color: rgba(var(--primary-rgbbody), 1);
background-color: rgba(var(--primary-rgbbody), 0.95);
width: 100% !important;
overflow: hidden
}
@@ -1302,7 +1327,7 @@ input[type='radio'] {
appearance: none !important;
-webkit-appearance: none !important;
background-image: url('data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'-4 -4 8 8\'%3e%3ccircle r=\'3\' fill=\'%23ccc\'/%3e%3c/svg%3e');
background-color: rgba(var(--primary-rgbm), 0.2);
background-color: rgba(var(--primary-rgbm), 0.3);
box-shadow: inset 0 2px 1px rgba(255, 255, 255, .6);
border-radius: 70%;
cursor: pointer;
@@ -1419,11 +1444,11 @@ textarea,
width: auto;
min-width: 100%;
max-width: none;
max-height: 200px !important;
max-height: 170px !important;
box-shadow: 0 0 4px var(--inputborder-color);
border-radius: var(--radius2);
border: 0px solid var(--inputborder-color);
background-color: rgba(var(--primary-rgbbody), 1);
background-color: rgba(var(--primary-rgbbody), 0.95);
color: var(--inputtext-color);
margin-left: 0 !important;
margin-top: 0rem;
@@ -1551,12 +1576,23 @@ textarea,
color: var(--menu-hover-color);
border-right: 0.18751rem solid rgba(255, 255, 255, 0);
letter-spacing: 1px;
white-space: nowrap;
/*white-space: nowrap; */
margin-bottom: 0.5rem;
overflow-x: auto;
padding: 0.3rem 1rem
}
.container .cbi-tabmenu, .container #tabmenu {
margin-left: -2%!important;
}
ul.tabs, .tabmenu .tabs, #tabmenu .tabs, ul.cbi-tabmenu {
padding-left: 2.5%!important;
box-shadow: 0 1px 0px 0px var(--input-boxcolor), 0 0px 1px -1px var(--input-boxcolor);
transition: box-shadow .25s, -webkit-box-shadow .25s;
margin-bottom: 10px;
padding-top: 1rem;
padding-bottom: 0.3rem;
}
.tabs>li,
.cbi-tabmenu li {
display: inline-block;
@@ -1584,6 +1620,7 @@ textarea,
margin-bottom: 0
}
[data-tab-title] {
overflow: hidden;
height: 0;
@@ -2060,7 +2097,7 @@ body.modal-overlay-active #modal_overlay {
.notice {
color: var(--primary-title-color);
background-color: rgba(var(--primary-rgbbody), 1);
background-color: rgba(var(--primary-rgbbody), 0.95);
padding: 2rem 1rem
}
@@ -2085,9 +2122,8 @@ body.modal-overlay-active #modal_overlay {
.cbi-map {
display: flex;
flex-direction: column;
overflow-x: auto;
gap: 1rem
overflow-x: auto;
/* gap: 1rem */
}
.cbi-section,
@@ -2111,7 +2147,7 @@ body.modal-overlay-active #modal_overlay {
.cbi-section {
border: 0px solid rgba(0, 0, 0, 0);
box-shadow: 0px 1px 0px var(--input-boxcolor);
box-shadow: 0 1px 0px 0px var(--input-boxcolor), 0 0px 1px -1px var(--input-boxcolor);
padding: 0
}
@@ -2210,7 +2246,7 @@ table>thead>tr>th,
letter-spacing: 1px;
color: var(--body-color);
white-space: nowrap;
padding: 1.2rem 1.5rem
padding: 0.5rem;
}
table .tr>.td.cbi-value-field,
@@ -2220,9 +2256,12 @@ table .tr>.th.cbi-section-table-cell {
display: table-cell !important;
color: var(--body-color);
white-space: nowrap;
padding: 0.2rem
width: auto;
padding: 0.5rem
}
tr>td, tr>th, .tr>.td, .tr>.th, .cbi-section-table-row::before, #cbi-wireless>#wifi_assoclist_table>.tr:nth-child(2) {
padding: 0.7rem 1rem;
}
table .tr>.td.cbi-value-field>[id*="ifc-description"] {
text-align: center;
font-weight: 400 !important
@@ -2251,28 +2290,29 @@ td.cbi-value-field var,
.tr.table-titles>.th,
.tr.cbi-section-table-titles>.th {
font-size: var(--font-z);
background-color: var(--menu-item-titlebg-color) !important;
color: var(--title-color) !important;
border-top: 0 !important;
padding: 0.7rem 0.2rem !important
}
.tr.table-titles>.th,
.tr.cbi-section-table-titles>.th {
background-color: var(--menu-item-titlebg-color) !important;
}
.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell {
width: auto !important
}
.cbi-tabcontainer>.cbi-value:nth-of-type(4n+2),
.cbi-map>.cbi-section .cbi-value:nth-of-type(4n+2),
fieldset>table>tbody>tr:nth-of-type(4n+2),
.cbi-map>div>.cbi-section>.cbi-section-node>.cbi-value:nth-of-type(4n+1),
table>tbody>tr:nth-of-type(4n+2),
div>.table>.tr:nth-of-type(4n+2) {
background-color: rgba(var(--primary-rgbs), var(--primary-rgbs-ts));
background-image: var(--bgqs-image)
}
.cbi-map>div>.cbi-section>.cbi-section-node>.cbi-value:nth-of-type(4n+2),
.cbi-tabcontainer>.cbi-value:nth-of-type(4n),
.cbi-map>.cbi-section .cbi-value:nth-of-type(4n),
fieldset>table>tbody>tr:nth-of-type(4n),
table>tbody>tr:nth-of-type(4n),
div>.table>.tr:nth-of-type(4n) {
background-color: rgba(var(--primary-rgbm), var(--primary-rgbs-ts));
@@ -2281,17 +2321,16 @@ div>.table>.tr:nth-of-type(4n) {
.cbi-tabcontainer>.cbi-value:nth-of-type(4n+2):hover,
.cbi-map>.cbi-section .cbi-value:nth-of-type(4n+2):hover,
fieldset>table>tbody>tr:nth-of-type(4n+2):hover,
.cbi-map>div>.cbi-section>.cbi-section-node>.cbi-value:nth-of-type(2n+1):hover,
table>tbody>tr:nth-of-type(4n+2):hover,
div>.table>.tr:nth-of-type(4n+2):hover,
.cbi-tabcontainer>.cbi-value:nth-of-type(4n):hover,
.cbi-map>.cbi-section .cbi-value:nth-of-type(4n):hover,
fieldset>table>tbody>tr:nth-of-type(4n):hover,
.cbi-map>div>.cbi-section>.cbi-section-node>.cbi-value:nth-of-type(2n):hover,
table>tbody>tr:nth-of-type(4n):hover,
div>.table>.tr:nth-of-type(4n):hover,
.cbi-tabcontainer>.cbi-value:nth-of-type(2n+1):hover,
.cbi-map>.cbi-section .cbi-value:nth-of-type(2n+1):hover,
fieldset>table>tbody>tr:nth-of-type(2n+1):hover,
table>tbody>tr:nth-of-type(2n+1):hover,
div>.table>.tr:nth-of-type(2n+1):hover {
background-color: var(--body-hover-bgcolor)
@@ -2366,7 +2405,6 @@ div>.table>.tr:nth-of-type(2n+1):hover {
display: inline-block;
width: 100%;
line-height: 2.3rem;
padding: 0 0.8rem
}
.cbi-value:first-child {
@@ -2508,7 +2546,10 @@ td>.ifacebadge,
.nft-rules tr>td,
.nft-rules tr>th {
text-align: left !important;
padding: 0 0.8rem !important
}
.nft-rules tr>td{
line-height: 2rem;
}
.network-status-table {
@@ -2867,7 +2908,8 @@ body.lang_pl.node-main-login .cbi-value-title {
}
.node-main-login>.main .cbi-map {
padding: 1rem !important
padding: 1rem !important;
overflow: hidden;
}
.node-main-login>.main .container h2 {
@@ -3065,6 +3107,7 @@ body.lang_pl.node-main-login .cbi-value-title {
#view>div,
#view>table {
backdrop-filter: var(--ufilter);
/* width: 100%; */
-webkit-backdrop-filter: var(--ufilter)
}
@@ -3117,7 +3160,6 @@ body.lang_pl.node-main-login .cbi-value-title {
-webkit-overflow-scrolling: touch;
margin-bottom: 20px;
margin-top: 5px;
padding: 10px
}
[data-page^="admin-status-channel"] #view>div:first-child,
@@ -3184,6 +3226,7 @@ body.lang_pl.node-main-login .cbi-value-title {
[data-page^="admin-system-ttyd-ttyd"] #view>iframe {
border-radius: var(--radius2) !important;
padding: 0 1%;
min-height: 600px !important
}
@@ -3561,6 +3604,12 @@ pre {
padding: 0 0.8rem
}
.controls>*>.btn:not([aria-label$="page"]) {
flex-grow: initial !important;
margin-top: .25rem;
padding: 0.5rem 1.2rem;
}
[data-theme="dark"] .pdboy-light:before,
[data-theme="light"] .pdboy-dark:before {
color: #aaa !important
@@ -3660,8 +3709,7 @@ input,
display: initial
}
.cbi-dropdown[open]>ul.dropdown>li>input.create-item-input:first-child:last-child,
#cbi-firewall .cbi-value-field {
.cbi-dropdown[open]>ul.dropdown>li>input.create-item-input:first-child:last-child {
width: 100%
}
@@ -3852,9 +3900,6 @@ input,
text-align: left
}
.cbi-section {
padding-bottom: 0.5rem;
}
.cbi-tab-descr {
padding: 0 0.3rem
}
@@ -3873,8 +3918,7 @@ input,
table>tbody>tr>td,
table>tfoot>tr>td,
table>thead>tr>td {
font-size: var(--font-x);
color: var(--body-color);
padding: 1rem 0.5rem
}
@@ -3912,6 +3956,12 @@ input,
input[name="nslookup"] {
width: 100%
}
[data-page="admin-network-diagnostics"] .tr .td {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
}
}
@media only screen and (max-width: 768px) {
@@ -3950,7 +4000,7 @@ input,
.th {
line-height: 1;
margin: 0;
padding: 10px
padding: 0.5rem;
}
h2 {
@@ -4008,11 +4058,6 @@ input,
padding: 0
}
.cbi-input-textarea,
textarea {
width: 18rem !important;
min-width: 8rem
}
.modal.cbi-modal {
max-width: 100%;
@@ -4086,6 +4131,11 @@ input,
[data-page^="admin-network-dhcp"] [data-tab-active="true"]{
padding: 0 !important
}
/* bandix */
.device-card {
background-color: rgba(var(--primary-rgbbody), 0.3)!important
}
}
@media (max-width: 600px) {

View File

@@ -202,7 +202,7 @@ if (dayword == "1") {
--menu-item-titlebg-color: rgba(var(--primary-rgbm),0.32);--body-hover-bgcolor: rgba(255,255,255,0.05);--menu-hover-color: #f5f5f5f5;--menu-fontcolor: #bbb;--primarytextcolor: #bbb;--primary-title-color: #ccc;--menu-color: #ddd;--title-color: #ddd;--body-color: #bbb;
--primary-rgbbody:33,45,60;--inputtext-color: #ccc; --inputborder-color: rgba(255,255,255,0.2); --input-bgcolor: rgba(0,0,0,0.2); --input-boxcolor: rgba(255,255,255,0.22); --input-boxhovercolor: rgba(255,255,255,0.32); --input-checkcolor: rgba(255,255,255,0.7);--filter-color:invert(90%);
{% else %}
--menu-item-titlebg-color: rgba(var(--primary-rgbm),0.12);--body-hover-bgcolor: rgba(50,50,50,0.05);--menu-hover-color: #fff;--menu-fontcolor: #f5f5f5f5;--primarytextcolor: #505063;--primary-title-color: #4d4d5d;--menu-color: #eee;--title-color: #3c3c3c;--body-color: #424242;
--menu-item-titlebg-color: rgba(var(--primary-rgbm),0.22);--body-hover-bgcolor: rgba(50,50,50,0.05);--menu-hover-color: #fff;--menu-fontcolor: #f5f5f5f5;--primarytextcolor: #505063;--primary-title-color: #4d4d5d;--menu-color: #eee;--title-color: #3c3c3c;--body-color: #424242;
--primary-rgbbody:244,245,247;--inputtext-color: #383838; --inputborder-color: rgba(0,0,0,0.2); --input-bgcolor: rgba(255,255,255,0.2); --input-boxcolor: rgba(0,0,0,0.22); --input-boxhovercolor: rgba(0,0,0,0.32); --input-checkcolor: rgba(var(--primary-rgbm),1);--filter-color:invert(10%);
{% endif %}
{% if (bgqs == '0'): %}

View File

@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=bandix
PKG_VERSION:=0.9.0
PKG_RELEASE:=2
PKG_VERSION:=0.10.1
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=timsaya
@@ -13,7 +13,7 @@ include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-values.mk
# 二进制文件的文件名和URL
RUST_BANDIX_VERSION:=0.9.0
RUST_BANDIX_VERSION:=0.10.1
RUST_BINARY_FILENAME:=bandix-$(RUST_BANDIX_VERSION)-$(RUSTC_TARGET_ARCH).tar.gz