mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-07 20:54:14 +08:00
🍉 Sync 2026-09-25 18:00:12
This commit is contained in:
1 parent
7c17dda4d0
commit
2ee65a261e
135 files changed
+5726
-7799
No files matched your search
@@ -16,9 +16,32 @@
|
||||
* Modules must not mutate state. host is cleared then rebuilt (idempotent replace).
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function chipValueNodes(spec, val) {
|
||||
const key = spec.key;
|
||||
const label = spec.label || key;
|
||||
const label = chipFieldLabel(spec);
|
||||
const p = log.parseFilterValue(val);
|
||||
if (!p.value) return [''];
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
* luciUrl, firewallZonesPath, firewallZonesUrl, firewallZonesLink
|
||||
*
|
||||
* Filter-aware helpers (require an onFilterClick callback):
|
||||
* filterLink, addrFilterLink, ruleAdminPath, ruleAdminLink, ifaceLink
|
||||
* filterLink, addrFilterLink, ruleAdminLink, ifaceLink
|
||||
*
|
||||
* Constant path helper:
|
||||
* ruleAdminPath
|
||||
*
|
||||
* No host element — all functions return DOM nodes or strings.
|
||||
* May require fwlive.log for formatCell and fwlive.hostname for cache reads.
|
||||
|
||||
@@ -90,7 +90,7 @@ return baseclass.extend({
|
||||
CLASSIFY_SPEC: CLASSIFY_SPEC,
|
||||
|
||||
TCP_FLAG_TAIL:
|
||||
/\b((?:SYN|ACK|FIN|RST|PSH|URG)(?:\s+(?:SYN|ACK|FIN|RST|PSH|URG))*)(?:\s+[A-Z][A-Z0-9_]*=[^\s]+)*\s*$/i,
|
||||
/\b((?:SYN|ACK|FIN|RST|PSH|URG|ECE|CWR)(?:\s+(?:SYN|ACK|FIN|RST|PSH|URG|ECE|CWR))*)(?:\s+[A-Z][A-Z0-9_]*=[^\s]+)*\s*$/i,
|
||||
NETFILTER_KV_GLUE: NETFILTER_KV_GLUE,
|
||||
|
||||
kvHas: function (msg, key) {
|
||||
@@ -105,7 +105,8 @@ return baseclass.extend({
|
||||
MAX_DATE_SECONDS: 8640000000000,
|
||||
|
||||
normalizeNetfilterMessage: function (message) {
|
||||
return (message || '').replace(this.NETFILTER_KV_GLUE, '$1 ');
|
||||
if (typeof message !== 'string') message = '';
|
||||
return message.replace(this.NETFILTER_KV_GLUE, '$1 ');
|
||||
},
|
||||
|
||||
parseKeyValueLog: function (message) {
|
||||
|
||||
@@ -69,6 +69,7 @@ function blockerCode(state) {
|
||||
blockers.indexOf('nf_log_ipv6_missing') >= 0
|
||||
)
|
||||
return 'nf_log_missing';
|
||||
if (blockers.length > 0) return 'unknown';
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -92,6 +93,63 @@ function appendLoggingNotice(host, state) {
|
||||
host.appendChild(E('span', { 'class': 'fwlive-logging-notice' }, [state.loggingNotice]));
|
||||
}
|
||||
|
||||
function appendBlockerStatus(host, blocker, st) {
|
||||
if (blocker === 'no_wan_zone') {
|
||||
host.appendChild(
|
||||
E(
|
||||
'span',
|
||||
{ 'class': 'fwlive-logging-status' },
|
||||
labelWithZoneCandidates(_('WAN logging unavailable: no WAN zone'), st)
|
||||
)
|
||||
);
|
||||
host.appendChild(links.firewallZonesLink());
|
||||
return;
|
||||
}
|
||||
|
||||
if (blocker === 'nf_log_missing') {
|
||||
host.appendChild(
|
||||
E('span', { 'class': 'fwlive-logging-status' }, [
|
||||
_('WAN logging unavailable: missing kernel log modules')
|
||||
])
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (blocker === 'unknown') {
|
||||
host.appendChild(
|
||||
E('span', { 'class': 'fwlive-logging-status' }, [_('WAN logging unavailable')])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function appendWanLogDisableControl(host, state, callbacks) {
|
||||
const st = state.loggingStatus;
|
||||
const limit = st.wan_log_limit || _('default 10/minute');
|
||||
const busy = !!state.loggingBusy;
|
||||
const children = busy
|
||||
? [_('Disabling…')]
|
||||
: [
|
||||
E('span', { 'class': 'fwlive-log-on-dot', 'aria-hidden': 'true' }, ['']),
|
||||
E('span', { 'class': 'fwlive-log-label' }, [_('WAN logging on')]),
|
||||
E('span', { 'class': 'fwlive-log-rate' }, [_('· %s').format(limit)])
|
||||
];
|
||||
host.appendChild(
|
||||
E(
|
||||
'button',
|
||||
{
|
||||
'class': 'cbi-button fwlive-log-merged',
|
||||
'type': 'button',
|
||||
'title': _('WAN logging on (%s). Click to disable.').format(limit),
|
||||
'disabled': busy ? '' : null,
|
||||
'click': function () {
|
||||
callbacks.onDisable();
|
||||
}
|
||||
},
|
||||
children
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function renderToolbar(host, state, callbacks) {
|
||||
host.innerHTML = '';
|
||||
const st = state.loggingStatus;
|
||||
@@ -103,54 +161,15 @@ function renderToolbar(host, state, callbacks) {
|
||||
host.style.display = 'contents';
|
||||
const blocker = blockerCode(state);
|
||||
|
||||
if (blocker === 'no_wan_zone') {
|
||||
host.appendChild(
|
||||
E(
|
||||
'span',
|
||||
{ 'class': 'fwlive-logging-status' },
|
||||
labelWithZoneCandidates(_('WAN logging unavailable: no WAN zone'), st)
|
||||
)
|
||||
);
|
||||
host.appendChild(links.firewallZonesLink());
|
||||
appendLoggingNotice(host, state);
|
||||
return;
|
||||
}
|
||||
if (blocker) appendBlockerStatus(host, blocker, st);
|
||||
|
||||
if (blocker === 'nf_log_missing') {
|
||||
host.appendChild(
|
||||
E('span', { 'class': 'fwlive-logging-status' }, [
|
||||
_('WAN logging unavailable: missing kernel log modules')
|
||||
])
|
||||
);
|
||||
appendLoggingNotice(host, state);
|
||||
return;
|
||||
}
|
||||
|
||||
const limit = st.wan_log_limit || _('default 10/minute');
|
||||
if (st.wan_log) {
|
||||
const busy = !!state.loggingBusy;
|
||||
const children = busy
|
||||
? [_('Disabling…')]
|
||||
: [
|
||||
E('span', { 'class': 'fwlive-log-on-dot', 'aria-hidden': 'true' }, ['']),
|
||||
E('span', { 'class': 'fwlive-log-label' }, [_('WAN logging on')]),
|
||||
E('span', { 'class': 'fwlive-log-rate' }, [_('· %s').format(limit)])
|
||||
];
|
||||
host.appendChild(
|
||||
E(
|
||||
'button',
|
||||
{
|
||||
'class': 'cbi-button fwlive-log-merged',
|
||||
'type': 'button',
|
||||
'title': _('WAN logging on (%s). Click to disable.').format(limit),
|
||||
'disabled': busy ? '' : null,
|
||||
'click': function () {
|
||||
callbacks.onDisable();
|
||||
}
|
||||
},
|
||||
children
|
||||
)
|
||||
);
|
||||
appendWanLogDisableControl(host, state, callbacks);
|
||||
appendLoggingNotice(host, state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (blocker) {
|
||||
appendLoggingNotice(host, state);
|
||||
return;
|
||||
}
|
||||
@@ -277,6 +296,16 @@ function buildEmptyStateNodes(state, callbacks) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
if (blocker === 'unknown') {
|
||||
nodes.push(E('p', { 'class': 'fwlive-empty-title' }, [_('WAN logging unavailable')]));
|
||||
nodes.push(
|
||||
E('p', {}, [
|
||||
_('This router reported a logging blocker that Live View does not recognize yet.')
|
||||
])
|
||||
);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
if (st && st.wan_log) {
|
||||
nodes.push(E('p', { 'class': 'fwlive-empty-title' }, [_('Waiting for firewall events')]));
|
||||
nodes.push(
|
||||
|
||||
@@ -312,7 +312,8 @@ function buildExpansionRow(row, state, columns) {
|
||||
E('td', { 'colspan': String(columns.length) }, [
|
||||
E('div', { 'class': 'fwlive-msg-expand-label' }, [_('Message')]),
|
||||
E('pre', { 'class': 'fwlive-msg-expand-body' }, [
|
||||
log.formatMessageDisplay(row.message, 'wrap') || '—'
|
||||
/* oneline is uncapped; wrap ellipsizes at 240 */
|
||||
log.formatMessageDisplay(row.message, 'oneline') || '—'
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
@@ -156,6 +156,8 @@ return view.extend({
|
||||
hostnameFailed: null,
|
||||
resolveInFlight: false,
|
||||
resolveGeneration: 0,
|
||||
/* Coalesce hostname-cache paints deferred while tablePaused. */
|
||||
resolvePaintPending: false,
|
||||
lastPollError: false,
|
||||
lastRulesError: null,
|
||||
followLive: true,
|
||||
@@ -166,6 +168,7 @@ return view.extend({
|
||||
loggingStatus: null,
|
||||
loggingBusy: false,
|
||||
loggingNotice: '',
|
||||
_loggingNoticeFromToggle: false,
|
||||
/* Session-only dismiss of first-run consent (Not now without checkbox). */
|
||||
consentDismissedSession: false,
|
||||
_loggingToolbarSig: '',
|
||||
@@ -212,7 +215,7 @@ return view.extend({
|
||||
parts.push('maxraw=%s'.format(encodeURIComponent(this.manualFetchLines)));
|
||||
}
|
||||
if (this.viewMode === 'detailed') parts.push('view=detailed');
|
||||
location.hash = parts.join('&');
|
||||
history.replaceState(history.state, '', '#' + parts.join('&'));
|
||||
},
|
||||
|
||||
hashEntries() {
|
||||
@@ -739,7 +742,7 @@ return view.extend({
|
||||
const status = await callFwliveLoggingStatus();
|
||||
if (this.viewDisposed) return;
|
||||
this.loggingStatus = status;
|
||||
this.loggingNotice = '';
|
||||
if (!this._loggingNoticeFromToggle) this.loggingNotice = '';
|
||||
this.weakDevice = !!(this.loggingStatus && this.loggingStatus.weak_device === true);
|
||||
} catch (_e) {
|
||||
if (this.viewDisposed) return;
|
||||
@@ -761,17 +764,20 @@ return view.extend({
|
||||
|
||||
this.loggingBusy = true;
|
||||
this.loggingNotice = '';
|
||||
this._loggingNoticeFromToggle = false;
|
||||
opts.initialUi();
|
||||
|
||||
try {
|
||||
const res = await opts.call();
|
||||
if (!res || !res.ok) {
|
||||
this.loggingNotice = opts.failureNotice(res);
|
||||
this._loggingNoticeFromToggle = true;
|
||||
await this.loadLoggingStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
this.loggingNotice = opts.successNotice(res);
|
||||
this._loggingNoticeFromToggle = !!this.loggingNotice;
|
||||
if (opts.onSuccess) opts.onSuccess(res);
|
||||
if (this.loggingStatus && typeof opts.wanLog === 'boolean')
|
||||
this.loggingStatus = Object.assign({}, this.loggingStatus, {
|
||||
@@ -780,8 +786,11 @@ return view.extend({
|
||||
await this.loadLoggingStatus();
|
||||
} catch (_e) {
|
||||
this.loggingNotice = opts.catchNotice();
|
||||
this._loggingNoticeFromToggle = true;
|
||||
await this.loadLoggingStatus();
|
||||
} finally {
|
||||
/* Survive only the refresh owned by this toggle. */
|
||||
this._loggingNoticeFromToggle = false;
|
||||
this.loggingBusy = false;
|
||||
this.updateEmptyStateUi();
|
||||
this.updateLoggingToolbarUi();
|
||||
@@ -803,6 +812,14 @@ return view.extend({
|
||||
return _(
|
||||
'Another change is staged for the firewall; apply or revert it first.'
|
||||
);
|
||||
if (res && res.error === 'no_wan_zone')
|
||||
return _('No WAN zone found; cannot toggle logging without one.');
|
||||
if (res && res.error === 'lock_failed')
|
||||
return _('Could not acquire the logging lock.');
|
||||
if (res && res.error === 'baseline_snapshot_failed')
|
||||
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.');
|
||||
return _('Could not enable logging.');
|
||||
},
|
||||
successNotice: (res) =>
|
||||
@@ -826,6 +843,12 @@ return view.extend({
|
||||
return _(
|
||||
'Another change is staged for the firewall; apply or revert it first.'
|
||||
);
|
||||
if (res && res.error === 'no_wan_zone')
|
||||
return _('No WAN zone found; cannot toggle logging without one.');
|
||||
if (res && res.error === 'lock_failed')
|
||||
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.');
|
||||
return _('Could not disable logging.');
|
||||
},
|
||||
successNotice: (res) => (res.changed ? _('WAN drop/reject logging is off.') : ''),
|
||||
@@ -1363,23 +1386,28 @@ return view.extend({
|
||||
this.updateSummaryUi();
|
||||
},
|
||||
|
||||
updateSummaryUi() {
|
||||
const card = document.getElementById('fwlive-summary');
|
||||
syncEmptyScrollVisibility(rowCount) {
|
||||
const hideTable = this.summaryMode && !this.summaryRowsShown;
|
||||
const scroll = document.getElementById('fwlive-scroll');
|
||||
const empty = document.getElementById('fwlive-empty');
|
||||
if (scroll) {
|
||||
if (!scroll.style) scroll.style = { display: '' };
|
||||
scroll.style.display = hideTable ? 'none' : '';
|
||||
}
|
||||
if (empty) {
|
||||
if (!empty.style) empty.style = { display: '' };
|
||||
empty.style.display = hideTable ? 'none' : rowCount ? 'none' : 'block';
|
||||
}
|
||||
},
|
||||
|
||||
updateSummaryUi() {
|
||||
const card = document.getElementById('fwlive-summary');
|
||||
const toggle = document.getElementById('fwlive-summary-rows');
|
||||
if (card) {
|
||||
if (!card.style) card.style = { display: '' };
|
||||
card.style.display = this.summaryMode ? 'block' : 'none';
|
||||
}
|
||||
if (scroll) {
|
||||
if (!scroll.style) scroll.style = { display: '' };
|
||||
scroll.style.display = this.summaryMode && !this.summaryRowsShown ? 'none' : '';
|
||||
}
|
||||
if (empty) {
|
||||
if (!empty.style) empty.style = { display: '' };
|
||||
empty.style.display = this.summaryMode && !this.summaryRowsShown ? 'none' : '';
|
||||
}
|
||||
this.syncEmptyScrollVisibility(this.filteredRows().length);
|
||||
if (toggle) {
|
||||
toggle.textContent = this.summaryRowsShown ? _('Hide rows') : _('Show rows');
|
||||
toggle.setAttribute('aria-pressed', this.summaryRowsShown ? 'true' : 'false');
|
||||
@@ -1400,13 +1428,12 @@ return view.extend({
|
||||
this.summaryData = null;
|
||||
this.updateSummaryUi();
|
||||
if (this.tablePaused) {
|
||||
const empty = document.getElementById('fwlive-empty');
|
||||
if (empty) {
|
||||
const rows = this.filteredRows();
|
||||
empty.style.display = rows.length ? 'none' : 'block';
|
||||
}
|
||||
this.syncEmptyScrollVisibility(this.filteredRows().length);
|
||||
this.updateStatus();
|
||||
} else this.renderRows(true);
|
||||
} else {
|
||||
this.resolvePaintPending = false;
|
||||
this.renderRows(true);
|
||||
}
|
||||
},
|
||||
|
||||
onSummaryRowsToggle() {
|
||||
@@ -1420,6 +1447,16 @@ return view.extend({
|
||||
this.ensureRenderScheduler().schedule(!!force);
|
||||
},
|
||||
|
||||
scheduleResolvePaint() {
|
||||
if (this.tablePaused) {
|
||||
this.resolvePaintPending = true;
|
||||
this.updateStatus();
|
||||
return;
|
||||
}
|
||||
this.resolvePaintPending = false;
|
||||
this.scheduleRenderRows(true);
|
||||
},
|
||||
|
||||
updateFloodBanner() {
|
||||
const el = document.getElementById('fwlive-flood');
|
||||
if (!el) return;
|
||||
@@ -1601,7 +1638,10 @@ return view.extend({
|
||||
this.requestPoll()
|
||||
.then(() => {
|
||||
/* A hide/show bump abandons this epoch; the catch-up poll paints. */
|
||||
if (epoch === this.currentPollEpoch()) this.renderRows(true);
|
||||
if (epoch === this.currentPollEpoch()) {
|
||||
this.resolvePaintPending = false;
|
||||
this.renderRows(true);
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
}
|
||||
@@ -1767,7 +1807,7 @@ return view.extend({
|
||||
}
|
||||
|
||||
this.updateAdaptiveBanner();
|
||||
if (updated) this.scheduleRenderRows(true);
|
||||
if (updated) this.scheduleResolvePaint();
|
||||
} catch (_e) {
|
||||
/* resolve unavailable — show IPs */
|
||||
} finally {
|
||||
@@ -1894,7 +1934,6 @@ return view.extend({
|
||||
if (!el || typeof el.querySelector !== 'function') return;
|
||||
|
||||
const body = el.querySelector('tbody');
|
||||
const empty = document.getElementById('fwlive-empty');
|
||||
const scroll = document.getElementById('fwlive-scroll');
|
||||
this.updateHash(this.readFilters());
|
||||
|
||||
@@ -1908,6 +1947,7 @@ return view.extend({
|
||||
|
||||
if (!paint) {
|
||||
this.updateFloodBanner();
|
||||
this.syncEmptyScrollVisibility(rows.length);
|
||||
this.updateStatus(rows);
|
||||
return;
|
||||
}
|
||||
@@ -1916,7 +1956,7 @@ return view.extend({
|
||||
|
||||
const prevScroll = scroll ? scroll.scrollTop : 0;
|
||||
|
||||
if (empty) empty.style.display = rows.length ? 'none' : 'block';
|
||||
this.syncEmptyScrollVisibility(rows.length);
|
||||
this.updateStatus(rows);
|
||||
this.renderFilterChips();
|
||||
|
||||
@@ -2068,7 +2108,18 @@ return view.extend({
|
||||
else if (this.summaryMode) {
|
||||
this.renderSummary();
|
||||
this.updateStatus();
|
||||
} else this.scheduleRenderRows();
|
||||
if (this.summaryRowsShown) {
|
||||
const forceHostnamePaint = this.resolvePaintPending;
|
||||
this.resolvePaintPending = false;
|
||||
this.scheduleRenderRows(forceHostnamePaint);
|
||||
}
|
||||
} else {
|
||||
/* Stale resume skips renderRows(true); catch-up polls must still
|
||||
* flush coalesced hostname paints. */
|
||||
const forceHostnamePaint = this.resolvePaintPending;
|
||||
this.resolvePaintPending = false;
|
||||
this.scheduleRenderRows(forceHostnamePaint);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.resolveHostnamesForEntries(this.filteredRows());
|
||||
|
||||
Reference in new issue
Block a user