← Назад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>
);
}