mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 21:12:51 +08:00
🐤 Sync 2026-09-18 23:02:02
This commit is contained in:
1 parent
d4bee34c32
commit
3587c9c61f
5 files changed
+363
-385
No files matched your search
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
PKG_VERSION:=0.1.44
|
PKG_VERSION:=0.1.44
|
||||||
PKG_RELEASE:=24
|
PKG_RELEASE:=25
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -46,6 +46,21 @@ function persistConsentDismissed() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enableLoggingButton(state, callbacks) {
|
||||||
|
return E(
|
||||||
|
'button',
|
||||||
|
{
|
||||||
|
'class': 'cbi-button cbi-button-action',
|
||||||
|
'type': 'button',
|
||||||
|
'disabled': state.loggingBusy ? '' : null,
|
||||||
|
'click': function () {
|
||||||
|
callbacks.onEnable();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[state.loggingBusy ? _('Enabling…') : _('Enable WAN drop/reject logging')]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function blockerCode(state) {
|
function blockerCode(state) {
|
||||||
const blockers = (state.loggingStatus && state.loggingStatus.blockers) || [];
|
const blockers = (state.loggingStatus && state.loggingStatus.blockers) || [];
|
||||||
if (blockers.indexOf('no_wan_zone') >= 0) return 'no_wan_zone';
|
if (blockers.indexOf('no_wan_zone') >= 0) return 'no_wan_zone';
|
||||||
@@ -164,19 +179,7 @@ function buildConsentPanel(state, callbacks) {
|
|||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
E('p', { 'class': 'fwlive-consent-actions' }, [
|
E('p', { 'class': 'fwlive-consent-actions' }, [
|
||||||
E(
|
enableLoggingButton(state, callbacks),
|
||||||
'button',
|
|
||||||
{
|
|
||||||
'class': 'cbi-button cbi-button-action',
|
|
||||||
'type': 'button',
|
|
||||||
'disabled': state.loggingBusy ? '' : null,
|
|
||||||
'click': function () {
|
|
||||||
persistConsentDismissed();
|
|
||||||
callbacks.onEnable();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[state.loggingBusy ? _('Enabling…') : _('Enable WAN drop/reject logging')]
|
|
||||||
),
|
|
||||||
' ',
|
' ',
|
||||||
E(
|
E(
|
||||||
'button',
|
'button',
|
||||||
@@ -289,19 +292,7 @@ function buildEmptyStateNodes(state, callbacks) {
|
|||||||
);
|
);
|
||||||
nodes.push(
|
nodes.push(
|
||||||
E('p', {}, [
|
E('p', {}, [
|
||||||
E(
|
enableLoggingButton(state, callbacks),
|
||||||
'button',
|
|
||||||
{
|
|
||||||
'class': 'cbi-button cbi-button-action',
|
|
||||||
'type': 'button',
|
|
||||||
'disabled': state.loggingBusy ? '' : null,
|
|
||||||
'click': function () {
|
|
||||||
persistConsentDismissed();
|
|
||||||
callbacks.onEnable();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[state.loggingBusy ? _('Enabling…') : _('Enable WAN drop/reject logging')]
|
|
||||||
),
|
|
||||||
' ',
|
' ',
|
||||||
links.firewallZonesLink(_('I’ll configure this under Network → Firewall'))
|
links.firewallZonesLink(_('I’ll configure this under Network → Firewall'))
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -687,40 +687,26 @@ return view.extend({
|
|||||||
this.renderRows(true);
|
this.renderRows(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleEnableLogging() {
|
async runLoggingToggle(opts) {
|
||||||
if (this.loggingBusy) return;
|
if (this.loggingBusy) return;
|
||||||
|
|
||||||
this.loggingBusy = true;
|
this.loggingBusy = true;
|
||||||
this.loggingNotice = '';
|
this.loggingNotice = '';
|
||||||
this.updateEmptyStateUi();
|
opts.initialUi();
|
||||||
this.updateLoggingToolbarUi();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await callFwliveEnableLogging();
|
const res = await opts.call();
|
||||||
if (!res || !res.ok) {
|
if (!res || !res.ok) {
|
||||||
if (res && res.error === 'nf_log_missing')
|
this.loggingNotice = opts.failureNotice(res);
|
||||||
this.loggingNotice = _(
|
|
||||||
'Cannot enable logging until kernel log modules are installed.'
|
|
||||||
);
|
|
||||||
else if (res && res.error === 'firewall_changes_pending')
|
|
||||||
this.loggingNotice = _(
|
|
||||||
'Another change is staged for the firewall; apply or revert it first.'
|
|
||||||
);
|
|
||||||
else this.loggingNotice = _('Could not enable logging.');
|
|
||||||
await this.loadLoggingStatus();
|
await this.loadLoggingStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.changed)
|
this.loggingNotice = opts.successNotice(res);
|
||||||
this.loggingNotice = _(
|
if (opts.onSuccess) opts.onSuccess(res);
|
||||||
'WAN drop/reject logging is on. Blocked inbound traffic should appear here as it happens — not normal LAN browsing.'
|
|
||||||
);
|
|
||||||
else this.loggingNotice = _('WAN logging is already enabled.');
|
|
||||||
|
|
||||||
logging.persistConsentDismissed();
|
|
||||||
await this.loadLoggingStatus();
|
await this.loadLoggingStatus();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.loggingNotice = _('Administrator access is required to enable logging.');
|
this.loggingNotice = opts.catchNotice();
|
||||||
await this.loadLoggingStatus();
|
await this.loadLoggingStatus();
|
||||||
} finally {
|
} finally {
|
||||||
this.loggingBusy = false;
|
this.loggingBusy = false;
|
||||||
@@ -729,35 +715,47 @@ return view.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleDisableLogging() {
|
async handleEnableLogging() {
|
||||||
if (this.loggingBusy) return;
|
return this.runLoggingToggle({
|
||||||
|
call: () => callFwliveEnableLogging(),
|
||||||
this.loggingBusy = true;
|
initialUi: () => {
|
||||||
this.loggingNotice = '';
|
this.updateEmptyStateUi();
|
||||||
this.updateLoggingToolbarUi();
|
this.updateLoggingToolbarUi();
|
||||||
|
},
|
||||||
try {
|
failureNotice: (res) => {
|
||||||
const res = await callFwliveDisableLogging();
|
if (res && res.error === 'nf_log_missing')
|
||||||
if (!res || !res.ok) {
|
return _('Cannot enable logging until kernel log modules are installed.');
|
||||||
if (res && res.error === 'firewall_changes_pending')
|
if (res && res.error === 'firewall_changes_pending')
|
||||||
this.loggingNotice = _(
|
return _(
|
||||||
'Another change is staged for the firewall; apply or revert it first.'
|
'Another change is staged for the firewall; apply or revert it first.'
|
||||||
);
|
);
|
||||||
else this.loggingNotice = _('Could not disable logging.');
|
return _('Could not enable logging.');
|
||||||
await this.loadLoggingStatus();
|
},
|
||||||
return;
|
successNotice: (res) =>
|
||||||
}
|
res.changed
|
||||||
|
? _(
|
||||||
|
'WAN drop/reject logging is on. Blocked inbound traffic should appear here as it happens — not normal LAN browsing.'
|
||||||
|
)
|
||||||
|
: _('WAN logging is already enabled.'),
|
||||||
|
onSuccess: () => logging.persistConsentDismissed(),
|
||||||
|
catchNotice: () => _('Administrator access is required to enable logging.')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
if (res.changed) this.loggingNotice = _('WAN drop/reject logging is off.');
|
async handleDisableLogging() {
|
||||||
await this.loadLoggingStatus();
|
return this.runLoggingToggle({
|
||||||
} catch (e) {
|
call: () => callFwliveDisableLogging(),
|
||||||
this.loggingNotice = _('Administrator access is required to disable logging.');
|
initialUi: () => this.updateLoggingToolbarUi(),
|
||||||
await this.loadLoggingStatus();
|
failureNotice: (res) => {
|
||||||
} finally {
|
if (res && res.error === 'firewall_changes_pending')
|
||||||
this.loggingBusy = false;
|
return _(
|
||||||
this.updateEmptyStateUi();
|
'Another change is staged for the firewall; apply or revert it first.'
|
||||||
this.updateLoggingToolbarUi();
|
);
|
||||||
}
|
return _('Could not disable logging.');
|
||||||
|
},
|
||||||
|
successNotice: (res) => (res.changed ? _('WAN drop/reject logging is off.') : ''),
|
||||||
|
catchNotice: () => _('Administrator access is required to disable logging.')
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldShowLoggingConsent() {
|
shouldShowLoggingConsent() {
|
||||||
@@ -889,6 +887,13 @@ return view.extend({
|
|||||||
|
|
||||||
/* Caller must discard stale epochs before this synchronous application.
|
/* Caller must discard stale epochs before this synchronous application.
|
||||||
* This updates transport/adaptive state, summary/banner UI, rows, and buffer. */
|
* This updates transport/adaptive state, summary/banner UI, rows, and buffer. */
|
||||||
|
failPollReply(rtt) {
|
||||||
|
this.lastPollError = true;
|
||||||
|
this.fillingBuffer = false;
|
||||||
|
this.notePollRtt(rtt, true);
|
||||||
|
this.updateAdaptiveBanner();
|
||||||
|
},
|
||||||
|
|
||||||
applyPollReply(poll, context) {
|
applyPollReply(poll, context) {
|
||||||
const reply = poll.reply;
|
const reply = poll.reply;
|
||||||
const rtt = poll.rtt;
|
const rtt = poll.rtt;
|
||||||
@@ -899,25 +904,16 @@ return view.extend({
|
|||||||
this.lastPollEffectiveLimit = null;
|
this.lastPollEffectiveLimit = null;
|
||||||
|
|
||||||
if (!reply || typeof reply !== 'object' || Array.isArray(reply)) {
|
if (!reply || typeof reply !== 'object' || Array.isArray(reply)) {
|
||||||
this.lastPollError = true;
|
this.failPollReply(rtt);
|
||||||
this.fillingBuffer = false;
|
|
||||||
this.notePollRtt(rtt, true);
|
|
||||||
this.updateAdaptiveBanner();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (reply.error) {
|
if (reply.error) {
|
||||||
this.lastPollError = true;
|
this.failPollReply(rtt);
|
||||||
this.fillingBuffer = false;
|
|
||||||
this.notePollRtt(rtt, true);
|
|
||||||
this.updateAdaptiveBanner();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const raw = reply.log;
|
const raw = reply.log;
|
||||||
if (!Array.isArray(raw)) {
|
if (!Array.isArray(raw)) {
|
||||||
this.lastPollError = true;
|
this.failPollReply(rtt);
|
||||||
this.fillingBuffer = false;
|
|
||||||
this.notePollRtt(rtt, true);
|
|
||||||
this.updateAdaptiveBanner();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1173,11 +1169,7 @@ return view.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateAdaptiveBanner() {
|
adaptiveBannerParts() {
|
||||||
const el = document.getElementById('fwlive-adaptive');
|
|
||||||
if (!el) return;
|
|
||||||
if (!el.style) el.style = { display: '' };
|
|
||||||
|
|
||||||
const parts = [];
|
const parts = [];
|
||||||
const mode = this.fetchMode === 'manual' ? _('Manual') : _('Auto');
|
const mode = this.fetchMode === 'manual' ? _('Manual') : _('Auto');
|
||||||
if (this.lastPollRequestedLines !== null && this.lastPollReturnedMessages !== null) {
|
if (this.lastPollRequestedLines !== null && this.lastPollReturnedMessages !== null) {
|
||||||
@@ -1218,6 +1210,15 @@ return view.extend({
|
|||||||
else if (this.serverTruncated) parts.push(_('Server truncated this poll (adaptive cap).'));
|
else if (this.serverTruncated) parts.push(_('Server truncated this poll (adaptive cap).'));
|
||||||
if (this.resolveLoadShed)
|
if (this.resolveLoadShed)
|
||||||
parts.push(_('Hostname resolve paused while the router is under load.'));
|
parts.push(_('Hostname resolve paused while the router is under load.'));
|
||||||
|
return parts;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAdaptiveBanner() {
|
||||||
|
const el = document.getElementById('fwlive-adaptive');
|
||||||
|
if (!el) return;
|
||||||
|
if (!el.style) el.style = { display: '' };
|
||||||
|
|
||||||
|
const parts = this.adaptiveBannerParts();
|
||||||
|
|
||||||
if (parts.length) {
|
if (parts.length) {
|
||||||
el.style.display = 'block';
|
el.style.display = 'block';
|
||||||
@@ -1603,6 +1604,18 @@ return view.extend({
|
|||||||
return Array.from(ips);
|
return Array.from(ips);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isLoadShedReply(res) {
|
||||||
|
return res && typeof res === 'object' && res.disabled === 'load';
|
||||||
|
},
|
||||||
|
|
||||||
|
resolveNamesFromReply(res) {
|
||||||
|
return res && typeof res === 'object' && res.names && typeof res.names === 'object'
|
||||||
|
? res.names
|
||||||
|
: res && typeof res === 'object' && !Array.isArray(res)
|
||||||
|
? res
|
||||||
|
: {};
|
||||||
|
},
|
||||||
|
|
||||||
async resolveHostnamesForEntries(entries) {
|
async resolveHostnamesForEntries(entries) {
|
||||||
if (!this.showHostnames || this.resolveInFlight) return;
|
if (!this.showHostnames || this.resolveInFlight) return;
|
||||||
|
|
||||||
@@ -1633,7 +1646,7 @@ return view.extend({
|
|||||||
const res = await callFwliveResolve({ addresses: need });
|
const res = await callFwliveResolve({ addresses: need });
|
||||||
if (gen !== this.resolveGeneration) return;
|
if (gen !== this.resolveGeneration) return;
|
||||||
|
|
||||||
if (res && typeof res === 'object' && res.disabled === 'load') {
|
if (this.isLoadShedReply(res)) {
|
||||||
this.resolveLoadShed = true;
|
this.resolveLoadShed = true;
|
||||||
this.resolveShedUntil = Date.now() + 60000;
|
this.resolveShedUntil = Date.now() + 60000;
|
||||||
this.updateAdaptiveBanner();
|
this.updateAdaptiveBanner();
|
||||||
@@ -1649,12 +1662,7 @@ return view.extend({
|
|||||||
if (res && typeof res === 'object' && typeof res.error === 'string' && res.error)
|
if (res && typeof res === 'object' && typeof res.error === 'string' && res.error)
|
||||||
return;
|
return;
|
||||||
/* Full reply: names map under .names; legacy expect-unwrap was the map. */
|
/* Full reply: names map under .names; legacy expect-unwrap was the map. */
|
||||||
const names =
|
const names = this.resolveNamesFromReply(res);
|
||||||
res && typeof res === 'object' && res.names && typeof res.names === 'object'
|
|
||||||
? res.names
|
|
||||||
: res && typeof res === 'object' && !Array.isArray(res)
|
|
||||||
? res
|
|
||||||
: {};
|
|
||||||
let updated = false;
|
let updated = false;
|
||||||
|
|
||||||
for (let i = 0; i < need.length; i++) {
|
for (let i = 0; i < need.length; i++) {
|
||||||
@@ -2008,6 +2016,168 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderDisplayDrawer() {
|
||||||
|
return E('div', { 'id': 'fwlive-display-drawer', 'class': 'fwlive-display-bar' }, [
|
||||||
|
E('span', { 'class': 'fwlive-display-bar-label' }, [_('Display options')]),
|
||||||
|
E('div', { 'class': 'fwlive-display-controls' }, [
|
||||||
|
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-limit' }, [
|
||||||
|
_('Limit'),
|
||||||
|
E(
|
||||||
|
'select',
|
||||||
|
{
|
||||||
|
'id': 'fwlive-limit',
|
||||||
|
'class': 'cbi-input-select'
|
||||||
|
},
|
||||||
|
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('input', {
|
||||||
|
'id': 'fwlive-row-tint-toggle',
|
||||||
|
'type': 'checkbox',
|
||||||
|
'title': _('Show pass/deny row background colors')
|
||||||
|
}),
|
||||||
|
_('Row tint')
|
||||||
|
]),
|
||||||
|
E(
|
||||||
|
'label',
|
||||||
|
{
|
||||||
|
'id': 'fwlive-row-tint-palette-wrap',
|
||||||
|
'class': 'fwlive-display-ctl',
|
||||||
|
'for': 'fwlive-row-tint'
|
||||||
|
},
|
||||||
|
[
|
||||||
|
_('Palette'),
|
||||||
|
E(
|
||||||
|
'select',
|
||||||
|
{
|
||||||
|
'id': 'fwlive-row-tint',
|
||||||
|
'class': 'cbi-input-select',
|
||||||
|
'title': _('Classic uses green/red; Accessible uses teal/orange')
|
||||||
|
},
|
||||||
|
this.rowTintPaletteOptions()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
E('label', { 'class': 'fwlive-display-ctl' }, [
|
||||||
|
E('input', {
|
||||||
|
'id': 'fwlive-show-hostnames',
|
||||||
|
'type': 'checkbox'
|
||||||
|
}),
|
||||||
|
_('Show hostnames')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHelpNodes() {
|
||||||
|
return E('div', { 'class': 'fwlive-help-row' }, [
|
||||||
|
E('details', { 'id': 'fwlive-help', 'class': 'fwlive-help' }, [
|
||||||
|
E('summary', {}, [_('Help')]),
|
||||||
|
E('ul', {}, [
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'The table updates automatically when your firewall logs traffic. Use Pause if it moves too fast.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'Enable logging turns on WAN zone drop/reject logging only (same as Network → Firewall). It does not add rules or log normal LAN browsing.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [
|
||||||
|
_('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', {}, [
|
||||||
|
_(
|
||||||
|
'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.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'Switching tabs pauses polling; returning performs one catch-up poll. Hostnames are off by default because lookups add work.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'The router log ring may evict older events before fwlive reads them. fwlive cannot recover evicted entries or change forwarding behavior.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'The rate shown for WAN logging is the firewall zone log_limit. OpenWrt defaults to 10/minute when no explicit limit is configured; fwlive does not impose this cap.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', { 'id': 'fwlive-manual-test' }, []),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'Click a row (Time or other non-link cells) to see the full log line (Simple view).'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'Click an IP, action, or protocol to filter; use the Protocol menu (or ≠ on a chip) to exclude.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'Row tint shows pass/deny row backgrounds when checked. Choose Classic (green/red, default) or Accessible (teal/orange). Action text stays colored either way.'
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
E('li', {}, [_('Use Detail for all columns (flags, length, raw message).')]),
|
||||||
|
E('li', {}, [
|
||||||
|
_(
|
||||||
|
'If Row tint looks missing, the active LuCI theme may omit success/error or info/warn CSS variables; fwlive falls back to local colors (air-gapped, no data leaves the device).'
|
||||||
|
)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
'id': 'fwlive-build',
|
||||||
|
'class': 'fwlive-build',
|
||||||
|
'title': 'luci-app-fwlive'
|
||||||
|
},
|
||||||
|
['v' + constants.APP_VERSION]
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
/* LuCI may finish the load/render/addFooter sequence after pagehide. */
|
/* LuCI may finish the load/render/addFooter sequence after pagehide. */
|
||||||
if (this.viewDisposed) return E('div', { 'class': 'cbi-map' });
|
if (this.viewDisposed) return E('div', { 'class': 'cbi-map' });
|
||||||
@@ -2164,89 +2334,7 @@ return view.extend({
|
|||||||
])
|
])
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
E('div', { 'id': 'fwlive-display-drawer', 'class': 'fwlive-display-bar' }, [
|
this.renderDisplayDrawer(),
|
||||||
E('span', { 'class': 'fwlive-display-bar-label' }, [_('Display options')]),
|
|
||||||
E('div', { 'class': 'fwlive-display-controls' }, [
|
|
||||||
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-limit' }, [
|
|
||||||
_('Limit'),
|
|
||||||
E(
|
|
||||||
'select',
|
|
||||||
{
|
|
||||||
'id': 'fwlive-limit',
|
|
||||||
'class': 'cbi-input-select'
|
|
||||||
},
|
|
||||||
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('input', {
|
|
||||||
'id': 'fwlive-row-tint-toggle',
|
|
||||||
'type': 'checkbox',
|
|
||||||
'title': _('Show pass/deny row background colors')
|
|
||||||
}),
|
|
||||||
_('Row tint')
|
|
||||||
]),
|
|
||||||
E(
|
|
||||||
'label',
|
|
||||||
{
|
|
||||||
'id': 'fwlive-row-tint-palette-wrap',
|
|
||||||
'class': 'fwlive-display-ctl',
|
|
||||||
'for': 'fwlive-row-tint'
|
|
||||||
},
|
|
||||||
[
|
|
||||||
_('Palette'),
|
|
||||||
E(
|
|
||||||
'select',
|
|
||||||
{
|
|
||||||
'id': 'fwlive-row-tint',
|
|
||||||
'class': 'cbi-input-select',
|
|
||||||
'title': _(
|
|
||||||
'Classic uses green/red; Accessible uses teal/orange'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
this.rowTintPaletteOptions()
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
E('label', { 'class': 'fwlive-display-ctl' }, [
|
|
||||||
E('input', {
|
|
||||||
'id': 'fwlive-show-hostnames',
|
|
||||||
'type': 'checkbox'
|
|
||||||
}),
|
|
||||||
_('Show hostnames')
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
E(
|
E(
|
||||||
'div',
|
'div',
|
||||||
{ 'id': 'fwlive-filter-panel', 'class': 'fwlive-filter-panel fwlive-find-row' },
|
{ 'id': 'fwlive-filter-panel', 'class': 'fwlive-filter-panel fwlive-find-row' },
|
||||||
@@ -2348,91 +2436,7 @@ return view.extend({
|
|||||||
E('tbody', {}, [])
|
E('tbody', {}, [])
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'fwlive-help-row' }, [
|
this.renderHelpNodes()
|
||||||
E('details', { 'id': 'fwlive-help', 'class': 'fwlive-help' }, [
|
|
||||||
E('summary', {}, [_('Help')]),
|
|
||||||
E('ul', {}, [
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'The table updates automatically when your firewall logs traffic. Use Pause if it moves too fast.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'Enable logging turns on WAN zone drop/reject logging only (same as Network → Firewall). It does not add rules or log normal LAN browsing.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'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', {}, [
|
|
||||||
_(
|
|
||||||
'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.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'Switching tabs pauses polling; returning performs one catch-up poll. Hostnames are off by default because lookups add work.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'The router log ring may evict older events before fwlive reads them. fwlive cannot recover evicted entries or change forwarding behavior.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'The rate shown for WAN logging is the firewall zone log_limit. OpenWrt defaults to 10/minute when no explicit limit is configured; fwlive does not impose this cap.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', { 'id': 'fwlive-manual-test' }, []),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'Click a row (Time or other non-link cells) to see the full log line (Simple view).'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'Click an IP, action, or protocol to filter; use the Protocol menu (or ≠ on a chip) to exclude.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'Row tint shows pass/deny row backgrounds when checked. Choose Classic (green/red, default) or Accessible (teal/orange). Action text stays colored either way.'
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_('Use Detail for all columns (flags, length, raw message).')
|
|
||||||
]),
|
|
||||||
E('li', {}, [
|
|
||||||
_(
|
|
||||||
'If Row tint looks missing, the active LuCI theme may omit success/error or info/warn CSS variables; fwlive falls back to local colors (air-gapped, no data leaves the device).'
|
|
||||||
)
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
E(
|
|
||||||
'span',
|
|
||||||
{
|
|
||||||
'id': 'fwlive-build',
|
|
||||||
'class': 'fwlive-build',
|
|
||||||
'title': 'luci-app-fwlive'
|
|
||||||
},
|
|
||||||
['v' + constants.APP_VERSION]
|
|
||||||
)
|
|
||||||
])
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -280,6 +280,64 @@ _fwlive_mktemp() {
|
|||||||
printf '%s' "$_mk_file"
|
printf '%s' "$_mk_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enrich_rules_dump() {
|
||||||
|
_dump_prefix="$1"
|
||||||
|
_dump_kind="$2"
|
||||||
|
|
||||||
|
case "$_dump_kind" in
|
||||||
|
nft)
|
||||||
|
_dump_error='nft_failed'
|
||||||
|
;;
|
||||||
|
iptables)
|
||||||
|
_dump_error='iptables_failed'
|
||||||
|
;;
|
||||||
|
ip6tables)
|
||||||
|
_dump_error='ip6tables_failed'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
_rules_dump=$(_fwlive_mktemp "$_dump_prefix") || _rules_dump=''
|
||||||
|
if [ -n "$_rules_dump" ]; then
|
||||||
|
# Keep the dump command and stream parser paired by backend. The
|
||||||
|
# ip6tables caller is availability-gated below; its empty-error
|
||||||
|
# precedence is intentionally preserved in the mktemp branch.
|
||||||
|
if case "$_dump_kind" in
|
||||||
|
nft)
|
||||||
|
nft_list_ruleset >"$_rules_dump" 2>/dev/null
|
||||||
|
;;
|
||||||
|
iptables|ip6tables)
|
||||||
|
run_with_timeout "$IPTABLES_TIMEOUT" "${_dump_kind}-save" >"$_rules_dump" 2>/dev/null
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
case "$_dump_kind" in
|
||||||
|
nft)
|
||||||
|
map_from_nft_stream labeled <"$_rules_dump"
|
||||||
|
map_from_nft_stream unlabeled <"$_rules_dump"
|
||||||
|
;;
|
||||||
|
iptables|ip6tables)
|
||||||
|
map_from_iptables_save_stream labeled <"$_rules_dump"
|
||||||
|
map_from_iptables_save_stream unlabeled <"$_rules_dump"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -z "$_enrich_error" ]; then
|
||||||
|
_enrich_error="$_dump_error"
|
||||||
|
fi
|
||||||
|
rm -f "$_rules_dump"
|
||||||
|
elif [ "$_dump_kind" = ip6tables ]; then
|
||||||
|
# IPv6 enrichment is optional and must not replace an earlier
|
||||||
|
# IPv4/backend error when its temp file cannot be allocated.
|
||||||
|
if [ -z "$_enrich_error" ]; then
|
||||||
|
_enrich_error='mktemp_failed'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_enrich_error='mktemp_failed'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
build_rules_map() {
|
build_rules_map() {
|
||||||
OUT=''
|
OUT=''
|
||||||
_map_keys=0
|
_map_keys=0
|
||||||
@@ -293,47 +351,12 @@ build_rules_map() {
|
|||||||
|
|
||||||
case "$backend" in
|
case "$backend" in
|
||||||
nft)
|
nft)
|
||||||
_rules_dump=$(_fwlive_mktemp fwlive-nft) || _rules_dump=''
|
enrich_rules_dump fwlive-nft nft
|
||||||
if [ -n "$_rules_dump" ]; then
|
|
||||||
# Dumps land on /tmp (tmpfs). Duration bounded by
|
|
||||||
# NFT_TIMEOUT / IPTABLES_TIMEOUT; key/byte caps in map_add.
|
|
||||||
if nft_list_ruleset >"$_rules_dump" 2>/dev/null; then
|
|
||||||
map_from_nft_stream labeled <"$_rules_dump"
|
|
||||||
map_from_nft_stream unlabeled <"$_rules_dump"
|
|
||||||
elif [ -z "$_enrich_error" ]; then
|
|
||||||
_enrich_error='nft_failed'
|
|
||||||
fi
|
|
||||||
rm -f "$_rules_dump"
|
|
||||||
else
|
|
||||||
_enrich_error='mktemp_failed'
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
iptables)
|
iptables)
|
||||||
_rules_dump=$(_fwlive_mktemp fwlive-ipt) || _rules_dump=''
|
enrich_rules_dump fwlive-ipt iptables
|
||||||
if [ -n "$_rules_dump" ]; then
|
|
||||||
if run_with_timeout "$IPTABLES_TIMEOUT" iptables-save >"$_rules_dump" 2>/dev/null; then
|
|
||||||
map_from_iptables_save_stream labeled <"$_rules_dump"
|
|
||||||
map_from_iptables_save_stream unlabeled <"$_rules_dump"
|
|
||||||
elif [ -z "$_enrich_error" ]; then
|
|
||||||
_enrich_error='iptables_failed'
|
|
||||||
fi
|
|
||||||
rm -f "$_rules_dump"
|
|
||||||
else
|
|
||||||
_enrich_error='mktemp_failed'
|
|
||||||
fi
|
|
||||||
if command -v ip6tables-save >/dev/null 2>&1; then
|
if command -v ip6tables-save >/dev/null 2>&1; then
|
||||||
_rules_dump=$(_fwlive_mktemp fwlive-ip6t) || _rules_dump=''
|
enrich_rules_dump fwlive-ip6t ip6tables
|
||||||
if [ -n "$_rules_dump" ]; then
|
|
||||||
if run_with_timeout "$IPTABLES_TIMEOUT" ip6tables-save >"$_rules_dump" 2>/dev/null; then
|
|
||||||
map_from_iptables_save_stream labeled <"$_rules_dump"
|
|
||||||
map_from_iptables_save_stream unlabeled <"$_rules_dump"
|
|
||||||
elif [ -z "$_enrich_error" ]; then
|
|
||||||
_enrich_error='ip6tables_failed'
|
|
||||||
fi
|
|
||||||
rm -f "$_rules_dump"
|
|
||||||
elif [ -z "$_enrich_error" ]; then
|
|
||||||
_enrich_error='mktemp_failed'
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -696,32 +719,36 @@ EOF
|
|||||||
printf '{"names":{%s}}' "$OUT"
|
printf '{"names":{%s}}' "$OUT"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_eq() {
|
||||||
|
_check_name="$1"
|
||||||
|
_check_got="$2"
|
||||||
|
_check_expected="$3"
|
||||||
|
_check_failure="$4"
|
||||||
|
if [ "$_check_got" != "$_check_expected" ]; then
|
||||||
|
# Keep the failure suffix at each call site so stderr stays byte-for-byte
|
||||||
|
# identical to the pre-extraction selftest messages.
|
||||||
|
echo "$_check_name: $_check_failure" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
run_selftest() {
|
run_selftest() {
|
||||||
input=$(printf 'a\\b"c\nd\te')
|
input=$(printf 'a\\b"c\nd\te')
|
||||||
escaped=$(printf '%s' "$input" | json_escape)
|
escaped=$(printf '%s' "$input" | json_escape)
|
||||||
expected=$(printf 'a\\\\b\\"c\\nd\\te')
|
expected=$(printf 'a\\\\b\\"c\\nd\\te')
|
||||||
if [ "$escaped" != "$expected" ]; then
|
check_eq json_escape "$escaped" "$expected" mismatch || return 1
|
||||||
echo "json_escape: mismatch" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remaining C0 controls must become \u00XX (RFC 8259), not raw bytes.
|
# Remaining C0 controls must become \u00XX (RFC 8259), not raw bytes.
|
||||||
input=$(printf 'x\001y\037z')
|
input=$(printf 'x\001y\037z')
|
||||||
escaped=$(printf '%s' "$input" | json_escape)
|
escaped=$(printf '%s' "$input" | json_escape)
|
||||||
expected='x\u0001y\u001fz'
|
expected='x\u0001y\u001fz'
|
||||||
if [ "$escaped" != "$expected" ]; then
|
check_eq 'json_escape C0' "$escaped" "$expected" "expected $expected got $escaped" || return 1
|
||||||
echo "json_escape C0: expected $expected got $escaped" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Blank lines are data (paragraph-mode RS="" used to drop them).
|
# Blank lines are data (paragraph-mode RS="" used to drop them).
|
||||||
input=$(printf 'a\n\nb')
|
input=$(printf 'a\n\nb')
|
||||||
escaped=$(printf '%s' "$input" | json_escape)
|
escaped=$(printf '%s' "$input" | json_escape)
|
||||||
expected=$(printf 'a\\n\\nb')
|
expected=$(printf 'a\\n\\nb')
|
||||||
if [ "$escaped" != "$expected" ]; then
|
check_eq 'json_escape blank line' "$escaped" "$expected" "expected $expected got $escaped" || return 1
|
||||||
echo "json_escape blank line: expected $expected got $escaped" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if is_resolvable_address 'not an ip'; then
|
if is_resolvable_address 'not an ip'; then
|
||||||
echo 'is_resolvable_address: expected reject for spaces' >&2
|
echo 'is_resolvable_address: expected reject for spaces' >&2
|
||||||
@@ -812,25 +839,13 @@ run_selftest() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
got=$(poll_clamp_lines "99999999999999999999")
|
got=$(poll_clamp_lines "99999999999999999999")
|
||||||
if [ "$got" != "$POLL_LINES_MAX" ]; then
|
check_eq poll_clamp_lines "$got" "$POLL_LINES_MAX" "over-long expected $POLL_LINES_MAX got $got" || return 1
|
||||||
echo "poll_clamp_lines: over-long expected $POLL_LINES_MAX got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_clamp_lines "0")
|
got=$(poll_clamp_lines "0")
|
||||||
if [ "$got" != "50" ]; then
|
check_eq poll_clamp_lines "$got" "50" "zero expected 50 got $got" || return 1
|
||||||
echo "poll_clamp_lines: zero expected 50 got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_clamp_lines "2001")
|
got=$(poll_clamp_lines "2001")
|
||||||
if [ "$got" != "$POLL_LINES_MAX" ]; then
|
check_eq poll_clamp_lines "$got" "$POLL_LINES_MAX" "2001 expected $POLL_LINES_MAX got $got" || return 1
|
||||||
echo "poll_clamp_lines: 2001 expected $POLL_LINES_MAX got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_clamp_lines "500")
|
got=$(poll_clamp_lines "500")
|
||||||
if [ "$got" != "500" ]; then
|
check_eq poll_clamp_lines "$got" "500" "expected 500 got $got" || return 1
|
||||||
echo "poll_clamp_lines: expected 500 got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! _fwlive_tmp_dir_ok; then
|
if ! _fwlive_tmp_dir_ok; then
|
||||||
echo "_fwlive_tmp_dir_ok: /tmp must be a sticky directory" >&2
|
echo "_fwlive_tmp_dir_ok: /tmp must be a sticky directory" >&2
|
||||||
@@ -841,18 +856,12 @@ run_selftest() {
|
|||||||
Address: 127.0.0.1:53
|
Address: 127.0.0.1:53
|
||||||
|
|
||||||
8.8.8.8.in-addr.arpa name = dns.google.")
|
8.8.8.8.in-addr.arpa name = dns.google.")
|
||||||
if [ "$got" != "dns.google" ]; then
|
check_eq parse_nslookup_name "$got" "dns.google" "bind-style expected dns.google got '$got'" || return 1
|
||||||
echo "parse_nslookup_name: bind-style expected dns.google got '$got'" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(parse_nslookup_name "Server: 127.0.0.1
|
got=$(parse_nslookup_name "Server: 127.0.0.1
|
||||||
Address: 127.0.0.1:53
|
Address: 127.0.0.1:53
|
||||||
|
|
||||||
Address 1: 8.8.8.8 dns.google.")
|
Address 1: 8.8.8.8 dns.google.")
|
||||||
if [ "$got" != "dns.google" ]; then
|
check_eq parse_nslookup_name "$got" "dns.google" "busybox Address N expected dns.google got '$got'" || return 1
|
||||||
echo "parse_nslookup_name: busybox Address N expected dns.google got '$got'" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(parse_nslookup_name "Server: 192.168.1.1
|
got=$(parse_nslookup_name "Server: 192.168.1.1
|
||||||
Address 1: 192.168.1.1 router.lan
|
Address 1: 192.168.1.1 router.lan
|
||||||
|
|
||||||
@@ -864,10 +873,7 @@ Address 1: 8.8.8.8 dns.google")
|
|||||||
fi
|
fi
|
||||||
got=$(parse_nslookup_name "Server: 127.0.0.1
|
got=$(parse_nslookup_name "Server: 127.0.0.1
|
||||||
Address: 127.0.0.1:53")
|
Address: 127.0.0.1:53")
|
||||||
if [ -n "$got" ]; then
|
check_eq parse_nslookup_name "$got" '' "server-only expected empty got '$got'" || return 1
|
||||||
echo "parse_nslookup_name: server-only expected empty got '$got'" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
run_logging_selftest || return 1
|
run_logging_selftest || return 1
|
||||||
|
|
||||||
@@ -888,41 +894,20 @@ Address: 127.0.0.1:53")
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
got=$(poll_lines_from_input '{"addresses":["999999"]}')
|
got=$(poll_lines_from_input '{"addresses":["999999"]}')
|
||||||
if [ "$got" != "$POLL_LINES_MAX" ]; then
|
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "expected $POLL_LINES_MAX got $got" || return 1
|
||||||
echo "poll_lines_from_input: expected $POLL_LINES_MAX got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
got=$(poll_lines_from_input '{"addresses":["99999999999999999999"]}')
|
got=$(poll_lines_from_input '{"addresses":["99999999999999999999"]}')
|
||||||
if [ "$got" != "$POLL_LINES_MAX" ]; then
|
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "over-long expected $POLL_LINES_MAX got $got" || return 1
|
||||||
echo "poll_lines_from_input: over-long expected $POLL_LINES_MAX got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_lines_from_input '{"addresses":["18446744073709551616"]}')
|
got=$(poll_lines_from_input '{"addresses":["18446744073709551616"]}')
|
||||||
if [ "$got" != "$POLL_LINES_MAX" ]; then
|
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "18446744073709551616 expected $POLL_LINES_MAX got $got" || return 1
|
||||||
echo "poll_lines_from_input: 18446744073709551616 expected $POLL_LINES_MAX got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_lines_from_input '{"addresses":["0"]}')
|
got=$(poll_lines_from_input '{"addresses":["0"]}')
|
||||||
if [ "$got" != "50" ]; then
|
check_eq poll_lines_from_input "$got" "50" "zero expected 50 got $got" || return 1
|
||||||
echo "poll_lines_from_input: zero expected 50 got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_lines_from_input '{"addresses":["2001"]}')
|
got=$(poll_lines_from_input '{"addresses":["2001"]}')
|
||||||
if [ "$got" != "$POLL_LINES_MAX" ]; then
|
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "2001 expected $POLL_LINES_MAX got $got" || return 1
|
||||||
echo "poll_lines_from_input: 2001 expected $POLL_LINES_MAX got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_lines_from_input '{"addresses":["500"]}')
|
got=$(poll_lines_from_input '{"addresses":["500"]}')
|
||||||
if [ "$got" != "500" ]; then
|
check_eq poll_lines_from_input "$got" "500" "expected 500 got $got" || return 1
|
||||||
echo "poll_lines_from_input: expected 500 got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
got=$(poll_lines_from_input '{"addresses":["100"]}')
|
got=$(poll_lines_from_input '{"addresses":["100"]}')
|
||||||
if [ "$got" != "100" ]; then
|
check_eq poll_lines_from_input "$got" "100" "expected 100 got $got" || return 1
|
||||||
echo "poll_lines_from_input: expected 100 got $got" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-14
@@ -1,17 +1,18 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=webd
|
PKG_NAME:=webd
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
PKG_VERSION:=20240223
|
PKG_VERSION:=20240223
|
||||||
PKG_VERSION_MEDIATEK_FILO:=20240710
|
PKG_VERSION_MEDIATEK_FILO:=20240710
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
define Package/$(PKG_NAME)
|
define Package/$(PKG_NAME)
|
||||||
SECTION:=net
|
SECTION:=net
|
||||||
CATEGORY:=Network
|
CATEGORY:=Network
|
||||||
TITLE:=A Lightweight self-hosted netdisk application
|
TITLE:=A Lightweight self-hosted netdisk application
|
||||||
URL:=https://webd90kb.github.io/webd/
|
URL:=https://webd.cf
|
||||||
endef
|
endef
|
||||||
|
|
||||||
TARGET_LIBC:=musl
|
TARGET_LIBC:=musl
|
||||||
@@ -79,12 +80,14 @@ endif
|
|||||||
|
|
||||||
ifeq ($(ARCH),aarch64)
|
ifeq ($(ARCH),aarch64)
|
||||||
TARGET_ARCH:=aarch64
|
TARGET_ARCH:=aarch64
|
||||||
|
|
||||||
ifeq ($(BOARD),rockchip)
|
ifeq ($(BOARD),rockchip)
|
||||||
TARGET_BOARD:=musl.rockchip-armv8
|
TARGET_BOARD:=musl.rockchip-armv8
|
||||||
endif
|
endif
|
||||||
ifeq ($(BOARD),brcm2708)
|
ifeq ($(BOARD),brcm2708)
|
||||||
TARGET_BOARD:=musl.brcm2708-bcm2710
|
TARGET_BOARD:=musl.brcm2708-bcm2710
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(BOARD),mediatek)
|
ifeq ($(BOARD),mediatek)
|
||||||
ifneq (,$(filter filogic,$(SUBTARGET)))
|
ifneq (,$(filter filogic,$(SUBTARGET)))
|
||||||
TARGET_BOARD:=musl.mediatek-filogic
|
TARGET_BOARD:=musl.mediatek-filogic
|
||||||
@@ -93,16 +96,8 @@ ifeq ($(ARCH),aarch64)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(DUMP),1)
|
|
||||||
ifneq ($(ARCH),)
|
|
||||||
ifeq ($(strip $(TARGET_ARCH)$(TARGET_BOARD)),)
|
|
||||||
$(error Unsupported ARCH/BOARD combo: ARCH=$(ARCH) BOARD=$(BOARD) SUBTARGET=$(SUBTARGET))
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(TARGET_ARCH)-openwrt-linux-$(TARGET_BOARD).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(TARGET_ARCH)-openwrt-linux-$(TARGET_BOARD).tar.gz
|
||||||
PKG_SOURCE_URL:=https://webd90kb.github.io/webd/webd/webd_dl/20240223/
|
PKG_SOURCE_URL:=https://webd90kb.github.io/webd/webd/webd_dl/$(PKG_VERSION)/
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|
||||||
UNTAR_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
UNTAR_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||||
@@ -118,10 +113,13 @@ endef
|
|||||||
define Package/$(PKG_NAME)/install
|
define Package/$(PKG_NAME)/install
|
||||||
$(INSTALL_DIR) $(1)/usr/bin
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
$(INSTALL_BIN) $(UNTAR_DIR)/webd/webd $(1)/usr/bin/webd
|
$(INSTALL_BIN) $(UNTAR_DIR)/webd/webd $(1)/usr/bin/webd
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/usr/share/$(PKG_NAME)/web
|
$(INSTALL_DIR) $(1)/usr/share/$(PKG_NAME)/web
|
||||||
$(CP) -r $(UNTAR_DIR)/webd/web/* $(1)/usr/share/$(PKG_NAME)/web/
|
$(CP) -r $(UNTAR_DIR)/webd/web/* $(1)/usr/share/$(PKG_NAME)/web/
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/etc
|
$(INSTALL_DIR) $(1)/etc
|
||||||
$(INSTALL_DATA) $(UNTAR_DIR)/webd/webd.conf $(1)/etc/webd.conf.example
|
$(INSTALL_DATA) $(UNTAR_DIR)/webd/webd.conf $(1)/etc/webd.conf.example
|
||||||
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||||
Reference in new issue
Block a user