From 480492454f593cd6c73c588e7bb66bb917d2fefd Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 24 Jan 2026 22:51:49 +0000 Subject: [PATCH] Fix patrol stats, UI sync, and trigger dedupe --- frontend-modern/src/pages/AIIntelligence.tsx | 17 ++++-- internal/ai/patrol.go | 57 +++++++++++++++----- internal/ai/patrol_triggers.go | 12 ++++- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/frontend-modern/src/pages/AIIntelligence.tsx b/frontend-modern/src/pages/AIIntelligence.tsx index e554b6e12..9922c62cb 100644 --- a/frontend-modern/src/pages/AIIntelligence.tsx +++ b/frontend-modern/src/pages/AIIntelligence.tsx @@ -148,11 +148,21 @@ export function AIIntelligence() { setIsTogglingPatrol(true); const newValue = !patrolEnabledLocal(); try { - await apiFetchJSON('/api/settings/ai', { + const data = await apiFetchJSON('/api/settings/ai', { method: 'PUT', body: JSON.stringify({ patrol_enabled: newValue }), }); - setPatrolEnabledLocal(newValue); + if (typeof data?.patrol_enabled === 'boolean') { + setPatrolEnabledLocal(data.patrol_enabled); + } else { + setPatrolEnabledLocal(newValue); + } + if (typeof data?.patrol_interval_minutes === 'number') { + setPatrolInterval(data.patrol_interval_minutes); + } + if (refetchPatrolStatus) { + refetchPatrolStatus(); + } } catch (err) { console.error('Failed to toggle patrol:', err); } finally { @@ -231,7 +241,7 @@ export function AIIntelligence() { } // Fetch patrol status to check license - const [patrolStatus] = createResource(async () => { + const [patrolStatus, { refetch: refetchPatrolStatus }] = createResource(async () => { try { return await getPatrolStatus(); } catch { @@ -309,6 +319,7 @@ export function AIIntelligence() { await Promise.all([ aiIntelligenceStore.loadFindings(), aiIntelligenceStore.loadCircuitBreakerStatus(), + refetchPatrolStatus(), ]); } finally { setIsRefreshing(false); diff --git a/internal/ai/patrol.go b/internal/ai/patrol.go index 3106b5378..d331a33ef 100644 --- a/internal/ai/patrol.go +++ b/internal/ai/patrol.go @@ -1092,10 +1092,29 @@ func (p *PatrolService) runScopedPatrol(ctx context.Context, scope PatrolScope) // Filter state based on scope filteredState := p.filterStateByScope(fullState, scope) - // Count filtered resources - resourceCount := len(filteredState.Nodes) + len(filteredState.VMs) + len(filteredState.Containers) + - len(filteredState.DockerHosts) + len(filteredState.Storage) + len(filteredState.PBSInstances) + - len(filteredState.Hosts) + len(filteredState.KubernetesClusters) + // Count filtered resources (respect analysis configuration) + resourceCount := 0 + if cfg.AnalyzeNodes { + resourceCount += len(filteredState.Nodes) + } + if cfg.AnalyzeGuests { + resourceCount += len(filteredState.VMs) + len(filteredState.Containers) + } + if cfg.AnalyzeDocker { + resourceCount += len(filteredState.DockerHosts) + } + if cfg.AnalyzeStorage { + resourceCount += len(filteredState.Storage) + } + if cfg.AnalyzePBS { + resourceCount += len(filteredState.PBSInstances) + } + if cfg.AnalyzeHosts { + resourceCount += len(filteredState.Hosts) + } + if cfg.AnalyzeKubernetes { + resourceCount += len(filteredState.KubernetesClusters) + } if resourceCount == 0 { log.Debug(). @@ -1637,14 +1656,28 @@ func (p *PatrolService) runPatrol(ctx context.Context) { return isNew } - // Count resources for statistics (but analysis is done by LLM only) - runStats.nodesChecked = len(state.Nodes) - runStats.guestsChecked = len(state.VMs) + len(state.Containers) - runStats.dockerChecked = len(state.DockerHosts) - runStats.storageChecked = len(state.Storage) - runStats.pbsChecked = len(state.PBSInstances) - runStats.hostsChecked = len(state.Hosts) - runStats.kubernetesChecked = len(state.KubernetesClusters) + // Count resources for statistics (respect analysis configuration) + if cfg.AnalyzeNodes { + runStats.nodesChecked = len(state.Nodes) + } + if cfg.AnalyzeGuests { + runStats.guestsChecked = len(state.VMs) + len(state.Containers) + } + if cfg.AnalyzeDocker { + runStats.dockerChecked = len(state.DockerHosts) + } + if cfg.AnalyzeStorage { + runStats.storageChecked = len(state.Storage) + } + if cfg.AnalyzePBS { + runStats.pbsChecked = len(state.PBSInstances) + } + if cfg.AnalyzeHosts { + runStats.hostsChecked = len(state.Hosts) + } + if cfg.AnalyzeKubernetes { + runStats.kubernetesChecked = len(state.KubernetesClusters) + } runStats.resourceCount = runStats.nodesChecked + runStats.guestsChecked + runStats.dockerChecked + runStats.storageChecked + runStats.pbsChecked + runStats.hostsChecked + runStats.kubernetesChecked diff --git a/internal/ai/patrol_triggers.go b/internal/ai/patrol_triggers.go index b66a1e5f5..a10254e9b 100644 --- a/internal/ai/patrol_triggers.go +++ b/internal/ai/patrol_triggers.go @@ -2,6 +2,7 @@ package ai import ( "context" + "sort" "sync" "time" @@ -424,8 +425,15 @@ func slicesEqual(a, b []string) bool { if len(a) != len(b) { return false } - for i, v := range a { - if v != b[i] { + if len(a) <= 1 { + return true + } + aSorted := append([]string(nil), a...) + bSorted := append([]string(nil), b...) + sort.Strings(aSorted) + sort.Strings(bSorted) + for i := range aSorted { + if aSorted[i] != bSorted[i] { return false } }