Update AppImage

This commit is contained in:
MacRimi
2025-10-11 17:55:25 +02:00
parent 1f55a0cbd8
commit 5aaaeb426c
2 changed files with 112 additions and 31 deletions

View File

@@ -106,6 +106,9 @@ export function SystemLogs() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const [dateFilter, setDateFilter] = useState("now")
const [customDays, setCustomDays] = useState("1")
const getApiUrl = (endpoint: string) => { const getApiUrl = (endpoint: string) => {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
return `${window.location.protocol}//${window.location.hostname}:8008${endpoint}` return `${window.location.protocol}//${window.location.hostname}:8008${endpoint}`
@@ -179,7 +182,19 @@ export function SystemLogs() {
const handleDownloadLogs = async (type = "system") => { const handleDownloadLogs = async (type = "system") => {
try { try {
const hours = 48 let hours = 48
if (filteredLogs.length > 0) {
const lastLog = filteredLogs[filteredLogs.length - 1]
const lastLogTime = new Date(lastLog.timestamp)
const now = new Date()
const diffMs = now.getTime() - lastLogTime.getTime()
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
// Download 48 hours from the last visible log
hours = 48
}
let url = getApiUrl(`/api/logs/download?type=${type}&hours=${hours}`) let url = getApiUrl(`/api/logs/download?type=${type}&hours=${hours}`)
// Apply filters if any are active // Apply filters if any are active
@@ -190,6 +205,11 @@ export function SystemLogs() {
url += `&service=${serviceFilter}` url += `&service=${serviceFilter}`
} }
if (dateFilter !== "now") {
const daysAgo = dateFilter === "custom" ? Number.parseInt(customDays) : Number.parseInt(dateFilter)
url += `&since_days=${daysAgo}`
}
const response = await fetch(url) const response = await fetch(url)
if (response.ok) { if (response.ok) {
const blob = await response.blob() const blob = await response.blob()
@@ -208,23 +228,31 @@ export function SystemLogs() {
} }
const handleDownloadNotificationLog = async (notification: Notification) => { const handleDownloadNotificationLog = async (notification: Notification) => {
// Added
try { try {
// Download the complete log for this notification const blob = new Blob(
const response = await fetch(getApiUrl(`/api/notifications/download?timestamp=${notification.timestamp}`)) [
if (response.ok) { `Notification Details\n`,
const blob = await response.blob() `==================\n\n`,
const url = window.URL.createObjectURL(blob) `Timestamp: ${notification.timestamp}\n`,
const a = document.createElement("a") `Type: ${notification.type}\n`,
a.href = url `Service: ${notification.service}\n`,
a.download = `notification_${notification.timestamp.replace(/[:\s]/g, "_")}.log` `Source: ${notification.source}\n\n`,
document.body.appendChild(a) `Complete Message:\n`,
a.click() `${notification.message}\n`,
window.URL.revokeObjectURL(url) ],
document.body.removeChild(a) { type: "text/plain" },
} )
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `notification_${notification.timestamp.replace(/[:\s]/g, "_")}.txt`
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
} catch (err) { } catch (err) {
console.error("[v0] Error downloading notification log:", err) console.error("[v0] Error downloading notification:", err)
} }
} }
@@ -294,6 +322,21 @@ export function SystemLogs() {
} }
} }
const getNotificationTypeColor = (type: string) => {
switch (type.toLowerCase()) {
case "error":
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":
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 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,
@@ -552,6 +595,32 @@ export function SystemLogs() {
</div> </div>
</div> </div>
<Select value={dateFilter} onValueChange={setDateFilter}>
<SelectTrigger className="w-full sm:w-[180px] bg-background border-border">
<SelectValue placeholder="Time range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="now">Current logs</SelectItem>
<SelectItem value="1">1 day ago</SelectItem>
<SelectItem value="3">3 days ago</SelectItem>
<SelectItem value="7">1 week ago</SelectItem>
<SelectItem value="14">2 weeks ago</SelectItem>
<SelectItem value="30">1 month ago</SelectItem>
<SelectItem value="custom">Custom days</SelectItem>
</SelectContent>
</Select>
{dateFilter === "custom" && (
<Input
type="number"
placeholder="Days ago"
value={customDays}
onChange={(e) => setCustomDays(e.target.value)}
className="w-full sm:w-[120px] bg-background border-border"
min="1"
/>
)}
<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" />
@@ -768,16 +837,13 @@ export function SystemLogs() {
> >
<div className="flex-shrink-0 flex items-center gap-2"> <div className="flex-shrink-0 flex items-center gap-2">
{getNotificationIcon(notification.type)} {getNotificationIcon(notification.type)}
<span className="text-sm font-medium text-muted-foreground capitalize md:hidden"> <Badge variant="outline" className={getNotificationTypeColor(notification.type)}>
{notification.type} {notification.type.toUpperCase()}
</span> </Badge>
</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-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>
@@ -993,10 +1059,9 @@ 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>
<div className="flex items-center gap-2"> <Badge variant="outline" className={getNotificationTypeColor(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>
@@ -1026,7 +1091,7 @@ export function SystemLogs() {
className="border-border w-full sm:w-auto" className="border-border w-full sm:w-auto"
> >
<Download className="h-4 w-4 mr-2" /> <Download className="h-4 w-4 mr-2" />
Download Complete Log Download Complete Message
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -3404,11 +3404,27 @@ def api_logs_download():
"""Download system logs as a text file""" """Download system logs as a text file"""
try: try:
log_type = request.args.get('type', 'system') log_type = request.args.get('type', 'system')
hours = int(request.args.get('hours', '48')) # Changed from lines to hours, default 48h hours = int(request.args.get('hours', '48'))
level = request.args.get('level', 'all') # Added level filter level = request.args.get('level', 'all')
service = request.args.get('service', 'all') # Added service filter service = request.args.get('service', 'all')
since_days = request.args.get('since_days', None)
cmd = ['journalctl', '--since', f'{hours} hours ago', '--no-pager'] if since_days:
days = int(since_days)
# Original code: cmd = ['journalctl', '--since', f'{days} days ago', '--until', f'{days - 1} days ago', '--no-pager']
# This logic seems incorrect if we want logs FROM since_days ago.
# Correct logic: logs from 'days' ago until 'now' (or 'days-1' ago for a specific 24h period)
# For simplicity and to keep the original intent of filtering *from* X days ago, let's use '--since'.
# If 'since_days' is 1, it means logs from yesterday until now.
# If 'since_days' is 2, it means logs from the day before yesterday until now.
# Let's assume 'since_days' means the number of *full 24-hour periods* to go back.
# So, if since_days = 1, we want logs from 24 hours ago.
# If since_days = 2, we want logs from 48 hours ago.
# The original '--until' logic was problematic. Let's simplify.
# If 'since_days' is provided, use it as the primary time filter.
cmd = ['journalctl', '--since', f'{days} days ago', '--no-pager']
else:
cmd = ['journalctl', '--since', f'{hours} hours ago', '--no-pager']
if log_type == 'kernel': if log_type == 'kernel':
cmd.extend(['-k']) cmd.extend(['-k'])