Update AppImage

This commit is contained in:
MacRimi
2025-10-06 14:12:28 +02:00
parent 23c91386dc
commit 154b6b9f74
2 changed files with 175 additions and 808 deletions

View File

@@ -3,152 +3,26 @@
import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Cpu, MemoryStick, HardDrive, Network, Thermometer, Fan, Battery, Server, CpuIcon } from "lucide-react"
import { Thermometer, CpuIcon, ChevronDown, ChevronUp, Zap } from "lucide-react"
import useSWR from "swr"
import { useState } from "react"
import { type HardwareData, fetcher } from "@/types/hardware"
const fetcher = (url: string) => fetch(url).then((res) => res.json())
interface CPUInfo {
model?: string
total_threads?: number
cores_per_socket?: number
sockets?: number
current_mhz?: number
max_mhz?: number
virtualization?: string
l1d_cache?: string
l2_cache?: string
l3_cache?: string
}
interface MotherboardInfo {
manufacturer?: string
model?: string
version?: string
serial?: string
bios?: {
vendor?: string
version?: string
date?: string
const getDeviceTypeColor = (type: string): string => {
const lowerType = type.toLowerCase()
if (lowerType.includes("storage") || lowerType.includes("sata") || lowerType.includes("raid")) {
return "bg-orange-500/10 text-orange-500 border-orange-500/20"
}
}
interface MemoryModule {
size: string
type: string
speed: string
manufacturer?: string
slot?: string
}
interface StorageDevice {
name: string
size: string
model: string
temperature: number
health: string
power_on_hours: number
rotation_rate: number
}
interface NetworkCard {
name: string
type: string
}
interface GraphicsCard {
name: string
memory?: string
temperature?: number
power_draw?: string
vendor: string
}
interface TemperatureSensor {
name: string
current: number
high?: number
critical?: number
}
interface FanSensor {
name: string
current_rpm: number
}
interface UPSInfo {
model?: string
status?: string
battery_charge?: string
time_left?: string
load_percent?: string
line_voltage?: string
}
interface IPMIFan {
name: string
speed: number
unit: string
}
interface IPMIPowerSupply {
name: string
watts: number
unit: string
status: string
}
interface IPMIPower {
power_supplies: IPMIPowerSupply[]
power_meter?: {
name: string
watts: number
unit: string
if (lowerType.includes("usb")) {
return "bg-purple-500/10 text-purple-500 border-purple-500/20"
}
}
interface UPSData {
model?: string
status?: string
battery_charge?: string
time_left?: string
load_percent?: string
line_voltage?: string
real_power?: string
}
interface PCIDevice {
slot: string
type: string
vendor: string
device: string
class: string
driver?: string
kernel_module?: string
irq?: string
memory_address?: string
link_speed?: string
capabilities?: string[]
}
interface HardwareData {
cpu: CPUInfo
motherboard: MotherboardInfo
memory_modules: MemoryModule[]
storage_devices: StorageDevice[]
network_cards: NetworkCard[]
graphics_cards: GraphicsCard[]
pci_devices: PCIDevice[]
sensors: {
temperatures: TemperatureSensor[]
fans: FanSensor[]
if (lowerType.includes("network") || lowerType.includes("ethernet")) {
return "bg-blue-500/10 text-blue-500 border-blue-500/20"
}
power: UPSInfo
ipmi_fans?: IPMIFan[]
ipmi_power?: IPMIPower
ups?: UPSData
if (lowerType.includes("graphics") || lowerType.includes("vga") || lowerType.includes("display")) {
return "bg-green-500/10 text-green-500 border-green-500/20"
}
return "bg-gray-500/10 text-gray-500 border-gray-500/20"
}
export default function Hardware() {
@@ -156,317 +30,76 @@ export default function Hardware() {
refreshInterval: 5000,
})
const [selectedPCIDevice, setSelectedPCIDevice] = useState<PCIDevice | null>(null)
if (error) {
return (
<div className="p-6">
<div className="rounded-lg border border-red-500/50 bg-red-500/10 p-4">
<p className="text-sm text-red-500">Failed to load hardware information</p>
</div>
</div>
)
}
if (!hardwareData) {
return (
<div className="p-6">
<div className="flex items-center justify-center py-12">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
</div>
</div>
)
}
const getHealthColor = (health: string) => {
switch (health.toLowerCase()) {
case "healthy":
return "text-green-500"
case "warning":
return "text-yellow-500"
case "critical":
case "failed":
return "text-red-500"
default:
return "text-muted-foreground"
}
}
const getHealthBadge = (health: string) => {
switch (health.toLowerCase()) {
case "healthy":
return <Badge className="bg-green-500/10 text-green-500 border-green-500/20">Healthy</Badge>
case "warning":
return <Badge className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20">Warning</Badge>
case "critical":
case "failed":
return <Badge className="bg-red-500/10 text-red-500 border-red-500/20">Critical</Badge>
default:
return <Badge variant="outline">Unknown</Badge>
}
}
const getTempColor = (temp: number, high?: number, critical?: number) => {
if (critical && temp >= critical) return "text-red-500"
if (high && temp >= high) return "text-yellow-500"
if (temp >= 70) return "text-red-500"
if (temp >= 60) return "text-yellow-500"
return "text-green-500"
}
const getTempProgress = (temp: number, critical?: number) => {
const max = critical || 100
return (temp / max) * 100
}
const hasSensors = hardwareData.sensors.temperatures.length > 0 || hardwareData.sensors.fans.length > 0
const hasUPS = hardwareData.power && Object.keys(hardwareData.power).length > 0
const hasIPMIFans = hardwareData.ipmi_fans && hardwareData.ipmi_fans.length > 0
const hasIPMIPower =
hardwareData.ipmi_power &&
(hardwareData.ipmi_power.power_supplies?.length > 0 || hardwareData.ipmi_power.power_meter)
const hasUPSData = hardwareData.ups && Object.keys(hardwareData.ups).length > 0
const storageSummary = hardwareData.storage_devices.reduce(
(acc, disk) => {
const sizeMatch = disk.size.match(/(\d+\.?\d*)\s*([KMGT]?B?)/)
if (sizeMatch) {
let sizeInTB = Number.parseFloat(sizeMatch[1])
const unit = sizeMatch[2]
if (unit === "TB" || unit === "T") sizeInTB *= 1
else if (unit === "GB" || unit === "G") sizeInTB /= 1024
else if (unit === "MB" || unit === "M") sizeInTB /= 1024 * 1024
else if (unit === "KB" || unit === "K") sizeInTB /= 1024 * 1024 * 1024
acc.totalCapacity += sizeInTB
}
if (disk.rotation_rate === 0) acc.ssd++
else if (disk.rotation_rate > 0) acc.hdd++
return acc
},
{ totalCapacity: 0, ssd: 0, hdd: 0 },
)
const networkControllers = hardwareData.pci_devices?.filter((device) => device.type === "Network Controller") || []
const [expandedPCIDevice, setExpandedPCIDevice] = useState<string | null>(null)
return (
<div className="space-y-6 p-6">
{/* System Information */}
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Server className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">System Information</h2>
</div>
<div className="grid gap-6 md:grid-cols-2">
{/* CPU */}
{hardwareData.cpu.model && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-muted-foreground" />
<h3 className="font-medium">CPU</h3>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Model</span>
<span className="font-mono">{hardwareData.cpu.model}</span>
</div>
{hardwareData.cpu.sockets && hardwareData.cpu.cores_per_socket && (
<div className="flex justify-between">
<span className="text-muted-foreground">Cores</span>
<span className="font-mono">
{hardwareData.cpu.sockets} × {hardwareData.cpu.cores_per_socket} ={" "}
{hardwareData.cpu.sockets * hardwareData.cpu.cores_per_socket} cores
</span>
</div>
)}
{hardwareData.cpu.total_threads && (
<div className="flex justify-between">
<span className="text-muted-foreground">Threads</span>
<span className="font-mono">{hardwareData.cpu.total_threads}</span>
</div>
)}
{hardwareData.cpu.current_mhz && (
<div className="flex justify-between">
<span className="text-muted-foreground">Frequency</span>
<span className="font-mono">{hardwareData.cpu.current_mhz.toFixed(0)} MHz</span>
</div>
)}
{hardwareData.cpu.l3_cache && (
<div className="flex justify-between">
<span className="text-muted-foreground">L3 Cache</span>
<span className="font-mono">{hardwareData.cpu.l3_cache}</span>
</div>
)}
{hardwareData.cpu.virtualization && (
<div className="flex justify-between">
<span className="text-muted-foreground">Virtualization</span>
<span className="font-mono">{hardwareData.cpu.virtualization}</span>
</div>
)}
</div>
</div>
)}
{/* Motherboard */}
{hardwareData.motherboard.manufacturer && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<Server className="h-4 w-4 text-muted-foreground" />
<h3 className="font-medium">Motherboard</h3>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Manufacturer</span>
<span className="font-mono">{hardwareData.motherboard.manufacturer}</span>
</div>
{hardwareData.motherboard.model && (
<div className="flex justify-between">
<span className="text-muted-foreground">Model</span>
<span className="font-mono">{hardwareData.motherboard.model}</span>
</div>
)}
{hardwareData.motherboard.bios && (
<>
<div className="flex justify-between">
<span className="text-muted-foreground">BIOS</span>
<span className="font-mono">{hardwareData.motherboard.bios.vendor}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Version</span>
<span className="font-mono">{hardwareData.motherboard.bios.version}</span>
</div>
{hardwareData.motherboard.bios.date && (
<div className="flex justify-between">
<span className="text-muted-foreground">Date</span>
<span className="font-mono">{hardwareData.motherboard.bios.date}</span>
</div>
)}
</>
)}
</div>
</div>
)}
</div>
</Card>
{/* Memory Modules */}
{hardwareData.memory_modules.length > 0 && (
{/* Thermal Monitoring */}
{hardwareData?.temperatures && hardwareData.temperatures.length > 0 && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<MemoryStick className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Memory Modules</h2>
<Thermometer className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Thermal Monitoring</h2>
<Badge variant="outline" className="ml-auto">
{hardwareData.memory_modules.length} installed
{hardwareData.temperatures.length} sensors
</Badge>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{hardwareData.memory_modules.map((module, index) => (
<Card key={index} className="border-border/30 bg-background/50 p-4">
<div className="space-y-2 text-sm">
{module.slot && <div className="font-medium">{module.slot}</div>}
<div className="flex justify-between">
<span className="text-muted-foreground">Size</span>
<span className="font-mono">{module.size}</span>
<div className="grid gap-4 md:grid-cols-2">
{hardwareData.temperatures.map((temp, index) => {
const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100
const isHot = temp.current > (temp.high || 80)
const isCritical = temp.current > (temp.critical || 90)
return (
<div key={index} className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">{temp.name}</span>
<span
className={`text-sm font-semibold ${isCritical ? "text-red-500" : isHot ? "text-orange-500" : "text-green-500"}`}
>
{temp.current.toFixed(1)}°C
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Type</span>
<span className="font-mono">{module.type}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Speed</span>
<span className="font-mono">{module.speed}</span>
</div>
{module.manufacturer && module.manufacturer !== "Unknown" && (
<div className="flex justify-between">
<span className="text-muted-foreground">Manufacturer</span>
<span className="font-mono text-xs">{module.manufacturer}</span>
</div>
)}
<Progress value={percentage} className="h-2" />
{temp.adapter && <span className="text-xs text-muted-foreground">{temp.adapter}</span>}
</div>
</Card>
))}
)
})}
</div>
</Card>
)}
{/* Storage Summary */}
{hardwareData.storage_devices.length > 0 && (
{hardwareData?.power_meter && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<HardDrive className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Storage Summary</h2>
<Badge variant="outline" className="ml-auto">
{hardwareData.storage_devices.length} devices
</Badge>
<Zap className="h-5 w-5 text-yellow-500" />
<h2 className="text-lg font-semibold">Power Consumption</h2>
</div>
<div className="grid gap-4 md:grid-cols-3">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">Total Capacity</p>
<p className="text-2xl font-semibold">
{storageSummary.totalCapacity >= 1
? `${storageSummary.totalCapacity.toFixed(1)} TB`
: `${(storageSummary.totalCapacity * 1024).toFixed(1)} GB`}
</p>
<div className="space-y-4">
<div className="flex items-center justify-between rounded-lg border border-border/30 bg-background/50 p-4">
<div className="space-y-1">
<p className="text-sm font-medium">{hardwareData.power_meter.name}</p>
{hardwareData.power_meter.adapter && (
<p className="text-xs text-muted-foreground">{hardwareData.power_meter.adapter}</p>
)}
</div>
<div className="text-right">
<p className="text-2xl font-bold text-yellow-500">{hardwareData.power_meter.watts.toFixed(1)} W</p>
<p className="text-xs text-muted-foreground">Current Draw</p>
</div>
</div>
{storageSummary.ssd > 0 && (
<div className="space-y-1">
<p className="text-sm text-muted-foreground">SSD/NVMe Drives</p>
<p className="text-2xl font-semibold">{storageSummary.ssd}</p>
</div>
)}
{storageSummary.hdd > 0 && (
<div className="space-y-1">
<p className="text-sm text-muted-foreground">HDD Drives</p>
<p className="text-2xl font-semibold">{storageSummary.hdd}</p>
</div>
)}
</div>
<p className="mt-4 text-xs text-muted-foreground">
For detailed storage information, see the Storage section
</p>
</Card>
)}
{/* Network Summary */}
{networkControllers.length > 0 && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Network className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Network Summary</h2>
<Badge variant="outline" className="ml-auto">
{networkControllers.length} interfaces
</Badge>
</div>
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
{networkControllers.map((nic, index) => (
<div
key={index}
className="flex items-center justify-between rounded-lg border border-border/30 bg-background/50 p-3"
>
<span className="font-mono text-sm">{nic.device}</span>
<Badge variant="outline" className="text-xs">
Ethernet
</Badge>
</div>
))}
</div>
<p className="mt-4 text-xs text-muted-foreground">
For detailed network information, see the Network section
</p>
</Card>
)}
{/* Storage Summary */}
{/* PCI Devices */}
{hardwareData.pci_devices && hardwareData.pci_devices.length > 0 && (
{hardwareData?.pci_devices && hardwareData.pci_devices.length > 0 && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<CpuIcon className="h-5 w-5 text-primary" />
@@ -477,352 +110,115 @@ export default function Hardware() {
</div>
<div className="space-y-3">
{hardwareData.pci_devices.map((device, index) => (
<div
key={index}
onClick={() => setSelectedPCIDevice(device)}
className="flex cursor-pointer items-start justify-between rounded-lg border border-border/30 bg-background/50 p-4 transition-colors hover:border-primary/50 hover:bg-background/80"
>
<div className="flex-1 space-y-1">
<div className="flex items-center gap-2">
<Badge variant="outline" className="text-xs">
{device.type}
</Badge>
<span className="font-mono text-xs text-muted-foreground">{device.slot}</span>
</div>
<p className="font-medium text-sm">{device.device}</p>
<p className="text-xs text-muted-foreground">{device.vendor}</p>
</div>
</div>
))}
</div>
</Card>
)}
{hardwareData.pci_devices.map((device, index) => {
const deviceKey = `${device.slot}-${index}`
const isExpanded = expandedPCIDevice === deviceKey
{/* Thermal Monitoring */}
{hardwareData.sensors.temperatures.length > 0 && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Thermometer className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Thermal Monitoring</h2>
</div>
<div className="grid gap-3 md:grid-cols-2">
{hardwareData.sensors.temperatures.map((sensor, index) => (
<div key={index} className="space-y-1">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">{sensor.name}</span>
<span
className={`font-mono font-medium ${getTempColor(sensor.current, sensor.high, sensor.critical)}`}
>
{sensor.current.toFixed(1)}°C
</span>
</div>
<Progress
value={getTempProgress(sensor.current, sensor.critical)}
className="h-1.5 [&>div]:bg-blue-500"
/>
</div>
))}
</div>
</Card>
)}
{/* Fan Monitoring */}
{hardwareData.sensors.fans.length > 0 && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Fan className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Fan Monitoring</h2>
</div>
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
{hardwareData.sensors.fans.map((fan, index) => (
<div
key={index}
className="flex items-center justify-between rounded-lg border border-border/30 bg-background/50 p-3"
>
<div className="flex items-center gap-2">
<Fan className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">{fan.name}</span>
</div>
<span className="font-mono font-medium text-sm">{fan.current_rpm} RPM</span>
</div>
))}
</div>
</Card>
)}
{/* IPMI Fan Monitoring */}
{hasIPMIFans && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Fan className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Server Fans (IPMI)</h2>
</div>
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
{hardwareData.ipmi_fans?.map((fan, index) => (
<div
key={index}
className="flex items-center justify-between rounded-lg border border-border/30 bg-background/50 p-3"
>
<div className="flex items-center gap-2">
<Fan className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">{fan.name}</span>
</div>
<span className="font-mono font-medium text-sm">
{fan.speed.toFixed(1)} {fan.unit}
</span>
</div>
))}
</div>
</Card>
)}
{/* IPMI Power Supplies */}
{hasIPMIPower && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Battery className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Power Supplies (IPMI)</h2>
</div>
<div className="space-y-4">
{/* Power Meter */}
{hardwareData.ipmi_power?.power_meter && (
<div className="rounded-lg border border-primary/30 bg-primary/5 p-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Total Power Consumption</span>
<span className="text-2xl font-bold text-primary">
{hardwareData.ipmi_power.power_meter.watts.toFixed(0)} W
</span>
</div>
</div>
)}
{/* Individual Power Supplies */}
{hardwareData.ipmi_power?.power_supplies && hardwareData.ipmi_power.power_supplies.length > 0 && (
<div className="grid gap-3 md:grid-cols-2">
{hardwareData.ipmi_power.power_supplies.map((psu, index) => (
return (
<div key={index} className="rounded-lg border border-border/30 bg-background/50">
<div
key={index}
className="flex items-center justify-between rounded-lg border border-border/30 bg-background/50 p-3"
onClick={() => setExpandedPCIDevice(isExpanded ? null : deviceKey)}
className="flex cursor-pointer items-start justify-between p-4 transition-colors hover:bg-background/80"
>
<div className="space-y-1">
<span className="text-sm font-medium">{psu.name}</span>
<div className="flex-1 space-y-1">
<div className="flex items-center gap-2">
<Badge
variant="outline"
className={psu.status === "ok" ? "border-green-500/50 text-green-500" : "border-muted"}
>
{psu.status}
</Badge>
<Badge className={getDeviceTypeColor(device.type)}>{device.type}</Badge>
<span className="font-mono text-xs text-muted-foreground">{device.slot}</span>
</div>
<p className="font-medium text-sm">{device.device}</p>
<p className="text-xs text-muted-foreground">{device.vendor}</p>
</div>
<span className="font-mono text-lg font-semibold">
{psu.watts.toFixed(0)} {psu.unit}
</span>
{isExpanded ? (
<ChevronUp className="h-5 w-5 text-muted-foreground" />
) : (
<ChevronDown className="h-5 w-5 text-muted-foreground" />
)}
</div>
))}
</div>
)}
</div>
</Card>
)}
{/* Power Supply / UPS */}
{hasUPS && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Battery className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">Power Supply / UPS</h2>
</div>
{isExpanded && (
<div className="border-t border-border/30 p-4 space-y-3">
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Device Type</span>
<Badge className={getDeviceTypeColor(device.type)}>{device.type}</Badge>
</div>
<div className="grid gap-4 md:grid-cols-2">
{hardwareData.power.model && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Model</span>
<span className="font-mono">{hardwareData.power.model}</span>
</div>
)}
{hardwareData.power.status && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Status</span>
<Badge variant="outline">{hardwareData.power.status}</Badge>
</div>
)}
{hardwareData.power.battery_charge && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Battery Charge</span>
<span className="font-mono font-medium text-green-500">{hardwareData.power.battery_charge}</span>
</div>
)}
{hardwareData.power.time_left && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Time Left</span>
<span className="font-mono">{hardwareData.power.time_left}</span>
</div>
)}
{hardwareData.power.load_percent && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Load</span>
<span className="font-mono">{hardwareData.power.load_percent}</span>
</div>
)}
{hardwareData.power.line_voltage && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Line Voltage</span>
<span className="font-mono">{hardwareData.power.line_voltage}</span>
</div>
)}
</div>
</Card>
)}
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">PCI Slot</span>
<span className="font-mono text-sm">{device.slot}</span>
</div>
{/* UPS Information */}
{hasUPSData && (
<Card className="border-border/50 bg-card/50 p-6">
<div className="mb-4 flex items-center gap-2">
<Battery className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold">UPS / SAI</h2>
</div>
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Device Name</span>
<span className="text-sm text-right">{device.device}</span>
</div>
<div className="grid gap-4 md:grid-cols-2">
{hardwareData.ups?.model && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Model</span>
<span className="font-mono">{hardwareData.ups.model}</span>
</div>
)}
{hardwareData.ups?.status && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Status</span>
<Badge variant="outline">{hardwareData.ups.status}</Badge>
</div>
)}
{hardwareData.ups?.battery_charge && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Battery Charge</span>
<span className="font-mono font-medium text-green-500">{hardwareData.ups.battery_charge}</span>
</div>
)}
{hardwareData.ups?.time_left && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Runtime Left</span>
<span className="font-mono">{hardwareData.ups.time_left}</span>
</div>
)}
{hardwareData.ups?.load_percent && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Load</span>
<span className="font-mono">{hardwareData.ups.load_percent}</span>
</div>
)}
{hardwareData.ups?.line_voltage && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Input Voltage</span>
<span className="font-mono">{hardwareData.ups.line_voltage}</span>
</div>
)}
{hardwareData.ups?.real_power && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Real Power</span>
<span className="font-mono font-medium">{hardwareData.ups.real_power}</span>
</div>
)}
</div>
</Card>
)}
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Vendor</span>
<span className="text-sm">{device.vendor}</span>
</div>
{/* PCI Device Details Modal */}
<Dialog open={!!selectedPCIDevice} onOpenChange={() => setSelectedPCIDevice(null)}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>PCI Device Details</DialogTitle>
<DialogDescription>Detailed information about the selected PCI device</DialogDescription>
</DialogHeader>
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Class</span>
<span className="font-mono text-sm">{device.class}</span>
</div>
{selectedPCIDevice && (
<div className="space-y-4">
<div className="grid gap-3">
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Device Type</span>
<Badge variant="outline">{selectedPCIDevice.type}</Badge>
</div>
{device.driver && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Driver</span>
<span className="font-mono text-sm">{device.driver}</span>
</div>
)}
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">PCI Slot</span>
<span className="font-mono text-sm">{selectedPCIDevice.slot}</span>
</div>
{device.kernel_module && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Kernel Module</span>
<span className="font-mono text-sm">{device.kernel_module}</span>
</div>
)}
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Device Name</span>
<span className="text-sm text-right">{selectedPCIDevice.device}</span>
</div>
{device.irq && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">IRQ</span>
<span className="font-mono text-sm">{device.irq}</span>
</div>
)}
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Vendor</span>
<span className="text-sm">{selectedPCIDevice.vendor}</span>
</div>
{device.memory_address && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Memory Address</span>
<span className="font-mono text-sm">{device.memory_address}</span>
</div>
)}
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Class</span>
<span className="font-mono text-sm">{selectedPCIDevice.class}</span>
</div>
{device.link_speed && (
<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="font-mono text-sm">{device.link_speed}</span>
</div>
)}
{selectedPCIDevice.driver && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Driver</span>
<span className="font-mono text-sm">{selectedPCIDevice.driver}</span>
</div>
)}
{selectedPCIDevice.kernel_module && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Kernel Module</span>
<span className="font-mono text-sm">{selectedPCIDevice.kernel_module}</span>
</div>
)}
{selectedPCIDevice.irq && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">IRQ</span>
<span className="font-mono text-sm">{selectedPCIDevice.irq}</span>
</div>
)}
{selectedPCIDevice.memory_address && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Memory Address</span>
<span className="font-mono text-sm">{selectedPCIDevice.memory_address}</span>
</div>
)}
{selectedPCIDevice.link_speed && (
<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="font-mono text-sm">{selectedPCIDevice.link_speed}</span>
</div>
)}
{selectedPCIDevice.capabilities && selectedPCIDevice.capabilities.length > 0 && (
<div className="space-y-2 border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Capabilities</span>
<div className="flex flex-wrap gap-2">
{selectedPCIDevice.capabilities.map((cap, idx) => (
<Badge key={idx} variant="secondary" className="text-xs">
{cap}
</Badge>
))}
{device.capabilities && device.capabilities.length > 0 && (
<div className="space-y-2 border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Capabilities</span>
<div className="flex flex-wrap gap-2">
{device.capabilities.map((cap, idx) => (
<Badge key={idx} variant="secondary" className="text-xs">
{cap}
</Badge>
))}
</div>
</div>
)}
</div>
</div>
)}
</div>
</div>
)}
</DialogContent>
</Dialog>
)}
</div>
)
})}
</div>
</Card>
)}
{/* ... existing code for Fans, Power Supply, UPS sections ... */}
</div>
)
}

View File

@@ -360,8 +360,8 @@ echo "📦 Extracting binaries from downloaded packages..."
extracted_count=0
for deb in *.deb; do
if [ -f "$deb" ] && file "$deb" | grep -q "Debian binary package"; then
echo " Extracting $deb..."
dpkg-deb -x "$deb" "$WORK_DIR/extracted" && extracted_count=$((extracted_count + 1))
echo " Extracting $deb directly into AppDir..."
dpkg-deb -x "$deb" "$APP_DIR" && extracted_count=$((extracted_count + 1))
fi
done
@@ -371,64 +371,42 @@ if [ $extracted_count -eq 0 ]; then
else
echo "✅ Extracted $extracted_count package(s)"
# Copy binaries to AppDir
echo "📋 Copying monitoring tools to AppDir..."
# Organizing monitoring tools
echo "📋 Organizing monitoring tools..."
# Copy from usr/bin
if [ -d "$WORK_DIR/extracted/usr/bin" ]; then
cp -r "$WORK_DIR/extracted/usr/bin"/* "$APP_DIR/usr/bin/" 2>/dev/null || true
fi
# Copy from usr/sbin
if [ -d "$WORK_DIR/extracted/usr/sbin" ]; then
cp -r "$WORK_DIR/extracted/usr/sbin"/* "$APP_DIR/usr/bin/" 2>/dev/null || true
fi
if [ -d "$WORK_DIR/extracted/bin" ]; then
if [ -d "$APP_DIR/bin" ]; then
echo " Moving binaries from /bin to usr/bin..."
cp -r "$WORK_DIR/extracted/bin"/* "$APP_DIR/usr/bin/" 2>/dev/null || true
cp -r "$APP_DIR/bin"/* "$APP_DIR/usr/bin/" 2>/dev/null || true
rm -rf "$APP_DIR/bin"
fi
if [ -d "$WORK_DIR/extracted/usr/lib" ]; then
if [ -d "$APP_DIR/lib" ]; then
echo " Moving libraries from /lib to usr/lib..."
mkdir -p "$APP_DIR/usr/lib"
cp -r "$WORK_DIR/extracted/usr/lib"/* "$APP_DIR/usr/lib/" 2>/dev/null || true
cp -r "$APP_DIR/lib"/* "$APP_DIR/usr/lib/" 2>/dev/null || true
rm -rf "$APP_DIR/lib"
fi
if [ -d "$WORK_DIR/extracted/lib" ]; then
mkdir -p "$APP_DIR/usr/lib"
cp -r "$WORK_DIR/extracted/lib"/* "$APP_DIR/usr/lib/" 2>/dev/null || true
fi
if [ -d "$APP_DIR/usr/lib/x86_64-linux-gnu" ]; then
if [ -f "$APP_DIR/usr/lib/x86_64-linux-gnu/libfreeipmi.so.17" ]; then
echo " ✅ libfreeipmi.so.17 found at usr/lib/x86_64-linux-gnu/"
echo " Creating library symlinks..."
ln -sf "x86_64-linux-gnu/libfreeipmi.so.17" "$APP_DIR/usr/lib/libfreeipmi.so.17" 2>/dev/null || true
ln -sf "libfreeipmi.so.17" "$APP_DIR/usr/lib/x86_64-linux-gnu/libfreeipmi.so" 2>/dev/null || true
# Link libupsclient
if [ -f "$APP_DIR/usr/lib/x86_64-linux-gnu/libupsclient.so.6" ]; then
ln -sf "$APP_DIR/usr/lib/x86_64-linux-gnu/libupsclient.so.6" "$APP_DIR/usr/lib/libupsclient.so.6" 2>/dev/null || true
ln -sf "x86_64-linux-gnu/libupsclient.so.6" "$APP_DIR/usr/lib/x86_64-linux-gnu/libupsclient.so" 2>/dev/null || true
fi
# Link libfreeipmi - create multiple symlinks to ensure it's found
if [ -f "$APP_DIR/usr/lib/x86_64-linux-gnu/libfreeipmi.so.17" ]; then
echo " ✅ Found libfreeipmi.so.17, creating symlinks..."
ln -sf "$APP_DIR/usr/lib/x86_64-linux-gnu/libfreeipmi.so.17" "$APP_DIR/usr/lib/libfreeipmi.so.17" 2>/dev/null || true
ln -sf "x86_64-linux-gnu/libfreeipmi.so.17" "$APP_DIR/usr/lib/x86_64-linux-gnu/libfreeipmi.so" 2>/dev/null || true
# Also copy to root lib directory as fallback
mkdir -p "$APP_DIR/lib/x86_64-linux-gnu"
cp "$APP_DIR/usr/lib/x86_64-linux-gnu/libfreeipmi.so.17"* "$APP_DIR/lib/x86_64-linux-gnu/" 2>/dev/null || true
else
echo " ⚠️ libfreeipmi.so.17 not found after extraction"
fi
# Copy all libfreeipmi dependencies
if ls "$APP_DIR/usr/lib/x86_64-linux-gnu"/libfreeipmi* 1> /dev/null 2>&1; then
echo " Copying all libfreeipmi libraries..."
for lib in "$APP_DIR/usr/lib/x86_64-linux-gnu"/libfreeipmi*; do
libname=$(basename "$lib")
ln -sf "$APP_DIR/usr/lib/x86_64-linux-gnu/$libname" "$APP_DIR/usr/lib/$libname" 2>/dev/null || true
done
fi
mkdir -p "$APP_DIR/lib/x86_64-linux-gnu"
ln -sf "../../usr/lib/x86_64-linux-gnu/libfreeipmi.so.17" "$APP_DIR/lib/x86_64-linux-gnu/libfreeipmi.so.17" 2>/dev/null || true
else
echo " ⚠️ libfreeipmi.so.17 NOT found - ipmitool will not work"
echo " Searched in: $APP_DIR/usr/lib/x86_64-linux-gnu/"
echo " Available libraries:"
find "$APP_DIR/usr/lib" -name "libfreeipmi*" 2>/dev/null || echo " None found"
fi
if [ -f "$APP_DIR/usr/lib/x86_64-linux-gnu/libupsclient.so.6" ]; then
echo " Creating libupsclient symlinks..."
ln -sf "x86_64-linux-gnu/libupsclient.so.6" "$APP_DIR/usr/lib/libupsclient.so.6" 2>/dev/null || true
ln -sf "libupsclient.so.6" "$APP_DIR/usr/lib/x86_64-linux-gnu/libupsclient.so" 2>/dev/null || true
fi
echo "✅ Hardware monitoring tools installed successfully"
@@ -436,13 +414,6 @@ else
[ -f "$APP_DIR/usr/bin/ipmitool" ] && echo " ✅ ipmitool" || echo " ⚠️ ipmitool not found"
[ -f "$APP_DIR/usr/bin/sensors" ] && echo " ✅ sensors (lm-sensors)" || echo " ⚠️ sensors not found"
[ -f "$APP_DIR/usr/bin/upsc" ] && echo " ✅ upsc (nut-client)" || echo " ⚠️ upsc not found"
echo "📋 Verifying libraries:"
if [ -f "$APP_DIR/usr/lib/x86_64-linux-gnu/libfreeipmi.so.17" ]; then
echo " ✅ libfreeipmi.so.17 found"
else
echo " ⚠️ libfreeipmi.so.17 NOT found - ipmitool may not work"
fi
fi
# Build AppImage