mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 11:36:17 +00:00
Update AppImage
This commit is contained in:
@@ -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}`} />
|
<SystemLogs key={`logs-${componentKey}`} nodeName={systemStatus.serverName} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,13 @@ interface CombinedLogEntry {
|
|||||||
sortTimestamp: number
|
sortTimestamp: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SystemLogs() {
|
// ADDED: Props interface
|
||||||
|
interface SystemLogsProps {
|
||||||
|
nodeName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODIFIED: Added props destructuring
|
||||||
|
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[]>([])
|
||||||
@@ -121,14 +127,18 @@ export function SystemLogs() {
|
|||||||
|
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||||
|
|
||||||
const [dateFilter, setDateFilter] = useState("30") // Changed from "now" to "30" to load all recent logs by default
|
const [dateFilter, setDateFilter] = useState("1")
|
||||||
const [customDays, setCustomDays] = useState("1")
|
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}`
|
// MODIFIED: Appended nodeName to URL if provided
|
||||||
|
const baseUrl = `${window.location.protocol}//${window.location.hostname}:8008${endpoint}`
|
||||||
|
return nodeName ? `${baseUrl}?node=${nodeName}` : baseUrl
|
||||||
}
|
}
|
||||||
return `http://localhost:8008${endpoint}`
|
// MODIFIED: Appended nodeName to URL if provided
|
||||||
|
const baseUrl = `http://localhost:8008${endpoint}`
|
||||||
|
return nodeName ? `${baseUrl}?node=${nodeName}` : baseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -152,7 +162,7 @@ export function SystemLogs() {
|
|||||||
console.error("[v0] Error loading logs:", err)
|
console.error("[v0] Error loading logs:", err)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}, [dateFilter, customDays])
|
}, [dateFilter, customDays, nodeName]) // Added nodeName dependency
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("[v0] Level or service filter changed:", levelFilter, serviceFilter)
|
console.log("[v0] Level or service filter changed:", levelFilter, serviceFilter)
|
||||||
@@ -174,16 +184,19 @@ export function SystemLogs() {
|
|||||||
console.error("[v0] Error loading logs:", err)
|
console.error("[v0] Error loading logs:", err)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}, [levelFilter, serviceFilter])
|
}, [levelFilter, serviceFilter, nodeName]) // Added nodeName dependency
|
||||||
|
|
||||||
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(),
|
||||||
fetch(getApiUrl("/api/backups")),
|
// MODIFIED: getApiUrl will now include nodeName if provided
|
||||||
|
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")),
|
||||||
])
|
])
|
||||||
@@ -295,8 +308,8 @@ export function SystemLogs() {
|
|||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
filters.push("searched")
|
filters.push("searched")
|
||||||
}
|
}
|
||||||
|
// MODIFIED: Include nodeName in filename if available
|
||||||
const filename = `proxmox_logs_${filters.length > 0 ? filters.join("_") + "_" : ""}${new Date().toISOString().split("T")[0]}.txt`
|
const filename = `proxmox_logs${nodeName ? `_${nodeName}` : ""}_${filters.length > 0 ? filters.join("_") + "_" : ""}${new Date().toISOString().split("T")[0]}.txt`
|
||||||
|
|
||||||
// Generate log content
|
// Generate log content
|
||||||
const logContent = [
|
const logContent = [
|
||||||
@@ -309,6 +322,8 @@ export function SystemLogs() {
|
|||||||
`- 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)}`,
|
||||||
``,
|
``,
|
||||||
@@ -352,6 +367,7 @@ export function SystemLogs() {
|
|||||||
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) {
|
||||||
@@ -439,7 +455,10 @@ export function SystemLogs() {
|
|||||||
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
|
||||||
|
|
||||||
return matchesSearch && matchesLevel && matchesService
|
// MODIFIED: Filter by nodeName if provided
|
||||||
|
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)
|
||||||
@@ -536,7 +555,10 @@ export function SystemLogs() {
|
|||||||
info: logs.filter((log) => ["info", "notice", "debug"].includes(log.level)).length,
|
info: logs.filter((log) => ["info", "notice", "debug"].includes(log.level)).length,
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueServices = [...new Set(logs.map((log) => log.service))]
|
// MODIFIED: Filter uniqueServices by nodeName if provided
|
||||||
|
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")) {
|
||||||
@@ -578,14 +600,16 @@ export function SystemLogs() {
|
|||||||
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: backups.length,
|
total: filteredBackups.length,
|
||||||
totalSize: backups.reduce((sum, b) => sum + b.size, 0),
|
totalSize: filteredBackups.reduce((sum, b) => sum + b.size, 0),
|
||||||
qemu: backups.filter((b) => {
|
qemu: filteredBackups.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: backups.filter((b) => {
|
lxc: filteredBackups.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,
|
||||||
@@ -629,7 +653,8 @@ export function SystemLogs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading && logs.length === 0 && events.length === 0) {
|
// MODIFIED: Conditionally render loading state based on nodeName
|
||||||
|
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" />
|
||||||
@@ -639,7 +664,7 @@ export function SystemLogs() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{loading && (logs.length > 0 || events.length > 0) && (
|
{loading && (logs.length > 0 || events.length > 0 || backups.length > 0 || notifications.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" />
|
||||||
@@ -705,6 +730,8 @@ export function SystemLogs() {
|
|||||||
<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" : ""}`} />
|
||||||
@@ -833,6 +860,8 @@ export function SystemLogs() {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
{/* MODIFIED: Only show service filter if not filtering by a specific node */}
|
||||||
|
{!nodeName && (
|
||||||
<Select value={serviceFilter} onValueChange={setServiceFilter}>
|
<Select value={serviceFilter} onValueChange={setServiceFilter}>
|
||||||
<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 service" />
|
<SelectValue placeholder="Filter by service" />
|
||||||
@@ -846,6 +875,7 @@ export function SystemLogs() {
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</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" />
|
||||||
@@ -858,7 +888,7 @@ export function SystemLogs() {
|
|||||||
{displayedLogs.map((log, index) => (
|
{displayedLogs.map((log, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
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"
|
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"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (log.isEvent) {
|
if (log.isEvent) {
|
||||||
setSelectedEvent(log.eventData)
|
setSelectedEvent(log.eventData)
|
||||||
@@ -884,17 +914,13 @@ export function SystemLogs() {
|
|||||||
|
|
||||||
<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 gap-1 mb-1">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="text-sm font-medium text-foreground truncate">{log.service}</div>
|
||||||
<div className="text-sm font-medium text-foreground truncate flex-1 min-w-0">
|
|
||||||
{log.service}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-muted-foreground font-mono truncate">{log.timestamp}</div>
|
<div className="text-xs text-muted-foreground font-mono truncate">{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}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground truncate">
|
<div className="text-xs text-muted-foreground break-words overflow-hidden">
|
||||||
{log.source}
|
{log.source}
|
||||||
{log.pid && ` • PID: ${log.pid}`}
|
{log.pid && ` • PID: ${log.pid}`}
|
||||||
{log.hostname && ` • Host: ${log.hostname}`}
|
{log.hostname && ` • Host: ${log.hostname}`}
|
||||||
@@ -951,7 +977,7 @@ export function SystemLogs() {
|
|||||||
|
|
||||||
<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">
|
||||||
{backups.map((backup, index) => (
|
{filteredBackups.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"
|
||||||
@@ -992,7 +1018,7 @@ export function SystemLogs() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{backups.length === 0 && (
|
{filteredBackups.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>
|
||||||
|
|||||||
Reference in New Issue
Block a user