mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-17 19:16:25 +00:00
Update AppImage
This commit is contained in:
@@ -64,9 +64,12 @@ const formatMemory = (memoryKB: number | string): string => {
|
||||
return `${tb.toFixed(1)} TB`
|
||||
}
|
||||
|
||||
// Convert to GB if >= 1024 MB
|
||||
if (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`
|
||||
}
|
||||
|
||||
@@ -1658,6 +1661,62 @@ export default function Hardware() {
|
||||
|
||||
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 (
|
||||
<div
|
||||
key={index}
|
||||
@@ -1672,6 +1731,14 @@ export default function Hardware() {
|
||||
{device.model && (
|
||||
<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>
|
||||
)
|
||||
})}
|
||||
@@ -1743,6 +1810,102 @@ export default function Hardware() {
|
||||
</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 && (
|
||||
<div className="flex justify-between border-b border-border/50 pb-2">
|
||||
<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>
|
||||
</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>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
|
||||
import { Progress } from "./ui/progress"
|
||||
import { Badge } from "./ui/badge"
|
||||
import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity, AlertCircle } from "lucide-react"
|
||||
import { formatStorage } from "@/lib/utils"
|
||||
|
||||
interface StorageData {
|
||||
total: number
|
||||
@@ -116,10 +117,10 @@ export function StorageMetrics() {
|
||||
<HardDrive className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<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" />
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -130,7 +131,7 @@ export function StorageMetrics() {
|
||||
<Database className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<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" />
|
||||
<p className="text-xs text-muted-foreground mt-2">{usagePercent.toFixed(1)}% of total space</p>
|
||||
</CardContent>
|
||||
@@ -144,7 +145,7 @@ export function StorageMetrics() {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<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">
|
||||
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
|
||||
{((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="text-right">
|
||||
<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>
|
||||
<Progress value={disk.usage_percent} className="w-24 mt-1" />
|
||||
</div>
|
||||
|
||||
@@ -76,12 +76,11 @@ const formatStorage = (sizeInGB: number): string => {
|
||||
if (sizeInGB < 1) {
|
||||
// Less than 1 GB, show in MB
|
||||
return `${(sizeInGB * 1024).toFixed(1)} MB`
|
||||
} else if (sizeInGB < 1024) {
|
||||
// Less than 1024 GB, show in GB
|
||||
return `${sizeInGB.toFixed(1)} GB`
|
||||
} else if (sizeInGB > 999) {
|
||||
return `${(sizeInGB / 1024).toFixed(2)} TB`
|
||||
} else {
|
||||
// 1024 GB or more, show in TB
|
||||
return `${(sizeInGB / 1024).toFixed(1)} TB`
|
||||
// Between 1 and 999 GB, show in GB
|
||||
return `${sizeInGB.toFixed(2)} GB`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,7 +597,7 @@ export function StorageOverview() {
|
||||
<div className="grid grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<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>
|
||||
<p className="text-muted-foreground">Used</p>
|
||||
@@ -611,12 +610,12 @@ export function StorageOverview() {
|
||||
: "text-blue-400"
|
||||
}`}
|
||||
>
|
||||
{storage.used.toLocaleString()} GB
|
||||
{formatStorage(storage.used)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
|
||||
@@ -388,12 +388,11 @@ export function SystemOverview() {
|
||||
if (sizeInGB < 1) {
|
||||
// Less than 1 GB, show in MB
|
||||
return `${(sizeInGB * 1024).toFixed(1)} MB`
|
||||
} else if (sizeInGB < 1024) {
|
||||
// Less than 1024 GB, show in GB
|
||||
return `${sizeInGB.toFixed(1)} GB`
|
||||
} else {
|
||||
// 1024 GB or more, show in TB
|
||||
} else if (sizeInGB > 999) {
|
||||
return `${(sizeInGB / 1024).toFixed(2)} TB`
|
||||
} else {
|
||||
// Between 1 and 999 GB, show in GB
|
||||
return `${sizeInGB.toFixed(2)} GB`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "lucide-react"
|
||||
import useSWR from "swr"
|
||||
import { MetricsView } from "./metrics-dialog"
|
||||
import { formatStorage } from "@/lib/utils" // Import formatStorage utility
|
||||
|
||||
interface VMData {
|
||||
vmid: number
|
||||
@@ -194,18 +195,18 @@ const extractIPFromConfig = (config?: VMConfig, lxcIPInfo?: VMDetails["lxc_ip_in
|
||||
return "DHCP"
|
||||
}
|
||||
|
||||
const formatStorage = (sizeInGB: number): string => {
|
||||
if (sizeInGB < 1) {
|
||||
// Less than 1 GB, show in MB
|
||||
return `${(sizeInGB * 1024).toFixed(1)} MB`
|
||||
} else if (sizeInGB < 1024) {
|
||||
// Less than 1024 GB, show in GB
|
||||
return `${sizeInGB.toFixed(1)} GB`
|
||||
} else {
|
||||
// 1024 GB or more, show in TB
|
||||
return `${(sizeInGB / 1024).toFixed(1)} TB`
|
||||
}
|
||||
}
|
||||
// const formatStorage = (sizeInGB: number): string => {
|
||||
// if (sizeInGB < 1) {
|
||||
// // Less than 1 GB, show in MB
|
||||
// return `${(sizeInGB * 1024).toFixed(1)} MB`
|
||||
// } else if (sizeInGB < 1024) {
|
||||
// // Less than 1024 GB, show in GB
|
||||
// return `${sizeInGB.toFixed(1)} GB`
|
||||
// } else {
|
||||
// // 1024 GB or more, show in TB
|
||||
// return `${(sizeInGB / 1024).toFixed(1)} TB`
|
||||
// }
|
||||
// }
|
||||
|
||||
const getUsageColor = (percent: number): string => {
|
||||
if (percent >= 95) return "text-red-500"
|
||||
|
||||
@@ -4,3 +4,18 @@ import { twMerge } from "tailwind-merge"
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
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`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,13 @@ export interface StorageDevice {
|
||||
rotation_rate?: number | string
|
||||
form_factor?: 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 {
|
||||
|
||||
Reference in New Issue
Block a user