aupdate AppImage

This commit is contained in:
MacRimi
2025-10-21 18:43:56 +02:00
parent 6d23d3510f
commit 797b088cc8
3 changed files with 46 additions and 23 deletions

View File

@@ -4,7 +4,7 @@ import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ArrowLeft, Loader2 } from "lucide-react" import { ArrowLeft, Loader2 } from "lucide-react"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts" import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
interface MetricsViewProps { interface MetricsViewProps {
vmid: number vmid: number
@@ -90,8 +90,8 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
timestamp: item.time, timestamp: item.time,
cpu: item.cpu ? Number((item.cpu * 100).toFixed(2)) : 0, cpu: item.cpu ? Number((item.cpu * 100).toFixed(2)) : 0,
memory: item.mem ? Number(((item.mem / item.maxmem) * 100).toFixed(2)) : 0, memory: item.mem ? Number(((item.mem / item.maxmem) * 100).toFixed(2)) : 0,
memoryMB: item.mem ? Number((item.mem / 1024 / 1024).toFixed(0)) : 0, memoryGB: item.mem ? Number((item.mem / 1024 / 1024 / 1024).toFixed(2)) : 0,
maxMemoryMB: item.maxmem ? Number((item.maxmem / 1024 / 1024).toFixed(0)) : 0, maxMemoryGB: item.maxmem ? Number((item.maxmem / 1024 / 1024 / 1024).toFixed(2)) : 0,
netin: item.netin ? Number((item.netin / 1024 / 1024).toFixed(2)) : 0, netin: item.netin ? Number((item.netin / 1024 / 1024).toFixed(2)) : 0,
netout: item.netout ? Number((item.netout / 1024 / 1024).toFixed(2)) : 0, netout: item.netout ? Number((item.netout / 1024 / 1024).toFixed(2)) : 0,
diskread: item.diskread ? Number((item.diskread / 1024 / 1024).toFixed(2)) : 0, diskread: item.diskread ? Number((item.diskread / 1024 / 1024).toFixed(2)) : 0,
@@ -162,7 +162,7 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
className="text-foreground" className="text-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
label={{ value: "%", angle: -90, position: "insideLeft", fill: "currentColor" }} label={{ value: "%", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, (dataMax: number) => Math.ceil(dataMax * 1.15)]} domain={[0, "dataMax"]}
/> />
<Tooltip <Tooltip
contentStyle={{ contentStyle={{
@@ -171,7 +171,6 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
borderRadius: "6px", borderRadius: "6px",
}} }}
/> />
<Legend wrapperStyle={{ paddingTop: "10px" }} />
<Area <Area
type="monotone" type="monotone"
dataKey="cpu" dataKey="cpu"
@@ -206,8 +205,8 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
stroke="currentColor" stroke="currentColor"
className="text-foreground" className="text-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
label={{ value: "%", angle: -90, position: "insideLeft", fill: "currentColor" }} label={{ value: "GB", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, (dataMax: number) => Math.ceil(dataMax * 1.15)]} domain={[0, "dataMax"]}
/> />
<Tooltip <Tooltip
contentStyle={{ contentStyle={{
@@ -216,15 +215,14 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
borderRadius: "6px", borderRadius: "6px",
}} }}
/> />
<Legend wrapperStyle={{ paddingTop: "10px" }} />
<Area <Area
type="monotone" type="monotone"
dataKey="memory" dataKey="memoryGB"
stroke="#10b981" stroke="#10b981"
fill="#10b981" fill="#10b981"
fillOpacity={0.3} fillOpacity={0.3}
strokeWidth={2} strokeWidth={2}
name="Memory %" name="Memory GB"
/> />
</AreaChart> </AreaChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -252,7 +250,7 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
className="text-foreground" className="text-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
label={{ value: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }} label={{ value: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, (dataMax: number) => Math.ceil(dataMax * 1.15)]} domain={[0, "dataMax"]}
/> />
<Tooltip <Tooltip
contentStyle={{ contentStyle={{
@@ -261,7 +259,6 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
borderRadius: "6px", borderRadius: "6px",
}} }}
/> />
<Legend wrapperStyle={{ paddingTop: "10px" }} />
<Area <Area
type="monotone" type="monotone"
dataKey="diskread" dataKey="diskread"
@@ -306,7 +303,7 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
className="text-foreground" className="text-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
label={{ value: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }} label={{ value: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, (dataMax: number) => Math.ceil(dataMax * 1.15)]} domain={[0, "dataMax"]}
/> />
<Tooltip <Tooltip
contentStyle={{ contentStyle={{
@@ -315,7 +312,6 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
borderRadius: "6px", borderRadius: "6px",
}} }}
/> />
<Legend wrapperStyle={{ paddingTop: "10px" }} />
<Area <Area
type="monotone" type="monotone"
dataKey="netin" dataKey="netin"

View File

@@ -809,9 +809,6 @@ export function VirtualMachines() {
{selectedVM && ( {selectedVM && (
<> <>
<div> <div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
Basic Information
</h3>
<Card <Card
className="border border-border bg-card/50 cursor-pointer hover:bg-card/70 transition-colors" className="border border-border bg-card/50 cursor-pointer hover:bg-card/70 transition-colors"
onClick={handleMetricsClick} onClick={handleMetricsClick}
@@ -1102,9 +1099,6 @@ export function VirtualMachines() {
</div> </div>
<div className="border-t border-border bg-background px-6 py-4 mt-auto"> <div className="border-t border-border bg-background px-6 py-4 mt-auto">
<h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
Control Actions
</h3>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<Button <Button
className="w-full bg-green-600 hover:bg-green-700 text-white" className="w-full bg-green-600 hover:bg-green-700 text-white"

View File

@@ -4831,12 +4831,45 @@ def api_vm_details(vmid):
if config_result.returncode == 0: if config_result.returncode == 0:
config = json.loads(config_result.stdout) config = json.loads(config_result.stdout)
return jsonify({ os_info = {}
if vm_type == 'lxc' and resource.get('status') == 'running':
try:
print(f"[v0] Reading /etc/os-release for LXC {vmid}...")
os_release_result = subprocess.run(
['pct', 'exec', str(vmid), '--', 'cat', '/etc/os-release'],
capture_output=True, text=True, timeout=5)
if os_release_result.returncode == 0:
# Parse /etc/os-release content
for line in os_release_result.stdout.split('\n'):
line = line.strip()
if line.startswith('ID='):
os_info['id'] = line.split('=', 1)[1].strip('"').strip("'")
elif line.startswith('VERSION_ID='):
os_info['version_id'] = line.split('=', 1)[1].strip('"').strip("'")
elif line.startswith('NAME='):
os_info['name'] = line.split('=', 1)[1].strip('"').strip("'")
elif line.startswith('PRETTY_NAME='):
os_info['pretty_name'] = line.split('=', 1)[1].strip('"').strip("'")
print(f"[v0] OS Info for LXC {vmid}: {os_info}")
else:
print(f"[v0] Failed to read /etc/os-release for LXC {vmid}: {os_release_result.stderr}")
except Exception as e:
print(f"[v0] Error reading OS info for LXC {vmid}: {e}")
response_data = {
**resource, **resource,
'config': config, 'config': config,
'node': node, 'node': node,
'vm_type': vm_type 'vm_type': vm_type
}) }
# Add OS info if available
if os_info:
response_data['os_info'] = os_info
return jsonify(response_data)
return jsonify({'error': f'VM/LXC {vmid} not found'}), 404 return jsonify({'error': f'VM/LXC {vmid} not found'}), 404
else: else:
@@ -4904,7 +4937,7 @@ def api_vm_control(vmid):
return jsonify({'error': 'Invalid action'}), 400 return jsonify({'error': 'Invalid action'}), 400
# Get VM type and node # Get VM type and node
result = subprocess.run(['pvesh', 'get', f'/cluster/resources', '--type', 'vm', '--output-format', 'json'], result = subprocess.run(['pvesh', 'get', '/cluster/resources', '--type', 'vm', '--output-format', 'json'],
capture_output=True, text=True, timeout=10) capture_output=True, text=True, timeout=10)
if result.returncode == 0: if result.returncode == 0: