🍕 Sync 2025-11-24 00:10:28

This commit is contained in:
actions-user
2025-11-24 00:10:28 +08:00
parent 8afad5393e
commit 0c7c1404f7
19 changed files with 1758 additions and 257 deletions

View File

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

View File

@@ -8,6 +8,40 @@
// 暗色模式检测已改为使用 CSS 媒体查询 @media (prefers-color-scheme: dark)
// 检测主题类型:返回 'wide'(宽主题,如 Argon或 'narrow'(窄主题,如 Bootstrap
function getThemeType() {
// 获取 LuCI 主题设置
var mediaUrlBase = uci.get('luci', 'main', 'mediaurlbase');
if (!mediaUrlBase) {
// 如果无法获取,尝试从 DOM 中检测
var linkTags = document.querySelectorAll('link[rel="stylesheet"]');
for (var i = 0; i < linkTags.length; i++) {
var href = linkTags[i].getAttribute('href') || '';
if (href.toLowerCase().includes('argon')) {
return 'wide';
}
}
// 默认返回窄主题
return 'narrow';
}
var mediaUrlBaseLower = mediaUrlBase.toLowerCase();
// 宽主题关键词列表(可以根据需要扩展)
var wideThemeKeywords = ['argon', 'material', 'design', 'edge'];
// 检查是否是宽主题
for (var i = 0; i < wideThemeKeywords.length; i++) {
if (mediaUrlBaseLower.includes(wideThemeKeywords[i])) {
return 'wide';
}
}
// 默认是窄主题Bootstrap 等)
return 'narrow';
}
// 格式化时间戳
function formatTimestamp(timestamp) {
if (!timestamp) return _('Never Online');
@@ -97,6 +131,21 @@ return view.extend({
font-size: 0.875rem;
}
/* 只在宽模式下应用警告样式 */
.bandix-alert.wide-theme {
background-color: rgba(251, 191, 36, 0.1);
border: 1px solid rgba(251, 191, 36, 0.3);
color: #92400e;
}
@media (prefers-color-scheme: dark) {
.bandix-alert.wide-theme {
background-color: rgba(251, 191, 36, 0.15);
border-color: rgba(251, 191, 36, 0.4);
color: #fbbf24;
}
}
.bandix-alert-icon {
font-size: 0.875rem;
font-weight: 700;
@@ -116,6 +165,189 @@ return view.extend({
margin-top: 0;
}
/* 移动端统计卡片布局 */
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.stats-grid .cbi-section {
padding: 12px;
}
.stats-card-main-value {
font-size: 1.75rem;
}
/* 移动端设备列表卡片式布局 */
.bandix-table {
display: none; /* 移动端隐藏表格 */
}
.device-list-cards {
display: block;
}
.device-card {
background-color: var(--cbi-section-bg, #fff);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
.device-card {
background-color: var(--cbi-section-bg, rgba(30, 30, 30, 0.98));
border-color: rgba(255, 255, 255, 0.15);
}
}
.device-card-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
.device-card-header {
border-bottom-color: rgba(255, 255, 255, 0.15);
}
}
.device-card-name {
flex: 1;
min-width: 0;
}
.device-card-name .device-name {
font-weight: 600;
margin-bottom: 4px;
}
.device-card-name .device-ip {
font-size: 0.75rem;
opacity: 0.7;
}
.device-card-name .device-mac {
font-size: 0.7rem;
opacity: 0.6;
margin-top: 2px;
}
.device-card-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 12px;
}
.device-card-stat-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.device-card-stat-label {
font-size: 0.75rem;
opacity: 0.7;
font-weight: 500;
}
.device-card-stat-value {
font-size: 1.125rem;
font-weight: 700;
}
.device-card-tcp-details {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 12px;
padding: 12px;
background-color: rgba(0, 0, 0, 0.02);
border-radius: 6px;
}
@media (prefers-color-scheme: dark) {
.device-card-tcp-details {
background-color: rgba(255, 255, 255, 0.05);
}
}
.device-card-tcp-details-label {
font-size: 0.75rem;
opacity: 0.7;
font-weight: 500;
margin-bottom: 4px;
}
.device-card-tcp-status-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.875rem;
}
.device-card-tcp-status-label {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
color: white;
min-width: 45px;
text-align: center;
}
.device-card-tcp-status-label.established {
background-color: #10b981;
}
.device-card-tcp-status-label.time-wait {
background-color: #f59e0b;
}
.device-card-tcp-status-label.closed {
background-color: #6b7280;
}
.device-card-tcp-status-value {
font-weight: 600;
}
.device-card-total {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
.device-card-total {
border-top-color: rgba(255, 255, 255, 0.15);
}
}
}
/* PC端显示表格隐藏卡片 */
@media (min-width: 769px) {
.bandix-table {
display: table;
}
.device-list-cards {
display: none;
}
}
.bandix-connection-container > .cbi-section:first-of-type {
margin-top: 0;
@@ -326,11 +558,16 @@ return view.extend({
// 检查连接监控是否启用
if (!connectionEnabled) {
var alertDiv = E('div', { 'class': 'bandix-alert' }, [
E('div', {}, [
E('strong', {}, _('Connection Monitor Disabled')),
E('p', { 'style': 'margin: 4px 0 0 0;' },
_('Please enable connection monitoring in settings'))
var alertDiv = E('div', {
'class': 'bandix-alert' + (getThemeType() === 'wide' ? ' wide-theme' : '')
}, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
E('span', { 'style': 'font-size: 1rem;' }, '⚠'),
E('div', {}, [
E('strong', {}, _('Connection Monitor Disabled')),
E('p', { 'style': 'margin: 4px 0 0 0;' },
_('Please enable connection monitoring in settings'))
])
])
]);
container.appendChild(alertDiv);
@@ -348,8 +585,13 @@ return view.extend({
}
// 添加提示信息
var infoAlert = E('div', { 'class': 'bandix-alert' }, [
E('span', {}, _('List only shows LAN device connections, data may differ from total connections.'))
var infoAlert = E('div', {
'class': 'bandix-alert' + (getThemeType() === 'wide' ? ' wide-theme' : '')
}, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
E('span', { 'style': 'font-size: 1rem;' }, '⚠'),
E('span', {}, _('List only shows LAN device connections, data may differ from total connections.'))
])
]);
container.appendChild(infoAlert);
@@ -429,6 +671,7 @@ return view.extend({
return;
}
// 创建表格PC端
var table = E('table', { 'class': 'bandix-table' }, [
E('thead', {}, [
E('tr', {}, [
@@ -474,8 +717,60 @@ return view.extend({
}))
]);
// 创建卡片容器(移动端)
var cardsContainer = E('div', { 'class': 'device-list-cards' });
devices.forEach(function (device) {
var card = E('div', { 'class': 'device-card' }, [
// 卡片头部:设备信息
E('div', { 'class': 'device-card-header' }, [
E('div', { 'class': 'device-status online' }),
E('div', { 'class': 'device-card-name' }, [
E('div', { 'class': 'device-name' }, formatDeviceName(device)),
E('div', { 'class': 'device-ip' }, device.ip_address || '-'),
E('div', { 'class': 'device-mac' }, device.mac_address || '-')
])
]),
// 统计信息TCP 和 UDP
E('div', { 'class': 'device-card-stats' }, [
E('div', { 'class': 'device-card-stat-item' }, [
E('div', { 'class': 'device-card-stat-label' }, 'TCP'),
E('div', { 'class': 'device-card-stat-value' }, device.tcp_connections || 0)
]),
E('div', { 'class': 'device-card-stat-item' }, [
E('div', { 'class': 'device-card-stat-label' }, 'UDP'),
E('div', { 'class': 'device-card-stat-value' }, device.udp_connections || 0)
])
]),
// TCP 状态详情
E('div', { 'class': 'device-card-tcp-details' }, [
E('div', { 'class': 'device-card-tcp-details-label' }, _('TCP Status Details')),
E('div', { 'class': 'device-card-tcp-status-row' }, [
E('span', { 'class': 'device-card-tcp-status-label established' }, 'EST'),
E('span', { 'class': 'device-card-tcp-status-value' }, device.established_tcp || 0)
]),
E('div', { 'class': 'device-card-tcp-status-row' }, [
E('span', { 'class': 'device-card-tcp-status-label time-wait' }, 'WAIT'),
E('span', { 'class': 'device-card-tcp-status-value' }, device.time_wait_tcp || 0)
]),
E('div', { 'class': 'device-card-tcp-status-row' }, [
E('span', { 'class': 'device-card-tcp-status-label closed' }, 'CLOSE'),
E('span', { 'class': 'device-card-tcp-status-value' }, device.close_wait_tcp || 0)
])
]),
// 总连接数
E('div', { 'class': 'device-card-total' }, [
E('div', { 'style': 'font-size: 0.875rem; opacity: 0.7; font-weight: 500;' }, _('Total Connections')),
E('div', { 'style': 'font-size: 1.25rem; font-weight: 700;' }, device.total_connections || 0)
])
]);
cardsContainer.appendChild(card);
});
container.innerHTML = '';
container.appendChild(table);
container.appendChild(cardsContainer);
}
// 显示错误信息

View File

@@ -8,6 +8,40 @@
// 暗色模式检测已改为使用 CSS 媒体查询 @media (prefers-color-scheme: dark)
// 检测主题类型:返回 'wide'(宽主题,如 Argon或 'narrow'(窄主题,如 Bootstrap
function getThemeType() {
// 获取 LuCI 主题设置
var mediaUrlBase = uci.get('luci', 'main', 'mediaurlbase');
if (!mediaUrlBase) {
// 如果无法获取,尝试从 DOM 中检测
var linkTags = document.querySelectorAll('link[rel="stylesheet"]');
for (var i = 0; i < linkTags.length; i++) {
var href = linkTags[i].getAttribute('href') || '';
if (href.toLowerCase().includes('argon')) {
return 'wide';
}
}
// 默认返回窄主题
return 'narrow';
}
var mediaUrlBaseLower = mediaUrlBase.toLowerCase();
// 宽主题关键词列表(可以根据需要扩展)
var wideThemeKeywords = ['argon', 'material', 'design', 'edge'];
// 检查是否是宽主题
for (var i = 0; i < wideThemeKeywords.length; i++) {
if (mediaUrlBaseLower.includes(wideThemeKeywords[i])) {
return 'wide';
}
}
// 默认是窄主题Bootstrap 等)
return 'narrow';
}
function formatTimestamp(timestamp) {
if (!timestamp) return '-';
var date = new Date(timestamp);
@@ -88,7 +122,7 @@ function formatResponseResult(query) {
var callGetDnsQueries = rpc.declare({
object: 'luci.bandix',
method: 'getDnsQueries',
params: ['domain', 'device', 'is_query', 'dns_server', 'page', 'page_size'],
params: ['domain', 'device', 'is_query', 'dns_server', 'query_type', 'page', 'page_size'],
expect: {}
});
@@ -138,6 +172,21 @@ return view.extend({
font-size: 0.875rem;
}
/* 只在宽模式下应用警告样式 */
.bandix-alert.wide-theme {
background-color: rgba(251, 191, 36, 0.1);
border: 1px solid rgba(251, 191, 36, 0.3);
color: #92400e;
}
@media (prefers-color-scheme: dark) {
.bandix-alert.wide-theme {
background-color: rgba(251, 191, 36, 0.15);
border-color: rgba(251, 191, 36, 0.4);
color: #fbbf24;
}
}
.bandix-alert-icon {
font-size: 0.875rem;
font-weight: 700;
@@ -175,10 +224,18 @@ return view.extend({
padding: 6px 12px;
border-radius: 4px;
font-size: 0.875rem;
min-width: 150px;
min-width: 120px;
max-width: 200px;
width: 120px;
opacity: 1;
}
.filter-section .cbi-select {
min-width: 120px;
max-width: 200px;
width: 120px;
}
.bandix-table {
width: 100%;
font-size: 0.875rem;
@@ -258,6 +315,35 @@ return view.extend({
gap: 8px;
}
/* 刷新按钮加载状态 */
.refresh-btn-loading {
position: relative;
pointer-events: none;
opacity: 0.8;
}
.refresh-btn-loading::before {
content: '';
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid currentColor;
border-top-color: transparent;
border-right-color: transparent;
border-radius: 50%;
animation: refresh-btn-spin 0.6s linear infinite;
margin-right: 8px;
vertical-align: middle;
}
@keyframes refresh-btn-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-state {
text-align: center;
@@ -402,6 +488,183 @@ return view.extend({
font-weight: 600;
opacity: 0.9;
}
/* 移动端优化 */
@media (max-width: 768px) {
/* 移动端隐藏表格,显示卡片 */
.bandix-table {
display: none;
}
.dns-query-cards {
display: block;
}
/* 移动端卡片样式 */
.dns-query-card {
background-color: var(--cbi-section-bg, #fff);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
.dns-query-card {
background-color: var(--cbi-section-bg, rgba(30, 30, 30, 0.98));
border-color: rgba(255, 255, 255, 0.15);
}
}
.dns-query-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
.dns-query-card-header {
border-bottom-color: rgba(255, 255, 255, 0.15);
}
}
.dns-query-card-time {
font-size: 0.75rem;
opacity: 0.7;
font-weight: 500;
}
.dns-query-card-type {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
}
.dns-query-card-body {
display: flex;
flex-direction: column;
gap: 10px;
}
.dns-query-card-row {
display: flex;
flex-direction: column;
gap: 4px;
}
.dns-query-card-label {
font-size: 0.75rem;
opacity: 0.7;
font-weight: 500;
}
.dns-query-card-value {
font-size: 0.875rem;
font-weight: 600;
word-break: break-word;
}
.dns-query-card-domain {
font-size: 0.9375rem;
font-weight: 600;
color: #3b82f6;
word-break: break-word;
}
.dns-query-card-response-result {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 4px;
}
.dns-query-card-response-badge {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.7rem;
background-color: rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
.dns-query-card-response-badge {
background-color: rgba(255, 255, 255, 0.1);
}
}
/* 移动端过滤器优化 */
.filter-section {
flex-direction: column;
align-items: stretch;
padding: 12px;
gap: 10px;
}
.filter-group {
width: 100%;
flex-direction: column;
align-items: stretch;
gap: 6px;
}
.filter-label {
font-size: 0.8125rem;
}
.filter-input,
.filter-section .cbi-select {
width: 100%;
max-width: 100%;
min-width: 100%;
}
/* 移动端分页优化 */
.pagination {
flex-direction: column;
align-items: stretch;
gap: 12px;
padding: 12px;
}
.pagination-info {
text-align: center;
font-size: 0.8125rem;
}
.pagination-controls {
width: 100%;
justify-content: center;
flex-wrap: wrap;
}
.pagination-controls .cbi-select {
width: 100%;
margin-right: 0;
margin-bottom: 8px;
}
.pagination-controls button {
flex: 1;
min-width: 100px;
}
}
/* PC端显示表格隐藏卡片 */
@media (min-width: 769px) {
.bandix-table {
display: table;
}
.dns-query-cards {
display: none;
}
}
`);
document.head.appendChild(style);
@@ -413,11 +676,16 @@ return view.extend({
container.appendChild(header);
if (!dnsEnabled) {
var alertDiv = E('div', { 'class': 'bandix-alert' }, [
E('div', {}, [
E('strong', {}, _('DNS Monitoring Disabled')),
E('p', { 'style': 'margin: 4px 0 0 0;' },
_('Please enable DNS monitoring in settings'))
var alertDiv = E('div', {
'class': 'bandix-alert' + (getThemeType() === 'wide' ? ' wide-theme' : '')
}, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
E('span', { 'style': 'font-size: 1rem;' }, '⚠'),
E('div', {}, [
E('strong', {}, _('DNS Monitoring Disabled')),
E('p', { 'style': 'margin: 4px 0 0 0;' },
_('Please enable DNS monitoring in settings'))
])
])
]);
container.appendChild(alertDiv);
@@ -435,8 +703,13 @@ return view.extend({
}
// 添加提示信息
var infoAlert = E('div', { 'class': 'bandix-alert' }, [
E('span', {}, _('Does not include DoH and DoT'))
var infoAlert = E('div', {
'class': 'bandix-alert' + (getThemeType() === 'wide' ? ' wide-theme' : '')
}, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
E('span', { 'style': 'font-size: 1rem;' }, '⚠'),
E('span', {}, _('Does not include DoH and DoT'))
])
]);
container.appendChild(infoAlert);
@@ -463,16 +736,32 @@ return view.extend({
'type': 'text',
'class': 'filter-input',
'id': 'domain-filter',
'placeholder': _('Search Domain')
'placeholder': _('Domain')
})
]),
E('div', { 'class': 'filter-group' }, [
E('label', { 'class': 'filter-label' }, _('Query Type') + ':'),
E('select', { 'class': 'cbi-select', 'id': 'query-type-filter' }, [
E('option', { 'value': '' }, _('All')),
E('option', { 'value': 'A' }, 'A'),
E('option', { 'value': 'AAAA' }, 'AAAA'),
E('option', { 'value': 'CNAME' }, 'CNAME'),
E('option', { 'value': 'MX' }, 'MX'),
E('option', { 'value': 'TXT' }, 'TXT'),
E('option', { 'value': 'NS' }, 'NS'),
E('option', { 'value': 'PTR' }, 'PTR'),
E('option', { 'value': 'SOA' }, 'SOA'),
E('option', { 'value': 'HTTPS' }, 'HTTPS'),
E('option', { 'value': 'SVCB' }, 'SVCB')
])
]),
E('div', { 'class': 'filter-group' }, [
E('label', { 'class': 'filter-label' }, _('Device Filter') + ':'),
E('input', {
'type': 'text',
'class': 'filter-input',
'id': 'device-filter',
'placeholder': _('Search Device')
'placeholder': _('Device')
})
]),
E('div', { 'class': 'filter-group' }, [
@@ -481,7 +770,7 @@ return view.extend({
'type': 'text',
'class': 'filter-input',
'id': 'dns-server-filter',
'placeholder': _('Search DNS Server')
'placeholder': _('DNS Server')
})
]),
E('div', { 'class': 'filter-group', 'style': 'margin-left: auto;' }, [
@@ -505,7 +794,8 @@ return view.extend({
domain: '',
device: '',
is_query: '',
dns_server: ''
dns_server: '',
query_type: ''
};
@@ -556,6 +846,7 @@ return view.extend({
currentFilters.device,
currentFilters.is_query,
currentFilters.dns_server,
currentFilters.query_type,
currentPage,
pageSize
).then(function (result) {
@@ -602,17 +893,20 @@ return view.extend({
return;
}
// 移除旧的表格和分页
// 移除旧的表格、卡片和分页
var oldTable = container.querySelector('.bandix-table');
var oldCards = container.querySelector('.dns-query-cards');
var oldPagination = container.querySelector('.pagination');
var oldLoadingState = container.querySelector('.loading-state');
if (oldTable) oldTable.remove();
if (oldCards) oldCards.remove();
if (oldPagination) oldPagination.remove();
if (oldLoadingState) oldLoadingState.remove();
// 确保容器是相对定位(用于遮罩层)
container.style.position = 'relative';
// 创建表格PC端
var table = E('table', { 'class': 'bandix-table' }, [
E('thead', {}, [
E('tr', {}, [
@@ -666,6 +960,78 @@ return view.extend({
]);
}))
]);
// 创建移动端卡片容器
var cardsContainer = E('div', { 'class': 'dns-query-cards' });
// 为每个查询创建卡片
queries.forEach(function (query) {
var result = formatResponseResult(query);
var responseResultBadges = [];
if (result.display.length === 0) {
responseResultBadges.push(E('span', { 'class': 'dns-query-card-response-badge' }, '-'));
} else {
result.display.forEach(function (item) {
responseResultBadges.push(E('span', { 'class': 'dns-query-card-response-badge' }, item));
});
if (result.hasMore) {
responseResultBadges.push(E('span', {
'class': 'dns-query-card-response-badge',
'style': 'opacity: 0.7;'
}, '...'));
}
}
var card = E('div', { 'class': 'dns-query-card' }, [
// 卡片头部:时间和类型
E('div', { 'class': 'dns-query-card-header' }, [
E('div', { 'class': 'dns-query-card-time' }, formatTimestamp(query.timestamp)),
E('span', {
'class': 'query-badge ' + (query.is_query ? 'query' : 'response')
}, query.is_query ? _('Query') : _('Response'))
]),
// 卡片主体
E('div', { 'class': 'dns-query-card-body' }, [
// 域名
E('div', { 'class': 'dns-query-card-row' }, [
E('div', { 'class': 'dns-query-card-label' }, _('Domain')),
E('div', { 'class': 'dns-query-card-value dns-query-card-domain' }, query.domain || '-')
]),
// 查询类型
E('div', { 'class': 'dns-query-card-row' }, [
E('div', { 'class': 'dns-query-card-label' }, _('Query Type')),
E('div', { 'class': 'dns-query-card-value' }, query.query_type || '-')
]),
// 响应时间
E('div', { 'class': 'dns-query-card-row' }, [
E('div', { 'class': 'dns-query-card-label' }, _('Response Time')),
E('div', { 'class': 'dns-query-card-value' }, query.response_time_ms ? query.response_time_ms + ' ' + _('ms') : '-')
]),
// 设备
E('div', { 'class': 'dns-query-card-row' }, [
E('div', { 'class': 'dns-query-card-label' }, _('Device')),
E('div', { 'class': 'dns-query-card-value' }, formatDeviceName(query))
]),
// DNS服务器
E('div', { 'class': 'dns-query-card-row' }, [
E('div', { 'class': 'dns-query-card-label' }, _('DNS Server')),
E('div', { 'class': 'dns-query-card-value' }, formatDnsServer(query))
]),
// 响应结果
E('div', { 'class': 'dns-query-card-row' }, [
E('div', { 'class': 'dns-query-card-label' }, _('Response Result')),
E('div', {
'class': 'dns-query-card-value',
'title': result.full.length > 0 ? result.full.join('\n') : ''
}, [
E('div', { 'class': 'dns-query-card-response-result' }, responseResultBadges)
])
])
])
]);
cardsContainer.appendChild(card);
});
var pagination = E('div', { 'class': 'pagination' }, [
E('div', { 'class': 'pagination-info' },
@@ -696,6 +1062,7 @@ return view.extend({
]);
container.appendChild(table);
container.appendChild(cardsContainer);
container.appendChild(pagination);
// 绑定分页事件
@@ -943,9 +1310,10 @@ return view.extend({
var deviceFilter = document.getElementById('device-filter');
var dnsServerFilter = document.getElementById('dns-server-filter');
var typeFilter = document.getElementById('type-filter');
var queryTypeFilter = document.getElementById('query-type-filter');
var refreshBtn = document.getElementById('refresh-queries-btn');
if (domainFilter && deviceFilter && dnsServerFilter && typeFilter) {
if (domainFilter && deviceFilter && dnsServerFilter && typeFilter && queryTypeFilter) {
var searchTimer = null;
function performSearch() {
@@ -953,6 +1321,7 @@ return view.extend({
currentFilters.device = deviceFilter.value.trim();
currentFilters.dns_server = dnsServerFilter.value.trim();
currentFilters.is_query = typeFilter.value;
currentFilters.query_type = queryTypeFilter.value;
currentPage = 1;
updateQueries();
}
@@ -972,16 +1341,34 @@ return view.extend({
// 下拉框立即搜索(不需要防抖)
typeFilter.addEventListener('change', performSearch);
queryTypeFilter.addEventListener('change', performSearch);
// 刷新按钮
if (refreshBtn) {
var originalBtnText = refreshBtn.textContent || refreshBtn.innerText || _('Refresh');
refreshBtn.addEventListener('click', function () {
// 保存原始按钮文本(如果还没有保存)
if (!originalBtnText) {
originalBtnText = refreshBtn.textContent || refreshBtn.innerText || _('Refresh');
}
// 设置按钮为加载状态
refreshBtn.classList.add('refresh-btn-loading');
refreshBtn.textContent = _('Loading...');
refreshBtn.disabled = true;
// 同时刷新统计数据和查询记录
updateStats();
var container = document.getElementById('dns-queries-container');
if (!container) {
updateQueries();
updateQueries().finally(function() {
// 恢复按钮状态
refreshBtn.classList.remove('refresh-btn-loading');
refreshBtn.textContent = originalBtnText;
refreshBtn.disabled = false;
});
return;
}
@@ -1006,7 +1393,12 @@ return view.extend({
}, 50);
// 刷新数据(蒙版会在 updateQueries 中自动移除)
updateQueries();
updateQueries().finally(function() {
// 恢复按钮状态
refreshBtn.classList.remove('refresh-btn-loading');
refreshBtn.textContent = originalBtnText;
refreshBtn.disabled = false;
});
});
}
}
@@ -1051,6 +1443,12 @@ return view.extend({
card.style.backgroundColor = bgColor;
});
// 应用到 DNS 查询卡片(移动端)
var dnsQueryCards = document.querySelectorAll('.dns-query-card');
dnsQueryCards.forEach(function(card) {
card.style.backgroundColor = bgColor;
});
// 应用到加载状态和错误状态
var loadingStates = document.querySelectorAll('.loading-state');
loadingStates.forEach(function(el) {

View File

@@ -745,3 +745,9 @@ msgstr "Respuestas"
msgid "Average Response Time"
msgstr "Tiempo de Respuesta Promedio"
msgid "Show More"
msgstr "Mostrar más"
msgid "Show Less"
msgstr "Mostrar menos"

View File

@@ -744,4 +744,10 @@ msgid "Responses"
msgstr "Réponses"
msgid "Average Response Time"
msgstr "Temps de Réponse Moyen"
msgstr "Temps de Réponse Moyen"
msgid "Show More"
msgstr "Afficher plus"
msgid "Show Less"
msgstr "Afficher moins"

View File

@@ -745,3 +745,9 @@ msgstr "Respons"
msgid "Average Response Time"
msgstr "Waktu Respons Rata-rata"
msgid "Show More"
msgstr "Tampilkan lebih banyak"
msgid "Show Less"
msgstr "Tampilkan lebih sedikit"

View File

@@ -745,3 +745,9 @@ msgstr "レスポンス"
msgid "Average Response Time"
msgstr "平均応答時間"
msgid "Show More"
msgstr "もっと見る"
msgid "Show Less"
msgstr "閉じる"

View File

@@ -746,3 +746,9 @@ msgstr "Odpowiedzi"
msgid "Average Response Time"
msgstr "Średni Czas Odpowiedzi"
msgid "Show More"
msgstr "Pokaż więcej"
msgid "Show Less"
msgstr "Pokaż mniej"

View File

@@ -744,4 +744,10 @@ msgid "Responses"
msgstr "Ответы"
msgid "Average Response Time"
msgstr "Среднее Время Ответа"
msgstr "Среднее Время Ответа"
msgid "Show More"
msgstr "Показать больше"
msgid "Show Less"
msgstr "Показать меньше"

View File

@@ -745,3 +745,9 @@ msgstr "คำตอบ"
msgid "Average Response Time"
msgstr "เวลาเฉลี่ยในการตอบสนอง"
msgid "Show More"
msgstr "แสดงเพิ่มเติม"
msgid "Show Less"
msgstr "แสดงน้อยลง"

View File

@@ -744,4 +744,10 @@ msgid "Responses"
msgstr "响应"
msgid "Average Response Time"
msgstr "平均响应时间"
msgstr "平均响应时间"
msgid "Show More"
msgstr "显示更多"
msgid "Show Less"
msgstr "显示更少"

View File

@@ -744,4 +744,10 @@ msgid "Responses"
msgstr "響應"
msgid "Average Response Time"
msgstr "平均響應時間"
msgstr "平均響應時間"
msgid "Show More"
msgstr "顯示更多"
msgid "Show Less"
msgstr "顯示更少"

View File

@@ -240,13 +240,15 @@ get_dns_queries() {
local device="$2"
local is_query="$3"
local dns_server="$4"
local page="$5"
local page_size="$6"
local query_type="$5"
local page="$6"
local page_size="$7"
[ -n "$domain" ] && query_params="${query_params}domain=$(printf '%s' "$domain" | sed 's/ /%20/g')&"
[ -n "$device" ] && query_params="${query_params}device=$(printf '%s' "$device" | sed 's/ /%20/g')&"
[ -n "$is_query" ] && query_params="${query_params}is_query=$is_query&"
[ -n "$dns_server" ] && query_params="${query_params}dns_server=$(printf '%s' "$dns_server" | sed 's/ /%20/g')&"
[ -n "$query_type" ] && query_params="${query_params}query_type=$(printf '%s' "$query_type" | sed 's/ /%20/g')&"
[ -n "$page" ] && query_params="${query_params}page=$page&"
[ -n "$page_size" ] && query_params="${query_params}page_size=$page_size&"
@@ -320,6 +322,7 @@ case "$1" in
json_add_string "device"
json_add_string "is_query"
json_add_string "dns_server"
json_add_string "query_type"
json_add_int "page"
json_add_int "page_size"
json_close_object
@@ -450,15 +453,18 @@ case "$1" in
dns_server=$(echo "$input" | jsonfilter -e '$[3]' 2>/dev/null)
[ -z "$dns_server" ] && dns_server=$(echo "$input" | jsonfilter -e '$.dns_server' 2>/dev/null)
page=$(echo "$input" | jsonfilter -e '$[4]' 2>/dev/null)
query_type=$(echo "$input" | jsonfilter -e '$[4]' 2>/dev/null)
[ -z "$query_type" ] && query_type=$(echo "$input" | jsonfilter -e '$.query_type' 2>/dev/null)
page=$(echo "$input" | jsonfilter -e '$[5]' 2>/dev/null)
[ -z "$page" ] && page=$(echo "$input" | jsonfilter -e '$.page' 2>/dev/null)
page_size=$(echo "$input" | jsonfilter -e '$[5]' 2>/dev/null)
page_size=$(echo "$input" | jsonfilter -e '$[6]' 2>/dev/null)
[ -z "$page_size" ] && page_size=$(echo "$input" | jsonfilter -e '$.page_size' 2>/dev/null)
get_dns_queries "$domain" "$device" "$is_query" "$dns_server" "$page" "$page_size"
get_dns_queries "$domain" "$device" "$is_query" "$dns_server" "$query_type" "$page" "$page_size"
else
get_dns_queries "" "" "" "" "" ""
get_dns_queries "" "" "" "" "" "" ""
fi
;;
getDnsStats)

View File

@@ -16,52 +16,52 @@ o.rmempty = false
---- gfwlist URL
o = s:option(DynamicList, "gfwlist_url", translate("GFW domains(gfwlist) Update URL"))
o:depends("geo2rule", false)
o:value("https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
o:value("https://fastly.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
o:value("https://fastly.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
o.default = "https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt"
o:value("https://cdn.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
o:value("https://cdn.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
o:value("https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
o.default = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt"
----chnroute URL
o = s:option(DynamicList, "chnroute_url", translate("China IPs(chnroute) Update URL"))
o:depends("geo2rule", false)
o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("gaoyifan/china-operator-ip/china"))
o:value("https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("gaoyifan/china-operator-ip/china"))
o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang.CN"))
o:value("https://fastly.jsdelivr.net/gh/soffchen/GeoIP2-CN@release/CN-ip-cidr.txt", translate("soffchen/GeoIP2-CN"))
o:value("https://fastly.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us/GeoIP2-CN"))
o:value("https://fastly.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP_No_IPv6.txt", translate("ios_rule_script/ChinaMax_IP_No_IPv6"))
o:value("https://cdn.jsdelivr.net/gh/soffchen/GeoIP2-CN@release/CN-ip-cidr.txt", translate("soffchen/GeoIP2-CN"))
o:value("https://cdn.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us/GeoIP2-CN"))
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP_No_IPv6.txt", translate("ios_rule_script/ChinaMax_IP_No_IPv6"))
----chnroute6 URL
o = s:option(DynamicList, "chnroute6_url", translate("China IPv6s(chnroute6) Update URL"))
o:depends("geo2rule", false)
o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt", translate("gaoyifan/china-operator-ip/china6"))
o:value("https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt", translate("gaoyifan/china-operator-ip/china6"))
o:value("https://ispip.clang.cn/all_cn_ipv6.txt", translate("Clang.CN.IPv6"))
o:value("https://fastly.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP.txt", translate("ios_rule_script/ChinaMax_IP"))
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP.txt", translate("ios_rule_script/ChinaMax_IP"))
----chnlist URL
o = s:option(DynamicList, "chnlist_url", translate("China List(Chnlist) Update URL"))
o:depends("geo2rule", false)
o:value("https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf", translate("felixonmars/domains.china"))
o:value("https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf", translate("felixonmars/apple.china"))
o:value("https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf", translate("felixonmars/google.china"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/china-list.txt", translate("Loyalsoldier/china-list"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/apple-cn.txt", translate("Loyalsoldier/apple-cn"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/google-cn.txt", translate("Loyalsoldier/google-cn"))
o:value("https://fastly.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_Domain.txt", translate("ios_rule_script/ChinaMax_Domain"))
o:value("https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf", translate("felixonmars/domains.china"))
o:value("https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf", translate("felixonmars/apple.china"))
o:value("https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf", translate("felixonmars/google.china"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/china-list.txt", translate("Loyalsoldier/china-list"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/apple-cn.txt", translate("Loyalsoldier/apple-cn"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/google-cn.txt", translate("Loyalsoldier/google-cn"))
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_Domain.txt", translate("ios_rule_script/ChinaMax_Domain"))
if has_xray or has_singbox then
o = s:option(ListValue, "geoip_url", translate("GeoIP Update URL"))
o:value("https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat", translate("Loyalsoldier/geoip"))
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.dat", translate("MetaCubeX/geoip"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat", translate("Loyalsoldier/geoip (CDN)"))
o:value("https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", translate("MetaCubeX/geoip (CDN)"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat", translate("Loyalsoldier/geoip (CDN)"))
o:value("https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", translate("MetaCubeX/geoip (CDN)"))
o.default = "https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat"
o = s:option(ListValue, "geosite_url", translate("Geosite Update URL"))
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", translate("Loyalsoldier/geosite"))
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geosite.dat", translate("MetaCubeX/geosite"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat", translate("Loyalsoldier/geosite (CDN)"))
o:value("https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", translate("MetaCubeX/geosite (CDN)"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat", translate("Loyalsoldier/geosite (CDN)"))
o:value("https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", translate("MetaCubeX/geosite (CDN)"))
o.default = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
o = s:option(Value, "v2ray_location_asset", translate("Location of Geo rule files"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))

View File

@@ -226,9 +226,13 @@ function url(...)
return require "luci.dispatcher".build_url(url)
end
function trim(text)
if not text or text == "" then return "" end
return text:match("^%s*(.-)%s*$")
function trim(s)
local len = #s
local i, j = 1, len
while i <= len and s:byte(i) <= 32 do i = i + 1 end
while j >= i and s:byte(j) <= 32 do j = j - 1 end
if i > j then return "" end
return s:sub(i, j)
end
-- 分割字符串
@@ -1258,20 +1262,49 @@ end
function get_std_domain(domain)
domain = trim(domain)
if domain == "" or domain:find("#") then return "" end
-- 删除首尾所有的 .
domain = domain:gsub("^[%.]+", ""):gsub("[%.]+$", "")
-- 如果 domain 包含 '*',则分割并删除包含 '*' 的部分及其前面的部分
if domain:find("%*") then
local parts = {}
for part in domain:gmatch("[^%.]+") do
table.insert(parts, part)
if domain == "" then return "" end
-- 含 # → ""
for i = 1, #domain do
if domain:byte(i) == 35 then return "" end -- '#'
end
local len = #domain
local si, ei = 1, len
-- 去前缀 '.'
while si <= len and domain:byte(si) == 46 do si = si + 1 end
-- 去后缀 '.'
while ei >= si and domain:byte(ei) == 46 do ei = ei - 1 end
if si > ei then return "" end
domain = domain:sub(si, ei)
len = #domain
-- 是否有 '*'
local star = false
for i = 1, len do
if domain:byte(i) == 42 then star = true break end
end
if not star then return domain end
-- 切割 label
local parts, pstart = {}, 1
for i = 1, len + 1 do
local b = (i <= len) and domain:byte(i) or 46 -- '.' 作为结束
if b == 46 then
parts[#parts + 1] = domain:sub(pstart, i - 1)
pstart = i + 1
end
for i = #parts, 1, -1 do
if parts[i]:find("%*") then
-- 删除包含 '*' 的部分及其前面的部分
return parts[i + 1] and parts[i + 1] .. "." .. table.concat(parts, ".", i + 2) or ""
end
-- 从右向左找含 '*' ,并删除包含 '*' 的部分及其左边部分
for i = #parts, 1, -1 do
local s = parts[i]
local has = false
for j = 1, #s do
if s:byte(j) == 42 then has = true break end
end
if has then
if i == #parts then return "" end
local out = parts[i + 1]
for k = i + 2, #parts do
out = out .. "." .. parts[k]
end
return out
end
end
return domain

View File

@@ -64,13 +64,13 @@ config global_rules
option gfwlist_update '1'
option geosite_update '0'
option geoip_update '0'
list gfwlist_url 'https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt'
list gfwlist_url 'https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt'
list chnroute_url 'https://ispip.clang.cn/all_cn.txt'
list chnroute_url 'https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt'
list chnroute_url 'https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt'
list chnroute6_url 'https://ispip.clang.cn/all_cn_ipv6.txt'
list chnroute6_url 'https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt'
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf'
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf'
list chnroute6_url 'https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt'
list chnlist_url 'https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf'
list chnlist_url 'https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf'
option v2ray_location_asset '/usr/share/v2ray/'
option geoip_url 'https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat'
option geosite_url 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat'

View File

@@ -21,12 +21,6 @@ local chnlist_update = "0"
local geoip_update = "0"
local geosite_update = "0"
-- match comments/title/whitelist/ip address/excluded_domain
local comment_pattern = "^[#!\\[@]+"
local ip_pattern = "^%d+%.%d+%.%d+%.%d+"
local ip4_ipset_pattern = "^%d+%.%d+%.%d+%.%d+[%/][%d]+$"
local ip6_ipset_pattern = ":-[%x]+%:+[%x]-[%/][%d]+$"
local domain_pattern = "([%w%-]+%.[%w%.%-]+)[%/%*]*"
local excluded_domain = {"apple.com","sina.cn","sina.com.cn","baidu.com","byr.cn","jlike.com","weibo.com","zhongsou.com","youdao.com","sogou.com","so.com","soso.com","aliyun.com","taobao.com","jd.com","qq.com","bing.com"}
local gfwlist_url = uci:get(name, "@global_rules[0]", "gfwlist_url") or {"https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt"}
@@ -87,7 +81,8 @@ end
-- curl
local function curl(url, file, valifile)
local args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "--max-time 300", "--speed-limit 51200 --speed-time 15"
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "--max-time 300", "--speed-limit 51200 --speed-time 15",
'-A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"'
}
if file then
args[#args + 1] = "-o " .. file
@@ -100,14 +95,43 @@ local function curl(url, file, valifile)
end
--check excluded domain
local excluded_map = {}
for _, d in ipairs(excluded_domain) do
excluded_map[d] = true
end
local function check_excluded_domain(value)
value = value and value:lower() or ""
for _, domain in ipairs(excluded_domain) do
local pattern = "^[a-z0-9-]+%.(" .. domain .. ")$"
if value:match(pattern) then
return true
end
if not value or value == "" then return false end
value = value:lower()
local eq_pos = value:find("=", 1, true)
if eq_pos then
value = value:sub(eq_pos + 1)
end
if value:sub(1,1) == "/" then
value = value:sub(2)
end
local slash_pos = value:find("/", 1, true)
local colon_pos = value:find(":", 1, true)
local cut_pos
if slash_pos and colon_pos then
cut_pos = (slash_pos < colon_pos) and slash_pos or colon_pos
else
cut_pos = slash_pos or colon_pos
end
if cut_pos then
value = value:sub(1, cut_pos - 1)
end
value = value:gsub("^%.*", ""):gsub("%.*$", "")
while value do
if excluded_map[value] then
return true
end
local dot_pos = value:find(".", 1, true)
if not dot_pos then
break
end
value = value:sub(dot_pos + 1)
end
return false
end
local function line_count(file_path)
@@ -118,6 +142,185 @@ local function line_count(file_path)
return num;
end
-- 替代 string.find 查找 "^[#!\\[@]+"
local function is_comment_line(s)
if not s or s == "" then return false end
local b = s:byte(1)
-- '#' = 35, '!' = 33, '\' = 92, '[' = 91, '@' = 64
if b == 35 or b == 33 or b == 92 or b == 91 or b == 64 then
return true
end
return false
end
-- IPv4 检测,替代 string.find "^%d+%.%d+%.%d+%.%d+"
-- IPv4 cidr检测替代 string.find "^%d+%.%d+%.%d+%.%d+[%/][%d]+$"
local function is_ipv4(s, check_cidr)
local dot = 0
local seg_start = 1
local len = #s
local mask_start = nil
local i = 1
while i <= len do
local b = s:byte(i)
if b >= 48 and b <= 57 then
-- 数字,继续
elseif b == 46 then -- "."
dot = dot + 1
if dot > 3 or i == seg_start then return false end
local seg = tonumber(s:sub(seg_start, i - 1))
if not seg or seg > 255 then return false end
seg_start = i + 1
elseif b == 47 then -- "/"
if not check_cidr then return false end
if dot ~= 3 or i == seg_start then return false end
local seg = tonumber(s:sub(seg_start, i - 1))
if not seg or seg > 255 then return false end
mask_start = i + 1
break
else
return false
end
i = i + 1
end
-- 如果没有 CIDR则检查最后一段即可
if not check_cidr or not mask_start then
if dot ~= 3 or seg_start > len then return false end
local seg = tonumber(s:sub(seg_start))
return seg and seg <= 255 or false
end
-- CIDR 掩码检查
if mask_start > len then return false end
local mask = tonumber(s:sub(mask_start))
return mask and mask >= 0 and mask <= 32 or false
end
local function is_ipv4_cidr(s)
return is_ipv4(s, true)
end
local function is_ipv6(s, check_cidr)
local first = s:byte(1)
local last = s:byte(#s)
if first == 91 and last == 93 then -- "[" and "]"
s = s:sub(2, -2)
end
local len = #s
local i = 1
local seg_len = 0
local segs = 0
local saw_dc = false -- 是否出现 "::"
local b
while i <= len do
b = s:byte(i)
-- CIDR 部分
if b == 47 then -- '/'
if not check_cidr then
return false
end
-- 处理 "/" 之前的段
if seg_len > 0 then segs = segs + 1 end
if (not saw_dc and segs ~= 8) or (saw_dc and segs > 8) then return false end
-- 解析掩码
i = i + 1
if i > len then return false end
local mask = 0
while i <= len do
b = s:byte(i)
if b < 48 or b > 57 then return false end
mask = mask * 10 + (b - 48)
if mask > 128 then return false end
i = i + 1
end
-- CIDR 解析成功
return true
end
-- 冒号处理(: 或 ::
if b == 58 then
local nextb = (i+1 <= len) and s:byte(i+1) or 0
-- "::"
if nextb == 58 then
if saw_dc then return false end
saw_dc = true
if seg_len > 0 then segs = segs + 1 end
seg_len = 0
i = i + 2
else
-- 普通 ":"
if seg_len == 0 then return false end
segs = segs + 1
seg_len = 0
i = i + 1
end
else
-- hex 数字
local is_hex =
(b >= 48 and b <= 57) or -- 0-9
(b >= 65 and b <= 70) or -- A-F
(b >= 97 and b <= 102) -- a-f
if not is_hex then return false end
seg_len = seg_len + 1
if seg_len > 4 then return false end
i = i + 1
end
end
if seg_len > 0 then segs = segs + 1 end
if not saw_dc then return segs == 8 end
return segs <= 8
end
-- IPv6 cidr检测替代 string.find ":-[%x]+%:+[%x]-[%/][%d]+$"
local function is_ipv6_cidr(s)
return is_ipv6(s, true)
end
-- 检测是否含有冒号,替代 string.find(line, ":")
local function has_colon(s)
for i = 1, #s do
if s:byte(i) == 58 then -- ':'
return true
end
end
return false
end
-- 域名提取,替代 string.match "([%w%-]+%.[%w%.%-]+)[%/%*]*"
local function extract_domain(s)
if not s or s == "" then return nil end
local len = #s
local start = nil
local last_dot = nil
for i = 1, len do
local b = s:byte(i)
-- 允许的域名字符a-zA-Z0-9.-
if (b >= 48 and b <= 57) or (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or b == 45 or b == 46 then
if not start then start = i end
if b == 46 then last_dot = i end
else
if start then
if last_dot and last_dot > start then
local domain = s:sub(start, i - 1)
while domain:byte(1) == 46 do
domain = domain:sub(2)
end
return domain
else
start = nil
last_dot = nil
end
end
end
end
if start and last_dot and last_dot > start then
local domain = s:sub(start)
while domain:byte(1) == 46 do
domain = domain:sub(2)
end
return domain
end
return nil
end
local function non_file_check(file_path, vali_file)
if fs.readfile(file_path, 10) then
local size_str = sys.exec("grep -i 'Content-Length' " .. vali_file .. " | tail -n1 | sed 's/[^0-9]//g'")
@@ -201,7 +404,6 @@ local function fetch_rule(rule_name,rule_type,url,exclude_domain)
if sret_tmp == 200 then
if rule_name == "gfwlist" and geo2rule == "0" then
local domains = {}
local gfwlist = io.open(download_file_tmp..k, "r")
local decode = api.base64Decode(gfwlist:read("*all"))
gfwlist:close()
@@ -214,8 +416,8 @@ local function fetch_rule(rule_name,rule_type,url,exclude_domain)
if rule_type == "domain" and exclude_domain == true then
for line in io.lines(download_file_tmp..k) do
line = line:gsub("full:", "")
if not (string.find(line, comment_pattern) or string.find(line, ip_pattern) or check_excluded_domain(line) or string.find(line, ":")) then
local match = string.match(line, domain_pattern)
if not (is_comment_line(line) or is_ipv4(line) or check_excluded_domain(line) or has_colon(line)) then
local match = extract_domain(line)
if match then
domains[match] = true
end
@@ -225,8 +427,8 @@ local function fetch_rule(rule_name,rule_type,url,exclude_domain)
elseif rule_type == "domain" then
for line in io.lines(download_file_tmp..k) do
line = line:gsub("full:", "")
if not (string.find(line, comment_pattern) or string.find(line, ip_pattern) or string.find(line, ":")) then
local match = string.match(line, domain_pattern)
if not (is_comment_line(line) or is_ipv4(line) or has_colon(line)) then
local match = extract_domain(line)
if match then
domains[match] = true
end
@@ -235,22 +437,35 @@ local function fetch_rule(rule_name,rule_type,url,exclude_domain)
elseif rule_type == "ip4" then
local out = io.open(unsort_file_tmp, "a")
local function is_0dot(s) -- "^0%..*"
return s and s:byte(1)==48 and s:byte(2)==46
end
for line in io.lines(download_file_tmp..k) do
if string.match(line, ip4_ipset_pattern) and not string.match(line, "^0%..*") then
out:write(string.format("%s\n", line))
if is_ipv4_cidr(line) and not is_0dot(line) then
out:write(line .. "\n")
end
end
out:close()
elseif rule_type == "ip6" then
local out = io.open(unsort_file_tmp, "a")
local function is_double_colon_cidr(s) -- "^::(/%d+)?$"
if not s or s:byte(1)~=58 or s:byte(2)~=58 then return false end
local l = #s
if l==2 then return true end
if l==3 or s:byte(3)~=47 then return false end
for i=4,l do
local b=s:byte(i)
if b<48 or b>57 then return false end
end
return true
end
for line in io.lines(download_file_tmp..k) do
if string.match(line, ip6_ipset_pattern) and not string.match(line, "^::(/%d+)?$") then
out:write(string.format("%s\n", line))
if is_ipv6_cidr(line) and not is_double_colon_cidr(line) then
out:write(line .. "\n")
end
end
out:close()
end
else
sret = 0
@@ -268,7 +483,7 @@ local function fetch_rule(rule_name,rule_type,url,exclude_domain)
end
out:close()
end
sys.call("LC_ALL=C sort -u " .. unsort_file_tmp .. " > " .. file_tmp)
os.execute("LC_ALL=C /bin/busybox sort -u " .. unsort_file_tmp .. " > " .. file_tmp)
os.remove(unsort_file_tmp)
local old_md5 = sys.exec("echo -n $(md5sum " .. rule_path .. "/" ..rule_name.. " | awk '{print $1}')"):gsub("\n", "")
@@ -290,10 +505,10 @@ local function fetch_rule(rule_name,rule_type,url,exclude_domain)
end
gen_cache(set_name, "ipv6_addr", file_tmp, output_file)
end
sys.exec(string.format('mv -f %s %s', output_file, rule_path .. "/" ..rule_name.. ".nft"))
sys.call(string.format('mv -f %s %s', output_file, rule_path .. "/" ..rule_name.. ".nft"))
os.remove(output_file)
end
sys.exec("mv -f "..file_tmp .. " " ..rule_path .. "/" ..rule_name)
sys.call("mv -f "..file_tmp .. " " ..rule_path .. "/" ..rule_name)
reboot = 1
log(rule_name.. " 更新成功,总规则数 " ..count.. " 条。")
else

View File

@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=v2ray-core
PKG_VERSION:=5.41.0
PKG_VERSION:=5.42.0
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/v2fly/v2ray-core/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=c67caa2d73f35a9562ecaeb5184733c943c9dafb47e8f1cfeacb892a9247e9b5
PKG_HASH:=24e2182bf77342165511150db014668723ac4a0c9e72269b0590b0be72875b49
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE