mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 23:15:13 +08:00
🌈 Sync 2026-09-29 11:54:37
This commit is contained in:
1 parent
aea69d1f87
commit
89626fd57d
44 files changed
+1356
-869
No files matched your search
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter +coreutils-timeout
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.47
|
||||
PKG_RELEASE:=48
|
||||
PKG_RELEASE:=49
|
||||
|
||||
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
||||
ifdef SOURCE_DATE_EPOCH
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
* Pure Map helpers — safe to unit-test on host without LuCI.
|
||||
*/
|
||||
return baseclass.extend({
|
||||
CACHE_MAX: 2000,
|
||||
CACHE_MAX: 4000,
|
||||
FAIL_TTL_MS: 60000,
|
||||
FAIL_MAX: 2000,
|
||||
FAIL_MAX: 4000,
|
||||
|
||||
/* Touch-on-write LRU: re-insert moves key to newest; evict oldest when over max. */
|
||||
lruSet: function (map, key, value, max) {
|
||||
|
||||
@@ -293,13 +293,16 @@ function buildEmptyStateNodes(state, callbacks) {
|
||||
nodes.push(
|
||||
E('p', {}, [
|
||||
_(
|
||||
'Kernel netfilter log modules are missing. Install kmod-nf-log and kmod-nf-log6, then reload the firewall.'
|
||||
'Install kmod-nf-log and kmod-nf-log6 with the command for your OpenWrt release, then reload the firewall.'
|
||||
)
|
||||
])
|
||||
);
|
||||
nodes.push(E('p', {}, [_('OpenWrt 24.10 and older (opkg):')]));
|
||||
nodes.push(
|
||||
E('p', {}, [E('code', {}, ['opkg update && opkg install kmod-nf-log kmod-nf-log6'])])
|
||||
);
|
||||
nodes.push(E('p', {}, [_('OpenWrt 25.12 and newer (apk):')]));
|
||||
nodes.push(E('p', {}, [E('code', {}, ['apk -U add kmod-nf-log kmod-nf-log6'])]));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
'require fwlive.render-policy as renderPolicy';
|
||||
'require fwlive.render-scheduler as renderScheduler';
|
||||
|
||||
const RULES_RETRY_BASE_MS = 5000;
|
||||
/* Includes the initial read; a page reload starts a fresh retry budget. */
|
||||
const RULES_RETRY_MAX_ATTEMPTS = 6;
|
||||
|
||||
const callFwlivePoll = rpc.declare({
|
||||
object: 'fwlive',
|
||||
method: 'poll',
|
||||
@@ -162,11 +166,13 @@ return view.extend({
|
||||
hostnameFailed: null,
|
||||
resolveInFlight: false,
|
||||
resolveGeneration: 0,
|
||||
/* Coalesce hostname-cache paints deferred while tablePaused. */
|
||||
/* Track hostname paints queued or deferred until a visible forced paint. */
|
||||
resolvePaintPending: false,
|
||||
lastPollError: false,
|
||||
lastPollErrorCode: null,
|
||||
lastRulesError: null,
|
||||
rulesRetryAttempt: 0,
|
||||
nextRulesRetryAt: 0,
|
||||
followLive: true,
|
||||
rulesMap: {},
|
||||
firewallBackend: 'nft',
|
||||
@@ -216,7 +222,7 @@ return view.extend({
|
||||
|
||||
updateHash(filters) {
|
||||
const parts = Object.keys(filters)
|
||||
.filter((k) => filters[k])
|
||||
.filter((k) => filters[k] && log.parseFilterValue(filters[k]).value)
|
||||
.map((k) => '%s=%s'.format(encodeURIComponent(k), encodeURIComponent(filters[k])));
|
||||
if (this.rowLimit !== constants.DEFAULT_ROW_LIMIT)
|
||||
parts.push('limit=%s'.format(encodeURIComponent(this.rowLimit)));
|
||||
@@ -703,7 +709,7 @@ return view.extend({
|
||||
this.rulesMap = (res && res.rules) || {};
|
||||
this.firewallBackend = (res && res.backend) || 'nft';
|
||||
/* Bounds / mktemp failures are reply.error — same idea as poll. */
|
||||
this.lastRulesError = (res && res.error) || null;
|
||||
this.noteRulesMapOutcome((res && res.error) || null);
|
||||
if (this.lastRulesError) console.warn('fwlive rules map error:', this.lastRulesError);
|
||||
this.refreshBufferedRuleLabels();
|
||||
} catch (_e) {
|
||||
@@ -712,11 +718,35 @@ return view.extend({
|
||||
this.rulesMap = {};
|
||||
this.firewallBackend = 'nft';
|
||||
}
|
||||
this.lastRulesError = 'rules_unavailable';
|
||||
this.noteRulesMapOutcome('rules_unavailable');
|
||||
}
|
||||
this.updateBackendUi();
|
||||
},
|
||||
|
||||
noteRulesMapOutcome(error) {
|
||||
this.lastRulesError = error;
|
||||
if (error !== 'rules_unavailable') {
|
||||
this.rulesRetryAttempt = 0;
|
||||
this.nextRulesRetryAt = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Back off consecutive failures, then stop after a bounded retry budget. */
|
||||
this.rulesRetryAttempt = Math.min(
|
||||
(this.rulesRetryAttempt || 0) + 1,
|
||||
RULES_RETRY_MAX_ATTEMPTS
|
||||
);
|
||||
if (this.rulesRetryAttempt >= RULES_RETRY_MAX_ATTEMPTS) {
|
||||
this.nextRulesRetryAt = 0;
|
||||
return;
|
||||
}
|
||||
const delay = Math.min(
|
||||
RULES_RETRY_BASE_MS * Math.pow(2, this.rulesRetryAttempt - 1),
|
||||
60000
|
||||
);
|
||||
this.nextRulesRetryAt = this.nowMs() + delay;
|
||||
},
|
||||
|
||||
backendDisplayLabel() {
|
||||
if (this.firewallBackend === 'nft') return _('using fw4');
|
||||
return '';
|
||||
@@ -1185,7 +1215,13 @@ return view.extend({
|
||||
/* A throw here is local UI work after a successful poll. Leave
|
||||
* lastPollError / lastPollErrorCode as applyPollReply set them. */
|
||||
}
|
||||
} else if (this.lastRulesError === 'rules_unavailable') {
|
||||
} else if (
|
||||
this.lastRulesError === 'rules_unavailable' &&
|
||||
this.rulesRetryAttempt < RULES_RETRY_MAX_ATTEMPTS &&
|
||||
!this.tablePaused &&
|
||||
!this.lastPollError &&
|
||||
this.nowMs() >= (this.nextRulesRetryAt || 0)
|
||||
) {
|
||||
await this.loadRulesMap(epoch);
|
||||
if (epoch !== this.currentPollEpoch() || this.viewDisposed) return;
|
||||
}
|
||||
@@ -2082,6 +2118,7 @@ return view.extend({
|
||||
actionRowTintClass: (action) => this.actionRowTintClass(action)
|
||||
}
|
||||
);
|
||||
if (force && !this.isTabHidden()) this.resolvePaintPending = false;
|
||||
|
||||
if (scroll) {
|
||||
if (!this.tablePaused && this.followLive) scroll.scrollTop = 0;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,15 +1,15 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1599
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1635
|
||||
msgid "%d matching · %d/%d stored"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1393
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1429
|
||||
msgid "%s · requested up to %d raw lines · %d firewall messages returned"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1383
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1419
|
||||
msgid ""
|
||||
"%s · requested up to %d raw lines · server limited fetch to %d · %d firewall "
|
||||
"messages returned"
|
||||
@@ -19,11 +19,11 @@ msgstr ""
|
||||
msgid "%s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1601
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1637
|
||||
msgid "0 matching · %d/%d stored"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:447
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:453
|
||||
msgid "Accessible (teal/orange)"
|
||||
msgstr ""
|
||||
|
||||
@@ -32,11 +32,11 @@ msgstr ""
|
||||
msgid "Action"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:915
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:945
|
||||
msgid "Administrator access is required to disable logging."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:882
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:912
|
||||
msgid "Administrator access is required to enable logging."
|
||||
msgstr ""
|
||||
|
||||
@@ -44,32 +44,32 @@ msgstr ""
|
||||
msgid "Allow/deny rules, LAN logging, or anything else."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1793
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1829
|
||||
msgid "Also seen"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:853
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:894
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:883
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:924
|
||||
msgid "Another change is staged for the firewall; apply or revert it first."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:871
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:910
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:901
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:940
|
||||
msgid ""
|
||||
"Another change overwrote WAN logging after it was saved; check the current "
|
||||
"state."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2601
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2638
|
||||
msgid "Any action"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1786
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1822
|
||||
msgid "Any protocol"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1374
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1434
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1410
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1470
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
@@ -77,7 +77,7 @@ msgstr ""
|
||||
msgid "Before you enable logging"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:850
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:880
|
||||
msgid "Cannot enable logging until kernel log modules are installed."
|
||||
msgstr ""
|
||||
|
||||
@@ -85,11 +85,11 @@ msgstr ""
|
||||
msgid "Changes:"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:446
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:452
|
||||
msgid "Classic (green/red)"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2332
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2369
|
||||
msgid "Classic uses green/red; Accessible uses teal/orange"
|
||||
msgstr ""
|
||||
|
||||
@@ -97,13 +97,13 @@ msgstr ""
|
||||
msgid "Clear all"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2673
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2710
|
||||
msgid ""
|
||||
"Click a cell to filter · ≠ on a chip to exclude · Ctrl+click a rule for "
|
||||
"firewall settings · in Simple view, click a row for the full message"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2400
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2437
|
||||
msgid ""
|
||||
"Click a row (Time or other non-link cells) to see the full log line (Simple "
|
||||
"view)."
|
||||
@@ -113,61 +113,61 @@ msgstr ""
|
||||
msgid "Click a row for the full message"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2405
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2442
|
||||
msgid ""
|
||||
"Click an IP, action, or protocol to filter; use the Protocol menu (or ≠ on a "
|
||||
"chip) to exclude."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1787
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1823
|
||||
msgid "Common"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1618
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1654
|
||||
msgid "Connection lost — retrying…"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:858
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:899
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:888
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:929
|
||||
msgid "Could not acquire the logging lock."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:866
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:905
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:896
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:935
|
||||
msgid "Could not clear the WAN zone log option."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:912
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:942
|
||||
msgid "Could not disable logging."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:873
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:903
|
||||
msgid "Could not enable logging."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:784
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:814
|
||||
msgid "Could not load logging status."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:783
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:813
|
||||
msgid "Could not refresh logging status; showing the last known state."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:868
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:907
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:898
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:937
|
||||
msgid "Could not save the firewall configuration."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:860
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:890
|
||||
msgid "Could not snapshot the current logging state."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:864
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:903
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:894
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:933
|
||||
msgid "Could not write the WAN zone log option."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2628
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2665
|
||||
msgid "Custom protocol (prefix ! to exclude). Overrides the menu when set."
|
||||
msgstr ""
|
||||
|
||||
@@ -175,11 +175,11 @@ msgstr ""
|
||||
msgid "DPort"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1236
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1272
|
||||
msgid "Degraded — sampling"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1405
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1441
|
||||
msgid "Degraded — sampling (slow poll RTT; cadence reduced)."
|
||||
msgstr ""
|
||||
|
||||
@@ -188,7 +188,7 @@ msgstr ""
|
||||
msgid "Destination"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2658
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2695
|
||||
msgid "Destination IP contains (! to exclude)"
|
||||
msgstr ""
|
||||
|
||||
@@ -196,11 +196,11 @@ msgstr ""
|
||||
msgid "Destination port"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2663
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2700
|
||||
msgid "Destination port (! to exclude)"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2516
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2553
|
||||
msgid "Detail"
|
||||
msgstr ""
|
||||
|
||||
@@ -212,15 +212,15 @@ msgstr ""
|
||||
msgid "Disabling…"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1232
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1268
|
||||
msgid "Display limited to %d rows on this device"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2273
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2310
|
||||
msgid "Display options"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2365
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2402
|
||||
msgid "Display options on the bar set Limit, row tint, palette, and hostnames."
|
||||
msgstr ""
|
||||
|
||||
@@ -244,7 +244,7 @@ msgstr ""
|
||||
msgid "Enable logging"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2361
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2398
|
||||
msgid ""
|
||||
"Enable logging turns on WAN zone drop/reject logging only (same as Network → "
|
||||
"Firewall). It does not add rules or log normal LAN browsing."
|
||||
@@ -255,7 +255,7 @@ msgstr ""
|
||||
msgid "Enabling…"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1800
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1836
|
||||
msgid "Exclude"
|
||||
msgstr ""
|
||||
|
||||
@@ -263,11 +263,11 @@ msgstr ""
|
||||
msgid "Exclude instead"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2287
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2324
|
||||
msgid "Fetch budget"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2369
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2406
|
||||
msgid ""
|
||||
"Fetch budget controls raw log lines per poll. Auto derives from Limit; "
|
||||
"Manual selects a bounded maximum."
|
||||
@@ -283,7 +283,7 @@ msgstr ""
|
||||
msgid "Filter logs by rule (hint: %s). Ctrl+click to open firewall settings."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2443
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2480
|
||||
#: openwrt-feed/luci-app-fwlive/root/usr/share/luci/menu.d/luci-app-fwlive.json:3
|
||||
msgid "Firewall Live View"
|
||||
msgstr ""
|
||||
@@ -296,7 +296,7 @@ msgstr ""
|
||||
msgid "Flow"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2379
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2416
|
||||
msgid ""
|
||||
"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."
|
||||
@@ -310,25 +310,25 @@ msgstr ""
|
||||
msgid "Grant access to firewall live log view"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2352
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2389
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1512
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1548
|
||||
msgid "Hide rows"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1567
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1603
|
||||
msgid ""
|
||||
"High event rate — table refresh is throttled to protect the browser. The "
|
||||
"buffer still updates; refresh will resume automatically."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1412
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1448
|
||||
msgid "Hostname resolve paused while the router is under load."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1791
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1827
|
||||
msgid "ICMPv6"
|
||||
msgstr ""
|
||||
|
||||
@@ -336,14 +336,14 @@ msgstr ""
|
||||
msgid "IN"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2416
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2453
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:328
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:331
|
||||
msgid ""
|
||||
"If the WAN is quiet, wait for probes or use the optional ping check in "
|
||||
"Help / the enabling-logs guide."
|
||||
@@ -357,7 +357,13 @@ msgstr ""
|
||||
msgid "Include instead"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1617
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:296
|
||||
msgid ""
|
||||
"Install kmod-nf-log and kmod-nf-log6 with the command for your OpenWrt "
|
||||
"release, then reload the firewall."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1653
|
||||
msgid "Installation is incomplete. Reinstall luci-app-fwlive."
|
||||
msgstr ""
|
||||
|
||||
@@ -366,12 +372,12 @@ msgstr ""
|
||||
msgid "Interface"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2643
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2680
|
||||
msgid "Interface (prefix ! to exclude)"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:250
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:365
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:368
|
||||
msgid "I’ll configure this under Network → Firewall"
|
||||
msgstr ""
|
||||
|
||||
@@ -379,59 +385,53 @@ msgstr ""
|
||||
msgid "Kernel log modules missing"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:296
|
||||
msgid ""
|
||||
"Kernel netfilter log modules are missing. Install kmod-nf-log and kmod-nf-"
|
||||
"log6, then reload the firewall."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:49
|
||||
msgid "Len"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2276
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2313
|
||||
msgid "Limit"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:746
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:776
|
||||
msgid "Live view may be incomplete — a legacy iptables table is registered"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:336
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:339
|
||||
msgid "Logging is off on this router"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1374
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1435
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1410
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1471
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2305
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2342
|
||||
msgid "Manual still uses server protection and poll cadence"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2374
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2411
|
||||
msgid ""
|
||||
"Manual still uses server protection and poll cadence; it changes the fetch "
|
||||
"budget, not the polling interval."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:384
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:387
|
||||
msgid "Manual test (System → Terminal):"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2299
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2336
|
||||
msgid "Maximum raw lines"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:51
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:344
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2528
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2535
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2565
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2572
|
||||
msgid "Message"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2638
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2675
|
||||
msgid "More filters"
|
||||
msgstr ""
|
||||
|
||||
@@ -448,8 +448,8 @@ msgstr ""
|
||||
msgid "No WAN zone found"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:856
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:897
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:886
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:927
|
||||
msgid "No WAN zone found; cannot toggle logging without one."
|
||||
msgstr ""
|
||||
|
||||
@@ -457,7 +457,7 @@ msgstr ""
|
||||
msgid "Not now"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:359
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:362
|
||||
msgid "Nothing changes until you click Enable."
|
||||
msgstr ""
|
||||
|
||||
@@ -465,15 +465,23 @@ msgstr ""
|
||||
msgid "OUT"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2558
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2595
|
||||
msgid "One line"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:332
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:335
|
||||
msgid "Open firewall zone settings"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:340
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:300
|
||||
msgid "OpenWrt 24.10 and older (opkg):"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:304
|
||||
msgid "OpenWrt 25.12 and newer (apk):"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:343
|
||||
msgid ""
|
||||
"OpenWrt does not write firewall events to the log until you turn logging on. "
|
||||
"Live View only shows what the firewall already logs — it does not add allow/"
|
||||
@@ -484,16 +492,16 @@ msgstr ""
|
||||
msgid "Outbound"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2326
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2363
|
||||
msgid "Palette"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1666
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2482
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1702
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2519
|
||||
msgid "Pause"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1665
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1701
|
||||
msgid "Paused"
|
||||
msgstr ""
|
||||
|
||||
@@ -502,11 +510,11 @@ msgstr ""
|
||||
msgid "Proto"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2619
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2656
|
||||
msgid "Protocol — common values"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2598
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2635
|
||||
msgid "Quick search"
|
||||
msgstr ""
|
||||
|
||||
@@ -514,22 +522,22 @@ msgstr ""
|
||||
msgid "Remove filter"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1666
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1702
|
||||
msgid "Resume"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2316
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2353
|
||||
msgid "Row tint"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2410
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2447
|
||||
msgid ""
|
||||
"Row tint shows pass/deny row backgrounds when checked. Choose Classic (green/"
|
||||
"red, default) or Accessible (teal/orange). Action text stays colored either "
|
||||
"way."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2466
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2503
|
||||
msgid ""
|
||||
"Row tint used a local color fallback because the active LuCI theme did not "
|
||||
"apply pass/deny backgrounds."
|
||||
@@ -539,15 +547,15 @@ msgstr ""
|
||||
msgid "Rule"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:737
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:767
|
||||
msgid "Rule labels incomplete — map truncated"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:740
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:770
|
||||
msgid "Rule labels unavailable"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:739
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:769
|
||||
msgid "Rule labels unavailable — temp file failed"
|
||||
msgstr ""
|
||||
|
||||
@@ -559,40 +567,40 @@ msgstr ""
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1403
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1439
|
||||
msgid "Server adaptive protection is disabled."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1401
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1437
|
||||
msgid "Server protection state is unknown."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1408
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1444
|
||||
msgid "Server shedding — at most %d log lines per poll."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1410
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1446
|
||||
msgid "Server truncated this poll (adaptive cap)."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2343
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2380
|
||||
msgid "Show hostnames"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2314
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2351
|
||||
msgid "Show pass/deny row background colors"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1512
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2581
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1548
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2618
|
||||
msgid "Show rows"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1469
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1505
|
||||
msgid "Showing a compact summary of the top of the shown sample."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2505
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2542
|
||||
msgid "Simple"
|
||||
msgstr ""
|
||||
|
||||
@@ -601,7 +609,7 @@ msgstr ""
|
||||
msgid "Source"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2648
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2685
|
||||
msgid "Source IP contains (! to exclude)"
|
||||
msgstr ""
|
||||
|
||||
@@ -609,61 +617,61 @@ msgstr ""
|
||||
msgid "Source port"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2653
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2690
|
||||
msgid "Source port (! to exclude)"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1471
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1507
|
||||
msgid "Summary data is unavailable; show rows to inspect the sample."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2572
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2609
|
||||
msgid "Summary mode"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1473
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1509
|
||||
msgid "Summary was bounded before all categories could be included."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2384
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2421
|
||||
msgid ""
|
||||
"Switching tabs pauses polling; returning performs one catch-up poll. "
|
||||
"Hostnames are off by default because lookups add work."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:862
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:901
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:892
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:931
|
||||
msgid "The firewall did not reload; saved and live logging may differ."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2394
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2431
|
||||
msgid ""
|
||||
"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."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2389
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2426
|
||||
msgid ""
|
||||
"The router log ring may evict older events before fwlive reads them. fwlive "
|
||||
"cannot recover evicted entries or change forwarding behavior."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2356
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2393
|
||||
msgid ""
|
||||
"The table updates automatically when your firewall logs traffic. Use Pause "
|
||||
"if it moves too fast."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2469
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2506
|
||||
msgid "Theme tint fallback"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:392
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:395
|
||||
msgid "Then ping the router."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:310
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:313
|
||||
msgid ""
|
||||
"This router reported a logging blocker that Live View does not recognize yet."
|
||||
msgstr ""
|
||||
@@ -672,15 +680,15 @@ msgstr ""
|
||||
msgid "Time"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1478
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1514
|
||||
msgid "Top drops"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1481
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1517
|
||||
msgid "Top rules"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1475
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1511
|
||||
msgid "Top talkers"
|
||||
msgstr ""
|
||||
|
||||
@@ -688,7 +696,7 @@ msgstr ""
|
||||
msgid "Turn it back off with the WAN logging control on the watch strip."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:354
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:357
|
||||
msgid ""
|
||||
"Turns on WAN zone drop/reject logging (same as Network → Firewall → wan → "
|
||||
"Log). Rate-limited by the zone log_limit (OpenWrt default 10/minute). Normal "
|
||||
@@ -703,32 +711,32 @@ msgstr ""
|
||||
msgid "Undo:"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2413
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2450
|
||||
msgid "Use Detail for all columns (flags, length, raw message)."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2487
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2493
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2524
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2530
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:914
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:944
|
||||
msgid "WAN drop/reject logging is off."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:321
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:324
|
||||
msgid ""
|
||||
"WAN drop/reject logging is on. Blocked inbound WAN traffic will show up "
|
||||
"here. Normal LAN browsing will not."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:878
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:908
|
||||
msgid ""
|
||||
"WAN drop/reject logging is on. Blocked inbound traffic should appear here as "
|
||||
"it happens — not normal LAN browsing."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:880
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:910
|
||||
msgid "WAN logging is already enabled."
|
||||
msgstr ""
|
||||
|
||||
@@ -741,7 +749,7 @@ msgid "WAN logging on (%s). Click to disable."
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:126
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:307
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:310
|
||||
msgid "WAN logging unavailable"
|
||||
msgstr ""
|
||||
|
||||
@@ -753,46 +761,46 @@ msgstr ""
|
||||
msgid "WAN logging unavailable: no WAN zone"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:317
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:320
|
||||
msgid "Waiting for firewall events"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1665
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2457
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1701
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2494
|
||||
msgid "Watching"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2547
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2584
|
||||
msgid "Wrap"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:71
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2603
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2640
|
||||
msgid "block"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1223
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1259
|
||||
msgid "buffer filling"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1227
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1263
|
||||
msgid "buffer full"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:133
|
||||
msgid "default 10/minute"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/chips.js:36
|
||||
msgid "contains"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:133
|
||||
msgid "default 10/minute"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/chips.js:50
|
||||
msgid "does not contain"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:72
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2604
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2641
|
||||
msgid "drop"
|
||||
msgstr ""
|
||||
|
||||
@@ -800,16 +808,16 @@ msgstr ""
|
||||
msgid "is"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1222
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1258
|
||||
msgid "loading buffer"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1597
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1633
|
||||
msgid "loading…"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1451
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1460
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1487
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1496
|
||||
msgid "none"
|
||||
msgstr ""
|
||||
|
||||
@@ -817,98 +825,98 @@ msgstr ""
|
||||
msgid "not"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1808
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1844
|
||||
msgid "not AH"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1807
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1843
|
||||
msgid "not ESP"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1806
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1842
|
||||
msgid "not GRE"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1803
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1839
|
||||
msgid "not ICMP"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1804
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1840
|
||||
msgid "not ICMPv6"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1805
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1841
|
||||
msgid "not IGMP"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1809
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1845
|
||||
msgid "not SCTP"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1801
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1837
|
||||
msgid "not TCP"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1802
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1838
|
||||
msgid "not UDP"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2609
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2646
|
||||
msgid "not block"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2608
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2645
|
||||
msgid "not drop"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2607
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2644
|
||||
msgid "not pass"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2610
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2647
|
||||
msgid "not reject"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2611
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2648
|
||||
msgid "not unknown"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2626
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2663
|
||||
msgid "or type…"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:70
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2602
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2639
|
||||
msgid "pass"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:73
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2605
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2642
|
||||
msgid "reject"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1229
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1265
|
||||
msgid "render paused (high rate)"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1238
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1274
|
||||
msgid "resolve paused (load)"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1240
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1276
|
||||
msgid "scroll frozen — scroll to top to follow live"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1237
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:1273
|
||||
msgid "truncated"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/table.js:62
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2606
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:2643
|
||||
msgid "unknown"
|
||||
msgstr ""
|
||||
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:721
|
||||
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:751
|
||||
msgid "using fw4"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.9.27
|
||||
PKG_RELEASE:=299
|
||||
PKG_RELEASE:=300
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -1779,6 +1779,9 @@ local function curl(url, file, ua, mode)
|
||||
ua = (ua == "passwall") and ("passwall/" .. api.get_version()) or ua
|
||||
curl_args[#curl_args + 1] = '--user-agent "' .. ua .. '"'
|
||||
|
||||
local cookie_file = "/tmp/cookie_" .. api.gen_random_char(5)
|
||||
curl_args[#curl_args + 1] = '-c "' .. cookie_file .. '" -b "' .. cookie_file .. '"'
|
||||
|
||||
local return_code, result
|
||||
if mode == "direct" then
|
||||
return_code, result = api.curl_base(url, file, curl_args)
|
||||
@@ -1801,6 +1804,8 @@ local function curl(url, file, ua, mode)
|
||||
header_str = header_str:gsub("\r", "")
|
||||
end
|
||||
|
||||
luci.sys.call('rm -f "%s"' % cookie_file)
|
||||
|
||||
return return_code, http_code, header_str
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
# 변경 기록
|
||||
|
||||
## [0.2.25-r1] - 2026-09-29
|
||||
|
||||
### 개선
|
||||
|
||||
- 관리자 비밀번호 변경 UI를 개선하였습니다.
|
||||
- 내부 모듈을 정리하였습니다.
|
||||
|
||||
## [0.2.24-r5] - 2026-09-29
|
||||
|
||||
### 개선
|
||||
|
||||
- 관리자 비밀번호 변경 화면에서 비밀번호 요구 사항 안내와 `새 비밀번호 확인` 입력 영역 사이의 간격을 넓혀 각 입력 단계가 더 명확하게 구분되도록 개선했습니다.
|
||||
|
||||
### 테스트
|
||||
|
||||
- 비밀번호 요구 사항 안내 다음의 확인 입력 영역에 추가 여백이 유지되는지 UI 계약 테스트를 보강했습니다.
|
||||
|
||||
## [0.2.24-r4] - 2026-09-29
|
||||
|
||||
### 수정
|
||||
|
||||
- Cloud 활동 기록 전송 설정이 없거나 잘못된 값인 경우 전송을 켜지 않고 OFF로 처리하도록 변경했습니다.
|
||||
- 과거 버전에서 Cloud 활동 기록을 암묵적으로 켜던 업그레이드 호환 코드를 제거해, 패키지 업데이트만으로 Cloud 전송이 자동 활성화되지 않도록 했습니다.
|
||||
|
||||
### 테스트
|
||||
|
||||
- 패키지 설치 과정에서 Cloud 전송을 강제로 켜는 레거시 migration이 남아 있지 않은지 검증하고, RPC·이벤트 기록·동기화·라이선스 경로 모두 명시적으로 ON인 경우에만 Cloud 기능을 사용하도록 계약 테스트를 보강했습니다.
|
||||
|
||||
## [0.2.24-r3] - 2026-09-29
|
||||
|
||||
### 수정
|
||||
|
||||
- `90-smartsafehub-system-defaults`가 펌웨어 업그레이드 때 기존 hostname, 시간대, 로그 및 NTP 사용자 설정을 다시 SmartSafeHub 기본값으로 덮어쓰지 않도록 하고, 새 설치의 OpenWrt 기본값 또는 누락된 옵션에만 기본값을 적용하도록 변경했습니다.
|
||||
- OpenWrt 표준 설정 백업에 기존 `/usr/share/smartsafehub/firmware.json`을 포함하고 `device_code`가 현재 공유기와 일치하는 경우에만 복원을 허용해 다른 모델의 설정 백업 적용을 차단했습니다. 별도의 장치 identity 파일은 추가하지 않습니다.
|
||||
- 백업에 포함된 과거 `firmware.json`은 호환성 검증에만 사용하고, 복원 직후 현재 설치된 펌웨어 metadata를 다시 복구해 `build_id`와 firmware identity가 과거 값으로 되돌아가지 않도록 했습니다.
|
||||
|
||||
### 테스트
|
||||
|
||||
- 기존 사용자 system/NTP 설정 보존, 새 설치 기본값 적용, `compat_version` 비변경을 검증하는 system-defaults 회귀 테스트를 보강했습니다.
|
||||
- 같은 `device_code` 백업 허용, 다른 장치 및 식별 metadata 없는 백업 거부, 성공/실패 복원 후 현재 `firmware.json` 보존을 검증하는 백업 회귀 테스트를 추가했습니다.
|
||||
|
||||
## [0.2.24-r2] - 2026-09-29
|
||||
|
||||
### 개선
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-smartsafehub
|
||||
PKG_VERSION:=0.2.24
|
||||
PKG_RELEASE:=4
|
||||
PKG_VERSION:=0.2.25
|
||||
PKG_RELEASE:=5
|
||||
|
||||
PKG_MAINTAINER:=Beomjun Kang <kals323@gmail.com>
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
@@ -36,15 +36,6 @@ if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
echo "luci-app-smartsafehub: firmware update required for SmartSafeHub Reset Policy v1" >&2
|
||||
exit 1
|
||||
fi
|
||||
# r18 and earlier had Cloud activity sync enabled implicitly whenever the
|
||||
# device was eligible. Preserve that behavior across the r19 upgrade even
|
||||
# if opkg replaces an otherwise-unmodified conffile with the new default-off
|
||||
# package config. Fresh installs have no existing activity-sync helper and
|
||||
# therefore keep the new explicit opt-in default.
|
||||
if [ -x /usr/libexec/smartsafehub-activity-sync ] && ! uci -q get smartsafehub.activity.cloud_sync_enabled >/dev/null 2>&1; then
|
||||
mkdir -p /tmp/smartsafehub
|
||||
: > /tmp/smartsafehub/activity-cloud-sync-upgrade-enable
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
@@ -54,10 +45,6 @@ define Package/luci-app-smartsafehub/postinst
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
mkdir -p /tmp/smartsafehub
|
||||
uci -q delete smartsafehub.firmware.check_enabled >/dev/null 2>&1 || true
|
||||
if [ -f /tmp/smartsafehub/activity-cloud-sync-upgrade-enable ]; then
|
||||
uci -q set smartsafehub.activity.cloud_sync_enabled='1' >/dev/null 2>&1 || true
|
||||
rm -f /tmp/smartsafehub/activity-cloud-sync-upgrade-enable
|
||||
fi
|
||||
uci -q commit smartsafehub >/dev/null 2>&1 || true
|
||||
if [ -x /etc/init.d/smartsafehub-events ]; then
|
||||
/etc/init.d/smartsafehub-events enable
|
||||
|
||||
@@ -32,7 +32,7 @@ SmartSafeHub는 OpenWrt 공유기를 위한 통합 홈 게이트웨이 관리 UI
|
||||
- **업데이트 관리**: SmartSafeHub 관리 소프트웨어와 펌웨어 업데이트를 확인하고 적용합니다.
|
||||
- **장치 진단**: 로컬 진단, 진단 정보 다운로드와 사용자가 동의한 원격 Health Reporter를 제공합니다.
|
||||
- **관리자 보안**: 최초 관리자 비밀번호 설정과 현재 비밀번호 확인을 거친 비밀번호 변경, 변경 후 재로그인을 제공합니다.
|
||||
- **최근 활동**: 인터넷, 보호, 업데이트, 라이선스와 진단 상태 변화를 로컬 이벤트 타임라인으로 정규화합니다.
|
||||
- **최근 활동**: 인터넷, 보호, 업데이트, 라이선스와 진단 상태 변화를 로컬 이벤트 타임라인으로 정규화하며, Cloud 활동 기록은 사용자가 명시적으로 켠 경우에만 전송합니다.
|
||||
- **IPTV (Beta)**: SK Broadband와 LG U+ 환경을 위한 IGMP Proxy/Snooping 구성을 제공합니다.
|
||||
|
||||
기능별 동작과 현재 제약은 [docs/FEATURES.md](docs/FEATURES.md)에서 확인할 수 있습니다.
|
||||
|
||||
@@ -88,6 +88,7 @@ luci-app-smartsafehub/
|
||||
│ ├── usr/libexec/smartsafehub-health
|
||||
│ ├── usr/libexec/smartsafehub-license
|
||||
│ ├── usr/libexec/smartsafehub-maintenance
|
||||
│ ├── lib/upgrade/keep.d/smartsafehub
|
||||
│ ├── usr/libexec/smartsafehub-backup
|
||||
│ ├── usr/share/luci/menu.d/
|
||||
│ ├── usr/share/rpcd/acl.d/
|
||||
|
||||
@@ -187,8 +187,8 @@ README에는 제품을 빠르게 파악하는 데 필요한 핵심 기능만 유
|
||||
- 진단 파일에 Wi-Fi 비밀번호와 SafeShield 라이선스 키를 포함하지 않음
|
||||
- 진단 파일에는 호스트명, WAN IPv4와 Wi-Fi SSID가 포함될 수 있으므로 외부 전달 전 확인 필요
|
||||
- OpenWrt 표준 `sysupgrade` 설정 백업을 SmartSafeHub에서 직접 다운로드하고, SmartSafeHub 또는 기본 LuCI에서 만든 `.tar.gz` 백업을 업로드·검증한 뒤 복원 가능
|
||||
- 복원 archive는 16MB로 제한하고 gzip/tar 구조, `/etc/config` 포함 여부와 위험한 경로를 검사하며, 업데이트나 펌웨어 작업 중에는 복원을 차단
|
||||
- 설정 복원 후 현재 펌웨어 이미지의 `firmware.json`을 기준으로 `current_build_id`를 다시 동기화하고 자동 재부팅해 이전 백업의 펌웨어 identity가 남지 않도록 처리
|
||||
- 복원 archive는 16MB로 제한하고 gzip/tar 구조, `/etc/config` 포함 여부와 위험한 경로를 검사하며, 백업에 포함된 기존 `/usr/share/smartsafehub/firmware.json`의 `device_code`가 현재 장치와 일치할 때만 복원을 허용
|
||||
- 백업의 `firmware.json`은 장치 식별에만 사용하고 복원 후에는 현재 설치된 펌웨어의 metadata를 다시 고정한 뒤 `current_build_id`를 동기화하므로 오래된 백업이 현재 firmware identity를 되돌리지 않음
|
||||
- 설정 백업에는 Wi-Fi 비밀번호, 관리자 설정, VPN 키와 라이선스 정보 등 민감한 설정이 포함될 수 있으므로 안전한 위치에 보관해야 하며, 펌웨어 이미지와 설치 패키지 자체는 포함하지 않음
|
||||
- 데스크톱의 시스템 관리 영역은 `관리자 비밀번호`와 `설정 백업 및 복원`을 1:1 두 열로 배치하고 `시스템 도구`는 그 아래 전체 폭으로 표시합니다. 모바일에서는 모든 카드를 한 열로 쌓습니다.
|
||||
- 백업/복원 카드는 절반 폭에서도 읽기 쉽도록 `현재 설정 백업`과 `설정 복원`을 compact 세로 섹션으로 표시
|
||||
@@ -202,7 +202,7 @@ README에는 제품을 빠르게 파악하는 데 필요한 핵심 기능만 유
|
||||
|
||||
설정 UI에서는 예약 재부팅을 별도 시스템 관리 카드로 분리하지 않고 `시간 및 시간대` 카드의 하위 섹션으로 표시합니다. 시간대 변경과 예약 시각의 관계를 한 화면에서 확인할 수 있고, 데스크톱에서는 주기·요일·시각을 한 행에 배치하며 모바일에서는 세로로 자연스럽게 쌓입니다.
|
||||
|
||||
설정 백업 다운로드는 LuCI의 인증된 `/cgi-bin/cgi-backup` 경로를 통해 OpenWrt `sysupgrade --create-backup` 형식을 그대로 사용합니다. 복원은 `/cgi-bin/cgi-upload`로 전용 `/tmp/smartsafehub/config-backup.tar.gz` 경로에만 업로드한 뒤 `/usr/libexec/smartsafehub-backup`이 archive 구조와 업데이트 충돌 여부를 확인하고 `sysupgrade --restore-backup`을 실행합니다. 따라서 SmartSafeHub 백업은 기본 LuCI/CLI와 상호 호환되며 별도의 독자 백업 포맷을 만들지 않습니다.
|
||||
설정 백업 다운로드는 LuCI의 인증된 `/cgi-bin/cgi-backup` 경로를 통해 OpenWrt `sysupgrade --create-backup` 형식을 그대로 사용합니다. `/lib/upgrade/keep.d/smartsafehub`는 이미 펌웨어에 존재하는 `/usr/share/smartsafehub/firmware.json`을 표준 백업에 포함해 별도의 장치 식별 파일을 만들지 않습니다. 복원은 `/cgi-bin/cgi-upload`로 전용 `/tmp/smartsafehub/config-backup.tar.gz` 경로에만 업로드한 뒤 `/usr/libexec/smartsafehub-backup`이 archive 구조와 `device_code` 일치 여부, 업데이트 충돌 여부를 확인하고 `sysupgrade --restore-backup`을 실행합니다. 백업 metadata는 검증에만 사용하며 복원 직후 현재 설치된 펌웨어의 `firmware.json`을 다시 복구합니다.
|
||||
|
||||
SmartSafeHub가 생성하는 휘발성 런타임 상태와 임시 파일은 `/tmp/smartsafehub/` 한 디렉터리에 모읍니다. 업데이트 상태와 릴리즈 노트, 펌웨어 상태·다운로드 이미지, Health 진단/Reporter 상태, 예약 재부팅 상태, Wi-Fi 변경 lock, 설정 백업 업로드 파일이 이 경로를 공유하며 `updater/`나 `firmware/` 같은 추가 하위 분류 디렉터리는 만들지 않습니다. `/tmp` 기반이므로 재부팅 시 함께 초기화되고 flash 저장 공간에는 기록하지 않습니다. 각 helper와 init script가 필요할 때 디렉터리를 다시 생성합니다.
|
||||
|
||||
@@ -247,7 +247,7 @@ Health observer의 첫 주기는 WAN/Health 현재 상태를 baseline으로만
|
||||
|
||||
공유기 웹사이트는 기존 로그인 세션과 RPC 시그니처를 그대로 유지하기 위해 인자 없는 read-only `smartsafehub.status` 응답에 최근 활동을 함께 포함해 읽고, 대시보드에는 최근 3건, `최근 활동` 전용 화면에는 최대 128건을 날짜별로 묶어 표시합니다. 대시보드의 compact 타임라인은 각 항목 사이에 작은 세로 간격을 두어 짧은 제목/설명이 연속해서 붙어 보이지 않도록 합니다. 저장된 원본에는 한국어 제목/설명을 넣지 않고 프론트엔드가 `event_type + metadata`를 렌더링합니다. 로컬 UI는 무료 장치 기능이며 **현재 부팅 이후의 휘발성 이력**을 제공합니다.
|
||||
|
||||
Pro/Ultimate 장치에서는 공유기 `최근 활동` 화면에서 **Cloud 활동 기록 전송을 사용자가 직접 ON/OFF**할 수 있습니다. 새 설치는 `smartsafehub.activity.cloud_sync_enabled=0`으로 시작하지만, r18 이하 장치에는 이 옵션이 존재하지 않았으므로 업그레이드 시 누락된 값은 기존 동작을 보존하기 위해 ON으로 해석합니다. 사용자가 OFF로 저장한 값 `0`은 명시적 opt-out으로 취급합니다.
|
||||
Pro/Ultimate 장치에서는 공유기 `최근 활동` 화면에서 **Cloud 활동 기록 전송을 사용자가 직접 ON/OFF**할 수 있습니다. `smartsafehub.activity.cloud_sync_enabled=0`이 기본값이며, Cloud 전송은 사용자가 명시적으로 ON으로 저장한 경우에만 동작합니다. 옵션이 없거나 해석할 수 없는 값이면 OFF로 처리해 설정 누락만으로 활동 기록이 서버에 전송되지 않도록 합니다.
|
||||
|
||||
Cloud 전송이 ON일 때 `/usr/libexec/smartsafehub-activity-sync`가 같은 이벤트의 Cloud outbox를 `/api/v1/activity/events`로 batch 전송합니다. Hub 1.4.86부터 `activity_history` upload credential은 artifact resolve와 분리된 `/api/v1/licenses/status`에서 발급되지만, **license status API의 단일 소유자는 `/usr/libexec/smartsafehub-license`** 입니다. `smartsafehub-license status-sync`가 기존 라이선스 reconciliation과 함께 `activity_history` token/URL을 검증해 `/tmp/smartsafehub/activity-sync-credential.json`에 원자적으로 저장하고, `smartsafehub-activity-sync`는 이 runtime credential을 소비해 upload/ack만 담당합니다. 따라서 activity sync가 license key/device fingerprint를 다시 읽거나 `/licenses/status`와 `/licenses/resolve`를 별도로 호출하지 않습니다.
|
||||
|
||||
|
||||
@@ -156,7 +156,11 @@ SmartSafeHub 펌웨어는 OpenWrt 기본 `/etc/rc.button/reset`과 제품 전용
|
||||
|
||||
펌웨어 build overlay에서 `/etc/config/system` 전체를 제공하면 OpenWrt가 장치별로 생성하는 `compat_version` 같은 메타데이터를 덮어쓸 수 있습니다. 특히 A3004T처럼 Sysupgrade metadata가 `compat_version=1.1`인 장치에서는 실행 중인 `/etc/config/system`에 값이 없을 경우 현재 버전을 1.0으로 판단해 정상 이미지도 호환성 불일치로 거부할 수 있습니다.
|
||||
|
||||
따라서 build 서버의 `overlays/common/etc/config/system`은 사용하지 않고, SmartSafeHub 패키지의 `/etc/uci-defaults/90-smartsafehub-system-defaults`가 기존 system 섹션을 유지한 채 hostname, 시간대, 로그와 NTP 옵션만 갱신합니다. 새 펌웨어를 만들기 전에 외부 build overlay의 기존 `/etc/config/system` 파일을 반드시 제거해야 합니다.
|
||||
따라서 build 서버의 `overlays/common/etc/config/system`은 사용하지 않고, SmartSafeHub 패키지의 `/etc/uci-defaults/90-smartsafehub-system-defaults`가 기존 system 섹션을 유지합니다. 새 설치의 OpenWrt 기본 hostname/UTC 값이나 누락된 옵션에만 SmartSafeHub 기본값을 채우고, 이미 사용자가 바꾼 hostname, 시간대, 로그와 NTP 설정 및 `compat_version`은 그대로 보존합니다. 새 펌웨어를 만들기 전에 외부 build overlay의 기존 `/etc/config/system` 파일을 반드시 제거해야 합니다.
|
||||
|
||||
### 설정 백업 장치 검증
|
||||
|
||||
SmartSafeHub는 별도의 `backup-device.json`을 만들지 않습니다. `/lib/upgrade/keep.d/smartsafehub`가 펌웨어에 이미 존재하는 `/usr/share/smartsafehub/firmware.json`을 OpenWrt 표준 설정 백업에 포함하고, 복원 시 해당 파일의 `device_code`와 현재 설치된 펌웨어의 `device_code`를 비교합니다. 다른 모델의 백업이나 장치 식별 metadata가 없는 기존 백업은 fail-closed로 거부합니다. `firmware.json`은 검증용 metadata일 뿐이므로 `sysupgrade --restore-backup` 후에는 복원 전에 보관해 둔 현재 펌웨어의 파일을 다시 기록하여 `build_id`가 과거 값으로 되돌아가지 않도록 합니다.
|
||||
|
||||
비밀번호 복구는 `/etc/smartsafehub/password-recovery` marker로 추적합니다. helper는 root 비밀번호만 비우고 Dropbear의 기존 enable 상태를 marker에 기록한 뒤 SSH를 중지/비활성화합니다. 재부팅 후 공개 recovery bridge는 marker가 존재하고 root 비밀번호가 비어 있을 때만 `system_root_password_status`와 `system_root_password_set` 두 RPC로 제한된 15분 ubus 세션을 발급하므로 일반 로그인 화면 없이 복구 UI로 바로 진입할 수 있습니다. 새 관리자 비밀번호 설정이 완료되면 marker를 삭제하고 이전 SSH enable 상태를 복원합니다.
|
||||
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "luci-app-smartsafehub-frontend",
|
||||
"version": "0.2.24",
|
||||
"version": "0.2.25",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "luci-app-smartsafehub-frontend",
|
||||
"version": "0.2.24",
|
||||
"version": "0.2.25",
|
||||
"dependencies": {
|
||||
"chart.js": "4.5.1",
|
||||
"preact": "10.29.8"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "luci-app-smartsafehub-frontend",
|
||||
"private": true,
|
||||
"version": "0.2.24",
|
||||
"version": "0.2.25",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=24.0"
|
||||
|
||||
@@ -85,9 +85,9 @@ export async function fetchActivityHistory(): Promise<ActivityHistory> {
|
||||
...activity,
|
||||
cloud: {
|
||||
...activity.cloud,
|
||||
// Pre-r19 RPC responses have no explicit toggle and always used Cloud
|
||||
// sync when eligible. Preserve that behavior during rolling upgrades.
|
||||
enabled: activity.cloud.enabled ?? true,
|
||||
// Cloud activity upload is explicit opt-in. Older or partial RPC
|
||||
// responses without the toggle must render as OFF rather than opt in.
|
||||
enabled: activity.cloud.enabled ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ function installBootstrap(sessionId: string, host: HTMLElement): void {
|
||||
sessionId,
|
||||
rpcUrl: luciUrl('/admin/ubus'),
|
||||
assetBase: host.dataset.assetBase ?? '/luci-static/smartsafehub/',
|
||||
assetVersion: host.dataset.assetVersion ?? '0.2.24-r2',
|
||||
assetVersion: host.dataset.assetVersion ?? '0.2.25-r1',
|
||||
locale: document.documentElement.lang || 'ko',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -912,16 +912,18 @@ function AdministratorPasswordCard(props: { onChanged: () => void }) {
|
||||
<PasswordRequirement label="숫자 포함" met={policy.number} />
|
||||
</ul>
|
||||
|
||||
<PasswordInputField
|
||||
autoComplete="new-password"
|
||||
disabled={submitting}
|
||||
id="smartsafehub-confirm-admin-password"
|
||||
label="새 비밀번호 확인"
|
||||
name="confirm-password"
|
||||
onInput={setConfirmation}
|
||||
placeholder="새 관리자 비밀번호 다시 입력"
|
||||
value={confirmation}
|
||||
/>
|
||||
<div class="pt-2">
|
||||
<PasswordInputField
|
||||
autoComplete="new-password"
|
||||
disabled={submitting}
|
||||
id="smartsafehub-confirm-admin-password"
|
||||
label="새 비밀번호 확인"
|
||||
name="confirm-password"
|
||||
onInput={setConfirmation}
|
||||
placeholder="새 관리자 비밀번호 다시 입력"
|
||||
value={confirmation}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{confirmation.length > 0 && (
|
||||
<p
|
||||
@@ -943,9 +945,11 @@ function AdministratorPasswordCard(props: { onChanged: () => void }) {
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3 text-xs leading-5 text-slate-600">
|
||||
비밀번호는 현재 공유기에 직접 적용되며 외부 서버로 전송되지 않습니다.
|
||||
변경 후 현재 로그인 세션은 종료됩니다.
|
||||
<div class="pt-2">
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3 text-xs leading-5 text-slate-600">
|
||||
비밀번호는 현재 공유기에 직접 적용되며 외부 서버로 전송되지 않습니다.
|
||||
변경 후 현재 로그인 세션은 종료됩니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs leading-5 text-amber-900">
|
||||
|
||||
@@ -1,31 +1,59 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Keep OpenWrt-generated system metadata such as compat_version intact. Only
|
||||
# apply SmartSafeHub product defaults to individual UCI options instead of
|
||||
# replacing /etc/config/system from a common firmware overlay.
|
||||
# Preserve OpenWrt-generated metadata (including compat_version) and user
|
||||
# changes across firmware upgrades. SmartSafeHub only replaces values that are
|
||||
# still unset or at the stock defaults used on a fresh OpenWrt installation.
|
||||
uci_get() {
|
||||
uci -q get "$1" 2>/dev/null || true
|
||||
}
|
||||
|
||||
set_if_missing() {
|
||||
local key="$1" value="$2"
|
||||
[ -n "$(uci_get "$key")" ] || uci -q set "$key=$value" || exit 1
|
||||
}
|
||||
|
||||
if ! uci -q get system.@system[0] >/dev/null 2>&1; then
|
||||
uci -q add system system >/dev/null || exit 1
|
||||
fi
|
||||
|
||||
uci -q set system.@system[0].hostname='SmartRouter' || exit 1
|
||||
uci -q set system.@system[0].zonename='Asia/Seoul' || exit 1
|
||||
uci -q set system.@system[0].timezone='UTC+9' || exit 1
|
||||
uci -q set system.@system[0].ttylogin='0' || exit 1
|
||||
uci -q set system.@system[0].log_size='64' || exit 1
|
||||
uci -q set system.@system[0].urandom_seed='0' || exit 1
|
||||
hostname="$(uci_get system.@system[0].hostname)"
|
||||
case "$hostname" in
|
||||
''|OpenWrt)
|
||||
uci -q set system.@system[0].hostname='SmartRouter' || exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
zonename="$(uci_get system.@system[0].zonename)"
|
||||
timezone="$(uci_get system.@system[0].timezone)"
|
||||
case "$zonename" in
|
||||
''|UTC)
|
||||
case "$timezone" in
|
||||
''|UTC|UTC0)
|
||||
uci -q set system.@system[0].zonename='Asia/Seoul' || exit 1
|
||||
uci -q set system.@system[0].timezone='UTC+9' || exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
set_if_missing system.@system[0].ttylogin '0'
|
||||
set_if_missing system.@system[0].log_size '64'
|
||||
set_if_missing system.@system[0].urandom_seed '0'
|
||||
|
||||
if ! uci -q get system.ntp >/dev/null 2>&1; then
|
||||
uci -q set system.ntp='timeserver' || exit 1
|
||||
fi
|
||||
|
||||
uci -q set system.ntp.enabled='1' || exit 1
|
||||
uci -q set system.ntp.enable_server='0' || exit 1
|
||||
uci -q delete system.ntp.server >/dev/null 2>&1 || true
|
||||
uci -q add_list system.ntp.server='0.openwrt.pool.ntp.org' || exit 1
|
||||
uci -q add_list system.ntp.server='1.openwrt.pool.ntp.org' || exit 1
|
||||
uci -q add_list system.ntp.server='2.openwrt.pool.ntp.org' || exit 1
|
||||
uci -q add_list system.ntp.server='3.openwrt.pool.ntp.org' || exit 1
|
||||
set_if_missing system.ntp.enabled '1'
|
||||
set_if_missing system.ntp.enable_server '0'
|
||||
|
||||
if [ -z "$(uci_get system.ntp.server)" ]; then
|
||||
uci -q add_list system.ntp.server='0.openwrt.pool.ntp.org' || exit 1
|
||||
uci -q add_list system.ntp.server='1.openwrt.pool.ntp.org' || exit 1
|
||||
uci -q add_list system.ntp.server='2.openwrt.pool.ntp.org' || exit 1
|
||||
uci -q add_list system.ntp.server='3.openwrt.pool.ntp.org' || exit 1
|
||||
fi
|
||||
|
||||
uci -q commit system || exit 1
|
||||
exit 0
|
||||
@@ -0,0 +1 @@
|
||||
/usr/share/smartsafehub/firmware.json
|
||||
@@ -54,9 +54,8 @@ cloud_sync_enabled() {
|
||||
local value
|
||||
value="$(uci_get cloud_sync_enabled)"
|
||||
case "$value" in
|
||||
0|false|off|no) return 1 ;;
|
||||
1|true|on|yes|'') return 0 ;;
|
||||
*) return 0 ;;
|
||||
1|true|on|yes) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,14 @@ REBOOT_DELAY_S="${SMARTSAFEHUB_BACKUP_REBOOT_DELAY_S:-2}"
|
||||
UCI_BIN="${SMARTSAFEHUB_BACKUP_UCI_BIN:-/sbin/uci}"
|
||||
JSONFILTER_BIN="${SMARTSAFEHUB_BACKUP_JSONFILTER_BIN:-/usr/bin/jsonfilter}"
|
||||
FIRMWARE_METADATA_FILE="${SMARTSAFEHUB_BACKUP_FIRMWARE_METADATA_FILE:-/usr/share/smartsafehub/firmware.json}"
|
||||
FIRMWARE_METADATA_ARCHIVE_PATH="usr/share/smartsafehub/firmware.json"
|
||||
UPDATE_STATE_FILE="${SMARTSAFEHUB_BACKUP_UPDATE_STATE_FILE:-$RUNTIME_DIR/updates.state}"
|
||||
FIRMWARE_STATE_FILE="${SMARTSAFEHUB_BACKUP_FIRMWARE_STATE_FILE:-$RUNTIME_DIR/firmware.state}"
|
||||
UPDATE_LOCK_DIR="${SMARTSAFEHUB_BACKUP_UPDATE_LOCK_DIR:-$RUNTIME_DIR/updater.lock}"
|
||||
FIRMWARE_LOCK_DIR="${SMARTSAFEHUB_BACKUP_FIRMWARE_LOCK_DIR:-$RUNTIME_DIR/firmware.lock}"
|
||||
FIRMWARE_IMAGE_FILE="${SMARTSAFEHUB_BACKUP_FIRMWARE_IMAGE_FILE:-$RUNTIME_DIR/firmware.bin}"
|
||||
BACKUP_DEVICE_MISMATCH=65
|
||||
BACKUP_DEVICE_UNVERIFIED=66
|
||||
|
||||
numeric_or_zero() {
|
||||
case "${1:-}" in
|
||||
@@ -31,6 +34,28 @@ file_size() {
|
||||
numeric_or_zero "$value"
|
||||
}
|
||||
|
||||
safe_identity_value() {
|
||||
case "${1:-}" in
|
||||
'' | *[!A-Za-z0-9._~:+-]*) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
json_value() {
|
||||
local file="$1" expression="$2"
|
||||
[ -f "$file" ] || return 0
|
||||
[ -x "$JSONFILTER_BIN" ] || return 0
|
||||
"$JSONFILTER_BIN" -i "$file" -e "$expression" 2>/dev/null || true
|
||||
}
|
||||
|
||||
current_device_code() {
|
||||
local value
|
||||
|
||||
value="$(json_value "$FIRMWARE_METADATA_FILE" '@.device_code')"
|
||||
safe_identity_value "$value" || return 1
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
archive_paths_are_safe() {
|
||||
local list_file="$1" entry normalized has_config=0
|
||||
|
||||
@@ -50,8 +75,54 @@ archive_paths_are_safe() {
|
||||
[ "$has_config" -eq 1 ]
|
||||
}
|
||||
|
||||
backup_firmware_metadata_archive_path() {
|
||||
local list_file="$1" entry normalized found='' count=0
|
||||
|
||||
while IFS= read -r entry || [ -n "$entry" ]; do
|
||||
normalized="${entry#./}"
|
||||
if [ "$normalized" = "$FIRMWARE_METADATA_ARCHIVE_PATH" ]; then
|
||||
found="$entry"
|
||||
count=$((count + 1))
|
||||
fi
|
||||
done < "$list_file"
|
||||
|
||||
[ "$count" -eq 1 ] || return 1
|
||||
printf '%s' "$found"
|
||||
}
|
||||
|
||||
validate_backup_device() {
|
||||
local list_file="$1" archive_path metadata_tmp metadata_size
|
||||
local backup_device current_device
|
||||
|
||||
archive_path="$(backup_firmware_metadata_archive_path "$list_file" 2>/dev/null || true)"
|
||||
[ -n "$archive_path" ] || return "$BACKUP_DEVICE_UNVERIFIED"
|
||||
|
||||
metadata_tmp="${BACKUP_FILE}.firmware-metadata.$$"
|
||||
rm -f "$metadata_tmp"
|
||||
if ! "$TAR_BIN" -xOzf "$BACKUP_FILE" "$archive_path" > "$metadata_tmp" 2>/dev/null; then
|
||||
rm -f "$metadata_tmp"
|
||||
return "$BACKUP_DEVICE_UNVERIFIED"
|
||||
fi
|
||||
|
||||
metadata_size="$(file_size "$metadata_tmp")"
|
||||
if [ "$metadata_size" -le 0 ] || [ "$metadata_size" -gt 4096 ]; then
|
||||
rm -f "$metadata_tmp"
|
||||
return "$BACKUP_DEVICE_UNVERIFIED"
|
||||
fi
|
||||
|
||||
backup_device="$(json_value "$metadata_tmp" '@.device_code')"
|
||||
rm -f "$metadata_tmp"
|
||||
safe_identity_value "$backup_device" || return "$BACKUP_DEVICE_UNVERIFIED"
|
||||
|
||||
current_device="$(current_device_code 2>/dev/null || true)"
|
||||
safe_identity_value "$current_device" || return "$BACKUP_DEVICE_UNVERIFIED"
|
||||
|
||||
[ "$backup_device" = "$current_device" ] || return "$BACKUP_DEVICE_MISMATCH"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_backup() {
|
||||
local size list_file
|
||||
local size list_file device_status=0
|
||||
|
||||
[ -f "$BACKUP_FILE" ] && [ ! -L "$BACKUP_FILE" ] || return 1
|
||||
size="$(file_size "$BACKUP_FILE")"
|
||||
@@ -68,7 +139,9 @@ validate_backup() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
validate_backup_device "$list_file" || device_status=$?
|
||||
rm -f "$list_file"
|
||||
[ "$device_status" -eq 0 ] || return "$device_status"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -121,7 +194,7 @@ sync_firmware_identity() {
|
||||
[ -x "$JSONFILTER_BIN" ] || return 0
|
||||
[ -x "$UCI_BIN" ] || return 0
|
||||
|
||||
build_id="$($JSONFILTER_BIN -i "$FIRMWARE_METADATA_FILE" -e '@.build_id' 2>/dev/null || true)"
|
||||
build_id="$(json_value "$FIRMWARE_METADATA_FILE" '@.build_id')"
|
||||
case "$build_id" in
|
||||
'' | *[!A-Za-z0-9._~:+-]*) return 0 ;;
|
||||
esac
|
||||
@@ -130,17 +203,54 @@ sync_firmware_identity() {
|
||||
"$UCI_BIN" -q commit smartsafehub 2>/dev/null || true
|
||||
}
|
||||
|
||||
save_installed_firmware_metadata() {
|
||||
local output_file="$1"
|
||||
[ -f "$FIRMWARE_METADATA_FILE" ] && [ ! -L "$FIRMWARE_METADATA_FILE" ] || return 1
|
||||
cp "$FIRMWARE_METADATA_FILE" "$output_file" || return 1
|
||||
[ "$(file_size "$output_file")" -gt 0 ] || return 1
|
||||
}
|
||||
|
||||
restore_installed_firmware_metadata() {
|
||||
local saved_file="$1" metadata_dir
|
||||
[ -f "$saved_file" ] || return 1
|
||||
metadata_dir="$(dirname "$FIRMWARE_METADATA_FILE")"
|
||||
mkdir -p "$metadata_dir" || return 1
|
||||
rm -f "$FIRMWARE_METADATA_FILE" || return 1
|
||||
cp "$saved_file" "$FIRMWARE_METADATA_FILE" || return 1
|
||||
chmod 0644 "$FIRMWARE_METADATA_FILE" || return 1
|
||||
}
|
||||
|
||||
perform_restore() {
|
||||
validate_backup || return 1
|
||||
local validation_status=0 saved_metadata restore_status=0
|
||||
|
||||
validate_backup || validation_status=$?
|
||||
[ "$validation_status" -eq 0 ] || return "$validation_status"
|
||||
restore_is_busy && return 75
|
||||
|
||||
if ! "$SYSUPGRADE_BIN" --restore-backup "$BACKUP_FILE" >/dev/null 2>&1; then
|
||||
# The backup contains firmware.json only as immutable source-device metadata
|
||||
# for compatibility validation. Preserve the currently installed image's
|
||||
# metadata so restoring an older backup cannot roll build_id/device metadata
|
||||
# back to the source firmware.
|
||||
saved_metadata="${RUNTIME_DIR}/firmware-metadata.before-restore.$$"
|
||||
rm -f "$saved_metadata"
|
||||
save_installed_firmware_metadata "$saved_metadata" || {
|
||||
rm -f "$saved_metadata"
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! "$SYSUPGRADE_BIN" --restore-backup "$BACKUP_FILE" >/dev/null 2>&1; then
|
||||
restore_status=1
|
||||
fi
|
||||
|
||||
# An older backup may contain the previous firmware build ID. Keep the
|
||||
# restored user configuration, but re-pin the runtime firmware identity to
|
||||
# the image that is actually installed before rebooting.
|
||||
# Even a failed restore may already have written some files. Re-pin
|
||||
# firmware.json before returning so the running image identity stays true.
|
||||
if ! restore_installed_firmware_metadata "$saved_metadata"; then
|
||||
rm -f "$saved_metadata"
|
||||
return 1
|
||||
fi
|
||||
rm -f "$saved_metadata"
|
||||
[ "$restore_status" -eq 0 ] || return "$restore_status"
|
||||
|
||||
sync_firmware_identity
|
||||
rm -f "$BACKUP_FILE"
|
||||
|
||||
|
||||
@@ -52,9 +52,8 @@ cloud_sync_enabled() {
|
||||
value="$("$UCI_BIN" -q get smartsafehub.activity.cloud_sync_enabled 2>/dev/null || true)"
|
||||
fi
|
||||
case "$value" in
|
||||
0|false|off|no) return 1 ;;
|
||||
1|true|on|yes|'') return 0 ;;
|
||||
*) return 0 ;;
|
||||
1|true|on|yes) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
@@ -69,9 +69,8 @@ activity_cloud_sync_enabled() {
|
||||
local value
|
||||
value="$("$UCI_BIN" -q get smartsafehub.activity.cloud_sync_enabled 2>/dev/null || true)"
|
||||
case "$value" in
|
||||
0|false|off|no) return 1 ;;
|
||||
1|true|on|yes|'') return 0 ;;
|
||||
*) return 0 ;;
|
||||
1|true|on|yes) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
@@ -98,13 +98,12 @@ function bool_config(value, fallback) {
|
||||
function cloud_sync_enabled() {
|
||||
const ctx = new_uci_cursor();
|
||||
if (ctx == null) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Releases before r19 had no explicit toggle and always uploaded when paid.
|
||||
// Treat a missing option as enabled for upgrade compatibility, while fresh
|
||||
// r19 installations ship cloud_sync_enabled=0.
|
||||
return bool_config(ctx.get('smartsafehub', 'activity', 'cloud_sync_enabled'), true);
|
||||
// Cloud activity upload is explicit opt-in. Missing or malformed values
|
||||
// must fail closed so local activity never starts uploading unexpectedly.
|
||||
return bool_config(ctx.get('smartsafehub', 'activity', 'cloud_sync_enabled'), false);
|
||||
}
|
||||
|
||||
function license_cloud_entitlement() {
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
const BACKUP_FILE = '/tmp/smartsafehub/config-backup.tar.gz';
|
||||
const BACKUP_HELPER = '/usr/libexec/smartsafehub-backup';
|
||||
const MAX_BACKUP_BYTES = 16777216;
|
||||
const BACKUP_DEVICE_MISMATCH = 65;
|
||||
const BACKUP_DEVICE_UNVERIFIED = 66;
|
||||
|
||||
function safe_upload_filename(value) {
|
||||
const filename = string_value(value, 'backup.tar.gz');
|
||||
@@ -21,6 +23,35 @@ function safe_upload_filename(value) {
|
||||
: 'backup.tar.gz';
|
||||
}
|
||||
|
||||
function run_backup_helper(action, timeout) {
|
||||
try {
|
||||
return system([ BACKUP_HELPER, action ], timeout);
|
||||
}
|
||||
catch (e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
function backup_validation_failure(status) {
|
||||
if (status == BACKUP_DEVICE_MISMATCH) {
|
||||
return failure(
|
||||
'SYSTEM_BACKUP_DEVICE_MISMATCH',
|
||||
'이 백업은 다른 장치에서 생성되었습니다. 현재 공유기와 같은 모델에서 만든 백업 파일만 복원할 수 있습니다.'
|
||||
);
|
||||
}
|
||||
if (status == BACKUP_DEVICE_UNVERIFIED) {
|
||||
return failure(
|
||||
'SYSTEM_BACKUP_DEVICE_UNVERIFIED',
|
||||
'이 백업에는 SmartSafeHub 장치 식별 정보가 없어 안전하게 호환성을 확인할 수 없습니다. 현재 공유기에서 새 백업을 생성해 주세요.'
|
||||
);
|
||||
}
|
||||
|
||||
return failure(
|
||||
'SYSTEM_BACKUP_INVALID',
|
||||
'올바른 OpenWrt 설정 백업 파일이 아닙니다. 파일이 손상되지 않았는지 확인해 주세요.'
|
||||
);
|
||||
}
|
||||
|
||||
export function validate_uploaded_backup(request) {
|
||||
const stat = fs.stat(BACKUP_FILE);
|
||||
if (stat == null || stat?.size <= 0) {
|
||||
@@ -31,12 +62,10 @@ export function validate_uploaded_backup(request) {
|
||||
return failure('SYSTEM_BACKUP_TOO_LARGE', '설정 백업 파일은 16MB 이하여야 합니다.');
|
||||
}
|
||||
|
||||
if (!run_command([ BACKUP_HELPER, 'validate' ], 20000)) {
|
||||
const status = run_backup_helper('validate', 20000);
|
||||
if (status != 0) {
|
||||
fs.unlink(BACKUP_FILE);
|
||||
return failure(
|
||||
'SYSTEM_BACKUP_INVALID',
|
||||
'올바른 OpenWrt 설정 백업 파일이 아닙니다. 같은 장치에서 생성한 백업인지 확인해 주세요.'
|
||||
);
|
||||
return backup_validation_failure(status);
|
||||
}
|
||||
|
||||
return success({
|
||||
@@ -51,24 +80,20 @@ export function restore_uploaded_backup(request) {
|
||||
return failure('SYSTEM_BACKUP_CONFIRM_REQUIRED', '설정 복원을 다시 확인해 주세요.');
|
||||
}
|
||||
|
||||
let status;
|
||||
try {
|
||||
status = system([ BACKUP_HELPER, 'restore' ], 30000);
|
||||
}
|
||||
catch (e) {
|
||||
status = -1;
|
||||
}
|
||||
|
||||
const status = run_backup_helper('restore', 30000);
|
||||
if (status == 75) {
|
||||
return failure(
|
||||
'SYSTEM_BACKUP_BUSY',
|
||||
'업데이트 또는 펌웨어 작업이 진행 중입니다. 작업이 끝난 뒤 다시 복원해 주세요.'
|
||||
);
|
||||
}
|
||||
if (status == BACKUP_DEVICE_MISMATCH || status == BACKUP_DEVICE_UNVERIFIED) {
|
||||
return backup_validation_failure(status);
|
||||
}
|
||||
if (status != 0) {
|
||||
return failure(
|
||||
'SYSTEM_BACKUP_RESTORE_FAILED',
|
||||
'설정 백업을 복원하지 못했습니다. 백업 파일과 장치 호환성을 확인해 주세요.'
|
||||
'설정 백업을 복원하지 못했습니다. 백업 파일이 손상되지 않았는지 확인해 주세요.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
<meta name="robots" content="noindex,nofollow,noarchive">
|
||||
<meta name="referrer" content="same-origin">
|
||||
<title>SmartSafeHub</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/luci-static/smartsafehub/favicon.svg?v=0.2.24-r2">
|
||||
<link rel="icon" type="image/svg+xml" href="/luci-static/smartsafehub/favicon.svg?v=0.2.25-r1">
|
||||
</head>
|
||||
<body style="margin:0;background:#f8fafc;min-height:100vh;">
|
||||
<div
|
||||
id="smartsafehub-entry-root"
|
||||
data-asset-base="/luci-static/smartsafehub/"
|
||||
data-asset-version="0.2.24-r2"
|
||||
data-asset-version="0.2.25-r1"
|
||||
style="display:block;width:100%;min-height:100vh;"
|
||||
></div>
|
||||
<noscript>SmartSafeHub를 사용하려면 JavaScript를 활성화해 주세요.</noscript>
|
||||
<script type="module" src="/luci-static/smartsafehub/app.js?v=0.2.24-r2"></script>
|
||||
<script type="module" src="/luci-static/smartsafehub/app.js?v=0.2.25-r1"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,3 @@
|
||||
var e,t,n,r,i,a,o,s,c,l,u,d,f,p,m={},h=[],g=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_=Array.isArray;function v(e,t){for(var n in t)e[n]=t[n];return e}function y(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function b(t,n,r){var i,a,o,s={};for(o in n)o==`key`?i=n[o]:o==`ref`?a=n[o]:s[o]=n[o];if(arguments.length>2&&(s.children=arguments.length>3?e.call(arguments,2):r),typeof t==`function`&&t.defaultProps!=null)for(o in t.defaultProps)s[o]===void 0&&(s[o]=t.defaultProps[o]);return x(t,s,i,a,null)}function x(e,r,i,a,o){var s={type:e,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++n,__i:-1,__u:0};return o==null&&t.vnode!=null&&t.vnode(s),s}function S(e){return e.children}function C(e,t){this.props=e,this.context=t}function w(e,t){if(t==null)return e.__?w(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type==`function`?w(e):null}function T(e){if(e.__P&&e.__d){var n=e.__v,r=n.__e,i=[],a=[],o=v({},n);o.__v=n.__v+1,t.vnode&&t.vnode(o),ie(e.__P,o,n,e.__n,e.__P.namespaceURI,32&n.__u?[r]:null,i,r??w(n),!!(32&n.__u),a),o.__v=n.__v,o.__.__k[o.__i]=o,oe(i,o,a),n.__e=n.__=null,o.__e!=r&&E(o)}}function E(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),E(e)}function D(e){(!e.__d&&(e.__d=!0)&&r.push(e)&&!O.__r++||i!=t.debounceRendering)&&((i=t.debounceRendering)||a)(O)}function O(){try{for(var e,t=1;r.length;)r.length>t&&r.sort(o),e=r.shift(),t=r.length,T(e)}finally{r.length=O.__r=0}}function k(e,t,n,r,i,a,o,s,c,l,u){var d,f,p,g,_,v,y=r&&r.__k||h,b=t.length;for(c=A(n,t,y,c,b),d=0;d<b;d++)(p=n.__k[d])!=null&&(f=p.__i!=-1&&y[p.__i]||m,p.__i=d,v=ie(e,p,f,i,a,o,s,c,l,u),g=p.__e,p.ref&&f.ref!=p.ref&&(f.ref&&le(f.ref,null,p),u.push(p.ref,p.__c||g,p)),_==null&&g!=null&&(_=g),4&p.__u?(c=ee(p,c,e),f.__e&&(f.__e=null)):typeof p.type==`function`&&v!==void 0?c=v:g&&(c=g.nextSibling),p.__u&=-7);return n.__e=_,c}function A(e,t,n,r,i){var a,o,s,c,l,u=n.length,d=u,f=0;for(e.__k=Array(i),a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o==`string`||typeof o==`number`||typeof o==`bigint`||o.constructor==String?o=e.__k[a]=x(null,o,null,null,null):_(o)?o=e.__k[a]=x(S,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[a]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[a]=o,c=a+f,o.__=e,o.__b=e.__b+1,s=null,(l=o.__i=te(o,n,c,d))!=-1&&(d--,(s=n[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(i>u?f--:i<u&&f++),typeof o.type!=`function`&&(o.__u|=4)):l!=c&&(l==c-1?f--:l==c+1?f++:(l>c?f--:f++,o.__u|=4))):e.__k[a]=null;if(d)for(a=0;a<u;a++)(s=n[a])!=null&&!(2&s.__u)&&(s.__e==r&&(r=w(s)),ue(s,s));return r}function ee(e,t,n){var r,i;if(typeof e.type==`function`){for(r=e.__k,i=0;r&&i<r.length;i++)r[i]&&(r[i].__=e,t=ee(r[i],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=w(e)),t=n.insertBefore(e.__e,t||null));do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function j(e,t){return t=t||[],e==null||typeof e==`boolean`||(_(e)?e.some(function(e){j(e,t)}):t.push(e)),t}function te(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l!=null&&!(2&l.__u);if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function ne(e,t,n){t[0]==`-`?e.setProperty(t,n??``):e[t]=n==null?``:typeof n!=`number`||g.test(t)?n:n+`px`}function M(e,t,n,r,i){var a,o;n:if(t==`style`){if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||ne(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||ne(e.style,t,n[t])}}else if(t[0]==`o`&&t[1]==`n`)a=t!=(t=t.replace(u,`$1`)),o=t.toLowerCase(),t=o in e||t==`onFocusOut`||t==`onFocusIn`?o.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+a]=n,n?r?n[l]=r[l]:(n[l]=d,e.addEventListener(t,a?p:f,a)):e.removeEventListener(t,a?p:f,a);else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function re(e){return function(n){if(this.l){var r=this.l[n.type+e];if(n[c]==null)n[c]=d++;else if(n[c]<r[l])return;return r(t.event?t.event(n):n)}}}function ie(e,n,r,i,a,o,s,c,l,u){var d,f,p,m,g,b,x,T,E,D,O,A,ee,j,te,ne,M=n.type;if(n.constructor!==void 0)return null;128&r.__u&&(l=!!(32&r.__u),o=[c=n.__e=r.__e]),(d=t.__b)&&d(n);n:if(typeof M==`function`){f=s.length;try{if(E=n.props,D=M.prototype&&M.prototype.render,O=(d=M.contextType)&&i[d.__c],A=d?O?O.props.value:d.__:i,r.__c?T=(p=n.__c=r.__c).__=p.__E:(D?n.__c=p=new M(E,A):(n.__c=p=new C(E,A),p.constructor=M,p.render=de),O&&O.sub(p),p.stLine truncated
|
||||
var e,t,n,r,i,a,o,s,c,l,u,d,f,p,m={},h=[],g=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_=Array.isArray;function v(e,t){for(var n in t)e[n]=t[n];return e}function y(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function b(t,n,r){var i,a,o,s={};for(o in n)o==`key`?i=n[o]:o==`ref`?a=n[o]:s[o]=n[o];if(arguments.length>2&&(s.children=arguments.length>3?e.call(arguments,2):r),typeof t==`function`&&t.defaultProps!=null)for(o in t.defaultProps)s[o]===void 0&&(s[o]=t.defaultProps[o]);return x(t,s,i,a,null)}function x(e,r,i,a,o){var s={type:e,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++n,__i:-1,__u:0};return o==null&&t.vnode!=null&&t.vnode(s),s}function S(e){return e.children}function C(e,t){this.props=e,this.context=t}function w(e,t){if(t==null)return e.__?w(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type==`function`?w(e):null}function T(e){if(e.__P&&e.__d){var n=e.__v,r=n.__e,i=[],a=[],o=v({},n);o.__v=n.__v+1,t.vnode&&t.vnode(o),ie(e.__P,o,n,e.__n,e.__P.namespaceURI,32&n.__u?[r]:null,i,r??w(n),!!(32&n.__u),a),o.__v=n.__v,o.__.__k[o.__i]=o,oe(i,o,a),n.__e=n.__=null,o.__e!=r&&E(o)}}function E(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),E(e)}function D(e){(!e.__d&&(e.__d=!0)&&r.push(e)&&!O.__r++||i!=t.debounceRendering)&&((i=t.debounceRendering)||a)(O)}function O(){try{for(var e,t=1;r.length;)r.length>t&&r.sort(o),e=r.shift(),t=r.length,T(e)}finally{r.length=O.__r=0}}function k(e,t,n,r,i,a,o,s,c,l,u){var d,f,p,g,_,v,y=r&&r.__k||h,b=t.length;for(c=A(n,t,y,c,b),d=0;d<b;d++)(p=n.__k[d])!=null&&(f=p.__i!=-1&&y[p.__i]||m,p.__i=d,v=ie(e,p,f,i,a,o,s,c,l,u),g=p.__e,p.ref&&f.ref!=p.ref&&(f.ref&&le(f.ref,null,p),u.push(p.ref,p.__c||g,p)),_==null&&g!=null&&(_=g),4&p.__u?(c=ee(p,c,e),f.__e&&(f.__e=null)):typeof p.type==`function`&&v!==void 0?c=v:g&&(c=g.nextSibling),p.__u&=-7);return n.__e=_,c}function A(e,t,n,r,i){var a,o,s,c,l,u=n.length,d=u,f=0;for(e.__k=Array(i),a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o==`string`||typeof o==`number`||typeof o==`bigint`||o.constructor==String?o=e.__k[a]=x(null,o,null,null,null):_(o)?o=e.__k[a]=x(S,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[a]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[a]=o,c=a+f,o.__=e,o.__b=e.__b+1,s=null,(l=o.__i=te(o,n,c,d))!=-1&&(d--,(s=n[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(i>u?f--:i<u&&f++),typeof o.type!=`function`&&(o.__u|=4)):l!=c&&(l==c-1?f--:l==c+1?f++:(l>c?f--:f++,o.__u|=4))):e.__k[a]=null;if(d)for(a=0;a<u;a++)(s=n[a])!=null&&!(2&s.__u)&&(s.__e==r&&(r=w(s)),ue(s,s));return r}function ee(e,t,n){var r,i;if(typeof e.type==`function`){for(r=e.__k,i=0;r&&i<r.length;i++)r[i]&&(r[i].__=e,t=ee(r[i],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=w(e)),t=n.insertBefore(e.__e,t||null));do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function j(e,t){return t=t||[],e==null||typeof e==`boolean`||(_(e)?e.some(function(e){j(e,t)}):t.push(e)),t}function te(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l!=null&&!(2&l.__u);if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function ne(e,t,n){t[0]==`-`?e.setProperty(t,n??``):e[t]=n==null?``:typeof n!=`number`||g.test(t)?n:n+`px`}function M(e,t,n,r,i){var a,o;n:if(t==`style`){if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||ne(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||ne(e.style,t,n[t])}}else if(t[0]==`o`&&t[1]==`n`)a=t!=(t=t.replace(u,`$1`)),o=t.toLowerCase(),t=o in e||t==`onFocusOut`||t==`onFocusIn`?o.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+a]=n,n?r?n[l]=r[l]:(n[l]=d,e.addEventListener(t,a?p:f,a)):e.removeEventListener(t,a?p:f,a);else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function re(e){return function(n){if(this.l){var r=this.l[n.type+e];if(n[c]==null)n[c]=d++;else if(n[c]<r[l])return;return r(t.event?t.event(n):n)}}}function ie(e,n,r,i,a,o,s,c,l,u){var d,f,p,m,g,b,x,T,E,D,O,A,ee,j,te,ne,M=n.type;if(n.constructor!==void 0)return null;128&r.__u&&(l=!!(32&r.__u),o=[c=n.__e=r.__e]),(d=t.__b)&&d(n);n:if(typeof M==`function`){f=s.length;try{if(E=n.props,D=M.prototype&&M.prototype.render,O=(d=M.contextType)&&i[d.__c],A=d?O?O.props.value:d.__:i,r.__c?T=(p=n.__c=r.__c).__=p.__E:(D?n.__c=p=new M(E,A):(n.__c=p=new C(E,A),p.constructor=M,p.render=de),O&&O.sub(p),p.stLine truncated
|
||||
`)>-1?e.split(`
|
||||
`):e}function Cm(e,t){let{element:n,datasetIndex:r,index:i}=t,a=e.getDatasetMeta(r).controller,{label:o,value:s}=a.getLabelAndValue(i);return{chart:e,label:o,parsed:a.getParsed(i),raw:e.data.datasets[r].data[i],formattedValue:s,dataset:a.getDataset(),dataIndex:i,datasetIndex:r,element:n}}function wm(e,t){let n=e.chart.ctx,{body:r,footer:i,title:a}=e,{boxWidth:o,boxHeight:s}=t,c=gl(t.bodyFont),l=gl(t.titleFont),u=gl(t.footerFont),d=a.length,f=i.length,p=r.length,m=hl(t.padding),h=m.height,g=0,_=r.reduce((e,t)=>e+t.before.length+t.lines.length+t.after.length,0);if(_+=e.beforeBody.length+e.afterBody.length,d&&(h+=d*l.lineHeight+(d-1)*t.titleSpacing+t.titleMarginBottom),_){let e=t.displayColors?Math.max(s,c.lineHeight):c.lineHeight;h+=p*e+(_-p)*c.lineHeight+(_-1)*t.bodySpacing}f&&(h+=t.footerMarginTop+f*u.lineHeight+(f-1)*t.footerSpacing);let v=0,y=function(e){g=Math.max(g,n.measureText(e).width+v)};return n.save(),n.font=l.string,Z(e.title,y),n.font=c.string,Z(e.beforeBody.concat(e.afterBody),y),v=t.displayColors?o+2+t.boxPadding:0,Z(r,e=>{Z(e.before,y),Z(e.lines,y),Z(e.after,y)}),v=0,n.font=u.string,Z(e.footer,y),n.restore(),g+=m.width,{width:g,height:h}}function Tm(e,t){let{y:n,height:r}=t;return n<r/2?`top`:n>e.height-r/2?`bottom`:`center`}function Em(e,t,n,r){let{x:i,width:a}=r,o=n.caretSize+n.caretPadding;if(e===`left`&&i+a+o>t.width||e===`right`&&i-a-o<0)return!0}function Dm(e,t,n,r){let{x:i,width:a}=n,{width:o,chartArea:{left:s,right:c}}=e,l=`center`;return r===`center`?l=i<=(s+c)/2?`left`:`right`:i<=a/2?l=`left`:i>=o-a/2&&(l=`right`),Em(l,e,t,n)&&(l=`center`),l}function Om(e,t,n){let r=n.yAlign||t.yAlign||Tm(e,n);return{xAlign:n.xAlign||t.xAlign||Dm(e,t,n,r),yAlign:r}}function km(e,t){let{x:n,width:r}=e;return t===`right`?n-=r:t===`center`&&(n-=r/2),n}function Am(e,t,n){let{y:r,height:i}=e;return t===`top`?r+=n:r-=t===`bottom`?i+n:i/2,r}function jm(e,t,n,r){let{caretSize:i,caretPadding:a,cornerRadius:o}=e,{xAlign:s,yAlign:c}=n,l=i+a,{topLeft:u,topRight:d,bottomLeft:f,bottomRight:p}=ml(o),m=km(t,s),h=Am(t,c,l);return c===`center`?s===`left`?m+=l:s===`right`&&(m-=l):s===`left`?m-=Math.max(u,f)+i:s===`right`&&(m+=Math.max(d,p)+i),{x:ic(m,0,r.width-t.width),y:ic(h,0,r.height-t.height)}}function Mm(e,t,n){let r=hl(n.padding);return t===`center`?e.x+e.width/2:t===`right`?e.x+e.width-r.right:e.x+r.left}function Nm(e){return xm([],Sm(e))}function Pm(e,t,n){return yl(e,{tooltip:t,tooltipItems:n,type:`tooltip`})}function Fm(e,t){let n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}var Im={beforeTitle:ds,title(e){if(e.length>0){let t=e[0],n=t.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode===`dataset`)return t.dataset.label||``;if(t.label)return t.label;if(r>0&&t.dataIndex<r)return n[t.dataIndex]}return``},afterTitle:ds,beforeBody:ds,beforeLabel:ds,label(e){if(this&&this.options&&this.options.mode===`dataset`)return e.label+`: `+e.formattedValue||e.formattedValue;let t=e.dataset.label||``;t&&(t+=`: `);let n=e.formattedValue;return J(n)||(t+=n),t},labelColor(e){let t=e.chart.getDatasetMeta(e.datasetIndex).controller.getStyle(e.dataIndex);return{borderColor:t.borderColor,backgroundColor:t.backgroundColor,borderWidth:t.borderWidth,borderDash:t.borderDash,borderDashOffset:t.borderDashOffset,borderRadius:0}},labelTextColor(){return this.options.bodyColor},labelPointStyle(e){let t=e.chart.getDatasetMeta(e.datasetIndex).controller.getStyle(e.dataIndex);return{pointStyle:t.pointStyle,rotation:t.rotation}},afterLabel:ds,afterBody:ds,beforeFooter:ds,footer:ds,afterFooter:ds};function Lm(e,t,n,r){let i=e[t].call(n,r);return i===void 0?Im[t].call(n,r):i}var Rm=class extends Af{constructor(e){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=e.chart,this.options=e.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(e){this.options=e,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){let e=this._cachedAnimations;if(e)return e;let t=this.chart,n=this.options.setContext(this.getContext()),r=n.enabled&&t.options.animation&&n.animations,i=new zu(this.chart,r);return r._cacheable&&(this._cachedAnimations=Object.freeze(i)),i}getContext(){return this.$context||(this.$context=Pm(this.chart.getContext(),this,this._tooltipItems))}getTitle(e,t){let{callbacks:n}=t,r=Lm(n,`beforeTitle`,this,e),i=Lm(n,`title`,this,e),a=Lm(n,`afterTitle`,this,e),o=[];return o=xm(o,Sm(r)),o=xm(o,Sm(i)),o=xm(o,Sm(a)),o}getBeforeBody(e,t){return Nm(Lm(t.callbacks,`beforeBody`,this,e))}getBody(e,t){let{callbacks:n}=t,r=[];return Z(e
Line truncated
|
||||
`):e}function Cm(e,t){let{element:n,datasetIndex:r,index:i}=t,a=e.getDatasetMeta(r).controller,{label:o,value:s}=a.getLabelAndValue(i);return{chart:e,label:o,parsed:a.getParsed(i),raw:e.data.datasets[r].data[i],formattedValue:s,dataset:a.getDataset(),dataIndex:i,datasetIndex:r,element:n}}function wm(e,t){let n=e.chart.ctx,{body:r,footer:i,title:a}=e,{boxWidth:o,boxHeight:s}=t,c=gl(t.bodyFont),l=gl(t.titleFont),u=gl(t.footerFont),d=a.length,f=i.length,p=r.length,m=hl(t.padding),h=m.height,g=0,_=r.reduce((e,t)=>e+t.before.length+t.lines.length+t.after.length,0);if(_+=e.beforeBody.length+e.afterBody.length,d&&(h+=d*l.lineHeight+(d-1)*t.titleSpacing+t.titleMarginBottom),_){let e=t.displayColors?Math.max(s,c.lineHeight):c.lineHeight;h+=p*e+(_-p)*c.lineHeight+(_-1)*t.bodySpacing}f&&(h+=t.footerMarginTop+f*u.lineHeight+(f-1)*t.footerSpacing);let v=0,y=function(e){g=Math.max(g,n.measureText(e).width+v)};return n.save(),n.font=l.string,Z(e.title,y),n.font=c.string,Z(e.beforeBody.concat(e.afterBody),y),v=t.displayColors?o+2+t.boxPadding:0,Z(r,e=>{Z(e.before,y),Z(e.lines,y),Z(e.after,y)}),v=0,n.font=u.string,Z(e.footer,y),n.restore(),g+=m.width,{width:g,height:h}}function Tm(e,t){let{y:n,height:r}=t;return n<r/2?`top`:n>e.height-r/2?`bottom`:`center`}function Em(e,t,n,r){let{x:i,width:a}=r,o=n.caretSize+n.caretPadding;if(e===`left`&&i+a+o>t.width||e===`right`&&i-a-o<0)return!0}function Dm(e,t,n,r){let{x:i,width:a}=n,{width:o,chartArea:{left:s,right:c}}=e,l=`center`;return r===`center`?l=i<=(s+c)/2?`left`:`right`:i<=a/2?l=`left`:i>=o-a/2&&(l=`right`),Em(l,e,t,n)&&(l=`center`),l}function Om(e,t,n){let r=n.yAlign||t.yAlign||Tm(e,n);return{xAlign:n.xAlign||t.xAlign||Dm(e,t,n,r),yAlign:r}}function km(e,t){let{x:n,width:r}=e;return t===`right`?n-=r:t===`center`&&(n-=r/2),n}function Am(e,t,n){let{y:r,height:i}=e;return t===`top`?r+=n:r-=t===`bottom`?i+n:i/2,r}function jm(e,t,n,r){let{caretSize:i,caretPadding:a,cornerRadius:o}=e,{xAlign:s,yAlign:c}=n,l=i+a,{topLeft:u,topRight:d,bottomLeft:f,bottomRight:p}=ml(o),m=km(t,s),h=Am(t,c,l);return c===`center`?s===`left`?m+=l:s===`right`&&(m-=l):s===`left`?m-=Math.max(u,f)+i:s===`right`&&(m+=Math.max(d,p)+i),{x:ic(m,0,r.width-t.width),y:ic(h,0,r.height-t.height)}}function Mm(e,t,n){let r=hl(n.padding);return t===`center`?e.x+e.width/2:t===`right`?e.x+e.width-r.right:e.x+r.left}function Nm(e){return xm([],Sm(e))}function Pm(e,t,n){return yl(e,{tooltip:t,tooltipItems:n,type:`tooltip`})}function Fm(e,t){let n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}var Im={beforeTitle:ds,title(e){if(e.length>0){let t=e[0],n=t.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode===`dataset`)return t.dataset.label||``;if(t.label)return t.label;if(r>0&&t.dataIndex<r)return n[t.dataIndex]}return``},afterTitle:ds,beforeBody:ds,beforeLabel:ds,label(e){if(this&&this.options&&this.options.mode===`dataset`)return e.label+`: `+e.formattedValue||e.formattedValue;let t=e.dataset.label||``;t&&(t+=`: `);let n=e.formattedValue;return J(n)||(t+=n),t},labelColor(e){let t=e.chart.getDatasetMeta(e.datasetIndex).controller.getStyle(e.dataIndex);return{borderColor:t.borderColor,backgroundColor:t.backgroundColor,borderWidth:t.borderWidth,borderDash:t.borderDash,borderDashOffset:t.borderDashOffset,borderRadius:0}},labelTextColor(){return this.options.bodyColor},labelPointStyle(e){let t=e.chart.getDatasetMeta(e.datasetIndex).controller.getStyle(e.dataIndex);return{pointStyle:t.pointStyle,rotation:t.rotation}},afterLabel:ds,afterBody:ds,beforeFooter:ds,footer:ds,afterFooter:ds};function Lm(e,t,n,r){let i=e[t].call(n,r);return i===void 0?Im[t].call(n,r):i}var Rm=class extends Af{constructor(e){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=e.chart,this.options=e.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(e){this.options=e,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){let e=this._cachedAnimations;if(e)return e;let t=this.chart,n=this.options.setContext(this.getContext()),r=n.enabled&&t.options.animation&&n.animations,i=new zu(this.chart,r);return r._cacheable&&(this._cachedAnimations=Object.freeze(i)),i}getContext(){return this.$context||(this.$context=Pm(this.chart.getContext(),this,this._tooltipItems))}getTitle(e,t){let{callbacks:n}=t,r=Lm(n,`beforeTitle`,this,e),i=Lm(n,`title`,this,e),a=Lm(n,`afterTitle`,this,e),o=[];return o=xm(o,Sm(r)),o=xm(o,Sm(i)),o=xm(o,Sm(a)),o}getBeforeBody(e,t){return Nm(Lm(t.callbacks,`beforeBody`,this,e))}getBody(e,t){let{callbacks:n}=t,r=[];return Z(e
Line truncated
|
||||
@@ -8,6 +8,7 @@ EVENTS_BIN="$ROOT_DIR/root/usr/libexec/smartsafehub-events"
|
||||
LICENSE_BIN="$ROOT_DIR/root/usr/libexec/smartsafehub-license"
|
||||
SAFE_ADAPTER="$ROOT_DIR/root/usr/share/rpcd/ucode/smartsafehub/safeshield-management.uc"
|
||||
HEALTH_BIN="$ROOT_DIR/root/usr/libexec/smartsafehub-health"
|
||||
ACTIVITY_RPC="$ROOT_DIR/root/usr/share/rpcd/ucode/smartsafehub/activity.uc"
|
||||
CONFIG="$ROOT_DIR/root/etc/config/smartsafehub"
|
||||
INIT_SYNC="$ROOT_DIR/root/etc/init.d/smartsafehub-activity-sync"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
@@ -18,7 +19,7 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
for file in "$SYNC_BIN" "$EVENTS_BIN" "$LICENSE_BIN" "$SAFE_ADAPTER" "$CONFIG" "$INIT_SYNC"; do
|
||||
for file in "$SYNC_BIN" "$EVENTS_BIN" "$LICENSE_BIN" "$SAFE_ADAPTER" "$ACTIVITY_RPC" "$CONFIG" "$INIT_SYNC"; do
|
||||
[ -f "$file" ] || fail "missing activity producer/sync component: $file"
|
||||
done
|
||||
|
||||
@@ -45,10 +46,18 @@ grep -Fq 'cloud_sync_enabled' "$SYNC_BIN" || fail 'activity sync must honor the
|
||||
grep -Fq 'apply-config' "$SYNC_BIN" || fail 'activity sync must expose a runtime apply command for immediate toggle cleanup'
|
||||
grep -Fq 'activity_cloud_sync_enabled' "$LICENSE_BIN" || fail 'license status must not retain an activity credential while Cloud transfer is disabled'
|
||||
grep -Fq "option cloud_sync_enabled '0'" "$CONFIG" || fail 'fresh installs must default Cloud activity transfer to OFF'
|
||||
grep -Fq 'activity-cloud-sync-upgrade-enable' "$ROOT_DIR/Makefile" || fail 'package upgrade must preserve pre-r19 implicit Cloud sync'
|
||||
grep -Fq "smartsafehub.activity.cloud_sync_enabled='1'" "$ROOT_DIR/Makefile" || fail 'postinst must restore Cloud sync for pre-r19 upgrades'
|
||||
if grep -Fq 'activity-cloud-sync-upgrade-enable' "$ROOT_DIR/Makefile"; then
|
||||
fail 'package install scripts must not retain the retired pre-r19 implicit Cloud-sync migration marker'
|
||||
fi
|
||||
if grep -Fq "smartsafehub.activity.cloud_sync_enabled='1'" "$ROOT_DIR/Makefile"; then
|
||||
fail 'package install scripts must not force-enable Cloud activity sync during upgrades'
|
||||
fi
|
||||
grep -Fq "set smartsafehub.activity.cloud_sync_enabled='0'" "$INIT_SYNC" || fail 'dynamically created activity sections must default Cloud activity transfer to OFF'
|
||||
grep -Fq "1|true|on|yes|'') return 0" "$SYNC_BIN" || fail 'missing pre-r19 Cloud preference must preserve legacy enabled behavior on upgrade'
|
||||
grep -Fq "1|true|on|yes) return 0" "$SYNC_BIN" || fail 'activity sync must enable Cloud upload only for explicit positive values'
|
||||
grep -Fq "1|true|on|yes) return 0" "$EVENTS_BIN" || fail 'event outbox creation must require an explicit positive Cloud preference'
|
||||
grep -Fq "1|true|on|yes) return 0" "$LICENSE_BIN" || fail 'license credential caching must require an explicit positive Cloud preference'
|
||||
grep -Fq "return bool_config(ctx.get('smartsafehub', 'activity', 'cloud_sync_enabled'), false);" "$ACTIVITY_RPC" || \
|
||||
fail 'activity RPC must interpret a missing Cloud preference as OFF'
|
||||
if grep -Fq '$base/licenses/status' "$SYNC_BIN" || grep -Fq '$base/licenses/resolve' "$SYNC_BIN" || grep -Fq 'post_json()' "$SYNC_BIN"; then
|
||||
fail 'activity sync must not duplicate license API calls; smartsafehub-license owns license status and activity credential acquisition'
|
||||
fi
|
||||
|
||||
@@ -203,8 +203,8 @@ grep -Fq 'activityHistory?: ActivityHistory;' "$API" || \
|
||||
fail 'frontend must tolerate a pre-reload status response without activityHistory'
|
||||
grep -Fq 'const activity = status.activityHistory ?? emptyActivityHistory();' "$API" || \
|
||||
fail 'frontend must render an empty activity state while an older in-memory RPC handler is draining'
|
||||
grep -Fq 'enabled: activity.cloud.enabled ?? true' "$API" || \
|
||||
fail 'pre-r19 activity responses must retain the legacy always-enabled Cloud behavior during rolling upgrades'
|
||||
grep -Fq 'enabled: activity.cloud.enabled ?? false' "$API" || \
|
||||
fail 'activity responses without an explicit Cloud toggle must fail closed to OFF'
|
||||
grep -Fq 'const ACTIVITY_REFRESH_INTERVAL_MS = 60_000;' "$HOOK" || \
|
||||
fail 'recent activity may refresh at a lightweight one-minute interval'
|
||||
grep -Fq 'refreshOnFocus: true' "$HOOK" || \
|
||||
|
||||
@@ -82,6 +82,20 @@ grep -Fq 'autoComplete="current-password"' "$SETTINGS_PAGE" || \
|
||||
fail 'current administrator password must expose password-manager autocomplete semantics'
|
||||
grep -Fq 'autoComplete="new-password"' "$SETTINGS_PAGE" || \
|
||||
fail 'new administrator password fields must expose new-password autocomplete semantics'
|
||||
awk '
|
||||
/aria-label="새 비밀번호 요구 사항"/ { in_requirements = 1 }
|
||||
in_requirements && /<div class="pt-2">/ { has_spacing = 1 }
|
||||
in_requirements && /id="smartsafehub-confirm-admin-password"/ { reached_confirmation = 1 }
|
||||
END { exit !(has_spacing && reached_confirmation) }
|
||||
' "$SETTINGS_PAGE" || \
|
||||
fail 'administrator password confirmation must keep extra spacing below the password requirement guidance'
|
||||
awk '
|
||||
/새 비밀번호 확인 값이 일치하지 않습니다/ { after_confirmation_status = 1 }
|
||||
after_confirmation_status && /<div class="pt-2">/ { has_notice_spacing = 1 }
|
||||
after_confirmation_status && /비밀번호는 현재 공유기에 직접 적용되며 외부 서버로 전송되지 않습니다/ { reached_local_notice = 1 }
|
||||
END { exit !(has_notice_spacing && reached_local_notice) }
|
||||
' "$SETTINGS_PAGE" || \
|
||||
fail 'administrator password status feedback must keep extra spacing before the local-only notice'
|
||||
grep -Fq 'await changeRootPassword(currentPassword, newPassword);' "$SETTINGS_PAGE" || \
|
||||
fail 'administrator password card must call the dedicated frontend API'
|
||||
grep -Fq 'await logoutLuciSession();' "$SETTINGS_PAGE" || \
|
||||
|
||||
@@ -4,6 +4,7 @@ set -eu
|
||||
|
||||
ROOT_DIR="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)"
|
||||
HELPER="$ROOT_DIR/root/usr/libexec/smartsafehub-backup"
|
||||
KEEP_FILE="$ROOT_DIR/root/lib/upgrade/keep.d/smartsafehub"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT INT TERM
|
||||
|
||||
@@ -14,11 +15,24 @@ fail() {
|
||||
|
||||
make_backup() {
|
||||
archive="$1"
|
||||
device_code="${2:-iptime-ax3000sm}"
|
||||
build_id="${3:-20260901T010203Z-oldbuild}"
|
||||
root="$TMP_DIR/archive"
|
||||
rm -rf "$root"
|
||||
mkdir -p "$root/etc/config" "$root/etc/dropbear"
|
||||
mkdir -p "$root/etc/config" "$root/etc/dropbear" "$root/usr/share/smartsafehub"
|
||||
printf "config system 'system'\n\toption hostname 'SmartSafeHub'\n" > "$root/etc/config/system"
|
||||
printf 'root:x:0:0:root:/root:/bin/ash\n' > "$root/etc/passwd"
|
||||
printf '{"schema":1,"device_code":"%s","build_id":"%s","channel":"stable"}\n' \
|
||||
"$device_code" "$build_id" > "$root/usr/share/smartsafehub/firmware.json"
|
||||
tar -czf "$archive" -C "$root" etc usr
|
||||
}
|
||||
|
||||
make_legacy_backup() {
|
||||
archive="$1"
|
||||
root="$TMP_DIR/legacy"
|
||||
rm -rf "$root"
|
||||
mkdir -p "$root/etc/config"
|
||||
printf "config system 'system'\n\toption hostname 'Legacy'\n" > "$root/etc/config/system"
|
||||
tar -czf "$archive" -C "$root" etc
|
||||
}
|
||||
|
||||
@@ -44,7 +58,7 @@ SYSUPGRADE_BIN="$TMP_DIR/sysupgrade"
|
||||
REBOOT_BIN="$TMP_DIR/reboot"
|
||||
UCI_BIN="$TMP_DIR/uci"
|
||||
JSONFILTER_BIN="$TMP_DIR/jsonfilter"
|
||||
FIRMWARE_METADATA_FILE="$TMP_DIR/firmware.json"
|
||||
FIRMWARE_METADATA_FILE="$TMP_DIR/installed/usr/share/smartsafehub/firmware.json"
|
||||
UPDATE_STATE_FILE="$TMP_DIR/updates.state"
|
||||
FIRMWARE_STATE_FILE="$TMP_DIR/firmware.state"
|
||||
UPDATE_LOCK_DIR="$TMP_DIR/updater.lock"
|
||||
@@ -53,10 +67,19 @@ FIRMWARE_IMAGE_FILE="$TMP_DIR/firmware.bin"
|
||||
SYSUPGRADE_LOG="$TMP_DIR/sysupgrade.log"
|
||||
REBOOT_MARKER="$TMP_DIR/rebooted"
|
||||
UCI_LOG="$TMP_DIR/uci.log"
|
||||
CURRENT_BUILD_ID='20260929T010203Z-currentbuild'
|
||||
|
||||
mkdir -p "$(dirname "$FIRMWARE_METADATA_FILE")"
|
||||
printf '{"schema":1,"device_code":"iptime-ax3000sm","build_id":"%s","channel":"stable"}\n' \
|
||||
"$CURRENT_BUILD_ID" > "$FIRMWARE_METADATA_FILE"
|
||||
|
||||
cat > "$SYSUPGRADE_BIN" <<EOF_SYSUPGRADE
|
||||
#!/bin/sh
|
||||
printf '%s\\n' "\$*" >> "$SYSUPGRADE_LOG"
|
||||
printf '%s\n' "\$*" >> "$SYSUPGRADE_LOG"
|
||||
if [ "\${1:-}" = '--restore-backup' ]; then
|
||||
tar -xOzf "\$2" usr/share/smartsafehub/firmware.json > \
|
||||
"\$SMARTSAFEHUB_BACKUP_FIRMWARE_METADATA_FILE"
|
||||
fi
|
||||
exit 0
|
||||
EOF_SYSUPGRADE
|
||||
cat > "$REBOOT_BIN" <<EOF_REBOOT
|
||||
@@ -65,43 +88,80 @@ touch "$REBOOT_MARKER"
|
||||
EOF_REBOOT
|
||||
cat > "$UCI_BIN" <<EOF_UCI
|
||||
#!/bin/sh
|
||||
printf '%s\\n' "\$*" >> "$UCI_LOG"
|
||||
printf '%s\n' "\$*" >> "$UCI_LOG"
|
||||
exit 0
|
||||
EOF_UCI
|
||||
cat > "$JSONFILTER_BIN" <<'EOF_JSONFILTER'
|
||||
#!/bin/sh
|
||||
printf '%s\n' '20260915T010203Z-abcd1234'
|
||||
file=''
|
||||
expr=''
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-i) file="$2"; shift 2 ;;
|
||||
-e) expr="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
[ -f "$file" ] || exit 0
|
||||
case "$expr" in
|
||||
'@.device_code') sed -n 's/.*"device_code"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$file" ;;
|
||||
'@.build_id') sed -n 's/.*"build_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$file" ;;
|
||||
esac
|
||||
EOF_JSONFILTER
|
||||
chmod +x "$SYSUPGRADE_BIN" "$REBOOT_BIN" "$UCI_BIN" "$JSONFILTER_BIN"
|
||||
|
||||
[ -f "$KEEP_FILE" ] || fail 'SmartSafeHub sysupgrade keep.d file must exist'
|
||||
grep -Fxq '/usr/share/smartsafehub/firmware.json' "$KEEP_FILE" || \
|
||||
fail 'standard OpenWrt backups must carry the existing SmartSafeHub firmware metadata'
|
||||
|
||||
make_backup "$BACKUP_FILE"
|
||||
run_helper validate || fail 'a normal OpenWrt-style configuration archive must validate'
|
||||
run_helper validate || fail 'a same-device OpenWrt-style configuration archive must validate'
|
||||
|
||||
make_backup "$BACKUP_FILE" 'xiaomi-ax3000t'
|
||||
set +e
|
||||
run_helper validate >/dev/null 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
[ "$status" -eq 65 ] || fail 'a backup from another device must fail with device mismatch status 65'
|
||||
|
||||
make_legacy_backup "$BACKUP_FILE"
|
||||
set +e
|
||||
run_helper validate >/dev/null 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
[ "$status" -eq 66 ] || fail 'a backup without firmware device metadata must fail closed with status 66'
|
||||
|
||||
printf 'not-a-gzip' > "$BACKUP_FILE"
|
||||
if run_helper validate >/dev/null 2>&1; then
|
||||
fail 'non-gzip input must be rejected'
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_DIR/no-config/etc"
|
||||
mkdir -p "$TMP_DIR/no-config/usr/share/smartsafehub" "$TMP_DIR/no-config/etc"
|
||||
printf '{"device_code":"iptime-ax3000sm"}\n' > "$TMP_DIR/no-config/usr/share/smartsafehub/firmware.json"
|
||||
printf 'invalid\n' > "$TMP_DIR/no-config/etc/banner"
|
||||
tar -czf "$BACKUP_FILE" -C "$TMP_DIR/no-config" etc
|
||||
tar -czf "$BACKUP_FILE" -C "$TMP_DIR/no-config" etc usr
|
||||
if run_helper validate >/dev/null 2>&1; then
|
||||
fail 'an archive without /etc/config content must be rejected'
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_DIR/empty-config/etc/config"
|
||||
tar -czf "$BACKUP_FILE" -C "$TMP_DIR/empty-config" etc
|
||||
mkdir -p "$TMP_DIR/empty-config/etc/config" "$TMP_DIR/empty-config/usr/share/smartsafehub"
|
||||
printf '{"device_code":"iptime-ax3000sm"}\n' > "$TMP_DIR/empty-config/usr/share/smartsafehub/firmware.json"
|
||||
tar -czf "$BACKUP_FILE" -C "$TMP_DIR/empty-config" etc usr
|
||||
if run_helper validate >/dev/null 2>&1; then
|
||||
fail 'an archive with only an empty /etc/config directory must be rejected'
|
||||
fi
|
||||
|
||||
make_backup "$BACKUP_FILE"
|
||||
printf '{"build_id":"20260915T010203Z-abcd1234"}\n' > "$FIRMWARE_METADATA_FILE"
|
||||
run_helper restore || fail 'validated backup must restore successfully'
|
||||
make_backup "$BACKUP_FILE" 'iptime-ax3000sm' '20260901T010203Z-oldbuild'
|
||||
run_helper restore || fail 'validated same-device backup must restore successfully'
|
||||
grep -Fxq -- "--restore-backup $BACKUP_FILE" "$SYSUPGRADE_LOG" || \
|
||||
fail 'restore must delegate to sysupgrade --restore-backup'
|
||||
[ ! -e "$BACKUP_FILE" ] || fail 'successful restore must delete the uploaded archive'
|
||||
grep -Fq 'set smartsafehub.firmware.current_build_id=20260915T010203Z-abcd1234' "$UCI_LOG" || \
|
||||
grep -Fq "\"build_id\":\"$CURRENT_BUILD_ID\"" "$FIRMWARE_METADATA_FILE" || \
|
||||
fail 'restore must keep firmware.json from the currently installed image, not the backup source image'
|
||||
if grep -Fq '20260901T010203Z-oldbuild' "$FIRMWARE_METADATA_FILE"; then
|
||||
fail 'backup source build metadata must not replace the installed firmware metadata'
|
||||
fi
|
||||
grep -Fq "set smartsafehub.firmware.current_build_id=$CURRENT_BUILD_ID" "$UCI_LOG" || \
|
||||
fail 'restore must re-sync firmware build identity from the installed image metadata'
|
||||
grep -Fq 'commit smartsafehub' "$UCI_LOG" || \
|
||||
fail 'firmware build identity re-sync must be committed'
|
||||
@@ -113,7 +173,7 @@ done
|
||||
[ -e "$REBOOT_MARKER" ] || fail 'successful restore must schedule a reboot'
|
||||
|
||||
: > "$SYSUPGRADE_LOG"
|
||||
rm -f "$REBOOT_MARKER" "$FIRMWARE_METADATA_FILE"
|
||||
rm -f "$REBOOT_MARKER"
|
||||
make_backup "$BACKUP_FILE"
|
||||
touch "$FIRMWARE_IMAGE_FILE"
|
||||
set +e
|
||||
@@ -125,8 +185,11 @@ set -e
|
||||
[ -e "$BACKUP_FILE" ] || fail 'busy restore must keep the validated archive for retry'
|
||||
rm -f "$FIRMWARE_IMAGE_FILE"
|
||||
|
||||
cat > "$SYSUPGRADE_BIN" <<'EOF_SYSUPGRADE_FAIL'
|
||||
cat > "$SYSUPGRADE_BIN" <<EOF_SYSUPGRADE_FAIL
|
||||
#!/bin/sh
|
||||
# Simulate a partially applied restore before sysupgrade reports failure.
|
||||
tar -xOzf "\$2" usr/share/smartsafehub/firmware.json > \
|
||||
"\$SMARTSAFEHUB_BACKUP_FIRMWARE_METADATA_FILE"
|
||||
exit 1
|
||||
EOF_SYSUPGRADE_FAIL
|
||||
chmod +x "$SYSUPGRADE_BIN"
|
||||
@@ -136,6 +199,8 @@ status=$?
|
||||
set -e
|
||||
[ "$status" -ne 0 ] || fail 'sysupgrade restore failure must propagate as an error'
|
||||
[ -e "$BACKUP_FILE" ] || fail 'failed restore must keep the archive for inspection or retry'
|
||||
grep -Fq "\"build_id\":\"$CURRENT_BUILD_ID\"" "$FIRMWARE_METADATA_FILE" || \
|
||||
fail 'failed restore must also restore the installed firmware metadata after a partial write'
|
||||
[ ! -e "$REBOOT_MARKER" ] || fail 'failed restore must never reboot the device'
|
||||
|
||||
run_helper discard || fail 'discard command must succeed'
|
||||
@@ -144,4 +209,4 @@ run_helper discard || fail 'discard command must succeed'
|
||||
grep -Fq '/* | .. | ../* | */.. | */../*' "$HELPER" || \
|
||||
fail 'backup validation must reject path traversal entries before restore'
|
||||
|
||||
echo 'PASS: configuration backup validation, restore safety, firmware identity sync and reboot contract are consistent'
|
||||
echo 'PASS: configuration backup validation, same-device enforcement, metadata preservation and reboot safety are consistent'
|
||||
@@ -66,6 +66,7 @@ require_executable "$ROOT_DIR/root/usr/libexec/smartsafehub-maintenance"
|
||||
require_executable "$ROOT_DIR/root/usr/libexec/smartsafehub-health"
|
||||
require_executable "$ROOT_DIR/root/usr/libexec/smartsafehub-license"
|
||||
require_executable "$ROOT_DIR/root/usr/libexec/smartsafehub-backup"
|
||||
require_file "$ROOT_DIR/root/lib/upgrade/keep.d/smartsafehub"
|
||||
require_file "$ROOT_DIR/root/usr/libexec/smartsafehub-root-entry"
|
||||
require_executable "$ROOT_DIR/root/etc/uci-defaults/90-smartsafehub-system-defaults"
|
||||
require_executable "$ROOT_DIR/root/etc/uci-defaults/99-smartsafehub-root-entry"
|
||||
|
||||
@@ -225,6 +225,16 @@ grep -Fq '"$SYSUPGRADE_BIN" --restore-backup "$BACKUP_FILE"' "$BACKUP_HELPER" ||
|
||||
fail 'configuration restore helper must use the OpenWrt sysupgrade restore path'
|
||||
grep -Fq 'restore_is_busy && return 75' "$BACKUP_HELPER" || \
|
||||
fail 'configuration restore must not run while firmware or management update work is active'
|
||||
grep -Fq 'BACKUP_DEVICE_MISMATCH=65' "$BACKUP_HELPER" || \
|
||||
fail 'configuration restore must distinguish backups created for another SmartSafeHub device'
|
||||
grep -Fq 'FIRMWARE_METADATA_ARCHIVE_PATH="usr/share/smartsafehub/firmware.json"' "$BACKUP_HELPER" || \
|
||||
fail 'configuration backup validation must reuse firmware.json as the source device identity'
|
||||
grep -Fq 'restore_installed_firmware_metadata "$saved_metadata"' "$BACKUP_HELPER" || \
|
||||
fail 'configuration restore must restore the currently installed firmware metadata after sysupgrade extraction'
|
||||
grep -Fq 'SYSTEM_BACKUP_DEVICE_MISMATCH' "$BACKUP_MODULE" || \
|
||||
fail 'configuration restore RPC must expose a same-device mismatch error'
|
||||
grep -Fq 'SYSTEM_BACKUP_DEVICE_UNVERIFIED' "$BACKUP_MODULE" || \
|
||||
fail 'configuration restore RPC must fail closed when source device identity cannot be verified'
|
||||
grep -Fq 'current_build_id="$build_id"' "$BACKUP_HELPER" || \
|
||||
fail 'configuration restore must re-sync current firmware identity after applying an older backup'
|
||||
|
||||
|
||||
@@ -13,17 +13,17 @@ fail() {
|
||||
[ -x "$DEFAULTS" ] || fail 'SmartSafeHub system defaults must be executable'
|
||||
|
||||
grep -Fq "uci -q set system.@system[0].hostname='SmartRouter'" "$DEFAULTS" || \
|
||||
fail 'system defaults must set the SmartSafeHub hostname'
|
||||
fail 'system defaults must set the SmartSafeHub hostname on fresh OpenWrt defaults'
|
||||
grep -Fq "uci -q set system.@system[0].zonename='Asia/Seoul'" "$DEFAULTS" || \
|
||||
fail 'system defaults must set the IANA timezone name'
|
||||
fail 'system defaults must set the IANA timezone name on fresh OpenWrt defaults'
|
||||
grep -Fq "uci -q set system.@system[0].timezone='UTC+9'" "$DEFAULTS" || \
|
||||
fail 'system defaults must keep the existing SmartSafeHub POSIX timezone default'
|
||||
grep -Fq "uci -q set system.@system[0].log_size='64'" "$DEFAULTS" || \
|
||||
fail 'system defaults must keep the configured log size'
|
||||
grep -Fq "set_if_missing system.@system[0].log_size '64'" "$DEFAULTS" || \
|
||||
fail 'system defaults must only fill a missing log size'
|
||||
grep -Fq "uci -q set system.ntp='timeserver'" "$DEFAULTS" || \
|
||||
fail 'system defaults must create the named NTP section when it is missing'
|
||||
grep -Fq "uci -q add_list system.ntp.server='3.openwrt.pool.ntp.org'" "$DEFAULTS" || \
|
||||
fail 'system defaults must configure the OpenWrt NTP pool'
|
||||
fail 'system defaults must configure the OpenWrt NTP pool only when no server list exists'
|
||||
grep -Fq 'uci -q commit system' "$DEFAULTS" || \
|
||||
fail 'system defaults must commit the system UCI package'
|
||||
|
||||
@@ -40,9 +40,25 @@ trap 'rm -rf "$TMP_DIR"' EXIT INT TERM
|
||||
cat > "$TMP_DIR/uci" <<'MOCK'
|
||||
#!/bin/sh
|
||||
printf '%s\n' "$*" >> "$MOCK_UCI_LOG"
|
||||
|
||||
mock_value() {
|
||||
value="$1"
|
||||
[ "$value" != '__MISSING__' ] || return 1
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
case "$*" in
|
||||
'-q get system.@system[0]') exit "${MOCK_SYSTEM_GET_RC:-0}" ;;
|
||||
'-q get system.ntp') exit "${MOCK_NTP_GET_RC:-0}" ;;
|
||||
'-q get system.@system[0].hostname') mock_value "${MOCK_HOSTNAME:-__MISSING__}" ;;
|
||||
'-q get system.@system[0].zonename') mock_value "${MOCK_ZONENAME:-__MISSING__}" ;;
|
||||
'-q get system.@system[0].timezone') mock_value "${MOCK_TIMEZONE:-__MISSING__}" ;;
|
||||
'-q get system.@system[0].ttylogin') mock_value "${MOCK_TTYLOGIN:-__MISSING__}" ;;
|
||||
'-q get system.@system[0].log_size') mock_value "${MOCK_LOG_SIZE:-__MISSING__}" ;;
|
||||
'-q get system.@system[0].urandom_seed') mock_value "${MOCK_URANDOM_SEED:-__MISSING__}" ;;
|
||||
'-q get system.ntp.enabled') mock_value "${MOCK_NTP_ENABLED:-__MISSING__}" ;;
|
||||
'-q get system.ntp.enable_server') mock_value "${MOCK_NTP_ENABLE_SERVER:-__MISSING__}" ;;
|
||||
'-q get system.ntp.server') mock_value "${MOCK_NTP_SERVERS:-__MISSING__}" ;;
|
||||
esac
|
||||
exit 0
|
||||
MOCK
|
||||
@@ -53,6 +69,15 @@ PATH="$TMP_DIR:$PATH" \
|
||||
MOCK_UCI_LOG="$LOG_EXISTING" \
|
||||
MOCK_SYSTEM_GET_RC=0 \
|
||||
MOCK_NTP_GET_RC=0 \
|
||||
MOCK_HOSTNAME='HomeGateway' \
|
||||
MOCK_ZONENAME='America/New_York' \
|
||||
MOCK_TIMEZONE='EST5EDT,M3.2.0,M11.1.0' \
|
||||
MOCK_TTYLOGIN='1' \
|
||||
MOCK_LOG_SIZE='128' \
|
||||
MOCK_URANDOM_SEED='1' \
|
||||
MOCK_NTP_ENABLED='0' \
|
||||
MOCK_NTP_ENABLE_SERVER='1' \
|
||||
MOCK_NTP_SERVERS='time.example.net' \
|
||||
/bin/sh "$DEFAULTS"
|
||||
|
||||
if grep -Fq -- '-q add system system' "$LOG_EXISTING"; then
|
||||
@@ -61,10 +86,49 @@ fi
|
||||
if grep -Fq -- '-q set system.ntp=timeserver' "$LOG_EXISTING"; then
|
||||
fail 'existing NTP section must not be recreated'
|
||||
fi
|
||||
for forbidden in \
|
||||
'-q set system.@system[0].hostname=' \
|
||||
'-q set system.@system[0].zonename=' \
|
||||
'-q set system.@system[0].timezone=' \
|
||||
'-q set system.@system[0].ttylogin=' \
|
||||
'-q set system.@system[0].log_size=' \
|
||||
'-q set system.@system[0].urandom_seed=' \
|
||||
'-q set system.ntp.enabled=' \
|
||||
'-q set system.ntp.enable_server=' \
|
||||
'-q add_list system.ntp.server='; do
|
||||
if grep -Fq -- "$forbidden" "$LOG_EXISTING"; then
|
||||
fail "existing user setting must be preserved: $forbidden"
|
||||
fi
|
||||
done
|
||||
if grep -Fq 'compat_version' "$LOG_EXISTING"; then
|
||||
fail 'runtime defaults must not mutate compat_version'
|
||||
fi
|
||||
|
||||
LOG_FRESH="$TMP_DIR/fresh.log"
|
||||
PATH="$TMP_DIR:$PATH" \
|
||||
MOCK_UCI_LOG="$LOG_FRESH" \
|
||||
MOCK_SYSTEM_GET_RC=0 \
|
||||
MOCK_NTP_GET_RC=0 \
|
||||
MOCK_HOSTNAME='OpenWrt' \
|
||||
MOCK_ZONENAME='UTC' \
|
||||
MOCK_TIMEZONE='UTC0' \
|
||||
MOCK_TTYLOGIN='__MISSING__' \
|
||||
MOCK_LOG_SIZE='__MISSING__' \
|
||||
MOCK_URANDOM_SEED='__MISSING__' \
|
||||
MOCK_NTP_ENABLED='__MISSING__' \
|
||||
MOCK_NTP_ENABLE_SERVER='__MISSING__' \
|
||||
MOCK_NTP_SERVERS='__MISSING__' \
|
||||
/bin/sh "$DEFAULTS"
|
||||
|
||||
grep -Fq -- '-q set system.@system[0].hostname=SmartRouter' "$LOG_FRESH" || \
|
||||
fail 'fresh OpenWrt hostname must receive the SmartSafeHub default'
|
||||
grep -Fq -- '-q set system.@system[0].zonename=Asia/Seoul' "$LOG_FRESH" || \
|
||||
fail 'fresh OpenWrt timezone must receive the SmartSafeHub zonename default'
|
||||
grep -Fq -- '-q set system.@system[0].timezone=UTC+9' "$LOG_FRESH" || \
|
||||
fail 'fresh OpenWrt timezone must receive the SmartSafeHub POSIX timezone default'
|
||||
grep -Fq -- '-q add_list system.ntp.server=0.openwrt.pool.ntp.org' "$LOG_FRESH" || \
|
||||
fail 'fresh OpenWrt NTP configuration must receive the default server list'
|
||||
|
||||
LOG_MISSING="$TMP_DIR/missing.log"
|
||||
PATH="$TMP_DIR:$PATH" \
|
||||
MOCK_UCI_LOG="$LOG_MISSING" \
|
||||
@@ -77,4 +141,4 @@ grep -Fq -- '-q add system system' "$LOG_MISSING" || \
|
||||
grep -Fq -- '-q set system.ntp=timeserver' "$LOG_MISSING" || \
|
||||
fail 'missing NTP section must be created defensively'
|
||||
|
||||
echo 'PASS: SmartSafeHub system defaults preserve OpenWrt compatibility metadata'
|
||||
echo 'PASS: SmartSafeHub defaults preserve OpenWrt metadata and existing user system settings'
|
||||
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=LuCI Status Pages
|
||||
LUCI_DEPENDS:=+luci-base +luci-app-oui +luci-mod-dashboard +libiwinfo +rpcd-mod-iwinfo +rpcd-mod-rrdns +ucode +ucode-mod-fs +ucode-mod-ubus +ucode-mod-socket
|
||||
|
||||
PKG_RELEASE:=22
|
||||
PKG_RELEASE:=23
|
||||
PKG_BUILD_DEPENDS:=iwinfo
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
|
||||
@@ -96,6 +96,13 @@ return baseclass.extend({
|
||||
return value != null ? [Number(value), String(value)] : [-1, '-'];
|
||||
}
|
||||
if (direction == 'total') {
|
||||
const traffic = data?.traffic?.[mac?.toUpperCase()];
|
||||
if (traffic?.upload != null && traffic?.download != null) {
|
||||
const upload = Number(traffic.upload), download = Number(traffic.download);
|
||||
return [upload + download, upload || download
|
||||
? '%1024.2mB'.format(upload) + ' / ' + '%1024.2mB'.format(download)
|
||||
: '%1024.2mB'.format(0)];
|
||||
}
|
||||
const value = data?.totals?.[mac?.toUpperCase()];
|
||||
return value != null ? [Number(value), '%1024.2mB'.format(value)] : [-1, '-'];
|
||||
}
|
||||
@@ -422,7 +429,7 @@ return baseclass.extend({
|
||||
E('th', { 'class': 'th' }, _('MAC address')),
|
||||
E('th', { 'class': 'th' }, _('Upload')),
|
||||
E('th', { 'class': 'th' }, _('Download')),
|
||||
E('th', { 'class': 'th', 'data-total-traffic': '1' }, _('Total traffic')),
|
||||
E('th', { 'class': 'th', 'data-total-traffic': '1' }, _('Total traffic') + ' (↑ / ↓)'),
|
||||
E('th', { 'class': 'th' }, _('Connection count')),
|
||||
isReadonlyView ? E([]) : E('th', { 'class': 'th cbi-section-actions center' }, _('Static Lease'))
|
||||
])
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=rpcd-mod-luci
|
||||
PKG_VERSION:=20240305
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
PKG_MAINTAINER:=Jo-Philipp Wich <jo@mein.io>
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
@@ -747,6 +747,19 @@ static int get_rates(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
}
|
||||
blobmsg_close_table(&result, totals);
|
||||
|
||||
void *traffic = blobmsg_open_table(&result, "traffic");
|
||||
avl_for_each_element(&hosts, host, avl) {
|
||||
if (!host->available && !sampled) continue;
|
||||
char mac[18];
|
||||
snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
host->mac[0], host->mac[1], host->mac[2], host->mac[3], host->mac[4], host->mac[5]);
|
||||
void *entry = blobmsg_open_table(&result, mac);
|
||||
blobmsg_add_u64(&result, "upload", host->total[0]);
|
||||
blobmsg_add_u64(&result, "download", host->total[1]);
|
||||
blobmsg_close_table(&result, entry);
|
||||
}
|
||||
blobmsg_close_table(&result, traffic);
|
||||
|
||||
void *connections = blobmsg_open_table(&result, "connections");
|
||||
if (sampled && !failure && now - sampled < 3 * SAMPLE_MS) {
|
||||
avl_for_each_element(&hosts, host, avl) {
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
`luci.client-rates get` accepts up to 1024 IP addresses and returns cached upload
|
||||
and download bytes/second for each address, plus cumulative upload + download
|
||||
bytes keyed by MAC in `totals`. It reads `NETLINK_NETFILTER` connection
|
||||
bytes keyed by MAC in `totals`. The `traffic` table also exposes cumulative
|
||||
`upload` and `download` bytes per MAC, each combining IPv4 and IPv6. The homepage
|
||||
displays upload / download and sorts by their sum. It reads `NETLINK_NETFILTER` connection
|
||||
counters; it never installs packet-filter rules or resets kernel counters.
|
||||
|
||||
On QSDK, ECM's NSS/SFE/PPE statistics synchronization updates these conntrack
|
||||
|
||||
@@ -33,6 +33,13 @@ assert.equal(view.rateValue(view.clientAddresses(lease,hints),totals,'total',mac
|
||||
assert.equal(view.rateValue(['192.168.0.2'],totals,'total',mac)[0],123456);
|
||||
assert.equal(view.rateValue([],totals,'total',mac.toLowerCase())[0],123456);
|
||||
assert.equal(view.rateValue([],{},'total',mac)[1],'-');
|
||||
const split = {traffic:{[mac]:{upload:1200,download:12200}}};
|
||||
assert.deepEqual(view.rateValue([], split, 'total', mac.toLowerCase()),
|
||||
[13400, '%1024.2mB'.format(1200) + ' / ' + '%1024.2mB'.format(12200)]);
|
||||
assert.equal(view.rateValue([], {traffic:{[mac]:{upload:0,download:0}}}, 'total', mac)[0], 0);
|
||||
assert.equal(view.rateValue([], {traffic:{[mac]:{upload:0,download:50}}}, 'total', mac)[1],
|
||||
'%1024.2mB'.format(0) + ' / ' + '%1024.2mB'.format(50));
|
||||
|
||||
assert.equal(view.rateValue([], {totals:{[mac]:0}}, 'total',mac)[0],0);
|
||||
assert.deepEqual(view.rateValue([], {connections:{[mac]:12}}, 'connections', mac), [12,'12']);
|
||||
assert.deepEqual(view.rateValue([], {connections:{[mac]:0}}, 'connections', mac), [0,'0']);
|
||||
|
||||
Reference in new issue
Block a user