Update AppImage

This commit is contained in:
MacRimi
2025-10-22 19:50:26 +02:00
parent 70509355de
commit e620010f10
3 changed files with 136 additions and 84 deletions

View File

@@ -215,15 +215,15 @@ export function NetworkMetrics() {
<CardContent> <CardContent>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground hidden lg:inline">Received:</span> <span className="text-sm text-muted-foreground">Received:</span>
<span className="text-base lg:text-xl font-bold text-green-500"> {trafficInFormatted}</span> <span className="text-base lg:text-xl font-bold text-green-500"> {trafficInFormatted}</span>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground hidden lg:inline">Sent:</span> <span className="text-sm text-muted-foreground">Sent:</span>
<span className="text-base lg:text-xl font-bold text-blue-500"> {trafficOutFormatted}</span> <span className="text-base lg:text-xl font-bold text-blue-500"> {trafficOutFormatted}</span>
</div> </div>
</div> </div>
<p className="text-xs text-muted-foreground mt-2 hidden lg:block">Total data transferred</p> <p className="text-xs text-muted-foreground mt-2">Total data transferred</p>
</CardContent> </CardContent>
</Card> </Card>
@@ -250,91 +250,130 @@ export function NetworkMetrics() {
</CardContent> </CardContent>
</Card> </Card>
{/* Firewall Status card */}
<Card className="bg-card border-border"> <Card className="bg-card border-border">
<CardHeader> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-foreground flex items-center"> <CardTitle className="text-sm font-medium text-muted-foreground">Firewall Status</CardTitle>
<Router className="h-5 w-5 mr-2" /> <Router className="h-4 w-4 text-muted-foreground" />
Physical Interfaces
<Badge variant="outline" className="ml-3 bg-blue-500/10 text-blue-500 border-blue-500/20">
{networkData.physical_active_count ?? 0}/{networkData.physical_total_count ?? 0} Active
</Badge>
</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> <div className="text-xl lg:text-2xl font-bold text-green-500">Active</div>
{networkData.physical_interfaces.map((interface_, index) => { <Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20 mt-2">
const typeBadge = getInterfaceTypeBadge(interface_.type) Protected
</Badge>
<p className="text-xs text-muted-foreground mt-2">System protected</p>
</CardContent>
</Card>
return ( {/* Network Health card */}
<div <Card className="bg-card border-border">
key={index} <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
className="flex flex-col gap-3 p-4 rounded-lg border border-white/10 bg-white/5 sm:bg-card sm:hover:bg-white/5 transition-colors cursor-pointer" <CardTitle className="text-sm font-medium text-muted-foreground">Network Health</CardTitle>
onClick={() => setSelectedInterface(interface_)} <Activity className="h-4 w-4 text-muted-foreground" />
> </CardHeader>
{/* First row: Icon, Name, Type Badge, Status */} <CardContent>
<div className="flex items-center gap-3 flex-wrap"> <Badge variant="outline" className={healthColor}>
<Wifi className="h-5 w-5 text-muted-foreground flex-shrink-0" /> {healthStatus}
<div className="flex items-center gap-2 min-w-0 flex-1 flex-wrap"> </Badge>
<div className="font-medium text-foreground">{interface_.name}</div> <div className="flex flex-col gap-1 mt-2 text-xs">
<Badge variant="outline" className={typeBadge.color}> <div className="flex items-center justify-between">
{typeBadge.label} <span className="text-muted-foreground">Packet Loss:</span>
</Badge> <span className="font-medium text-foreground">{avgPacketLoss}%</span>
</div> </div>
<Badge <div className="flex items-center justify-between">
variant="outline" <span className="text-muted-foreground">Errors:</span>
className={ <span className="font-medium text-foreground">{totalErrors}</span>
interface_.status === "up" </div>
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-red-500/10 text-red-500 border-red-500/20"
}
>
{interface_.status.toUpperCase()}
</Badge>
</div>
{/* Second row: Details - Responsive layout */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<div className="text-muted-foreground text-xs">IP Address</div>
<div className="font-medium text-foreground font-mono text-sm truncate">
{interface_.addresses.length > 0 ? interface_.addresses[0].ip : "N/A"}
</div>
</div>
<div>
<div className="text-muted-foreground text-xs">Speed</div>
<div className="font-medium text-foreground flex items-center gap-1 text-xs">
<Zap className="h-3 w-3" />
{formatSpeed(interface_.speed)}
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="text-muted-foreground text-xs">Traffic</div>
<div className="font-medium text-foreground text-xs">
<span className="text-green-500"> {formatBytes(interface_.bytes_recv)}</span>
{" / "}
<span className="text-blue-500"> {formatBytes(interface_.bytes_sent)}</span>
</div>
</div>
{interface_.mac_address && (
<div className="col-span-2 md:col-span-1">
<div className="text-muted-foreground text-xs">MAC</div>
<div className="font-medium text-foreground font-mono text-xs truncate">
{interface_.mac_address}
</div>
</div>
)}
</div>
</div>
)
})}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
{/* Physical Interfaces section */}
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Router className="h-5 w-5 mr-2" />
Physical Interfaces
<Badge variant="outline" className="ml-3 bg-blue-500/10 text-blue-500 border-blue-500/20">
{networkData.physical_active_count ?? 0}/{networkData.physical_total_count ?? 0} Active
</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{networkData.physical_interfaces.map((interface_, index) => {
const typeBadge = getInterfaceTypeBadge(interface_.type)
return (
<div
key={index}
className="flex flex-col gap-3 p-4 rounded-lg border border-white/10 bg-white/5 sm:bg-card sm:hover:bg-white/5 transition-colors cursor-pointer"
onClick={() => setSelectedInterface(interface_)}
>
{/* First row: Icon, Name, Type Badge, Status */}
<div className="flex items-center gap-3 flex-wrap">
<Wifi className="h-5 w-5 text-muted-foreground flex-shrink-0" />
<div className="flex items-center gap-2 min-w-0 flex-1 flex-wrap">
<div className="font-medium text-foreground">{interface_.name}</div>
<Badge variant="outline" className={typeBadge.color}>
{typeBadge.label}
</Badge>
</div>
<Badge
variant="outline"
className={
interface_.status === "up"
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-red-500/10 text-red-500 border-red-500/20"
}
>
{interface_.status.toUpperCase()}
</Badge>
</div>
{/* Second row: Details - Responsive layout */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<div className="text-muted-foreground text-xs">IP Address</div>
<div className="font-medium text-foreground font-mono text-sm truncate">
{interface_.addresses.length > 0 ? interface_.addresses[0].ip : "N/A"}
</div>
</div>
<div>
<div className="text-muted-foreground text-xs">Speed</div>
<div className="font-medium text-foreground flex items-center gap-1 text-xs">
<Zap className="h-3 w-3" />
{formatSpeed(interface_.speed)}
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="text-muted-foreground text-xs">Traffic</div>
<div className="font-medium text-foreground text-xs">
<span className="text-green-500"> {formatBytes(interface_.bytes_recv)}</span>
{" / "}
<span className="text-blue-500"> {formatBytes(interface_.bytes_sent)}</span>
</div>
</div>
{interface_.mac_address && (
<div className="col-span-2 md:col-span-1">
<div className="text-muted-foreground text-xs">MAC</div>
<div className="font-medium text-foreground font-mono text-xs truncate">
{interface_.mac_address}
</div>
</div>
)}
</div>
</div>
)
})}
</div>
</CardContent>
</Card>
{networkData.bridge_interfaces && networkData.bridge_interfaces.length > 0 && ( {networkData.bridge_interfaces && networkData.bridge_interfaces.length > 0 && (
<Card className="bg-card border-border"> <Card className="bg-card border-border">
<CardHeader> <CardHeader>

View File

@@ -43,14 +43,22 @@ export function NodeMetricsCharts() {
typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : ""
const apiUrl = `${baseUrl}/api/node/metrics?timeframe=${timeframe}` const apiUrl = `${baseUrl}/api/node/metrics?timeframe=${timeframe}`
console.log("[v0] Fetching node metrics from:", apiUrl)
const response = await fetch(apiUrl) const response = await fetch(apiUrl)
console.log("[v0] Response status:", response.status)
console.log("[v0] Response ok:", response.ok)
if (!response.ok) { if (!response.ok) {
const errorData = await response.json() const errorData = await response.json()
console.log("[v0] Error response data:", errorData)
throw new Error(errorData.error || "Failed to fetch node metrics") throw new Error(errorData.error || "Failed to fetch node metrics")
} }
const result = await response.json() const result = await response.json()
console.log("[v0] Node metrics result:", result)
console.log("[v0] Data points received:", result.data?.length || 0)
const transformedData = result.data.map((item: any) => { const transformedData = result.data.map((item: any) => {
const date = new Date(item.time * 1000) const date = new Date(item.time * 1000)
@@ -94,9 +102,14 @@ export function NodeMetricsCharts() {
} }
}) })
console.log("[v0] Transformed data sample:", transformedData[0])
console.log("[v0] Total transformed data points:", transformedData.length)
setData(transformedData) setData(transformedData)
} catch (err: any) { } catch (err: any) {
console.error("[v0] Error fetching node metrics:", err) console.error("[v0] Error fetching node metrics:", err)
console.error("[v0] Error message:", err.message)
console.error("[v0] Error stack:", err.stack)
setError(err.message || "Error loading metrics") setError(err.message || "Error loading metrics")
} finally { } finally {
setLoading(false) setLoading(false)

View File

@@ -427,14 +427,14 @@ export function StorageOverview() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Storage Summary */} {/* Storage Summary */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-6">
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Storage</CardTitle> <CardTitle className="text-sm font-medium">Total Storage</CardTitle>
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">{storageData.total.toFixed(1)} TB</div> <div className="text-xl lg:text-2xl font-bold">{storageData.total.toFixed(1)} TB</div>
<p className="text-xs text-muted-foreground mt-1">{storageData.disk_count} physical disks</p> <p className="text-xs text-muted-foreground mt-1">{storageData.disk_count} physical disks</p>
</CardContent> </CardContent>
</Card> </Card>
@@ -445,7 +445,7 @@ export function StorageOverview() {
<Database className="h-4 w-4 text-muted-foreground" /> <Database className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">{formatStorage(totalProxmoxUsed)}</div> <div className="text-xl lg:text-2xl font-bold">{formatStorage(totalProxmoxUsed)}</div>
<p className="text-xs text-muted-foreground mt-1">{usagePercent}% used</p> <p className="text-xs text-muted-foreground mt-1">{usagePercent}% used</p>
</CardContent> </CardContent>
</Card> </Card>
@@ -457,7 +457,7 @@ export function StorageOverview() {
<CheckCircle2 className="h-4 w-4 text-muted-foreground" /> <CheckCircle2 className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">{storageData.disk_count} disks</div> <div className="text-xl lg:text-2xl font-bold">{storageData.disk_count} disks</div>
<p className="text-xs mt-1"> <p className="text-xs mt-1">
<span className="text-green-500">{diskHealthBreakdown.normal} normal</span> <span className="text-green-500">{diskHealthBreakdown.normal} normal</span>
{diskHealthBreakdown.warning > 0 && ( {diskHealthBreakdown.warning > 0 && (
@@ -483,7 +483,7 @@ export function StorageOverview() {
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">{storageData.disk_count} disks</div> <div className="text-xl lg:text-2xl font-bold">{storageData.disk_count} disks</div>
<p className="text-xs mt-1"> <p className="text-xs mt-1">
{diskTypesBreakdown.nvme > 0 && <span className="text-purple-500">{diskTypesBreakdown.nvme} NVMe</span>} {diskTypesBreakdown.nvme > 0 && <span className="text-purple-500">{diskTypesBreakdown.nvme} NVMe</span>}
{diskTypesBreakdown.ssd > 0 && ( {diskTypesBreakdown.ssd > 0 && (