diff --git a/src-tauri/src/commands/proxmox.rs b/src-tauri/src/commands/proxmox.rs index 923e77d0..026125be 100644 --- a/src-tauri/src/commands/proxmox.rs +++ b/src-tauri/src/commands/proxmox.rs @@ -715,6 +715,11 @@ pub async fn list_proxmox_datastores( } else { format!("storage/{}/{}", node_name, storage_name) }; + if storage_name.is_empty() { + tracing::warn!(node = node_name, "storage entry has empty storage name — skipping"); + return None; + } + tracing::debug!(storage_id = %storage_id, "generated storage ID"); normalized.insert("id".to_string(), serde_json::Value::String(storage_id)); normalized.insert( "storage".to_string(), diff --git a/src/components/Proxmox/VMList.tsx b/src/components/Proxmox/VMList.tsx index 7eeb8b7b..9665b7b4 100644 --- a/src/components/Proxmox/VMList.tsx +++ b/src/components/Proxmox/VMList.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { clsx } from 'clsx'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index'; import { Button } from '@/components/ui/index'; @@ -461,11 +462,13 @@ function VMActionMenu({ onDelete, }: VMActionMenuProps) { const [isOpen, setIsOpen] = useState(false); - const menuRef = useRef(null); + const [flipUpward, setFlipUpward] = useState(false); + const containerRef = useRef(null); + const menuContentRef = useRef(null); useEffect(() => { function handleClickOutside(event: MouseEvent) { - if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false); } } @@ -479,6 +482,14 @@ function VMActionMenu({ }; }, [isOpen]); + // After the menu renders, check whether it overflows the viewport bottom and flip if needed. + // Done in useEffect (not during render) to avoid the react-hooks/refs ESLint violation. + useEffect(() => { + if (!isOpen || !menuContentRef.current) return; + const rect = menuContentRef.current.getBoundingClientRect(); + setFlipUpward(window.innerHeight - rect.bottom < 20); + }, [isOpen]); + const toggleMenu = (e: React.MouseEvent) => { e.stopPropagation(); setIsOpen(!isOpen); @@ -491,7 +502,7 @@ function VMActionMenu({ }; return ( -
+
{isOpen && ( -
+
{vm.status === 'stopped' && ( - {/* Disabled when: no target node typed/selected, - OR same-cluster migration with no enumerated nodes to choose from */}