feat: stabilize WebSocket connections by using memoized filter objects in LiveLogViewer

This commit is contained in:
GitHub Actions
2025-12-16 06:10:34 +00:00
parent 67d671bc0c
commit 90ee8c7f83
3 changed files with 412 additions and 168 deletions
+6 -2
View File
@@ -134,9 +134,13 @@ const getLevelColor = (level: string): string => {
return 'text-gray-300';
};
// Stable default filter objects to prevent useEffect re-triggers on parent re-render
const EMPTY_LIVE_FILTER: LiveLogFilter = {};
const EMPTY_SECURITY_FILTER: SecurityLogFilter = {};
export function LiveLogViewer({
filters = {},
securityFilters = {},
filters = EMPTY_LIVE_FILTER,
securityFilters = EMPTY_SECURITY_FILTER,
mode = 'security',
maxLogs = 500,
className = '',
+7 -5
View File
@@ -1,5 +1,5 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useState, useEffect } from 'react'
import { useState, useEffect, useMemo } from 'react'
import { useNavigate, Outlet } from 'react-router-dom'
import { Shield, ShieldAlert, ShieldCheck, Lock, Activity, ExternalLink } from 'lucide-react'
import { getSecurityStatus, type SecurityStatus } from '../api/security'
@@ -32,6 +32,8 @@ export default function Security() {
const generateBreakGlassMutation = useGenerateBreakGlassToken()
const queryClient = useQueryClient()
const [crowdsecStatus, setCrowdsecStatus] = useState<{ running: boolean; pid?: number } | null>(null)
// Stable reference to prevent WebSocket reconnection loops in LiveLogViewer
const emptySecurityFilters = useMemo(() => ({}), [])
// Generic toggle mutation for per-service settings
const toggleServiceMutation = useMutation({
mutationFn: async ({ key, enabled }: { key: string; enabled: boolean }) => {
@@ -266,7 +268,7 @@ export default function Security() {
</div>
<div>
<div className="text-2xl font-bold mb-1 text-white">
{(crowdsecStatus?.running ?? status.crowdsec.enabled) ? 'Active' : 'Disabled'}
{(crowdsecStatus?.running ?? status.crowdsec.enabled) ? 'Active' : 'Disabled'}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400">
{(crowdsecStatus?.running ?? status.crowdsec.enabled)
@@ -307,7 +309,7 @@ export default function Security() {
</div>
<div>
<div className="text-2xl font-bold mb-1 text-white">
{status.acl.enabled ? 'Active' : 'Disabled'}
{status.acl.enabled ? 'Active' : 'Disabled'}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400">
Protects against: Unauthorized IPs, geo-based attacks, insider threats
@@ -349,7 +351,7 @@ export default function Security() {
</div>
<div>
<div className="text-2xl font-bold mb-1 text-white">
{status.waf.enabled ? 'Active' : 'Disabled'}
{status.waf.enabled ? 'Active' : 'Disabled'}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400">
{status.waf.enabled
@@ -416,7 +418,7 @@ export default function Security() {
{/* Live Activity Section */}
{status.cerberus?.enabled && (
<div className="mt-6">
<LiveLogViewer mode="security" securityFilters={{}} className="w-full" />
<LiveLogViewer mode="security" securityFilters={emptySecurityFilters} className="w-full" />
</div>
)}