- 修正 import 順序 (standard → third-party → local) - 修復 datetime/timedelta 未定義錯誤 - 移除未使用的 imports Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
166 lines
4.0 KiB
Python
166 lines
4.0 KiB
Python
"""
|
|
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
|
|
from enum import Enum
|
|
from typing import Any
|
|
|
|
from src.utils.timezone import now_taipei
|
|
|
|
|
|
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: now_taipei())
|
|
|
|
@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: now_taipei())
|
|
|
|
|
|
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
|