import { useEffect, useState } from 'react' import { Bell, Loader2, Play, Plus, Square } from 'lucide-react' import toast from 'react-hot-toast' import { notifyChannelsAPI } from '../api/notify_channels' import { confirmAction } from '../components/confirmAction' import type { NotifyChannel } from '../types' import { NotifyChannelCard } from './NotifyChannelCard' import { NotifyChannelFormModal } from './NotifyChannelFormModal' // NotifyChannelsPage replaces the Vue NotifyTab. Operators can register // multiple Telegram bots / Bark devices / WeChat keys / Webhooks and // fire a test notification on demand. export function NotifyChannelsPage() { const [channels, setChannels] = useState([]) const [loading, setLoading] = useState(true) const [editing, setEditing] = useState(null) const [showForm, setShowForm] = useState(false) const [testingID, setTestingID] = useState(null) const [pollingBusy, setPollingBusy] = useState<'start' | 'stop' | null>(null) const refresh = async () => { setLoading(true) try { setChannels(await notifyChannelsAPI.list()) } finally { setLoading(false) } } useEffect(() => { refresh().catch(() => undefined) }, []) const onTest = async (id: string) => { setTestingID(id) try { await notifyChannelsAPI.test(id) toast.success('测试消息已发送') } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '发送失败' toast.error(msg) } finally { setTestingID(null) } } const onStartPolling = async () => { setPollingBusy('start') try { const res = await notifyChannelsAPI.startTelegramPolling() if ((res.started ?? 0) > 0) { toast.success(`已启动 ${res.started} 个 Telegram Bot 轮询`) } else if ((res.already_running ?? 0) > 0) { toast.success('Telegram Bot 轮询已在运行') } else { toast.error(res.errors?.[0] ?? '没有可启动的 Telegram Bot 渠道') } } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '启动轮询失败' toast.error(msg) } finally { setPollingBusy(null) } } const onStopPolling = async () => { setPollingBusy('stop') try { const res = await notifyChannelsAPI.stopTelegramPolling() toast.success(res.stopped > 0 ? `已停止 ${res.stopped} 个 Telegram Bot 轮询` : '当前没有运行中的轮询') } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '停止轮询失败' toast.error(msg) } finally { setPollingBusy(null) } } const onDelete = async (ch: NotifyChannel) => { if (!(await confirmAction({ title: '删除通知渠道', message: `确定删除「${ch.name}」?`, confirmText: '删除' }))) return try { await notifyChannelsAPI.remove(ch.id) toast.success('已删除') await refresh() } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '删除失败' toast.error(msg) } } return (

通知渠道

配置 Telegram / Bark / 企业微信 / Webhook 多通道推送

{loading && (
)} {!loading && channels.length === 0 && (
暂无通知渠道
)} {!loading && channels.length > 0 && (
{channels.map((ch) => ( onTest(ch.id)} testing={testingID === ch.id} onEdit={() => { setEditing(ch) setShowForm(true) }} onDelete={() => onDelete(ch)} /> ))}
)} {showForm && ( setShowForm(false)} onSaved={async () => { setShowForm(false) await refresh() }} /> )}
) }