"use client" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" import { Button } from "./ui/button" import { Input } from "./ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" import { ScrollArea } from "./ui/scroll-area" import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" import { FileText, Search, Download, AlertTriangle, Info, CheckCircle, XCircle, Database, Activity, HardDrive, Calendar, RefreshCw, Bell, Mail, } from "lucide-react" import { useState, useEffect } from "react" interface Log { timestamp: string level: string service: string message: string source: string pid?: string hostname?: string } interface Backup { volid: string storage: string vmid: string | null type: string | null size: number size_human: string created: string timestamp: number } interface Event { upid: string type: string status: string level: string node: string user: string vmid: string starttime: string endtime: string duration: string } interface Notification { timestamp: string type: string service: string message: string source: string } interface SystemLog { timestamp: string level: string service: string message: string source: string pid?: string hostname?: string } export function SystemLogs() { const [logs, setLogs] = useState([]) const [backups, setBackups] = useState([]) const [events, setEvents] = useState([]) const [notifications, setNotifications] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [searchTerm, setSearchTerm] = useState("") const [levelFilter, setLevelFilter] = useState("all") const [serviceFilter, setServiceFilter] = useState("all") const [activeTab, setActiveTab] = useState("logs") const getApiUrl = (endpoint: string) => { if (typeof window !== "undefined") { return `${window.location.protocol}//${window.location.hostname}:8008${endpoint}` } return `http://localhost:8008${endpoint}` } // Fetch data useEffect(() => { fetchAllData() // Refresh every 30 seconds const interval = setInterval(fetchAllData, 30000) return () => clearInterval(interval) }, []) const fetchAllData = async () => { try { setLoading(true) setError(null) const [logsRes, backupsRes, eventsRes, notificationsRes] = await Promise.all([ fetchSystemLogs(), fetch(getApiUrl("/api/backups")), fetch(getApiUrl("/api/events?limit=50")), fetch(getApiUrl("/api/notifications")), ]) setLogs(logsRes) if (backupsRes.ok) { const backupsData = await backupsRes.json() setBackups(backupsData.backups || []) } if (eventsRes.ok) { const eventsData = await eventsRes.json() setEvents(eventsData.events || []) } if (notificationsRes.ok) { const notificationsData = await notificationsRes.json() setNotifications(notificationsData.notifications || []) } } catch (err) { console.error("[v0] Error fetching system logs data:", err) setError("Failed to connect to server") } finally { setLoading(false) } } const fetchSystemLogs = async (): Promise => { try { const apiUrl = getApiUrl("/api/logs") const response = await fetch(apiUrl, { method: "GET", headers: { "Content-Type": "application/json", }, cache: "no-store", }) if (!response.ok) { throw new Error(`Flask server responded with status: ${response.status}`) } const data = await response.json() return Array.isArray(data) ? data : data.logs || [] } catch (error) { console.error("[v0] Failed to fetch system logs:", error) return [] } } const handleDownloadLogs = async (type = "system") => { try { const response = await fetch(getApiUrl(`/api/logs/download?type=${type}&lines=1000`)) if (response.ok) { const blob = await response.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = `proxmox_${type}.log` document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) document.body.removeChild(a) } } catch (err) { console.error("[v0] Error downloading logs:", err) } } // Filter logs const filteredLogs = logs.filter((log) => { const matchesSearch = log.message.toLowerCase().includes(searchTerm.toLowerCase()) || log.service.toLowerCase().includes(searchTerm.toLowerCase()) const matchesLevel = levelFilter === "all" || log.level === levelFilter const matchesService = serviceFilter === "all" || log.service === serviceFilter return matchesSearch && matchesLevel && matchesService }) const getLevelColor = (level: string) => { switch (level) { case "error": case "critical": case "emergency": case "alert": return "bg-red-500/10 text-red-500 border-red-500/20" case "warning": return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" case "info": case "notice": return "bg-blue-500/10 text-blue-500 border-blue-500/20" case "success": return "bg-green-500/10 text-green-500 border-green-500/20" default: return "bg-gray-500/10 text-gray-500 border-gray-500/20" } } const getLevelIcon = (level: string) => { switch (level) { case "error": case "critical": case "emergency": case "alert": return case "warning": return case "info": case "notice": return case "success": return default: return } } const getNotificationIcon = (type: string) => { switch (type) { case "email": return case "webhook": return case "alert": return case "error": return case "success": return default: return } } const logCounts = { total: logs.length, error: logs.filter((log) => ["error", "critical", "emergency", "alert"].includes(log.level)).length, warning: logs.filter((log) => log.level === "warning").length, info: logs.filter((log) => ["info", "notice", "debug"].includes(log.level)).length, } const uniqueServices = [...new Set(logs.map((log) => log.service))] // Calculate backup statistics const backupStats = { total: backups.length, totalSize: backups.reduce((sum, b) => sum + b.size, 0), qemu: backups.filter((b) => b.type === "qemu").length, lxc: backups.filter((b) => b.type === "lxc").length, } const formatBytes = (bytes: number) => { if (bytes === 0) return "0 B" const k = 1024 const sizes = ["B", "KB", "MB", "GB", "TB"] const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}` } if (loading && logs.length === 0) { return (
) } return (
{/* Statistics Cards */}
Total Logs
{logCounts.total}

Last 200 entries

Errors
{logCounts.error}

Requires attention

Warnings
{logCounts.warning}

Monitor closely

Backups
{backupStats.total}

{formatBytes(backupStats.totalSize)}

{/* Main Content with Tabs */}
System Logs & Events
System Logs Recent Events Backups Notifications {/* System Logs Tab */}
setSearchTerm(e.target.value)} className="pl-10 bg-background border-border" />
{filteredLogs.map((log, index) => (
{getLevelIcon(log.level)} {log.level.toUpperCase()}
{log.service}
{log.timestamp}
{log.message}
Source: {log.source} {log.pid && ` • PID: ${log.pid}`} {log.hostname && ` • Host: ${log.hostname}`}
))} {filteredLogs.length === 0 && (

No logs found matching your criteria

)}
{/* Recent Events Tab */}
{events.map((event, index) => (
{getLevelIcon(event.level)} {event.status}
{event.type} {event.vmid && ` (VM/CT ${event.vmid})`}
{event.duration}
Node: {event.node} • User: {event.user}
Started: {event.starttime} • Ended: {event.endtime}
))} {events.length === 0 && (

No recent events found

)}
{/* Backups Tab */}
{backupStats.qemu}

QEMU Backups

{backupStats.lxc}

LXC Backups

{formatBytes(backupStats.totalSize)}

Total Size

{backups.map((backup, index) => (
{backup.type?.toUpperCase()} {backup.vmid && `VM ${backup.vmid}`}
{backup.size_human}
Storage: {backup.storage}
{backup.created}
{backup.volid}
))} {backups.length === 0 && (

No backups found

)}
{notifications.map((notification, index) => (
{getNotificationIcon(notification.type)}
{notification.type}
{notification.timestamp}
{notification.message}
Service: {notification.service} • Source: {notification.source}
))} {notifications.length === 0 && (

No notifications found

)}
) }