diff --git a/AppImage/components/system-logs.tsx b/AppImage/components/system-logs.tsx index dbf6e68..8e9e16c 100644 --- a/AppImage/components/system-logs.tsx +++ b/AppImage/components/system-logs.tsx @@ -7,6 +7,7 @@ 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 { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog" import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "./ui/sheet" import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover" import { Calendar } from "./ui/calendar" @@ -20,6 +21,7 @@ import { XCircle, Database, Activity, + HardDrive, CalendarIcon, RefreshCw, Bell, @@ -100,18 +102,18 @@ export function SystemLogs() { const [selectedLog, setSelectedLog] = useState(null) const [selectedEvent, setSelectedEvent] = useState(null) const [selectedBackup, setSelectedBackup] = useState(null) - const [selectedNotification, setSelectedNotification] = useState(null) // Added + const [selectedNotification, setSelectedNotification] = useState(null) const [isLogModalOpen, setIsLogModalOpen] = useState(false) const [isEventModalOpen, setIsEventModalOpen] = useState(false) const [isBackupModalOpen, setIsBackupModalOpen] = useState(false) - const [isNotificationModalOpen, setIsNotificationModalOpen] = useState(false) // Added + const [isNotificationModalOpen, setIsNotificationModalOpen] = useState(false) const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) const [dateFilter, setDateFilter] = useState("now") const [customDays, setCustomDays] = useState("1") const [dateRange, setDateRange] = useState(undefined) - const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) + const [isCalendarOpen, setIsCalendarOpen] = useState(false) const getApiUrl = (endpoint: string) => { if (typeof window !== "undefined") { @@ -125,11 +127,11 @@ export function SystemLogs() { }, []) useEffect(() => { - if (dateFilter !== "custom") { - // Reload logs when non-custom filter changes + if (dateFilter !== "now" && dateFilter !== "custom") { + // Reload logs when a predefined time range is selected fetchSystemLogs().then(setLogs) } - }, [dateFilter, customDays]) + }, [dateFilter]) const fetchAllData = async () => { try { @@ -167,25 +169,17 @@ export function SystemLogs() { } } + const handleApplyDateRange = async () => { + if (dateRange?.from && dateRange?.to) { + setIsCalendarOpen(false) + const logsRes = await fetchSystemLogs() + setLogs(logsRes) + } + } + const fetchSystemLogs = async (): Promise => { try { - let apiUrl = getApiUrl("/api/logs") - - const params = new URLSearchParams() - - if (dateFilter === "custom" && dateRange?.from && dateRange?.to) { - params.append("from_date", format(dateRange.from, "yyyy-MM-dd")) - params.append("to_date", format(dateRange.to, "yyyy-MM-dd")) - } else if (dateFilter !== "now") { - const daysAgo = dateFilter === "custom" ? Number.parseInt(customDays) : Number.parseInt(dateFilter) - params.append("since_days", daysAgo.toString()) - } - - if (params.toString()) { - apiUrl += `?${params.toString()}` - } - - console.log("[v0] Fetching logs from:", apiUrl) + const apiUrl = getApiUrl("/api/logs") const response = await fetch(apiUrl, { method: "GET", @@ -474,23 +468,6 @@ export function SystemLogs() { return twoYearsAgo } - const handleApplyDateRange = async () => { - if (dateRange?.from && dateRange?.to) { - console.log("[v0] Applying date range filter:", dateRange) - setIsDatePickerOpen(false) - setLoading(true) - try { - const newLogs = await fetchSystemLogs() - setLogs(newLogs) - console.log("[v0] Loaded", newLogs.length, "logs for date range") - } catch (error) { - console.error("[v0] Error loading logs for date range:", error) - } finally { - setLoading(false) - } - } - } - if (loading && logs.length === 0) { return (
@@ -665,7 +642,7 @@ export function SystemLogs() { {dateFilter === "custom" && ( - + -
+ date > new Date() || date < getMinDate()} + /> +
+
@@ -817,14 +792,12 @@ export function SystemLogs() { {event.type} {event.vmid && ` (VM/CT ${event.vmid})`} -
- {event.starttime} -
+
{event.duration}
-
{event.message}
Node: {event.node} • User: {event.user}
+
{event.starttime}
))} @@ -832,7 +805,7 @@ export function SystemLogs() { {events.length === 0 && (
-

No events found

+

No recent events found

)} @@ -841,32 +814,64 @@ export function SystemLogs() { {/* Backups Tab */} - +
+ + +
{backupStats.qemu}
+

QEMU Backups

+
+
+ + +
{backupStats.lxc}
+

LXC Backups

+
+
+ + +
{formatBytes(backupStats.totalSize)}
+

Total Size

+
+
+
+ +
{backups.map((backup, index) => (
{ setSelectedBackup(backup) setIsBackupModalOpen(true) }} >
- - {getBackupTypeLabel(backup.volid)} - +
-
-
{backup.storage}
-
- {backup.created} +
+
+ + {getBackupTypeLabel(backup.volid)} + + + {getBackupStorageLabel(backup.volid)} +
+ + {backup.size_human} + +
+
Storage: {backup.storage}
+
+ + {backup.created}
-
{backup.volid}
-
Size: {backup.size_human}
))} @@ -894,22 +899,23 @@ export function SystemLogs() { setIsNotificationModalOpen(true) }} > -
+
+ {getNotificationIcon(notification.type)} - {getNotificationIcon(notification.type)} {notification.type.toUpperCase()}
-
{notification.service}
-
+
{notification.timestamp}
{notification.message}
-
Source: {notification.source}
+
+ Service: {notification.service} • Source: {notification.source} +
))} @@ -926,6 +932,236 @@ export function SystemLogs() { + + + + + + + Log Details + + Complete information about this log entry + + {selectedLog && ( +
+
+
+
Level
+ + {getLevelIcon(selectedLog.level)} + {selectedLog.level.toUpperCase()} + +
+
+
Service
+
{selectedLog.service}
+
+
+
Timestamp
+
{selectedLog.timestamp}
+
+
+
Source
+
{selectedLog.source}
+
+ {selectedLog.pid && ( +
+
Process ID
+
{selectedLog.pid}
+
+ )} + {selectedLog.hostname && ( +
+
Hostname
+
{selectedLog.hostname}
+
+ )} +
+
+
Message
+
+
{selectedLog.message}
+
+
+
+ )} +
+
+ + + + + + + Event Details + + Complete information about this event + + {selectedEvent && ( +
+
+
+
Status
+ + {getLevelIcon(selectedEvent.level)} + {selectedEvent.status} + +
+
+
Type
+
{selectedEvent.type}
+
+
+
Node
+
{selectedEvent.node}
+
+
+
User
+
{selectedEvent.user}
+
+ {selectedEvent.vmid && ( +
+
VM/CT ID
+
{selectedEvent.vmid}
+
+ )} +
+
Duration
+
{selectedEvent.duration}
+
+
+
Start Time
+
{selectedEvent.starttime}
+
+
+
End Time
+
{selectedEvent.endtime}
+
+
+
+
UPID
+
+
+                    {selectedEvent.upid}
+                  
+
+
+
+ )} +
+
+ + + + + + + Backup Details + + Complete information about this backup + + {selectedBackup && ( +
+
+
+
Type
+ + {getBackupTypeLabel(selectedBackup.volid)} + +
+
+
Storage Type
+ + {getBackupStorageLabel(selectedBackup.volid)} + +
+
+
Storage
+
{selectedBackup.storage}
+
+
+
Size
+ + {selectedBackup.size_human} + +
+ {selectedBackup.vmid && ( +
+
VM/CT ID
+
{selectedBackup.vmid}
+
+ )} +
+
Created
+
{selectedBackup.created}
+
+
+
+
Volume ID
+
+
+                    {selectedBackup.volid}
+                  
+
+
+
+ )} +
+
+ + + + + + + Notification Details + + Complete information about this notification + + {selectedNotification && ( +
+
+
+
Type
+ + {selectedNotification.type.toUpperCase()} + +
+
+
Timestamp
+
{selectedNotification.timestamp}
+
+
+
Service
+
{selectedNotification.service}
+
+
+
Source
+
{selectedNotification.source}
+
+
+
+
Message
+
+
+                    {selectedNotification.message}
+                  
+
+
+
+ +
+
+ )} +
+
) } diff --git a/AppImage/components/ui/calendar.tsx b/AppImage/components/ui/calendar.tsx index 399be24..553b906 100644 --- a/AppImage/components/ui/calendar.tsx +++ b/AppImage/components/ui/calendar.tsx @@ -22,31 +22,24 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C nav: "space-x-1 flex items-center", nav_button: cn( buttonVariants({ variant: "outline" }), - "h-8 w-8 bg-background p-0 hover:bg-accent hover:text-accent-foreground border-border", + "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100", ), nav_button_previous: "absolute left-1", nav_button_next: "absolute right-1", table: "w-full border-collapse space-y-1", - head_row: "grid grid-cols-7 gap-1", - head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem] flex items-center justify-center", - row: "grid grid-cols-7 gap-1 mt-2", - cell: cn( - "h-9 w-9 text-center text-sm p-0 relative flex items-center justify-center", - "[&:has([aria-selected].day-range-end)]:rounded-r-md", - "[&:has([aria-selected].day-range-start)]:rounded-l-md", - "[&:has([aria-selected].day-range-middle)]:bg-primary/20", - "focus-within:relative focus-within:z-20", - ), + head_row: "flex", + head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]", + row: "flex w-full mt-2", + cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", day: cn(buttonVariants({ variant: "ghost" }), "h-9 w-9 p-0 font-normal aria-selected:opacity-100"), - day_range_end: "day-range-end rounded-r-md", - day_range_start: "day-range-start rounded-l-md", + day_range_end: "day-range-end", day_selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", - day_today: "bg-accent text-accent-foreground font-bold", + day_today: "bg-accent text-accent-foreground", day_outside: "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", day_disabled: "text-muted-foreground opacity-50", - day_range_middle: "aria-selected:bg-primary/20 aria-selected:text-foreground rounded-none", + day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground", day_hidden: "invisible", ...classNames, }}