""" Action Dispatcher für Alerts Agent. Verteilt Aktionen an die entsprechenden Handler. """ import logging from typing import Dict, Any, List, Optional from datetime import datetime from .base import ActionHandler, ActionResult, ActionType, AlertContext from .email_action import EmailAction from .webhook_action import WebhookAction from .slack_action import SlackAction logger = logging.getLogger(__name__) class ActionDispatcher: """ Zentrale Verteilung von Aktionen an Handler. Registriert Handler für verschiedene Aktionstypen und führt Aktionen basierend auf Regel-Konfigurationen aus. """ def __init__(self): """Initialisiert den Dispatcher mit Standard-Handlern.""" self._handlers: Dict[ActionType, ActionHandler] = {} # Standard-Handler registrieren self.register_handler(EmailAction()) self.register_handler(WebhookAction()) self.register_handler(SlackAction()) def register_handler(self, handler: ActionHandler) -> None: """ Registriert einen Action-Handler. Args: handler: Handler-Instanz """ self._handlers[handler.action_type] = handler logger.debug(f"Registered action handler: {handler.action_type.value}") def get_handler(self, action_type: ActionType) -> Optional[ActionHandler]: """ Gibt den Handler für einen Aktionstyp zurück. Args: action_type: Aktionstyp Returns: Handler oder None """ return self._handlers.get(action_type) def list_handlers(self) -> List[str]: """Gibt Liste der registrierten Handler zurück.""" return [at.value for at in self._handlers.keys()] async def dispatch( self, action_type: str, context: AlertContext, config: Dict[str, Any], ) -> ActionResult: """ Führt eine Aktion aus. Args: action_type: Aktionstyp als String (email, webhook, slack) context: Alert-Kontext config: Aktionsspezifische Konfiguration Returns: ActionResult """ try: # ActionType aus String at = ActionType(action_type.lower()) except ValueError: return ActionResult( success=False, action_type=ActionType.WEBHOOK, # Fallback message=f"Unbekannter Aktionstyp: {action_type}", error="Unknown action type", ) handler = self.get_handler(at) if not handler: return ActionResult( success=False, action_type=at, message=f"Kein Handler für {action_type} registriert", error="No handler registered", ) # Konfiguration validieren if not handler.validate_config(config): required = handler.get_required_config_fields() return ActionResult( success=False, action_type=at, message=f"Ungültige Konfiguration für {action_type}", error=f"Required fields: {required}", ) # Aktion ausführen logger.info(f"Dispatching {action_type} action for alert {context.alert_id[:8]}") result = await handler.execute(context, config) return result async def dispatch_multiple( self, actions: List[Dict[str, Any]], context: AlertContext, ) -> List[ActionResult]: """ Führt mehrere Aktionen aus. Args: actions: Liste von Aktionen [{type, config}, ...] context: Alert-Kontext Returns: Liste von ActionResults """ results = [] for action in actions: action_type = action.get("type", action.get("action_type", "")) config = action.get("config", action.get("action_config", {})) result = await self.dispatch(action_type, context, config) results.append(result) return results # Singleton-Instanz _dispatcher: Optional[ActionDispatcher] = None def get_dispatcher() -> ActionDispatcher: """Gibt den globalen ActionDispatcher zurück.""" global _dispatcher if _dispatcher is None: _dispatcher = ActionDispatcher() return _dispatcher async def execute_action( action_type: str, alert_id: str, title: str, url: str, snippet: str, topic_name: str, config: Dict[str, Any], relevance_score: Optional[float] = None, relevance_decision: Optional[str] = None, matched_rule: Optional[str] = None, tags: Optional[List[str]] = None, ) -> ActionResult: """ Convenience-Funktion zum Ausführen einer Aktion. Erstellt den Kontext und ruft den Dispatcher auf. """ context = AlertContext( alert_id=alert_id, title=title, url=url, snippet=snippet, topic_name=topic_name, relevance_score=relevance_score, relevance_decision=relevance_decision, matched_rule=matched_rule, tags=tags or [], ) dispatcher = get_dispatcher() return await dispatcher.dispatch(action_type, context, config) async def execute_rule_actions( alert_id: str, title: str, url: str, snippet: str, topic_name: str, rule_action: str, rule_config: Dict[str, Any], rule_name: str, ) -> ActionResult: """ Führt die Aktion einer gematschten Regel aus. Args: alert_id: Alert-ID title: Alert-Titel url: Alert-URL snippet: Alert-Snippet topic_name: Topic-Name rule_action: Aktionstyp der Regel rule_config: Aktions-Konfiguration rule_name: Name der Regel Returns: ActionResult """ # Nur externe Aktionen (email, webhook, slack) hier behandeln # keep/drop/tag werden direkt von der Rule Engine behandelt if rule_action not in ["email", "webhook", "slack"]: return ActionResult( success=True, action_type=ActionType.TAG, # Dummy message=f"Interne Aktion {rule_action} von Rule Engine behandelt", ) return await execute_action( action_type=rule_action, alert_id=alert_id, title=title, url=url, snippet=snippet, topic_name=topic_name, config=rule_config, matched_rule=rule_name, )