Update AppImage

This commit is contained in:
MacRimi
2025-11-04 12:47:26 +01:00
parent 83dcc0c4f2
commit 55394cbf09
7 changed files with 216 additions and 38 deletions

View File

@@ -64,9 +64,12 @@ const formatMemory = (memoryKB: number | string): string => {
return `${tb.toFixed(1)} TB` return `${tb.toFixed(1)} TB`
} }
// Convert to GB if >= 1024 MB
if (mb >= 1024) { if (mb >= 1024) {
const gb = mb / 1024 const gb = mb / 1024
// If GB value is greater than 999, convert to TB
if (gb > 999) {
return `${(gb / 1024).toFixed(2)} TB`
}
return `${gb.toFixed(1)} GB` return `${gb.toFixed(1)} GB`
} }
@@ -1658,6 +1661,62 @@ export default function Hardware() {
const diskBadge = getDiskTypeBadge(device.name, device.rotation_rate) const diskBadge = getDiskTypeBadge(device.name, device.rotation_rate)
const getLinkSpeedInfo = (device: StorageDevice) => {
// NVMe PCIe information
if (device.name.startsWith("nvme") && (device.pcie_gen || device.pcie_width)) {
const current = `${device.pcie_gen || ""} ${device.pcie_width || ""}`.trim()
const max =
device.pcie_max_gen && device.pcie_max_width
? `${device.pcie_max_gen} ${device.pcie_max_width}`.trim()
: null
// Check if running at lower speed than maximum
const isLowerSpeed = max && current !== max
return {
text: current || null,
maxText: max,
isWarning: isLowerSpeed,
color: isLowerSpeed ? "text-orange-500" : "text-blue-500",
}
}
// SATA information
if (device.sata_version) {
return {
text: device.sata_version,
maxText: null,
isWarning: false,
color: "text-blue-500",
}
}
// SAS information
if (device.sas_version || device.sas_speed) {
const text = [device.sas_version, device.sas_speed].filter(Boolean).join(" ")
return {
text: text || null,
maxText: null,
isWarning: false,
color: "text-blue-500",
}
}
// Generic link speed
if (device.link_speed) {
return {
text: device.link_speed,
maxText: null,
isWarning: false,
color: "text-blue-500",
}
}
return null
}
const linkSpeed = getLinkSpeedInfo(device)
return ( return (
<div <div
key={index} key={index}
@@ -1672,6 +1731,14 @@ export default function Hardware() {
{device.model && ( {device.model && (
<p className="text-xs text-muted-foreground line-clamp-2 break-words">{device.model}</p> <p className="text-xs text-muted-foreground line-clamp-2 break-words">{device.model}</p>
)} )}
{linkSpeed && (
<div className="mt-1 flex items-center gap-1">
<span className={`text-xs font-medium ${linkSpeed.color}`}>{linkSpeed.text}</span>
{linkSpeed.isWarning && linkSpeed.maxText && (
<span className="text-xs text-muted-foreground">(max: {linkSpeed.maxText})</span>
)}
</div>
)}
</div> </div>
) )
})} })}
@@ -1743,6 +1810,102 @@ export default function Hardware() {
</div> </div>
)} )}
{(selectedDisk.pcie_gen ||
selectedDisk.pcie_width ||
selectedDisk.sata_version ||
selectedDisk.sas_version ||
selectedDisk.link_speed) && (
<>
<div className="pt-2">
<h3 className="text-sm font-semibold text-muted-foreground mb-2 uppercase tracking-wide">
Interface Information
</h3>
</div>
{/* NVMe PCIe Information */}
{selectedDisk.name.startsWith("nvme") && (selectedDisk.pcie_gen || selectedDisk.pcie_width) && (
<>
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Current Link Speed</span>
<span className="text-sm font-medium text-blue-500">
{selectedDisk.pcie_gen} {selectedDisk.pcie_width}
</span>
</div>
{selectedDisk.pcie_max_gen && selectedDisk.pcie_max_width && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Maximum Link Speed</span>
<span className="text-sm font-medium">
{selectedDisk.pcie_max_gen} {selectedDisk.pcie_max_width}
</span>
</div>
)}
{/* Warning if running at lower speed */}
{selectedDisk.pcie_max_gen &&
selectedDisk.pcie_max_width &&
`${selectedDisk.pcie_gen} ${selectedDisk.pcie_width}` !==
`${selectedDisk.pcie_max_gen} ${selectedDisk.pcie_max_width}` && (
<div className="rounded-lg bg-orange-500/10 p-3 border border-orange-500/20">
<div className="flex gap-2">
<svg
className="h-5 w-5 text-orange-500 flex-shrink-0"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
<div>
<h4 className="text-sm font-semibold text-orange-500 mb-1">Performance Notice</h4>
<p className="text-xs text-muted-foreground">
This drive is running at {selectedDisk.pcie_gen} {selectedDisk.pcie_width} but
supports up to {selectedDisk.pcie_max_gen} {selectedDisk.pcie_max_width}. Check if the
slot supports the maximum speed or if the drive is properly seated.
</p>
</div>
</div>
</div>
)}
</>
)}
{/* SATA Information */}
{selectedDisk.sata_version && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">SATA Version</span>
<span className="text-sm font-medium text-blue-500">{selectedDisk.sata_version}</span>
</div>
)}
{/* SAS Information */}
{selectedDisk.sas_version && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">SAS Version</span>
<span className="text-sm font-medium text-blue-500">{selectedDisk.sas_version}</span>
</div>
)}
{selectedDisk.sas_speed && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">SAS Speed</span>
<span className="text-sm font-medium text-blue-500">{selectedDisk.sas_speed}</span>
</div>
)}
{/* Generic Link Speed */}
{selectedDisk.link_speed &&
!selectedDisk.pcie_gen &&
!selectedDisk.sata_version &&
!selectedDisk.sas_version && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Link Speed</span>
<span className="text-sm font-medium text-blue-500">{selectedDisk.link_speed}</span>
</div>
)}
</>
)}
{selectedDisk.model && ( {selectedDisk.model && (
<div className="flex justify-between border-b border-border/50 pb-2"> <div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Model</span> <span className="text-sm font-medium text-muted-foreground">Model</span>
@@ -1806,13 +1969,6 @@ export default function Hardware() {
<span className="text-sm">{selectedDisk.form_factor}</span> <span className="text-sm">{selectedDisk.form_factor}</span>
</div> </div>
)} )}
{selectedDisk.sata_version && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">SATA Version</span>
<span className="text-sm">{selectedDisk.sata_version}</span>
</div>
)}
</div> </div>
)} )}
</DialogContent> </DialogContent>

View File

@@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Progress } from "./ui/progress" import { Progress } from "./ui/progress"
import { Badge } from "./ui/badge" import { Badge } from "./ui/badge"
import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity, AlertCircle } from "lucide-react" import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity, AlertCircle } from "lucide-react"
import { formatStorage } from "@/lib/utils"
interface StorageData { interface StorageData {
total: number total: number
@@ -116,10 +117,10 @@ export function StorageMetrics() {
<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-xl lg:text-2xl font-bold text-foreground">{storageData.total.toFixed(1)} GB</div> <div className="text-xl lg:text-2xl font-bold text-foreground">{formatStorage(storageData.total)}</div>
<Progress value={usagePercent} className="mt-2" /> <Progress value={usagePercent} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2"> <p className="text-xs text-muted-foreground mt-2">
{storageData.used.toFixed(1)} GB used {storageData.available.toFixed(1)} GB available {formatStorage(storageData.used)} used {formatStorage(storageData.available)} available
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -130,7 +131,7 @@ export function StorageMetrics() {
<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-xl lg:text-2xl font-bold text-foreground">{storageData.used.toFixed(1)} GB</div> <div className="text-xl lg:text-2xl font-bold text-foreground">{formatStorage(storageData.used)}</div>
<Progress value={usagePercent} className="mt-2" /> <Progress value={usagePercent} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2">{usagePercent.toFixed(1)}% of total space</p> <p className="text-xs text-muted-foreground mt-2">{usagePercent.toFixed(1)}% of total space</p>
</CardContent> </CardContent>
@@ -144,7 +145,7 @@ export function StorageMetrics() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-xl lg:text-2xl font-bold text-foreground">{storageData.available.toFixed(1)} GB</div> <div className="text-xl lg:text-2xl font-bold text-foreground">{formatStorage(storageData.available)}</div>
<div className="flex items-center mt-2"> <div className="flex items-center mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20"> <Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
{((storageData.available / storageData.total) * 100).toFixed(1)}% Free {((storageData.available / storageData.total) * 100).toFixed(1)}% Free
@@ -201,7 +202,7 @@ export function StorageMetrics() {
<div className="flex items-center space-x-6"> <div className="flex items-center space-x-6">
<div className="text-right"> <div className="text-right">
<div className="text-sm font-medium text-foreground"> <div className="text-sm font-medium text-foreground">
{disk.used.toFixed(1)} GB / {disk.total.toFixed(1)} GB {formatStorage(disk.used)} / {formatStorage(disk.total)}
</div> </div>
<Progress value={disk.usage_percent} className="w-24 mt-1" /> <Progress value={disk.usage_percent} className="w-24 mt-1" />
</div> </div>

View File

@@ -76,12 +76,11 @@ const formatStorage = (sizeInGB: number): string => {
if (sizeInGB < 1) { if (sizeInGB < 1) {
// Less than 1 GB, show in MB // Less than 1 GB, show in MB
return `${(sizeInGB * 1024).toFixed(1)} MB` return `${(sizeInGB * 1024).toFixed(1)} MB`
} else if (sizeInGB < 1024) { } else if (sizeInGB > 999) {
// Less than 1024 GB, show in GB return `${(sizeInGB / 1024).toFixed(2)} TB`
return `${sizeInGB.toFixed(1)} GB`
} else { } else {
// 1024 GB or more, show in TB // Between 1 and 999 GB, show in GB
return `${(sizeInGB / 1024).toFixed(1)} TB` return `${sizeInGB.toFixed(2)} GB`
} }
} }
@@ -598,7 +597,7 @@ export function StorageOverview() {
<div className="grid grid-cols-3 gap-4 text-sm"> <div className="grid grid-cols-3 gap-4 text-sm">
<div> <div>
<p className="text-muted-foreground">Total</p> <p className="text-muted-foreground">Total</p>
<p className="font-medium">{storage.total.toLocaleString()} GB</p> <p className="font-medium">{formatStorage(storage.total)}</p>
</div> </div>
<div> <div>
<p className="text-muted-foreground">Used</p> <p className="text-muted-foreground">Used</p>
@@ -611,12 +610,12 @@ export function StorageOverview() {
: "text-blue-400" : "text-blue-400"
}`} }`}
> >
{storage.used.toLocaleString()} GB {formatStorage(storage.used)}
</p> </p>
</div> </div>
<div> <div>
<p className="text-muted-foreground">Available</p> <p className="text-muted-foreground">Available</p>
<p className="font-medium text-green-400">{storage.available.toLocaleString()} GB</p> <p className="font-medium text-green-400">{formatStorage(storage.available)}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -388,12 +388,11 @@ export function SystemOverview() {
if (sizeInGB < 1) { if (sizeInGB < 1) {
// Less than 1 GB, show in MB // Less than 1 GB, show in MB
return `${(sizeInGB * 1024).toFixed(1)} MB` return `${(sizeInGB * 1024).toFixed(1)} MB`
} else if (sizeInGB < 1024) { } else if (sizeInGB > 999) {
// Less than 1024 GB, show in GB
return `${sizeInGB.toFixed(1)} GB`
} else {
// 1024 GB or more, show in TB
return `${(sizeInGB / 1024).toFixed(2)} TB` return `${(sizeInGB / 1024).toFixed(2)} TB`
} else {
// Between 1 and 999 GB, show in GB
return `${sizeInGB.toFixed(2)} GB`
} }
} }

View File

@@ -25,6 +25,7 @@ import {
} from "lucide-react" } from "lucide-react"
import useSWR from "swr" import useSWR from "swr"
import { MetricsView } from "./metrics-dialog" import { MetricsView } from "./metrics-dialog"
import { formatStorage } from "@/lib/utils" // Import formatStorage utility
interface VMData { interface VMData {
vmid: number vmid: number
@@ -194,18 +195,18 @@ const extractIPFromConfig = (config?: VMConfig, lxcIPInfo?: VMDetails["lxc_ip_in
return "DHCP" return "DHCP"
} }
const formatStorage = (sizeInGB: number): string => { // const formatStorage = (sizeInGB: number): string => {
if (sizeInGB < 1) { // if (sizeInGB < 1) {
// Less than 1 GB, show in MB // // Less than 1 GB, show in MB
return `${(sizeInGB * 1024).toFixed(1)} MB` // return `${(sizeInGB * 1024).toFixed(1)} MB`
} else if (sizeInGB < 1024) { // } else if (sizeInGB < 1024) {
// Less than 1024 GB, show in GB // // Less than 1024 GB, show in GB
return `${sizeInGB.toFixed(1)} GB` // return `${sizeInGB.toFixed(1)} GB`
} else { // } else {
// 1024 GB or more, show in TB // // 1024 GB or more, show in TB
return `${(sizeInGB / 1024).toFixed(1)} TB` // return `${(sizeInGB / 1024).toFixed(1)} TB`
} // }
} // }
const getUsageColor = (percent: number): string => { const getUsageColor = (percent: number): string => {
if (percent >= 95) return "text-red-500" if (percent >= 95) return "text-red-500"

View File

@@ -4,3 +4,18 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
} }
export function formatStorage(sizeInGB: number): string {
if (sizeInGB < 1) {
// Less than 1 GB, show in MB
const mb = sizeInGB * 1024
return `${mb % 1 === 0 ? mb.toFixed(0) : mb.toFixed(1)} MB`
} else if (sizeInGB < 1024) {
// Less than 1024 GB, show in GB
return `${sizeInGB % 1 === 0 ? sizeInGB.toFixed(0) : sizeInGB.toFixed(1)} GB`
} else {
// 1024 GB or more, show in TB
const tb = sizeInGB / 1024
return `${tb % 1 === 0 ? tb.toFixed(0) : tb.toFixed(1)} TB`
}
}

View File

@@ -33,6 +33,13 @@ export interface StorageDevice {
rotation_rate?: number | string rotation_rate?: number | string
form_factor?: string form_factor?: string
sata_version?: string sata_version?: string
pcie_gen?: string // e.g., "PCIe 4.0"
pcie_width?: string // e.g., "x4"
pcie_max_gen?: string // Maximum supported PCIe generation
pcie_max_width?: string // Maximum supported PCIe lanes
sas_version?: string // e.g., "SAS-3"
sas_speed?: string // e.g., "12Gb/s"
link_speed?: string // Generic link speed info
} }
export interface PCIDevice { export interface PCIDevice {