⛅ Sync 2026-09-22 23:24:55

This commit is contained in:
github-actions[bot] committed 2026-09-22 23:24:55 +08:00
1 parent de9cc9a6a9
commit d45e210b8a
16 files changed
+174 -72

No files matched your search

@@ -3,6 +3,7 @@
/* Copyright 2025-2026 Lucas Albers <lucas.b.albers@gmail.com> */
'require baseclass'; /* LuCI require() needs Class.isSubclass — plain return {} fails */
'require fwlive.log as log';
'require fwlive.hostname as hostname';
/**
* Link-builder helpers for luci-app-fwlive.
@@ -14,7 +15,7 @@
* filterLink, addrFilterLink, ruleAdminPath, ruleAdminLink, ifaceLink
*
* No host element — all functions return DOM nodes or strings.
* May require fwlive.log for formatCell.
* May require fwlive.log for formatCell and fwlive.hostname for cache reads.
*/
function luciUrl(path) {
@@ -75,7 +76,14 @@ function filterLink(field, value, label, onFilterClick) {
function addrFilterLink(field, ip, showHostnames, hostnameCache, onFilterClick) {
if (!ip) return log.formatCell(ip);
const name = showHostnames && hostnameCache ? hostnameCache.get(ip) : null;
let name = null;
if (showHostnames && hostnameCache) {
/* Display reads refresh recency so visible names remain warm in the LRU. */
name =
hostname && typeof hostname.lruGet === 'function'
? hostname.lruGet(hostnameCache, ip)
: hostnameCache.get(ip);
}
const display = name || ip;
const title = name ? ip : _('Filter by %s').format(field);
@@ -87,6 +87,11 @@ function labelWithZoneCandidates(label, st) {
return children;
}
function appendLoggingNotice(host, state) {
if (!state.loggingNotice) return;
host.appendChild(E('span', { 'class': 'fwlive-logging-notice' }, [state.loggingNotice]));
}
function renderToolbar(host, state, callbacks) {
host.innerHTML = '';
const st = state.loggingStatus;
@@ -107,6 +112,7 @@ function renderToolbar(host, state, callbacks) {
)
);
host.appendChild(links.firewallZonesLink());
appendLoggingNotice(host, state);
return;
}
@@ -116,6 +122,7 @@ function renderToolbar(host, state, callbacks) {
_('WAN logging unavailable: missing kernel log modules')
])
);
appendLoggingNotice(host, state);
return;
}
@@ -144,6 +151,7 @@ function renderToolbar(host, state, callbacks) {
children
)
);
appendLoggingNotice(host, state);
return;
}
@@ -162,6 +170,7 @@ function renderToolbar(host, state, callbacks) {
[state.loggingBusy ? _('Enabling…') : _('Enable logging')]
)
);
appendLoggingNotice(host, state);
}
function buildConsentPanel(state, callbacks) {
@@ -77,6 +77,14 @@ function createScheduler(options) {
const elapsed = bucketMs === null ? 0 : Math.max(0, timestamp - bucketMs);
bucketMs = timestamp;
bucket = Math.min(capacity, bucket + (elapsed * capacity) / 1000);
/* A single batch can cost more than one second's budget. Once the
* bucket is full, spend that full interval on one paint so a sustained
* flood cannot freeze the table until the user changes a control. */
if (cost > bucket && bucket >= capacity) {
bucket = 0;
floodSuppressed = true;
return true;
}
floodSuppressed = cost > bucket;
if (floodSuppressed) return false;
bucket -= cost;
@@ -683,10 +683,15 @@ return view.extend({
const status = await callFwliveLoggingStatus();
if (this.viewDisposed) return;
this.loggingStatus = status;
this.loggingNotice = '';
this.weakDevice = !!(this.loggingStatus && this.loggingStatus.weak_device === true);
} catch (_e) {
if (this.viewDisposed) return;
this.loggingStatus = null;
/* Keep last toolbar state. Do not clobber a toggle success/failure notice. */
if (!this.loggingNotice)
this.loggingNotice = _(
'Could not refresh logging status; showing the last known state.'
);
}
this.updateBackendUi();
this.updateLoggingToolbarUi();
@@ -712,6 +717,10 @@ return view.extend({
this.loggingNotice = opts.successNotice(res);
if (opts.onSuccess) opts.onSuccess(res);
if (this.loggingStatus && typeof opts.wanLog === 'boolean')
this.loggingStatus = Object.assign({}, this.loggingStatus, {
wan_log: opts.wanLog
});
await this.loadLoggingStatus();
} catch (_e) {
this.loggingNotice = opts.catchNotice();
@@ -725,6 +734,7 @@ return view.extend({
async handleEnableLogging() {
return this.runLoggingToggle({
wanLog: true,
call: () => callFwliveEnableLogging(),
initialUi: () => {
this.updateEmptyStateUi();
@@ -752,6 +762,7 @@ return view.extend({
async handleDisableLogging() {
return this.runLoggingToggle({
wanLog: false,
call: () => callFwliveDisableLogging(),
initialUi: () => this.updateLoggingToolbarUi(),
failureNotice: (res) => {
@@ -1333,7 +1344,14 @@ return view.extend({
this.summaryRowsShown = false;
this.summaryData = null;
this.updateSummaryUi();
this.renderRows(true);
if (this.tablePaused) {
const empty = document.getElementById('fwlive-empty');
if (empty) {
const rows = this.filteredRows();
empty.style.display = rows.length ? 'none' : 'block';
}
this.updateStatus();
} else this.renderRows(true);
},
onSummaryRowsToggle() {
@@ -1469,8 +1487,10 @@ return view.extend({
this.resolveGeneration = (this.resolveGeneration || 0) + 1;
this.resolveInFlight = false;
if (this.showHostnames) this.resolveHostnamesForEntries(this.filteredRows());
/* Paint the existing cache immediately; resolving only fills misses. */
if (this.tablePaused) this.updateStatus();
else this.renderRows(true);
if (this.showHostnames) this.resolveHostnamesForEntries(this.filteredRows());
},
onFetchModeChange(ev) {
@@ -1646,7 +1666,7 @@ return view.extend({
for (let i = 0; i < ips.length && need.length < 32; i++) {
const ip = ips[i];
if (this.hostnameCache.has(ip)) continue;
if (hostname.lruGet(this.hostnameCache, ip) !== undefined) continue;
if (hostname.failIsHot(this.hostnameFailed, ip, now)) continue;
need.push(ip);
}