feat: add all application source code
- apps/api: FastAPI backend with Dockerfile - apps/web: Next.js frontend with Dockerfile - apps/sensor: Signal collection agent - packages: shared packages Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
163
apps/api/src/services/notifications/base.py
Normal file
163
apps/api/src/services/notifications/base.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
Notification Provider Base Interface
|
||||
=====================================
|
||||
Phase 6: leWOOOgo Output Plugins
|
||||
|
||||
設計原則:
|
||||
1. 抽象介面 - 所有 Provider 必須實作 send()
|
||||
2. 統一訊息格式 - NotificationMessage
|
||||
3. 結果追蹤 - NotificationResult
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
|
||||
class NotificationStatus(str, Enum):
|
||||
"""通知狀態"""
|
||||
SUCCESS = "success"
|
||||
FAILED = "failed"
|
||||
SKIPPED = "skipped"
|
||||
|
||||
|
||||
class ExecutionStatus(str, Enum):
|
||||
"""執行狀態"""
|
||||
SUCCESS = "success"
|
||||
FAILED = "failed"
|
||||
DRY_RUN_BLOCKED = "dry_run_blocked"
|
||||
PENDING = "pending"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NotificationMessage:
|
||||
"""
|
||||
通知訊息統一格式
|
||||
|
||||
所有 Provider 都從這個格式轉換成各自的 API 格式
|
||||
"""
|
||||
# 執行結果
|
||||
execution_status: ExecutionStatus
|
||||
|
||||
# 核心資訊
|
||||
action_title: str
|
||||
action_description: str
|
||||
approval_id: str
|
||||
|
||||
# 簽核資訊
|
||||
signers: list[dict[str, str]] = field(default_factory=list) # [{"name": "CTO", "comment": "..."}]
|
||||
required_signatures: int = 1
|
||||
|
||||
# 影響範圍 (Blast Radius)
|
||||
affected_pods: int = 0
|
||||
estimated_downtime: str = "N/A"
|
||||
related_services: list[str] = field(default_factory=list)
|
||||
data_impact: str = "none"
|
||||
|
||||
# 執行細節
|
||||
namespace: str = "default"
|
||||
operation_type: str = "unknown"
|
||||
duration_ms: int | None = None
|
||||
error_message: str | None = None
|
||||
|
||||
# AI 分析
|
||||
risk_level: str = "medium"
|
||||
ai_provider: str = "unknown"
|
||||
confidence: float | None = None
|
||||
|
||||
# 時間戳
|
||||
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
@property
|
||||
def status_emoji(self) -> str:
|
||||
"""狀態 Emoji"""
|
||||
if self.execution_status == ExecutionStatus.SUCCESS:
|
||||
return "✅"
|
||||
elif self.execution_status == ExecutionStatus.FAILED:
|
||||
return "❌"
|
||||
elif self.execution_status == ExecutionStatus.DRY_RUN_BLOCKED:
|
||||
return "🛡️"
|
||||
return "⏳"
|
||||
|
||||
@property
|
||||
def status_text(self) -> str:
|
||||
"""狀態文字"""
|
||||
if self.execution_status == ExecutionStatus.SUCCESS:
|
||||
return "任務執行成功"
|
||||
elif self.execution_status == ExecutionStatus.FAILED:
|
||||
return "執行失敗"
|
||||
elif self.execution_status == ExecutionStatus.DRY_RUN_BLOCKED:
|
||||
return "Dry-Run 攔截"
|
||||
return "等待中"
|
||||
|
||||
@property
|
||||
def risk_emoji(self) -> str:
|
||||
"""風險等級 Emoji"""
|
||||
if self.risk_level == "critical":
|
||||
return "🔴"
|
||||
elif self.risk_level == "medium":
|
||||
return "🟡"
|
||||
return "🟢"
|
||||
|
||||
@property
|
||||
def signers_display(self) -> str:
|
||||
"""簽核者顯示文字"""
|
||||
if not self.signers:
|
||||
return "無"
|
||||
return ", ".join([s.get("name", "Unknown") for s in self.signers])
|
||||
|
||||
|
||||
@dataclass
|
||||
class NotificationResult:
|
||||
"""通知發送結果"""
|
||||
status: NotificationStatus
|
||||
provider: str
|
||||
message: str
|
||||
response_data: dict[str, Any] | None = None
|
||||
error: str | None = None
|
||||
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
|
||||
class NotificationProvider(ABC):
|
||||
"""
|
||||
通知提供者抽象介面
|
||||
|
||||
所有 Output Plugin 必須實作此介面
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""Provider 名稱"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def enabled(self) -> bool:
|
||||
"""是否啟用"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def send(self, message: NotificationMessage) -> NotificationResult:
|
||||
"""
|
||||
發送通知
|
||||
|
||||
Args:
|
||||
message: 統一格式的通知訊息
|
||||
|
||||
Returns:
|
||||
NotificationResult: 發送結果
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def test_connection(self) -> bool:
|
||||
"""
|
||||
測試連線
|
||||
|
||||
Returns:
|
||||
bool: 是否連線成功
|
||||
"""
|
||||
pass
|
||||
Reference in New Issue
Block a user