"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import { useI18n } from "@/lib/i18n"; interface AlertRule { id: string; name: string; enabled: boolean; threshold?: number; targetAgents?: string[]; } interface AlertConfig { enabled: boolean; receiveAgent: string; rules: AlertRule[]; checkInterval?: number; } interface Agent { id: string; name: string; emoji: string; } const RULE_DESCRIPTIONS: Record> = { "zh-TW": { model_unavailable: "模型不可用 - 當測試模型失敗時觸發", bot_no_response: "Bot 長時間無回應 - 當機器人超過設定時間未回應時觸發", message_failure_rate: "訊息失敗率升高 - 當訊息失敗率超過閾值時觸發", cron连续_failure: "Cron 連續失敗 - 當定時任務連續失敗超過設定次數時觸發", }, zh: { model_unavailable: "模型不可用 - 当测试模型失败时触发", bot_no_response: "Bot 长时间无响应 - 当机器人超过设定时间未响应时触发", message_failure_rate: "消息失败率升高 - 当消息失败率超过阈值时触发", cron连续_failure: "Cron 连续失败 - 当定时任务连续失败超过设定次数时触发", }, en: { model_unavailable: "Model Unavailable - Triggered when model test fails", bot_no_response: "Bot Long Time No Response - Triggered when bot is inactive for set period", message_failure_rate: "Message Failure Rate High - Triggered when failure rate exceeds threshold", cron连续_failure: "Cron Continuous Failure - Triggered when cron jobs fail multiple times in a row", }, }; export default function AlertsPage() { const { t, locale } = useI18n(); const [config, setConfig] = useState(null); const [agents, setAgents] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const [checking, setChecking] = useState(false); const [checkResults, setCheckResults] = useState([]); const [lastCheckTime, setLastCheckTime] = useState(""); const [checkInterval, setCheckInterval] = useState(10); // 从配置加载 checkInterval useEffect(() => { if (config?.checkInterval) { setCheckInterval(config.checkInterval); } }, [config?.checkInterval]); const ruleDescriptions = RULE_DESCRIPTIONS[locale as keyof typeof RULE_DESCRIPTIONS] || RULE_DESCRIPTIONS.zh; const isEnglish = locale === "en"; const isTraditionalChinese = locale === "zh-TW"; const timeLocale = isEnglish ? "en-US" : isTraditionalChinese ? "zh-TW" : "zh-CN"; const ui = { minutes5: isEnglish ? "5 minutes" : isTraditionalChinese ? "5 分鐘" : "5 分钟", minutes10: isEnglish ? "10 minutes" : isTraditionalChinese ? "10 分鐘" : "10 分钟", minutes30: isEnglish ? "30 minutes" : isTraditionalChinese ? "30 分鐘" : "30 分钟", hour1: isEnglish ? "1 hour" : isTraditionalChinese ? "1 小時" : "1 小时", hours2: isEnglish ? "2 hours" : isTraditionalChinese ? "2 小時" : "2 小时", hours5: isEnglish ? "5 hours" : isTraditionalChinese ? "5 小時" : "5 小时", checking: isEnglish ? "⏳ Checking..." : isTraditionalChinese ? "⏳ 檢查中..." : "⏳ 检查中...", checkNow: isEnglish ? "🔄 Check Now" : isTraditionalChinese ? "🔄 立即檢查" : "🔄 立即检查", alertsTriggered: isEnglish ? "⚠️ Alerts Triggered" : isTraditionalChinese ? "⚠️ 警報觸發" : "⚠️ 告警触发", checkingAlerts: isEnglish ? "⏳ Checking alerts..." : isTraditionalChinese ? "⏳ 正在檢查警報..." : "⏳ 正在检查告警...", timeout: isEnglish ? "Timeout (s):" : isTraditionalChinese ? "超時 (秒):" : "超时 (秒):", failureRate: isEnglish ? "Failure rate (%):" : isTraditionalChinese ? "失敗率 (%):" : "失败率 (%):", maxFailures: isEnglish ? "Max failures:" : isTraditionalChinese ? "最大失敗數:" : "最大失败数:", threshold: isEnglish ? "Threshold:" : isTraditionalChinese ? "閾值:" : "阈值:", monitor: isEnglish ? "Monitor:" : isTraditionalChinese ? "檢測機器人:" : "检测机器人:", emptyMeansAll: isEnglish ? "(empty = all)" : isTraditionalChinese ? "(不選則檢測所有)" : "(不选则检测所有)", saved: isEnglish ? "Saved" : isTraditionalChinese ? "已保存" : "已保存", }; // 加载配置 useEffect(() => { Promise.all([ fetch("/api/alerts").then((r) => r.json()), fetch("/api/config").then((r) => r.json()), ]) .then(([alertData, configData]) => { setConfig(alertData); setAgents(configData.agents || []); }) .catch(console.error) .finally(() => setLoading(false)); }, []); // 定时检查告警(不自动触发,由用户点击按钮触发) useEffect(() => { if (!config?.enabled) return; const checkAlerts = () => { setChecking(true); fetch("/api/alerts/check", { method: "POST" }) .then((r) => r.json()) .then((data) => { if (data.results && data.results.length > 0) { setCheckResults(data.results); setLastCheckTime(new Date().toLocaleTimeString(timeLocale)); } }) .catch(console.error) .finally(() => setChecking(false)); }; // 只设置定时器,不立即检查 const timer = setInterval(checkAlerts, checkInterval * 60 * 1000); return () => clearInterval(timer); }, [config?.enabled, checkInterval, locale]); const handleManualCheck = () => { setChecking(true); fetch("/api/alerts/check", { method: "POST" }) .then((r) => r.json()) .then((data) => { if (data.results && data.results.length > 0) { setCheckResults(data.results); setLastCheckTime(new Date().toLocaleTimeString(timeLocale)); } }) .catch(console.error) .finally(() => setChecking(false)); }; const handleToggle = () => { if (!config) return; setSaving(true); fetch("/api/alerts", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enabled: !config.enabled }), }) .then((r) => r.json()) .then((newConfig) => { setConfig(newConfig); setSaved(true); setTimeout(() => setSaved(false), 2000); }) .finally(() => setSaving(false)); }; const handleAgentChange = (agentId: string) => { if (!config) return; setSaving(true); fetch("/api/alerts", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ receiveAgent: agentId }), }) .then((r) => r.json()) .then((newConfig) => { setConfig(newConfig); setSaved(true); setTimeout(() => setSaved(false), 2000); }) .finally(() => setSaving(false)); }; const handleIntervalChange = (value: number) => { if (!config) return; setCheckInterval(value); setSaving(true); fetch("/api/alerts", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ checkInterval: value }), }) .then((r) => r.json()) .then((newConfig) => { setConfig(newConfig); setSaved(true); setTimeout(() => setSaved(false), 2000); }) .finally(() => setSaving(false)); }; const handleRuleToggle = (ruleId: string) => { if (!config) return; const rules = config.rules.map((r) => r.id === ruleId ? { ...r, enabled: !r.enabled } : r ); setSaving(true); fetch("/api/alerts", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rules }), }) .then((r) => r.json()) .then((newConfig) => { setConfig(newConfig); setSaved(true); setTimeout(() => setSaved(false), 2000); }) .finally(() => setSaving(false)); }; const handleThresholdChange = (ruleId: string, value: number) => { if (!config) return; const rules = config.rules.map((r) => r.id === ruleId ? { ...r, threshold: value } : r ); setSaving(true); fetch("/api/alerts", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rules }), }) .then((r) => r.json()) .then((newConfig) => { setConfig(newConfig); setSaved(true); setTimeout(() => setSaved(false), 2000); }) .finally(() => setSaving(false)); }; if (loading) { return (

{t("common.loading")}

); } if (!config) { return (

{t("common.loadError")}

); } return (

🔔 {t("alerts.title") || "Alert Center"}

{t("alerts.subtitle") || "Configure system alerts and notifications"}

{/* 检查间隔设置 */} {config.enabled && (
{t("alerts.checkInterval") || "Check Interval"}:
)} {/* 手动检查按钮 */} {config.enabled && ( )} {t("common.backHome") || "Back"}
{/* 检查结果展示 */} {config.enabled && checkResults.length > 0 && (

{ui.alertsTriggered} ({checkResults.length})

{lastCheckTime && {lastCheckTime}}
    {checkResults.map((result, i) => (
  • • {result}
  • ))}
)} {config.enabled && checking && (
{ui.checkingAlerts}
)} {/* 告警总开关 */}

{t("alerts.enableAlerts") || "Enable Alerts"}

{t("alerts.enableDesc") || "Turn on/off all alert notifications"}

{/* 接收告警的机器人 */}

{t("alerts.receiveAgent") || "Receive Alert Agent"}

{agents.map((agent) => ( ))}
{/* 告警规则列表 */}

{t("alerts.rules") || "Alert Rules"}

{t("alerts.rulesDesc") || "Configure which conditions trigger alerts"}

{config.rules.map((rule) => (

{rule.name}

{ruleDescriptions[rule.id] || rule.id}

{rule.threshold !== undefined && rule.id !== "bot_no_response" && (
{rule.id === "bot_no_response" ? ui.timeout : rule.id === "message_failure_rate" ? ui.failureRate : rule.id === "cron连续_failure" ? ui.maxFailures : ui.threshold} handleThresholdChange(rule.id, Number(e.target.value))} disabled={!config.enabled || !rule.enabled || saving} className="w-20 px-2 py-1 text-sm rounded border border-[var(--border)] bg-[var(--card)] text-[var(--text)] disabled:opacity-50" />
)} {rule.id === "bot_no_response" && rule.threshold !== undefined && (
{ui.timeout} handleThresholdChange(rule.id, Number(e.target.value))} disabled={!config.enabled || !rule.enabled || saving} className="w-20 px-2 py-1 text-sm rounded border border-[var(--border)] bg-[var(--card)] text-[var(--text)] disabled:opacity-50" />
)}
{/* bot_no_response 规则:选择要检测的机器人 */} {rule.id === "bot_no_response" && rule.enabled && (
{ui.monitor} {agents.map((agent) => { const selected = rule.targetAgents?.includes(agent.id) ?? true; return ( ); })} {ui.emptyMeansAll}
)}
))}
{/* 保存提示 */} {saved && (
✓ {ui.saved}
)}
); }