Update AppImage

This commit is contained in:
MacRimi
2025-10-11 17:34:10 +02:00
parent 4ad026b398
commit 1f55a0cbd8
2 changed files with 25 additions and 54 deletions

View File

@@ -27,7 +27,6 @@ import {
Menu, Menu,
Terminal, Terminal,
CalendarDays, CalendarDays,
Clock,
} from "lucide-react" } from "lucide-react"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
@@ -94,7 +93,6 @@ export function SystemLogs() {
const [searchTerm, setSearchTerm] = useState("") const [searchTerm, setSearchTerm] = useState("")
const [levelFilter, setLevelFilter] = useState("all") const [levelFilter, setLevelFilter] = useState("all")
const [serviceFilter, setServiceFilter] = useState("all") const [serviceFilter, setServiceFilter] = useState("all")
const [timeRange, setTimeRange] = useState("1") // Default 1 minute for fast loading
const [activeTab, setActiveTab] = useState("logs") const [activeTab, setActiveTab] = useState("logs")
const [selectedLog, setSelectedLog] = useState<SystemLog | null>(null) const [selectedLog, setSelectedLog] = useState<SystemLog | null>(null)
@@ -117,7 +115,7 @@ export function SystemLogs() {
useEffect(() => { useEffect(() => {
fetchAllData() fetchAllData()
}, [timeRange]) // Re-fetch when time range changes }, [])
const fetchAllData = async () => { const fetchAllData = async () => {
try { try {
@@ -157,7 +155,7 @@ export function SystemLogs() {
const fetchSystemLogs = async (): Promise<SystemLog[]> => { const fetchSystemLogs = async (): Promise<SystemLog[]> => {
try { try {
const apiUrl = getApiUrl(`/api/logs?minutes=${timeRange}`) const apiUrl = getApiUrl("/api/logs")
const response = await fetch(apiUrl, { const response = await fetch(apiUrl, {
method: "GET", method: "GET",
@@ -296,20 +294,6 @@ export function SystemLogs() {
} }
} }
const getNotificationTypeColor = (type: string) => {
const lowerType = type.toLowerCase()
if (lowerType.includes("error") || lowerType.includes("critical") || lowerType.includes("alert")) {
return "bg-red-500/10 text-red-500 border-red-500/20"
} else if (lowerType.includes("warning") || lowerType.includes("warn")) {
return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
} else if (lowerType.includes("success") || lowerType.includes("ok")) {
return "bg-green-500/10 text-green-500 border-green-500/20"
} else if (lowerType.includes("info")) {
return "bg-blue-500/10 text-blue-500 border-blue-500/20"
}
return "bg-gray-500/10 text-gray-500 border-gray-500/20"
}
const logCounts = { const logCounts = {
total: logs.length, total: logs.length,
error: logs.filter((log) => ["error", "critical", "emergency", "alert"].includes(log.level)).length, error: logs.filter((log) => ["error", "critical", "emergency", "alert"].includes(log.level)).length,
@@ -568,22 +552,6 @@ export function SystemLogs() {
</div> </div>
</div> </div>
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-full sm:w-[180px] bg-background border-border">
<Clock className="h-4 w-4 mr-2" />
<SelectValue placeholder="Time range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Last 1 minute</SelectItem>
<SelectItem value="5">Last 5 minutes</SelectItem>
<SelectItem value="15">Last 15 minutes</SelectItem>
<SelectItem value="30">Last 30 minutes</SelectItem>
<SelectItem value="60">Last 1 hour</SelectItem>
<SelectItem value="360">Last 6 hours</SelectItem>
<SelectItem value="1440">Last 24 hours</SelectItem>
</SelectContent>
</Select>
<Select value={levelFilter} onValueChange={setLevelFilter}> <Select value={levelFilter} onValueChange={setLevelFilter}>
<SelectTrigger className="w-full sm:w-[180px] bg-background border-border"> <SelectTrigger className="w-full sm:w-[180px] bg-background border-border">
<SelectValue placeholder="Filter by level" /> <SelectValue placeholder="Filter by level" />
@@ -798,22 +766,26 @@ export function SystemLogs() {
setIsNotificationModalOpen(true) setIsNotificationModalOpen(true)
}} }}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0 flex items-center gap-2">
<Badge variant="outline" className={getNotificationTypeColor(notification.type)}>
{getNotificationIcon(notification.type)} {getNotificationIcon(notification.type)}
{notification.type.toUpperCase()} <span className="text-sm font-medium text-muted-foreground capitalize md:hidden">
</Badge> {notification.type}
</span>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<div className="text-sm font-medium text-foreground truncate">{notification.service}</div> <div className="text-sm font-medium text-muted-foreground capitalize truncate hidden md:block">
{notification.type}
</div>
<div className="text-xs text-muted-foreground font-mono whitespace-nowrap ml-2"> <div className="text-xs text-muted-foreground font-mono whitespace-nowrap ml-2">
{notification.timestamp} {notification.timestamp}
</div> </div>
</div> </div>
<div className="text-sm text-foreground mb-1 line-clamp-2">{notification.message}</div> <div className="text-sm text-foreground mb-1 line-clamp-2">{notification.message}</div>
<div className="text-xs text-muted-foreground truncate">Source: {notification.source}</div> <div className="text-xs text-muted-foreground truncate">
Service: {notification.service} Source: {notification.source}
</div>
</div> </div>
</div> </div>
))} ))}
@@ -1021,10 +993,10 @@ export function SystemLogs() {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<div className="text-sm font-medium text-muted-foreground mb-1">Type</div> <div className="text-sm font-medium text-muted-foreground mb-1">Type</div>
<Badge variant="outline" className={getNotificationTypeColor(selectedNotification.type)}> <div className="flex items-center gap-2">
{getNotificationIcon(selectedNotification.type)} {getNotificationIcon(selectedNotification.type)}
{selectedNotification.type.toUpperCase()} <span className="text-sm text-foreground capitalize">{selectedNotification.type}</span>
</Badge> </div>
</div> </div>
<div className="sm:col-span-2"> <div className="sm:col-span-2">
<div className="text-sm font-medium text-muted-foreground mb-1">Timestamp</div> <div className="text-sm font-medium text-muted-foreground mb-1">Timestamp</div>
@@ -1040,11 +1012,10 @@ export function SystemLogs() {
</div> </div>
</div> </div>
<div> <div>
<div className="text-sm font-medium text-muted-foreground mb-2">Message (First 20 lines)</div> <div className="text-sm font-medium text-muted-foreground mb-2">Message</div>
<div className="p-4 rounded-lg bg-muted/50 border border-border max-h-[300px] overflow-y-auto"> <div className="p-4 rounded-lg bg-muted/50 border border-border max-h-[300px] overflow-y-auto">
<pre className="text-sm text-foreground whitespace-pre-wrap break-words"> <pre className="text-sm text-foreground whitespace-pre-wrap break-words">
{selectedNotification.message.split("\n").slice(0, 20).join("\n")} {selectedNotification.message}
{selectedNotification.message.split("\n").length > 20 && "\n\n... (download full log to see more)"}
</pre> </pre>
</div> </div>
</div> </div>

View File

@@ -3339,11 +3339,11 @@ def api_vms():
def api_logs(): def api_logs():
"""Get system logs""" """Get system logs"""
try: try:
minutes = request.args.get('minutes', '1') # Default 1 minute for fast loading limit = request.args.get('limit', '200')
priority = request.args.get('priority', None) # 0-7 (0=emerg, 3=err, 4=warning, 6=info) priority = request.args.get('priority', None) # 0-7 (0=emerg, 3=err, 4=warning, 6=info)
service = request.args.get('service', None) service = request.args.get('service', None)
cmd = ['journalctl', '--since', f'{minutes} minutes ago', '--output', 'json', '--no-pager'] cmd = ['journalctl', '-n', limit, '--output', 'json', '--no-pager']
# Add priority filter if specified # Add priority filter if specified
if priority: if priority:
@@ -3390,14 +3390,14 @@ def api_logs():
'error': 'journalctl not available or failed', 'error': 'journalctl not available or failed',
'logs': [], 'logs': [],
'total': 0 'total': 0
}), 500 })
except Exception as e: except Exception as e:
print(f"Error getting logs: {e}") print(f"Error getting logs: {e}")
return jsonify({ return jsonify({
'error': str(e), 'error': f'Unable to access system logs: {str(e)}',
'logs': [], 'logs': [],
'total': 0 'total': 0
}), 500 })
@app.route('/api/logs/download', methods=['GET']) @app.route('/api/logs/download', methods=['GET'])
def api_logs_download(): def api_logs_download():
@@ -3609,7 +3609,7 @@ def api_notifications_download():
download_name=f'notification_{timestamp.replace(":", "_").replace(" ", "_")}.log' download_name=f'notification_{timestamp.replace(":", "_").replace(" ", "_")}.log'
) )
else: else:
return jsonify({'error': 'Failed to generate log file'}), 500 return jsonify({'error': 'Failed to generate notification log'}), 500
except Exception as e: except Exception as e:
print(f"Error downloading notification log: {e}") print(f"Error downloading notification log: {e}")
@@ -3969,7 +3969,7 @@ def api_gpu_realtime(slot):
def api_vm_details(vmid): def api_vm_details(vmid):
"""Get detailed information for a specific VM/LXC""" """Get detailed information for a specific VM/LXC"""
try: try:
result = subprocess.run(['pvesh', 'get', '/cluster/resources', '--type', 'vm', '--output-format', 'json'], result = subprocess.run(['pvesh', 'get', f'/cluster/resources', '--type', 'vm', '--output-format', 'json'],
capture_output=True, text=True, timeout=10) capture_output=True, text=True, timeout=10)
if result.returncode == 0: if result.returncode == 0: