This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

199 lines
5.9 KiB
Python

"""
Slack Action für Alerts Agent.
Sendet Slack-Nachrichten für Alerts via Incoming Webhooks.
"""
import logging
from typing import Dict, Any, List
import httpx
from .base import ActionHandler, ActionResult, ActionType, AlertContext
logger = logging.getLogger(__name__)
class SlackAction(ActionHandler):
"""
Slack-Benachrichtigungen für Alerts via Incoming Webhooks.
Konfiguration:
- webhook_url: Slack Incoming Webhook URL
- channel: Optional - Channel überschreiben
- username: Optional - Bot-Username (default: BreakPilot Alerts)
- icon_emoji: Optional - Bot-Icon (default: :bell:)
"""
@property
def action_type(self) -> ActionType:
return ActionType.SLACK
def get_required_config_fields(self) -> List[str]:
return ["webhook_url"]
def validate_config(self, config: Dict[str, Any]) -> bool:
url = config.get("webhook_url", "")
return "hooks.slack.com" in url or url.startswith("https://")
async def execute(
self,
context: AlertContext,
config: Dict[str, Any],
) -> ActionResult:
"""
Sendet eine Slack-Nachricht.
Args:
context: Alert-Kontext
config: Slack-Konfiguration (webhook_url, channel, etc.)
Returns:
ActionResult
"""
try:
webhook_url = config.get("webhook_url")
# Slack-Payload mit Block Kit
payload = self._build_slack_payload(context, config)
# Request senden
async with httpx.AsyncClient(timeout=30) as client:
response = await client.post(
webhook_url,
json=payload,
headers={"Content-Type": "application/json"},
)
# Slack gibt "ok" als Text zurück bei Erfolg
success = response.status_code == 200 and response.text == "ok"
return ActionResult(
success=success,
action_type=self.action_type,
message="Slack-Nachricht gesendet" if success else "Slack-Fehler",
details={
"status_code": response.status_code,
"response": response.text[:100],
},
error=None if success else response.text,
)
except Exception as e:
logger.error(f"Slack action error: {e}")
return ActionResult(
success=False,
action_type=self.action_type,
message="Slack-Fehler",
error=str(e),
)
def _build_slack_payload(
self,
context: AlertContext,
config: Dict[str, Any],
) -> Dict[str, Any]:
"""
Erstellt den Slack-Payload mit Block Kit.
Verwendet Rich-Formatting für bessere Darstellung.
"""
# Basis-Payload
payload = {
"username": config.get("username", "BreakPilot Alerts"),
"icon_emoji": config.get("icon_emoji", ":bell:"),
}
# Channel überschreiben wenn angegeben
if config.get("channel"):
payload["channel"] = config["channel"]
# Block Kit Blocks
blocks = [
# Header
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"📰 {context.topic_name}",
"emoji": True,
}
},
# Alert-Titel als Link
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*<{context.url}|{context.title}>*",
}
},
]
# Snippet wenn vorhanden
if context.snippet:
snippet = context.snippet[:200]
if len(context.snippet) > 200:
snippet += "..."
blocks.append({
"type": "section",
"text": {
"type": "plain_text",
"text": snippet,
"emoji": False,
}
})
# Kontext-Felder (Score, Decision, Rule)
fields = []
if context.relevance_score is not None:
score_emoji = "🟢" if context.relevance_score >= 0.7 else "🟡" if context.relevance_score >= 0.4 else "🔴"
fields.append({
"type": "mrkdwn",
"text": f"*Score:* {score_emoji} {context.relevance_score:.0%}",
})
if context.relevance_decision:
decision_emoji = {"KEEP": "", "DROP": "", "REVIEW": "👀"}.get(context.relevance_decision, "")
fields.append({
"type": "mrkdwn",
"text": f"*Decision:* {decision_emoji} {context.relevance_decision}",
})
if context.matched_rule:
fields.append({
"type": "mrkdwn",
"text": f"*Regel:* {context.matched_rule}",
})
if context.tags:
fields.append({
"type": "mrkdwn",
"text": f"*Tags:* {', '.join(context.tags)}",
})
if fields:
blocks.append({
"type": "section",
"fields": fields[:10], # Max 10 Felder
})
# Divider
blocks.append({"type": "divider"})
# Actions (Link zur Inbox)
blocks.append({
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": f"<{config.get('dashboard_url', 'http://localhost:8000/studio#alerts')}|Zur Alerts Inbox> | Gesendet von BreakPilot",
}
]
})
payload["blocks"] = blocks
# Fallback-Text für Notifications
payload["text"] = f"Neuer Alert: {context.title}"
return payload