mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-10-10 20:06:18 +00:00
Update AppImage
This commit is contained in:
@@ -87,6 +87,38 @@ interface UPSInfo {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -114,6 +146,9 @@ interface HardwareData {
|
||||
fans: FanSensor[]
|
||||
}
|
||||
power: UPSInfo
|
||||
ipmi_fans?: IPMIFan[]
|
||||
ipmi_power?: IPMIPower
|
||||
ups?: UPSData
|
||||
}
|
||||
|
||||
export default function Hardware() {
|
||||
@@ -186,6 +221,11 @@ export default function Hardware() {
|
||||
|
||||
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) => {
|
||||
@@ -513,6 +553,84 @@ export default function Hardware() {
|
||||
</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) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between rounded-lg border border-border/30 bg-background/50 p-3"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<span className="text-sm font-medium">{psu.name}</span>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<span className="font-mono text-lg font-semibold">
|
||||
{psu.watts.toFixed(0)} {psu.unit}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Power Supply / UPS */}
|
||||
{hasUPS && (
|
||||
<Card className="border-border/50 bg-card/50 p-6">
|
||||
@@ -562,6 +680,61 @@ export default function Hardware() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 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="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>
|
||||
)}
|
||||
|
||||
{/* PCI Device Details Modal */}
|
||||
<Dialog open={!!selectedPCIDevice} onOpenChange={() => setSelectedPCIDevice(null)}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
|
@@ -1293,6 +1293,143 @@ def get_proxmox_vms():
|
||||
'vms': []
|
||||
}
|
||||
|
||||
def get_ipmi_fans():
|
||||
"""Get fan information from IPMI"""
|
||||
fans = []
|
||||
try:
|
||||
result = subprocess.run(['ipmitool', 'sensor'], capture_output=True, text=True, timeout=10)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'fan' in line.lower() and '|' in line:
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) >= 3:
|
||||
name = parts[0]
|
||||
value_str = parts[1]
|
||||
unit = parts[2] if len(parts) > 2 else ''
|
||||
|
||||
# Skip "DutyCycle" and "Presence" entries
|
||||
if 'dutycycle' in name.lower() or 'presence' in name.lower():
|
||||
continue
|
||||
|
||||
try:
|
||||
value = float(value_str)
|
||||
fans.append({
|
||||
'name': name,
|
||||
'speed': value,
|
||||
'unit': unit
|
||||
})
|
||||
print(f"[v0] IPMI Fan: {name} = {value} {unit}")
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
print(f"[v0] Found {len(fans)} IPMI fans")
|
||||
except FileNotFoundError:
|
||||
print("[v0] ipmitool not found")
|
||||
except Exception as e:
|
||||
print(f"[v0] Error getting IPMI fans: {e}")
|
||||
|
||||
return fans
|
||||
|
||||
def get_ipmi_power():
|
||||
"""Get power supply information from IPMI"""
|
||||
power_supplies = []
|
||||
power_meter = None
|
||||
|
||||
try:
|
||||
result = subprocess.run(['ipmitool', 'sensor'], capture_output=True, text=True, timeout=10)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.split('\n'):
|
||||
if ('power supply' in line.lower() or 'power meter' in line.lower()) and '|' in line:
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) >= 3:
|
||||
name = parts[0]
|
||||
value_str = parts[1]
|
||||
unit = parts[2] if len(parts) > 2 else ''
|
||||
|
||||
try:
|
||||
value = float(value_str)
|
||||
|
||||
if 'power meter' in name.lower():
|
||||
power_meter = {
|
||||
'name': name,
|
||||
'watts': value,
|
||||
'unit': unit
|
||||
}
|
||||
print(f"[v0] IPMI Power Meter: {value} {unit}")
|
||||
else:
|
||||
power_supplies.append({
|
||||
'name': name,
|
||||
'watts': value,
|
||||
'unit': unit,
|
||||
'status': 'ok' if value > 0 else 'off'
|
||||
})
|
||||
print(f"[v0] IPMI PSU: {name} = {value} {unit}")
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
print(f"[v0] Found {len(power_supplies)} IPMI power supplies")
|
||||
except FileNotFoundError:
|
||||
print("[v0] ipmitool not found")
|
||||
except Exception as e:
|
||||
print(f"[v0] Error getting IPMI power: {e}")
|
||||
|
||||
return {
|
||||
'power_supplies': power_supplies,
|
||||
'power_meter': power_meter
|
||||
}
|
||||
|
||||
def get_ups_info():
|
||||
"""Get UPS information from NUT (upsc)"""
|
||||
ups_data = {}
|
||||
|
||||
try:
|
||||
# First, list available UPS devices
|
||||
result = subprocess.run(['upsc', '-l'], capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
ups_list = result.stdout.strip().split('\n')
|
||||
if ups_list and ups_list[0]:
|
||||
ups_name = ups_list[0]
|
||||
print(f"[v0] Found UPS: {ups_name}")
|
||||
|
||||
# Get detailed UPS info
|
||||
result = subprocess.run(['upsc', ups_name], capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.split('\n'):
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
|
||||
# Map common UPS variables
|
||||
if key == 'device.model':
|
||||
ups_data['model'] = value
|
||||
elif key == 'ups.status':
|
||||
ups_data['status'] = value
|
||||
elif key == 'battery.charge':
|
||||
ups_data['battery_charge'] = f"{value}%"
|
||||
elif key == 'battery.runtime':
|
||||
# Convert seconds to minutes
|
||||
try:
|
||||
runtime_sec = int(value)
|
||||
runtime_min = runtime_sec // 60
|
||||
ups_data['time_left'] = f"{runtime_min} minutes"
|
||||
except ValueError:
|
||||
ups_data['time_left'] = value
|
||||
elif key == 'ups.load':
|
||||
ups_data['load_percent'] = f"{value}%"
|
||||
elif key == 'input.voltage':
|
||||
ups_data['line_voltage'] = f"{value}V"
|
||||
elif key == 'ups.realpower':
|
||||
ups_data['real_power'] = f"{value}W"
|
||||
|
||||
print(f"[v0] UPS data: {ups_data}")
|
||||
except FileNotFoundError:
|
||||
print("[v0] upsc not found")
|
||||
except Exception as e:
|
||||
print(f"[v0] Error getting UPS info: {e}")
|
||||
|
||||
return ups_data
|
||||
|
||||
def get_hardware_info():
|
||||
"""Get comprehensive hardware information"""
|
||||
hardware_data = {
|
||||
@@ -1307,7 +1444,10 @@ def get_hardware_info():
|
||||
'temperatures': [],
|
||||
'fans': []
|
||||
},
|
||||
'power': {}
|
||||
'power': {},
|
||||
'ipmi_fans': [], # Added IPMI fans
|
||||
'ipmi_power': {}, # Added IPMI power
|
||||
'ups': {} # Added UPS info
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -1654,6 +1794,18 @@ def get_hardware_info():
|
||||
except Exception as e:
|
||||
print(f"[v0] Error getting UPS info: {e}")
|
||||
|
||||
ipmi_fans = get_ipmi_fans()
|
||||
if ipmi_fans:
|
||||
hardware_data['ipmi_fans'] = ipmi_fans
|
||||
|
||||
ipmi_power = get_ipmi_power()
|
||||
if ipmi_power['power_supplies'] or ipmi_power['power_meter']:
|
||||
hardware_data['ipmi_power'] = ipmi_power
|
||||
|
||||
ups_info = get_ups_info()
|
||||
if ups_info:
|
||||
hardware_data['ups'] = ups_info
|
||||
|
||||
return hardware_data
|
||||
|
||||
except Exception as e:
|
||||
|
Reference in New Issue
Block a user