mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
fix: Gate AI subsystems and intelligence endpoints on AI enabled state. Related to #885
- Add IsAIEnabled() method to AISettingsHandler for consistent checks - Gate baseline learning, pattern detector, and correlation detector initialization in StartPatrol() on AI being enabled - Add AI enabled checks to all /api/ai/intelligence/* endpoints as defense-in-depth - Return empty results with "AI is not enabled" message when AI is disabled This ensures no AI-related data is collected, persisted, or returned when AI is disabled, preventing the "undismissable alerts" issue where old AI findings would appear.
This commit is contained in:
@@ -78,6 +78,11 @@ func (h *AISettingsHandler) StartPatrol(ctx context.Context) {
|
||||
h.aiService.StartPatrol(ctx)
|
||||
}
|
||||
|
||||
// IsAIEnabled returns true if AI features are enabled
|
||||
func (h *AISettingsHandler) IsAIEnabled() bool {
|
||||
return h.aiService.IsEnabled()
|
||||
}
|
||||
|
||||
// SetPatrolThresholdProvider sets the threshold provider for the patrol service
|
||||
func (h *AISettingsHandler) SetPatrolThresholdProvider(provider ai.ThresholdProvider) {
|
||||
h.aiService.SetPatrolThresholdProvider(provider)
|
||||
|
||||
@@ -20,6 +20,17 @@ func (h *AISettingsHandler) HandleGetPatterns(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return intelligence data
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"patterns": []interface{}{},
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write patterns response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
@@ -92,6 +103,17 @@ func (h *AISettingsHandler) HandleGetPredictions(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return intelligence data
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"predictions": []interface{}{},
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write predictions response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
@@ -166,6 +188,17 @@ func (h *AISettingsHandler) HandleGetCorrelations(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return intelligence data
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"correlations": []interface{}{},
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write correlations response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
@@ -244,6 +277,17 @@ func (h *AISettingsHandler) HandleGetRecentChanges(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return intelligence data
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"changes": []interface{}{},
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write changes response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
@@ -322,6 +366,17 @@ func (h *AISettingsHandler) HandleGetBaselines(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return intelligence data
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"baselines": []interface{}{},
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write baselines response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
@@ -395,6 +450,17 @@ func (h *AISettingsHandler) HandleGetRemediations(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return intelligence data
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"remediations": []interface{}{},
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write remediations response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
@@ -539,6 +605,17 @@ func (h *AISettingsHandler) HandleGetAnomalies(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return intelligence data
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"anomalies": []interface{}{},
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write anomalies response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
@@ -771,6 +848,19 @@ func (h *AISettingsHandler) HandleGetLearningStatus(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
// AI must be enabled to return learning status
|
||||
if !h.aiService.IsEnabled() {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
"resources_baselined": 0,
|
||||
"total_metrics": 0,
|
||||
"status": "ai_disabled",
|
||||
"message": "AI is not enabled",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write learning status response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
patrol := h.aiService.GetPatrolService()
|
||||
if patrol == nil {
|
||||
if err := utils.WriteJSONResponse(w, map[string]interface{}{
|
||||
|
||||
+74
-66
@@ -1588,18 +1588,22 @@ func (r *Router) StartPatrol(ctx context.Context) {
|
||||
r.aiSettingsHandler.SetMetricsHistoryProvider(adapter)
|
||||
}
|
||||
|
||||
// Initialize baseline store for anomaly detection
|
||||
// Uses config dir for persistence
|
||||
baselineCfg := ai.DefaultBaselineConfig()
|
||||
if r.persistence != nil {
|
||||
baselineCfg.DataDir = r.persistence.DataDir()
|
||||
}
|
||||
baselineStore := ai.NewBaselineStore(baselineCfg)
|
||||
if baselineStore != nil {
|
||||
r.aiSettingsHandler.SetBaselineStore(baselineStore)
|
||||
// Only initialize baseline learning if AI is enabled
|
||||
// This prevents anomaly data from being collected and displayed when AI is disabled
|
||||
if r.aiSettingsHandler.IsAIEnabled() {
|
||||
// Initialize baseline store for anomaly detection
|
||||
// Uses config dir for persistence
|
||||
baselineCfg := ai.DefaultBaselineConfig()
|
||||
if r.persistence != nil {
|
||||
baselineCfg.DataDir = r.persistence.DataDir()
|
||||
}
|
||||
baselineStore := ai.NewBaselineStore(baselineCfg)
|
||||
if baselineStore != nil {
|
||||
r.aiSettingsHandler.SetBaselineStore(baselineStore)
|
||||
|
||||
// Start background baseline learning loop
|
||||
go r.startBaselineLearning(ctx, baselineStore, metricsHistory)
|
||||
// Start background baseline learning loop
|
||||
go r.startBaselineLearning(ctx, baselineStore, metricsHistory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1626,63 +1630,67 @@ func (r *Router) StartPatrol(ctx context.Context) {
|
||||
r.aiSettingsHandler.SetRemediationLog(remediationLog)
|
||||
}
|
||||
|
||||
// Initialize pattern detector for failure prediction
|
||||
patternDetector := ai.NewPatternDetector(ai.PatternDetectorConfig{
|
||||
MaxEvents: 5000,
|
||||
MinOccurrences: 3,
|
||||
PatternWindow: 90 * 24 * time.Hour,
|
||||
PredictionLimit: 30 * 24 * time.Hour,
|
||||
DataDir: dataDir,
|
||||
})
|
||||
if patternDetector != nil {
|
||||
r.aiSettingsHandler.SetPatternDetector(patternDetector)
|
||||
// Only initialize pattern and correlation detectors if AI is enabled
|
||||
// This prevents these subsystems from collecting data and displaying findings when AI is disabled
|
||||
if r.aiSettingsHandler.IsAIEnabled() {
|
||||
// Initialize pattern detector for failure prediction
|
||||
patternDetector := ai.NewPatternDetector(ai.PatternDetectorConfig{
|
||||
MaxEvents: 5000,
|
||||
MinOccurrences: 3,
|
||||
PatternWindow: 90 * 24 * time.Hour,
|
||||
PredictionLimit: 30 * 24 * time.Hour,
|
||||
DataDir: dataDir,
|
||||
})
|
||||
if patternDetector != nil {
|
||||
r.aiSettingsHandler.SetPatternDetector(patternDetector)
|
||||
|
||||
// Wire alert history to pattern detector for event tracking
|
||||
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
|
||||
alertManager.OnAlertHistory(func(alert alerts.Alert) {
|
||||
// Convert alert type to trackable event
|
||||
patternDetector.RecordFromAlert(alert.ResourceID, alert.Type+"_"+string(alert.Level), alert.StartTime)
|
||||
})
|
||||
log.Info().Msg("AI Pattern Detector: Wired to alert history for failure prediction")
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize correlation detector for multi-resource relationships
|
||||
correlationDetector := ai.NewCorrelationDetector(ai.CorrelationConfig{
|
||||
MaxEvents: 10000,
|
||||
CorrelationWindow: 10 * time.Minute,
|
||||
MinOccurrences: 3,
|
||||
RetentionWindow: 30 * 24 * time.Hour,
|
||||
DataDir: dataDir,
|
||||
})
|
||||
if correlationDetector != nil {
|
||||
r.aiSettingsHandler.SetCorrelationDetector(correlationDetector)
|
||||
|
||||
// Wire alert history to correlation detector
|
||||
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
|
||||
alertManager.OnAlertHistory(func(alert alerts.Alert) {
|
||||
// Record as correlation event
|
||||
eventType := ai.CorrelationEventType(ai.CorrelationEventAlert)
|
||||
switch alert.Type {
|
||||
case "cpu":
|
||||
eventType = ai.CorrelationEventHighCPU
|
||||
case "memory":
|
||||
eventType = ai.CorrelationEventHighMem
|
||||
case "disk":
|
||||
eventType = ai.CorrelationEventDiskFull
|
||||
case "offline", "connectivity":
|
||||
eventType = ai.CorrelationEventOffline
|
||||
}
|
||||
correlationDetector.RecordEvent(ai.CorrelationEvent{
|
||||
ResourceID: alert.ResourceID,
|
||||
ResourceName: alert.ResourceName,
|
||||
ResourceType: alert.Type,
|
||||
EventType: eventType,
|
||||
Timestamp: alert.StartTime,
|
||||
Value: alert.Value,
|
||||
// Wire alert history to pattern detector for event tracking
|
||||
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
|
||||
alertManager.OnAlertHistory(func(alert alerts.Alert) {
|
||||
// Convert alert type to trackable event
|
||||
patternDetector.RecordFromAlert(alert.ResourceID, alert.Type+"_"+string(alert.Level), alert.StartTime)
|
||||
})
|
||||
})
|
||||
log.Info().Msg("AI Correlation Detector: Wired to alert history for multi-resource analysis")
|
||||
log.Info().Msg("AI Pattern Detector: Wired to alert history for failure prediction")
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize correlation detector for multi-resource relationships
|
||||
correlationDetector := ai.NewCorrelationDetector(ai.CorrelationConfig{
|
||||
MaxEvents: 10000,
|
||||
CorrelationWindow: 10 * time.Minute,
|
||||
MinOccurrences: 3,
|
||||
RetentionWindow: 30 * 24 * time.Hour,
|
||||
DataDir: dataDir,
|
||||
})
|
||||
if correlationDetector != nil {
|
||||
r.aiSettingsHandler.SetCorrelationDetector(correlationDetector)
|
||||
|
||||
// Wire alert history to correlation detector
|
||||
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
|
||||
alertManager.OnAlertHistory(func(alert alerts.Alert) {
|
||||
// Record as correlation event
|
||||
eventType := ai.CorrelationEventType(ai.CorrelationEventAlert)
|
||||
switch alert.Type {
|
||||
case "cpu":
|
||||
eventType = ai.CorrelationEventHighCPU
|
||||
case "memory":
|
||||
eventType = ai.CorrelationEventHighMem
|
||||
case "disk":
|
||||
eventType = ai.CorrelationEventDiskFull
|
||||
case "offline", "connectivity":
|
||||
eventType = ai.CorrelationEventOffline
|
||||
}
|
||||
correlationDetector.RecordEvent(ai.CorrelationEvent{
|
||||
ResourceID: alert.ResourceID,
|
||||
ResourceName: alert.ResourceName,
|
||||
ResourceType: alert.Type,
|
||||
EventType: eventType,
|
||||
Timestamp: alert.StartTime,
|
||||
Value: alert.Value,
|
||||
})
|
||||
})
|
||||
log.Info().Msg("AI Correlation Detector: Wired to alert history for multi-resource analysis")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user