🎄 Sync 2026-09-26 02:25:56

This commit is contained in:
github-actions[bot] committed 2026-09-26 02:25:56 +08:00
1 parent 99196a85d3
commit 4136a9b488
73 files changed
+2401 -4124

No files matched your search

@@ -17,26 +17,9 @@
*/
function chipFieldLabel(spec) {
switch (spec.key) {
case 'q':
return _('Search');
case 'action':
return _('Action');
case 'interface':
return _('Interface');
case 'proto':
return _('Proto');
case 'src':
return _('Source');
case 'dst':
return _('Destination');
case 'sport':
return _('Source port');
case 'dport':
return _('Destination port');
default:
return spec.label || spec.key;
}
const mapped = log.filterFieldLabel(spec.key);
if (mapped !== spec.key) return mapped;
return spec.label || spec.key;
}
function chipValueNodes(spec, val) {
@@ -58,9 +41,7 @@ function chipValueNodes(spec, val) {
if (key === 'q' || key === 'src' || key === 'dst')
return [
label + ': ',
E('strong', { 'class': 'fwlive-chip-not' }, [_('not')]),
' ',
_('contains'),
E('strong', { 'class': 'fwlive-chip-not' }, [_('does not contain')]),
' ',
valueNode
];
@@ -60,7 +60,7 @@ function filterLink(field, value, label, onFilterClick) {
{
'href': '#',
'class': 'fwlive-filter-link',
'title': _('Filter by %s').format(field),
'title': _('Filter by %s').format(log.filterFieldLabel(field)),
'click': function (ev) {
onFilterClick(field, value, ev);
}
@@ -88,7 +88,7 @@ function addrFilterLink(field, ip, showHostnames, hostnameCache, onFilterClick)
: hostnameCache.get(ip);
}
const display = name || ip;
const title = name ? ip : _('Filter by %s').format(field);
const title = name ? ip : _('Filter by %s').format(log.filterFieldLabel(field));
return E(
'a',
@@ -153,7 +153,7 @@ function ifaceLink(value, onFilterClick) {
{
'href': '#',
'class': 'fwlive-filter-link fwlive-iface-badge',
'title': _('Filter by interface'),
'title': _('Filter by %s').format(log.filterFieldLabel('interface')),
'click': function (ev) {
onFilterClick('interface', value, ev);
}
@@ -26,6 +26,8 @@ const CLASSIFY_SPEC = {
'PREC',
'DF'
],
/* Classify trim (not JS String#trim): ASCII space/tab/LF/CR + NBSP (U+00A0). */
trimWhitespace: [' ', '\t', '\n', '\r', '\u00a0'],
nonFirewallPrefixes: [
'dnsmasq',
'procd',
@@ -64,6 +66,15 @@ function wordPattern(words) {
return new RegExp('(^|[^A-Za-z0-9_])(' + alt + ')([^A-Za-z0-9_]|$)', 'i');
}
function classifyTrim(s) {
const ws = CLASSIFY_SPEC.trimWhitespace;
let start = 0;
let end = s.length;
while (start < end && ws.indexOf(s.charAt(start)) >= 0) start++;
while (end > start && ws.indexOf(s.charAt(end - 1)) >= 0) end--;
return s.slice(start, end);
}
/* ---- spec-derived classification regexes (mirror core/fwlive-log.js) ---- */
const DENY_CLASS_WORDS = CLASSIFY_SPEC.actionWords.slice(3);
const NON_FIREWALL_PREFIX = new RegExp(
@@ -335,10 +346,25 @@ return baseclass.extend({
return m;
},
filterFieldLabel: function (field) {
const labels = {
'q': _('Search'),
'action': _('Action'),
'interface': _('Interface'),
'proto': _('Proto'),
'src': _('Source'),
'dst': _('Destination'),
'sport': _('Source port'),
'dport': _('Destination port')
};
return labels[field] || field;
},
/* @fwlive-codegen:luci-preserve-end */
isFirewallEvent: function (entry) {
const msg = this.normalizeNetfilterMessage((entry && entry.msg) || '').trim();
const msg = classifyTrim(this.normalizeNetfilterMessage((entry && entry.msg) || ''));
if (!msg) return false;
if (this.NON_FIREWALL_PREFIX.test(msg)) return false;
@@ -17,7 +17,7 @@
*
* renderManualTestNodes(host, state, callbacks) → void
* host - <ul> element inside #fwlive-help (cleared and rebuilt)
* state - unused; nft-only instruction
* state - unused; instruction is nft-only (rpcd never emits iptables)
*
* Empty-state helpers:
* buildEmptyStateNodes(state, callbacks) → Node[]
@@ -200,17 +200,17 @@ function buildConsentPanel(state, callbacks) {
E('li', {}, [
E('strong', {}, [_('Changes:')]),
' ',
_('sets log on the WAN firewall zone and reloads the firewall.')
_('Turns on logging for the WAN firewall zone and reloads the firewall.')
]),
E('li', {}, [
E('strong', {}, [_('Does not change:')]),
' ',
_('allow/deny rules, LAN logging, or anything else.')
_('Allow/deny rules, LAN logging, or anything else.')
]),
E('li', {}, [
E('strong', {}, [_('Undo:')]),
' ',
_('turn it back off with the WAN logging on control on the watch strip.')
_('Turn it back off with the WAN logging control on the watch strip.')
])
]),
E('p', { 'class': 'fwlive-consent-check' }, [
@@ -272,7 +272,8 @@ function buildEmptyStateNodes(state, callbacks) {
);
nodes.push(
E('p', {}, [
_('No WAN firewall zone found in /etc/config/firewall. Configure zones under '),
_('No WAN firewall zone found in /etc/config/firewall. Configure zones under:'),
' ',
links.firewallZonesLink()
])
);
@@ -368,19 +369,21 @@ function renderEmptyState(host, state, callbacks) {
}
/**
* renderManualTestNodes — fills a <li> host element with the nft manual test
* renderManualTestNodes — fills a <li> host element with the firewall manual test
* instruction. Call from addFooter() after render() has inserted
* the placeholder <li id="fwlive-manual-test">.
*/
function renderManualTestNodes(host, _state, _callbacks) {
host.innerHTML = '';
host.appendChild(document.createTextNode(_('Manual test (System → Terminal): ')));
host.appendChild(document.createTextNode(_('Manual test (System → Terminal):')));
host.appendChild(document.createTextNode(' '));
host.appendChild(
E('code', {}, [
'nft insert rule inet fw4 input ip protocol icmp icmp type echo-request log prefix "fwlive-ping " accept'
])
);
host.appendChild(document.createTextNode(_(' then ping the router.')));
host.appendChild(document.createTextNode(' '));
host.appendChild(document.createTextNode(_('Then ping the router.')));
}
return baseclass.extend({
@@ -52,6 +52,28 @@ function columnLabel(col) {
return labels[col] || col;
}
function dirLabel(dir) {
const labels = {
'in': _('Inbound'),
'out': _('Outbound'),
'forward': _('Forwarded'),
'unknown': _('unknown')
};
return labels[dir] || log.formatCell(dir);
}
function actionLabel(action) {
const labels = {
'pass': _('pass'),
'block': _('block'),
'drop': _('drop'),
'reject': _('reject')
};
return labels[action] || log.formatActionLabel(action);
}
function columnCellClass(col) {
switch (col) {
case 'time':
@@ -124,12 +146,7 @@ function buildColumnCell(col, row, state, callbacks) {
const msgDisplay = log.formatMessageDisplay(row.message, state.messageLayout);
const actionCell =
row.action && row.action !== 'unknown'
? links.filterLink(
'action',
row.action,
log.formatActionLabel(row.action),
onFilterClick
)
? links.filterLink('action', row.action, actionLabel(row.action), onFilterClick)
: log.formatActionLabel(row.action);
switch (col) {
@@ -162,7 +179,7 @@ function buildColumnCell(col, row, state, callbacks) {
)
]);
case 'dir':
return E('td', { 'class': columnCellClass(col) }, [log.formatCell(row.direction)]);
return E('td', { 'class': columnCellClass(col) }, [dirLabel(row.direction)]);
case 'proto':
return E('td', { 'class': columnCellClass(col) }, [
links.filterLink('proto', row.proto, null, onFilterClick)
@@ -2488,11 +2488,11 @@ return view.extend({
}),
E('select', { 'id': 'fwlive-action', 'class': 'cbi-input-select' }, [
E('option', { 'value': '' }, [_('Any action')]),
E('option', { 'value': 'pass' }, ['pass']),
E('option', { 'value': 'block' }, ['block']),
E('option', { 'value': 'drop' }, ['drop']),
E('option', { 'value': 'reject' }, ['reject']),
E('option', { 'value': 'unknown' }, ['unknown']),
E('option', { 'value': 'pass' }, [_('pass')]),
E('option', { 'value': 'block' }, [_('block')]),
E('option', { 'value': 'drop' }, [_('drop')]),
E('option', { 'value': 'reject' }, [_('reject')]),
E('option', { 'value': 'unknown' }, [_('unknown')]),
E('option', { 'value': '!pass' }, [_('not pass')]),
E('option', { 'value': '!drop' }, [_('not drop')]),
E('option', { 'value': '!block' }, [_('not block')]),
@@ -2607,7 +2607,8 @@ return view.extend({
this.updateTintWarnUi();
this.renderRows(true);
const testLi = document.getElementById('fwlive-manual-test');
if (testLi) logging.renderManualTestNodes(testLi, {}, {});
if (testLi)
logging.renderManualTestNodes(testLi, { firewallBackend: this.firewallBackend }, {});
if (this.showHostnames) this.resolveHostnamesForEntries(this.filteredRows());
}
});