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