mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 11:36:17 +00:00
Update AppImage
This commit is contained in:
@@ -20,6 +20,8 @@ import {
|
|||||||
HardDrive,
|
HardDrive,
|
||||||
Calendar,
|
Calendar,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
|
Bell,
|
||||||
|
Mail,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
|
|
||||||
@@ -57,6 +59,14 @@ interface Event {
|
|||||||
duration: string
|
duration: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
timestamp: string
|
||||||
|
type: string
|
||||||
|
service: string
|
||||||
|
message: string
|
||||||
|
source: string
|
||||||
|
}
|
||||||
|
|
||||||
interface SystemLog {
|
interface SystemLog {
|
||||||
timestamp: string
|
timestamp: string
|
||||||
level: string
|
level: string
|
||||||
@@ -71,6 +81,7 @@ export function SystemLogs() {
|
|||||||
const [logs, setLogs] = useState<SystemLog[]>([])
|
const [logs, setLogs] = useState<SystemLog[]>([])
|
||||||
const [backups, setBackups] = useState<Backup[]>([])
|
const [backups, setBackups] = useState<Backup[]>([])
|
||||||
const [events, setEvents] = useState<Event[]>([])
|
const [events, setEvents] = useState<Event[]>([])
|
||||||
|
const [notifications, setNotifications] = useState<Notification[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
@@ -79,6 +90,13 @@ export function SystemLogs() {
|
|||||||
const [serviceFilter, setServiceFilter] = useState("all")
|
const [serviceFilter, setServiceFilter] = useState("all")
|
||||||
const [activeTab, setActiveTab] = useState("logs")
|
const [activeTab, setActiveTab] = useState("logs")
|
||||||
|
|
||||||
|
const getApiUrl = (endpoint: string) => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
return `${window.location.protocol}//${window.location.hostname}:8008${endpoint}`
|
||||||
|
}
|
||||||
|
return `http://localhost:8008${endpoint}`
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch data
|
// Fetch data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAllData()
|
fetchAllData()
|
||||||
@@ -92,11 +110,11 @@ export function SystemLogs() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
// Fetch logs, backups, and events in parallel
|
const [logsRes, backupsRes, eventsRes, notificationsRes] = await Promise.all([
|
||||||
const [logsRes, backupsRes, eventsRes] = await Promise.all([
|
|
||||||
fetchSystemLogs(),
|
fetchSystemLogs(),
|
||||||
fetch("http://localhost:8008/api/backups"),
|
fetch(getApiUrl("/api/backups")),
|
||||||
fetch("http://localhost:8008/api/events?limit=50"),
|
fetch(getApiUrl("/api/events?limit=50")),
|
||||||
|
fetch(getApiUrl("/api/notifications")),
|
||||||
])
|
])
|
||||||
|
|
||||||
setLogs(logsRes)
|
setLogs(logsRes)
|
||||||
@@ -110,6 +128,11 @@ export function SystemLogs() {
|
|||||||
const eventsData = await eventsRes.json()
|
const eventsData = await eventsRes.json()
|
||||||
setEvents(eventsData.events || [])
|
setEvents(eventsData.events || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (notificationsRes.ok) {
|
||||||
|
const notificationsData = await notificationsRes.json()
|
||||||
|
setNotifications(notificationsData.notifications || [])
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[v0] Error fetching system logs data:", err)
|
console.error("[v0] Error fetching system logs data:", err)
|
||||||
setError("Failed to connect to server")
|
setError("Failed to connect to server")
|
||||||
@@ -120,9 +143,7 @@ export function SystemLogs() {
|
|||||||
|
|
||||||
const fetchSystemLogs = async (): Promise<SystemLog[]> => {
|
const fetchSystemLogs = async (): Promise<SystemLog[]> => {
|
||||||
try {
|
try {
|
||||||
const baseUrl =
|
const apiUrl = getApiUrl("/api/logs")
|
||||||
typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : ""
|
|
||||||
const apiUrl = `${baseUrl}/api/logs`
|
|
||||||
|
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -146,7 +167,7 @@ export function SystemLogs() {
|
|||||||
|
|
||||||
const handleDownloadLogs = async (type = "system") => {
|
const handleDownloadLogs = async (type = "system") => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:8008/api/logs/download?type=${type}&lines=1000`)
|
const response = await fetch(getApiUrl(`/api/logs/download?type=${type}&lines=1000`))
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob)
|
||||||
@@ -186,6 +207,8 @@ export function SystemLogs() {
|
|||||||
case "info":
|
case "info":
|
||||||
case "notice":
|
case "notice":
|
||||||
return "bg-blue-500/10 text-blue-500 border-blue-500/20"
|
return "bg-blue-500/10 text-blue-500 border-blue-500/20"
|
||||||
|
case "success":
|
||||||
|
return "bg-green-500/10 text-green-500 border-green-500/20"
|
||||||
default:
|
default:
|
||||||
return "bg-gray-500/10 text-gray-500 border-gray-500/20"
|
return "bg-gray-500/10 text-gray-500 border-gray-500/20"
|
||||||
}
|
}
|
||||||
@@ -203,11 +226,30 @@ export function SystemLogs() {
|
|||||||
case "info":
|
case "info":
|
||||||
case "notice":
|
case "notice":
|
||||||
return <Info className="h-3 w-3 mr-1" />
|
return <Info className="h-3 w-3 mr-1" />
|
||||||
|
case "success":
|
||||||
|
return <CheckCircle className="h-3 w-3 mr-1" />
|
||||||
default:
|
default:
|
||||||
return <CheckCircle className="h-3 w-3 mr-1" />
|
return <CheckCircle className="h-3 w-3 mr-1" />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNotificationIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case "email":
|
||||||
|
return <Mail className="h-4 w-4 text-blue-500" />
|
||||||
|
case "webhook":
|
||||||
|
return <Activity className="h-4 w-4 text-purple-500" />
|
||||||
|
case "alert":
|
||||||
|
return <AlertTriangle className="h-4 w-4 text-yellow-500" />
|
||||||
|
case "error":
|
||||||
|
return <XCircle className="h-4 w-4 text-red-500" />
|
||||||
|
case "success":
|
||||||
|
return <CheckCircle className="h-4 w-4 text-green-500" />
|
||||||
|
default:
|
||||||
|
return <Bell className="h-4 w-4 text-gray-500" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const logCounts = {
|
const logCounts = {
|
||||||
total: logs.length,
|
total: logs.length,
|
||||||
error: logs.filter((log) => ["error", "critical", "emergency", "alert"].includes(log.level)).length,
|
error: logs.filter((log) => ["error", "critical", "emergency", "alert"].includes(log.level)).length,
|
||||||
@@ -306,10 +348,11 @@ export function SystemLogs() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-4">
|
||||||
<TabsTrigger value="logs">System Logs</TabsTrigger>
|
<TabsTrigger value="logs">System Logs</TabsTrigger>
|
||||||
<TabsTrigger value="events">Recent Events</TabsTrigger>
|
<TabsTrigger value="events">Recent Events</TabsTrigger>
|
||||||
<TabsTrigger value="backups">Backups</TabsTrigger>
|
<TabsTrigger value="backups">Backups</TabsTrigger>
|
||||||
|
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* System Logs Tab */}
|
{/* System Logs Tab */}
|
||||||
@@ -508,6 +551,39 @@ export function SystemLogs() {
|
|||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="notifications" className="space-y-4">
|
||||||
|
<ScrollArea className="h-[600px] w-full rounded-md border border-border">
|
||||||
|
<div className="space-y-2 p-4">
|
||||||
|
{notifications.map((notification, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-start space-x-4 p-3 rounded-lg bg-card/50 border border-border/50 hover:bg-card/80 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0">{getNotificationIcon(notification.type)}</div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<div className="text-sm font-medium text-foreground capitalize">{notification.type}</div>
|
||||||
|
<div className="text-xs text-muted-foreground font-mono">{notification.timestamp}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-foreground mb-1">{notification.message}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
Service: {notification.service} • Source: {notification.source}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{notifications.length === 0 && (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
<Bell className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||||
|
<p>No notifications found</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -437,14 +437,6 @@ def get_system_info():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Note: pveversion not available: {e}")
|
print(f"Note: pveversion not available: {e}")
|
||||||
|
|
||||||
kernel_version = None
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['uname', '-r'], capture_output=True, text=True, timeout=5)
|
|
||||||
if result.returncode == 0:
|
|
||||||
kernel_version = result.stdout.strip()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Note: uname not available: {e}")
|
|
||||||
|
|
||||||
cpu_cores = psutil.cpu_count(logical=False) # Physical cores only
|
cpu_cores = psutil.cpu_count(logical=False) # Physical cores only
|
||||||
|
|
||||||
available_updates = 0
|
available_updates = 0
|
||||||
@@ -2964,12 +2956,41 @@ def get_hardware_info():
|
|||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
if line.startswith('Memory Device'):
|
if line.startswith('Memory Device'):
|
||||||
if current_module and current_module.get('size') != 'No Module Installed':
|
# Ensure only modules with size and not 'No Module Installed' are appended
|
||||||
|
if current_module and current_module.get('size') and current_module.get('size') != 'No Module Installed' and current_module.get('size') != 0:
|
||||||
hardware_data['memory_modules'].append(current_module)
|
hardware_data['memory_modules'].append(current_module)
|
||||||
current_module = {}
|
current_module = {}
|
||||||
elif line.startswith('Size:'):
|
elif line.startswith('Size:'):
|
||||||
size = line.split(':', 1)[1].strip()
|
size_str = line.split(':', 1)[1].strip()
|
||||||
current_module['size'] = size
|
if size_str and size_str != 'No Module Installed' and size_str != 'Not Specified':
|
||||||
|
try:
|
||||||
|
# Parse size like "32768 MB" or "32 GB"
|
||||||
|
parts = size_str.split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
value = float(parts[0])
|
||||||
|
unit = parts[1].upper()
|
||||||
|
|
||||||
|
# Convert to KB
|
||||||
|
if unit == 'GB':
|
||||||
|
size_kb = value * 1024 * 1024
|
||||||
|
elif unit == 'MB':
|
||||||
|
size_kb = value * 1024
|
||||||
|
elif unit == 'KB':
|
||||||
|
size_kb = value
|
||||||
|
else:
|
||||||
|
size_kb = value # Assume KB if no unit
|
||||||
|
|
||||||
|
current_module['size'] = size_kb
|
||||||
|
print(f"[v0] Parsed memory size: {size_str} -> {size_kb} KB")
|
||||||
|
else:
|
||||||
|
# Handle cases where unit might be missing but value is present
|
||||||
|
current_module['size'] = float(size_str) if size_str else 0
|
||||||
|
print(f"[v0] Parsed memory size (no unit): {size_str} -> {current_module['size']} KB")
|
||||||
|
except (ValueError, IndexError) as e:
|
||||||
|
print(f"[v0] Error parsing memory size '{size_str}': {e}")
|
||||||
|
current_module['size'] = 0
|
||||||
|
else:
|
||||||
|
current_module['size'] = 0 # Default to 0 if no size or explicitly 'No Module Installed'
|
||||||
elif line.startswith('Type:'):
|
elif line.startswith('Type:'):
|
||||||
current_module['type'] = line.split(':', 1)[1].strip()
|
current_module['type'] = line.split(':', 1)[1].strip()
|
||||||
elif line.startswith('Speed:'):
|
elif line.startswith('Speed:'):
|
||||||
@@ -2981,7 +3002,8 @@ def get_hardware_info():
|
|||||||
elif line.startswith('Locator:'):
|
elif line.startswith('Locator:'):
|
||||||
current_module['slot'] = line.split(':', 1)[1].strip()
|
current_module['slot'] = line.split(':', 1)[1].strip()
|
||||||
|
|
||||||
if current_module and current_module.get('size') != 'No Module Installed':
|
# Append the last module if it's valid
|
||||||
|
if current_module and current_module.get('size') and current_module.get('size') != 'No Module Installed' and current_module.get('size') != 0:
|
||||||
hardware_data['memory_modules'].append(current_module)
|
hardware_data['memory_modules'].append(current_module)
|
||||||
|
|
||||||
print(f"[v0] Memory modules: {len(hardware_data['memory_modules'])} installed")
|
print(f"[v0] Memory modules: {len(hardware_data['memory_modules'])} installed")
|
||||||
@@ -3520,6 +3542,166 @@ def api_notifications():
|
|||||||
'total': 0
|
'total': 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.route('/api/backups', methods=['GET'])
|
||||||
|
def api_backups():
|
||||||
|
"""Get list of all backup files from Proxmox storage"""
|
||||||
|
try:
|
||||||
|
backups = []
|
||||||
|
|
||||||
|
# Get list of storage locations
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['pvesh', 'get', '/storage', '--output-format', 'json'],
|
||||||
|
capture_output=True, text=True, timeout=10)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
storages = json.loads(result.stdout)
|
||||||
|
|
||||||
|
# For each storage, get backup files
|
||||||
|
for storage in storages:
|
||||||
|
storage_id = storage.get('storage')
|
||||||
|
storage_type = storage.get('type')
|
||||||
|
|
||||||
|
# Only check storages that can contain backups
|
||||||
|
if storage_type in ['dir', 'nfs', 'cifs', 'pbs']:
|
||||||
|
try:
|
||||||
|
# Get content of storage
|
||||||
|
content_result = subprocess.run(
|
||||||
|
['pvesh', 'get', f'/nodes/localhost/storage/{storage_id}/content', '--output-format', 'json'],
|
||||||
|
capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if content_result.returncode == 0:
|
||||||
|
contents = json.loads(content_result.stdout)
|
||||||
|
|
||||||
|
for item in contents:
|
||||||
|
if item.get('content') == 'backup':
|
||||||
|
# Parse backup information
|
||||||
|
volid = item.get('volid', '')
|
||||||
|
size = item.get('size', 0)
|
||||||
|
ctime = item.get('ctime', 0)
|
||||||
|
|
||||||
|
# Extract VMID from volid (format: storage:backup/vzdump-qemu-100-...)
|
||||||
|
vmid = None
|
||||||
|
backup_type = None
|
||||||
|
if 'vzdump-qemu-' in volid:
|
||||||
|
backup_type = 'qemu'
|
||||||
|
try:
|
||||||
|
vmid = volid.split('vzdump-qemu-')[1].split('-')[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
elif 'vzdump-lxc-' in volid:
|
||||||
|
backup_type = 'lxc'
|
||||||
|
try:
|
||||||
|
vmid = volid.split('vzdump-lxc-')[1].split('-')[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
backups.append({
|
||||||
|
'volid': volid,
|
||||||
|
'storage': storage_id,
|
||||||
|
'vmid': vmid,
|
||||||
|
'type': backup_type,
|
||||||
|
'size': size,
|
||||||
|
'size_human': format_bytes(size),
|
||||||
|
'created': datetime.fromtimestamp(ctime).strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'timestamp': ctime
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting content for storage {storage_id}: {e}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting storage list: {e}")
|
||||||
|
|
||||||
|
# Sort by creation time (newest first)
|
||||||
|
backups.sort(key=lambda x: x['timestamp'], reverse=True)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'backups': backups,
|
||||||
|
'total': len(backups)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting backups: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'error': str(e),
|
||||||
|
'backups': [],
|
||||||
|
'total': 0
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route('/api/events', methods=['GET'])
|
||||||
|
def api_events():
|
||||||
|
"""Get recent Proxmox events and tasks"""
|
||||||
|
try:
|
||||||
|
limit = request.args.get('limit', '50')
|
||||||
|
events = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['pvesh', 'get', '/cluster/tasks', '--output-format', 'json'],
|
||||||
|
capture_output=True, text=True, timeout=10)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
tasks = json.loads(result.stdout)
|
||||||
|
|
||||||
|
for task in tasks[:int(limit)]:
|
||||||
|
upid = task.get('upid', '')
|
||||||
|
task_type = task.get('type', 'unknown')
|
||||||
|
status = task.get('status', 'unknown')
|
||||||
|
node = task.get('node', 'unknown')
|
||||||
|
user = task.get('user', 'unknown')
|
||||||
|
vmid = task.get('id', '')
|
||||||
|
starttime = task.get('starttime', 0)
|
||||||
|
endtime = task.get('endtime', 0)
|
||||||
|
|
||||||
|
# Calculate duration
|
||||||
|
duration = ''
|
||||||
|
if endtime and starttime:
|
||||||
|
duration_sec = endtime - starttime
|
||||||
|
if duration_sec < 60:
|
||||||
|
duration = f"{duration_sec}s"
|
||||||
|
elif duration_sec < 3600:
|
||||||
|
duration = f"{duration_sec // 60}m {duration_sec % 60}s"
|
||||||
|
else:
|
||||||
|
hours = duration_sec // 3600
|
||||||
|
minutes = (duration_sec % 3600) // 60
|
||||||
|
duration = f"{hours}h {minutes}m"
|
||||||
|
|
||||||
|
# Determine level based on status
|
||||||
|
level = 'info'
|
||||||
|
if status == 'OK':
|
||||||
|
level = 'info'
|
||||||
|
elif status in ['stopped', 'error']:
|
||||||
|
level = 'error'
|
||||||
|
elif status == 'running':
|
||||||
|
level = 'warning'
|
||||||
|
|
||||||
|
events.append({
|
||||||
|
'upid': upid,
|
||||||
|
'type': task_type,
|
||||||
|
'status': status,
|
||||||
|
'level': level,
|
||||||
|
'node': node,
|
||||||
|
'user': user,
|
||||||
|
'vmid': str(vmid) if vmid else '',
|
||||||
|
'starttime': datetime.fromtimestamp(starttime).strftime('%Y-%m-%d %H:%M:%S') if starttime else '',
|
||||||
|
'endtime': datetime.fromtimestamp(endtime).strftime('%Y-%m-%d %H:%M:%S') if endtime else 'Running',
|
||||||
|
'duration': duration
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting events: {e}")
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'events': events,
|
||||||
|
'total': len(events)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting events: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'error': str(e),
|
||||||
|
'events': [],
|
||||||
|
'total': 0
|
||||||
|
})
|
||||||
|
|
||||||
@app.route('/api/health', methods=['GET'])
|
@app.route('/api/health', methods=['GET'])
|
||||||
def api_health():
|
def api_health():
|
||||||
"""Health check endpoint"""
|
"""Health check endpoint"""
|
||||||
@@ -3619,7 +3801,7 @@ def api_hardware():
|
|||||||
'motherboard': hardware_info.get('motherboard', {}), # Corrected: use hardware_info
|
'motherboard': hardware_info.get('motherboard', {}), # Corrected: use hardware_info
|
||||||
'bios': hardware_info.get('motherboard', {}).get('bios', {}), # Extract BIOS info
|
'bios': hardware_info.get('motherboard', {}).get('bios', {}), # Extract BIOS info
|
||||||
'memory_modules': hardware_info.get('memory_modules', []),
|
'memory_modules': hardware_info.get('memory_modules', []),
|
||||||
'storage_devices': hardware_info.get('storage_devices', []), # Fixed: use hardware_info
|
'storage_devices': hardware_info.get('storage_devices', []), # Fixed: use hardware_data
|
||||||
'pci_devices': hardware_info.get('pci_devices', []),
|
'pci_devices': hardware_info.get('pci_devices', []),
|
||||||
'temperatures': hardware_info.get('sensors', {}).get('temperatures', []),
|
'temperatures': hardware_info.get('sensors', {}).get('temperatures', []),
|
||||||
'fans': all_fans, # Return combined fans (sensors + IPMI)
|
'fans': all_fans, # Return combined fans (sensors + IPMI)
|
||||||
|
|||||||
Reference in New Issue
Block a user