Update AppImage

This commit is contained in:
MacRimi
2025-10-18 10:43:58 +02:00
parent 0ee574eaaa
commit d52ce400fb
2 changed files with 37 additions and 65 deletions

View File

@@ -455,7 +455,7 @@ export function ProxmoxDashboard() {
</TabsContent> </TabsContent>
<TabsContent value="logs" className="space-y-4 md:space-y-6"> <TabsContent value="logs" className="space-y-4 md:space-y-6">
<SystemLogs key={`logs-${componentKey}`} nodeName={systemStatus.serverName} /> <SystemLogs key={`logs-${componentKey}`} />
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View File

@@ -95,13 +95,7 @@ interface CombinedLogEntry {
sortTimestamp: number sortTimestamp: number
} }
// MODIFIED: Fixed props interface - nodeName is already optional export function SystemLogs() {
interface SystemLogsProps {
nodeName?: string
}
// MODIFIED: Fixed function signature - removed incorrect default value syntax
export function SystemLogs({ nodeName }: SystemLogsProps) {
const [logs, setLogs] = useState<SystemLog[]>([]) const [logs, setLogs] = useState<SystemLog[]>([])
const [backups, setBackups] = useState<Backup[]>([]) const [backups, setBackups] = useState<Backup[]>([])
const [events, setEvents] = useState<Event[]>([]) const [events, setEvents] = useState<Event[]>([])
@@ -132,13 +126,9 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
const getApiUrl = (endpoint: string) => { const getApiUrl = (endpoint: string) => {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
// MODIFIED: Appended nodeName to URL if provided return `${window.location.protocol}//${window.location.hostname}:8008${endpoint}`
const baseUrl = `${window.location.protocol}//${window.location.hostname}:8008${endpoint}`
return nodeName ? `${baseUrl}?node=${nodeName}` : baseUrl
} }
// MODIFIED: Appended nodeName to URL if provided return `http://localhost:8008${endpoint}`
const baseUrl = `http://localhost:8008${endpoint}`
return nodeName ? `${baseUrl}?node=${nodeName}` : baseUrl
} }
useEffect(() => { useEffect(() => {
@@ -162,7 +152,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
console.error("[v0] Error loading logs:", err) console.error("[v0] Error loading logs:", err)
setLoading(false) setLoading(false)
}) })
}, [dateFilter, customDays, nodeName]) // Added nodeName dependency }, [dateFilter, customDays])
useEffect(() => { useEffect(() => {
console.log("[v0] Level or service filter changed:", levelFilter, serviceFilter) console.log("[v0] Level or service filter changed:", levelFilter, serviceFilter)
@@ -184,19 +174,16 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
console.error("[v0] Error loading logs:", err) console.error("[v0] Error loading logs:", err)
setLoading(false) setLoading(false)
}) })
}, [levelFilter, serviceFilter, nodeName]) // Added nodeName dependency }, [levelFilter, serviceFilter])
const fetchAllData = async () => { const fetchAllData = async () => {
try { try {
setLoading(true) setLoading(true)
setError(null) setError(null)
// Similar to how /api/vms filters VMs/LXCs by node. This ensures that in a cluster
// environment, only backups from the current node are displayed.
const [logsRes, backupsRes, eventsRes, notificationsRes] = await Promise.all([ const [logsRes, backupsRes, eventsRes, notificationsRes] = await Promise.all([
fetchSystemLogs(), fetchSystemLogs(),
// MODIFIED: getApiUrl will now include nodeName if provided fetch(getApiUrl("/api/backups")),
fetch(getApiUrl("/api/backups")), // Backend should filter by node
fetch(getApiUrl("/api/events?limit=50")), fetch(getApiUrl("/api/events?limit=50")),
fetch(getApiUrl("/api/notifications")), fetch(getApiUrl("/api/notifications")),
]) ])
@@ -308,8 +295,8 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
if (searchTerm) { if (searchTerm) {
filters.push("searched") filters.push("searched")
} }
// MODIFIED: Include nodeName in filename if available
const filename = `proxmox_logs${nodeName ? `_${nodeName}` : ""}_${filters.length > 0 ? filters.join("_") + "_" : ""}${new Date().toISOString().split("T")[0]}.txt` const filename = `proxmox_logs_${filters.length > 0 ? filters.join("_") + "_" : ""}${new Date().toISOString().split("T")[0]}.txt`
// Generate log content // Generate log content
const logContent = [ const logContent = [
@@ -322,8 +309,6 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
`- Level: ${levelFilter === "all" ? "All Levels" : levelFilter}`, `- Level: ${levelFilter === "all" ? "All Levels" : levelFilter}`,
`- Service: ${serviceFilter === "all" ? "All Services" : serviceFilter}`, `- Service: ${serviceFilter === "all" ? "All Services" : serviceFilter}`,
`- Search: ${searchTerm || "None"}`, `- Search: ${searchTerm || "None"}`,
// MODIFIED: Include Node in filters if available
nodeName && `- Node: ${nodeName}`,
``, ``,
`${"=".repeat(80)}`, `${"=".repeat(80)}`,
``, ``,
@@ -367,7 +352,6 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
if (upid) { if (upid) {
// Try to fetch the complete task log from Proxmox // Try to fetch the complete task log from Proxmox
try { try {
// MODIFIED: getApiUrl will now include nodeName if provided
const response = await fetch(getApiUrl(`/api/task-log/${encodeURIComponent(upid)}`)) const response = await fetch(getApiUrl(`/api/task-log/${encodeURIComponent(upid)}`))
if (response.ok) { if (response.ok) {
@@ -455,10 +439,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
const matchesLevel = levelFilter === "all" || log.level === levelFilter const matchesLevel = levelFilter === "all" || log.level === levelFilter
const matchesService = serviceFilter === "all" || log.service === serviceFilter const matchesService = serviceFilter === "all" || log.service === serviceFilter
// MODIFIED: Filter by nodeName if provided return matchesSearch && matchesLevel && matchesService
const matchesNode = !nodeName || log.source.includes(`Node: ${nodeName}`)
return matchesSearch && matchesLevel && matchesService && matchesNode
}) })
const displayedLogs = filteredCombinedLogs.slice(0, displayedLogsCount) const displayedLogs = filteredCombinedLogs.slice(0, displayedLogsCount)
@@ -555,10 +536,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
info: logs.filter((log) => ["info", "notice", "debug"].includes(log.level)).length, info: logs.filter((log) => ["info", "notice", "debug"].includes(log.level)).length,
} }
// MODIFIED: Filter uniqueServices by nodeName if provided const uniqueServices = [...new Set(logs.map((log) => log.service))]
const uniqueServices = [
...new Set(logs.filter((log) => !nodeName || log.source.includes(`Node: ${nodeName}`)).map((log) => log.service)),
]
const getBackupType = (volid: string): "vm" | "lxc" => { const getBackupType = (volid: string): "vm" | "lxc" => {
if (volid.includes("/vm/") || volid.includes("vzdump-qemu")) { if (volid.includes("/vm/") || volid.includes("vzdump-qemu")) {
@@ -600,16 +578,14 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
return type === "pbs" ? "PBS" : "PVE" return type === "pbs" ? "PBS" : "PVE"
} }
// MODIFIED: Filter backups by nodeName if provided
const filteredBackups = nodeName ? backups.filter((b) => b.storage.includes(nodeName)) : backups
const backupStats = { const backupStats = {
total: filteredBackups.length, total: backups.length,
totalSize: filteredBackups.reduce((sum, b) => sum + b.size, 0), totalSize: backups.reduce((sum, b) => sum + b.size, 0),
qemu: filteredBackups.filter((b) => { qemu: backups.filter((b) => {
// Check if volid contains /vm/ for QEMU or vzdump-qemu for PVE // Check if volid contains /vm/ for QEMU or vzdump-qemu for PVE
return b.volid.includes("/vm/") || b.volid.includes("vzdump-qemu") return b.volid.includes("/vm/") || b.volid.includes("vzdump-qemu")
}).length, }).length,
lxc: filteredBackups.filter((b) => { lxc: backups.filter((b) => {
// Check if volid contains /ct/ for LXC or vzdump-lxc for PVE // Check if volid contains /ct/ for LXC or vzdump-lxc for PVE
return b.volid.includes("/ct/") || b.volid.includes("vzdump-lxc") return b.volid.includes("/ct/") || b.volid.includes("vzdump-lxc")
}).length, }).length,
@@ -653,8 +629,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
} }
} }
// MODIFIED: Conditionally render loading state based on nodeName if (loading && logs.length === 0 && events.length === 0) {
if (loading && logs.length === 0 && events.length === 0 && backups.length === 0 && notifications.length === 0) {
return ( return (
<div className="flex items-center justify-center h-64"> <div className="flex items-center justify-center h-64">
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" /> <RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" />
@@ -664,7 +639,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{loading && (logs.length > 0 || events.length > 0 || backups.length > 0 || notifications.length > 0) && ( {loading && (logs.length > 0 || events.length > 0) && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center"> <div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center">
<div className="flex flex-col items-center gap-4 p-8 rounded-lg bg-card border border-border shadow-lg"> <div className="flex flex-col items-center gap-4 p-8 rounded-lg bg-card border border-border shadow-lg">
<RefreshCw className="h-12 w-12 animate-spin text-primary" /> <RefreshCw className="h-12 w-12 animate-spin text-primary" />
@@ -730,8 +705,6 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
<CardTitle className="text-foreground flex items-center"> <CardTitle className="text-foreground flex items-center">
<Activity className="h-5 w-5 mr-2" /> <Activity className="h-5 w-5 mr-2" />
System Logs & Events System Logs & Events
{/* MODIFIED: Display nodeName if provided */}
{nodeName && <span className="text-muted-foreground ml-2 text-sm">({nodeName})</span>}
</CardTitle> </CardTitle>
<Button variant="outline" size="sm" onClick={fetchAllData} disabled={loading}> <Button variant="outline" size="sm" onClick={fetchAllData} disabled={loading}>
<RefreshCw className={`h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}`} /> <RefreshCw className={`h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}`} />
@@ -860,22 +833,19 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
</SelectContent> </SelectContent>
</Select> </Select>
{/* MODIFIED: Only show service filter if not filtering by a specific node */} <Select value={serviceFilter} onValueChange={setServiceFilter}>
{!nodeName && ( <SelectTrigger className="w-full sm:w-[180px] bg-background border-border">
<Select value={serviceFilter} onValueChange={setServiceFilter}> <SelectValue placeholder="Filter by service" />
<SelectTrigger className="w-full sm:w-[180px] bg-background border-border"> </SelectTrigger>
<SelectValue placeholder="Filter by service" /> <SelectContent>
</SelectTrigger> <SelectItem value="all">All Services</SelectItem>
<SelectContent> {uniqueServices.slice(0, 20).map((service) => (
<SelectItem value="all">All Services</SelectItem> <SelectItem key={service} value={service}>
{uniqueServices.slice(0, 20).map((service) => ( {service}
<SelectItem key={service} value={service}> </SelectItem>
{service} ))}
</SelectItem> </SelectContent>
))} </Select>
</SelectContent>
</Select>
)}
<Button variant="outline" className="border-border bg-transparent" onClick={handleDownloadLogs}> <Button variant="outline" className="border-border bg-transparent" onClick={handleDownloadLogs}>
<Download className="h-4 w-4 mr-2" /> <Download className="h-4 w-4 mr-2" />
@@ -888,7 +858,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
{displayedLogs.map((log, index) => ( {displayedLogs.map((log, index) => (
<div <div
key={index} key={index}
className="flex flex-col space-y-2 p-3 rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 transition-colors cursor-pointer overflow-hidden" className="flex flex-col md:flex-row md:items-start space-y-2 md:space-y-0 md:space-x-4 p-3 rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 transition-colors cursor-pointer overflow-hidden"
onClick={() => { onClick={() => {
if (log.isEvent) { if (log.isEvent) {
setSelectedEvent(log.eventData) setSelectedEvent(log.eventData)
@@ -913,9 +883,11 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
</div> </div>
<div className="flex-1 min-w-0 overflow-hidden"> <div className="flex-1 min-w-0 overflow-hidden">
<div className="flex flex-col gap-1 mb-1"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-1 gap-1">
<div className="text-sm font-medium text-foreground truncate">{log.service}</div> <div className="text-sm font-medium text-foreground truncate">{log.service}</div>
<div className="text-xs text-muted-foreground font-mono truncate">{log.timestamp}</div> <div className="text-xs text-muted-foreground font-mono truncate flex-shrink-0">
{log.timestamp}
</div>
</div> </div>
<div className="text-sm text-foreground mb-1 line-clamp-2 break-words overflow-hidden"> <div className="text-sm text-foreground mb-1 line-clamp-2 break-words overflow-hidden">
{log.message} {log.message}
@@ -977,7 +949,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
<ScrollArea className="h-[500px] w-full rounded-md border border-border"> <ScrollArea className="h-[500px] w-full rounded-md border border-border">
<div className="space-y-2 p-4"> <div className="space-y-2 p-4">
{filteredBackups.map((backup, index) => ( {backups.map((backup, index) => (
<div <div
key={index} key={index}
className="flex items-start space-x-4 p-3 rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 transition-colors cursor-pointer overflow-hidden" className="flex items-start space-x-4 p-3 rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 transition-colors cursor-pointer overflow-hidden"
@@ -1018,7 +990,7 @@ export function SystemLogs({ nodeName }: SystemLogsProps) {
</div> </div>
))} ))}
{filteredBackups.length === 0 && ( {backups.length === 0 && (
<div className="text-center py-8 text-muted-foreground"> <div className="text-center py-8 text-muted-foreground">
<Database className="h-12 w-12 mx-auto mb-4 opacity-50" /> <Database className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No backups found</p> <p>No backups found</p>