mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-10 18:34:18 +08:00
🌴 Sync 2026-09-05 22:29:54
This commit is contained in:
@@ -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 => {
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -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 "阿里云公共 DNS(DNS over HTTPS)"
|
|||||||
msgid "Aliyun Public DNS (DNS over QUIC)"
|
msgid "Aliyun Public DNS (DNS over QUIC)"
|
||||||
msgstr "阿里云公共 DNS(DNS over QUIC)"
|
msgstr "阿里云公共 DNS(DNS 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 "思科公共 DNS(208.67.220.220)"
|
|||||||
msgid "Cisco Public DNS (208.67.222.222)"
|
msgid "Cisco Public DNS (208.67.222.222)"
|
||||||
msgstr "思科公共 DNS(208.67.222.222)"
|
msgstr "思科公共 DNS(208.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 "谷歌公共 DNS(8.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 公共 DNS(149.112.112.112)"
|
|||||||
msgid "Quad9 Public DNS (9.9.9.9)"
|
msgid "Quad9 Public DNS (9.9.9.9)"
|
||||||
msgstr "Quad9 公共 DNS(9.9.9.9)"
|
msgstr "Quad9 公共 DNS(9.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 "腾讯公共 DNS(119.29.29.29)"
|
|||||||
msgid "Tencent Public DNS (DNS over HTTPS)"
|
msgid "Tencent Public DNS (DNS over HTTPS)"
|
||||||
msgstr "腾讯公共 DNS(DNS over HTTPS)"
|
msgstr "腾讯公共 DNS(DNS 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 "火山引擎公共 DNS(180.184.2.2)"
|
msgstr "火山引擎公共 DNS(180.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 "信风公共 DNS(114.114.114.114)"
|
|||||||
msgid "Xinfeng Public DNS (114.114.115.115)"
|
msgid "Xinfeng Public DNS (114.114.115.115)"
|
||||||
msgstr "信风公共 DNS(114.114.115.115)"
|
msgstr "信风公共 DNS(114.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
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user