🌴 Sync 2026-09-05 22:29:54

This commit is contained in:
github-actions[bot]
2026-09-05 22:29:54 +08:00
parent 8eec681054
commit fc820ab4f5
12 changed files with 1474 additions and 468 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-mosdns PKG_NAME:=luci-app-mosdns
PKG_VERSION:=1.7.12 PKG_VERSION:=1.7.13
PKG_RELEASE:=20 PKG_RELEASE:=21
LUCI_TITLE:=LuCI Support for mosdns LUCI_TITLE:=LuCI Support for mosdns
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
@@ -6,214 +6,100 @@
return view.extend({ return view.extend({
render() { render() {
let m, s, o; const m = new form.Map('mosdns', _('Rule Settings'),
m = new form.Map('mosdns', _('Rule Settings'),
_('The list of rules only apply to \'Default Config\' profiles.')); _('The list of rules only apply to \'Default Config\' profiles.'));
s = m.section(form.TypedSection); const s = m.section(form.TypedSection);
s.anonymous = true; s.anonymous = true;
s.sortable = true; s.sortable = true;
s.tab('whitelist', _('White Lists')); const handleSaveError = e => {
s.tab('blocklist', _('Block Lists'));
s.tab('greylist', _('Grey Lists'));
s.tab('ddnslist', _('DDNS Lists'));
s.tab('hostslist', _('Hosts'));
s.tab('redirectlist', _('Redirect'));
s.tab('localptrlist', _('Block PTR'));
s.tab('streamingmedialist', _('Streaming Media'));
// --- White Lists ---
o = s.taboption('whitelist', form.TextValue, '_whitelist',
null,
'<font color=\'red\'>'
+ _('Added domain names always permit resolution using \'local DNS\' with the highest priority (one domain per line, supports domain matching rules).')
+ '</font>'
);
o.rows = 25;
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/whitelist.txt').catch(e => '');
o.write = function(section_id, formvalue) {
return this.cfgvalue(section_id).then(value => {
if (value === formvalue) {
return;
}
return fs.write('/etc/mosdns/rule/whitelist.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : ''))
.catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
});
};
o.remove = section_id => fs.write('/etc/mosdns/rule/whitelist.txt', '').catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message))); ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
// --- Block Lists ---
o = s.taboption('blocklist', form.TextValue, '_blocklist',
null,
'<font color=\'red\'>'
+ _('Added domain names will block DNS resolution (one domain per line, supports domain matching rules).')
+ '</font>'
);
o.rows = 25;
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/blocklist.txt').catch(e => '');
o.write = function(section_id, formvalue) {
return this.cfgvalue(section_id).then(value => {
if (value === formvalue) {
return;
}
return fs.write('/etc/mosdns/rule/blocklist.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : ''))
.catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
});
}; };
o.remove = section_id => fs.write('/etc/mosdns/rule/blocklist.txt', '').catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
// --- Grey Lists --- const rules = [
o = s.taboption('greylist', form.TextValue, '_greylist', {
null, name: 'whitelist',
'<font color=\'red\'>' title: _('White Lists'),
+ _('Added domain names will always use \'Remote DNS\' for resolution (one domain per line, supports domain matching rules).') file: '/etc/mosdns/rule/whitelist.txt',
+ '</font>' desc: '<font color=\'red\'>'
); + _('Added domain names always permit resolution using \'local DNS\' with the highest priority (one domain per line, supports domain matching rules).')
o.rows = 25; + '</font>'
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/greylist.txt').catch(e => ''); },
o.write = function(section_id, formvalue) { {
return this.cfgvalue(section_id).then(value => { name: 'blocklist',
if (value === formvalue) { title: _('Block Lists'),
return; file: '/etc/mosdns/rule/blocklist.txt',
} desc: '<font color=\'red\'>'
return fs.write('/etc/mosdns/rule/greylist.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : '')) + _('Added domain names will block DNS resolution (one domain per line, supports domain matching rules).')
.catch(e => { + '</font>'
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message))); },
}); {
}); name: 'greylist',
}; title: _('Grey Lists'),
o.remove = section_id => fs.write('/etc/mosdns/rule/greylist.txt', '').catch(e => { file: '/etc/mosdns/rule/greylist.txt',
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message))); desc: '<font color=\'red\'>'
}); + _('Added domain names will always use \'Remote DNS\' for resolution (one domain per line, supports domain matching rules).')
+ '</font>'
},
{
name: 'ddnslist',
title: _('DDNS Lists'),
file: '/etc/mosdns/rule/ddnslist.txt',
desc: '<font color=\'red\'>'
+ _('Added domain names will always use \'Local DNS\' for resolution, with a forced TTL of 5 seconds, and results will not be cached (one domain per line, supports domain matching rules).')
+ '</font>'
},
{
name: 'hostslist',
title: _('Hosts'),
file: '/etc/mosdns/rule/hosts.txt',
desc: '<font color=\'red\'>'
+ _('Custom Hosts rewrite, for example: baidu.com 10.0.0.1 (one rule per line, supports domain matching rules).')
+ '</font>'
},
{
name: 'redirectlist',
title: _('Redirect'),
file: '/etc/mosdns/rule/redirect.txt',
desc: '<font color=\'red\'>'
+ _('Redirecting requests for domain names. Request domain A, but return records for domain B, for example: baidu.com qq.com (one rule per line).')
+ '</font>'
},
{
name: 'localptrlist',
title: _('Block PTR'),
file: '/etc/mosdns/rule/local-ptr.txt',
desc: '<font color=\'red\'>'
+ _('Added domain names will block PTR requests (one domain per line, supports domain matching rules).')
+ '</font>'
},
{
name: 'streamingmedialist',
title: _('Streaming Media'),
file: '/etc/mosdns/rule/streaming.txt',
desc: '<font color=\'red\'>'
+ _('When enabling \'Custom Stream Media DNS\', added domains will always use the \'Streaming Media DNS server\' for resolution (one domain per line, supports domain matching rules).')
+ '</font>'
}
];
// --- DDNS Lists --- rules.forEach(rule => {
o = s.taboption('ddnslist', form.TextValue, '_ddnslist', s.tab(rule.name, rule.title);
null,
'<font color=\'red\'>'
+ _('Added domain names will always use \'Local DNS\' for resolution, with a forced TTL of 5 seconds, and results will not be cached (one domain per line, supports domain matching rules).')
+ '</font>'
);
o.rows = 25;
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/ddnslist.txt').catch(e => '');
o.write = function(section_id, formvalue) {
return this.cfgvalue(section_id).then(value => {
if (value === formvalue) {
return;
}
return fs.write('/etc/mosdns/rule/ddnslist.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : ''))
.catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
});
};
o.remove = section_id => fs.write('/etc/mosdns/rule/ddnslist.txt', '').catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
// --- Hosts --- const o = s.taboption(rule.name, form.TextValue, '_' + rule.name, null, rule.desc);
o = s.taboption('hostslist', form.TextValue, '_hostslist', o.rows = 25;
null, o.cfgvalue = () => fs.trimmed(rule.file).catch(() => '');
'<font color=\'red\'>' o.write = function(section_id, formvalue) {
+ _('Custom Hosts rewrite, for example: baidu.com 10.0.0.1 (one rule per line, supports domain matching rules).') return this.cfgvalue(section_id).then(value => {
+ '</font>' if (value === formvalue) {
); return;
o.rows = 25; }
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/hosts.txt').catch(e => ''); const content = (formvalue && formvalue.trim()) ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : '';
o.write = function(section_id, formvalue) { return fs.write(rule.file, content).catch(handleSaveError);
return this.cfgvalue(section_id).then(value => { });
if (value === formvalue) { };
return; o.remove = () => fs.write(rule.file, '').catch(handleSaveError);
}
return fs.write('/etc/mosdns/rule/hosts.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : ''))
.catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
});
};
o.remove = section_id => fs.write('/etc/mosdns/rule/hosts.txt', '').catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
// --- Redirect ---
o = s.taboption('redirectlist', form.TextValue, '_redirectlist',
null,
'<font color=\'red\'>'
+ _('Redirecting requests for domain names. Request domain A, but return records for domain B, for example: baidu.com qq.com (one rule per line).')
+ '</font>'
);
o.rows = 25;
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/redirect.txt').catch(e => '');
o.write = function(section_id, formvalue) {
return this.cfgvalue(section_id).then(value => {
if (value === formvalue) {
return;
}
return fs.write('/etc/mosdns/rule/redirect.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : ''))
.catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
});
};
o.remove = section_id => fs.write('/etc/mosdns/rule/redirect.txt', '').catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
// --- Block PTR ---
o = s.taboption('localptrlist', form.TextValue, '_localptrlist',
null,
'<font color=\'red\'>'
+ _('Added domain names will block PTR requests (one domain per line, supports domain matching rules).')
+ '</font>'
);
o.rows = 25;
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/local-ptr.txt').catch(e => '');
o.write = function(section_id, formvalue) {
return this.cfgvalue(section_id).then(value => {
if (value === formvalue) {
return;
}
return fs.write('/etc/mosdns/rule/local-ptr.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : ''))
.catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
});
};
o.remove = section_id => fs.write('/etc/mosdns/rule/local-ptr.txt', '').catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
// --- Streaming Media ---
o = s.taboption('streamingmedialist', form.TextValue, '_streamingmedialist',
null,
'<font color=\'red\'>'
+ _('When enabling \'Custom Stream Media DNS\', added domains will always use the \'Streaming Media DNS server\' for resolution (one domain per line, supports domain matching rules).')
+ '</font>'
);
o.rows = 25;
o.cfgvalue = section_id => fs.trimmed('/etc/mosdns/rule/streaming.txt').catch(e => '');
o.write = function(section_id, formvalue) {
return this.cfgvalue(section_id).then(value => {
if (value === formvalue) {
return;
}
return fs.write('/etc/mosdns/rule/streaming.txt', (formvalue.trim() ? formvalue.trim().replace(/\r\n/g, '\n') + '\n' : ''))
.catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
});
});
};
o.remove = section_id => fs.write('/etc/mosdns/rule/streaming.txt', '').catch(e => {
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
}); });
return m.render(); return m.render();
@@ -1,5 +1,6 @@
'use strict'; 'use strict';
'require dom'; 'require dom';
'require fs';
'require poll'; 'require poll';
'require rpc'; 'require rpc';
'require ui'; 'require ui';
@@ -38,10 +39,18 @@ const callClearQueryLogs = rpc.declare({
expect: { '': {} } expect: { '': {} }
}); });
const callGetLeases = rpc.declare({
object: 'luci.mosdns',
method: 'get_leases',
expect: { '': {} }
});
let filterVal = 'all'; let filterVal = 'all';
let searchVal = ''; let searchVal = '';
let pageIdx = 0; let pageIdx = 0;
const PAGE_SIZE = 20; const PAGE_SIZE = 20;
const WHITELIST_FILE = '/etc/mosdns/rule/whitelist.txt';
const BLOCKLIST_FILE = '/etc/mosdns/rule/blocklist.txt';
let isUserPaused = false; let isUserPaused = false;
let nodeStats; let nodeStats;
@@ -52,12 +61,35 @@ let statsElements = null;
let currentBadgeState = null; let currentBadgeState = null;
let lastTopJson = ''; let lastTopJson = '';
let lastLogsJson = ''; let lastLogsJson = '';
let clientLeases = {};
const cleanIP = ip => { const cleanIP = ip => {
if (!ip) return '-'; if (!ip) return '-';
return ip.replace(/^::ffff:/i, ''); return ip.replace(/^::ffff:/i, '');
}; };
const getClientDisplay = ip => {
const cleaned = cleanIP(ip);
if (!cleaned || cleaned === '-') return { display: '-', title: '-', host: null, ip: '-' };
const host = clientLeases[cleaned] || clientLeases[cleaned.toLowerCase()] || clientLeases[ip] || clientLeases[ip?.toLowerCase?.()];
if (host && host !== '*' && host !== 'unknown' && host !== '') {
return {
display: `${host} (${cleaned})`,
title: `${host} (${cleaned})`,
host: host,
ip: cleaned
};
}
return {
display: cleaned,
title: cleaned,
host: null,
ip: cleaned
};
};
const injectStyles = () => { const injectStyles = () => {
if (document.getElementById('mosdns-statistics-styles')) if (document.getElementById('mosdns-statistics-styles'))
return; return;
@@ -94,25 +126,55 @@ const injectStyles = () => {
'.dns-latency-timeout { color: #dc2626; 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-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-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }',
'.mosdns-log-row { cursor: pointer; transition: background 0.15s ease; }',
'.mosdns-log-row:hover { background: rgba(125,125,125,0.05); }',
'@media (max-width: 992px) {',
' .mosdns-table { display: block !important; width: 100% !important; border: none !important; }',
' .mosdns-table tbody { display: block !important; width: 100% !important; }',
' .mosdns-table .table-titles, .mosdns-table .col-client, .mosdns-table .col-answers { display: none !important; }',
' .mosdns-table .mosdns-log-row { display: grid !important; grid-template-columns: 1fr auto !important; grid-template-rows: auto auto !important; grid-template-areas: "domain status" "time latency" !important; align-items: center !important; padding: 0.6rem 0.75rem !important; border-bottom: 1px solid rgba(125,125,125,0.12) !important; border-radius: 6px !important; margin-bottom: 0.25rem !important; cursor: pointer !important; -webkit-tap-highlight-color: transparent !important; }',
' .mosdns-table .mosdns-log-row:active { background: rgba(125,125,125,0.1) !important; }',
' .mosdns-table .col-domain { grid-area: domain !important; display: flex !important; align-items: center !important; min-width: 0 !important; overflow: hidden !important; padding: 0 !important; border: none !important; }',
' .mosdns-table .col-domain .mosdns-mono { overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; font-size: 0.88rem !important; font-weight: 600 !important; }',
' .mosdns-table .col-domain .badge-qtype { flex-shrink: 0 !important; margin-left: 0.35rem !important; font-size: 0.68rem !important; }',
' .mosdns-table .col-status { grid-area: status !important; display: flex !important; justify-content: flex-end !important; align-items: center !important; padding: 0 0 0 0.5rem !important; border: none !important; flex-shrink: 0 !important; }',
' .mosdns-table .col-status .mosdns-badge { font-size: 0.72rem !important; min-width: 62px !important; padding: 0.15em 0.4em !important; }',
' .mosdns-table .col-time { grid-area: time !important; display: block !important; font-size: 0.76rem !important; opacity: 0.6 !important; padding: 0.25rem 0 0 0 !important; border: none !important; white-space: nowrap !important; }',
' .mosdns-table .col-latency { grid-area: latency !important; display: block !important; text-align: right !important; font-size: 0.78rem !important; font-weight: 600 !important; padding: 0.25rem 0 0 0.5rem !important; border: none !important; white-space: nowrap !important; }',
' .mosdns-table .mosdns-empty-row { display: block !important; text-align: center !important; border-bottom: none !important; }',
' .mosdns-table .mosdns-empty-row .td { display: block !important; width: 100% !important; padding: 2rem 0 !important; border: none !important; }',
'}',
'.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-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-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-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 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 { background: rgba(125,125,125,0.04); border: 1px solid rgba(125,125,125,0.08); border-radius: 6px; padding: 0.5rem 0.75rem; min-width: 0; overflow: hidden; }',
'.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-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-meta-item .meta-val { font-size: 0.85rem; font-weight: 600; word-break: break-all; overflow-wrap: anywhere; }',
'.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-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-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-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-data { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }',
'.mosdns-answer-ttl { font-size: 0.75rem; opacity: 0.65; white-space: nowrap; }', '.mosdns-answer-ttl { font-size: 0.75rem; opacity: 0.65; white-space: nowrap; }',
'.mosdns-action-btn { padding: 0.18rem 0.5rem !important; font-size: 0.72rem !important; line-height: 1.25 !important; border-radius: 4px !important; margin: 0 2px !important; cursor: pointer; transition: all 0.2s ease; font-weight: 600; text-decoration: none !important; display: inline-block; vertical-align: middle; }',
'.btn-allow { background: rgba(16, 185, 129, 0.12) !important; color: #059669 !important; border: 1px solid rgba(16, 185, 129, 0.3) !important; }',
'.btn-allow:hover { background: #059669 !important; color: #fff !important; }',
'.btn-block { background: rgba(239, 68, 68, 0.12) !important; color: #dc2626 !important; border: 1px solid rgba(239, 68, 68, 0.3) !important; }',
'.btn-block:hover { background: #dc2626 !important; color: #fff !important; }',
'@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }', '@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }',
'@media (prefers-color-scheme: dark) {', '@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-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); }', ' .mosdns-sparkline-tooltip { background: #1e242b; border-color: rgba(255,255,255,0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.5); }',
' .mosdns-log-row:hover { background: rgba(255,255,255,0.04); }',
' .mosdns-log-row:active { background: rgba(255,255,255,0.08) !important; }',
' .mosdns-log-row { border-bottom-color: rgba(255,255,255,0.08) !important; }',
' .badge-danger { background: rgba(239, 68, 68, 0.2); color: #f87171; border-color: rgba(239, 68, 68, 0.35); }', ' .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-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-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); }', ' .badge-neutral { background: rgba(156, 163, 175, 0.2); color: #9ca3af; border-color: rgba(156, 163, 175, 0.3); }',
' .btn-allow { background: rgba(16, 185, 129, 0.2) !important; color: #34d399 !important; border-color: rgba(16, 185, 129, 0.35) !important; }',
' .btn-allow:hover { background: #059669 !important; color: #fff !important; }',
' .btn-block { background: rgba(239, 68, 68, 0.2) !important; color: #f87171 !important; border-color: rgba(239, 68, 68, 0.35) !important; }',
' .btn-block:hover { background: #dc2626 !important; color: #fff !important; }',
' .dns-latency-fastest { color: #34d399; }', ' .dns-latency-fastest { color: #34d399; }',
' .dns-latency-normal { color: #60a5fa; }', ' .dns-latency-normal { color: #60a5fa; }',
' .dns-latency-timeout { color: #f87171; }', ' .dns-latency-timeout { color: #f87171; }',
@@ -221,10 +283,16 @@ const createSparklineSVG = (strokeColor, fillGradId) => {
if (coord.time) { if (coord.time) {
const d = new Date(coord.time); const d = new Date(coord.time);
if (!isNaN(d)) { if (!isNaN(d)) {
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const hh = String(d.getHours()).padStart(2, '0'); const hh = String(d.getHours()).padStart(2, '0');
timeStr = hh + ':00'; const min = String(d.getMinutes()).padStart(2, '0');
timeStr = `${yyyy}-${mm}-${dd} ${hh}:${min}`;
} else if (coord.time.length >= 16 && coord.time.includes('T')) {
timeStr = coord.time.slice(0, 10) + ' ' + coord.time.slice(11, 16);
} else { } else {
timeStr = coord.time.slice(11, 16) || coord.time; timeStr = coord.time;
} }
} }
@@ -238,7 +306,7 @@ const createSparklineSVG = (strokeColor, fillGradId) => {
const tooltipX = (coord.x / width) * containerWidth; const tooltipX = (coord.x / width) * containerWidth;
const ratio = coord.x / width; const ratio = coord.x / width;
if (ratio > 0.65) { if (ratio > 0.5) {
tooltip.style.left = 'auto'; tooltip.style.left = 'auto';
tooltip.style.right = (containerWidth - tooltipX + 8) + 'px'; tooltip.style.right = (containerWidth - tooltipX + 8) + 'px';
} else { } else {
@@ -356,6 +424,7 @@ const createOverviewStatsDOM = () => {
const metricTotal = E('div', { class: 'metric-val' }, '-'); const metricTotal = E('div', { class: 'metric-val' }, '-');
const subtextTotal = E('div', { class: 'subtext' }, '-'); const subtextTotal = E('div', { class: 'subtext' }, '-');
const sparklineTotal = createSparklineSVG('#3b82f6', 'spark-grad-total'); const sparklineTotal = createSparklineSVG('#3b82f6', 'spark-grad-total');
const badgeQps = E('span', { class: 'mosdns-badge badge-teal badge-pulse' }, _('● Live') + ' - QPS');
const badgeBlocked = E('span', { class: 'mosdns-badge badge-danger' }, '-'); const badgeBlocked = E('span', { class: 'mosdns-badge badge-danger' }, '-');
const metricBlocked = E('div', { class: 'metric-val', style: 'color: #dc2626;' }, '-'); const metricBlocked = E('div', { class: 'metric-val', style: 'color: #dc2626;' }, '-');
@@ -373,7 +442,7 @@ const createOverviewStatsDOM = () => {
E('div', {}, [ E('div', {}, [
E('div', { class: 'title-row' }, [ E('div', { class: 'title-row' }, [
E('span', {}, _('DNS Queries Total')), E('span', {}, _('DNS Queries Total')),
E('span', { class: 'mosdns-badge badge-teal badge-pulse' }, _('● Live')) badgeQps
]), ]),
metricTotal, metricTotal,
subtextTotal subtextTotal
@@ -420,6 +489,7 @@ const createOverviewStatsDOM = () => {
metricTotal, metricTotal,
subtextTotal, subtextTotal,
sparklineTotal, sparklineTotal,
badgeQps,
badgeBlocked, badgeBlocked,
metricBlocked, metricBlocked,
sparklineBlocked, sparklineBlocked,
@@ -453,7 +523,8 @@ const updateOverviewStats = (stats, historyData) => {
cached_queries: cached = 0, cached_queries: cached = 0,
blocked_percentage: blocked_pct = 0, blocked_percentage: blocked_pct = 0,
cached_percentage: cached_pct = 0, cached_percentage: cached_pct = 0,
avg_latency_ms: avg_ms = 0 avg_latency_ms: avg_ms = 0,
qps = 0
} = stats; } = stats;
const points = historyData?.points || []; const points = historyData?.points || [];
@@ -466,6 +537,7 @@ const updateOverviewStats = (stats, historyData) => {
statsElements.metricTotal.textContent = total.toLocaleString(); statsElements.metricTotal.textContent = total.toLocaleString();
statsElements.subtextTotal.textContent = _('Avg Processing') + ': ' + avg_ms + ' ms'; statsElements.subtextTotal.textContent = _('Avg Processing') + ': ' + avg_ms + ' ms';
statsElements.sparklineTotal.update(totalItems, baseMax); statsElements.sparklineTotal.update(totalItems, baseMax);
statsElements.badgeQps.textContent = _('● Live') + ' ' + (qps || 0) + ' QPS';
statsElements.badgeBlocked.textContent = blocked_pct + '%'; statsElements.badgeBlocked.textContent = blocked_pct + '%';
statsElements.metricBlocked.textContent = blocked.toLocaleString(); statsElements.metricBlocked.textContent = blocked.toLocaleString();
@@ -490,8 +562,14 @@ const renderTopRankings = topData => {
const maxCount = Math.max(...items.map(i => i.count || 1)); const maxCount = Math.max(...items.map(i => i.count || 1));
return E('div', { style: 'display: flex; flex-direction: column;' }, return E('div', { style: 'display: flex; flex-direction: column;' },
items.map(item => { items.map(item => {
let val = item[key] || '-'; let rawVal = item[key] || '-';
if (isClient) val = cleanIP(val); let valText = rawVal;
let valTitle = rawVal;
if (isClient) {
const clientInfo = getClientDisplay(rawVal);
valText = clientInfo.display;
valTitle = clientInfo.title;
}
const cnt = item.count || 0; const cnt = item.count || 0;
const pct = Math.round((cnt / maxCount) * 100); const pct = Math.round((cnt / maxCount) * 100);
@@ -500,8 +578,8 @@ const renderTopRankings = topData => {
E('span', { E('span', {
class: 'mosdns-mono', class: 'mosdns-mono',
style: 'font-size: 0.82rem; z-index: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 0.5rem;', style: 'font-size: 0.82rem; z-index: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 0.5rem;',
title: val title: valTitle
}, val), }, valText),
E('span', { class: 'mosdns-badge badge-neutral mosdns-mono', style: 'z-index: 1;' }, cnt.toLocaleString()) E('span', { class: 'mosdns-badge badge-neutral mosdns-mono', style: 'z-index: 1;' }, cnt.toLocaleString())
]); ]);
}) })
@@ -534,12 +612,132 @@ const renderTopRankings = topData => {
}; };
const updateTopRankings = topData => { const updateTopRankings = topData => {
if (topData?.leases && typeof topData.leases === 'object') {
clientLeases = Object.assign(clientLeases, topData.leases);
}
const json = JSON.stringify(topData || {}); const json = JSON.stringify(topData || {});
if (json === lastTopJson) return; if (json === lastTopJson) return;
lastTopJson = json; lastTopJson = json;
dom.content(nodeTop, renderTopRankings(topData)); dom.content(nodeTop, renderTopRankings(topData));
}; };
const promptAddRule = (rawDomain, actionType) => {
const cleanDomain = (rawDomain || '').replace(/\.+$/, '').trim().toLowerCase();
if (!cleanDomain) {
ui.addNotification(null, E('p', _('Domain name cannot be empty.')), 'warning');
return;
}
const isPermit = (actionType === 'permit' || actionType === 'allow' || actionType === 'whitelist');
const targetFile = isPermit ? WHITELIST_FILE : BLOCKLIST_FILE;
const targetName = isPermit ? _('White Lists') : _('Block Lists');
const opposingFile = isPermit ? BLOCKLIST_FILE : WHITELIST_FILE;
const opposingName = isPermit ? _('Block Lists') : _('White Lists');
const cleanRuleLine = l => {
let s = (l || '').trim().toLowerCase();
if (!s || s.startsWith('#')) return null;
s = s.replace(/^\|\|/, '').replace(/\^.*$/, '');
s = s.replace(/^(domain|full|keyword):/, '');
return s.replace(/\.+$/, '').trim();
};
const inputDomain = E('input', {
class: 'cbi-input-text',
type: 'text',
value: cleanDomain,
style: 'width: 100%; font-family: monospace; font-size: 0.95rem; margin-top: 0.5rem;'
});
const executeAddRule = () => {
const domainToAdd = inputDomain.value.replace(/\.+$/, '').trim().toLowerCase();
if (!domainToAdd) {
ui.addNotification(null, E('p', _('Domain name cannot be empty.')), 'warning');
return;
}
ui.hideModal();
fs.read(targetFile).then(content => {
const rawLines = (content || '').split(/\r?\n/);
const existingRules = rawLines.map(cleanRuleLine).filter(Boolean);
const alreadyInTarget = existingRules.includes(domainToAdd);
let appendPromise = Promise.resolve();
if (!alreadyInTarget) {
let newContent = (content || '').trimEnd();
if (newContent.length > 0) {
newContent += '\n' + domainToAdd + '\n';
} else {
newContent = '# MosDNS Rules\n' + domainToAdd + '\n';
}
appendPromise = fs.write(targetFile, newContent);
}
return appendPromise.then(() => {
return fs.read(opposingFile).then(oppContent => {
if (!oppContent) return;
const oppLines = oppContent.split(/\r?\n/);
let changed = false;
const filtered = oppLines.filter(l => {
const trimmed = l.trim().toLowerCase();
const cleaned = cleanRuleLine(l);
if (trimmed === domainToAdd || cleaned === domainToAdd) {
changed = true;
return false;
}
return true;
});
if (changed) {
let oppNew = filtered.join('\n').trimEnd();
if (oppNew.length > 0) oppNew += '\n';
return fs.write(opposingFile, oppNew);
}
});
});
}).catch(err => {
ui.addNotification(null, E('p', _('Failed to update rule file: %s').format(err.message)), 'error');
});
};
inputDomain.addEventListener('keydown', e => {
if (e.key === 'Enter') {
e.preventDefault();
executeAddRule();
}
});
const title = isPermit ? _('Add to Whitelist') : _('Add to Blocklist');
const btnClass = isPermit ? 'cbi-button-save' : 'cbi-button-reset';
ui.showModal(title, [
E('div', { style: 'padding: 0.5rem 0;' }, [
E('p', { style: 'margin-bottom: 0.5rem;' },
isPermit
? _('Add domain to the whitelist to permit DNS resolution:')
: _('Add domain to the blocklist to block DNS resolution:')
),
E('label', { style: 'font-weight: 600; font-size: 0.85rem;' }, _('Domain:')),
inputDomain,
E('p', { class: 'cbi-value-description', style: 'margin-top: 0.4rem; font-size: 0.8rem; opacity: 0.7;' },
_('Target file: %s (supports domain matching rules)').format(targetFile)
)
]),
E('div', { class: 'right', style: 'margin-top: 1.25rem; display: flex; justify-content: flex-end; gap: 0.5rem;' }, [
E('button', {
class: 'btn cbi-button cbi-button-neutral',
click: ui.hideModal
}, _('Cancel')),
E('button', {
class: 'btn cbi-button ' + btnClass,
click: executeAddRule
}, _('Confirm'))
])
]);
setTimeout(() => inputDomain.focus(), 100);
};
const showLogDetailsModal = item => { const showLogDetailsModal = item => {
let statusBadge; let statusBadge;
if (item.is_blocked) { if (item.is_blocked) {
@@ -570,6 +768,8 @@ const showLogDetailsModal = item => {
}, _('No DNS answer records returned.')); }, _('No DNS answer records returned.'));
} }
const clientInfo = getClientDisplay(item.client_ip);
const body = E('div', { style: 'padding: 0.25rem 0;' }, [ const body = E('div', { style: 'padding: 0.25rem 0;' }, [
E('div', { class: 'mosdns-modal-header' }, [ E('div', { class: 'mosdns-modal-header' }, [
E('div', { class: 'mosdns-modal-domain' }, [ E('div', { class: 'mosdns-modal-domain' }, [
@@ -585,7 +785,7 @@ const showLogDetailsModal = item => {
E('div', { class: 'mosdns-modal-meta-grid' }, [ E('div', { class: 'mosdns-modal-meta-grid' }, [
E('div', { class: 'mosdns-modal-meta-item' }, [ E('div', { class: 'mosdns-modal-meta-item' }, [
E('div', { class: 'meta-label' }, _('Client IP')), E('div', { class: 'meta-label' }, _('Client IP')),
E('div', { class: 'meta-val mosdns-mono' }, cleanIP(item.client_ip)) E('div', { class: 'meta-val mosdns-mono' }, clientInfo.display)
]), ]),
E('div', { class: 'mosdns-modal-meta-item' }, [ E('div', { class: 'mosdns-modal-meta-item' }, [
E('div', { class: 'meta-label' }, _('Time')), E('div', { class: 'meta-label' }, _('Time')),
@@ -610,7 +810,22 @@ const showLogDetailsModal = item => {
ui.showModal(_('Query Log Details'), [ ui.showModal(_('Query Log Details'), [
body, body,
E('div', { class: 'right', style: 'margin-top: 1.25rem;' }, [ E('div', { class: 'right', style: 'margin-top: 1.25rem; display: flex; justify-content: space-between; align-items: center;' }, [
item.is_blocked ? E('button', {
class: 'btn cbi-button mosdns-action-btn btn-allow',
style: 'padding: 0.35rem 0.75rem !important; font-size: 0.82rem !important;',
click: () => {
ui.hideModal();
promptAddRule(item.domain, 'permit');
}
}, _('Permit')) : E('button', {
class: 'btn cbi-button mosdns-action-btn btn-block',
style: 'padding: 0.35rem 0.75rem !important; font-size: 0.82rem !important;',
click: () => {
ui.hideModal();
promptAddRule(item.domain, 'intercept');
}
}, _('Intercept')),
E('button', { E('button', {
class: 'btn cbi-button cbi-button-action', class: 'btn cbi-button cbi-button-action',
click: ui.hideModal click: ui.hideModal
@@ -639,26 +854,35 @@ const renderLogsTable = logsData => {
? item.answers.map(a => a.data + ' (' + a.type + ')').join(', ') ? item.answers.map(a => a.data + ' (' + a.type + ')').join(', ')
: '-'; : '-';
return E('tr', { class: 'tr' }, [ const clientInfo = getClientDisplay(item.client_ip);
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)), return E('tr', {
E('td', { class: 'td', style: 'max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', title: item.domain || '-' }, [ class: 'tr mosdns-log-row',
title: _('Click to view full details'),
click: () => showLogDetailsModal(item)
}, [
E('td', { class: 'td col-time', style: 'font-size: 0.82rem; opacity: 0.7; white-space: nowrap;' }, formatTimestamp(item.timestamp)),
E('td', {
class: 'td col-client mosdns-mono',
style: 'font-size: 0.82rem; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
title: clientInfo.title
}, clientInfo.display),
E('td', { class: 'td col-domain', 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: 'mosdns-mono', style: 'font-weight: 600;' }, item.domain || '-'),
E('span', { class: 'badge-qtype' }, item.qtype || 'A') E('span', { class: 'badge-qtype' }, item.qtype || 'A')
]), ]),
E('td', { class: 'td' }, statusBadge), E('td', { class: 'td col-status' }, statusBadge),
E('td', { E('td', {
class: 'td mosdns-mono', class: 'td col-answers mosdns-mono',
style: 'font-size: 0.82rem; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer;', style: 'font-size: 0.82rem; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
title: _('Click to view full details'), title: _('Click to view full details')
click: () => showLogDetailsModal(item)
}, answersText), }, answersText),
E('td', { class: 'td mosdns-mono ' + getLatencyClass(item.elapsed_ms), style: 'text-align: right; font-size: 0.82rem;' }, item.elapsed_ms + ' ms') E('td', { class: 'td col-latency mosdns-mono ' + getLatencyClass(item.elapsed_ms), style: 'text-align: right; font-size: 0.82rem;' }, item.elapsed_ms + ' ms')
]); ]);
}); });
if (!rows.length) { if (!rows.length) {
rows.push(E('tr', { class: 'tr' }, [ rows.push(E('tr', { class: 'tr mosdns-empty-row' }, [
E('td', { class: 'td', colspan: 6, style: 'text-align: center; opacity: 0.5; padding: 2rem;' }, _('No query log entries found.')) E('td', { class: 'td', colspan: 6, style: 'text-align: center; opacity: 0.5; padding: 2rem;' }, _('No query log entries found.'))
])); ]));
} }
@@ -666,12 +890,12 @@ const renderLogsTable = logsData => {
return E('div', {}, [ return E('div', {}, [
E('table', { class: 'table cbi-section-table mosdns-table', style: 'margin-top: 0.25rem;' }, [ E('table', { class: 'table cbi-section-table mosdns-table', style: 'margin-top: 0.25rem;' }, [
E('tr', { class: 'tr table-titles' }, [ E('tr', { class: 'tr table-titles' }, [
E('th', { class: 'th', style: 'width: 85px;' }, _('Time')), E('th', { class: 'th col-time', style: 'width: 85px;' }, _('Time')),
E('th', { class: 'th', style: 'width: 125px;' }, _('Client IP')), E('th', { class: 'th col-client', style: 'width: 125px;' }, _('Client IP')),
E('th', { class: 'th' }, _('Domain & Record')), E('th', { class: 'th col-domain' }, _('Domain & Record')),
E('th', { class: 'th', style: 'width: 90px;' }, _('Status')), E('th', { class: 'th col-status', style: 'width: 90px;' }, _('Status')),
E('th', { class: 'th' }, _('Answers')), E('th', { class: 'th col-answers' }, _('Answers')),
E('th', { class: 'th', style: 'width: 90px; text-align: right;' }, _('Elapsed')) E('th', { class: 'th col-latency', style: 'width: 80px; text-align: right;' }, _('Elapsed'))
]), ]),
...rows ...rows
]), ]),
@@ -753,7 +977,8 @@ return view.extend({
L.resolveDefault(callGetStats(), {}), L.resolveDefault(callGetStats(), {}),
L.resolveDefault(callGetTop(10), {}), L.resolveDefault(callGetTop(10), {}),
L.resolveDefault(callGetLogs(PAGE_SIZE, 0, searchVal, filterVal), {}), L.resolveDefault(callGetLogs(PAGE_SIZE, 0, searchVal, filterVal), {}),
L.resolveDefault(callGetHistory(24), {}) L.resolveDefault(callGetHistory(24), {}),
L.resolveDefault(callGetLeases(), {})
]); ]);
}, },
@@ -764,6 +989,14 @@ return view.extend({
currentBadgeState = null; currentBadgeState = null;
lastTopJson = ''; lastTopJson = '';
lastLogsJson = ''; lastLogsJson = '';
clientLeases = {};
if (data[4]?.leases) {
clientLeases = Object.assign(clientLeases, data[4].leases);
}
if (data[1]?.leases) {
clientLeases = Object.assign(clientLeases, data[1].leases);
}
nodeStats = E('div', { id: 'overview-stats' }); nodeStats = E('div', { id: 'overview-stats' });
nodeTop = E('div', { id: 'top-rankings' }); nodeTop = E('div', { id: 'top-rankings' });
@@ -86,9 +86,20 @@ return view.extend({
}); });
}, 1000); }, 1000);
} else { } else {
statusMsg.textContent = res.error || _('Failed to start update.'); if (res.error && res.error.match(/Another update is already in progress/)) {
statusMsg.textContent = _('Another update is already in progress.');
statusMsg.style.color = '#ff9900';
callGetUpdateLog().then(logRes => {
if (logRes && logRes.log) {
logTextarea.value = logRes.log;
logTextarea.scrollTop = logTextarea.scrollHeight;
}
});
} else {
statusMsg.textContent = res.error || _('Failed to start update.');
statusMsg.style.color = '#ed4014';
}
statusMsg.classList.remove('spinning'); statusMsg.classList.remove('spinning');
statusMsg.style.color = '#ed4014';
closeButton.style.display = 'inline'; closeButton.style.display = 'inline';
} }
}).catch(e => { }).catch(e => {
+152 -114
View File
@@ -17,32 +17,48 @@ msgstr ""
msgid "API Options" msgid "API Options"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:718
msgid "Add domain to the blocklist to block DNS resolution:"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:717
msgid "Add domain to the whitelist to permit DNS resolution:"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710
msgid "Add to Blocklist"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710
msgid "Add to Whitelist"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:26
msgid "" msgid ""
"Added domain names always permit resolution using 'local DNS' with the " "Added domain names always permit resolution using 'local DNS' with the "
"highest priority (one domain per line, supports domain matching rules)." "highest priority (one domain per line, supports domain matching rules)."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:103 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:50
msgid "" msgid ""
"Added domain names will always use 'Local DNS' for resolution, with a forced " "Added domain names will always use 'Local DNS' for resolution, with a forced "
"TTL of 5 seconds, and results will not be cached (one domain per line, " "TTL of 5 seconds, and results will not be cached (one domain per line, "
"supports domain matching rules)." "supports domain matching rules)."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:79 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:42
msgid "" msgid ""
"Added domain names will always use 'Remote DNS' for resolution (one domain " "Added domain names will always use 'Remote DNS' for resolution (one domain "
"per line, supports domain matching rules)." "per line, supports domain matching rules)."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:55 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:34
msgid "" msgid ""
"Added domain names will block DNS resolution (one domain per line, supports " "Added domain names will block DNS resolution (one domain per line, supports "
"domain matching rules)." "domain matching rules)."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:175 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:74
msgid "" msgid ""
"Added domain names will block PTR requests (one domain per line, supports " "Added domain names will block PTR requests (one domain per line, supports "
"domain matching rules)." "domain matching rules)."
@@ -74,16 +90,17 @@ msgstr ""
msgid "Aliyun Public DNS (DNS over QUIC)" msgid "Aliyun Public DNS (DNS over QUIC)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:640 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1026
msgid "All Queries" msgid "All Queries"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:68 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:68
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:90
msgid "Another update is already in progress." msgid "Another update is already in progress."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:463 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:805
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:531 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:897
msgid "Answers" msgid "Answers"
msgstr "" msgstr ""
@@ -91,7 +108,7 @@ msgstr ""
msgid "Apple domains optimization" msgid "Apple domains optimization"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:667 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1053
msgid "" msgid ""
"Are you sure you want to clear all real-time query logs and top rankings?" "Are you sure you want to clear all real-time query logs and top rankings?"
msgstr "" msgstr ""
@@ -104,17 +121,17 @@ msgstr ""
msgid "Auto Save Stats Interval" msgid "Auto Save Stats Interval"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:106 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:117
msgid "" msgid ""
"Automatically update GeoIP and GeoSite databases as well as ad filtering " "Automatically update GeoIP and GeoSite databases as well as ad filtering "
"rules through scheduled tasks." "rules through scheduled tasks."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:336 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:478
msgid "Average Processing Time" msgid "Average Processing Time"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:306 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538
msgid "Avg Processing" msgid "Avg Processing"
msgstr "" msgstr ""
@@ -137,19 +154,21 @@ msgid ""
"using only A/AAAA records." "using only A/AAAA records."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:19 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635
msgid "Block Lists" msgid "Block Lists"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:24 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:71
msgid "Block PTR" msgid "Block PTR"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:641 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1027
msgid "Blocked Only" msgid "Blocked Only"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:314 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:456
msgid "Blocked by Filters" msgid "Blocked by Filters"
msgstr "" msgstr ""
@@ -171,23 +190,24 @@ msgstr ""
msgid "Cache Prefetching" msgid "Cache Prefetching"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:642 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1028
msgid "Cached Only" msgid "Cached Only"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:325 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:467
msgid "Cached Queries" msgid "Cached Queries"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:672 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:730
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1058
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:150 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:161
msgid "Check And Update" msgid "Check And Update"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:148 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:159
msgid "Check And Update GeoData." msgid "Check And Update GeoData."
msgstr "" msgstr ""
@@ -209,7 +229,7 @@ msgstr ""
msgid "Cisco Public DNS (208.67.222.222)" msgid "Cisco Public DNS (208.67.222.222)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:692 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1078
msgid "Clear" msgid "Clear"
msgstr "" msgstr ""
@@ -217,21 +237,22 @@ msgstr ""
msgid "Clear logs" msgid "Clear logs"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:663 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1049
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:666 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1052
msgid "Clear query logs" msgid "Clear query logs"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:511 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:861
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:878
msgid "Click to view full details" msgid "Click to view full details"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:445 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:787
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:528 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:894
msgid "Client IP" msgid "Client IP"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:475 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:832
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35
msgid "Close" msgid "Close"
msgstr "" msgstr ""
@@ -271,6 +292,10 @@ msgstr ""
msgid "Configuration Editor" msgid "Configuration Editor"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:734
msgid "Confirm"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:199 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:199
msgid "Custom China DNS" msgid "Custom China DNS"
msgstr "" msgstr ""
@@ -279,7 +304,7 @@ msgstr ""
msgid "Custom Config" msgid "Custom Config"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:127 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:58
msgid "" msgid ""
"Custom Hosts rewrite, for example: baidu.com 10.0.0.1 (one rule per line, " "Custom Hosts rewrite, for example: baidu.com 10.0.0.1 (one rule per line, "
"supports domain matching rules)." "supports domain matching rules)."
@@ -293,7 +318,7 @@ msgstr ""
msgid "Custom Stream Media DNS" msgid "Custom Stream Media DNS"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:21 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:47
msgid "DDNS Lists" msgid "DDNS Lists"
msgstr "" msgstr ""
@@ -305,7 +330,7 @@ msgstr ""
msgid "DNS Forward" msgid "DNS Forward"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:302 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:444
msgid "DNS Queries Total" msgid "DNS Queries Total"
msgstr "" msgstr ""
@@ -319,7 +344,7 @@ msgstr ""
msgid "DNS redirect" msgid "DNS redirect"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:149 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:160
msgid "Database Update" msgid "Database Update"
msgstr "" msgstr ""
@@ -349,15 +374,24 @@ msgstr ""
msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)" msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:529 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:895
msgid "Domain & Record" msgid "Domain & Record"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:532 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:627
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:655
msgid "Domain name cannot be empty."
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720
msgid "Domain:"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:898
msgid "Elapsed" msgid "Elapsed"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:111 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:122
msgid "Enable Auto Database Update" msgid "Enable Auto Database Update"
msgstr "" msgstr ""
@@ -403,35 +437,35 @@ msgstr ""
msgid "Error" msgid "Error"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:115 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:126
msgid "Every Day" msgid "Every Day"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:120 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:131
msgid "Every Friday" msgid "Every Friday"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:116 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:127
msgid "Every Monday" msgid "Every Monday"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:121 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:132
msgid "Every Saturday" msgid "Every Saturday"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:122 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:133
msgid "Every Sunday" msgid "Every Sunday"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:119 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:130
msgid "Every Thursday" msgid "Every Thursday"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:117 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:128
msgid "Every Tuesday" msgid "Every Tuesday"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:118 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:129
msgid "Every Wednesday" msgid "Every Wednesday"
msgstr "" msgstr ""
@@ -444,19 +478,23 @@ msgstr ""
msgid "Failed to clean logs." msgid "Failed to clean logs."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:686 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1072
msgid "Failed to clear query logs:" msgid "Failed to clear query logs:"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:89 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:99
msgid "Failed to start update." msgid "Failed to start update."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:970
msgid "Failed to update query logs:" msgid "Failed to update query logs:"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:658 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:699
msgid "Failed to update rule file: %s"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1044
msgid "First Page / Resume" msgid "First Page / Resume"
msgstr "" msgstr ""
@@ -496,11 +534,11 @@ msgstr ""
msgid "Forward Dnsmasq Domain Name resolution requests to MosDNS" msgid "Forward Dnsmasq Domain Name resolution requests to MosDNS"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:136 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:147
msgid "Full" msgid "Full"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:134 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:145
msgid "Full: includes all Countries and Private IP addresses." msgid "Full: includes all Countries and Private IP addresses."
msgstr "" msgstr ""
@@ -512,7 +550,7 @@ msgstr ""
msgid "GeoIP Tags" msgid "GeoIP Tags"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:131 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:142
msgid "GeoIP Type" msgid "GeoIP Type"
msgstr "" msgstr ""
@@ -524,7 +562,7 @@ msgstr ""
msgid "Geodata Update" msgid "Geodata Update"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:141 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:152
msgid "GitHub Proxy" msgid "GitHub Proxy"
msgstr "" msgstr ""
@@ -543,11 +581,11 @@ msgstr ""
msgid "Grant UCI access for luci-app-mosdns" msgid "Grant UCI access for luci-app-mosdns"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:20 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:39
msgid "Grey Lists" msgid "Grey Lists"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:22 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:55
msgid "Hosts" msgid "Hosts"
msgstr "" msgstr ""
@@ -557,10 +595,10 @@ msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410
msgid "" msgid ""
"IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" target=\"_blank" "IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" "
"\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a href=\"https://" "target=\"_blank\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a "
"www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-" "href=\"https://www.cloudflare.com/ips-v6\" target=\"_blank\">https://www."
"v6</a>" "cloudflare.com/ips-v6</a>"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190
@@ -583,13 +621,17 @@ msgstr ""
msgid "Info" msgid "Info"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:828
msgid "Intercept"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:347 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:347
msgid "" msgid ""
"Interval for the background thread to scan the cache for prefetching (in " "Interval for the background thread to scan the cache for prefetching (in "
"seconds)." "seconds)."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:337 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:479
msgid "Latency" msgid "Latency"
msgstr "" msgstr ""
@@ -610,11 +652,11 @@ msgstr ""
msgid "Listen Port" msgid "Listen Port"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:137 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:148
msgid "Little" msgid "Little"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:132 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:143
msgid "Little: only include Mainland China and Private IP addresses." msgid "Little: only include Mainland China and Private IP addresses."
msgstr "" msgstr ""
@@ -674,12 +716,12 @@ msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:33 #: 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/basic.js:106
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:70 #: 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/htdocs/luci-static/resources/view/mosdns/statistics.js:1094
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3 #: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3
msgid "MosDNS" msgid "MosDNS"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:279 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:510
msgid "" msgid ""
"MosDNS API is unreachable. Please ensure MosDNS is running and stats_api " "MosDNS API is unreachable. Please ensure MosDNS is running and stats_api "
"plugin is enabled." "plugin is enabled."
@@ -697,15 +739,15 @@ msgstr ""
msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS" msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:561 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:927
msgid "Next" msgid "Next"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:428 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:768
msgid "No DNS answer records returned." msgid "No DNS answer records returned."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:353 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:560
msgid "No data available" msgid "No data available"
msgstr "" msgstr ""
@@ -713,18 +755,22 @@ msgstr ""
msgid "No log data." msgid "No log data."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:520 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:886
msgid "No query log entries found." msgid "No query log entries found."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:904
msgid "Page %d / %d (%d entries)" msgid "Page %d / %d (%d entries)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:340 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:438
msgid "Per-query speed" msgid "Per-query speed"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:821
msgid "Permit"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:301 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:301
msgid "" msgid ""
"Please provide the IP address you use when accessing foreign websites. This " "Please provide the IP address you use when accessing foreign websites. This "
@@ -756,7 +802,7 @@ msgstr ""
msgid "Prevent DNS Leaks" msgid "Prevent DNS Leaks"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:550 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:916
msgid "Previous" msgid "Previous"
msgstr "" msgstr ""
@@ -775,7 +821,7 @@ msgstr ""
msgid "Quad9 Public DNS (9.9.9.9)" msgid "Quad9 Public DNS (9.9.9.9)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:469 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:811
msgid "Query Log Details" msgid "Query Log Details"
msgstr "" msgstr ""
@@ -785,7 +831,7 @@ msgid ""
"consume more memory)" "consume more memory)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:681 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1067
msgid "Query logs cleared successfully." msgid "Query logs cleared successfully."
msgstr "" msgstr ""
@@ -793,15 +839,15 @@ msgstr ""
msgid "RUNNING" msgid "RUNNING"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:712 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1098
msgid "Real-time Query Logs" msgid "Real-time Query Logs"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:63
msgid "Redirect" msgid "Redirect"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:151 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:66
msgid "" msgid ""
"Redirecting requests for domain names. Request domain A, but return records " "Redirecting requests for domain names. Request domain A, but return records "
"for domain B, for example: baidu.com qq.com (one rule per line)." "for domain B, for example: baidu.com qq.com (one rule per line)."
@@ -823,11 +869,11 @@ msgstr ""
msgid "Ring Buffer Capacity" msgid "Ring Buffer Capacity"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:457 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:799
msgid "Rule Hit" msgid "Rule Hit"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:11 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:9
msgid "Rule Settings" msgid "Rule Settings"
msgstr "" msgstr ""
@@ -843,7 +889,7 @@ msgstr ""
msgid "Save the cache locally and reload the cache dump on the next startup" msgid "Save the cache locally and reload the cache dump on the next startup"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:629 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1015
msgid "Search domain or client IP..." msgid "Search domain or client IP..."
msgstr "" msgstr ""
@@ -855,7 +901,7 @@ msgstr ""
msgid "Starting update..." msgid "Starting update..."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:708 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1094
#: 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:38
msgid "Statistics" msgid "Statistics"
msgstr "" msgstr ""
@@ -864,11 +910,11 @@ msgstr ""
msgid "Stats Dump" msgid "Stats Dump"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:530 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:896
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:25 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:79
msgid "Streaming Media" msgid "Streaming Media"
msgstr "" msgstr ""
@@ -884,6 +930,10 @@ msgstr ""
msgid "TCP/DoT Connection Multiplexing" msgid "TCP/DoT Connection Multiplexing"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:723
msgid "Target file: %s (supports domain matching rules)"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:210 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:210
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:258 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:258
msgid "Tencent Public DNS (119.28.28.28)" msgid "Tencent Public DNS (119.28.28.28)"
@@ -898,7 +948,7 @@ msgstr ""
msgid "Tencent Public DNS (DNS over HTTPS)" msgid "Tencent Public DNS (DNS over HTTPS)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:12 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:10
msgid "The list of rules only apply to 'Default Config' profiles." msgid "The list of rules only apply to 'Default Config' profiles."
msgstr "" msgstr ""
@@ -916,20 +966,20 @@ msgid ""
"content in yaml format." "content in yaml format."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:449 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:791
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:527 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:893
msgid "Time" msgid "Time"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:386 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599
msgid "Top Blocked Domains" msgid "Top Blocked Domains"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:393 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:606
msgid "Top Clients" msgid "Top Clients"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:379 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:592
msgid "Top Queried Domains" msgid "Top Queried Domains"
msgstr "" msgstr ""
@@ -942,38 +992,23 @@ msgid "TrafficRoute Public DNS (180.184.2.2)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:494 #: 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:17
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:48
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:67
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:72
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:91
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:96
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:115
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:120
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:139
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:144
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:163
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:168
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:187
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:192
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:211
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:216
msgid "Unable to save contents: %s" msgid "Unable to save contents: %s"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:114 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:125
msgid "Update Cycle" msgid "Update Cycle"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:105 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:116
msgid "Update GeoIP & GeoSite databases" msgid "Update GeoIP & GeoSite databases"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:125 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:136
msgid "Update Time" msgid "Update Time"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:142 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:153
msgid "" msgid ""
"Update data files with GitHub Proxy, leave blank to disable proxy downloads." "Update data files with GitHub Proxy, leave blank to disable proxy downloads."
msgstr "" msgstr ""
@@ -982,7 +1017,7 @@ msgstr ""
msgid "Update failed" msgid "Update failed"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:95 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:106
msgid "Update failed: %s" msgid "Update failed: %s"
msgstr "" msgstr ""
@@ -994,7 +1029,7 @@ msgstr ""
msgid "Updating Database..." msgid "Updating Database..."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:453 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:795
msgid "Upstream" msgid "Upstream"
msgstr "" msgstr ""
@@ -1002,7 +1037,7 @@ msgstr ""
msgid "Warning" msgid "Warning"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:199 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:82
msgid "" msgid ""
"When enabling 'Custom Stream Media DNS', added domains will always use the " "When enabling 'Custom Stream Media DNS', added domains will always use the "
"'Streaming Media DNS server' for resolution (one domain per line, supports " "'Streaming Media DNS server' for resolution (one domain per line, supports "
@@ -1015,7 +1050,9 @@ msgid ""
"(domain list or AdGuardHome rules)." "(domain list or AdGuardHome rules)."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:18 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635
msgid "White Lists" msgid "White Lists"
msgstr "" msgstr ""
@@ -1029,18 +1066,19 @@ msgstr ""
msgid "Xinfeng Public DNS (114.114.115.115)" msgid "Xinfeng Public DNS (114.114.115.115)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:143 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:154
msgid "https://gh-proxy.com" msgid "https://gh-proxy.com"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:303 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:427
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:540
msgid "● Live" msgid "● Live"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:148 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:223
msgid "● Live Auto-refresh" msgid "● Live Auto-refresh"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:152 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:227
msgid "❚❚ Paused (Page %d)" msgid "❚❚ Paused (Page %d)"
msgstr "" msgstr ""
+165 -118
View File
@@ -27,7 +27,23 @@ msgstr "API 监听端口"
msgid "API Options" msgid "API Options"
msgstr "API 选项" msgstr "API 选项"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:718
msgid "Add domain to the blocklist to block DNS resolution:"
msgstr "将域名添加到黑名单以拦截 DNS 解析:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:717
msgid "Add domain to the whitelist to permit DNS resolution:"
msgstr "将域名添加到白名单以放行 DNS 解析:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710
msgid "Add to Blocklist"
msgstr "添加到黑名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710
msgid "Add to Whitelist"
msgstr "添加到白名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:26
msgid "" msgid ""
"Added domain names always permit resolution using 'local DNS' with the " "Added domain names always permit resolution using 'local DNS' with the "
"highest priority (one domain per line, supports domain matching rules)." "highest priority (one domain per line, supports domain matching rules)."
@@ -35,7 +51,7 @@ msgstr ""
"加入的域名始终允许使用 “本地 DNS” 进行解析,且优先级最高(每个域名一行,支持" "加入的域名始终允许使用 “本地 DNS” 进行解析,且优先级最高(每个域名一行,支持"
"域名匹配规则)" "域名匹配规则)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:103 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:50
msgid "" msgid ""
"Added domain names will always use 'Local DNS' for resolution, with a forced " "Added domain names will always use 'Local DNS' for resolution, with a forced "
"TTL of 5 seconds, and results will not be cached (one domain per line, " "TTL of 5 seconds, and results will not be cached (one domain per line, "
@@ -44,20 +60,20 @@ msgstr ""
"加入的域名始终使用 “本地 DNS” 进行解析,并且强制 TTL 5 秒,解析结果不会进入缓" "加入的域名始终使用 “本地 DNS” 进行解析,并且强制 TTL 5 秒,解析结果不会进入缓"
"存(每个域名一行,支持域名匹配规则)" "存(每个域名一行,支持域名匹配规则)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:79 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:42
msgid "" msgid ""
"Added domain names will always use 'Remote DNS' for resolution (one domain " "Added domain names will always use 'Remote DNS' for resolution (one domain "
"per line, supports domain matching rules)." "per line, supports domain matching rules)."
msgstr "" msgstr ""
"加入的域名始终使用 “远程 DNS” 进行解析(每个域名一行,支持域名匹配规则)" "加入的域名始终使用 “远程 DNS” 进行解析(每个域名一行,支持域名匹配规则)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:55 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:34
msgid "" msgid ""
"Added domain names will block DNS resolution (one domain per line, supports " "Added domain names will block DNS resolution (one domain per line, supports "
"domain matching rules)." "domain matching rules)."
msgstr "加入的域名将屏蔽 DNS 解析(每个域名一行,支持域名匹配规则)" msgstr "加入的域名将屏蔽 DNS 解析(每个域名一行,支持域名匹配规则)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:175 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:74
msgid "" msgid ""
"Added domain names will block PTR requests (one domain per line, supports " "Added domain names will block PTR requests (one domain per line, supports "
"domain matching rules)." "domain matching rules)."
@@ -89,16 +105,17 @@ msgstr "阿里云公共 DNSDNS over HTTPS"
msgid "Aliyun Public DNS (DNS over QUIC)" msgid "Aliyun Public DNS (DNS over QUIC)"
msgstr "阿里云公共 DNSDNS over QUIC" msgstr "阿里云公共 DNSDNS over QUIC"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:640 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1026
msgid "All Queries" msgid "All Queries"
msgstr "所有查询" msgstr "所有查询"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:68 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:68
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:90
msgid "Another update is already in progress." msgid "Another update is already in progress."
msgstr "另一个更新正在进行中。" msgstr "另一个更新正在进行中。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:463 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:805
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:531 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:897
msgid "Answers" msgid "Answers"
msgstr "应答结果" msgstr "应答结果"
@@ -106,7 +123,7 @@ msgstr "应答结果"
msgid "Apple domains optimization" msgid "Apple domains optimization"
msgstr "Apple 域名解析优化" msgstr "Apple 域名解析优化"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:667 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1053
msgid "" msgid ""
"Are you sure you want to clear all real-time query logs and top rankings?" "Are you sure you want to clear all real-time query logs and top rankings?"
msgstr "确定要清空所有实时查询日志和排行数据吗?" msgstr "确定要清空所有实时查询日志和排行数据吗?"
@@ -119,17 +136,17 @@ msgstr "自动保存缓存间隔(秒)"
msgid "Auto Save Stats Interval" msgid "Auto Save Stats Interval"
msgstr "自动保存统计间隔" msgstr "自动保存统计间隔"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:106 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:117
msgid "" msgid ""
"Automatically update GeoIP and GeoSite databases as well as ad filtering " "Automatically update GeoIP and GeoSite databases as well as ad filtering "
"rules through scheduled tasks." "rules through scheduled tasks."
msgstr "通过定时任务自动更新 GeoIP 和 GeoSite 数据库以及广告过滤规则。" msgstr "通过定时任务自动更新 GeoIP 和 GeoSite 数据库以及广告过滤规则。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:336 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:478
msgid "Average Processing Time" msgid "Average Processing Time"
msgstr "平均延迟" msgstr "平均延迟"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:306 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538
msgid "Avg Processing" msgid "Avg Processing"
msgstr "平均处理" msgstr "平均处理"
@@ -154,19 +171,21 @@ msgstr ""
"屏蔽 DNS 类型 65 记录(HTTPS/SVCB,用于 HTTP/3、ECH 等),强制仅使用 A/AAAA " "屏蔽 DNS 类型 65 记录(HTTPS/SVCB,用于 HTTP/3、ECH 等),强制仅使用 A/AAAA "
"解析。" "解析。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:19 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635
msgid "Block Lists" msgid "Block Lists"
msgstr "黑名单" msgstr "黑名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:24 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:71
msgid "Block PTR" msgid "Block PTR"
msgstr "PTR 黑名单" msgstr "PTR 黑名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:641 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1027
msgid "Blocked Only" msgid "Blocked Only"
msgstr "仅拦截" msgstr "仅拦截"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:314 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:456
msgid "Blocked by Filters" msgid "Blocked by Filters"
msgstr "拦截总数" msgstr "拦截总数"
@@ -188,23 +207,24 @@ msgstr "自动保存缓存"
msgid "Cache Prefetching" msgid "Cache Prefetching"
msgstr "缓存预取" msgstr "缓存预取"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:642 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1028
msgid "Cached Only" msgid "Cached Only"
msgstr "仅缓存" msgstr "仅缓存"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:325 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:467
msgid "Cached Queries" msgid "Cached Queries"
msgstr "缓存命中" msgstr "缓存命中"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:672 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:730
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1058
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:150 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:161
msgid "Check And Update" msgid "Check And Update"
msgstr "检查并更新" msgstr "检查并更新"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:148 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:159
msgid "Check And Update GeoData." msgid "Check And Update GeoData."
msgstr "检查并更新 GeoData 数据库。" msgstr "检查并更新 GeoData 数据库。"
@@ -226,7 +246,7 @@ msgstr "思科公共 DNS208.67.220.220"
msgid "Cisco Public DNS (208.67.222.222)" msgid "Cisco Public DNS (208.67.222.222)"
msgstr "思科公共 DNS208.67.222.222" msgstr "思科公共 DNS208.67.222.222"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:692 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1078
msgid "Clear" msgid "Clear"
msgstr "清空" msgstr "清空"
@@ -234,21 +254,22 @@ msgstr "清空"
msgid "Clear logs" msgid "Clear logs"
msgstr "清空日志" msgstr "清空日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:663 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1049
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:666 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1052
msgid "Clear query logs" msgid "Clear query logs"
msgstr "清除查询日志" msgstr "清除查询日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:511 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:861
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:878
msgid "Click to view full details" msgid "Click to view full details"
msgstr "点击查看完整详情" msgstr "点击查看完整详情"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:445 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:787
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:528 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:894
msgid "Client IP" msgid "Client IP"
msgstr "客户端 IP" msgstr "客户端 IP"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:475 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:832
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35
msgid "Close" msgid "Close"
msgstr "关闭" msgstr "关闭"
@@ -288,6 +309,10 @@ msgstr "配置文件"
msgid "Configuration Editor" msgid "Configuration Editor"
msgstr "配置编辑器" msgstr "配置编辑器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:734
msgid "Confirm"
msgstr "确认"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:199 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:199
msgid "Custom China DNS" msgid "Custom China DNS"
msgstr "自定义国内 DNS" msgstr "自定义国内 DNS"
@@ -296,7 +321,7 @@ msgstr "自定义国内 DNS"
msgid "Custom Config" msgid "Custom Config"
msgstr "自定义" msgstr "自定义"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:127 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:58
msgid "" msgid ""
"Custom Hosts rewrite, for example: baidu.com 10.0.0.1 (one rule per line, " "Custom Hosts rewrite, for example: baidu.com 10.0.0.1 (one rule per line, "
"supports domain matching rules)." "supports domain matching rules)."
@@ -311,7 +336,7 @@ msgstr "自选 IP"
msgid "Custom Stream Media DNS" msgid "Custom Stream Media DNS"
msgstr "自定义流媒体 DNS" msgstr "自定义流媒体 DNS"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:21 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:47
msgid "DDNS Lists" msgid "DDNS Lists"
msgstr "DDNS 域名" msgstr "DDNS 域名"
@@ -323,7 +348,7 @@ msgstr "DNS 缓存大小"
msgid "DNS Forward" msgid "DNS Forward"
msgstr "DNS 转发" msgstr "DNS 转发"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:302 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:444
msgid "DNS Queries Total" msgid "DNS Queries Total"
msgstr "DNS 查询总数" msgstr "DNS 查询总数"
@@ -337,7 +362,7 @@ msgstr "DNS 查询请求并发数,允许同时发起请求的上游 DNS 服务
msgid "DNS redirect" msgid "DNS redirect"
msgstr "DNS 重定向" msgstr "DNS 重定向"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:149 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:160
msgid "Database Update" msgid "Database Update"
msgstr "数据库更新" msgstr "数据库更新"
@@ -368,15 +393,24 @@ msgstr ""
msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)" msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)"
msgstr "DoH/TCP/DoT 连接复用空闲保持时间(默认 30 秒)" msgstr "DoH/TCP/DoT 连接复用空闲保持时间(默认 30 秒)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:529 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:895
msgid "Domain & Record" msgid "Domain & Record"
msgstr "域名与类型" msgstr "域名与类型"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:532 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:627
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:655
msgid "Domain name cannot be empty."
msgstr "域名不能为空"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720
msgid "Domain:"
msgstr "域名:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:898
msgid "Elapsed" msgid "Elapsed"
msgstr "耗时" msgstr "耗时"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:111 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:122
msgid "Enable Auto Database Update" msgid "Enable Auto Database Update"
msgstr "启用自动更新" msgstr "启用自动更新"
@@ -422,35 +456,35 @@ msgstr "填写需要导出的 GeoSite.dat 类别条目,允许添加多个标
msgid "Error" msgid "Error"
msgstr "错误" msgstr "错误"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:115 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:126
msgid "Every Day" msgid "Every Day"
msgstr "每天" msgstr "每天"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:120 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:131
msgid "Every Friday" msgid "Every Friday"
msgstr "每周五" msgstr "每周五"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:116 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:127
msgid "Every Monday" msgid "Every Monday"
msgstr "每周一" msgstr "每周一"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:121 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:132
msgid "Every Saturday" msgid "Every Saturday"
msgstr "每周六" msgstr "每周六"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:122 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:133
msgid "Every Sunday" msgid "Every Sunday"
msgstr "每周日" msgstr "每周日"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:119 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:130
msgid "Every Thursday" msgid "Every Thursday"
msgstr "每周四" msgstr "每周四"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:117 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:128
msgid "Every Tuesday" msgid "Every Tuesday"
msgstr "每周二" msgstr "每周二"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:118 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:129
msgid "Every Wednesday" msgid "Every Wednesday"
msgstr "每周三" msgstr "每周三"
@@ -463,19 +497,23 @@ msgstr "导出目录:/var/mosdns"
msgid "Failed to clean logs." msgid "Failed to clean logs."
msgstr "清理日志失败:%s" msgstr "清理日志失败:%s"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:686 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1072
msgid "Failed to clear query logs:" msgid "Failed to clear query logs:"
msgstr "清除查询日志失败:" msgstr "清除查询日志失败:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:89 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:99
msgid "Failed to start update." msgid "Failed to start update."
msgstr "启动更新失败" msgstr "启动更新失败"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:970
msgid "Failed to update query logs:" msgid "Failed to update query logs:"
msgstr "更新查询日志失败:" msgstr "更新查询日志失败:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:658 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:699
msgid "Failed to update rule file: %s"
msgstr "更新规则文件失败: %s"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1044
msgid "First Page / Resume" msgid "First Page / Resume"
msgstr "重置并恢复" msgstr "重置并恢复"
@@ -515,11 +553,11 @@ msgstr "强制将所有本地 DNS 查询重定向到 MosDNS,即 DNS 劫持。"
msgid "Forward Dnsmasq Domain Name resolution requests to MosDNS" msgid "Forward Dnsmasq Domain Name resolution requests to MosDNS"
msgstr "将 Dnsmasq 域名解析请求转发到 MosDNS 服务器" msgstr "将 Dnsmasq 域名解析请求转发到 MosDNS 服务器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:136 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:147
msgid "Full" msgid "Full"
msgstr "全量" msgstr "全量"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:134 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:145
msgid "Full: includes all Countries and Private IP addresses." msgid "Full: includes all Countries and Private IP addresses."
msgstr "全量:包含所有国家和私有 IP 地址。" msgstr "全量:包含所有国家和私有 IP 地址。"
@@ -531,7 +569,7 @@ msgstr "GeoData 导出"
msgid "GeoIP Tags" msgid "GeoIP Tags"
msgstr "GeoIP 标签" msgstr "GeoIP 标签"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:131 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:142
msgid "GeoIP Type" msgid "GeoIP Type"
msgstr "GeoIP 类型" msgstr "GeoIP 类型"
@@ -543,7 +581,7 @@ msgstr "GeoSite 标签"
msgid "Geodata Update" msgid "Geodata Update"
msgstr "更新数据库" msgstr "更新数据库"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:141 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:152
msgid "GitHub Proxy" msgid "GitHub Proxy"
msgstr "GitHub 代理" msgstr "GitHub 代理"
@@ -562,11 +600,11 @@ msgstr "谷歌公共 DNS8.8.8.8"
msgid "Grant UCI access for luci-app-mosdns" msgid "Grant UCI access for luci-app-mosdns"
msgstr "授予 luci-app-mosdns 的 UCI 访问权限" msgstr "授予 luci-app-mosdns 的 UCI 访问权限"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:20 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:39
msgid "Grey Lists" msgid "Grey Lists"
msgstr "灰名单" msgstr "灰名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:22 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:55
msgid "Hosts" msgid "Hosts"
msgstr "Hosts 列表" msgstr "Hosts 列表"
@@ -576,15 +614,15 @@ msgstr "IP 地址"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410
msgid "" msgid ""
"IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" target=\"_blank" "IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" "
"\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a href=\"https://" "target=\"_blank\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a "
"www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-" "href=\"https://www.cloudflare.com/ips-v6\" target=\"_blank\">https://www."
"v6</a>" "cloudflare.com/ips-v6</a>"
msgstr "" msgstr ""
"IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" target=\"_blank" "IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" "
"\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a href=\"https://" "target=\"_blank\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a "
"www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-" "href=\"https://www.cloudflare.com/ips-v6\" target=\"_blank\">https://www."
"v6</a>" "cloudflare.com/ips-v6</a>"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190
msgid "" msgid ""
@@ -606,13 +644,17 @@ msgstr "空闲超时"
msgid "Info" msgid "Info"
msgstr "信息" msgstr "信息"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:828
msgid "Intercept"
msgstr "拦截"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:347 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:347
msgid "" msgid ""
"Interval for the background thread to scan the cache for prefetching (in " "Interval for the background thread to scan the cache for prefetching (in "
"seconds)." "seconds)."
msgstr "后台线程扫描缓存以进行预取的时间间隔(单位:秒)" msgstr "后台线程扫描缓存以进行预取的时间间隔(单位:秒)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:337 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:479
msgid "Latency" msgid "Latency"
msgstr "延迟" msgstr "延迟"
@@ -633,11 +675,11 @@ msgstr "监听地址"
msgid "Listen Port" msgid "Listen Port"
msgstr "监听端口" msgstr "监听端口"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:137 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:148
msgid "Little" msgid "Little"
msgstr "轻量" msgstr "轻量"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:132 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:143
msgid "Little: only include Mainland China and Private IP addresses." msgid "Little: only include Mainland China and Private IP addresses."
msgstr "轻量:仅包含中国大陆和私有 IP 地址。" msgstr "轻量:仅包含中国大陆和私有 IP 地址。"
@@ -700,12 +742,12 @@ 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:33
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:106 #: 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/logs.js:70
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:708 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1094
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3 #: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3
msgid "MosDNS" msgid "MosDNS"
msgstr "MosDNS" msgstr "MosDNS"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:279 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:510
msgid "" msgid ""
"MosDNS API is unreachable. Please ensure MosDNS is running and stats_api " "MosDNS API is unreachable. Please ensure MosDNS is running and stats_api "
"plugin is enabled." "plugin is enabled."
@@ -725,15 +767,15 @@ msgstr "未运行"
msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS" msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS"
msgstr "自定义 Netflix、Disney+、Hulu 以及 “流媒体” 规则列表的 DNS 服务器" msgstr "自定义 Netflix、Disney+、Hulu 以及 “流媒体” 规则列表的 DNS 服务器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:561 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:927
msgid "Next" msgid "Next"
msgstr "下一页" msgstr "下一页"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:428 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:768
msgid "No DNS answer records returned." msgid "No DNS answer records returned."
msgstr "无 DNS 应答记录" msgstr "无 DNS 应答记录"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:353 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:560
msgid "No data available" msgid "No data available"
msgstr "暂无数据" msgstr "暂无数据"
@@ -741,18 +783,22 @@ msgstr "暂无数据"
msgid "No log data." msgid "No log data."
msgstr "无日志数据。" msgstr "无日志数据。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:520 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:886
msgid "No query log entries found." msgid "No query log entries found."
msgstr "未找到查询日志记录。" msgstr "未找到查询日志记录。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:904
msgid "Page %d / %d (%d entries)" msgid "Page %d / %d (%d entries)"
msgstr "第 %d / %d 页(%d 条记录)" msgstr "第 %d / %d 页(%d 条记录)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:340 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:438
msgid "Per-query speed" msgid "Per-query speed"
msgstr "单次查询速度" msgstr "单次查询速度"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:821
msgid "Permit"
msgstr "放行"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:301 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:301
msgid "" msgid ""
"Please provide the IP address you use when accessing foreign websites. This " "Please provide the IP address you use when accessing foreign websites. This "
@@ -786,7 +832,7 @@ msgstr "当剩余 TTL 小于此值(秒)时触发预取"
msgid "Prevent DNS Leaks" msgid "Prevent DNS Leaks"
msgstr "防止 DNS 泄漏" msgstr "防止 DNS 泄漏"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:550 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:916
msgid "Previous" msgid "Previous"
msgstr "上一页" msgstr "上一页"
@@ -805,7 +851,7 @@ msgstr "Quad9 公共 DNS149.112.112.112"
msgid "Quad9 Public DNS (9.9.9.9)" msgid "Quad9 Public DNS (9.9.9.9)"
msgstr "Quad9 公共 DNS9.9.9.9" msgstr "Quad9 公共 DNS9.9.9.9"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:469 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:811
msgid "Query Log Details" msgid "Query Log Details"
msgstr "查询日志详情" msgstr "查询日志详情"
@@ -815,7 +861,7 @@ msgid ""
"consume more memory)" "consume more memory)"
msgstr "查询日志环形缓冲区容量(FIFO 覆盖,默认 2000,数值越大占用内存越多)" msgstr "查询日志环形缓冲区容量(FIFO 覆盖,默认 2000,数值越大占用内存越多)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:681 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1067
msgid "Query logs cleared successfully." msgid "Query logs cleared successfully."
msgstr "查询日志清除成功。" msgstr "查询日志清除成功。"
@@ -823,15 +869,15 @@ msgstr "查询日志清除成功。"
msgid "RUNNING" msgid "RUNNING"
msgstr "运行中" msgstr "运行中"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:712 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1098
msgid "Real-time Query Logs" msgid "Real-time Query Logs"
msgstr "实时查询日志" msgstr "实时查询日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:63
msgid "Redirect" msgid "Redirect"
msgstr "重定向" msgstr "重定向"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:151 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:66
msgid "" msgid ""
"Redirecting requests for domain names. Request domain A, but return records " "Redirecting requests for domain names. Request domain A, but return records "
"for domain B, for example: baidu.com qq.com (one rule per line)." "for domain B, for example: baidu.com qq.com (one rule per line)."
@@ -855,11 +901,11 @@ msgstr "远程 DNS 服务器"
msgid "Ring Buffer Capacity" msgid "Ring Buffer Capacity"
msgstr "环形缓冲区容量" msgstr "环形缓冲区容量"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:457 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:799
msgid "Rule Hit" msgid "Rule Hit"
msgstr "命中规则" msgstr "命中规则"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:11 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:9
msgid "Rule Settings" msgid "Rule Settings"
msgstr "自定义规则列表" msgstr "自定义规则列表"
@@ -875,7 +921,7 @@ msgstr "保存统计数据与查询日志在本地,并在下次启动时重新
msgid "Save the cache locally and reload the cache dump on the next startup" msgid "Save the cache locally and reload the cache dump on the next startup"
msgstr "保存缓存到本地文件,以供下次启动时重新载入使用" msgstr "保存缓存到本地文件,以供下次启动时重新载入使用"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:629 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1015
msgid "Search domain or client IP..." msgid "Search domain or client IP..."
msgstr "搜索域名或客户端 IP..." msgstr "搜索域名或客户端 IP..."
@@ -887,7 +933,7 @@ msgstr "设置日志文件的最大容量(单位:MB)。"
msgid "Starting update..." msgid "Starting update..."
msgstr "正在启动更新..." msgstr "正在启动更新..."
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:708 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1094
#: 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:38
msgid "Statistics" msgid "Statistics"
msgstr "统计" msgstr "统计"
@@ -896,11 +942,11 @@ msgstr "统计"
msgid "Stats Dump" msgid "Stats Dump"
msgstr "自动保存统计" msgstr "自动保存统计"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:530 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:896
msgid "Status" msgid "Status"
msgstr "状态" msgstr "状态"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:25 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:79
msgid "Streaming Media" msgid "Streaming Media"
msgstr "流媒体" msgstr "流媒体"
@@ -916,6 +962,10 @@ msgstr "支持本地文件,例如:file:///var/mosdns/example.txt"
msgid "TCP/DoT Connection Multiplexing" msgid "TCP/DoT Connection Multiplexing"
msgstr "TCP/DoT 连接复用" msgstr "TCP/DoT 连接复用"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:723
msgid "Target file: %s (supports domain matching rules)"
msgstr "目标文件: %s (支持域名匹配规则)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:210 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:210
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:258 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:258
msgid "Tencent Public DNS (119.28.28.28)" msgid "Tencent Public DNS (119.28.28.28)"
@@ -930,7 +980,7 @@ msgstr "腾讯公共 DNS119.29.29.29"
msgid "Tencent Public DNS (DNS over HTTPS)" msgid "Tencent Public DNS (DNS over HTTPS)"
msgstr "腾讯公共 DNSDNS over HTTPS" msgstr "腾讯公共 DNSDNS over HTTPS"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:12 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:10
msgid "The list of rules only apply to 'Default Config' profiles." msgid "The list of rules only apply to 'Default Config' profiles."
msgstr "规则列表仅适用于 “内置预设” 配置文件。" msgstr "规则列表仅适用于 “内置预设” 配置文件。"
@@ -952,20 +1002,20 @@ msgstr ""
"这是文件 “/etc/mosdns/config_custom.yaml” 的内容,您的 MosDNS 配置将从此文件" "这是文件 “/etc/mosdns/config_custom.yaml” 的内容,您的 MosDNS 配置将从此文件"
"生成。仅接受 yaml 格式的配置内容。" "生成。仅接受 yaml 格式的配置内容。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:449 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:791
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:527 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:893
msgid "Time" msgid "Time"
msgstr "时间" msgstr "时间"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:386 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599
msgid "Top Blocked Domains" msgid "Top Blocked Domains"
msgstr "拦截域名 (Top 10)" msgstr "拦截域名 (Top 10)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:393 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:606
msgid "Top Clients" msgid "Top Clients"
msgstr "活跃客户端 (Top 10)" msgstr "活跃客户端 (Top 10)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:379 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:592
msgid "Top Queried Domains" msgid "Top Queried Domains"
msgstr "查询域名 (Top 10)" msgstr "查询域名 (Top 10)"
@@ -978,38 +1028,23 @@ msgid "TrafficRoute Public DNS (180.184.2.2)"
msgstr "火山引擎公共 DNS180.184.2.2" msgstr "火山引擎公共 DNS180.184.2.2"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:494 #: 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:17
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:48
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:67
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:72
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:91
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:96
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:115
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:120
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:139
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:144
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:163
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:168
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:187
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:192
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:211
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:216
msgid "Unable to save contents: %s" msgid "Unable to save contents: %s"
msgstr "无法保存内容:%s" msgstr "无法保存内容:%s"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:114 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:125
msgid "Update Cycle" msgid "Update Cycle"
msgstr "更新周期" msgstr "更新周期"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:105 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:116
msgid "Update GeoIP & GeoSite databases" msgid "Update GeoIP & GeoSite databases"
msgstr "更新广告规则、GeoIP & GeoSite 数据库" msgstr "更新广告规则、GeoIP & GeoSite 数据库"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:125 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:136
msgid "Update Time" msgid "Update Time"
msgstr "更新时间" msgstr "更新时间"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:142 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:153
msgid "" msgid ""
"Update data files with GitHub Proxy, leave blank to disable proxy downloads." "Update data files with GitHub Proxy, leave blank to disable proxy downloads."
msgstr "通过 GitHub 代理更新数据文件,留空则禁用代理下载。" msgstr "通过 GitHub 代理更新数据文件,留空则禁用代理下载。"
@@ -1018,7 +1053,7 @@ msgstr "通过 GitHub 代理更新数据文件,留空则禁用代理下载。"
msgid "Update failed" msgid "Update failed"
msgstr "更新失败" msgstr "更新失败"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:95 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:106
msgid "Update failed: %s" msgid "Update failed: %s"
msgstr "更新失败: %s" msgstr "更新失败: %s"
@@ -1030,7 +1065,7 @@ msgstr "更新成功"
msgid "Updating Database..." msgid "Updating Database..."
msgstr "更新数据库..." msgstr "更新数据库..."
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:453 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:795
msgid "Upstream" msgid "Upstream"
msgstr "上游服务器" msgstr "上游服务器"
@@ -1038,7 +1073,7 @@ msgstr "上游服务器"
msgid "Warning" msgid "Warning"
msgstr "警告" msgstr "警告"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:199 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:82
msgid "" msgid ""
"When enabling 'Custom Stream Media DNS', added domains will always use the " "When enabling 'Custom Stream Media DNS', added domains will always use the "
"'Streaming Media DNS server' for resolution (one domain per line, supports " "'Streaming Media DNS server' for resolution (one domain per line, supports "
@@ -1055,7 +1090,9 @@ msgstr ""
"使用自定义规则来源时,请使用 MosDNS 支持的规则类型(域名列表或 AdGuardHome 规" "使用自定义规则来源时,请使用 MosDNS 支持的规则类型(域名列表或 AdGuardHome 规"
"则)" "则)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:18 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635
msgid "White Lists" msgid "White Lists"
msgstr "白名单" msgstr "白名单"
@@ -1069,18 +1106,28 @@ msgstr "信风公共 DNS114.114.114.114"
msgid "Xinfeng Public DNS (114.114.115.115)" msgid "Xinfeng Public DNS (114.114.115.115)"
msgstr "信风公共 DNS114.114.115.115" msgstr "信风公共 DNS114.114.115.115"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:143 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:154
msgid "https://gh-proxy.com" msgid "https://gh-proxy.com"
msgstr "https://gh-proxy.com" msgstr "https://gh-proxy.com"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:303 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:427
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:540
msgid "● Live" msgid "● Live"
msgstr "● 实时" msgstr "● 实时"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:148 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:223
msgid "● Live Auto-refresh" msgid "● Live Auto-refresh"
msgstr "● 实时刷新" msgstr "● 实时刷新"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:152 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:227
msgid "❚❚ Paused (Page %d)" msgid "❚❚ Paused (Page %d)"
msgstr "❚❚ 暂停 (第 %d 页)" msgstr "❚❚ 暂停 (第 %d 页)"
#~ msgid "Action"
#~ msgstr "操作"
#~ msgid "Add %s to Blocklist"
#~ msgstr "将 %s 添加到黑名单"
#~ msgid "Add %s to Whitelist"
#~ msgstr "将 %s 添加到白名单"
@@ -3,9 +3,13 @@
"description": "Grant UCI access for luci-app-mosdns", "description": "Grant UCI access for luci-app-mosdns",
"read": { "read": {
"file": { "file": {
"/etc/hosts": [ "read" ],
"/etc/init.d/mosdns": [ "exec" ], "/etc/init.d/mosdns": [ "exec" ],
"/etc/mosdns/config_custom.yaml": [ "read" ], "/etc/mosdns/config_custom.yaml": [ "read" ],
"/etc/mosdns/rule/*": [ "read" ], "/etc/mosdns/rule/*": [ "read" ],
"/tmp/dhcp.leases": [ "read" ],
"/tmp/hosts/*": [ "read" ],
"/tmp/odhcpd.leases": [ "read" ],
"/usr/bin/mosdns": [ "exec" ], "/usr/bin/mosdns": [ "exec" ],
"/usr/share/mosdns/mosdns.uc": [ "exec" ], "/usr/share/mosdns/mosdns.uc": [ "exec" ],
"/var/log/*": [ "read" ], "/var/log/*": [ "read" ],
@@ -19,6 +23,7 @@
"clear_query_logs", "clear_query_logs",
"flush_cache", "flush_cache",
"get_history", "get_history",
"get_leases",
"get_logs", "get_logs",
"get_stats", "get_stats",
"get_top", "get_top",
@@ -88,6 +88,91 @@ function call_mosdns_api(endpoint, method) {
} }
} }
function get_client_leases() {
let leases = {};
let add_lease = function(ip, name) {
if (!ip || !name) return;
ip = replace(ip, /^\s+|\s+$/g, '');
name = replace(name, /^\s+|\s+$/g, '');
if (ip == "" || name == "" || name == "*" || name == "unknown") return;
ip = replace(ip, /\/[0-9]+$/, '');
ip = replace(ip, /^::ffff:/i, '');
leases[ip] = name;
try {
if (type(lc) == 'function') {
leases[lc(ip)] = name;
}
} catch (e) {}
};
let parse_hosts_file = function(filepath) {
try {
if (stat(filepath)) {
let content = readfile(filepath);
if (content) {
let lines = split(content, '\n');
for (let i = 0; i < length(lines); i++) {
let line = replace(lines[i], /^\s+|\s+$/g, '');
if (line == "" || match(line, /^#/)) continue;
let parts = split(line, /[ \t]+/);
if (length(parts) >= 2) {
add_lease(parts[0], parts[1]);
}
}
}
}
} catch (e) {}
};
try {
if (stat('/tmp/dhcp.leases')) {
let content = readfile('/tmp/dhcp.leases');
if (content) {
let lines = split(content, '\n');
for (let i = 0; i < length(lines); i++) {
let line = replace(lines[i], /^\s+|\s+$/g, '');
if (line == "" || match(line, /^#/)) continue;
let parts = split(line, /[ \t]+/);
if (length(parts) >= 4) {
add_lease(parts[2], parts[3]);
}
}
}
}
} catch (e) {}
parse_hosts_file('/tmp/hosts/odhcpd');
parse_hosts_file('/tmp/hosts/dhcp');
try {
if (stat('/tmp/odhcpd.leases')) {
let content = readfile('/tmp/odhcpd.leases');
if (content) {
let lines = split(content, '\n');
for (let i = 0; i < length(lines); i++) {
let line = replace(lines[i], /^\s+|\s+$/g, '');
if (line == "" || match(line, /^#/)) continue;
let parts = split(line, /[ \t]+/);
if (length(parts) >= 6) {
let host = parts[3];
for (let j = 5; j < length(parts); j++) {
add_lease(parts[j], host);
}
}
}
}
}
} catch (e) {}
parse_hosts_file('/etc/hosts');
return leases;
}
const methods = { const methods = {
get_stats: { get_stats: {
call: function() { call: function() {
@@ -107,7 +192,17 @@ const methods = {
args: { limit: 10 }, args: { limit: 10 },
call: function(request) { call: function(request) {
let limit = request?.args?.limit || 10; let limit = request?.args?.limit || 10;
return call_mosdns_api(`/api/v1/top?limit=${limit}`, 'GET'); let res = call_mosdns_api(`/api/v1/top?limit=${limit}`, 'GET');
if (type(res) == 'object' && !res.error) {
res.leases = get_client_leases();
}
return res;
}
},
get_leases: {
call: function() {
return { leases: get_client_leases() };
} }
}, },
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=mosdns PKG_NAME:=mosdns
PKG_VERSION:=5.3.4 PKG_VERSION:=5.3.4
PKG_RELEASE:=20 PKG_RELEASE:=21
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/IrineSistiana/mosdns/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/IrineSistiana/mosdns/tar.gz/v$(PKG_VERSION)?
@@ -0,0 +1,136 @@
From 6af46a12d9149422a9b9cac5ca3510c25ca7220f Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Wed, 2 Sep 2026 23:08:38 +0800
Subject: [PATCH 1/3] feat(stats_api): support real-time QPS in stats endpoint
Signed-off-by: sbwml <admin@cooluc.com>
---
plugin/executable/stats_api/stats_api.go | 51 +++++++++++++++++++
plugin/executable/stats_api/stats_api_test.go | 24 +++++++++
2 files changed, 75 insertions(+)
--- a/plugin/executable/stats_api/stats_api.go
+++ b/plugin/executable/stats_api/stats_api.go
@@ -513,6 +513,7 @@ type StatsAPI struct {
blockedQueries atomic.Uint64
cachedQueries atomic.Uint64
totalLatencyUs atomic.Uint64
+ currentQPS atomic.Uint64
updatedCount atomic.Uint64
closeNotify chan struct{}
@@ -575,6 +576,7 @@ func NewStatsAPI(args *Args, logger *zap
s.logger.Error("failed to load stats dump", zap.Error(err))
}
s.startDumpLoop()
+ s.startQPSLoop()
if len(args.Listen) > 0 {
srv := &http.Server{
@@ -641,6 +643,7 @@ func (s *StatsAPI) handleStats(w http.Re
blockedPct = math.Round(blockedPct*100) / 100
cachedPct = math.Round(cachedPct*100) / 100
avgLat = math.Round(avgLat*100) / 100
+ qps := s.currentQPS.Load()
resp := map[string]any{
"total_queries": total,
@@ -649,6 +652,7 @@ func (s *StatsAPI) handleStats(w http.Re
"blocked_percentage": blockedPct,
"cached_percentage": cachedPct,
"avg_latency_ms": avgLat,
+ "qps": qps,
}
w.Header().Set("Content-Type", "application/json")
@@ -878,6 +882,53 @@ func (s *StatsAPI) startDumpLoop() {
case <-s.closeNotify:
return
}
+ }
+ }()
+}
+
+func (s *StatsAPI) startQPSLoop() {
+ go func() {
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+
+ const windowSize = 5
+ history := make([]uint64, windowSize)
+ timestamps := make([]int64, windowSize)
+ lastTotal := s.totalQueries.Load()
+
+ for {
+ select {
+ case now := <-ticker.C:
+ curTotal := s.totalQueries.Load()
+ var delta uint64
+ if curTotal >= lastTotal {
+ delta = curTotal - lastTotal
+ }
+ lastTotal = curTotal
+
+ nowSec := now.Unix()
+ idx := int(nowSec % windowSize)
+ history[idx] = delta
+ timestamps[idx] = nowSec
+
+ var sum uint64
+ validSecs := 0
+ for i := 0; i < windowSize; i++ {
+ if timestamps[i] > 0 && nowSec-timestamps[i] < windowSize {
+ sum += history[i]
+ validSecs++
+ }
+ }
+
+ var qps uint64
+ if validSecs > 0 {
+ qps = (sum + uint64(validSecs)/2) / uint64(validSecs)
+ }
+ s.currentQPS.Store(qps)
+
+ case <-s.closeNotify:
+ return
+ }
}
}()
}
--- a/plugin/executable/stats_api/stats_api_test.go
+++ b/plugin/executable/stats_api/stats_api_test.go
@@ -281,6 +281,9 @@ func TestStatsAPIHTTPEndpoints(t *testin
if statsResp["total_queries"].(float64) != 1 {
t.Errorf("expected total_queries 1, got %v", statsResp["total_queries"])
}
+ if _, ok := statsResp["qps"]; !ok {
+ t.Errorf("expected qps field in stats response")
+ }
// Test GET /api/v1/history
reqHist := httptest.NewRequest(http.MethodGet, "/api/v1/history?points=24", nil)
@@ -546,3 +549,24 @@ func TestStatsAPICachedPercentageExclude
}
}
+func TestStatsAPIQPS(t *testing.T) {
+ s := NewStatsAPI(&Args{Capacity: 100}, zap.NewNop())
+ defer s.Close()
+
+ // Initially QPS is 0
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/stats", nil)
+ w := httptest.NewRecorder()
+ s.Router().ServeHTTP(w, req)
+
+ var statsResp map[string]any
+ if err := json.Unmarshal(w.Body.Bytes(), &statsResp); err != nil {
+ t.Fatalf("failed to unmarshal: %v", err)
+ }
+ if qps, ok := statsResp["qps"]; !ok {
+ t.Fatalf("expected qps field in stats response")
+ } else if qps.(float64) != 0 {
+ t.Errorf("expected initial qps 0, got %v", qps)
+ }
+}
+
+
@@ -0,0 +1,432 @@
From aa3e90c717c234a3483716c81a349736b0d1b27c Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Thu, 3 Sep 2026 22:34:10 +0800
Subject: [PATCH 2/3] feat(stats_api): exclude hosts and arbitrary queries from
cache hit percentage
Exclude queries resolved by hosts and arbitrary plugins from the cache
hit rate calculation (total - blocked - hosts - arbitrary), as locally
resolved queries bypass cache and should not skew the cache hit rate.
Also track hosts_queries and arbitrary_queries in stats endpoint and
dump data.
Signed-off-by: sbwml <admin@cooluc.com>
---
pkg/query_context/context.go | 22 +++
plugin/executable/arbitrary/arbitrary.go | 1 +
plugin/executable/cache/cache.go | 2 +
plugin/executable/forward/forward.go | 2 +
plugin/executable/hosts/hosts.go | 1 +
plugin/executable/stats_api/stats_api.go | 81 +++++++---
plugin/executable/stats_api/stats_api_test.go | 143 ++++++++++++++++++
7 files changed, 227 insertions(+), 25 deletions(-)
--- a/pkg/query_context/context.go
+++ b/pkg/query_context/context.go
@@ -75,6 +75,8 @@ type Context struct {
UpstreamSelected *UpstreamLog
RuleHits []RuleHit
CacheState CacheLog
+ FromHosts bool
+ FromArbitrary bool
// lazy init.
kv map[uint32]any
@@ -107,6 +109,16 @@ func (ctx *Context) SetCacheState(hit, l
}
}
+func (ctx *Context) SetFromHosts() {
+ ctx.FromHosts = true
+ ctx.FromArbitrary = false
+}
+
+func (ctx *Context) SetFromArbitrary() {
+ ctx.FromArbitrary = true
+ ctx.FromHosts = false
+}
+
var contextUid atomic.Uint32
type ServerMeta = server.QueryMeta
@@ -241,6 +253,9 @@ func (ctx *Context) CopyTo(d *Context) *
}
d.upstreamOpt = ctx.upstreamOpt
+ d.FromHosts = ctx.FromHosts
+ d.FromArbitrary = ctx.FromArbitrary
+
d.kv = copyMap(ctx.kv)
d.marks = copyMap(ctx.marks)
return d
@@ -350,6 +365,13 @@ func (ctx *Context) MarshalLogObject(enc
return nil
}))
+ if ctx.FromHosts {
+ encoder.AddBool("hosts", true)
+ }
+ if ctx.FromArbitrary {
+ encoder.AddBool("arbitrary", true)
+ }
+
if r := ctx.resp; r != nil {
encoder.AddInt("rcode", r.Rcode)
encoder.AddInt("resp_size", r.Len())
--- a/plugin/executable/arbitrary/arbitrary.go
+++ b/plugin/executable/arbitrary/arbitrary.go
@@ -99,6 +99,7 @@ func (a *Arbitrary) Exec(_ context.Conte
if inner := a.m.Load(); inner != nil {
if r := inner.Reply(qCtx.Q()); r != nil {
qCtx.SetResponse(r)
+ qCtx.SetFromArbitrary()
}
}
return nil
--- a/plugin/executable/cache/cache.go
+++ b/plugin/executable/cache/cache.go
@@ -234,6 +234,8 @@ func (c *Cache) Exec(ctx context.Context
c.hitTotal.Inc()
cachedResp.Id = q.Id // change msg id
qCtx.SetResponse(cachedResp)
+ qCtx.FromHosts = false
+ qCtx.FromArbitrary = false
if v, _, ok := c.backend.Get(key(msgKey)); ok && v != nil {
v.hitCount.Add(1)
ttl := int(v.expirationTime.Sub(v.storedTime).Seconds())
--- a/plugin/executable/forward/forward.go
+++ b/plugin/executable/forward/forward.go
@@ -201,6 +201,8 @@ func (f *Forward) Exec(ctx context.Conte
return err
}
qCtx.SetResponse(r)
+ qCtx.FromHosts = false
+ qCtx.FromArbitrary = false
return nil
}
--- a/plugin/executable/hosts/hosts.go
+++ b/plugin/executable/hosts/hosts.go
@@ -113,6 +113,7 @@ func (h *Hosts) Exec(_ context.Context,
r := inner.LookupMsg(qCtx.Q())
if r != nil {
qCtx.SetResponse(r)
+ qCtx.SetFromHosts()
}
}
return nil
--- a/plugin/executable/stats_api/stats_api.go
+++ b/plugin/executable/stats_api/stats_api.go
@@ -487,18 +487,20 @@ func (h *HistoryStats) Import(points map
}
type StatsDumpData struct {
- Version int `json:"version"`
- Timestamp int64 `json:"timestamp"`
- TotalQueries uint64 `json:"total_queries"`
- BlockedQueries uint64 `json:"blocked_queries"`
- CachedQueries uint64 `json:"cached_queries"`
- TotalLatencyUs uint64 `json:"total_latency_us"`
- TopDomains map[string]uint64 `json:"top_domains,omitempty"`
- TopClients map[string]uint64 `json:"top_clients,omitempty"`
- TopBlocked map[string]uint64 `json:"top_blocked,omitempty"`
- History map[int64]HistoryBucketData `json:"history,omitempty"`
- Logs []LogEntry `json:"logs,omitempty"`
- SeqID uint64 `json:"seq_id,omitempty"`
+ Version int `json:"version"`
+ Timestamp int64 `json:"timestamp"`
+ TotalQueries uint64 `json:"total_queries"`
+ BlockedQueries uint64 `json:"blocked_queries"`
+ CachedQueries uint64 `json:"cached_queries"`
+ HostsQueries uint64 `json:"hosts_queries,omitempty"`
+ ArbitraryQueries uint64 `json:"arbitrary_queries,omitempty"`
+ TotalLatencyUs uint64 `json:"total_latency_us"`
+ TopDomains map[string]uint64 `json:"top_domains,omitempty"`
+ TopClients map[string]uint64 `json:"top_clients,omitempty"`
+ TopBlocked map[string]uint64 `json:"top_blocked,omitempty"`
+ History map[int64]HistoryBucketData `json:"history,omitempty"`
+ Logs []LogEntry `json:"logs,omitempty"`
+ SeqID uint64 `json:"seq_id,omitempty"`
}
type StatsAPI struct {
@@ -509,11 +511,13 @@ type StatsAPI struct {
topStats *TopStats
historyStats *HistoryStats
- totalQueries atomic.Uint64
- blockedQueries atomic.Uint64
- cachedQueries atomic.Uint64
- totalLatencyUs atomic.Uint64
- currentQPS atomic.Uint64
+ totalQueries atomic.Uint64
+ blockedQueries atomic.Uint64
+ cachedQueries atomic.Uint64
+ hostsQueries atomic.Uint64
+ arbitraryQueries atomic.Uint64
+ totalLatencyUs atomic.Uint64
+ currentQPS atomic.Uint64
updatedCount atomic.Uint64
closeNotify chan struct{}
@@ -626,6 +630,8 @@ func (s *StatsAPI) handleStats(w http.Re
total := s.totalQueries.Load()
blocked := s.blockedQueries.Load()
cached := s.cachedQueries.Load()
+ hosts := s.hostsQueries.Load()
+ arbitrary := s.arbitraryQueries.Load()
latUs := s.totalLatencyUs.Load()
var blockedPct, cachedPct, avgLat float64
@@ -633,8 +639,9 @@ func (s *StatsAPI) handleStats(w http.Re
blockedPct = float64(blocked) / float64(total) * 100.0
avgLat = (float64(latUs) / float64(total)) / 1000.0
}
- if total > blocked {
- cachedPct = float64(cached) / float64(total-blocked) * 100.0
+ excluded := blocked + hosts + arbitrary
+ if total > excluded {
+ cachedPct = float64(cached) / float64(total-excluded) * 100.0
if cachedPct > 100.0 {
cachedPct = 100.0
}
@@ -649,6 +656,8 @@ func (s *StatsAPI) handleStats(w http.Re
"total_queries": total,
"blocked_queries": blocked,
"cached_queries": cached,
+ "hosts_queries": hosts,
+ "arbitrary_queries": arbitrary,
"blocked_percentage": blockedPct,
"cached_percentage": cachedPct,
"avg_latency_ms": avgLat,
@@ -778,12 +787,14 @@ func (s *StatsAPI) writeDump(w io.Writer
gw.Name = statsDumpHeader
data := StatsDumpData{
- Version: 1,
- Timestamp: time.Now().Unix(),
- TotalQueries: s.totalQueries.Load(),
- BlockedQueries: s.blockedQueries.Load(),
- CachedQueries: s.cachedQueries.Load(),
- TotalLatencyUs: s.totalLatencyUs.Load(),
+ Version: 1,
+ Timestamp: time.Now().Unix(),
+ TotalQueries: s.totalQueries.Load(),
+ BlockedQueries: s.blockedQueries.Load(),
+ CachedQueries: s.cachedQueries.Load(),
+ HostsQueries: s.hostsQueries.Load(),
+ ArbitraryQueries: s.arbitraryQueries.Load(),
+ TotalLatencyUs: s.totalLatencyUs.Load(),
}
data.TopDomains, data.TopClients, data.TopBlocked = s.topStats.Export()
@@ -817,6 +828,8 @@ func (s *StatsAPI) readDump(r io.Reader)
s.totalQueries.Store(data.TotalQueries)
s.blockedQueries.Store(data.BlockedQueries)
s.cachedQueries.Store(data.CachedQueries)
+ s.hostsQueries.Store(data.HostsQueries)
+ s.arbitraryQueries.Store(data.ArbitraryQueries)
s.totalLatencyUs.Store(data.TotalLatencyUs)
s.topStats.Import(data.TopDomains, data.TopClients, data.TopBlocked)
@@ -1022,10 +1035,24 @@ func (s *StatsAPI) Exec(ctx context.Cont
s.blockedQueries.Add(1)
}
+ isHosts := qCtx.FromHosts && !isBlocked && !isCached && qCtx.UpstreamSelected == nil
+ if isHosts {
+ s.hostsQueries.Add(1)
+ }
+
+ isArbitrary := qCtx.FromArbitrary && !isBlocked && !isCached && qCtx.UpstreamSelected == nil
+ if isArbitrary {
+ s.arbitraryQueries.Add(1)
+ }
+
// Extract Upstream information
var upstream string
if isCached {
upstream = "cache"
+ } else if isHosts {
+ upstream = "hosts"
+ } else if isArbitrary {
+ upstream = "arbitrary"
} else if u := qCtx.UpstreamSelected; u != nil {
if u.Addr != "" {
if !strings.Contains(u.Addr, "://") {
@@ -1083,6 +1110,10 @@ func (s *StatsAPI) Exec(ctx context.Cont
if rule == "" {
if isCached {
rule = "cache"
+ } else if isHosts {
+ rule = "hosts"
+ } else if isArbitrary {
+ rule = "arbitrary"
} else {
rule = "-"
}
--- a/plugin/executable/stats_api/stats_api_test.go
+++ b/plugin/executable/stats_api/stats_api_test.go
@@ -428,6 +428,12 @@ func TestStatsAPIPersistence(t *testing.
if i%3 == 0 {
s1.cachedQueries.Add(1)
}
+ if i%4 == 0 {
+ s1.hostsQueries.Add(1)
+ }
+ if i%5 == 0 {
+ s1.arbitraryQueries.Add(1)
+ }
s1.totalLatencyUs.Add(uint64(i * 5000))
s1.topStats.Record(fmt.Sprintf("test%d.com.", i), "192.168.1.100", i%2 == 0)
s1.historyStats.Record(time.Now(), i%2 == 0, i%3 == 0)
@@ -479,6 +485,12 @@ func TestStatsAPIPersistence(t *testing.
if s2.cachedQueries.Load() != 3 {
t.Errorf("expected 3 cached queries after load, got %d", s2.cachedQueries.Load())
}
+ if s2.hostsQueries.Load() != 2 {
+ t.Errorf("expected 2 hosts queries after load, got %d", s2.hostsQueries.Load())
+ }
+ if s2.arbitraryQueries.Load() != 2 {
+ t.Errorf("expected 2 arbitrary queries after load, got %d", s2.arbitraryQueries.Load())
+ }
totalLogs, logs := s2.ringBuffer.QueryLogs(10, 0, "", "all")
if totalLogs != 10 || len(logs) != 10 {
@@ -569,4 +581,135 @@ func TestStatsAPIQPS(t *testing.T) {
}
}
+func TestStatsAPICachedPercentageExcludesHostsAndArbitrary(t *testing.T) {
+ s := NewStatsAPI(&Args{Capacity: 100}, zap.NewNop())
+ router := s.Router()
+
+ // 100 total queries: 20 blocked, 15 hosts, 15 arbitrary, 25 cached, 25 forwarded to upstream
+ // denominator = 100 - (20 + 15 + 15) = 50
+ // cached_percentage = 25 / 50 = 50%
+ s.totalQueries.Store(100)
+ s.blockedQueries.Store(20)
+ s.hostsQueries.Store(15)
+ s.arbitraryQueries.Store(15)
+ s.cachedQueries.Store(25)
+ s.totalLatencyUs.Store(50000)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/stats", nil)
+ w := httptest.NewRecorder()
+ router.ServeHTTP(w, req)
+
+ if w.Code != http.StatusOK {
+ t.Fatalf("expected HTTP 200, got %d", w.Code)
+ }
+
+ var statsResp map[string]any
+ if err := json.Unmarshal(w.Body.Bytes(), &statsResp); err != nil {
+ t.Fatalf("failed to unmarshal stats response: %v", err)
+ }
+
+ if hostsQueries := statsResp["hosts_queries"].(float64); hostsQueries != 15 {
+ t.Errorf("expected hosts_queries 15, got %v", hostsQueries)
+ }
+ if arbitraryQueries := statsResp["arbitrary_queries"].(float64); arbitraryQueries != 15 {
+ t.Errorf("expected arbitrary_queries 15, got %v", arbitraryQueries)
+ }
+ if blockedPct := statsResp["blocked_percentage"].(float64); blockedPct != 20.0 {
+ t.Errorf("expected blocked_percentage 20.0, got %v", blockedPct)
+ }
+ if cachedPct := statsResp["cached_percentage"].(float64); cachedPct != 50.0 {
+ t.Errorf("expected cached_percentage 50.0, got %v", cachedPct)
+ }
+}
+
+func TestStatsAPIExecWithHostsAndArbitrary(t *testing.T) {
+ s := NewStatsAPI(&Args{Capacity: 100}, zap.NewNop())
+
+ // Test 1: Normal hosts resolution
+ q1 := new(dns.Msg)
+ q1.SetQuestion("lan.local.", dns.TypeA)
+ qCtx1 := query_context.NewContext(q1)
+
+ execHosts := sequence.ExecutableFunc(func(ctx context.Context, qCtx *query_context.Context) error {
+ resp := new(dns.Msg)
+ resp.SetReply(qCtx.Q())
+ resp.Answer = append(resp.Answer, &dns.A{
+ Hdr: dns.RR_Header{Name: "lan.local.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300},
+ A: []byte{192, 168, 1, 10},
+ })
+ qCtx.SetResponse(resp)
+ qCtx.SetFromHosts()
+ return nil
+ })
+
+ walker1 := sequence.NewChainWalker([]*sequence.ChainNode{{E: execHosts}}, nil)
+ if err := s.Exec(context.Background(), qCtx1, walker1); err != nil {
+ t.Fatalf("Exec failed: %v", err)
+ }
+ if s.hostsQueries.Load() != 1 {
+ t.Errorf("expected hostsQueries 1, got %d", s.hostsQueries.Load())
+ }
+ _, logs1 := s.ringBuffer.QueryLogs(1, 0, "", "all")
+ if len(logs1) != 1 || logs1[0].Upstream != "hosts" {
+ t.Errorf("expected upstream 'hosts', got %+v", logs1)
+ }
+
+ // Test 2: Hosts returning 0.0.0.0 (adblock) -> should be counted in blockedQueries, NOT hostsQueries
+ q2 := new(dns.Msg)
+ q2.SetQuestion("ad.example.com.", dns.TypeA)
+ qCtx2 := query_context.NewContext(q2)
+
+ execHostsBlock := sequence.ExecutableFunc(func(ctx context.Context, qCtx *query_context.Context) error {
+ resp := new(dns.Msg)
+ resp.SetReply(qCtx.Q())
+ resp.Answer = append(resp.Answer, &dns.A{
+ Hdr: dns.RR_Header{Name: "ad.example.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300},
+ A: []byte{0, 0, 0, 0},
+ })
+ qCtx.SetResponse(resp)
+ qCtx.SetFromHosts()
+ return nil
+ })
+
+ walker2 := sequence.NewChainWalker([]*sequence.ChainNode{{E: execHostsBlock}}, nil)
+ if err := s.Exec(context.Background(), qCtx2, walker2); err != nil {
+ t.Fatalf("Exec failed: %v", err)
+ }
+ if s.blockedQueries.Load() != 1 {
+ t.Errorf("expected blockedQueries 1, got %d", s.blockedQueries.Load())
+ }
+ if s.hostsQueries.Load() != 1 { // Should still be 1 (not incremented)
+ t.Errorf("expected hostsQueries to remain 1, got %d", s.hostsQueries.Load())
+ }
+
+ // Test 3: Arbitrary resolution
+ q3 := new(dns.Msg)
+ q3.SetQuestion("custom.zone.", dns.TypeA)
+ qCtx3 := query_context.NewContext(q3)
+
+ execArbitrary := sequence.ExecutableFunc(func(ctx context.Context, qCtx *query_context.Context) error {
+ resp := new(dns.Msg)
+ resp.SetReply(qCtx.Q())
+ resp.Answer = append(resp.Answer, &dns.A{
+ Hdr: dns.RR_Header{Name: "custom.zone.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300},
+ A: []byte{10, 0, 0, 1},
+ })
+ qCtx.SetResponse(resp)
+ qCtx.SetFromArbitrary()
+ return nil
+ })
+
+ walker3 := sequence.NewChainWalker([]*sequence.ChainNode{{E: execArbitrary}}, nil)
+ if err := s.Exec(context.Background(), qCtx3, walker3); err != nil {
+ t.Fatalf("Exec failed: %v", err)
+ }
+ if s.arbitraryQueries.Load() != 1 {
+ t.Errorf("expected arbitraryQueries 1, got %d", s.arbitraryQueries.Load())
+ }
+ _, logs3 := s.ringBuffer.QueryLogs(1, 0, "", "all")
+ if len(logs3) != 1 || logs3[0].Upstream != "arbitrary" {
+ t.Errorf("expected upstream 'arbitrary', got %+v", logs3)
+ }
+}
+
@@ -0,0 +1,123 @@
From f178e9542a7effb74e8feb8b535197b3131ff05a Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Thu, 3 Sep 2026 22:42:53 +0800
Subject: [PATCH 3/3] fix(fallback): preserve upstream and query context
metadata from winning branch
Signed-off-by: sbwml <admin@cooluc.com>
---
pkg/query_context/context.go | 41 +++++++++++++++++++
.../executable/sequence/fallback/fallback.go | 22 +++++++---
2 files changed, 57 insertions(+), 6 deletions(-)
--- a/pkg/query_context/context.go
+++ b/pkg/query_context/context.go
@@ -253,6 +253,15 @@ func (ctx *Context) CopyTo(d *Context) *
}
d.upstreamOpt = ctx.upstreamOpt
+ if ctx.UpstreamSelected != nil {
+ u := *ctx.UpstreamSelected
+ d.UpstreamSelected = &u
+ }
+ if len(ctx.RuleHits) > 0 {
+ d.RuleHits = make([]RuleHit, len(ctx.RuleHits))
+ copy(d.RuleHits, ctx.RuleHits)
+ }
+ d.CacheState = ctx.CacheState
d.FromHosts = ctx.FromHosts
d.FromArbitrary = ctx.FromArbitrary
@@ -261,6 +270,38 @@ func (ctx *Context) CopyTo(d *Context) *
return d
}
+// CopyMetadataFrom copies execution metadata and log details from src Context.
+func (ctx *Context) CopyMetadataFrom(src *Context) {
+ if src == nil {
+ return
+ }
+ ctx.upstreamOpt = src.upstreamOpt
+ ctx.UpstreamSelected = src.UpstreamSelected
+ if len(src.RuleHits) > 0 {
+ ctx.RuleHits = src.RuleHits
+ }
+ ctx.CacheState = src.CacheState
+ ctx.FromHosts = src.FromHosts
+ ctx.FromArbitrary = src.FromArbitrary
+
+ if src.marks != nil {
+ if ctx.marks == nil {
+ ctx.marks = make(map[uint32]struct{})
+ }
+ for k, v := range src.marks {
+ ctx.marks[k] = v
+ }
+ }
+ if src.kv != nil {
+ if ctx.kv == nil {
+ ctx.kv = make(map[uint32]any)
+ }
+ for k, v := range src.kv {
+ ctx.kv[k] = v
+ }
+ }
+}
+
// StoreValue stores any v in to this Context
// k MUST from RegKey.
func (ctx *Context) StoreValue(k uint32, v any) {
--- a/plugin/executable/sequence/fallback/fallback.go
+++ b/plugin/executable/sequence/fallback/fallback.go
@@ -107,8 +107,13 @@ func (f *fallback) Exec(ctx context.Cont
return f.doFallback(ctx, qCtx)
}
+type fallbackResult struct {
+ r *dns.Msg
+ qCtx *query_context.Context
+}
+
func (f *fallback) doFallback(ctx context.Context, qCtx *query_context.Context) error {
- respChan := make(chan *dns.Msg, 2) // resp could be nil.
+ respChan := make(chan *fallbackResult, 2) // resp could be nil.
primFailed := make(chan struct{})
primDone := make(chan struct{})
@@ -129,7 +134,7 @@ func (f *fallback) doFallback(ctx contex
respChan <- nil
} else {
close(primDone)
- respChan <- r
+ respChan <- &fallbackResult{r: r, qCtx: qCtx}
}
}()
@@ -167,18 +172,23 @@ func (f *fallback) doFallback(ctx contex
case <-timer.C: // or timed out.
}
}
- respChan <- r
+ if r == nil {
+ respChan <- nil
+ } else {
+ respChan <- &fallbackResult{r: r, qCtx: qCtx}
+ }
}()
for i := 0; i < 2; i++ {
select {
case <-ctx.Done():
return context.Cause(ctx)
- case r := <-respChan:
- if r == nil { // One of goroutines finished but failed.
+ case res := <-respChan:
+ if res == nil || res.r == nil { // One of goroutines finished but failed.
continue
}
- qCtx.SetResponse(r)
+ qCtx.SetResponse(res.r)
+ qCtx.CopyMetadataFrom(res.qCtx)
return nil
}
}