update 2026-08-30 11:09:43

This commit is contained in:
action
2026-08-30 11:09:43 +08:00
parent b09780b109
commit a8ecc43b8a
29 changed files with 1484 additions and 49 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

+3 -3
View File
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
PKG_SOURCE_DATE:=2026-08-19
PKG_SOURCE_VERSION:=58933a7cdbc90f70f2381f0c72e76d29be1d43a9
PKG_MIRROR_HASH:=b4fd70abf2fe8e3017ac3c4e025fd25cd4364dbe2470035b3daf58a129f9f54f
PKG_SOURCE_DATE:=2026-08-28
PKG_SOURCE_VERSION:=c1b663788ed1ecf2ffd53781c98ae2291ee002cf
PKG_MIRROR_HASH:=aa635f1ce4b0cf2b7776dc44ad28671eb0fb79276097addca503ce4708ccdc5d
PKG_LICENSE:=BSD 3-Clause
PKG_LICENSE_FILES:=LICENSE
+2 -2
View File
@@ -3,7 +3,7 @@
#ifndef OUT_LAST_COMMIT_POSITION_H_
#define OUT_LAST_COMMIT_POSITION_H_
#define LAST_COMMIT_POSITION_NUM 2528
#define LAST_COMMIT_POSITION "2528 (58933a7cdbc9)"
#define LAST_COMMIT_POSITION_NUM 2539
#define LAST_COMMIT_POSITION "2539 (c1b663788ed1)"
#endif // OUT_LAST_COMMIT_POSITION_H_
+3 -2
View File
@@ -1,12 +1,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-mosdns
PKG_VERSION:=1.7.9
PKG_VERSION:=1.7.10
PKG_RELEASE:=1
LUCI_TITLE:=LuCI Support for mosdns
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+mosdns +curl +v2ray-geoip +v2ray-geosite +geo2txt +ucode
LUCI_DEPENDS:=+mosdns +uclient-fetch +v2ray-geoip +v2ray-geosite +geo2txt +ucode
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
@@ -15,6 +15,7 @@ define Package/$(PKG_NAME)/conffiles
/etc/mosdns/cache.dump
/etc/mosdns/config_custom.yaml
/etc/mosdns/rule
/etc/mosdns/stats.dump
endef
include $(TOPDIR)/feeds/luci/luci.mk
@@ -163,7 +163,7 @@ return view.extend({
o.value('info', _('Info'));
o.value('warn', _('Warning'));
o.value('error', _('Error'));
o.default = 'info';
o.default = 'error';
o.depends('configfile', '/var/etc/mosdns.json');
o = s.taboption('basic', form.Value, 'log_file', _('Log File'));
@@ -426,6 +426,29 @@ return view.extend({
o.default = 52001;
o.depends('configfile', '/var/etc/mosdns.json');
o = s.taboption('api', form.Flag, 'stats_collector', _('Enable Stats Collector'));
o.rmempty = false;
o.default = o.enabled;
o.depends('configfile', '/var/etc/mosdns.json');
o = s.taboption('api', form.Value, 'stats_capacity', _('Ring Buffer Capacity'),
_('Query log ring buffer capacity (FIFO overwrite, default 2000, larger values consume more memory)'));
o.datatype = 'and(uinteger,min(1))';
o.default = 2000;
o.depends('stats_collector', '1');
o = s.taboption('api', form.Flag, 'stats_dump_file', _('Stats Dump'),
_('Save query statistics and logs locally and reload on next startup.'));
o.rmempty = false;
o.default = false;
o.depends('stats_collector', '1');
o = s.taboption('api', form.Value, 'stats_dump_interval',
_('Auto Save Stats Interval'));
o.datatype = 'and(uinteger,min(1))';
o.default = 600;
o.depends('stats_dump_file', '1');
o = s.taboption('api', form.Button, '_flush_cache', null,
_('Flushing DNS Cache will clear any IP addresses or DNS records from MosDNS cache.'));
o.title = '&#160;';
@@ -0,0 +1,722 @@
'use strict';
'require dom';
'require poll';
'require rpc';
'require ui';
'require view';
const callGetStats = rpc.declare({
object: 'luci.mosdns',
method: 'get_stats',
expect: { '': {} }
});
const callGetHistory = rpc.declare({
object: 'luci.mosdns',
method: 'get_history',
params: ['points'],
expect: { '': {} }
});
const callGetTop = rpc.declare({
object: 'luci.mosdns',
method: 'get_top',
params: ['limit'],
expect: { '': {} }
});
const callGetLogs = rpc.declare({
object: 'luci.mosdns',
method: 'get_logs',
params: ['limit', 'offset', 'search', 'filter'],
expect: { '': {} }
});
const callClearQueryLogs = rpc.declare({
object: 'luci.mosdns',
method: 'clear_query_logs',
expect: { '': {} }
});
let filterVal = 'all';
let searchVal = '';
let pageIdx = 0;
const PAGE_SIZE = 20;
let isUserPaused = false;
let nodeStats;
let nodeTop;
let nodeLogs;
let autoStatusBadge;
const cleanIP = ip => {
if (!ip) return '-';
return ip.replace(/^::ffff:/i, '');
};
const injectStyles = () => {
if (document.getElementById('mosdns-statistics-styles'))
return;
/* HTML Styles provided by DeepSeek Chat */
const css = [
'.mosdns-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 1.25rem; }',
'.mosdns-rankings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }',
'.mosdns-stat-card { background: var(--cbi-section-bg, #fff); border: 1px solid rgba(0,0,0,0.08); border-radius: 8px; padding: 1rem 1.2rem; box-shadow: 0 2px 6px rgba(0,0,0,0.03); display: flex; flex-direction: column; justify-content: space-between; position: relative; overflow: hidden; }',
'.mosdns-stat-card .title-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; font-size: 0.85rem; opacity: 0.75; font-weight: 500; }',
'.mosdns-stat-card .metric-val { font-size: 1.85rem; font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; }',
'.mosdns-stat-card .subtext { font-size: 0.8rem; opacity: 0.6; margin-top: 0.4rem; }',
'.mosdns-sparkline-wrap { margin-top: 0.5rem; height: 44px; position: relative; overflow: visible; display: flex; align-items: flex-end; }',
'.mosdns-sparkline { width: 100%; height: 100%; display: block; overflow: visible; }',
'.mosdns-sparkline-tooltip { position: absolute; pointer-events: none; z-index: 20; padding: 0.25rem 0.5rem; border-radius: 5px; background: var(--cbi-section-bg, #fff); border: 1px solid rgba(0,0,0,0.12); box-shadow: 0 3px 10px rgba(0,0,0,0.12); line-height: 1.25; text-align: center; white-space: nowrap; transition: opacity 0.15s ease; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }',
'.mosdns-rank-panel { background: var(--cbi-section-bg, #fff); border: 1px solid rgba(0,0,0,0.08); border-radius: 8px; padding: 1rem 1.1rem; box-shadow: 0 2px 6px rgba(0,0,0,0.03); }',
'.mosdns-rank-panel h4 { margin: 0 0 0.85rem 0; font-size: 0.95rem; font-weight: 600; display: flex; align-items: center; justify-content: space-between; }',
'.mosdns-rank-item { position: relative; overflow: hidden; border-radius: 6px; padding: 0.35rem 0.65rem; display: flex; justify-content: space-between; align-items: center; background: rgba(125,125,125,0.03); border: 1px solid rgba(125,125,125,0.08); margin-bottom: 0.35rem; }',
'.mosdns-rank-bar { position: absolute; left: 0; top: 0; bottom: 0; opacity: 0.15; pointer-events: none; transition: width .3s ease; }',
'.mosdns-badge { display: inline-block; padding: 0.15em 0.55em; font-size: 0.75rem; font-weight: 600; border-radius: 4px; line-height: 1.25; text-align: center; white-space: nowrap; box-sizing: border-box; }',
'.mosdns-status-badge { min-width: 68px; }',
'.badge-danger { background: rgba(239, 68, 68, 0.12); color: #dc2626; border: 1px solid rgba(239, 68, 68, 0.25); }',
'.badge-teal { background: rgba(16, 185, 129, 0.12); color: #059669; border: 1px solid rgba(16, 185, 129, 0.25); }',
'.badge-primary { background: rgba(59, 130, 246, 0.12); color: #2563eb; border: 1px solid rgba(59, 130, 246, 0.25); }',
'.badge-neutral { background: rgba(107, 114, 128, 0.12); color: #4b5563; border: 1px solid rgba(107, 114, 128, 0.25); }',
'.badge-qtype { font-family: monospace; font-size: 0.72rem; padding: 0.1em 0.4em; background: rgba(125,125,125,0.1); border-radius: 3px; opacity: 0.8; margin-left: 0.4rem; }',
'.badge-pulse { animation: pulse 2s infinite; }',
'.dns-latency-fastest { color: #10b981; font-weight: 600; }',
'.dns-latency-fast { color: #059669; font-weight: 600; }',
'.dns-latency-normal { color: #3b82f6; font-weight: 600; }',
'.dns-latency-slow { color: #d97706; font-weight: 600; }',
'.dns-latency-slower { color: #ea580c; font-weight: 600; }',
'.dns-latency-timeout { color: #dc2626; font-weight: 600; }',
'.mosdns-table td { vertical-align: middle !important; padding: 0.45rem 0.6rem !important; }',
'.mosdns-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }',
'.mosdns-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.85rem; padding-bottom: 0.75rem; border-bottom: 1px solid rgba(125,125,125,0.15); flex-wrap: wrap; gap: 0.5rem; }',
'.mosdns-modal-domain { font-size: 1.05rem; font-weight: 700; word-break: break-all; display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }',
'.mosdns-modal-meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0.6rem; margin-bottom: 1rem; }',
'.mosdns-modal-meta-item { background: rgba(125,125,125,0.04); border: 1px solid rgba(125,125,125,0.08); border-radius: 6px; padding: 0.5rem 0.75rem; }',
'.mosdns-modal-meta-item .meta-label { font-size: 0.75rem; opacity: 0.6; margin-bottom: 0.2rem; font-weight: 600; }',
'.mosdns-modal-meta-item .meta-val { font-size: 0.85rem; font-weight: 600; }',
'.mosdns-modal-section-title { font-size: 0.9rem; font-weight: 700; margin: 0.85rem 0 0.45rem 0; display: flex; align-items: center; justify-content: space-between; }',
'.mosdns-answers-list { display: flex; flex-direction: column; gap: 0.35rem; max-height: 240px; overflow-y: auto; }',
'.mosdns-answer-row { display: flex; justify-content: space-between; align-items: center; background: rgba(125,125,125,0.04); border: 1px solid rgba(125,125,125,0.08); border-radius: 6px; padding: 0.4rem 0.65rem; gap: 0.5rem; font-size: 0.82rem; }',
'.mosdns-answer-data { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }',
'.mosdns-answer-ttl { font-size: 0.75rem; opacity: 0.65; white-space: nowrap; }',
'@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }',
'@media (prefers-color-scheme: dark) {',
' .mosdns-stat-card, .mosdns-rank-panel, .mosdns-modal-meta-item, .mosdns-answer-row { background: rgba(255,255,255,0.03); border-color: rgba(255,255,255,0.08); box-shadow: none; }',
' .mosdns-sparkline-tooltip { background: #1e242b; border-color: rgba(255,255,255,0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.5); }',
' .badge-danger { background: rgba(239, 68, 68, 0.2); color: #f87171; border-color: rgba(239, 68, 68, 0.35); }',
' .badge-teal { background: rgba(16, 185, 129, 0.2); color: #34d399; border-color: rgba(16, 185, 129, 0.35); }',
' .badge-primary { background: rgba(59, 130, 246, 0.2); color: #60a5fa; border-color: rgba(59, 130, 246, 0.35); }',
' .badge-neutral { background: rgba(156, 163, 175, 0.2); color: #9ca3af; border-color: rgba(156, 163, 175, 0.3); }',
' .dns-latency-fastest { color: #34d399; }',
' .dns-latency-normal { color: #60a5fa; }',
' .dns-latency-timeout { color: #f87171; }',
'}'
].join('\n');
document.head.appendChild(E('style', { id: 'mosdns-statistics-styles' }, css));
};
const debounce = (fn, delay = 300) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
const formatTimestamp = iso => {
if (!iso) return '-';
const d = new Date(iso);
return isNaN(d) ? iso : d.toTimeString().slice(0, 8);
};
const getLatencyClass = elapsedMs => {
const val = parseFloat(elapsedMs) || 0;
if (val < 5) return 'dns-latency-fastest';
if (val < 20) return 'dns-latency-fast';
if (val < 50) return 'dns-latency-normal';
if (val < 100) return 'dns-latency-slow';
if (val < 300) return 'dns-latency-slower';
return 'dns-latency-timeout';
};
const updateLiveStatusBadge = () => {
if (!autoStatusBadge) return;
if (pageIdx === 0 && !searchVal && !isUserPaused) {
dom.content(autoStatusBadge, [
E('span', { class: 'mosdns-badge badge-teal badge-pulse' }, _('● Live Auto-refresh'))
]);
} else {
dom.content(autoStatusBadge, [
E('span', { class: 'mosdns-badge badge-neutral' }, _('❚❚ Paused (Page %d)').format(pageIdx + 1))
]);
}
};
const createSparklineSVG = (dataItems, strokeColor, fillGradId, maxScale) => {
const width = 300;
const height = 44;
const padTop = 4;
const padBottom = 2;
const drawHeight = height - padTop - padBottom;
let items = (dataItems && dataItems.length > 0) ? dataItems : [];
if (!items.length) {
items = new Array(24).fill(0).map(() => ({ time: '', val: 0 }));
}
if (items.length < 2) {
items = [items[0] || { time: '', val: 0 }, items[0] || { time: '', val: 0 }];
}
const vals = items.map(i => i.val || 0);
const maxVal = maxScale || Math.max(...vals, 1);
const len = items.length;
const coords = items.map((item, idx) => {
const x = (idx / (len - 1)) * width;
const y = height - padBottom - ((item.val || 0) / maxVal) * drawHeight;
return { x, y, time: item.time, val: item.val || 0 };
});
let pathD = 'M ' + coords[0].x.toFixed(1) + ',' + coords[0].y.toFixed(1);
for (let i = 0; i < coords.length - 1; i++) {
const p0 = coords[i === 0 ? 0 : i - 1];
const p1 = coords[i];
const p2 = coords[i + 1];
const p3 = coords[i + 2 < coords.length ? i + 2 : i + 1];
const cp1x = p1.x + (p2.x - p0.x) / 6;
const cp1y = p1.y + (p2.y - p0.y) / 6;
const cp2x = p2.x - (p3.x - p1.x) / 6;
const cp2y = p2.y - (p3.y - p1.y) / 6;
pathD += ' C ' + cp1x.toFixed(1) + ',' + cp1y.toFixed(1) + ' ' + cp2x.toFixed(1) + ',' + cp2y.toFixed(1) + ' ' + p2.x.toFixed(1) + ',' + p2.y.toFixed(1);
}
const areaD = pathD + ' L ' + width + ',' + height + ' L 0,' + height + ' Z';
const svgContainer = E('div', { class: 'mosdns-sparkline-wrap' });
svgContainer.innerHTML =
'<svg viewBox="0 0 ' + width + ' ' + height + '" class="mosdns-sparkline" preserveAspectRatio="none">' +
'<defs>' +
'<linearGradient id="' + fillGradId + '" x1="0" y1="0" x2="0" y2="1">' +
'<stop offset="0%" stop-color="' + strokeColor + '" stop-opacity="0.30" />' +
'<stop offset="100%" stop-color="' + strokeColor + '" stop-opacity="0.02" />' +
'</linearGradient>' +
'</defs>' +
'<path d="' + areaD + '" fill="url(#' + fillGradId + ')" />' +
'<path d="' + pathD + '" fill="none" stroke="' + strokeColor + '" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />' +
'<g class="spark-hover-group" style="display: none;">' +
'<line class="spark-hover-line" x1="0" y1="' + padTop + '" x2="0" y2="' + (height - padBottom) + '" stroke="' + strokeColor + '" stroke-width="1.2" stroke-dasharray="2 2" opacity="0.6" />' +
'<circle class="spark-hover-dot" cx="0" cy="0" r="3.5" fill="' + strokeColor + '" stroke="#fff" stroke-width="1.5" />' +
'</g>' +
'<rect width="' + width + '" height="' + height + '" fill="transparent" class="spark-hover-hitbox" style="cursor: crosshair;" />' +
'</svg>' +
'<div class="mosdns-sparkline-tooltip" style="display: none;"></div>';
const hoverGroup = svgContainer.querySelector('.spark-hover-group');
const hoverLine = svgContainer.querySelector('.spark-hover-line');
const hoverDot = svgContainer.querySelector('.spark-hover-dot');
const hitbox = svgContainer.querySelector('.spark-hover-hitbox');
const tooltip = svgContainer.querySelector('.mosdns-sparkline-tooltip');
const onMove = e => {
const rect = svgContainer.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const ratio = Math.max(0, Math.min(1, mouseX / rect.width));
const idx = Math.round(ratio * (len - 1));
const coord = coords[idx];
if (!coord) return;
hoverLine.setAttribute('x1', coord.x);
hoverLine.setAttribute('x2', coord.x);
hoverDot.setAttribute('cx', coord.x);
hoverDot.setAttribute('cy', coord.y);
hoverGroup.style.display = 'block';
let timeStr = '-';
if (coord.time) {
const d = new Date(coord.time);
if (!isNaN(d)) {
const hh = String(d.getHours()).padStart(2, '0');
timeStr = hh + ':00';
} else {
timeStr = coord.time.slice(11, 16) || coord.time;
}
}
tooltip.innerHTML =
'<div style="font-weight: 700; color: ' + strokeColor + '; font-size: 0.82rem; line-height: 1.1;">' + coord.val.toLocaleString() + '</div>' +
'<div style="font-size: 0.72rem; opacity: 0.75; margin-top: 0.15rem;">' + timeStr + '</div>';
tooltip.style.display = 'block';
const tooltipX = (coord.x / width) * rect.width;
if (ratio > 0.65) {
tooltip.style.left = 'auto';
tooltip.style.right = (rect.width - tooltipX + 8) + 'px';
} else {
tooltip.style.left = (tooltipX + 8) + 'px';
tooltip.style.right = 'auto';
}
tooltip.style.top = '-6px';
};
const onLeave = () => {
hoverGroup.style.display = 'none';
tooltip.style.display = 'none';
};
hitbox.addEventListener('mousemove', onMove);
hitbox.addEventListener('mouseleave', onLeave);
return svgContainer;
};
const renderOverviewStats = (stats, historyData) => {
if (!stats || stats.error) {
return E('div', { class: 'alert-message warning' },
_('MosDNS API is unreachable. Please ensure MosDNS is running and stats_api plugin is enabled.'));
}
const {
total_queries: total = 0,
blocked_queries: blocked = 0,
cached_queries: cached = 0,
blocked_percentage: blocked_pct = 0,
cached_percentage: cached_pct = 0,
avg_latency_ms: avg_ms = 0
} = stats;
const points = historyData?.points || [];
const totalItems = points.map(p => ({ time: p.time, val: Number(p.total) || 0 }));
const blockedItems = points.map(p => ({ time: p.time, val: Number(p.blocked) || 0 }));
const cachedItems = points.map(p => ({ time: p.time, val: Number(p.cached) || 0 }));
const baseMax = Math.max(...totalItems.map(i => i.val), 1);
return E('div', { class: 'mosdns-grid' }, [
E('div', { class: 'mosdns-stat-card' }, [
E('div', {}, [
E('div', { class: 'title-row' }, [
E('span', {}, _('DNS Queries Total')),
E('span', { class: 'mosdns-badge badge-teal badge-pulse' }, _('● Live'))
]),
E('div', { class: 'metric-val' }, total.toLocaleString()),
E('div', { class: 'subtext' }, _('Avg Processing') + ': ' + avg_ms + ' ms')
]),
createSparklineSVG(totalItems, '#3b82f6', 'spark-grad-total', baseMax)
]),
E('div', { class: 'mosdns-stat-card' }, [
E('div', {}, [
E('div', { class: 'title-row' }, [
E('span', {}, _('Blocked by Filters')),
E('span', { class: 'mosdns-badge badge-danger' }, blocked_pct + '%')
]),
E('div', { class: 'metric-val', style: 'color: #dc2626;' }, blocked.toLocaleString())
]),
createSparklineSVG(blockedItems, '#dc2626', 'spark-grad-blocked', baseMax)
]),
E('div', { class: 'mosdns-stat-card' }, [
E('div', {}, [
E('div', { class: 'title-row' }, [
E('span', {}, _('Cached Queries')),
E('span', { class: 'mosdns-badge badge-teal' }, cached_pct + '%')
]),
E('div', { class: 'metric-val', style: 'color: #059669;' }, cached.toLocaleString())
]),
createSparklineSVG(cachedItems, '#059669', 'spark-grad-cached', baseMax)
]),
E('div', { class: 'mosdns-stat-card' }, [
E('div', {}, [
E('div', { class: 'title-row' }, [
E('span', {}, _('Average Processing Time')),
E('span', { class: 'mosdns-badge badge-primary' }, _('Latency'))
]),
E('div', { class: 'metric-val', style: 'color: #2563eb;' }, avg_ms + ' ms'),
E('div', { class: 'subtext' }, _('Per-query speed'))
])
])
]);
};
const renderTopRankings = topData => {
if (!topData || topData.error) return E('div', {});
const { top_blocked = [], top_domains = [], top_clients = [] } = topData;
const renderList = (items, key, color, isClient = false) => {
if (!items || !items.length) {
return E('div', { style: 'padding: 1.5rem; text-align: center; opacity: 0.5; font-size: 0.85rem;' }, _('No data available'));
}
const maxCount = Math.max(...items.map(i => i.count || 1));
return E('div', { style: 'display: flex; flex-direction: column;' },
items.map(item => {
let val = item[key] || '-';
if (isClient) val = cleanIP(val);
const cnt = item.count || 0;
const pct = Math.round((cnt / maxCount) * 100);
return E('div', { class: 'mosdns-rank-item' }, [
E('div', { class: 'mosdns-rank-bar', style: 'width: ' + pct + '%; background-color: ' + color + ';' }),
E('span', {
class: 'mosdns-mono',
style: 'font-size: 0.82rem; z-index: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 0.5rem;',
title: val
}, val),
E('span', { class: 'mosdns-badge badge-neutral mosdns-mono', style: 'z-index: 1;' }, cnt.toLocaleString())
]);
})
);
};
return E('div', { class: 'mosdns-rankings-grid' }, [
E('div', { class: 'mosdns-rank-panel' }, [
E('h4', { style: 'color: #2563eb;' }, [
E('span', {}, _('Top Queried Domains')),
E('span', { class: 'mosdns-badge badge-primary' }, top_domains.length)
]),
renderList(top_domains, 'domain', '#2563eb')
]),
E('div', { class: 'mosdns-rank-panel' }, [
E('h4', { style: 'color: #dc2626;' }, [
E('span', {}, _('Top Blocked Domains')),
E('span', { class: 'mosdns-badge badge-danger' }, top_blocked.length)
]),
renderList(top_blocked, 'domain', '#dc2626')
]),
E('div', { class: 'mosdns-rank-panel' }, [
E('h4', { style: 'color: #059669;' }, [
E('span', {}, _('Top Clients')),
E('span', { class: 'mosdns-badge badge-teal' }, top_clients.length)
]),
renderList(top_clients, 'client_ip', '#059669', true)
])
]);
};
const showLogDetailsModal = item => {
let statusBadge;
if (item.is_blocked) {
statusBadge = E('span', { class: 'mosdns-badge badge-danger mosdns-status-badge' }, 'BLOCKED');
} else if (item.is_cached) {
statusBadge = E('span', { class: 'mosdns-badge badge-teal mosdns-status-badge' }, 'CACHED');
} else if (item.status === 'NOERROR') {
statusBadge = E('span', { class: 'mosdns-badge badge-primary mosdns-status-badge' }, 'NOERROR');
} else {
statusBadge = E('span', { class: 'mosdns-badge badge-neutral mosdns-status-badge' }, item.status || 'NOERROR');
}
const answersCount = (item.answers && item.answers.length) || 0;
let answersContent;
if (answersCount > 0) {
answersContent = E('div', { class: 'mosdns-answers-list' },
item.answers.map(a => E('div', { class: 'mosdns-answer-row' }, [
E('div', { style: 'display: flex; align-items: center; gap: 0.5rem; overflow: hidden; flex: 1;' }, [
E('span', { class: 'badge-qtype', style: 'margin: 0;' }, a.type || 'A'),
E('span', { class: 'mosdns-mono mosdns-answer-data', title: a.data }, a.data)
]),
E('span', { class: 'mosdns-badge badge-neutral mosdns-mono mosdns-answer-ttl' }, 'TTL ' + a.ttl + 's')
]))
);
} else {
answersContent = E('div', {
style: 'text-align: center; padding: 1.25rem; background: rgba(125,125,125,0.03); border: 1px dashed rgba(125,125,125,0.15); border-radius: 6px; opacity: 0.6; font-size: 0.85rem;'
}, _('No DNS answer records returned.'));
}
const body = E('div', { style: 'padding: 0.25rem 0;' }, [
E('div', { class: 'mosdns-modal-header' }, [
E('div', { class: 'mosdns-modal-domain' }, [
E('span', { class: 'mosdns-mono' }, item.domain || '-'),
E('span', { class: 'badge-qtype' }, item.qtype || 'A')
]),
E('div', { style: 'display: flex; align-items: center; gap: 0.5rem;' }, [
statusBadge,
E('span', { class: 'mosdns-mono ' + getLatencyClass(item.elapsed_ms), style: 'font-size: 0.85rem;' }, item.elapsed_ms + ' ms')
])
]),
E('div', { class: 'mosdns-modal-meta-grid' }, [
E('div', { class: 'mosdns-modal-meta-item' }, [
E('div', { class: 'meta-label' }, _('Client IP')),
E('div', { class: 'meta-val mosdns-mono' }, cleanIP(item.client_ip))
]),
E('div', { class: 'mosdns-modal-meta-item' }, [
E('div', { class: 'meta-label' }, _('Time')),
E('div', { class: 'meta-val mosdns-mono' }, formatTimestamp(item.timestamp) + (item.timestamp ? ' (' + item.timestamp.slice(0, 10) + ')' : ''))
]),
E('div', { class: 'mosdns-modal-meta-item' }, [
E('div', { class: 'meta-label' }, _('Upstream')),
E('div', { class: 'meta-val mosdns-mono', style: 'word-break: break-all;' }, item.upstream || '-')
]),
E('div', { class: 'mosdns-modal-meta-item' }, [
E('div', { class: 'meta-label' }, _('Rule Hit')),
E('div', { class: 'meta-val mosdns-mono', style: 'word-break: break-all;' }, item.rule || '-')
])
]),
E('div', { class: 'mosdns-modal-section-title' }, [
E('span', {}, _('Answers')),
E('span', { class: 'mosdns-badge badge-neutral' }, answersCount)
]),
answersContent
]);
ui.showModal(_('Query Log Details'), [
body,
E('div', { class: 'right', style: 'margin-top: 1.25rem;' }, [
E('button', {
class: 'btn cbi-button cbi-button-action',
click: ui.hideModal
}, _('Close'))
])
]);
};
const renderLogsTable = logsData => {
const { total = 0, items = [] } = logsData || {};
const totalPages = Math.ceil(total / PAGE_SIZE) || 1;
const rows = items.map(item => {
let statusBadge;
if (item.is_blocked) {
statusBadge = E('span', { class: 'mosdns-badge badge-danger mosdns-status-badge' }, 'BLOCKED');
} else if (item.is_cached) {
statusBadge = E('span', { class: 'mosdns-badge badge-teal mosdns-status-badge' }, 'CACHED');
} else if (item.status === 'NOERROR') {
statusBadge = E('span', { class: 'mosdns-badge badge-primary mosdns-status-badge' }, 'NOERROR');
} else {
statusBadge = E('span', { class: 'mosdns-badge badge-neutral mosdns-status-badge' }, item.status || 'NOERROR');
}
const answersText = (item.answers && item.answers.length > 0)
? item.answers.map(a => a.data + ' (' + a.type + ')').join(', ')
: '-';
return E('tr', { class: 'tr' }, [
E('td', { class: 'td', style: 'font-size: 0.82rem; opacity: 0.7; white-space: nowrap;' }, formatTimestamp(item.timestamp)),
E('td', { class: 'td mosdns-mono', style: 'font-size: 0.82rem; white-space: nowrap;' }, cleanIP(item.client_ip)),
E('td', { class: 'td', style: 'max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', title: item.domain || '-' }, [
E('span', { class: 'mosdns-mono', style: 'font-weight: 600;' }, item.domain || '-'),
E('span', { class: 'badge-qtype' }, item.qtype || 'A')
]),
E('td', { class: 'td' }, statusBadge),
E('td', {
class: 'td mosdns-mono',
style: 'font-size: 0.82rem; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer;',
title: _('Click to view full details'),
click: () => showLogDetailsModal(item)
}, answersText),
E('td', { class: 'td mosdns-mono ' + getLatencyClass(item.elapsed_ms), style: 'text-align: right; font-size: 0.82rem;' }, item.elapsed_ms + ' ms')
]);
});
if (!rows.length) {
rows.push(E('tr', { class: 'tr' }, [
E('td', { class: 'td', colspan: 6, style: 'text-align: center; opacity: 0.5; padding: 2rem;' }, _('No query log entries found.'))
]));
}
return E('div', {}, [
E('table', { class: 'table cbi-section-table mosdns-table', style: 'margin-top: 0.25rem;' }, [
E('tr', { class: 'tr table-titles' }, [
E('th', { class: 'th', style: 'width: 85px;' }, _('Time')),
E('th', { class: 'th', style: 'width: 125px;' }, _('Client IP')),
E('th', { class: 'th' }, _('Domain & Record')),
E('th', { class: 'th', style: 'width: 90px;' }, _('Status')),
E('th', { class: 'th' }, _('Answers')),
E('th', { class: 'th', style: 'width: 90px; text-align: right;' }, _('Elapsed'))
]),
...rows
]),
E('div', { style: 'display: flex; justify-content: space-between; align-items: center; margin-top: 0.75rem;' }, [
E('span', { style: 'font-size: 0.85rem; opacity: 0.7;' }, _('Page %d / %d (%d entries)').format(pageIdx + 1, totalPages, total)),
E('div', { style: 'display: flex; gap: 0.5rem;' }, [
E('button', {
class: 'btn cbi-button cbi-button-action',
disabled: pageIdx === 0 ? 'disabled' : null,
click: () => {
if (pageIdx > 0) {
pageIdx--;
updateLiveStatusBadge();
refreshLogs();
}
}
}, _('Previous')),
E('button', {
class: 'btn cbi-button cbi-button-action',
disabled: (pageIdx + 1) >= totalPages ? 'disabled' : null,
click: () => {
if ((pageIdx + 1) < totalPages) {
pageIdx++;
updateLiveStatusBadge();
refreshLogs();
}
}
}, _('Next'))
])
])
]);
};
const pollScheduler = async () => {
try {
const promises = [
callGetStats(),
callGetTop(10),
callGetHistory(24)
];
const shouldRefreshLogs = (pageIdx === 0 && !searchVal && !isUserPaused);
if (shouldRefreshLogs) {
promises.push(callGetLogs(PAGE_SIZE, 0, '', filterVal));
}
const results = await Promise.all(promises);
dom.content(nodeStats, renderOverviewStats(results[0], results[2]));
dom.content(nodeTop, renderTopRankings(results[1]));
if (shouldRefreshLogs && results[3]) {
dom.content(nodeLogs, renderLogsTable(results[3]));
}
updateLiveStatusBadge();
} catch (e) {
}
};
const refreshLogs = async () => {
try {
const logs = await callGetLogs(PAGE_SIZE, pageIdx * PAGE_SIZE, searchVal, filterVal);
dom.content(nodeLogs, renderLogsTable(logs));
updateLiveStatusBadge();
} catch (e) {
ui.addNotification(null, E('p', [_('Failed to update query logs: '), e.message]), 'error');
}
};
return view.extend({
async load() {
return Promise.all([
L.resolveDefault(callGetStats(), {}),
L.resolveDefault(callGetTop(10), {}),
L.resolveDefault(callGetLogs(PAGE_SIZE, 0, searchVal, filterVal), {}),
L.resolveDefault(callGetHistory(24), {})
]);
},
render(data) {
injectStyles();
nodeStats = E('div', { id: 'overview-stats' });
nodeTop = E('div', { id: 'top-rankings' });
nodeLogs = E('div', { id: 'logs-table' });
autoStatusBadge = E('div', { style: 'display: inline-block;' });
dom.content(nodeStats, renderOverviewStats(data[0], data[3]));
dom.content(nodeTop, renderTopRankings(data[1]));
dom.content(nodeLogs, renderLogsTable(data[2]));
updateLiveStatusBadge();
const searchInput = E('input', {
type: 'text',
class: 'cbi-input-text',
placeholder: _('Search domain or client IP...'),
style: 'min-width: 220px;'
});
searchInput.addEventListener('input', debounce(() => {
searchVal = searchInput.value.trim();
pageIdx = 0;
updateLiveStatusBadge();
refreshLogs();
}, 300));
const filterSelect = E('select', { class: 'cbi-input-select' }, [
E('option', { value: 'all', selected: filterVal === 'all' ? 'selected' : null }, _('All Queries')),
E('option', { value: 'blocked', selected: filterVal === 'blocked' ? 'selected' : null }, _('Blocked Only')),
E('option', { value: 'cached', selected: filterVal === 'cached' ? 'selected' : null }, _('Cached Only'))
]);
filterSelect.addEventListener('change', () => {
filterVal = filterSelect.value;
pageIdx = 0;
updateLiveStatusBadge();
refreshLogs();
});
const resetPageBtn = E('button', {
class: 'cbi-button cbi-button-apply',
click: () => {
pageIdx = 0;
updateLiveStatusBadge();
refreshLogs();
}
}, _('First Page / Resume'));
const clearBtn = E('button', {
class: 'btn cbi-button cbi-button-remove',
style: 'margin-left: auto;'
}, _('Clear query logs'));
clearBtn.addEventListener('click', () => {
ui.showModal(_('Clear query logs'), [
E('p', {}, _('Are you sure you want to clear all real-time query logs and top rankings?')),
E('div', { class: 'right', style: 'margin-top: 1rem;' }, [
E('button', {
class: 'btn cbi-button cbi-button-neutral',
click: ui.hideModal
}, _('Cancel')),
' ',
E('button', {
class: 'btn cbi-button cbi-button-remove',
click: async () => {
ui.hideModal();
try {
const res = await callClearQueryLogs();
if (res?.success) {
ui.addNotification(null, E('p', _('Query logs cleared successfully.')), 'info');
pageIdx = 0;
await pollScheduler();
await refreshLogs();
} else {
ui.addNotification(null, E('p', [_('Failed to clear query logs: '), res?.error || '']), 'error');
}
} catch (e) {
ui.addNotification(null, E('p', e.message), 'error');
}
}
}, _('Clear'))
])
]);
});
const controlBar = E('div', { style: 'display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.75rem;' }, [
searchInput,
filterSelect,
resetPageBtn,
autoStatusBadge,
clearBtn
]);
poll.add(pollScheduler);
return E('div', { class: 'cbi-map' }, [
E('h2', { name: 'content' }, '%s - %s'.format(_('MosDNS'), _('Statistics'))),
nodeStats,
nodeTop,
E('div', { class: 'cbi-section' }, [
E('h3', {}, _('Real-time Query Logs')),
controlBar,
nodeLogs
])
]);
},
handleSave: null,
handleSaveApply: null,
handleReset: null
});
+224 -12
View File
@@ -74,24 +74,50 @@ msgstr ""
msgid "Aliyun Public DNS (DNS over QUIC)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:640
msgid "All Queries"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:68
msgid "Another update is already in progress."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:463
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:531
msgid "Answers"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:203
msgid "Apple domains optimization"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:667
msgid ""
"Are you sure you want to clear all real-time query logs and top rankings?"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:359
msgid "Auto Save Cache Interval"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:447
msgid "Auto Save Stats Interval"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:106
msgid ""
"Automatically update GeoIP and GeoSite databases as well as ad filtering "
"rules through scheduled tasks."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:336
msgid "Average Processing Time"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:306
msgid "Avg Processing"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:217
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:263
msgid "Baidu Public DNS (180.76.76.76)"
@@ -119,6 +145,14 @@ msgstr ""
msgid "Block PTR"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:641
msgid "Blocked Only"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:314
msgid "Blocked by Filters"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:255
msgid "Bootstrap DNS servers"
msgstr ""
@@ -137,6 +171,18 @@ msgstr ""
msgid "Cache Prefetching"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:642
msgid "Cached Only"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:325
msgid "Cached Queries"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:672
msgid "Cancel"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:150
msgid "Check And Update"
msgstr ""
@@ -163,10 +209,29 @@ msgstr ""
msgid "Cisco Public DNS (208.67.222.222)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:692
msgid "Clear"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:65
msgid "Clear logs"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:663
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:666
msgid "Clear query logs"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:511
msgid "Click to view full details"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:445
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:528
msgid "Client IP"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:475
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35
msgid "Close"
msgstr ""
@@ -202,7 +267,7 @@ msgstr ""
msgid "Config File"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:455
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:478
msgid "Configuration Editor"
msgstr ""
@@ -240,6 +305,10 @@ msgstr ""
msgid "DNS Forward"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:302
msgid "DNS Queries Total"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:271
msgid ""
"DNS query request concurrency, The number of upstream DNS servers that are "
@@ -280,6 +349,14 @@ msgstr ""
msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:529
msgid "Domain & Record"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:532
msgid "Elapsed"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:111
msgid "Enable Auto Database Update"
msgstr ""
@@ -296,6 +373,10 @@ msgstr ""
msgid "Enable EDNS client subnet"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:429
msgid "Enable Stats Collector"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:283
msgid ""
"Enable TCP/DoT RFC 7766 new Query Pipelining connection multiplexing mode"
@@ -310,11 +391,11 @@ msgstr ""
msgid "Enabled"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:482
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:505
msgid "Enter the GeoIP.dat category to be exported, Allow add multiple tags"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:477
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:500
msgid "Enter the GeoSite.dat category to be exported, Allow add multiple tags"
msgstr ""
@@ -354,8 +435,8 @@ msgstr ""
msgid "Every Wednesday"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:478
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:483
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:501
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:506
msgid "Export directory: /var/mosdns"
msgstr ""
@@ -363,11 +444,23 @@ msgstr ""
msgid "Failed to clean logs."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:686
msgid "Failed to clear query logs:"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:89
msgid "Failed to start update."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:432
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599
msgid "Failed to update query logs:"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:658
msgid "First Page / Resume"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:455
msgid "Flush DNS Cache"
msgstr ""
@@ -379,7 +472,7 @@ msgstr ""
msgid "Flushing DNS Cache Success."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:430
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:453
msgid ""
"Flushing DNS Cache will clear any IP addresses or DNS records from MosDNS "
"cache."
@@ -415,7 +508,7 @@ msgstr ""
msgid "GeoData Export"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:481
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:504
msgid "GeoIP Tags"
msgstr ""
@@ -423,7 +516,7 @@ msgstr ""
msgid "GeoIP Type"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:476
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:499
msgid "GeoSite Tags"
msgstr ""
@@ -496,6 +589,10 @@ msgid ""
"seconds)."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:337
msgid "Latency"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:322
msgid "Lazy Cache TTL"
msgstr ""
@@ -537,7 +634,7 @@ msgstr ""
msgid "Log Level"
msgstr ""
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:46
msgid "Logs"
msgstr ""
@@ -577,10 +674,17 @@ msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:33
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:106
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:70
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:708
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3
msgid "MosDNS"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:279
msgid ""
"MosDNS API is unreachable. Please ensure MosDNS is running and stats_api "
"plugin is enabled."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:107
msgid "MosDNS is a plugin-based DNS forwarder/traffic splitter."
msgstr ""
@@ -593,10 +697,34 @@ msgstr ""
msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:561
msgid "Next"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:428
msgid "No DNS answer records returned."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:353
msgid "No data available"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:27
msgid "No log data."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:520
msgid "No query log entries found."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538
msgid "Page %d / %d (%d entries)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:340
msgid "Per-query speed"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:301
msgid ""
"Please provide the IP address you use when accessing foreign websites. This "
@@ -628,6 +756,10 @@ msgstr ""
msgid "Prevent DNS Leaks"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:550
msgid "Previous"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:329
msgid ""
"Proactively refresh hot cache entries in the background before they expire."
@@ -643,10 +775,28 @@ msgstr ""
msgid "Quad9 Public DNS (9.9.9.9)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:469
msgid "Query Log Details"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:435
msgid ""
"Query log ring buffer capacity (FIFO overwrite, default 2000, larger values "
"consume more memory)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:681
msgid "Query logs cleared successfully."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:31
msgid "RUNNING"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:712
msgid "Real-time Query Logs"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23
msgid "Redirect"
msgstr ""
@@ -669,6 +819,14 @@ msgstr ""
msgid "Remote DNS server"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:434
msgid "Ring Buffer Capacity"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:457
msgid "Rule Hit"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:11
msgid "Rule Settings"
msgstr ""
@@ -677,10 +835,18 @@ msgstr ""
msgid "Rules"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:441
msgid "Save query statistics and logs locally and reload on next startup."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:353
msgid "Save the cache locally and reload the cache dump on the next startup"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:629
msgid "Search domain or client IP..."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:175
msgid "Set the maximum size of the log file (in MB)."
msgstr ""
@@ -689,6 +855,19 @@ msgstr ""
msgid "Starting update..."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:708
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38
msgid "Statistics"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:440
msgid "Stats Dump"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:530
msgid "Status"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:25
msgid "Streaming Media"
msgstr ""
@@ -730,13 +909,30 @@ msgid ""
"server)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:456
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:479
msgid ""
"This is the content of the file '/etc/mosdns/config_custom.yaml' from which "
"your MosDNS configuration will be generated. Only accepts configuration "
"content in yaml format."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:449
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:527
msgid "Time"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:386
msgid "Top Blocked Domains"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:393
msgid "Top Clients"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:379
msgid "Top Queried Domains"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:213
msgid "TrafficRoute Public DNS (180.184.1.1)"
msgstr ""
@@ -745,7 +941,7 @@ msgstr ""
msgid "TrafficRoute Public DNS (180.184.2.2)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:471
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:494
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:43
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:48
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:67
@@ -798,6 +994,10 @@ msgstr ""
msgid "Updating Database..."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:453
msgid "Upstream"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:164
msgid "Warning"
msgstr ""
@@ -832,3 +1032,15 @@ msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:143
msgid "https://gh-proxy.com"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:303
msgid "● Live"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:148
msgid "● Live Auto-refresh"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:152
msgid "❚❚ Paused (Page %d)"
msgstr ""
+234 -16
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-20 09:23+0800\n"
"PO-Revision-Date: 2026-05-15 14:09+0800\n"
"PO-Revision-Date: 2026-08-30 08:26+0800\n"
"Last-Translator: sbwml <admin@cooluc.com>\n"
"Language-Team: Chinese\n"
"Language: zh_Hans\n"
@@ -89,24 +89,50 @@ msgstr "阿里云公共 DNSDNS over HTTPS"
msgid "Aliyun Public DNS (DNS over QUIC)"
msgstr "阿里云公共 DNSDNS over QUIC"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:640
msgid "All Queries"
msgstr "所有查询"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:68
msgid "Another update is already in progress."
msgstr "另一个更新正在进行中。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:463
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:531
msgid "Answers"
msgstr "应答结果"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:203
msgid "Apple domains optimization"
msgstr "Apple 域名解析优化"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:667
msgid ""
"Are you sure you want to clear all real-time query logs and top rankings?"
msgstr "确定要清空所有实时查询日志和排行数据吗?"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:359
msgid "Auto Save Cache Interval"
msgstr "自动保存缓存间隔(秒)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:447
msgid "Auto Save Stats Interval"
msgstr "自动保存统计间隔"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:106
msgid ""
"Automatically update GeoIP and GeoSite databases as well as ad filtering "
"rules through scheduled tasks."
msgstr "通过定时任务自动更新 GeoIP 和 GeoSite 数据库以及广告过滤规则。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:336
msgid "Average Processing Time"
msgstr "平均延迟"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:306
msgid "Avg Processing"
msgstr "平均处理"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:217
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:263
msgid "Baidu Public DNS (180.76.76.76)"
@@ -136,6 +162,14 @@ msgstr "黑名单"
msgid "Block PTR"
msgstr "PTR 黑名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:641
msgid "Blocked Only"
msgstr "仅拦截"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:314
msgid "Blocked by Filters"
msgstr "拦截总数"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:255
msgid "Bootstrap DNS servers"
msgstr "Bootstrap DNS 服务器"
@@ -154,6 +188,18 @@ msgstr "自动保存缓存"
msgid "Cache Prefetching"
msgstr "缓存预取"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:642
msgid "Cached Only"
msgstr "仅缓存"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:325
msgid "Cached Queries"
msgstr "缓存命中"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:672
msgid "Cancel"
msgstr "取消"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:150
msgid "Check And Update"
msgstr "检查并更新"
@@ -180,10 +226,29 @@ msgstr "思科公共 DNS208.67.220.220"
msgid "Cisco Public DNS (208.67.222.222)"
msgstr "思科公共 DNS208.67.222.222"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:692
msgid "Clear"
msgstr "清空"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:65
msgid "Clear logs"
msgstr "清空日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:663
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:666
msgid "Clear query logs"
msgstr "清除查询日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:511
msgid "Click to view full details"
msgstr "点击查看完整详情"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:445
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:528
msgid "Client IP"
msgstr "客户端 IP"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:475
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35
msgid "Close"
msgstr "关闭"
@@ -219,7 +284,7 @@ msgstr "DNS 服务器并发数(默认 2"
msgid "Config File"
msgstr "配置文件"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:455
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:478
msgid "Configuration Editor"
msgstr "配置编辑器"
@@ -258,6 +323,10 @@ msgstr "DNS 缓存大小"
msgid "DNS Forward"
msgstr "DNS 转发"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:302
msgid "DNS Queries Total"
msgstr "DNS 查询总数"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:271
msgid ""
"DNS query request concurrency, The number of upstream DNS servers that are "
@@ -299,6 +368,14 @@ msgstr ""
msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)"
msgstr "DoH/TCP/DoT 连接复用空闲保持时间(默认 30 秒)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:529
msgid "Domain & Record"
msgstr "域名与类型"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:532
msgid "Elapsed"
msgstr "耗时"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:111
msgid "Enable Auto Database Update"
msgstr "启用自动更新"
@@ -315,6 +392,10 @@ msgstr "启用 DNS 缓存"
msgid "Enable EDNS client subnet"
msgstr "启用 EDNS 客户端子网"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:429
msgid "Enable Stats Collector"
msgstr "启用统计收集器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:283
msgid ""
"Enable TCP/DoT RFC 7766 new Query Pipelining connection multiplexing mode"
@@ -329,11 +410,11 @@ msgstr "启用此选项 fallback 策略会强制转发到远程 DNS"
msgid "Enabled"
msgstr "启用"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:482
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:505
msgid "Enter the GeoIP.dat category to be exported, Allow add multiple tags"
msgstr "输入需要导出的 GeoIP.dat 类别条目,允许添加多个标签"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:477
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:500
msgid "Enter the GeoSite.dat category to be exported, Allow add multiple tags"
msgstr "填写需要导出的 GeoSite.dat 类别条目,允许添加多个标签"
@@ -373,8 +454,8 @@ msgstr "每周二"
msgid "Every Wednesday"
msgstr "每周三"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:478
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:483
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:501
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:506
msgid "Export directory: /var/mosdns"
msgstr "导出目录:/var/mosdns"
@@ -382,11 +463,23 @@ msgstr "导出目录:/var/mosdns"
msgid "Failed to clean logs."
msgstr "清理日志失败:%s"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:686
msgid "Failed to clear query logs:"
msgstr "清除查询日志失败:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:89
msgid "Failed to start update."
msgstr "启动更新失败"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:432
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599
msgid "Failed to update query logs:"
msgstr "更新查询日志失败:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:658
msgid "First Page / Resume"
msgstr "重置并恢复"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:455
msgid "Flush DNS Cache"
msgstr "刷新 DNS 缓存"
@@ -398,7 +491,7 @@ msgstr "刷新 DNS 缓存失败,请检查 MosDNS 状态是否在运行中。"
msgid "Flushing DNS Cache Success."
msgstr "刷新 DNS 缓存成功"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:430
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:453
msgid ""
"Flushing DNS Cache will clear any IP addresses or DNS records from MosDNS "
"cache."
@@ -434,7 +527,7 @@ msgstr "全量:包含所有国家和私有 IP 地址。"
msgid "GeoData Export"
msgstr "GeoData 导出"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:481
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:504
msgid "GeoIP Tags"
msgstr "GeoIP 标签"
@@ -442,7 +535,7 @@ msgstr "GeoIP 标签"
msgid "GeoIP Type"
msgstr "GeoIP 类型"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:476
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:499
msgid "GeoSite Tags"
msgstr "GeoSite 标签"
@@ -475,7 +568,7 @@ msgstr "灰名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:22
msgid "Hosts"
msgstr ""
msgstr "Hosts 列表"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:300
msgid "IP Address"
@@ -488,6 +581,10 @@ msgid ""
"www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-"
"v6</a>"
msgstr ""
"IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" target=\"_blank"
"\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a href=\"https://"
"www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-"
"v6</a>"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190
msgid ""
@@ -515,6 +612,10 @@ msgid ""
"seconds)."
msgstr "后台线程扫描缓存以进行预取的时间间隔(单位:秒)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:337
msgid "Latency"
msgstr "延迟"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:322
msgid "Lazy Cache TTL"
msgstr "乐观缓存 TTL"
@@ -556,7 +657,7 @@ msgstr "日志文件大小"
msgid "Log Level"
msgstr "日志等级"
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:46
msgid "Logs"
msgstr "日志"
@@ -599,10 +700,19 @@ msgstr "修改 DNS 应答结果的最小 TTL 值 (秒),0 表示不修改"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:33
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:106
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:70
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:708
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3
msgid "MosDNS"
msgstr "MosDNS"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:279
msgid ""
"MosDNS API is unreachable. Please ensure MosDNS is running and stats_api "
"plugin is enabled."
msgstr ""
"MosDNS API 无法连接。请确保 MosDNS 正在运行且已启用统计收集器(stats_api)插"
"件。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:107
msgid "MosDNS is a plugin-based DNS forwarder/traffic splitter."
msgstr "MosDNS 是一个插件化的 DNS 转发/分流器。"
@@ -615,10 +725,34 @@ msgstr "未运行"
msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS"
msgstr "自定义 Netflix、Disney+、Hulu 以及 “流媒体” 规则列表的 DNS 服务器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:561
msgid "Next"
msgstr "下一页"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:428
msgid "No DNS answer records returned."
msgstr "无 DNS 应答记录"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:353
msgid "No data available"
msgstr "暂无数据"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:27
msgid "No log data."
msgstr "无日志数据。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:520
msgid "No query log entries found."
msgstr "未找到查询日志记录。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538
msgid "Page %d / %d (%d entries)"
msgstr "第 %d / %d 页(%d 条记录)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:340
msgid "Per-query speed"
msgstr "单次查询速度"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:301
msgid ""
"Please provide the IP address you use when accessing foreign websites. This "
@@ -652,6 +786,10 @@ msgstr "当剩余 TTL 小于此值(秒)时触发预取"
msgid "Prevent DNS Leaks"
msgstr "防止 DNS 泄漏"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:550
msgid "Previous"
msgstr "上一页"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:329
msgid ""
"Proactively refresh hot cache entries in the background before they expire."
@@ -667,10 +805,28 @@ msgstr "Quad9 公共 DNS149.112.112.112"
msgid "Quad9 Public DNS (9.9.9.9)"
msgstr "Quad9 公共 DNS9.9.9.9"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:469
msgid "Query Log Details"
msgstr "查询日志详情"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:435
msgid ""
"Query log ring buffer capacity (FIFO overwrite, default 2000, larger values "
"consume more memory)"
msgstr "查询日志环形缓冲区容量(FIFO 覆盖,默认 2000,数值越大占用内存越多)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:681
msgid "Query logs cleared successfully."
msgstr "查询日志清除成功。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:31
msgid "RUNNING"
msgstr "运行中"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:712
msgid "Real-time Query Logs"
msgstr "实时查询日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23
msgid "Redirect"
msgstr "重定向"
@@ -695,6 +851,14 @@ msgstr "远程 DNS 首选 IPv4"
msgid "Remote DNS server"
msgstr "远程 DNS 服务器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:434
msgid "Ring Buffer Capacity"
msgstr "环形缓冲区容量"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:457
msgid "Rule Hit"
msgstr "命中规则"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:11
msgid "Rule Settings"
msgstr "自定义规则列表"
@@ -703,17 +867,38 @@ msgstr "自定义规则列表"
msgid "Rules"
msgstr "规则列表"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:441
msgid "Save query statistics and logs locally and reload on next startup."
msgstr "保存统计数据与查询日志在本地,并在下次启动时重新加载"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:353
msgid "Save the cache locally and reload the cache dump on the next startup"
msgstr "保存缓存到本地文件,以供下次启动时重新载入使用"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:629
msgid "Search domain or client IP..."
msgstr "搜索域名或客户端 IP..."
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:175
msgid "Set the maximum size of the log file (in MB)."
msgstr "设置日志文件的最大容量(单位:MB)。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:28
msgid "Starting update..."
msgstr ""
msgstr "正在启动更新..."
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:708
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38
msgid "Statistics"
msgstr "统计"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:440
msgid "Stats Dump"
msgstr "自动保存统计"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:530
msgid "Status"
msgstr "状态"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:25
msgid "Streaming Media"
@@ -758,7 +943,7 @@ msgstr ""
"此功能通常在使用自建 DNS 服务器作为 远程 / 流媒体 DNS 上游时使用(需要上游服"
"务器的支持)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:456
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:479
msgid ""
"This is the content of the file '/etc/mosdns/config_custom.yaml' from which "
"your MosDNS configuration will be generated. Only accepts configuration "
@@ -767,6 +952,23 @@ msgstr ""
"这是文件 “/etc/mosdns/config_custom.yaml” 的内容,您的 MosDNS 配置将从此文件"
"生成。仅接受 yaml 格式的配置内容。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:449
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:527
msgid "Time"
msgstr "时间"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:386
msgid "Top Blocked Domains"
msgstr "拦截域名 (Top 10)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:393
msgid "Top Clients"
msgstr "活跃客户端 (Top 10)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:379
msgid "Top Queried Domains"
msgstr "查询域名 (Top 10)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:213
msgid "TrafficRoute Public DNS (180.184.1.1)"
msgstr "火山引擎公共 DNS180.184.1.1"
@@ -775,7 +977,7 @@ msgstr "火山引擎公共 DNS180.184.1.1"
msgid "TrafficRoute Public DNS (180.184.2.2)"
msgstr "火山引擎公共 DNS180.184.2.2"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:471
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:494
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:43
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:48
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:67
@@ -828,6 +1030,10 @@ msgstr "更新成功"
msgid "Updating Database..."
msgstr "更新数据库..."
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:453
msgid "Upstream"
msgstr "上游服务器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:164
msgid "Warning"
msgstr "警告"
@@ -865,4 +1071,16 @@ msgstr "信风公共 DNS114.114.115.115"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:143
msgid "https://gh-proxy.com"
msgstr ""
msgstr "https://gh-proxy.com"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:303
msgid "● Live"
msgstr "● 实时"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:148
msgid "● Live Auto-refresh"
msgstr "● 实时刷新"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:152
msgid "❚❚ Paused (Page %d)"
msgstr "❚❚ 暂停 (第 %d 页)"
+6 -1
View File
@@ -8,7 +8,7 @@ config mosdns 'config'
option geo_update_day_time '2'
option geoip_type 'geoip-only-cn-private'
option configfile '/var/etc/mosdns.json'
option log_level 'info'
option log_level 'error'
option log_file '/var/log/mosdns.log'
option log_size '1'
option cache '1'
@@ -30,4 +30,9 @@ config mosdns 'config'
option lazy_cache_ttl '86400'
option dump_file '0'
option reject_type65 '1'
option stats_collector '1'
option stats_capacity '2000'
option stats_dump_file '0'
option stats_dump_interval '600'
+24 -1
View File
@@ -34,7 +34,7 @@ get_config() {
config_get listen_address $1 listen_address "0.0.0.0"
config_get log_file $1 log_file "/var/log/mosdns.log"
config_get log_size $1 log_size "1"
config_get log_level $1 log_level "info"
config_get log_level $1 log_level "error"
config_get minimal_ttl $1 minimal_ttl 0
config_get maximum_ttl $1 maximum_ttl 0
config_get redirect $1 redirect 0
@@ -57,6 +57,10 @@ get_config() {
config_get cloudflare $1 cloudflare 0
config_get cloudflare_ip $1 cloudflare_ip ""
config_get reject_type65 $1 reject_type65 "0"
config_get stats_collector $1 stats_collector 1
config_get stats_capacity $1 stats_capacity 2000
config_get stats_dump_file $1 stats_dump_file 0
config_get stats_dump_interval $1 stats_dump_interval 600
}
generate_config() {
@@ -79,6 +83,20 @@ generate_config() {
json_close_array
# plugins
json_add_array "plugins"
# plugin: stats_collector
[ "$stats_collector" -eq 1 ] && {
json_add_object
json_add_string "tag" "stats_collector"
json_add_string "type" "stats_api"
json_add_object "args"
json_add_int "capacity" "$stats_capacity"
[ "$stats_dump_file" -eq 1 ] && {
json_add_string "dump_file" "/etc/mosdns/stats.dump"
json_add_int "dump_interval" "$stats_dump_interval"
}
json_close_object
json_close_object
}
# plugin: geosite_cn
json_add_object
json_add_string "tag" "geosite_cn"
@@ -589,6 +607,11 @@ generate_config() {
json_add_string "tag" "main_sequence"
json_add_string "type" "sequence"
json_add_array "args"
[ "$stats_collector" -eq 1 ] && {
json_add_object
json_add_string "exec" "\$stats_collector"
json_close_object
}
json_add_object
json_add_string "exec" "\$hosts"
json_close_object
@@ -9,6 +9,14 @@ api:
include: []
plugins:
# 统计收集器
- tag: stats_collector
type: stats_api
args:
capacity: 2000 # 查询日志环缓冲区容量 (FIFO 覆盖)
dump_file: "/etc/mosdns/stats.dump" # 数据持久化转存文件路径,不设置则不转存
dump_interval: 600 # 周期性转储间隔(秒)
# 国内域名
- tag: geosite_cn
type: domain_set
@@ -120,6 +128,7 @@ plugins:
- tag: main_sequence
type: sequence
args:
- exec: $stats_collector # 执行全局统计控制器
- exec: $lazy_cache
- exec: jump has_resp_sequence
- exec: $query_is_local_domain
@@ -34,9 +34,17 @@
"path": "mosdns/update"
}
},
"admin/services/mosdns/statistics": {
"title": "Statistics",
"order": 25,
"action": {
"type": "view",
"path": "mosdns/statistics"
}
},
"admin/services/mosdns/logs": {
"title": "Logs",
"order": 25,
"order": 30,
"action": {
"type": "view",
"path": "mosdns/logs"
@@ -149,8 +149,8 @@ function update_adlist() {
print(`Downloading ${mirror}${url}\n`);
stdout.flush();
let curl_res = exec_sys(`curl --connect-timeout 5 -m 90 --ipv4 -kfSLo "${ad_tmpdir}/${filename}" "${mirror}${url}"`);
if (curl_res.code !== 0) download_failed = true;
let dl_res = exec_sys(`wget -4 -q --no-check-certificate -T 90 -t 2 -O "${ad_tmpdir}/${filename}" "${mirror}${url}"`);
if (dl_res.code !== 0) download_failed = true;
}
}
@@ -192,7 +192,7 @@ function update_geodat() {
print(`Downloading ${geoip_url}.sha256sum\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 20 --ipv4 -kfSLo "${tmpdir}/geoip.dat.sha256sum" "${geoip_url}.sha256sum"`).code !== 0) {
if (exec_sys(`wget -4 -q --no-check-certificate -T 20 -t 2 -O "${tmpdir}/geoip.dat.sha256sum" "${geoip_url}.sha256sum"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`);
die("Failed to download geoip.dat.sha256sum");
}
@@ -209,7 +209,7 @@ function update_geodat() {
} else {
print(`Downloading ${geoip_url}\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 120 --ipv4 -kfSLo "${tmpdir}/geoip.dat" "${geoip_url}"`).code !== 0) {
if (exec_sys(`wget -4 -q --no-check-certificate -T 120 -t 2 -O "${tmpdir}/geoip.dat" "${geoip_url}"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`);
die("Failed to download geoip.dat");
}
@@ -227,7 +227,7 @@ function update_geodat() {
print(`Downloading ${geosite_url}.sha256sum\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 20 --ipv4 -kfSLo "${tmpdir}/geosite.dat.sha256sum" "${geosite_url}.sha256sum"`).code !== 0) {
if (exec_sys(`wget -4 -q --no-check-certificate -T 20 -t 2 -O "${tmpdir}/geosite.dat.sha256sum" "${geosite_url}.sha256sum"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`);
die("Failed to download geosite.dat.sha256sum");
}
@@ -244,7 +244,7 @@ function update_geodat() {
} else {
print(`Downloading ${geosite_url}\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 120 --ipv4 -kfSLo "${tmpdir}/geosite.dat" "${geosite_url}"`).code !== 0) {
if (exec_sys(`wget -4 -q --no-check-certificate -T 120 -t 2 -O "${tmpdir}/geosite.dat" "${geosite_url}"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`);
die("Failed to download geosite.dat");
}
@@ -16,10 +16,15 @@
"service": [ "list" ],
"luci.mosdns": [
"clean_log",
"clear_query_logs",
"flush_cache",
"get_history",
"get_logs",
"get_stats",
"get_top",
"get_update_log",
"get_version",
"print_log",
"get_update_log",
"start_update"
]
},
@@ -35,7 +40,8 @@
"/var/mosdns/*": [ "write" ]
},
"ubus": {
"file": [ "write" ]
"file": [ "write" ],
"luci.mosdns": [ "clear_query_logs" ]
},
"uci": [ "mosdns" ]
}
@@ -58,16 +58,85 @@ function get_logfile_path_internal() {
}
}
function parse_json_safe(str) {
if (!str || str == "")
return null;
try {
return json(str);
} catch(e) {
return null;
}
}
function call_mosdns_api(endpoint, method) {
try {
let uci_cursor = cursor();
uci_cursor.load('mosdns');
let port = uci_cursor.get('mosdns', 'config', 'listen_port_api') || '9091';
let http_method = method ? method : 'GET';
let post_flag = (http_method === 'POST') ? '--post-data=""' : '';
let cmd = `wget -q -O - ${post_flag} "http://127.0.0.1:${port}/plugins/stats_collector${endpoint}"`;
let res = exec_sys(cmd);
if (res.code === 0 && res.stdout != "") {
let json_val = parse_json_safe(res.stdout);
if (json_val) return json_val;
}
return { error: "MosDNS API unreachable" };
} catch (e) {
return { error: String(e) };
}
}
const methods = {
get_stats: {
call: function() {
return call_mosdns_api('/api/v1/stats', 'GET');
}
},
get_history: {
args: { points: 24 },
call: function(request) {
let points = request?.args?.points || 24;
return call_mosdns_api(`/api/v1/history?points=${points}`, 'GET');
}
},
get_top: {
args: { limit: 10 },
call: function(request) {
let limit = request?.args?.limit || 10;
return call_mosdns_api(`/api/v1/top?limit=${limit}`, 'GET');
}
},
get_logs: {
args: { limit: 50, offset: 0, search: '', filter: 'all' },
call: function(request) {
let limit = request?.args?.limit || 50;
let offset = request?.args?.offset || 0;
let search = request?.args?.search || '';
let filter = request?.args?.filter || 'all';
return call_mosdns_api(`/api/v1/logs?limit=${limit}&offset=${offset}&search=${search}&filter=${filter}`, 'GET');
}
},
clear_query_logs: {
call: function() {
return call_mosdns_api('/api/v1/logs/clear', 'POST');
}
},
flush_cache: {
call: function() {
try {
let uci_cursor = cursor();
uci_cursor.load('mosdns');
let port = uci_cursor.get('mosdns', 'config', 'listen_port_api');
let port = uci_cursor.get('mosdns', 'config', 'listen_port_api') || '9091';
if (!port) return { error: "API listen port not configured." };
let res = exec_sys(`curl -s 127.0.0.1:${port}/plugins/lazy_cache/flush`);
let res = exec_sys(`wget -q -O - "http://127.0.0.1:${port}/plugins/lazy_cache/flush"`);
if (res.code === 0) {
return { success: true };
} else {
+22
View File
@@ -0,0 +1,22 @@
# Copyright (C) 2020 Gyj1109
# 适配 OpenWrt 25.12 修改版
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-syncthing
PKG_VERSION:=1.0
PKG_RELEASE:=3
LUCI_TITLE:=LuCI support for Syncthing
LUCI_PKGARCH:=all
# 适配 25.12:确保安装了 syncthing 后,LuCI 界面能正确拉起
LUCI_DEPENDS:=+syncthing
include $(TOPDIR)/feeds/luci/luci.mk
# 明确定义配置文件的归属,这对 25.12 的 apk 管理器非常重要
define Package/$(PKG_NAME)/conffiles
/etc/config/syncthing
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
+2
View File
@@ -0,0 +1,2 @@
# luci-app-syncthing
在Potat0000源码基础上进行了汉化优化,官方openwrt23.05.3编译后可使用
@@ -0,0 +1,17 @@
module("luci.controller.syncthing", package.seeall)
function index()
if not nixio.fs.access("/etc/config/syncthing") then
return
end
entry({"admin", "services", "syncthing"}, cbi("syncthing"), _("文件同步"), 10).dependent = true
entry({"admin", "services", "syncthing", "status"}, call("act_status")).leaf = true
end
function act_status()
local e = {}
e.running = luci.sys.call("pgrep syncthing >/dev/null") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
@@ -0,0 +1,48 @@
require("nixio.fs")
m = Map("syncthing", translate("Syncthing同步工具"))
m:section(SimpleSection).template = "syncthing/syncthing_status"
s = m:section(TypedSection, "syncthing")
s.anonymous = true
o = s:option(Flag, "enabled", translate("启用"))
o.default = 0
o.rmempty = false
gui_address = s:option(Value, "gui_address", translate("GUI访问地址"))
gui_address.description = translate("使用0.0.0.0以监控所有访问。")
gui_address.default = "http://0.0.0.0:8384"
gui_address.placeholder = "http://0.0.0.0:8384"
gui_address.rmempty = false
home = s:option(Value, "home", translate("配置文件目录"))
home.description = translate("只有保存在/etc/syncthing中的配置会自动备份!")
home.default = "/etc/syncthing"
home.placeholder = "/etc/syncthing"
home.rmempty = false
user = s:option(ListValue, "user", translate("用户"))
user.description = translate("默认是syncthing,但这可能会导致权限被拒绝。Syncthing官方不建议以root身份运行。")
user:value("", translate("syncthing"))
for u in luci.util.execi("cat /etc/passwd | cut -d ':' -f1") do
user:value(u)
end
macprocs = s:option(Value, "macprocs", translate("线程限制"))
macprocs.description = translate("0表示匹配CPU数量(默认),>0表示显式指定并发数。")
macprocs.default = "0"
macprocs.placeholder = "0"
macprocs.datatype = "range(0,32)"
macprocs.rmempty = false
nice = s:option(Value, "nice", translate("优先级"))
nice.description = translate("显式指定优先级值。0是最高,19是最低。(暂时不允许设置负值)")
nice.default = "19"
nice.placeholder = "19"
nice.datatype = "range(0,19)"
nice.rmempty = false
return m
@@ -0,0 +1,27 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(1, '<%=url([[admin]], [[services]], [[syncthing]], [[status]])%>', null,
function (x, data) {
var tb = document.getElementById('syncthing_status');
if (data && tb) {
if (data.running) {
var links = '<em><b><font color="green">Syncthing <%:运行中%></font></b></em><input class="btn cbi-button mar-10" type="button" value="<%:打开Syncthing页面%>" onclick="openwebui();" />';
tb.innerHTML = links;
} else {
tb.innerHTML = '<em><b><font color="red">Syncthing <%:未运行%></font></b></em>';
}
}
}
);
function openwebui(){
var url = window.location.host+":<%=luci.sys.exec("uci -q get syncthing.syncthing.gui_address"):match(":[0-9]+"):gsub(":", "")%>";
window.open('http://'+url,'target','');
}
//]]>
</script>
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
<fieldset class="cbi-section">
<p id="syncthing_status">
<em><%:正在收集数据...%></em>
</p>
</fieldset>
@@ -0,0 +1,12 @@
#!/bin/sh
touch /etc/config/syncthing
uci -q batch <<-EOF >/dev/null
delete ucitrack.@syncthing[-1]
add ucitrack syncthing
set ucitrack.@syncthing[-1].exec='/etc/init.d/syncthing stop && /etc/init.d/syncthing start'
commit ucitrack
EOF
# remove LuCI cache
rm -f /tmp/luci*
exit 0
@@ -0,0 +1,11 @@
{
"luci-app-syncthing": {
"description": "Grant UCI access for luci-app-syncthing",
"read": {
"uci": [ "syncthing" ]
},
"write": {
"uci": [ "syncthing" ]
}
}
}