mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-03 16:23:56 +08:00
🔥 Sync 2026-10-24 23:19:23
This commit is contained in:
1 parent
9886197604
commit
37e1475e87
59 files changed
+5167
-1483
No files matched your search
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.10.22
|
||||
PKG_RELEASE:=347
|
||||
PKG_RELEASE:=348
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
* Click-to-sort headers and a per-group filter bar for the node list.
|
||||
*
|
||||
* Sort reorders rows inside one group table and calls set_order(), the same
|
||||
* path as drag-and-drop and To Top. Nothing is written until Save & Apply.
|
||||
*
|
||||
* A filter only toggles the pw-row-hidden class. Rows stay in the form, so
|
||||
* Save & Apply still posts every node and the full group.order value.
|
||||
*
|
||||
* Translated strings are read from #pw-nodesort-i18n (the node list template).
|
||||
* The template calls init() once, then enhanceAll() after loadNodeList()
|
||||
* has built the group tables.
|
||||
*
|
||||
* auto_detection_time is a top-level let in the template, not a window
|
||||
* property. It is read by name so rows are not moved while automatic Ping
|
||||
* or TCPing is still writing results by row position.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var COLUMNS = {
|
||||
'pw-remark': 'name',
|
||||
'pw-ping': 'ping',
|
||||
'pw-tcping': 'tcping',
|
||||
'pw-urltest': 'urltest'
|
||||
};
|
||||
/* sub controls drawn inside the Remarks header: [sort key, label key or symbol, title key] */
|
||||
var NAME_EXTRA = [
|
||||
['type', 'type', 'typeHint'],
|
||||
['addr', 'addr', 'addrHint'],
|
||||
['orig', '\u21BA', 'origHint']
|
||||
];
|
||||
|
||||
var state = {
|
||||
app: 'passwall',
|
||||
root: null,
|
||||
text: {},
|
||||
bound: false
|
||||
};
|
||||
var searchTimer = null;
|
||||
|
||||
function T(key) {
|
||||
var value = state.text[key];
|
||||
return (value == null || value === '') ? key : value;
|
||||
}
|
||||
|
||||
function readText(box) {
|
||||
var text = {};
|
||||
var spans = box.querySelectorAll('[data-k]');
|
||||
for (var i = 0; i < spans.length; i++)
|
||||
text[spans[i].getAttribute('data-k')] = spans[i].textContent;
|
||||
return text;
|
||||
}
|
||||
|
||||
var collator = null;
|
||||
function prepareCollator() {
|
||||
var lang = document.documentElement.lang || (navigator.language || '');
|
||||
try {
|
||||
collator = (window.Intl && Intl.Collator) ? new Intl.Collator(lang || undefined, { numeric: true, sensitivity: 'base' }) : null;
|
||||
} catch (e) {
|
||||
collator = null;
|
||||
}
|
||||
}
|
||||
|
||||
function cmpText(a, b) {
|
||||
if (collator)
|
||||
return collator.compare(a, b);
|
||||
return a < b ? -1 : (a > b ? 1 : 0);
|
||||
}
|
||||
|
||||
/* "123 ms", "12.5 ms", "1.2 s" -> milliseconds. Timeout, Error, ---, Test stay null. */
|
||||
function parseLatency(text) {
|
||||
var t = String(text || '').replace(/\s+/g, ' ').trim();
|
||||
var m = /^(\d+(?:[.,]\d+)?)\s*(ms|s)?$/i.exec(t);
|
||||
if (!m)
|
||||
return null;
|
||||
var v = parseFloat(m[1].replace(',', '.'));
|
||||
if (isNaN(v))
|
||||
return null;
|
||||
if (m[2] && /^s$/i.test(m[2]))
|
||||
v *= 1000;
|
||||
return v;
|
||||
}
|
||||
|
||||
function hiddenVal(id, opt) {
|
||||
var el = document.getElementById('cbid.' + state.app + '.' + id + '.' + opt);
|
||||
return el && el.value != null ? String(el.value).trim() : '';
|
||||
}
|
||||
|
||||
function cellsOf(tr) {
|
||||
var out = [];
|
||||
for (var i = 0; i < tr.children.length; i++) {
|
||||
var tag = tr.children[i].tagName;
|
||||
if (tag === 'TD' || tag === 'TH')
|
||||
out.push(tr.children[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
var wordSplit;
|
||||
try {
|
||||
wordSplit = new RegExp('[^\\p{L}\\p{N}]+', 'u');
|
||||
} catch (e) {
|
||||
wordSplit = /[\s.,;:!?|\/\\()\[\]{}<>"'`~@#$%^&*=+_-]+/;
|
||||
}
|
||||
|
||||
function wordsOf(text) {
|
||||
var seen = {};
|
||||
var out = [];
|
||||
var parts = String(text || '').split(wordSplit);
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var w = parts[i];
|
||||
if (w.length < 2 || /^\d+$/.test(w))
|
||||
continue;
|
||||
var k = w.toLowerCase();
|
||||
if (!seen[k]) {
|
||||
seen[k] = 1;
|
||||
out.push(w);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function infoOf(table) {
|
||||
var prefix = 'cbi-' + state.app + '-nodes-';
|
||||
var id = table && table.id ? table.id : '';
|
||||
if (id.indexOf(prefix) !== 0 || id.slice(-6) !== '-table')
|
||||
return null;
|
||||
return { group: id.slice(prefix.length, -6) };
|
||||
}
|
||||
|
||||
function rowId(tr) {
|
||||
var prefix = 'cbi-' + state.app + '-';
|
||||
return tr.id.indexOf(prefix) === 0 ? tr.id.substring(prefix.length) : tr.id;
|
||||
}
|
||||
|
||||
function bodyOf(table) {
|
||||
return table.querySelector('tbody') || table;
|
||||
}
|
||||
|
||||
function dataRows(table) {
|
||||
var body = bodyOf(table);
|
||||
var out = [];
|
||||
for (var i = 0; i < body.children.length; i++) {
|
||||
var tr = body.children[i];
|
||||
if (tr.tagName === 'TR' && tr.classList.contains('cbi-section-table-row') && !tr.classList.contains('placeholder'))
|
||||
out.push(tr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function colIndexMap(table) {
|
||||
var rows = dataRows(table);
|
||||
var map = {};
|
||||
if (!rows.length)
|
||||
return map;
|
||||
var cells = cellsOf(rows[0]);
|
||||
for (var i = 0; i < cells.length; i++) {
|
||||
for (var cls in COLUMNS) {
|
||||
if (cells[i].classList.contains(cls))
|
||||
map[COLUMNS[cls]] = i;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function typeOf(tr, cols) {
|
||||
var cell = cellsOf(tr)[cols.name];
|
||||
var text = cell ? cell.textContent.replace(/\s+/g, ' ').trim() : '';
|
||||
var i = text.indexOf('\uFF1A');
|
||||
if (i < 0)
|
||||
i = text.indexOf(':');
|
||||
return (i < 0 ? '' : text.slice(0, i)).replace(/\s+Realm$/, '').trim();
|
||||
}
|
||||
|
||||
function sortKey(tr, key, cols) {
|
||||
var id = rowId(tr);
|
||||
var cell;
|
||||
if (key === 'name')
|
||||
return hiddenVal(id, 'remarks') || ((cellsOf(tr)[cols.name] || {}).textContent || '').trim();
|
||||
if (key === 'type')
|
||||
return typeOf(tr, cols) + '\u0000' + hiddenVal(id, 'remarks');
|
||||
if (key === 'addr') {
|
||||
var address = hiddenVal(id, 'address');
|
||||
if (!address)
|
||||
return null;
|
||||
return address + '\u0000' + ('00000' + hiddenVal(id, 'port')).slice(-5);
|
||||
}
|
||||
cell = cellsOf(tr)[cols[key]];
|
||||
return cell ? parseLatency(cell.textContent) : null;
|
||||
}
|
||||
|
||||
function detectionMode() {
|
||||
try {
|
||||
if (typeof auto_detection_time === 'undefined')
|
||||
return null;
|
||||
return auto_detection_time;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* { inflight: requests still running, left: cells still waiting for a result } */
|
||||
function pingBusy() {
|
||||
var mode = detectionMode();
|
||||
var inflight = 0;
|
||||
var left = 0;
|
||||
if (mode !== 'icmp' && mode !== 'tcping')
|
||||
return { inflight: 0, left: 0 };
|
||||
var list = window.ajax && window.ajax.list;
|
||||
if (list && list.length) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (list[i].readyState > 0 && list[i].readyState < 4)
|
||||
inflight++;
|
||||
}
|
||||
}
|
||||
if (!state.root)
|
||||
return { inflight: inflight, left: 0 };
|
||||
var spans = state.root.getElementsByClassName(mode === 'tcping' ? 'tcping_value' : 'ping_value');
|
||||
for (var s = 0; s < spans.length; s++) {
|
||||
var id = spans[s].getAttribute('cbiid');
|
||||
if (!id || spans[s].textContent.trim() !== '---')
|
||||
continue;
|
||||
var full = null;
|
||||
if (typeof window.get_address_full === 'function')
|
||||
full = window.get_address_full(id);
|
||||
if (!full)
|
||||
full = { address: hiddenVal(id, 'address'), port: hiddenVal(id, 'port') };
|
||||
if (full.address && (mode === 'icmp' || full.port))
|
||||
left++;
|
||||
}
|
||||
return { inflight: inflight, left: left };
|
||||
}
|
||||
|
||||
function note(table, text) {
|
||||
var id = 'pw-note-' + table.id;
|
||||
var el = document.getElementById(id);
|
||||
if (!text) {
|
||||
if (el && el.parentNode)
|
||||
el.parentNode.removeChild(el);
|
||||
return;
|
||||
}
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
el.id = id;
|
||||
el.className = 'pw-sort-note';
|
||||
table.parentNode.insertBefore(el, table);
|
||||
}
|
||||
el.textContent = text;
|
||||
}
|
||||
|
||||
function setIndicator(table, th, sub, key, dir) {
|
||||
var arrows = table.querySelectorAll('.pw-sort-arrow');
|
||||
var subs = table.querySelectorAll('.pw-sort-sub');
|
||||
var headers = table.querySelectorAll('th[data-pw-sort]');
|
||||
var i;
|
||||
for (i = 0; i < arrows.length; i++)
|
||||
arrows[i].textContent = '';
|
||||
for (i = 0; i < subs.length; i++)
|
||||
subs[i].classList.remove('pw-sort-active');
|
||||
for (i = 0; i < headers.length; i++) {
|
||||
if (headers[i] !== th) {
|
||||
delete headers[i].dataset.pwDir;
|
||||
delete headers[i].dataset.pwKey;
|
||||
}
|
||||
}
|
||||
var arrow = th.querySelector('.pw-sort-arrow');
|
||||
if (arrow && key !== 'orig' && !sub)
|
||||
arrow.textContent = dir === 'desc' ? '\u25BC' : '\u25B2';
|
||||
if (sub && key !== 'orig')
|
||||
sub.classList.add('pw-sort-active');
|
||||
}
|
||||
|
||||
function applyOrder(table, info, rows) {
|
||||
var body = bodyOf(table);
|
||||
var frag = document.createDocumentFragment();
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
rows[i].classList.remove('cbi-rowstyle-1', 'cbi-rowstyle-2');
|
||||
rows[i].classList.add('cbi-rowstyle-' + (i % 2 + 1));
|
||||
frag.appendChild(rows[i]);
|
||||
}
|
||||
body.appendChild(frag);
|
||||
if (typeof window.set_order === 'function')
|
||||
window.set_order(info.group);
|
||||
}
|
||||
|
||||
function doSort(table, th, sub, key) {
|
||||
var info = infoOf(table);
|
||||
var rows = dataRows(table);
|
||||
var dir = 'asc';
|
||||
if (!info || rows.length < 2)
|
||||
return;
|
||||
if (key === 'orig') {
|
||||
var pos = {};
|
||||
var orig = table.__pwOrig || [];
|
||||
for (var n = 0; n < orig.length; n++)
|
||||
pos[orig[n]] = n;
|
||||
rows.sort(function (a, b) {
|
||||
var pa = pos[rowId(a)];
|
||||
var pb = pos[rowId(b)];
|
||||
return (pa == null ? 1e9 : pa) - (pb == null ? 1e9 : pb);
|
||||
});
|
||||
delete th.dataset.pwDir;
|
||||
delete th.dataset.pwKey;
|
||||
} else {
|
||||
dir = (th.dataset.pwKey === key && th.dataset.pwDir === 'asc') ? 'desc' : 'asc';
|
||||
th.dataset.pwKey = key;
|
||||
th.dataset.pwDir = dir;
|
||||
var cols = colIndexMap(table);
|
||||
var sign = dir === 'desc' ? -1 : 1;
|
||||
var list = [];
|
||||
for (var i = 0; i < rows.length; i++)
|
||||
list.push({ tr: rows[i], i: i, k: sortKey(rows[i], key, cols) });
|
||||
list.sort(function (a, b) {
|
||||
var emptyA = (a.k === null || a.k === '');
|
||||
var emptyB = (b.k === null || b.k === '');
|
||||
if (emptyA || emptyB)
|
||||
return emptyA === emptyB ? a.i - b.i : (emptyA ? 1 : -1);
|
||||
var cmp = (typeof a.k === 'number' && typeof b.k === 'number') ? a.k - b.k : cmpText(String(a.k), String(b.k));
|
||||
return cmp !== 0 ? sign * cmp : a.i - b.i;
|
||||
});
|
||||
rows = [];
|
||||
for (var j = 0; j < list.length; j++)
|
||||
rows.push(list[j].tr);
|
||||
}
|
||||
applyOrder(table, info, rows);
|
||||
refreshFilter(table);
|
||||
setIndicator(table, th, sub, key, dir);
|
||||
note(table, key === 'orig' ? T('restored') : T('changed'));
|
||||
}
|
||||
|
||||
function requestSort(table, th, sub, key) {
|
||||
if (table.__pwWait) {
|
||||
clearInterval(table.__pwWait);
|
||||
table.__pwWait = null;
|
||||
}
|
||||
var st = pingBusy();
|
||||
if (!st.inflight && !st.left) {
|
||||
doSort(table, th, sub, key);
|
||||
return;
|
||||
}
|
||||
var started = Date.now();
|
||||
var idle = 0;
|
||||
note(table, T('wait').replace('%d', st.left || st.inflight));
|
||||
table.__pwWait = setInterval(function () {
|
||||
st = pingBusy();
|
||||
idle = st.inflight ? 0 : idle + 1;
|
||||
if ((!st.inflight && !st.left) || idle >= 6) {
|
||||
clearInterval(table.__pwWait);
|
||||
table.__pwWait = null;
|
||||
if (document.body.contains(table))
|
||||
doSort(table, th, sub, key);
|
||||
return;
|
||||
}
|
||||
if (Date.now() - started > 120000) {
|
||||
clearInterval(table.__pwWait);
|
||||
table.__pwWait = null;
|
||||
note(table, T('waitFail'));
|
||||
return;
|
||||
}
|
||||
note(table, T('wait').replace('%d', st.left || st.inflight));
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function enhance(table) {
|
||||
var info = infoOf(table);
|
||||
if (!info || table.__pwReady)
|
||||
return;
|
||||
var titles = table.querySelector('tr.cbi-section-table-titles');
|
||||
var rows = dataRows(table);
|
||||
if (!titles || !rows.length)
|
||||
return;
|
||||
var cols = colIndexMap(table);
|
||||
var headers = cellsOf(titles);
|
||||
table.__pwReady = true;
|
||||
table.__pwOrig = [];
|
||||
for (var r = 0; r < rows.length; r++)
|
||||
table.__pwOrig.push(rowId(rows[r]));
|
||||
for (var key in cols) {
|
||||
if (!Object.prototype.hasOwnProperty.call(cols, key))
|
||||
continue;
|
||||
var th = headers[cols[key]];
|
||||
if (!th || th.classList.contains('cbi-section-actions'))
|
||||
continue;
|
||||
th.setAttribute('data-pw-sort', key);
|
||||
th.title = T('hint');
|
||||
var arrow = document.createElement('span');
|
||||
arrow.className = 'pw-sort-arrow';
|
||||
th.appendChild(arrow);
|
||||
if (key !== 'name')
|
||||
continue;
|
||||
for (var s = 0; s < NAME_EXTRA.length; s++) {
|
||||
var spec = NAME_EXTRA[s];
|
||||
var sp = document.createElement('span');
|
||||
sp.className = 'pw-sort-sub';
|
||||
sp.setAttribute('data-pw-sort', spec[0]);
|
||||
sp.textContent = T(spec[1]);
|
||||
sp.title = T(spec[2]);
|
||||
th.appendChild(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rowVisible(el) {
|
||||
var row = el && el.closest ? el.closest('tr') : null;
|
||||
return !row || !row.classList.contains('pw-row-hidden');
|
||||
}
|
||||
|
||||
function filterDefs(rows, cols) {
|
||||
return [
|
||||
{
|
||||
key: 'type',
|
||||
label: T('type'),
|
||||
multi: false,
|
||||
get: function (tr) { return typeOf(tr, cols); }
|
||||
},
|
||||
{
|
||||
key: 'word',
|
||||
label: T('fWord'),
|
||||
multi: true,
|
||||
minCount: 2,
|
||||
max: 40,
|
||||
get: function (tr) { return wordsOf(hiddenVal(rowId(tr), 'remarks')); }
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function searchText(tr, cols) {
|
||||
var id = rowId(tr);
|
||||
return [
|
||||
typeOf(tr, cols),
|
||||
hiddenVal(id, 'remarks'),
|
||||
hiddenVal(id, 'address') + ':' + hiddenVal(id, 'port')
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
function collect(table) {
|
||||
var rows = dataRows(table);
|
||||
var cols = colIndexMap(table);
|
||||
var defs = rows.length >= 2 ? filterDefs(rows, cols) : [];
|
||||
var shown = [];
|
||||
for (var d = 0; d < defs.length; d++) {
|
||||
var def = defs[d];
|
||||
var counts = {};
|
||||
var labels = {};
|
||||
var vals = [];
|
||||
for (var r = 0; r < rows.length; r++) {
|
||||
var raw = def.get(rows[r]);
|
||||
var list = def.multi ? (raw || []) : [raw == null ? '' : String(raw)];
|
||||
var keys = [];
|
||||
for (var n = 0; n < list.length; n++) {
|
||||
var item = list[n];
|
||||
var k = def.multi ? String(item).toLowerCase() : String(item);
|
||||
counts[k] = (counts[k] || 0) + 1;
|
||||
if (!labels[k])
|
||||
labels[k] = {};
|
||||
labels[k][item] = (labels[k][item] || 0) + 1;
|
||||
keys.push(k);
|
||||
}
|
||||
vals.push(keys);
|
||||
}
|
||||
var opts = [];
|
||||
for (var k in counts) {
|
||||
if (!Object.prototype.hasOwnProperty.call(counts, k))
|
||||
continue;
|
||||
if (def.multi && (counts[k] < (def.minCount || 1) || counts[k] >= rows.length))
|
||||
continue;
|
||||
var best = '';
|
||||
var bestN = -1;
|
||||
for (var label in labels[k]) {
|
||||
if (labels[k][label] > bestN) {
|
||||
bestN = labels[k][label];
|
||||
best = label;
|
||||
}
|
||||
}
|
||||
opts.push({ v: k, label: best === '' ? T('empty') : best, n: counts[k] });
|
||||
}
|
||||
opts.sort(function (a, b) {
|
||||
if (def.multi && a.n !== b.n)
|
||||
return b.n - a.n;
|
||||
return cmpText(a.label, b.label);
|
||||
});
|
||||
if (def.max && opts.length > def.max)
|
||||
opts = opts.slice(0, def.max);
|
||||
var enough = def.multi ? opts.length >= 1 : opts.length >= 2;
|
||||
if (!enough)
|
||||
continue;
|
||||
def.vals = vals;
|
||||
def.opts = opts;
|
||||
shown.push(def);
|
||||
}
|
||||
var search = rows.length >= 2;
|
||||
var sig = String(rows.length) + (search ? 's' : '');
|
||||
for (var i = 0; i < shown.length; i++) {
|
||||
sig += '|' + shown[i].key + '=';
|
||||
for (var o = 0; o < shown[i].opts.length; o++)
|
||||
sig += shown[i].opts[o].v + ':' + shown[i].opts[o].n + ',';
|
||||
}
|
||||
return { rows: rows, cols: cols, defs: shown, search: search, sig: sig };
|
||||
}
|
||||
|
||||
function barId(table) {
|
||||
return 'pw-filter-' + table.id;
|
||||
}
|
||||
|
||||
function buildBar(table, collected) {
|
||||
var st = table.__pwF || (table.__pwF = { sel: {}, q: '' });
|
||||
var id = barId(table);
|
||||
var bar = document.getElementById(id);
|
||||
if (!bar) {
|
||||
bar = document.createElement('div');
|
||||
bar.id = id;
|
||||
bar.className = 'pw-filter';
|
||||
bar.title = T('filterHint');
|
||||
table.parentNode.insertBefore(bar, table);
|
||||
bar.__pwTable = table;
|
||||
}
|
||||
while (bar.firstChild)
|
||||
bar.removeChild(bar.firstChild);
|
||||
var head = document.createElement('span');
|
||||
head.textContent = T('filter');
|
||||
bar.appendChild(head);
|
||||
var keep = {};
|
||||
for (var d = 0; d < collected.defs.length; d++) {
|
||||
var def = collected.defs[d];
|
||||
var lab = document.createElement('label');
|
||||
var sel = document.createElement('select');
|
||||
lab.appendChild(document.createTextNode(def.label));
|
||||
sel.className = 'cbi-input-select';
|
||||
sel.setAttribute('data-pw-filter', def.key);
|
||||
var all = document.createElement('option');
|
||||
all.value = '';
|
||||
all.textContent = T('all') + ' (' + collected.rows.length + ')';
|
||||
sel.appendChild(all);
|
||||
for (var o = 0; o < def.opts.length; o++) {
|
||||
var op = document.createElement('option');
|
||||
op.value = 'v:' + def.opts[o].v;
|
||||
op.textContent = def.opts[o].label + ' (' + def.opts[o].n + ')';
|
||||
sel.appendChild(op);
|
||||
}
|
||||
var cur = st.sel[def.key] || '';
|
||||
var known = false;
|
||||
if (cur) {
|
||||
for (var c = 0; c < def.opts.length; c++) {
|
||||
if (('v:' + def.opts[c].v) === cur) {
|
||||
known = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!known)
|
||||
cur = '';
|
||||
sel.value = cur;
|
||||
if (cur)
|
||||
keep[def.key] = cur;
|
||||
lab.appendChild(sel);
|
||||
bar.appendChild(lab);
|
||||
}
|
||||
st.sel = keep;
|
||||
if (collected.search) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'search';
|
||||
input.className = 'cbi-input-text';
|
||||
input.placeholder = T('search');
|
||||
input.title = T('searchHint');
|
||||
input.autocomplete = 'off';
|
||||
input.setAttribute('data-pw-search', '1');
|
||||
input.value = st.q;
|
||||
bar.appendChild(input);
|
||||
} else {
|
||||
st.q = '';
|
||||
}
|
||||
var count = document.createElement('span');
|
||||
count.className = 'pw-filter-count';
|
||||
bar.appendChild(count);
|
||||
var reset = document.createElement('button');
|
||||
reset.type = 'button';
|
||||
reset.className = 'btn cbi-button';
|
||||
reset.setAttribute('data-pw-reset', '1');
|
||||
reset.textContent = T('reset');
|
||||
reset.title = T('resetHint');
|
||||
bar.appendChild(reset);
|
||||
return bar;
|
||||
}
|
||||
|
||||
function listHas(list, value) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (list[i] === value)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyFilter(table, collected, bar) {
|
||||
var st = table.__pwF || { sel: {}, q: '' };
|
||||
var q = String(st.q || '').toLowerCase().trim();
|
||||
var active = !!q;
|
||||
var shown = 0;
|
||||
var d;
|
||||
for (d = 0; d < collected.defs.length; d++) {
|
||||
if (st.sel[collected.defs[d].key])
|
||||
active = true;
|
||||
}
|
||||
for (var i = 0; i < collected.rows.length; i++) {
|
||||
var tr = collected.rows[i];
|
||||
var ok = true;
|
||||
for (d = 0; d < collected.defs.length; d++) {
|
||||
var sel = st.sel[collected.defs[d].key];
|
||||
if (ok && sel && !listHas(collected.defs[d].vals[i], sel.slice(2)))
|
||||
ok = false;
|
||||
}
|
||||
if (ok && q && searchText(tr, collected.cols).toLowerCase().indexOf(q) < 0)
|
||||
ok = false;
|
||||
if (ok !== !tr.classList.contains('pw-row-hidden'))
|
||||
tr.classList.toggle('pw-row-hidden', !ok);
|
||||
if (ok) {
|
||||
shown++;
|
||||
var want = 'cbi-rowstyle-' + ((shown - 1) % 2 + 1);
|
||||
var other = want === 'cbi-rowstyle-1' ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1';
|
||||
if (!tr.classList.contains(want))
|
||||
tr.classList.add(want);
|
||||
if (tr.classList.contains(other))
|
||||
tr.classList.remove(other);
|
||||
} else {
|
||||
var cb = tr.querySelector('input.nodes_select');
|
||||
if (cb && cb.checked) {
|
||||
cb.checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof window.update_select_state === 'function')
|
||||
window.update_select_state();
|
||||
var count = bar.querySelector('.pw-filter-count');
|
||||
var txt = T('shown').replace('%s', shown).replace('%t', collected.rows.length);
|
||||
if (count && count.textContent !== txt)
|
||||
count.textContent = txt;
|
||||
if (bar.classList.contains('pw-filter-on') !== active)
|
||||
bar.classList.toggle('pw-filter-on', active);
|
||||
}
|
||||
|
||||
function removeBar(table) {
|
||||
var bar = document.getElementById(barId(table));
|
||||
if (bar && bar.parentNode)
|
||||
bar.parentNode.removeChild(bar);
|
||||
var rows = dataRows(table);
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
if (rows[i].classList.contains('pw-row-hidden'))
|
||||
rows[i].classList.remove('pw-row-hidden');
|
||||
}
|
||||
table.__pwSig = null;
|
||||
}
|
||||
|
||||
function refreshFilter(table) {
|
||||
if (!infoOf(table))
|
||||
return;
|
||||
var collected = collect(table);
|
||||
if (!collected.defs.length && !collected.search) {
|
||||
if (table.__pwSig)
|
||||
removeBar(table);
|
||||
return;
|
||||
}
|
||||
var bar = document.getElementById(barId(table));
|
||||
if (!bar || collected.sig !== table.__pwSig) {
|
||||
bar = buildBar(table, collected);
|
||||
table.__pwSig = collected.sig;
|
||||
}
|
||||
applyFilter(table, collected, bar);
|
||||
}
|
||||
|
||||
function barTable(el) {
|
||||
var bar = el && el.closest ? el.closest('.pw-filter') : null;
|
||||
return bar && bar.__pwTable ? bar.__pwTable : null;
|
||||
}
|
||||
|
||||
function eventTarget(ev) {
|
||||
var t = ev.target;
|
||||
if (t && t.nodeType !== 1)
|
||||
t = t.parentNode;
|
||||
return t;
|
||||
}
|
||||
|
||||
function bind(root) {
|
||||
root.addEventListener('click', function (ev) {
|
||||
var t = eventTarget(ev);
|
||||
if (t && t.getAttribute && t.getAttribute('data-pw-reset')) {
|
||||
var resetTable = barTable(t);
|
||||
if (!resetTable)
|
||||
return;
|
||||
ev.preventDefault();
|
||||
resetTable.__pwF = { sel: {}, q: '' };
|
||||
resetTable.__pwSig = null;
|
||||
refreshFilter(resetTable);
|
||||
return;
|
||||
}
|
||||
var el = t && t.closest ? t.closest('[data-pw-sort]') : null;
|
||||
if (!el || !root.contains(el))
|
||||
return;
|
||||
if (t.closest && t.closest('input, a, button, select'))
|
||||
return;
|
||||
var th = el.closest('th');
|
||||
var table = el.closest('table');
|
||||
if (!th || !table || !infoOf(table))
|
||||
return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
requestSort(table, th, el === th ? null : el, el.getAttribute('data-pw-sort'));
|
||||
});
|
||||
root.addEventListener('change', function (ev) {
|
||||
var t = eventTarget(ev);
|
||||
if (!t || !t.getAttribute || !t.getAttribute('data-pw-filter'))
|
||||
return;
|
||||
var table = barTable(t);
|
||||
if (!table)
|
||||
return;
|
||||
(table.__pwF = table.__pwF || { sel: {}, q: '' }).sel[t.getAttribute('data-pw-filter')] = t.value;
|
||||
refreshFilter(table);
|
||||
});
|
||||
root.addEventListener('input', function (ev) {
|
||||
var t = eventTarget(ev);
|
||||
if (!t || !t.getAttribute || !t.getAttribute('data-pw-search'))
|
||||
return;
|
||||
var table = barTable(t);
|
||||
if (!table)
|
||||
return;
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(function () {
|
||||
(table.__pwF = table.__pwF || { sel: {}, q: '' }).q = t.value;
|
||||
refreshFilter(table);
|
||||
}, 150);
|
||||
});
|
||||
root.addEventListener('keydown', function (ev) {
|
||||
var t = eventTarget(ev);
|
||||
if (ev.key === 'Enter' && t && t.getAttribute && t.getAttribute('data-pw-search'))
|
||||
ev.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
var box = document.getElementById('pw-nodesort-i18n');
|
||||
var root = document.getElementById('node_list');
|
||||
if (!box || !root)
|
||||
return;
|
||||
state.app = box.getAttribute('data-app') || 'passwall';
|
||||
state.text = readText(box);
|
||||
state.root = root;
|
||||
prepareCollator();
|
||||
if (!state.bound) {
|
||||
bind(root);
|
||||
state.bound = true;
|
||||
}
|
||||
}
|
||||
|
||||
function enhanceAll() {
|
||||
if (!state.root)
|
||||
return;
|
||||
var tables = state.root.querySelectorAll('table.cbi-section-table');
|
||||
for (var i = 0; i < tables.length; i++) {
|
||||
try {
|
||||
enhance(tables[i]);
|
||||
refreshFilter(tables[i]);
|
||||
} catch (e) {
|
||||
if (window.console)
|
||||
console.warn('passwall node list sort:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.pwNodeSort = {
|
||||
init: init,
|
||||
enhanceAll: enhanceAll,
|
||||
rowVisible: rowVisible
|
||||
};
|
||||
})();
|
||||
@@ -78,7 +78,7 @@ if has_xray or has_singbox then
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
o.description = translate("Generate rule lists such as GFW, China domains, and China IP ranges based on Geo files.") .. "<br><font color='red'>" ..
|
||||
translate("When manually updating with this option enabled, rules will be regenerated from existing Geo files even if no new version is available.") .. "</font>"
|
||||
translate("GeoSite files without GFW rules cannot generate a GFW list (e.g. the original v2fly/Xray files).") .. "</font>"
|
||||
|
||||
o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing"))
|
||||
o.default = 0
|
||||
|
||||
@@ -1692,8 +1692,21 @@ function gen_config(var)
|
||||
local bin = api.finded_com("geoview")
|
||||
if bin then
|
||||
local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
|
||||
if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then
|
||||
domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw"
|
||||
local gfw_rule
|
||||
if sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" 2>/dev/null | grep -q "^GFW$"') == 0 then
|
||||
gfw_rule = "geosite:gfw"
|
||||
elseif api.compare_versions(api.get_app_version("geoview"), ">=", "0.2.8") then
|
||||
local output = GEO_VAR.TO_SRS_PATH .. "geosite-gfw.srs"
|
||||
if not fs.access(output) then
|
||||
fs.mkdirr(GEO_VAR.TO_SRS_PATH)
|
||||
local cmd = string.format("%s -type geosite -action convert -input /usr/share/passwall/rules/gfwlist -list '*' -output %s -format srs",
|
||||
api.util.shellquote(bin), api.util.shellquote(output))
|
||||
if api.exec_call(cmd) ~= 0 then fs.unlink(output) end
|
||||
end
|
||||
gfw_rule = fs.access(output) and ("rule-set:local:" .. output) or nil
|
||||
end
|
||||
if gfw_rule then
|
||||
domain_list = (domain_list == "") and gfw_rule or domain_list .. "\n" .. gfw_rule
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -579,11 +579,15 @@ function gen_config_server(node)
|
||||
end
|
||||
end
|
||||
|
||||
if node.protocol == "vmess" or node.protocol == "vless" then
|
||||
if node.protocol == "vless" then
|
||||
settings = {
|
||||
users = users,
|
||||
decryption = (node.decryption and node.decryption ~= "") and node.decryption or "none"
|
||||
}
|
||||
elseif node.protocol == "vmess" then
|
||||
if users then
|
||||
settings = {
|
||||
users = users,
|
||||
decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil
|
||||
users = users
|
||||
}
|
||||
end
|
||||
elseif node.protocol == "socks" then
|
||||
@@ -1479,8 +1483,21 @@ function gen_config(var)
|
||||
local bin = api.finded_com("geoview")
|
||||
if bin then
|
||||
local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
|
||||
if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then
|
||||
domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw"
|
||||
local gfw_rule
|
||||
if sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" 2>/dev/null | grep -q "^GFW$"') == 0 then
|
||||
gfw_rule = "geosite:gfw"
|
||||
elseif api.compare_versions(api.get_app_version("geoview"), ">=", "0.2.8") then
|
||||
local output = api.CACHE_PATH .. "/geodata/gfwlist.dat"
|
||||
if not fs.access(output) then
|
||||
fs.mkdirr(api.CACHE_PATH .. "/geodata")
|
||||
local cmd = string.format("%s -type geosite -action convert -input /usr/share/passwall/rules/gfwlist -list '*' -output %s -format geosite",
|
||||
api.util.shellquote(bin), api.util.shellquote(output))
|
||||
if api.exec_call(cmd) ~= 0 then fs.unlink(output) end
|
||||
end
|
||||
gfw_rule = fs.access(output) and "ext:gfwlist.dat:all" or nil
|
||||
end
|
||||
if gfw_rule then
|
||||
domain_list = (domain_list == "") and gfw_rule or domain_list .. "\n" .. gfw_rule
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1539,7 +1556,7 @@ function gen_config(var)
|
||||
w = api.trim(w)
|
||||
if w == "" or w:find("#") == 1 then return end
|
||||
if w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then return end
|
||||
if w:find("ext:", 1, true) == 1 then return end -- Rule currently not support ext:
|
||||
if w:find("ext:", 1, true) == 1 and not (e[".name"] == "GFW_Mode_List" and w == "ext:gfwlist.dat:all") then return end
|
||||
table.insert(domains, w)
|
||||
table.insert(domain_table.domain, w)
|
||||
end)
|
||||
@@ -2051,10 +2068,7 @@ function gen_config(var)
|
||||
|
||||
if inbounds or outbounds then
|
||||
local config = {
|
||||
env = (function()
|
||||
local asset_location = api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
|
||||
return { XRAY_LOCATION_ASSET = asset_location }
|
||||
end)(),
|
||||
env = { XRAY_LOCATION_ASSET = api.CACHE_PATH .. "/geodata" },
|
||||
log = {
|
||||
-- error = string.format("/tmp/etc/passwall/%s.log", node[".name"]),
|
||||
loglevel = get_log_level(loglevel)
|
||||
|
||||
@@ -24,6 +24,7 @@ if node then
|
||||
end
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js?v=1.15.7"></script>
|
||||
<script src="<%=resource%>/view/<%=appname%>/node_list_sort.js"></script>
|
||||
|
||||
<style>
|
||||
table th, .table .th {
|
||||
@@ -385,6 +386,89 @@ table td, .table .td {
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* node list: click-to-sort headers and the per-group filter bar */
|
||||
th[data-pw-sort] {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
th[data-pw-sort]:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.pw-sort-arrow {
|
||||
display: inline-block;
|
||||
min-width: 1em;
|
||||
margin-left: 0.25em;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.pw-sort-sub {
|
||||
font-weight: normal;
|
||||
font-size: 85%;
|
||||
opacity: 0.85;
|
||||
margin-left: 0.6em;
|
||||
cursor: pointer;
|
||||
text-decoration: underline dotted;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pw-sort-sub.pw-sort-active {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pw-sort-note {
|
||||
margin: 0.3em 0;
|
||||
padding: 0.3em 0.6em;
|
||||
border-radius: 3px;
|
||||
background: rgba(251, 154, 5, 0.15);
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
tr.pw-row-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.pw-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.3em 0.8em;
|
||||
margin: 0.4em 0;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.pw-filter label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pw-filter select,
|
||||
.pw-filter input[type="search"],
|
||||
.pw-filter .cbi-button {
|
||||
width: auto !important;
|
||||
min-width: 6em;
|
||||
max-width: 22em;
|
||||
height: auto;
|
||||
padding: 0.15em 0.3em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pw-filter .pw-filter-count {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.pw-filter.pw-filter-on .pw-filter-count {
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
<% if is_js_luci then -%>
|
||||
var cbi_t = [];
|
||||
@@ -540,15 +624,29 @@ table td, .table .td {
|
||||
}
|
||||
}
|
||||
|
||||
function node_row_visible(el) {
|
||||
if (window.pwNodeSort && typeof pwNodeSort.rowVisible === "function")
|
||||
return pwNodeSort.rowVisible(el);
|
||||
var row = el && el.closest ? el.closest("tr") : null;
|
||||
return !row || !row.classList.contains("pw-row-hidden");
|
||||
}
|
||||
|
||||
function set_select_all_state(sectionChecked) {
|
||||
var visibleContainer = document.querySelector('#cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||
var selectAllBtn = document.getElementById("select_all_btn");
|
||||
var visibleCount = 0;
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (!node_row_visible(nodes[i])) {
|
||||
nodes[i].checked = false;
|
||||
continue;
|
||||
}
|
||||
nodes[i].checked = sectionChecked;
|
||||
visibleCount++;
|
||||
}
|
||||
sectionChecked = sectionChecked && visibleCount > 0;
|
||||
if (selectAllChk) {
|
||||
selectAllChk.checked = sectionChecked;
|
||||
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
@@ -576,10 +674,13 @@ table td, .table .td {
|
||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||
var selectAllBtn = document.getElementById("select_all_btn");
|
||||
var checkedCount = 0;
|
||||
var visibleCount = 0;
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (!node_row_visible(nodes[i])) continue;
|
||||
visibleCount++;
|
||||
if (nodes[i].checked) checkedCount++;
|
||||
}
|
||||
var allChecked = checkedCount === nodes.length;
|
||||
var allChecked = visibleCount > 0 && checkedCount === visibleCount;
|
||||
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
|
||||
|
||||
@@ -602,7 +703,7 @@ table td, .table .td {
|
||||
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
@@ -1163,7 +1264,7 @@ table td, .table .td {
|
||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (let i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
@@ -1195,7 +1296,7 @@ table td, .table .td {
|
||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (let i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
@@ -1307,8 +1408,33 @@ table td, .table .td {
|
||||
<<%=section_tag%> class="cbi-section" id="node_list">
|
||||
</<%=section_tag%>>
|
||||
|
||||
<div id="pw-nodesort-i18n" data-app="<%=appname%>" style="display:none">
|
||||
<span data-k="hint"><%:Click to sort; click again to reverse. Press Save & Apply to keep the order.%></span>
|
||||
<span data-k="typeHint"><%:Sort by type or protocol, then by name%></span>
|
||||
<span data-k="addrHint"><%:Sort by server address and port%></span>
|
||||
<span data-k="origHint"><%:Restore the saved order%></span>
|
||||
<span data-k="changed"><%:The order changed on this page only. Press Save & Apply to store it on the router.%></span>
|
||||
<span data-k="restored"><%:Saved order restored.%></span>
|
||||
<span data-k="wait"><%:Automatic ping is still running (%d left). Sorting will run when it finishes.%></span>
|
||||
<span data-k="waitFail"><%:Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again.%></span>
|
||||
<span data-k="filter"><%:Filter:%></span>
|
||||
<span data-k="all"><%:All%></span>
|
||||
<span data-k="empty"><%:(empty)%></span>
|
||||
<span data-k="type"><%:Type%></span>
|
||||
<span data-k="addr"><%:Address%></span>
|
||||
<span data-k="fWord"><%:Word in remarks%></span>
|
||||
<span data-k="search"><%:search...%></span>
|
||||
<span data-k="searchHint"><%:Show only rows that contain this text%></span>
|
||||
<span data-k="shown"><%:Shown %s of %t%></span>
|
||||
<span data-k="reset"><%:Reset%></span>
|
||||
<span data-k="resetHint"><%:Show all rows%></span>
|
||||
<span data-k="filterHint"><%:The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows.%></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
//init logic
|
||||
if (window.pwNodeSort)
|
||||
pwNodeSort.init();
|
||||
|
||||
function get_remarks_name(o) {
|
||||
let str = "";
|
||||
@@ -1511,6 +1637,8 @@ table td, .table .td {
|
||||
get_now_use_node();
|
||||
|
||||
pingAllNodes();
|
||||
if (window.pwNodeSort)
|
||||
pwNodeSort.enhanceAll();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -974,8 +974,8 @@ msgstr "从 Geo 文件生成规则"
|
||||
msgid "Generate rule lists such as GFW, China domains, and China IP ranges based on Geo files."
|
||||
msgstr "根据 Geo 文件生成规则列表,包括 GFW、中国域名和中国 IP 段等。"
|
||||
|
||||
msgid "When manually updating with this option enabled, rules will be regenerated from existing Geo files even if no new version is available."
|
||||
msgstr "启用此选项后手动更新时,即使没有新版本也会从现有 Geo 文件重新生成规则。"
|
||||
msgid "GeoSite files without GFW rules cannot generate a GFW list (e.g. the original v2fly/Xray files)."
|
||||
msgstr "不含 GFW 规则的 GeoSite 文件无法生成 GFW 列表(如 v2fly/Xray 原版)。"
|
||||
|
||||
msgid "Enable Geo Data Parsing"
|
||||
msgstr "开启 Geo 数据解析"
|
||||
@@ -2048,3 +2048,57 @@ msgstr "WARP 设备私钥"
|
||||
|
||||
msgid "WARP Endpoint Public Key"
|
||||
msgstr "WARP 服务端公钥"
|
||||
|
||||
msgid "Click to sort; click again to reverse. Press Save & Apply to keep the order."
|
||||
msgstr "点击表头排序,再次点击切换方向。按保存并应用可保留顺序。"
|
||||
|
||||
msgid "Sort by type or protocol, then by name"
|
||||
msgstr "先按类型或协议排序,再按名称排序"
|
||||
|
||||
msgid "Sort by server address and port"
|
||||
msgstr "按服务器地址和端口排序"
|
||||
|
||||
msgid "Restore the saved order"
|
||||
msgstr "恢复已保存的顺序"
|
||||
|
||||
msgid "The order changed on this page only. Press Save & Apply to store it on the router."
|
||||
msgstr "顺序仅在本页改变。按保存并应用才会写入路由器。"
|
||||
|
||||
msgid "Saved order restored."
|
||||
msgstr "已恢复保存的顺序。"
|
||||
|
||||
msgid "Automatic ping is still running (%d left). Sorting will run when it finishes."
|
||||
msgstr "自动探测仍在进行(剩余 %d)。结束后会立即排序。"
|
||||
|
||||
msgid "Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again."
|
||||
msgstr "自动探测在 2 分钟内未结束,已取消排序。请刷新页面后重试。"
|
||||
|
||||
msgid "Filter:"
|
||||
msgstr "筛选:"
|
||||
|
||||
msgid "(empty)"
|
||||
msgstr "(空)"
|
||||
|
||||
msgid "Word in remarks"
|
||||
msgstr "备注关键词"
|
||||
|
||||
msgid "Address"
|
||||
msgstr "地址"
|
||||
|
||||
msgid "search..."
|
||||
msgstr "搜索..."
|
||||
|
||||
msgid "Show only rows that contain this text"
|
||||
msgstr "只显示包含此文本的行"
|
||||
|
||||
msgid "Shown %s of %t"
|
||||
msgstr "显示 %s / %t"
|
||||
|
||||
msgid "Reset"
|
||||
msgstr "重置"
|
||||
|
||||
msgid "Show all rows"
|
||||
msgstr "显示全部行"
|
||||
|
||||
msgid "The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows."
|
||||
msgstr "筛选只在本页隐藏行,不会改动路由器上的设置。全选会跳过隐藏行。"
|
||||
@@ -640,8 +640,8 @@ start_global() {
|
||||
}
|
||||
|
||||
[ "$protocol" = "_shunt" ] && {
|
||||
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
|
||||
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
|
||||
local geoip_path="${GEO_DIR%/}/geoip.dat"
|
||||
local geosite_path="${GEO_DIR%/}/geosite.dat"
|
||||
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
|
||||
echolog "* 缺少Geo规则文件,TCP Sing-Box分流节点无法正常使用!"
|
||||
fi
|
||||
@@ -724,8 +724,8 @@ start_global() {
|
||||
}
|
||||
|
||||
[ "$protocol" = "_shunt" ] && {
|
||||
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
|
||||
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
|
||||
local geoip_path="${GEO_DIR%/}/geoip.dat"
|
||||
local geosite_path="${GEO_DIR%/}/geosite.dat"
|
||||
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
|
||||
echolog "* 缺少Geo规则文件,TCP Xray分流节点无法正常使用!"
|
||||
fi
|
||||
@@ -1718,8 +1718,14 @@ start() {
|
||||
mkdir -p /tmp/etc /tmp/log $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2
|
||||
set_cache_var "LOCAL_DNS" "$LOCAL_DNS"
|
||||
get_config
|
||||
export V2RAY_LOCATION_ASSET=$(config_n_get @global_rules[0] v2ray_location_asset "/usr/share/v2ray/")
|
||||
export XRAY_LOCATION_ASSET=$V2RAY_LOCATION_ASSET
|
||||
GEO_DIR=$(config_n_get @global_rules[0] v2ray_location_asset "/usr/share/v2ray/")
|
||||
export XRAY_LOCATION_ASSET="$TMP_PATH2/geodata"
|
||||
mkdir -p "$XRAY_LOCATION_ASSET" 2>/dev/null
|
||||
local geo_file
|
||||
for geo_file in "${GEO_DIR%/}"/*.dat; do
|
||||
[ -f "$geo_file" ] || continue
|
||||
ln -sf "$geo_file" "$XRAY_LOCATION_ASSET/${geo_file##*/}" 2>/dev/null
|
||||
done
|
||||
export ENABLE_DEPRECATED_GEOSITE=true
|
||||
export ENABLE_DEPRECATED_GEOIP=true
|
||||
export SS_SYSTEM_DNS_RESOLVER_FORCE_BUILTIN=1
|
||||
@@ -1800,7 +1806,6 @@ stop() {
|
||||
busybox pgrep -af "${CONFIG}/monitor\.sh" | xargs -r kill -9 >/dev/null 2>&1
|
||||
busybox pgrep -f "sleep.*(6s|9s|58s)" | xargs -r kill -9 >/dev/null 2>&1
|
||||
busybox pgrep -af "${CONFIG}/" | awk '! /app\.sh|subscribe\.lua|rule_update\.lua|tasks\.sh|server_app\.lua|ujail/{print $1}' | xargs -r kill -9 >/dev/null 2>&1
|
||||
unset V2RAY_LOCATION_ASSET
|
||||
unset XRAY_LOCATION_ASSET
|
||||
unset SS_SYSTEM_DNS_RESOLVER_FORCE_BUILTIN
|
||||
stop_crontab "$1"
|
||||
|
||||
@@ -1597,6 +1597,7 @@ stop() {
|
||||
rm -rf $TMP_PATH2/singbox*
|
||||
rm -rf $TMP_PATH2/dnsmasq*
|
||||
rm -rf $TMP_PATH2/geo_output
|
||||
rm -rf $TMP_PATH2/geodata
|
||||
rm -rf $TMP_PATH2/user_rules
|
||||
}
|
||||
flush_include
|
||||
|
||||
@@ -1610,6 +1610,7 @@ stop() {
|
||||
rm -rf $TMP_PATH2/singbox*
|
||||
rm -rf $TMP_PATH2/dnsmasq*
|
||||
rm -rf $TMP_PATH2/geo_output
|
||||
rm -rf $TMP_PATH2/geodata
|
||||
rm -rf $TMP_PATH2/user_rules
|
||||
}
|
||||
flush_include
|
||||
|
||||
Reference in new issue
Block a user