🍕 Sync 2026-09-17 11:06:12

This commit is contained in:
github-actions[bot] committed 2026-09-17 11:06:12 +08:00
1 parent d2ed090859
commit a63296e920
10 files changed
+510 -48

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
PKG_VERSION:=0.1.41 PKG_VERSION:=0.1.41
PKG_RELEASE:=19 PKG_RELEASE:=20
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI). # Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
ifdef SOURCE_DATE_EPOCH ifdef SOURCE_DATE_EPOCH
@@ -12,6 +12,10 @@ return baseclass.extend({
APP_VERSION: '0.1.41', APP_VERSION: '0.1.41',
ROW_LIMIT_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000], ROW_LIMIT_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000],
DEFAULT_ROW_LIMIT: 100, DEFAULT_ROW_LIMIT: 100,
FETCH_MODE_OPTIONS: ['auto', 'manual'],
MANUAL_FETCH_LINES_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000],
DEFAULT_FETCH_MODE: 'auto',
DEFAULT_MANUAL_FETCH_LINES: 100,
/* Row pass/deny tint: classic green/red default; accessible teal/orange */ /* Row pass/deny tint: classic green/red default; accessible teal/orange */
ROW_TINT_OPTIONS: ['off', 'classic', 'accessible'], ROW_TINT_OPTIONS: ['off', 'classic', 'accessible'],
DEFAULT_ROW_TINT: 'classic', DEFAULT_ROW_TINT: 'classic',
@@ -94,6 +94,10 @@ function optionNodes(pairs) {
return view.extend({ return view.extend({
rowLimit: constants.DEFAULT_ROW_LIMIT, rowLimit: constants.DEFAULT_ROW_LIMIT,
fetchMode: constants.DEFAULT_FETCH_MODE,
manualFetchLines: constants.DEFAULT_MANUAL_FETCH_LINES,
manualFetchLinesExplicit: false,
rpcPreferencesResolved: false,
entries: [], entries: [],
sessionSeen: null, sessionSeen: null,
pauseBufferLoading: false, pauseBufferLoading: false,
@@ -108,14 +112,19 @@ return view.extend({
pollRequestQueued: false, pollRequestQueued: false,
pollRequestWaiters: [], pollRequestWaiters: [],
pollRequestQueuedWaiters: [], pollRequestQueuedWaiters: [],
coordinatorDisposed: false,
/* Layer 2 — visibility / RTT cadence / shed surfacing. */ /* Layer 2 — visibility / RTT cadence / shed surfacing. */
pollEpoch: 0, pollEpoch: 0,
pollCadenceSec: 1, pollCadenceSec: 1,
rttStreakKind: null, rttStreakKind: null,
rttStreakCount: 0, rttStreakCount: 0,
serverAdaptive: 1, serverAdaptive: undefined,
serverTruncated: 0, serverTruncated: 0,
serverShed: null, serverShed: null,
lastPollRequestedLines: null,
lastPollEffectiveLimit: null,
lastPollReturnedMessages: null,
fillingBuffer: false,
weakDevice: false, weakDevice: false,
degradedSampling: false, degradedSampling: false,
/* Layer 2 summary fallback — rows remain available behind an explicit toggle. */ /* Layer 2 summary fallback — rows remain available behind an explicit toggle. */
@@ -194,27 +203,163 @@ return view.extend({
.map((k) => '%s=%s'.format(encodeURIComponent(k), encodeURIComponent(filters[k]))); .map((k) => '%s=%s'.format(encodeURIComponent(k), encodeURIComponent(filters[k])));
if (this.rowLimit !== constants.DEFAULT_ROW_LIMIT) if (this.rowLimit !== constants.DEFAULT_ROW_LIMIT)
parts.push('limit=%s'.format(encodeURIComponent(this.rowLimit))); parts.push('limit=%s'.format(encodeURIComponent(this.rowLimit)));
if (this.fetchMode === 'manual') {
parts.push('poll=manual');
parts.push('maxraw=%s'.format(encodeURIComponent(this.manualFetchLines)));
}
if (this.viewMode === 'detailed') parts.push('view=detailed'); if (this.viewMode === 'detailed') parts.push('view=detailed');
location.hash = parts.join('&'); location.hash = parts.join('&');
}, },
applyHash() { hashEntries() {
if (!location.hash || location.hash.length < 2) return; if (!location.hash || location.hash.length < 2) return [];
const entries = location.hash.substring(1).split('&'); const entries = location.hash.substring(1).split('&');
const result = [];
for (let i = 0; i < entries.length; i++) { for (let i = 0; i < entries.length; i++) {
const kv = entries[i].split('='); const kv = entries[i].split('=');
if (kv.length !== 2) continue; if (kv.length !== 2) continue;
const key = decodeURIComponent(kv[0]); let key;
const val = decodeURIComponent(kv[1]); let val;
if (key === 'limit') { try {
const n = parseInt(val, 10); key = decodeURIComponent(kv[0]);
if (isFinite(n) && constants.ROW_LIMIT_OPTIONS.indexOf(n) >= 0) { val = decodeURIComponent(kv[1]);
this.applyRowLimit(n); } catch (e) {
this.saveRowLimit();
}
continue; continue;
} }
result.push({ key: key, val: val });
}
return result;
},
readFetchMode() {
const v = storedValue('fwlive-poll-mode', constants.DEFAULT_FETCH_MODE);
return constants.FETCH_MODE_OPTIONS.indexOf(v) >= 0 ? v : constants.DEFAULT_FETCH_MODE;
},
saveFetchMode() {
storeValue('fwlive-poll-mode', this.fetchMode);
},
readManualFetchLines() {
const raw = storedValue('fwlive-manual-lines', '');
const n = Number(raw);
return String(n) === raw && constants.MANUAL_FETCH_LINES_OPTIONS.indexOf(n) >= 0 ? n : null;
},
saveManualFetchLines() {
storeValue('fwlive-manual-lines', String(this.manualFetchLines));
},
autoFetchLines() {
return Math.min(Math.max(this.rowLimit * 4, 100), constants.FETCH_LINES_MAX);
},
snapManualFetchLines(lines) {
const options = constants.MANUAL_FETCH_LINES_OPTIONS;
let snapped = options[0];
for (let i = 0; i < options.length; i++) {
if (options[i] > lines) break;
snapped = options[i];
}
return snapped;
},
resolveRpcPreferences() {
if (this.rpcPreferencesResolved) return;
this.applyRowLimit(this.readRowLimit());
this.fetchMode = this.readFetchMode();
const storedManual = this.readManualFetchLines();
this.manualFetchLinesExplicit = storedManual !== null;
this.manualFetchLines =
storedManual === null ? this.snapManualFetchLines(this.autoFetchLines()) : storedManual;
let hashLimit = null;
let hashMode = null;
let hashManual = null;
const entries = this.hashEntries();
for (let i = 0; i < entries.length; i++) {
const key = entries[i].key;
const val = entries[i].val;
if (key === 'limit') {
const n = Number(val);
if (String(n) === val && constants.ROW_LIMIT_OPTIONS.indexOf(n) >= 0) hashLimit = n;
continue;
}
if (key === 'poll') {
if (constants.FETCH_MODE_OPTIONS.indexOf(val) >= 0) hashMode = val;
continue;
}
if (key === 'maxraw') {
const n = Number(val);
if (String(n) === val && constants.MANUAL_FETCH_LINES_OPTIONS.indexOf(n) >= 0)
hashManual = n;
}
}
if (hashLimit !== null) {
this.applyRowLimit(hashLimit);
this.saveRowLimit();
}
if (hashMode !== null) {
this.fetchMode = hashMode;
this.saveFetchMode();
}
if (hashManual !== null && this.fetchMode === 'manual') {
this.manualFetchLines = hashManual;
this.manualFetchLinesExplicit = true;
this.saveManualFetchLines();
}
if (storedManual === null && !(hashManual !== null && this.fetchMode === 'manual')) {
this.manualFetchLines = this.snapManualFetchLines(this.autoFetchLines());
}
this.rpcPreferencesResolved = true;
},
requestedFetchLines() {
this.resolveRpcPreferences();
if (this.paused)
return this.fetchMode === 'manual' ? this.manualFetchLines : constants.FETCH_LINES_MAX;
return this.fetchMode === 'manual' ? this.manualFetchLines : this.autoFetchLines();
},
updateFillingState(beforeLength, reply, requestedLines) {
if (!this.paused || this.resumeMerge) {
this.fillingBuffer = false;
return;
}
const cap = this.ingestCap();
const grew = this.entries.length > beforeLength;
if (this.entries.length >= cap || !grew) {
this.fillingBuffer = false;
return;
}
const rawCount =
reply &&
typeof reply.messages_received === 'number' &&
isFinite(reply.messages_received) &&
Math.floor(reply.messages_received) === reply.messages_received &&
reply.messages_received >= 0
? reply.messages_received
: null;
const effective =
this.lastPollEffectiveLimit !== null ? this.lastPollEffectiveLimit : requestedLines;
const shortRead = rawCount !== null && rawCount < effective;
this.fillingBuffer = !shortRead && (rawCount === null || rawCount >= effective || grew);
},
applyHash() {
this.resolveRpcPreferences();
const entries = this.hashEntries();
for (let i = 0; i < entries.length; i++) {
const key = entries[i].key;
const val = entries[i].val;
if (key === 'limit' || key === 'poll' || key === 'maxraw') continue;
if (key === 'view') { if (key === 'view') {
if (val === 'advanced' || val === 'detailed') this.viewMode = 'detailed'; if (val === 'advanced' || val === 'detailed') this.viewMode = 'detailed';
else if (val === 'simple') this.viewMode = 'simple'; else if (val === 'simple') this.viewMode = 'simple';
@@ -732,6 +877,11 @@ return view.extend({
const epoch = this.pollEpoch; const epoch = this.pollEpoch;
const resumeMerge = !!this.resumeMerge; const resumeMerge = !!this.resumeMerge;
const fetchLines = this.requestedFetchLines();
const beforeLength = this.entries.length;
this.lastPollRequestedLines = fetchLines;
this.lastPollReturnedMessages = null;
this.lastPollEffectiveLimit = null;
const t0 = this.nowMs(); const t0 = this.nowMs();
let reply; let reply;
let errored = false; let errored = false;
@@ -739,9 +889,6 @@ return view.extend({
/* Raw logd lines, not post-filter rows. Fetch a multiple of the /* Raw logd lines, not post-filter rows. Fetch a multiple of the
* display limit so mixed syslog still fills the table; pause * display limit so mixed syslog still fills the table; pause
* reads the ring cap so the buffer can catch up. */ * reads the ring cap so the buffer can catch up. */
const fetchLines = this.paused
? constants.FETCH_LINES_MAX
: Math.min(Math.max(this.rowLimit * 4, 100), constants.FETCH_LINES_MAX);
reply = await callFwlivePoll({ reply = await callFwlivePoll({
addresses: [String(fetchLines)] addresses: [String(fetchLines)]
}); });
@@ -756,12 +903,14 @@ return view.extend({
if (!reply || typeof reply !== 'object' || Array.isArray(reply)) { if (!reply || typeof reply !== 'object' || Array.isArray(reply)) {
this.lastPollError = true; this.lastPollError = true;
this.fillingBuffer = false;
this.notePollRtt(rtt, true); this.notePollRtt(rtt, true);
this.updateAdaptiveBanner(); this.updateAdaptiveBanner();
return; return;
} }
if (reply.error) { if (reply.error) {
this.lastPollError = true; this.lastPollError = true;
this.fillingBuffer = false;
this.notePollRtt(rtt, true); this.notePollRtt(rtt, true);
this.updateAdaptiveBanner(); this.updateAdaptiveBanner();
return; return;
@@ -769,18 +918,32 @@ return view.extend({
const raw = reply.log; const raw = reply.log;
if (!Array.isArray(raw)) { if (!Array.isArray(raw)) {
this.lastPollError = true; this.lastPollError = true;
this.fillingBuffer = false;
this.notePollRtt(rtt, true); this.notePollRtt(rtt, true);
this.updateAdaptiveBanner(); this.updateAdaptiveBanner();
return; return;
} }
this.lastPollError = false; this.lastPollError = false;
this.serverAdaptive = reply.adaptive === 0 || reply.adaptive === false ? 0 : 1; if (reply.adaptive === 0 || reply.adaptive === false) this.serverAdaptive = 0;
else if (reply.adaptive === 1 || reply.adaptive === true) this.serverAdaptive = 1;
else this.serverAdaptive = undefined;
this.serverTruncated = reply.truncated ? 1 : 0; this.serverTruncated = reply.truncated ? 1 : 0;
this.serverShed = this.serverShed =
reply.shed && typeof reply.shed === 'object' && !Array.isArray(reply.shed) reply.shed && typeof reply.shed === 'object' && !Array.isArray(reply.shed)
? reply.shed ? reply.shed
: null; : null;
this.lastPollReturnedMessages = raw.length;
if (
this.serverAdaptive === 1 &&
typeof reply.effective_limit === 'number' &&
isFinite(reply.effective_limit) &&
Math.floor(reply.effective_limit) === reply.effective_limit &&
reply.effective_limit >= 1 &&
reply.effective_limit <= constants.FETCH_LINES_MAX
) {
this.lastPollEffectiveLimit = reply.effective_limit;
}
this.notePollRtt(rtt, errored); this.notePollRtt(rtt, errored);
this.updateAdaptiveBanner(); this.updateAdaptiveBanner();
@@ -803,6 +966,7 @@ return view.extend({
rowLimit: this.rowLimit, rowLimit: this.rowLimit,
fetchLinesMax: constants.FETCH_LINES_MAX fetchLinesMax: constants.FETCH_LINES_MAX
}); });
this.updateFillingState(beforeLength, reply, fetchLines);
/* A stale request returns above. Keep this obligation until a current /* A stale request returns above. Keep this obligation until a current
* request has actually applied the merged batch. */ * request has actually applied the merged batch. */
if (resumeMerge) this.resumeMerge = false; if (resumeMerge) this.resumeMerge = false;
@@ -835,6 +999,7 @@ return view.extend({
const bits = []; const bits = [];
if (this.paused) { if (this.paused) {
if (this.pauseBufferLoading) bits.push(_('loading buffer')); if (this.pauseBufferLoading) bits.push(_('loading buffer'));
if (this.fillingBuffer) bits.push(_('buffer filling'));
} }
const cap = this.ingestCap(); const cap = this.ingestCap();
@@ -954,6 +1119,7 @@ return view.extend({
}, },
resumePollingAfterVisible() { resumePollingAfterVisible() {
if (this.coordinatorDisposed) return;
this.bumpPollEpoch(); this.bumpPollEpoch();
this.resetRttHistory(); this.resetRttHistory();
if (!this.pollFn) this.pollFn = this.pollData.bind(this); if (!this.pollFn) this.pollFn = this.pollData.bind(this);
@@ -999,18 +1165,55 @@ return view.extend({
this.visibilityBound = false; this.visibilityBound = false;
}, },
disposeCoordinator() {
if (this.coordinatorDisposed) return;
this.coordinatorDisposed = true;
this.bumpPollEpoch();
this.stopPollingForHidden();
this.unbindVisibility();
this.pollFn = null;
this.pollRequestQueued = false;
const queued = this.pollRequestQueuedWaiters;
this.pollRequestQueuedWaiters = [];
for (let i = 0; i < queued.length; i++) queued[i].resolve();
},
updateAdaptiveBanner() { updateAdaptiveBanner() {
const el = document.getElementById('fwlive-adaptive'); const el = document.getElementById('fwlive-adaptive');
if (!el) return; if (!el) return;
if (!el.style) el.style = { display: '' }; if (!el.style) el.style = { display: '' };
if (!this.clientBackoffEnabled()) {
el.style.display = 'none';
el.textContent = '';
return;
}
const parts = []; const parts = [];
const mode = this.fetchMode === 'manual' ? _('Manual') : _('Auto');
if (this.lastPollRequestedLines !== null && this.lastPollReturnedMessages !== null) {
if (
this.serverAdaptive === 1 &&
this.lastPollEffectiveLimit !== null &&
this.lastPollEffectiveLimit < this.lastPollRequestedLines
) {
parts.push(
_(
'%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned'
).format(
mode,
this.lastPollRequestedLines,
this.lastPollEffectiveLimit,
this.lastPollReturnedMessages
)
);
} else {
parts.push(
_('%s · requested up to %d raw lines · %d firewall messages returned').format(
mode,
this.lastPollRequestedLines,
this.lastPollReturnedMessages
)
);
}
}
if (this.serverAdaptive === undefined) parts.push(_('Server protection state is unknown.'));
else if (this.serverAdaptive === 0)
parts.push(_('Server adaptive protection is disabled.'));
if (this.degradedSampling) if (this.degradedSampling)
parts.push(_('Degraded — sampling (slow poll RTT; cadence reduced).')); parts.push(_('Degraded — sampling (slow poll RTT; cadence reduced).'));
if (this.serverShed && this.serverShed.limit) if (this.serverShed && this.serverShed.limit)
@@ -1030,6 +1233,22 @@ return view.extend({
} }
}, },
fetchModeOptions() {
return optionNodes([
['auto', _('Auto')],
['manual', _('Manual')]
]);
},
manualFetchLinesOptions() {
const pairs = [];
for (let i = 0; i < constants.MANUAL_FETCH_LINES_OPTIONS.length; i++) {
const n = constants.MANUAL_FETCH_LINES_OPTIONS[i];
pairs.push([String(n), String(n)]);
}
return optionNodes(pairs);
},
summaryListText(label, values) { summaryListText(label, values) {
const lines = [label + ':']; const lines = [label + ':'];
if (!Array.isArray(values) || !values.length) { if (!Array.isArray(values) || !values.length) {
@@ -1284,6 +1503,8 @@ return view.extend({
const label = document.getElementById('fwlive-watch-label'); const label = document.getElementById('fwlive-watch-label');
const pauseBtn = document.getElementById('fwlive-pause'); const pauseBtn = document.getElementById('fwlive-pause');
const sel = document.getElementById('fwlive-limit'); const sel = document.getElementById('fwlive-limit');
const modeSel = document.getElementById('fwlive-fetch-mode');
const manualSel = document.getElementById('fwlive-manual-lines');
const hostCb = document.getElementById('fwlive-show-hostnames'); const hostCb = document.getElementById('fwlive-show-hostnames');
if (map) { if (map) {
@@ -1297,6 +1518,11 @@ return view.extend({
if (label) label.textContent = this.paused ? _('Paused') : _('Watching'); if (label) label.textContent = this.paused ? _('Paused') : _('Watching');
if (pauseBtn) pauseBtn.textContent = this.paused ? _('Resume') : _('Pause'); if (pauseBtn) pauseBtn.textContent = this.paused ? _('Resume') : _('Pause');
if (sel) sel.value = String(this.rowLimit); if (sel) sel.value = String(this.rowLimit);
if (modeSel) modeSel.value = this.fetchMode;
if (manualSel) {
manualSel.value = String(this.manualFetchLines);
manualSel.disabled = this.fetchMode !== 'manual';
}
if (hostCb) hostCb.checked = !!this.showHostnames; if (hostCb) hostCb.checked = !!this.showHostnames;
this.updateRowTintUi(); this.updateRowTintUi();
}, },
@@ -1312,6 +1538,29 @@ return view.extend({
else this.renderRows(true); else this.renderRows(true);
}, },
onFetchModeChange(ev) {
const mode = ev && ev.target ? ev.target.value : '';
if (constants.FETCH_MODE_OPTIONS.indexOf(mode) < 0) return;
this.fetchMode = mode;
if (mode === 'manual' && !this.manualFetchLinesExplicit) {
this.manualFetchLines = this.snapManualFetchLines(this.autoFetchLines());
this.manualFetchLinesExplicit = true;
}
this.saveFetchMode();
if (mode === 'manual') this.saveManualFetchLines();
this.updateStreamControlsUi();
this.updateHash(this.readFilters());
},
onManualFetchLinesChange(ev) {
const n = parseInt(ev && ev.target ? ev.target.value : '', 10);
if (constants.MANUAL_FETCH_LINES_OPTIONS.indexOf(n) < 0) return;
this.manualFetchLines = n;
this.manualFetchLinesExplicit = true;
this.saveManualFetchLines();
this.updateHash(this.readFilters());
},
onPauseClick() { onPauseClick() {
const wasPaused = this.paused; const wasPaused = this.paused;
this.paused = !this.paused; this.paused = !this.paused;
@@ -1332,6 +1581,7 @@ return view.extend({
} }
if (wasPaused && !this.paused) { if (wasPaused && !this.paused) {
this.fillingBuffer = false;
this.followLive = true; this.followLive = true;
/* Merge pause buffer with the first live poll — do not replace. */ /* Merge pause buffer with the first live poll — do not replace. */
this.resumeMerge = true; this.resumeMerge = true;
@@ -1761,6 +2011,13 @@ return view.extend({
const limitSel = document.getElementById('fwlive-limit'); const limitSel = document.getElementById('fwlive-limit');
if (limitSel) limitSel.addEventListener('change', this.onRowLimitChange.bind(this)); if (limitSel) limitSel.addEventListener('change', this.onRowLimitChange.bind(this));
const modeSel = document.getElementById('fwlive-fetch-mode');
if (modeSel) modeSel.addEventListener('change', this.onFetchModeChange.bind(this));
const manualSel = document.getElementById('fwlive-manual-lines');
if (manualSel)
manualSel.addEventListener('change', this.onManualFetchLinesChange.bind(this));
const hostCb = document.getElementById('fwlive-show-hostnames'); const hostCb = document.getElementById('fwlive-show-hostnames');
if (hostCb) hostCb.addEventListener('change', this.onShowHostnamesChange.bind(this)); if (hostCb) hostCb.addEventListener('change', this.onShowHostnamesChange.bind(this));
@@ -1779,7 +2036,7 @@ return view.extend({
}, },
requestPoll() { requestPoll() {
if (this.isTabHidden()) return Promise.resolve(); if (this.coordinatorDisposed || this.isTabHidden()) return Promise.resolve();
const waiter = {}; const waiter = {};
const promise = new Promise(function (resolve) { const promise = new Promise(function (resolve) {
@@ -1836,7 +2093,7 @@ return view.extend({
/* If visibility changed while the request was active, retain the /* If visibility changed while the request was active, retain the
* queued intent until the visible catch-up can start it. */ * queued intent until the visible catch-up can start it. */
if (this.pollRequestQueued && !this.isTabHidden()) { if (this.pollRequestQueued && !this.coordinatorDisposed && !this.isTabHidden()) {
const queued = this.pollRequestQueuedWaiters; const queued = this.pollRequestQueuedWaiters;
this.pollRequestQueuedWaiters = []; this.pollRequestQueuedWaiters = [];
this.pollRequestQueued = false; this.pollRequestQueued = false;
@@ -1877,6 +2134,9 @@ return view.extend({
}, },
load() { load() {
/* RPC-affecting preferences must precede poll registration and the first
* request; filter widgets still restore in addFooter after render. */
this.resolveRpcPreferences();
this.bindVisibility(); this.bindVisibility();
if (!this.pollFn) { if (!this.pollFn) {
this.pollFn = this.pollData.bind(this); this.pollFn = this.pollData.bind(this);
@@ -1887,15 +2147,7 @@ return view.extend({
window.addEventListener( window.addEventListener(
'pagehide', 'pagehide',
function () { function () {
this.unbindVisibility(); this.disposeCoordinator();
if (this.pollFn) {
try {
poll.remove(this.pollFn);
} catch (e) {
/* poll gone */
}
this.pollFn = null;
}
if (this.filterInputTimer) { if (this.filterInputTimer) {
clearTimeout(this.filterInputTimer); clearTimeout(this.filterInputTimer);
this.filterInputTimer = null; this.filterInputTimer = null;
@@ -2077,6 +2329,36 @@ return view.extend({
this.limitSelectOptions() this.limitSelectOptions()
) )
]), ]),
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-fetch-mode' }, [
_('Fetch budget'),
E(
'select',
{
'id': 'fwlive-fetch-mode',
'class': 'cbi-input-select',
'aria-controls': 'fwlive-manual-lines'
},
this.fetchModeOptions()
)
]),
E(
'label',
{ 'class': 'fwlive-display-ctl', 'for': 'fwlive-manual-lines' },
[
_('Maximum raw lines'),
E(
'select',
{
'id': 'fwlive-manual-lines',
'class': 'cbi-input-select',
'title': _(
'Manual still uses server protection and poll cadence'
)
},
this.manualFetchLinesOptions()
)
]
),
E('label', { 'class': 'fwlive-display-ctl' }, [ E('label', { 'class': 'fwlive-display-ctl' }, [
E('input', { E('input', {
'id': 'fwlive-row-tint-toggle', 'id': 'fwlive-row-tint-toggle',
@@ -2236,6 +2518,16 @@ return view.extend({
'Display options on the bar set Limit, row tint, palette, and hostnames.' 'Display options on the bar set Limit, row tint, palette, and hostnames.'
) )
]), ]),
E('li', {}, [
_(
'Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum.'
)
]),
E('li', {}, [
_(
'Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval.'
)
]),
E('li', {}, [ E('li', {}, [
_( _(
'For a responsive table on a weak device, keep Limit at 250 rows or below. The weak-device cap affects rendered rows; the buffer can still retain more.' 'For a responsive table on a weak device, keep Limit at 250 rows or below. The weak-device cap affects rendered rows; the buffer can still retain more.'
@@ -2306,7 +2598,6 @@ return view.extend({
this.hostnameFailed = new Map(); this.hostnameFailed = new Map();
this.resolveGeneration = 0; this.resolveGeneration = 0;
this.lastPollError = false; this.lastPollError = false;
this.applyRowLimit(this.readRowLimit());
this.applyHash(); this.applyHash();
this.attachHandlers(); this.attachHandlers();
this.applyRowTintMode(); this.applyRowTintMode();
+38
View File
@@ -790,3 +790,41 @@ msgstr "Häufigste Regeln"
msgid "Top talkers" msgid "Top talkers"
msgstr "Häufigste Absender" msgstr "Häufigste Absender"
msgid "Auto"
msgstr "Auto"
msgid "Fetch budget"
msgstr "Fetch budget"
msgid "Maximum raw lines"
msgstr "Maximum raw lines"
msgid "Manual"
msgstr "Manual"
msgid "Manual still uses server protection and poll cadence"
msgstr "Manual still uses server protection and poll cadence"
msgid "%s · requested up to %d raw lines · %d firewall messages returned"
msgstr "%s · requested up to %d raw lines · %d firewall messages returned"
msgid "%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned"
msgstr "%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned"
msgid "Server adaptive protection is disabled."
msgstr "Server adaptive protection is disabled."
msgid "Server protection state is unknown."
msgstr "Server protection state is unknown."
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2250
msgid "Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum."
msgstr "Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum."
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2255
msgid "Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval."
msgstr "Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval."
msgid "buffer filling"
msgstr "buffer filling"
+38
View File
@@ -795,3 +795,41 @@ msgstr "Частые правила"
msgid "Top talkers" msgid "Top talkers"
msgstr "Частые источники" msgstr "Частые источники"
msgid "Auto"
msgstr "Auto"
msgid "Fetch budget"
msgstr "Fetch budget"
msgid "Maximum raw lines"
msgstr "Maximum raw lines"
msgid "Manual"
msgstr "Manual"
msgid "Manual still uses server protection and poll cadence"
msgstr "Manual still uses server protection and poll cadence"
msgid "%s · requested up to %d raw lines · %d firewall messages returned"
msgstr "%s · requested up to %d raw lines · %d firewall messages returned"
msgid "%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned"
msgstr "%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned"
msgid "Server adaptive protection is disabled."
msgstr "Server adaptive protection is disabled."
msgid "Server protection state is unknown."
msgstr "Server protection state is unknown."
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2250
msgid "Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum."
msgstr "Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum."
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2255
msgid "Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval."
msgstr "Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval."
msgid "buffer filling"
msgstr "buffer filling"
@@ -735,3 +735,53 @@ msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1959 #: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1959
msgid "Show rows" msgid "Show rows"
msgstr "" msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1134
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1182
msgid "Auto"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2283
msgid "Fetch budget"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2295
msgid "Maximum raw lines"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1134
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1183
msgid "Manual"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2301
msgid "Manual still uses server protection and poll cadence"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1151
msgid "%s · requested up to %d raw lines · %d firewall messages returned"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1142
msgid "%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1160
msgid "Server adaptive protection is disabled."
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1159
msgid "Server protection state is unknown."
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2250
msgid "Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum."
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2255
msgid "Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval."
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:997
msgid "buffer filling"
msgstr ""
@@ -759,3 +759,41 @@ msgstr "主要规则"
msgid "Top talkers" msgid "Top talkers"
msgstr "主要通信方" msgstr "主要通信方"
msgid "Auto"
msgstr "Auto"
msgid "Fetch budget"
msgstr "Fetch budget"
msgid "Maximum raw lines"
msgstr "Maximum raw lines"
msgid "Manual"
msgstr "Manual"
msgid "Manual still uses server protection and poll cadence"
msgstr "Manual still uses server protection and poll cadence"
msgid "%s · requested up to %d raw lines · %d firewall messages returned"
msgstr "%s · requested up to %d raw lines · %d firewall messages returned"
msgid "%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned"
msgstr "%s · requested up to %d raw lines · server limited fetch to %d · %d firewall messages returned"
msgid "Server adaptive protection is disabled."
msgstr "Server adaptive protection is disabled."
msgid "Server protection state is unknown."
msgstr "Server protection state is unknown."
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2250
msgid "Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum."
msgstr "Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum."
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2255
msgid "Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval."
msgstr "Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval."
msgid "buffer filling"
msgstr "buffer filling"
@@ -443,13 +443,14 @@ fwlive_adaptive_is_hot() {
# Merge adaptive siblings into a JSON object ending with }. A successful # Merge adaptive siblings into a JSON object ending with }. A successful
# filter already carries the exact messages_received count; do not append a # filter already carries the exact messages_received count; do not append a
# duplicate key in that case. # duplicate key in that case.
# Args: json_body shed_flag limit truncated messages_received # Args: json_body shed_flag limit truncated messages_received success_flag
fwlive_adaptive_merge_reply() { fwlive_adaptive_merge_reply() {
_body=$1 _body=$1
_shed=$2 _shed=$2
_limit=$3 _limit=$3
_trunc=$4 _trunc=$4
_msgs=$5 _msgs=$5
_success=${6:-0}
_adapt=1 _adapt=1
fwlive_adaptive_enabled || _adapt=0 fwlive_adaptive_enabled || _adapt=0
case "$_body" in case "$_body" in
@@ -457,6 +458,8 @@ fwlive_adaptive_merge_reply() {
*) printf '%s' "$_body"; return 0 ;; *) printf '%s' "$_body"; return 0 ;;
esac esac
_base=${_body%\}} _base=${_body%\}}
_effective=
[ "$_success" = 1 ] && _effective=",\"effective_limit\":$_limit"
_has_msgs=0 _has_msgs=0
case "$_body" in case "$_body" in
*',"messages_received":'*) _has_msgs=1 ;; *',"messages_received":'*) _has_msgs=1 ;;
@@ -471,18 +474,18 @@ fwlive_adaptive_merge_reply() {
fi fi
if [ "$_shed" = 1 ]; then if [ "$_shed" = 1 ]; then
if [ "$_has_msgs" = 1 ]; then if [ "$_has_msgs" = 1 ]; then
printf '%s,"adaptive":1,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \ printf '%s,"adaptive":1%s,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \
"$_base" "$_trunc" "$_limit" "$_base" "$_effective" "$_trunc" "$_limit"
else else
printf '%s,"adaptive":1,"messages_received":%s,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \ printf '%s,"adaptive":1,"messages_received":%s%s,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \
"$_base" "$_msgs" "$_trunc" "$_limit" "$_base" "$_msgs" "$_effective" "$_trunc" "$_limit"
fi fi
else else
if [ "$_has_msgs" = 1 ]; then if [ "$_has_msgs" = 1 ]; then
printf '%s,"adaptive":1,"truncated":%s}' "$_base" "$_trunc" printf '%s,"adaptive":1%s,"truncated":%s}' "$_base" "$_effective" "$_trunc"
else else
printf '%s,"adaptive":1,"messages_received":%s,"truncated":%s}' \ printf '%s,"adaptive":1,"messages_received":%s%s,"truncated":%s}' \
"$_base" "$_msgs" "$_trunc" "$_base" "$_msgs" "$_effective" "$_trunc"
fi fi
fi fi
} }
+2 -2
View File
@@ -440,7 +440,7 @@ fetch_firewall_logs() {
# Do not record ~0 ms as cold: failure is not healthy fast processing # Do not record ~0 ms as cold: failure is not healthy fast processing
# and must not clear an existing hot/shed cap (#329 Hermes Q1). # and must not clear an existing hot/shed cap (#329 Hermes Q1).
out='{"log":[],"error":"log_read_failed"}' out='{"log":[],"error":"log_read_failed"}'
fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" 0 0 fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" 0 0 0
return 0 return 0
fi fi
@@ -486,7 +486,7 @@ fetch_firewall_logs() {
_trunc=0 _trunc=0
[ "$shed_flag" = 1 ] && _trunc=1 [ "$shed_flag" = 1 ] && _trunc=1
[ "$lines" -lt "$requested" ] && _trunc=1 [ "$lines" -lt "$requested" ] && _trunc=1
fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" "$_trunc" 0 fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" "$_trunc" 0 "$_filter_ok"
} }
poll_logs() { poll_logs() {
+4 -4
View File
@@ -1,14 +1,14 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=mihomo-meta PKG_NAME:=mihomo-meta
PKG_VERSION:=1.19.30 PKG_VERSION:=1.19.31
PKG_RELEASE:=14 PKG_RELEASE:=15
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_SOURCE_PROTO:=git PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
PKG_SOURCE_VERSION:=v1.19.30 PKG_SOURCE_VERSION:=v1.19.31
PKG_MIRROR_HASH:=skip PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL3.0+ PKG_LICENSE:=GPL3.0+
@@ -18,7 +18,7 @@ PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1 PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16 PKG_BUILD_FLAGS:=no-mips16
PKG_BUILD_VERSION:=v1.19.30 PKG_BUILD_VERSION:=v1.19.31
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_BUILD_TIME:=$(shell date -u -Iseconds) PKG_BUILD_TIME:=$(shell date -u -Iseconds)