🔥 Sync 2026-09-28 00:13:15

This commit is contained in:
github-actions[bot] committed 2026-09-28 00:13:15 +08:00
1 parent c540ea3fd1
commit 1854a1c960
151 files changed
+25199 -9649

No files matched your search

@@ -11,5 +11,5 @@
* GENERATED — do not edit. Edit fwlive.css and run: node scripts/embed-fwlive-css.js
*/
return baseclass.extend({
styleText: "\n/* SPDX-License-Identifier: Apache-2.0 */\n.fwlive-map {\n\tmax-width: none;\n\twidth: 100%;\n\t--fwlive-pass-color: var(--success-color-high, var(--success-color, #46a546));\n\t--fwlive-deny-color: var(--error-color-high, var(--error-color, #ca3c3c));\n\t--fwlive-bg-medium: var(--background-color-medium, var(--white-color-low, #f9f9f9));\n\t--fwlive-paused-color: #b45309;\n}\n/* Wide page in Detail only — Simple keeps LuCI’s 940px column. */\n#maincontent.container:has(.fwlive-map[data-view=\"detailed\"]) {\n\tmax-width: none;\n\tpadding-left: 16px;\n\tpadding-right: 16px;\n}\n.fwlive-map[data-row-tint=\"accessible\"] {\n\t--fwlive-pass-color: #0d9488;\n\t--fwlive-deny-color: #c2410c;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row,\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tbackground: rgba(180, 83, 9, 0.08);\n\tbackground: color-mix(in srgb, var(--fwlive-paused-color) 8%, transparent);\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row {\n\tmargin: 0 -8px;\n\tpadding: 6px 8px 0;\n\tborder-radius: 3px 3px 0 0;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tmargin: 0 -8px 10px;\n\tpadding: 8px;\n\tborder-radius: 0 0 3px 3px;\n\tborder-bottom-color: transparent;\n}\n.fwlive-title-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tjustify-content: space-between;\n\tgap: 8px 16px;\n\tmargin: 0 0 4px;\n}\n.fwlive-title-row h2 {\n\tmargin: 0;\n\tflex: 0 1 auto;\n}\n.fwlive-title-status {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmin-width: 0;\n\tmargin-left: auto;\n}\n.fwlive-watch-strip {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: stretch;\n\tjustify-content: flex-start;\n\tgap: 0;\n\tmin-height: 40px;\n\tpadding: 8px 0 10px;\n\tborder-bottom: 1px solid var(--border-color-medium);\n\tmargin-bottom: 10px;\n}\n.fwlive-watch-group {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-height: 28px;\n\tpadding: 0 10px;\n\tborder-left: 1px solid var(--border-color-medium);\n\tflex-wrap: wrap;\n}\n.fwlive-watch-group:last-child {\n\tpadding-right: 0;\n}\n.fwlive-watch-group-label {\n\tmargin-right: 2px;\n\tcolor: var(--text-color-medium);\n\tfont-size: 0.72em;\n\tfont-weight: 700;\n\tletter-spacing: 0.08em;\n\ttext-transform: uppercase;\n\twhite-space: nowrap;\n}\n.fwlive-watch-seg {\n\tdisplay: inline-flex;\n\talign-items: stretch;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\toverflow: hidden;\n}\n.fwlive-watch-seg .fwlive-seg-btn {\n\tmargin: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tborder-right: 1px solid var(--border-color-medium);\n\tbackground: transparent;\n\tpadding: 4px 10px;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n}\n.fwlive-watch-seg .fwlive-seg-btn:last-child {\n\tborder-right: 0;\n}\n.fwlive-watch-seg .fwlive-seg-btn[aria-pressed=\"true\"] {\n\tbackground: rgba(26, 111, 156, 0.12);\n\tbackground: color-mix(in srgb, var(--primary-color-high) 12%, transparent);\n\tcolor: var(--primary-color-high);\n\tfont-weight: 600;\n}\n.fwlive-watch-label { font-weight: 600; }\n.fwlive-watch-paused .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-map.fwlive-watch-paused .fwlive-title-status .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--text-color-low, var(--border-color-high));\n\tdisplay: inline-block;\n\tflex-shrink: 0;\n}\n.fwlive-dot.fwlive-dot-on {\n\tbackground: var(--fwlive-pass-color);\n\tbox-shadow: 0 0 0 3px color-mix(in srgb, var(--fwlive-pass-color) 22%, transparent);\n}\n.fwlive-btn-ghost {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n}\n.fwlive-btn-quiet {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n\tcolor: var(--text-color-medium);\n\tfont-weight: 500;\n}\n.fwlive-log-merged {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-medium, var(--white-color-low, var(--background-color-high)));\n\tcolor: var(--text-color-high);\n\tfont-weight: 500;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n\tcursor: pointer;\n}\n.fwlive-log-merged:hover {\n\tbackground: var(--background-color-low, var(--background-color-medium));\n}\n.fwlive-log-merged:disabled {\n\topacity: 0.65;\n\tcursor: default;\n}\n.fwlive-log-on-dot {\n\twidth: 6px;\n\theight: 6px;\n\tborder-radius: 50%;\n\tbackground: var(--fwlive-pass-color);\n\tflex-shrink: 0;\n}\n.fwlive-log-rate {\n\tcolor: var(--text-color-medium);\n\tfont-weight: 400;\n}\n.fwlive-display-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px 14px;\n\tmargin: 0 0 12px;\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-high);\n}\n.fwlive-dLine truncated
styleText: "\n/* SPDX-License-Identifier: Apache-2.0 */\n.fwlive-map {\n\tmax-width: none;\n\twidth: 100%;\n\t--fwlive-pass-color: var(--success-color-high, var(--success-color, #46a546));\n\t--fwlive-deny-color: var(--error-color-high, var(--error-color, #ca3c3c));\n\t--fwlive-bg-medium: var(--background-color-medium, var(--white-color-low, #f9f9f9));\n\t--fwlive-paused-color: #b45309;\n}\n/* Wide page in Detail only — Simple keeps LuCI’s 940px column. */\n#maincontent.container:has(.fwlive-map[data-view=\"detailed\"]) {\n\tmax-width: none;\n\tpadding-left: 16px;\n\tpadding-right: 16px;\n}\n.fwlive-map[data-row-tint=\"accessible\"] {\n\t--fwlive-pass-color: #0d9488;\n\t--fwlive-deny-color: #c2410c;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row,\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tbackground: rgba(180, 83, 9, 0.08);\n\tbackground: color-mix(in srgb, var(--fwlive-paused-color) 8%, transparent);\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row {\n\tmargin: 0 -8px;\n\tpadding: 6px 8px 0;\n\tborder-radius: 3px 3px 0 0;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tmargin: 0 -8px 10px;\n\tpadding: 8px;\n\tborder-radius: 0 0 3px 3px;\n\tborder-bottom-color: transparent;\n}\n.fwlive-title-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tjustify-content: space-between;\n\tgap: 8px 16px;\n\tmargin: 0 0 4px;\n}\n.fwlive-title-row h2 {\n\tmargin: 0;\n\tflex: 0 1 auto;\n}\n.fwlive-title-status {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmin-width: 0;\n\tmargin-left: auto;\n}\n.fwlive-watch-strip {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: stretch;\n\tjustify-content: flex-start;\n\tgap: 0;\n\tmin-height: 40px;\n\tpadding: 8px 0 10px;\n\tborder-bottom: 1px solid var(--border-color-medium);\n\tmargin-bottom: 10px;\n}\n.fwlive-watch-group {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-height: 28px;\n\tpadding: 0 10px;\n\tborder-left: 1px solid var(--border-color-medium);\n\tflex-wrap: wrap;\n}\n.fwlive-watch-group:last-child {\n\tpadding-right: 0;\n}\n.fwlive-watch-group-label {\n\tmargin-right: 2px;\n\tcolor: var(--text-color-medium);\n\tfont-size: 0.72em;\n\tfont-weight: 700;\n\tletter-spacing: 0.08em;\n\ttext-transform: uppercase;\n\twhite-space: nowrap;\n}\n.fwlive-watch-seg {\n\tdisplay: inline-flex;\n\talign-items: stretch;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\toverflow: hidden;\n}\n.fwlive-watch-seg .fwlive-seg-btn {\n\tmargin: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tborder-right: 1px solid var(--border-color-medium);\n\tbackground: transparent;\n\tpadding: 4px 10px;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n}\n.fwlive-watch-seg .fwlive-seg-btn:last-child {\n\tborder-right: 0;\n}\n.fwlive-watch-seg .fwlive-seg-btn[aria-pressed=\"true\"] {\n\tbackground: rgba(26, 111, 156, 0.12);\n\tbackground: color-mix(in srgb, var(--primary-color-high) 12%, transparent);\n\tcolor: var(--primary-color-high);\n\tfont-weight: 600;\n}\n.fwlive-watch-label { font-weight: 600; }\n.fwlive-watch-paused .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-map.fwlive-watch-paused .fwlive-title-status .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--text-color-low, var(--border-color-high));\n\tdisplay: inline-block;\n\tflex-shrink: 0;\n}\n.fwlive-dot.fwlive-dot-on {\n\tbackground: var(--fwlive-pass-color);\n\tbox-shadow: 0 0 0 3px color-mix(in srgb, var(--fwlive-pass-color) 22%, transparent);\n}\n.fwlive-btn-ghost {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n}\n.fwlive-btn-quiet {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n\tcolor: var(--text-color-medium);\n\tfont-weight: 500;\n}\n.fwlive-log-merged {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-medium, var(--white-color-low, var(--background-color-high)));\n\tcolor: var(--text-color-high);\n\tfont-weight: 500;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n\tcursor: pointer;\n}\n.fwlive-log-merged:hover {\n\tbackground: var(--background-color-low, var(--background-color-medium));\n}\n.fwlive-log-merged:disabled {\n\topacity: 0.65;\n\tcursor: default;\n}\n.fwlive-log-on-dot {\n\twidth: 6px;\n\theight: 6px;\n\tborder-radius: 50%;\n\tbackground: var(--fwlive-pass-color);\n\tflex-shrink: 0;\n}\n.fwlive-log-rate {\n\tcolor: var(--text-color-medium);\n\tfont-weight: 400;\n}\n.fwlive-display-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px 14px;\n\tmargin: 0 0 12px;\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-high);\n}\n.fwlive-dLine truncated
});
@@ -533,6 +533,9 @@
.fwlive-logging-notice {
color: var(--fwlive-pass-color);
}
.fwlive-logging-notice-fail {
color: var(--fwlive-deny-color);
}
.fwlive-filter-link {
color: inherit;
text-decoration: underline;
@@ -9,7 +9,7 @@
*
* renderToolbar(host, state, callbacks) → void
* host - #fwlive-logging-bar strip slot (cleared and rebuilt; element kept)
* state - { loggingStatus, loggingBusy, loggingNotice }
* state - { loggingStatus, loggingBusy, loggingNotice, loggingNoticeFail }
* callbacks - { onEnable(), onDisable() }
*
* G Hybrid chrome: when WAN logging is on, one merged control carries status +
@@ -88,9 +88,15 @@ function labelWithZoneCandidates(label, st) {
return children;
}
function loggingNoticeClass(state) {
return state.loggingNoticeFail
? 'fwlive-logging-notice fwlive-logging-notice-fail'
: 'fwlive-logging-notice';
}
function appendLoggingNotice(host, state) {
if (!state.loggingNotice) return;
host.appendChild(E('span', { 'class': 'fwlive-logging-notice' }, [state.loggingNotice]));
host.appendChild(E('span', { 'class': loggingNoticeClass(state) }, [state.loggingNotice]));
}
function appendBlockerStatus(host, blocker, st) {
@@ -254,7 +260,7 @@ function buildEmptyStateNodes(state, callbacks) {
if (state.loggingNotice) {
nodes.push(
E('p', { 'class': 'fwlive-logging-notice' }, [
E('p', { 'class': loggingNoticeClass(state) }, [
state.loggingNotice,
' ',
links.firewallZonesLink()
@@ -287,14 +293,12 @@ function buildEmptyStateNodes(state, callbacks) {
nodes.push(
E('p', {}, [
_(
'Kernel netfilter log modules are missing. Install kmod-nf-log-ipv4 and kmod-nf-log-ipv6 (or kmod-nf-log / kmod-nf-log6), then reload the firewall.'
'Kernel netfilter log modules are missing. Install kmod-nf-log and kmod-nf-log6, then reload the firewall.'
)
])
);
nodes.push(
E('p', {}, [
E('code', {}, ['opkg update && opkg install kmod-nf-log-ipv4 kmod-nf-log-ipv6'])
])
E('p', {}, [E('code', {}, ['opkg update && opkg install kmod-nf-log kmod-nf-log6'])])
);
return nodes;
}
@@ -177,6 +177,7 @@ return view.extend({
loggingStatusReadGeneration: 0,
loggingBusy: false,
loggingNotice: '',
loggingNoticeFail: false,
_loggingNoticeFromToggle: false,
/* Session-only dismiss of first-run consent (Not now without checkbox). */
consentDismissedSession: false,
@@ -704,10 +705,13 @@ return view.extend({
/* Bounds / mktemp failures are reply.error — same idea as poll. */
this.lastRulesError = (res && res.error) || null;
if (this.lastRulesError) console.warn('fwlive rules map error:', this.lastRulesError);
this.refreshBufferedRuleLabels();
} catch (_e) {
if (!this.isCurrentPollEpoch(epoch)) return;
this.rulesMap = {};
this.firewallBackend = 'nft';
if (!(this.rulesMap && Object.keys(this.rulesMap).length)) {
this.rulesMap = {};
this.firewallBackend = 'nft';
}
this.lastRulesError = 'rules_unavailable';
}
this.updateBackendUi();
@@ -762,7 +766,10 @@ return view.extend({
)
return;
this.loggingStatus = status;
if (!this._loggingNoticeFromToggle) this.loggingNotice = '';
if (!this._loggingNoticeFromToggle) {
this.loggingNotice = '';
this.loggingNoticeFail = false;
}
this.weakDevice = !!(this.loggingStatus && this.loggingStatus.weak_device === true);
} catch (_e) {
if (
@@ -771,10 +778,12 @@ return view.extend({
)
return;
/* Keep last-known toolbar; unknown until the first successful fetch. */
if (!this.loggingNotice)
if (!this.loggingNotice) {
this.loggingNotice = this.loggingStatus
? _('Could not refresh logging status; showing the last known state.')
: _('Could not load logging status.');
this.loggingNoticeFail = true;
}
}
this.updateBackendUi();
this.updateLoggingToolbarUi();
@@ -791,6 +800,7 @@ return view.extend({
this.loggingStatusReadGeneration++;
this.loggingBusy = true;
this.loggingNotice = '';
this.loggingNoticeFail = false;
this._loggingNoticeFromToggle = false;
opts.initialUi();
@@ -798,12 +808,14 @@ return view.extend({
const res = await opts.call();
if (!res || !res.ok) {
this.loggingNotice = opts.failureNotice(res);
this.loggingNoticeFail = true;
this._loggingNoticeFromToggle = true;
await this.loadLoggingStatus();
return;
}
this.loggingNotice = opts.successNotice(res);
this.loggingNoticeFail = false;
this._loggingNoticeFromToggle = !!this.loggingNotice;
if (opts.onSuccess) opts.onSuccess(res);
if (this.loggingStatus && typeof opts.wanLog === 'boolean')
@@ -813,6 +825,7 @@ return view.extend({
await this.loadLoggingStatus();
} catch (_e) {
this.loggingNotice = opts.catchNotice();
this.loggingNoticeFail = true;
this._loggingNoticeFromToggle = true;
await this.loadLoggingStatus();
} finally {
@@ -847,6 +860,16 @@ return view.extend({
return _('Could not snapshot the current logging state.');
if (res && res.error === 'firewall_reload_failed')
return _('The firewall did not reload; saved and live logging may differ.');
if (res && res.error === 'uci_set_failed')
return _('Could not write the WAN zone log option.');
if (res && res.error === 'uci_delete_failed')
return _('Could not clear the WAN zone log option.');
if (res && res.error === 'uci_commit_failed')
return _('Could not save the firewall configuration.');
if (res && res.error === 'firewall_commit_raced')
return _(
'Another change overwrote WAN logging after it was saved; check the current state.'
);
return _('Could not enable logging.');
},
successNotice: (res) =>
@@ -876,6 +899,16 @@ return view.extend({
return _('Could not acquire the logging lock.');
if (res && res.error === 'firewall_reload_failed')
return _('The firewall did not reload; saved and live logging may differ.');
if (res && res.error === 'uci_set_failed')
return _('Could not write the WAN zone log option.');
if (res && res.error === 'uci_delete_failed')
return _('Could not clear the WAN zone log option.');
if (res && res.error === 'uci_commit_failed')
return _('Could not save the firewall configuration.');
if (res && res.error === 'firewall_commit_raced')
return _(
'Another change overwrote WAN logging after it was saved; check the current state.'
);
return _('Could not disable logging.');
},
successNotice: (res) => (res.changed ? _('WAN drop/reject logging is off.') : ''),
@@ -905,6 +938,7 @@ return view.extend({
loggingStatus: this.loggingStatus,
loggingBusy: this.loggingBusy,
loggingNotice: this.loggingNotice,
loggingNoticeFail: !!this.loggingNoticeFail,
showConsent: this.shouldShowLoggingConsent()
};
},
@@ -924,6 +958,7 @@ return view.extend({
candidates,
this.loggingBusy ? '1' : '0',
this.loggingNotice || '',
this.loggingNoticeFail ? 'f1' : 'f0',
this.shouldShowLoggingConsent() ? 'c1' : 'c0'
].join('|');
},
@@ -1145,6 +1180,9 @@ return view.extend({
/* Recovery RPCs can outlive the poll epoch; don't repaint stale views. */
if (epoch !== this.currentPollEpoch() || this.viewDisposed) return;
this.refreshBufferedRuleLabels();
} else if (this.lastRulesError === 'rules_unavailable') {
await this.loadRulesMap(epoch);
if (epoch !== this.currentPollEpoch() || this.viewDisposed) return;
}
},
@@ -1505,12 +1543,11 @@ return view.extend({
},
scheduleResolvePaint() {
this.resolvePaintPending = true;
if (this.tablePaused) {
this.resolvePaintPending = true;
this.updateStatus();
return;
}
this.resolvePaintPending = false;
this.scheduleRenderRows(true);
},