mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-30 03:16:34 +00:00
update virtual-machines.tsx
This commit is contained in:
@@ -142,8 +142,8 @@ export function NetworkMetrics() {
|
|||||||
error,
|
error,
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useSWR<NetworkData>("/api/network", fetcher, {
|
} = useSWR<NetworkData>("/api/network", fetcher, {
|
||||||
refreshInterval: 53000,
|
refreshInterval: 15000,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: true,
|
||||||
revalidateOnReconnect: true,
|
revalidateOnReconnect: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export function StorageOverview() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchStorageData()
|
fetchStorageData()
|
||||||
const interval = setInterval(fetchStorageData, 60000)
|
const interval = setInterval(fetchStorageData, 30000)
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@@ -423,6 +423,36 @@ export function VirtualMachines() {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Keep the open modal's VM in sync with the 5s poll of /api/vms so CPU/RAM/I-O
|
||||||
|
// values don't stay frozen at click-time while the user has the modal open.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedVM || !vmData) return
|
||||||
|
const updated = vmData.find((v) => v.vmid === selectedVM.vmid)
|
||||||
|
if (!updated) return
|
||||||
|
// Avoid unnecessary setState when no field changed (reference-equal shortcut first).
|
||||||
|
if (updated === selectedVM) return
|
||||||
|
setSelectedVM(updated)
|
||||||
|
}, [vmData])
|
||||||
|
|
||||||
|
// Faster per-VM live status poll that only runs while the modal is open.
|
||||||
|
// SWR disables polling when the key is null, so this is truly scoped to the modal.
|
||||||
|
const { data: liveVMStatus } = useSWR<VMData>(
|
||||||
|
selectedVM ? `/api/vms/${selectedVM.vmid}/status` : null,
|
||||||
|
fetcher,
|
||||||
|
{
|
||||||
|
refreshInterval: 2500,
|
||||||
|
revalidateOnFocus: true,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
dedupingInterval: 1000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!liveVMStatus || !selectedVM) return
|
||||||
|
if (liveVMStatus.vmid !== selectedVM.vmid) return
|
||||||
|
setSelectedVM((prev) => (prev ? { ...prev, ...liveVMStatus } : prev))
|
||||||
|
}, [liveVMStatus])
|
||||||
|
|
||||||
const handleVMClick = async (vm: VMData) => {
|
const handleVMClick = async (vm: VMData) => {
|
||||||
setSelectedVM(vm)
|
setSelectedVM(vm)
|
||||||
setCurrentView("main")
|
setCurrentView("main")
|
||||||
|
|||||||
@@ -8046,6 +8046,55 @@ def api_vms():
|
|||||||
"""Get virtual machine information"""
|
"""Get virtual machine information"""
|
||||||
return jsonify(get_proxmox_vms())
|
return jsonify(get_proxmox_vms())
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/vms/<int:vmid>/status', methods=['GET'])
|
||||||
|
@require_auth
|
||||||
|
def api_vm_status(vmid):
|
||||||
|
"""Lightweight per-VM live status: cpu, mem, disk, I/O counters, uptime.
|
||||||
|
|
||||||
|
Designed to be polled every 2-3s from the detail modal while it's open.
|
||||||
|
Single pvesh call (~200-400ms local socket); returns the same shape as
|
||||||
|
/api/vms entries so the frontend can swap in-place.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
local_node = get_proxmox_node_name()
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
['pvesh', 'get', f'/nodes/{local_node}/qemu/{vmid}/status/current', '--output-format', 'json'],
|
||||||
|
capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
vm_type = 'qemu'
|
||||||
|
if result.returncode != 0:
|
||||||
|
result = subprocess.run(
|
||||||
|
['pvesh', 'get', f'/nodes/{local_node}/lxc/{vmid}/status/current', '--output-format', 'json'],
|
||||||
|
capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
vm_type = 'lxc'
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
return jsonify({'error': f'VM/LXC {vmid} not found'}), 404
|
||||||
|
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
return jsonify({
|
||||||
|
'vmid': vmid,
|
||||||
|
'name': data.get('name', f'VM-{vmid}'),
|
||||||
|
'status': data.get('status', 'unknown'),
|
||||||
|
'type': vm_type if vm_type == 'lxc' else 'qemu',
|
||||||
|
'cpu': data.get('cpu', 0),
|
||||||
|
'mem': data.get('mem', 0),
|
||||||
|
'maxmem': data.get('maxmem', 0),
|
||||||
|
'disk': data.get('disk', 0),
|
||||||
|
'maxdisk': data.get('maxdisk', 0),
|
||||||
|
'uptime': data.get('uptime', 0),
|
||||||
|
'netin': data.get('netin', 0),
|
||||||
|
'netout': data.get('netout', 0),
|
||||||
|
'diskread': data.get('diskread', 0),
|
||||||
|
'diskwrite': data.get('diskwrite', 0),
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/vms/<int:vmid>/metrics', methods=['GET'])
|
@app.route('/api/vms/<int:vmid>/metrics', methods=['GET'])
|
||||||
@require_auth
|
@require_auth
|
||||||
def api_vm_metrics(vmid):
|
def api_vm_metrics(vmid):
|
||||||
@@ -9065,8 +9114,9 @@ def api_prometheus():
|
|||||||
timestamp = int(datetime.now().timestamp() * 1000)
|
timestamp = int(datetime.now().timestamp() * 1000)
|
||||||
node = socket.gethostname()
|
node = socket.gethostname()
|
||||||
|
|
||||||
# Get system data
|
# Non-blocking: returns %CPU since the last psutil call (sampler keeps state primed).
|
||||||
cpu_usage = psutil.cpu_percent(interval=0.5)
|
# Avoids 500ms worker block on each Prometheus scrape.
|
||||||
|
cpu_usage = psutil.cpu_percent(interval=0)
|
||||||
memory = psutil.virtual_memory()
|
memory = psutil.virtual_memory()
|
||||||
load_avg = os.getloadavg()
|
load_avg = os.getloadavg()
|
||||||
uptime_seconds = time.time() - psutil.boot_time()
|
uptime_seconds = time.time() - psutil.boot_time()
|
||||||
|
|||||||
Reference in New Issue
Block a user