← Назад
import { useState } from 'react'; import { Bell, AlertCircle, CheckCircle2, Info } from 'lucide-react'; import * as Popover from '@radix-ui/react-popover'; import { useSystem } from '../hooks/useSystem'; export function NotificationCenter() { const [open, setOpen] = useState(false); const { data } = useSystem(); // Extract issues from system health data to show as notifications // In a real app, this would come from a dedicated /api/notifications endpoint const getNotifications = () => { const notifications = []; // Check CPU const cpuUsage = parseFloat(data?.kpis?.cpu?.usage || '0'); if (cpuUsage > 80) { notifications.push({ id: 'cpu-high', type: 'warning', title: 'Высокая нагрузка CPU', message: `CPU используется на ${cpuUsage}%`, time: new Date().toLocaleTimeString(), }); } // Check Memory const memUsage = parseFloat(data?.kpis?.memory?.usedPercent || '0'); if (memUsage > 90) { notifications.push({ id: 'mem-high', type: 'warning', title: 'Критическое использование RAM', message: `Память заполнена на ${memUsage}%`, time: new Date().toLocaleTimeString(), }); } // Check OpenClaw Gateway if (data?.kpis?.gateway?.status !== 'running') { notifications.push({ id: 'gateway-error', type: 'error', title: 'Ошибка OpenClaw Gateway', message: 'Сервис шлюза недоступен или отключен', time: new Date().toLocaleTimeString(), }); } // Check overall status if (data?.status === 'error') { notifications.push({ id: 'system-error', type: 'error', title: 'Системная ошибка', message: 'Некоторые базовые сервисы недоступны', time: new Date().toLocaleTimeString(), }); } // Placeholder success if empty if (notifications.length === 0 && data) { notifications.push({ id: 'system-ok', type: 'success', title: 'Система стабильна', message: 'Все метрики в норме', time: new Date().toLocaleTimeString(), }); } return notifications; }; const notifications = getNotifications(); const unreadCount = notifications.filter(n => n.type === 'error' || n.type === 'warning').length; return ( <Popover.Root open={open} onOpenChange={setOpen}> <Popover.Trigger asChild> <button className="relative p-2 rounded-xl text-gray-400 hover:text-white hover:bg-white/10 transition-colors"> <Bell className="w-5 h-5" /> {unreadCount > 0 && ( <span className="absolute top-1.5 right-1.5 w-2 h-2 bg-danger rounded-full animate-pulse" /> )} </button> </Popover.Trigger> <Popover.Portal> <Popover.Content className="z-50 w-80 rounded-xl bg-slate-900 border border-white/10 shadow-2xl p-4 mr-4 mt-2" sideOffset={5} align="end" > <div className="flex items-center justify-between mb-4 pb-2 border-b border-white/10"> <h3 className="font-semibold text-white">Уведомления</h3> {unreadCount > 0 && ( <span className="text-xs bg-danger/20 text-danger px-2 py-0.5 rounded-full"> {unreadCount} новых </span> )} </div> <div className="space-y-3 max-h-[300px] overflow-y-auto pr-1"> {notifications.length === 0 ? ( <div className="text-center py-4 text-gray-500 text-sm"> Нет новых уведомлений </div> ) : ( notifications.map(notif => ( <div key={notif.id} className="flex gap-3 text-sm bg-white/5 p-3 rounded-lg border border-white/5"> <div className="flex-shrink-0 mt-0.5"> {notif.type === 'error' && <AlertCircle className="w-4 h-4 text-danger" />} {notif.type === 'warning' && <Info className="w-4 h-4 text-warning" />} {notif.type === 'success' && <CheckCircle2 className="w-4 h-4 text-success" />} </div> <div> <div className="font-medium text-white">{notif.title}</div> <div className="text-xs text-gray-400 mt-1">{notif.message}</div> <div className="text-[10px] text-gray-500 mt-2">{notif.time}</div> </div> </div> )) )} </div> <Popover.Arrow className="fill-slate-900 stroke-1 stroke-white/10" /> </Popover.Content> </Popover.Portal> </Popover.Root> ); }