diff --git a/management-ui/src/pages/Alerts/index.tsx b/management-ui/src/pages/Alerts/index.tsx
index d82232c..bf57949 100644
--- a/management-ui/src/pages/Alerts/index.tsx
+++ b/management-ui/src/pages/Alerts/index.tsx
@@ -438,7 +438,7 @@ export default function AlertsPage() {
onChange: (keys) => setSelectedIds(keys as number[]),
}}
rowClassName={(r) => (r.acknowledged_at ? 'eg-row-muted' : '')}
- pagination={{ pageSize: 25 }}
+ pagination={{ defaultPageSize: 25 }}
locale={{ emptyText: (
}
diff --git a/management-ui/src/pages/Backups/History.tsx b/management-ui/src/pages/Backups/History.tsx
index 0396bbb..9942f43 100644
--- a/management-ui/src/pages/Backups/History.tsx
+++ b/management-ui/src/pages/Backups/History.tsx
@@ -350,7 +350,7 @@ export default function HistoryTab() {
loading={list.isFetching}
dataSource={list.data ?? []}
columns={columns}
- pagination={{ pageSize: 25, showSizeChanger: true, pageSizeOptions: [25, 50, 100] }}
+ pagination={{ defaultPageSize: 25, showSizeChanger: true, pageSizeOptions: [25, 50, 100] }}
locale={{ emptyText: t('backups.empty') }}
/>
diff --git a/management-ui/src/pages/CrowdSec/index.tsx b/management-ui/src/pages/CrowdSec/index.tsx
index 6775483..3042f2e 100644
--- a/management-ui/src/pages/CrowdSec/index.tsx
+++ b/management-ui/src/pages/CrowdSec/index.tsx
@@ -225,7 +225,7 @@ function DecisionsTab() {
loading={isLoading}
dataSource={decisions ?? []}
columns={columns}
- pagination={{ pageSize: 20 }}
+ pagination={{ defaultPageSize: 20 }}
/>
)
@@ -379,7 +379,7 @@ function BouncersTab() {
loading={isLoading}
dataSource={bouncers ?? []}
columns={columns}
- pagination={{ pageSize: 20 }}
+ pagination={{ defaultPageSize: 20 }}
/>
)
@@ -436,7 +436,7 @@ function MachinesTab() {
loading={isLoading}
dataSource={machines ?? []}
columns={columns}
- pagination={{ pageSize: 20 }}
+ pagination={{ defaultPageSize: 20 }}
/>
)
@@ -515,7 +515,7 @@ function CollectionsTab() {
loading={isLoading}
dataSource={collections ?? []}
columns={columns}
- pagination={{ pageSize: 50 }}
+ pagination={{ defaultPageSize: 50 }}
/>
)
diff --git a/management-ui/src/pages/FirewallLive/index.tsx b/management-ui/src/pages/FirewallLive/index.tsx
index 46083f4..4a86e6a 100644
--- a/management-ui/src/pages/FirewallLive/index.tsx
+++ b/management-ui/src/pages/FirewallLive/index.tsx
@@ -129,6 +129,12 @@ export default function FirewallLivePage() {
pausedRef.current = paused
const pendingDuringPauseRef = useRef([])
const wsRef = useRef(null)
+ // tRef statt `t` in den WS-Effect-Deps: `t` wechselt bei jedem
+ // i18n-Store-Update die Identität. Stünde es in den Deps, würde der
+ // Effect neu laufen — inkl. `setEntries([])` und WS-Reconnect, d. h.
+ // der Live-Puffer wäre ohne erkennbaren Grund plötzlich leer.
+ const tRef = useRef(t)
+ useEffect(() => { tRef.current = t }, [t])
useEffect(() => {
const t = setTimeout(() => setAppliedFilters(filters), 300)
@@ -187,7 +193,7 @@ export default function FirewallLivePage() {
if (!cancelled && active) scheduleReconnect()
}
ws.onerror = () => {
- setError(t('fwlog.connError'))
+ setError(tRef.current('fwlog.connError'))
}
}
@@ -204,7 +210,7 @@ export default function FirewallLivePage() {
if (reconnectTimer) clearTimeout(reconnectTimer)
if (wsRef.current) { wsRef.current.close(); wsRef.current = null }
}
- }, [active, query, t])
+ }, [active, query])
// Resume: gebufferte Events in die Tabelle mergen.
useEffect(() => {
@@ -412,7 +418,7 @@ export default function FirewallLivePage() {
size="small"
dataSource={[...entries].reverse()}
columns={columns}
- pagination={{ pageSize: 50, showSizeChanger: true, pageSizeOptions: [25, 50, 100, 200] }}
+ pagination={{ defaultPageSize: 50, showSizeChanger: true, pageSizeOptions: [25, 50, 100, 200] }}
locale={{ emptyText: connected ? t('fwlog.empty') : t('fwlog.connecting') }}
/>
>
diff --git a/management-ui/src/pages/Logs/index.tsx b/management-ui/src/pages/Logs/index.tsx
index 82a0fac..19fb0d9 100644
--- a/management-ui/src/pages/Logs/index.tsx
+++ b/management-ui/src/pages/Logs/index.tsx
@@ -293,7 +293,7 @@ export default function LogsPage() {
loading={logsQuery.isFetching}
dataSource={entries}
columns={columns}
- pagination={{ pageSize: 50, showSizeChanger: true, pageSizeOptions: [25, 50, 100, 200] }}
+ pagination={{ defaultPageSize: 50, showSizeChanger: true, pageSizeOptions: [25, 50, 100, 200] }}
/>
{entries.length === 0 && !logsQuery.isFetching && (
diff --git a/management-ui/src/pages/Networks/Routes.tsx b/management-ui/src/pages/Networks/Routes.tsx
index 5a028a4..b1959bf 100644
--- a/management-ui/src/pages/Networks/Routes.tsx
+++ b/management-ui/src/pages/Networks/Routes.tsx
@@ -228,7 +228,7 @@ export default function RoutesTab() {
size="small"
dataSource={live.data ?? []}
columns={liveColumns}
- pagination={{ pageSize: 25, showSizeChanger: true, pageSizeOptions: [25, 50, 100] }}
+ pagination={{ defaultPageSize: 25, showSizeChanger: true, pageSizeOptions: [25, 50, 100] }}
locale={{ emptyText: t('routes.liveEmpty') }}
style={{ marginTop: 12 }}
/>
diff --git a/packaging/debian/edgeguard-api/DEBIAN/postinst b/packaging/debian/edgeguard-api/DEBIAN/postinst
index 1b88419..dabef4a 100755
--- a/packaging/debian/edgeguard-api/DEBIAN/postinst
+++ b/packaging/debian/edgeguard-api/DEBIAN/postinst
@@ -410,9 +410,25 @@ SYSCTL
if [ ! -f /var/log/edgeguard/firewall.jsonl ]; then
: > /var/log/edgeguard/firewall.jsonl
fi
- # ulogd2 läuft als root (eigener Daemon); File muss von ihm
- # schreibbar UND von edgeguard-API lesbar sein.
- chown root:"$EG_USER" /var/log/edgeguard/firewall.jsonl
+ # WICHTIG (Incident 2026-09-06): ulogd2 läuft NICHT als root —
+ # die Debian-Unit startet `ulogd --daemon --uid ulog`, der Daemon
+ # dropped also nach dem Öffnen seiner Files auf den User `ulog`.
+ # Solange er läuft schreibt er über den offenen fd weiter, egal
+ # wem die Datei gehört. Aber: das Distro-Profil
+ # /etc/logrotate.d/ulogd2 schickt nachts SIGHUP an ulogd, damit
+ # es seine Logfiles neu öffnet — und DAS passiert als `ulog`.
+ # Gehörte die Datei root:edgeguard 0640, scheitert das Reopen mit
+ # "can't open JSON log file: Permission denied", ulogd wertet das
+ # als fatal und BEENDET sich. Ohne Restart= blieb der Dienst dann
+ # tagelang still tot (5 Tage unbemerkt) → Firewall-Live-Log zeigte
+ # nur noch den alten In-Memory-Ring der API, nie neue Events.
+ # Daher: Owner = ulog (Schreiber), Gruppe = edgeguard (Leser).
+ if getent passwd ulog >/dev/null 2>&1; then
+ chown ulog:"$EG_USER" /var/log/edgeguard/firewall.jsonl
+ else
+ # ulogd2 (noch) nicht installiert — Fallback wie bisher.
+ chown root:"$EG_USER" /var/log/edgeguard/firewall.jsonl
+ fi
chmod 0640 /var/log/edgeguard/firewall.jsonl
cat > /etc/ulogd.conf <<'ULOGD'
@@ -460,7 +476,9 @@ ULOGD
compress
delaycompress
copytruncate
- create 0640 root edgeguard
+ # Owner muss ulog sein — siehe Kommentar oben: ulogd re-öffnet die
+ # Datei bei SIGHUP als `ulog` und stirbt sonst an EACCES.
+ create 0640 ulog edgeguard
}
LOGROTATE
chmod 0644 /etc/logrotate.d/edgeguard-firewall
@@ -469,6 +487,18 @@ LOGROTATE
# da ist (Dependency-Konflikt o.ä.), nur warnen — die Firewall
# läuft auch ohne Logger.
if systemctl list-unit-files ulogd2.service >/dev/null 2>&1; then
+ # Restart=always als Selbstheilung: ulogd beendet sich bei
+ # einem fehlgeschlagenen Reopen mit Exit-Code 0 (also KEIN
+ # Restart=on-failure — das würde nicht greifen). Ohne das
+ # bleibt Firewall-Logging nach einem Rotate-Hiccup still tot.
+ install -d -m 0755 /etc/systemd/system/ulogd2.service.d
+ cat > /etc/systemd/system/ulogd2.service.d/edgeguard-restart.conf <<'ULOGDUNIT'
+# Managed by edgeguard — re-installation overwrites this file.
+[Service]
+Restart=always
+RestartSec=5s
+ULOGDUNIT
+ systemctl daemon-reload
systemctl enable ulogd2.service >/dev/null 2>&1 || true
systemctl restart ulogd2.service || \
echo "postinst: ulogd2.service restart failed (firewall logs disabled until fixed)" >&2