mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 19:46:18 +00:00
aupdate AppImage
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user