mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-05-01 11:56:21 +00:00
update gpu-switch-mode-indicator.tsx
This commit is contained in:
@@ -8,7 +8,6 @@ interface GpuSwitchModeIndicatorProps {
|
|||||||
pendingMode?: "lxc" | "vm" | null
|
pendingMode?: "lxc" | "vm" | null
|
||||||
onToggle?: (e: React.MouseEvent) => void
|
onToggle?: (e: React.MouseEvent) => void
|
||||||
className?: string
|
className?: string
|
||||||
compact?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GpuSwitchModeIndicator({
|
export function GpuSwitchModeIndicator({
|
||||||
@@ -17,483 +16,235 @@ export function GpuSwitchModeIndicator({
|
|||||||
pendingMode = null,
|
pendingMode = null,
|
||||||
onToggle,
|
onToggle,
|
||||||
className,
|
className,
|
||||||
compact = false,
|
|
||||||
}: GpuSwitchModeIndicatorProps) {
|
}: GpuSwitchModeIndicatorProps) {
|
||||||
const displayMode = pendingMode ?? mode
|
const displayMode = pendingMode ?? mode
|
||||||
const isLxcActive = displayMode === "lxc"
|
const isLxcActive = displayMode === "lxc"
|
||||||
const isVmActive = displayMode === "vm"
|
const isVmActive = displayMode === "vm"
|
||||||
const hasChanged = pendingMode !== null && pendingMode !== mode
|
const hasChanged = pendingMode !== null && pendingMode !== mode
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const activeColor = isLxcActive ? "#3b82f6" : isVmActive ? "#a855f7" : "#6b7280"
|
||||||
|
const inactiveColor = "#374151" // gray-700 for dark theme
|
||||||
|
const lxcColor = isLxcActive ? "#3b82f6" : inactiveColor
|
||||||
|
const vmColor = isVmActive ? "#a855f7" : inactiveColor
|
||||||
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation() // Prevent card click propagation
|
e.stopPropagation()
|
||||||
if (isEditing && onToggle) {
|
if (isEditing && onToggle) {
|
||||||
onToggle(e)
|
onToggle(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact version for GPU card
|
|
||||||
if (compact) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-3",
|
|
||||||
isEditing && "cursor-pointer",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 140 40"
|
|
||||||
className="h-8 w-32"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
{/* GPU Chip Icon - LARGER and always colored */}
|
|
||||||
<g transform="translate(0, 6)">
|
|
||||||
{/* Chip body */}
|
|
||||||
<rect
|
|
||||||
x="2"
|
|
||||||
y="4"
|
|
||||||
width="22"
|
|
||||||
height="18"
|
|
||||||
rx="3"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isLxcActive
|
|
||||||
? "fill-blue-500/20 stroke-blue-500"
|
|
||||||
: isVmActive
|
|
||||||
? "fill-purple-500/20 stroke-purple-500"
|
|
||||||
: "fill-muted-foreground/20 stroke-muted-foreground"
|
|
||||||
)}
|
|
||||||
strokeWidth="1.5"
|
|
||||||
/>
|
|
||||||
{/* Chip pins top */}
|
|
||||||
<line x1="7" y1="1" x2="7" y2="4" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="1.5" />
|
|
||||||
<line x1="13" y1="1" x2="13" y2="4" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="1.5" />
|
|
||||||
<line x1="19" y1="1" x2="19" y2="4" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="1.5" />
|
|
||||||
{/* Chip pins bottom */}
|
|
||||||
<line x1="7" y1="22" x2="7" y2="25" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="1.5" />
|
|
||||||
<line x1="13" y1="22" x2="13" y2="25" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="1.5" />
|
|
||||||
<line x1="19" y1="22" x2="19" y2="25" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="1.5" />
|
|
||||||
{/* GPU text */}
|
|
||||||
<text
|
|
||||||
x="13"
|
|
||||||
y="16"
|
|
||||||
textAnchor="middle"
|
|
||||||
className={cn(
|
|
||||||
"text-[7px] font-bold transition-all duration-300",
|
|
||||||
isLxcActive ? "fill-blue-500" : isVmActive ? "fill-purple-500" : "fill-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
GPU
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
{/* Connection line from GPU to switch */}
|
|
||||||
<line
|
|
||||||
x1="26"
|
|
||||||
y1="20"
|
|
||||||
x2="48"
|
|
||||||
y2="20"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isLxcActive ? "stroke-blue-500/60" : isVmActive ? "stroke-purple-500/60" : "stroke-muted-foreground/40"
|
|
||||||
)}
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Switch node - central junction */}
|
|
||||||
<circle
|
|
||||||
cx="55"
|
|
||||||
cy="20"
|
|
||||||
r="6"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isEditing
|
|
||||||
? "fill-amber-500/30 stroke-amber-500"
|
|
||||||
: isLxcActive
|
|
||||||
? "fill-blue-500/30 stroke-blue-500"
|
|
||||||
: isVmActive
|
|
||||||
? "fill-purple-500/30 stroke-purple-500"
|
|
||||||
: "fill-muted stroke-muted-foreground/50"
|
|
||||||
)}
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Animated dot inside switch */}
|
|
||||||
<circle
|
|
||||||
cx="55"
|
|
||||||
cy="20"
|
|
||||||
r="2.5"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isEditing
|
|
||||||
? "fill-amber-500"
|
|
||||||
: isLxcActive
|
|
||||||
? "fill-blue-500"
|
|
||||||
: isVmActive
|
|
||||||
? "fill-purple-500"
|
|
||||||
: "fill-muted-foreground"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* LXC branch line */}
|
|
||||||
<path
|
|
||||||
d="M 61 17 L 80 8"
|
|
||||||
fill="none"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isLxcActive ? "stroke-blue-500" : "stroke-muted-foreground/30"
|
|
||||||
)}
|
|
||||||
strokeWidth={isLxcActive ? "2.5" : "1.5"}
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* VM branch line */}
|
|
||||||
<path
|
|
||||||
d="M 61 23 L 80 32"
|
|
||||||
fill="none"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isVmActive ? "stroke-purple-500" : "stroke-muted-foreground/30"
|
|
||||||
)}
|
|
||||||
strokeWidth={isVmActive ? "2.5" : "1.5"}
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* LXC Icon - Container box */}
|
|
||||||
<g transform="translate(83, 0)">
|
|
||||||
<rect
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="18"
|
|
||||||
height="14"
|
|
||||||
rx="2"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isLxcActive ? "fill-blue-500/25 stroke-blue-500" : "fill-muted stroke-muted-foreground/30"
|
|
||||||
)}
|
|
||||||
strokeWidth="1.5"
|
|
||||||
/>
|
|
||||||
{/* Container layers */}
|
|
||||||
<line x1="0" y1="5" x2="18" y2="5" className={cn(isLxcActive ? "stroke-blue-500" : "stroke-muted-foreground/30")} strokeWidth="1" />
|
|
||||||
<line x1="0" y1="9" x2="18" y2="9" className={cn(isLxcActive ? "stroke-blue-500" : "stroke-muted-foreground/30")} strokeWidth="1" />
|
|
||||||
{/* Dots */}
|
|
||||||
<circle cx="4" cy="2.5" r="1" className={cn(isLxcActive ? "fill-blue-500" : "fill-muted-foreground/30")} />
|
|
||||||
<circle cx="4" cy="7" r="1" className={cn(isLxcActive ? "fill-blue-500" : "fill-muted-foreground/30")} />
|
|
||||||
<circle cx="4" cy="11.5" r="1" className={cn(isLxcActive ? "fill-blue-500" : "fill-muted-foreground/30")} />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
{/* VM Icon - Monitor */}
|
|
||||||
<g transform="translate(83, 24)">
|
|
||||||
<rect
|
|
||||||
x="1"
|
|
||||||
y="0"
|
|
||||||
width="16"
|
|
||||||
height="10"
|
|
||||||
rx="1.5"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isVmActive ? "fill-purple-500/25 stroke-purple-500" : "fill-muted stroke-muted-foreground/30"
|
|
||||||
)}
|
|
||||||
strokeWidth="1.5"
|
|
||||||
/>
|
|
||||||
{/* Screen shine */}
|
|
||||||
<rect
|
|
||||||
x="3"
|
|
||||||
y="2"
|
|
||||||
width="12"
|
|
||||||
height="6"
|
|
||||||
rx="0.5"
|
|
||||||
className={cn(isVmActive ? "fill-purple-500/30" : "fill-muted-foreground/10")}
|
|
||||||
/>
|
|
||||||
{/* Stand */}
|
|
||||||
<line x1="9" y1="10" x2="9" y2="13" className={cn(isVmActive ? "stroke-purple-500" : "stroke-muted-foreground/30")} strokeWidth="1.5" />
|
|
||||||
<line x1="5" y1="13" x2="13" y2="13" className={cn(isVmActive ? "stroke-purple-500" : "stroke-muted-foreground/30")} strokeWidth="1.5" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
{/* LXC Label */}
|
|
||||||
<text
|
|
||||||
x="115"
|
|
||||||
y="10"
|
|
||||||
textAnchor="start"
|
|
||||||
className={cn(
|
|
||||||
"text-[8px] font-bold transition-all duration-300",
|
|
||||||
isLxcActive ? "fill-blue-500" : "fill-muted-foreground/40"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
LXC
|
|
||||||
</text>
|
|
||||||
|
|
||||||
{/* VM Label */}
|
|
||||||
<text
|
|
||||||
x="115"
|
|
||||||
y="35"
|
|
||||||
textAnchor="start"
|
|
||||||
className={cn(
|
|
||||||
"text-[8px] font-bold transition-all duration-300",
|
|
||||||
isVmActive ? "fill-purple-500" : "fill-muted-foreground/40"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
VM
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
{/* Status description */}
|
|
||||||
<div className="flex flex-col items-start gap-0.5 min-w-0 flex-1">
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-xs font-medium transition-all duration-300",
|
|
||||||
isLxcActive ? "text-blue-500" : isVmActive ? "text-purple-500" : "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isLxcActive
|
|
||||||
? "Ready for LXC containers"
|
|
||||||
: isVmActive
|
|
||||||
? "Ready for VM passthrough"
|
|
||||||
: "Mode unknown"}
|
|
||||||
</span>
|
|
||||||
<span className="text-[10px] text-muted-foreground">
|
|
||||||
{isLxcActive
|
|
||||||
? "Native driver active"
|
|
||||||
: isVmActive
|
|
||||||
? "VFIO-PCI driver active"
|
|
||||||
: "No driver detected"}
|
|
||||||
</span>
|
|
||||||
{hasChanged && (
|
|
||||||
<span className="text-[10px] text-amber-500 font-medium animate-pulse">
|
|
||||||
Change pending...
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full version (not used in current implementation but kept for flexibility)
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative rounded-lg border p-4 transition-all duration-300",
|
"flex items-center gap-6",
|
||||||
isEditing
|
isEditing && "cursor-pointer",
|
||||||
? "border-amber-500/50 bg-amber-500/5"
|
|
||||||
: "border-border/50 bg-muted/30",
|
|
||||||
isEditing && onToggle && "cursor-pointer hover:bg-amber-500/10",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-4">
|
{/* Large SVG Diagram */}
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 200 60"
|
viewBox="0 0 220 100"
|
||||||
className="h-14 w-full max-w-[200px]"
|
className="h-24 w-56 flex-shrink-0"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
{/* GPU Chip - Large with "GPU" text */}
|
||||||
|
<g transform="translate(0, 22)">
|
||||||
|
{/* Main chip body */}
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="8"
|
||||||
|
width="44"
|
||||||
|
height="36"
|
||||||
|
rx="6"
|
||||||
|
fill={`${activeColor}20`}
|
||||||
|
stroke={activeColor}
|
||||||
|
strokeWidth="2.5"
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
{/* Chip pins - top */}
|
||||||
|
<line x1="14" y1="2" x2="14" y2="8" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
<line x1="26" y1="2" x2="26" y2="8" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
<line x1="38" y1="2" x2="38" y2="8" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
{/* Chip pins - bottom */}
|
||||||
|
<line x1="14" y1="44" x2="14" y2="50" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
<line x1="26" y1="44" x2="26" y2="50" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
<line x1="38" y1="44" x2="38" y2="50" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
{/* GPU text */}
|
||||||
|
<text
|
||||||
|
x="26"
|
||||||
|
y="32"
|
||||||
|
textAnchor="middle"
|
||||||
|
fill={activeColor}
|
||||||
|
className="text-[14px] font-bold transition-all duration-300"
|
||||||
|
style={{ fontFamily: 'system-ui, sans-serif' }}
|
||||||
|
>
|
||||||
|
GPU
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Connection line from GPU to switch */}
|
||||||
|
<line
|
||||||
|
x1="52"
|
||||||
|
y1="50"
|
||||||
|
x2="78"
|
||||||
|
y2="50"
|
||||||
|
stroke={activeColor}
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Central Switch Node - Large circle with inner dot */}
|
||||||
|
<circle
|
||||||
|
cx="95"
|
||||||
|
cy="50"
|
||||||
|
r="14"
|
||||||
|
fill={isEditing ? "#f59e0b20" : `${activeColor}20`}
|
||||||
|
stroke={isEditing ? "#f59e0b" : activeColor}
|
||||||
|
strokeWidth="3"
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="95"
|
||||||
|
cy="50"
|
||||||
|
r="6"
|
||||||
|
fill={isEditing ? "#f59e0b" : activeColor}
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* LXC Branch Line - going up-right */}
|
||||||
|
<path
|
||||||
|
d="M 109 42 L 135 20"
|
||||||
|
fill="none"
|
||||||
|
stroke={lxcColor}
|
||||||
|
strokeWidth={isLxcActive ? "3.5" : "2"}
|
||||||
|
strokeLinecap="round"
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* VM Branch Line - going down-right */}
|
||||||
|
<path
|
||||||
|
d="M 109 58 L 135 80"
|
||||||
|
fill="none"
|
||||||
|
stroke={vmColor}
|
||||||
|
strokeWidth={isVmActive ? "3.5" : "2"}
|
||||||
|
strokeLinecap="round"
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* LXC Container Icon - Server/Stack icon */}
|
||||||
|
<g transform="translate(138, 2)">
|
||||||
|
{/* Container box */}
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="32"
|
||||||
|
height="28"
|
||||||
|
rx="4"
|
||||||
|
fill={isLxcActive ? `${lxcColor}25` : "transparent"}
|
||||||
|
stroke={lxcColor}
|
||||||
|
strokeWidth={isLxcActive ? "2.5" : "1.5"}
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
{/* Container layers/lines */}
|
||||||
|
<line x1="0" y1="10" x2="32" y2="10" stroke={lxcColor} strokeWidth={isLxcActive ? "1.5" : "1"} className="transition-all duration-300" />
|
||||||
|
<line x1="0" y1="19" x2="32" y2="19" stroke={lxcColor} strokeWidth={isLxcActive ? "1.5" : "1"} className="transition-all duration-300" />
|
||||||
|
{/* Status dots */}
|
||||||
|
<circle cx="7" cy="5" r="2" fill={lxcColor} className="transition-all duration-300" />
|
||||||
|
<circle cx="7" cy="14.5" r="2" fill={lxcColor} className="transition-all duration-300" />
|
||||||
|
<circle cx="7" cy="23.5" r="2" fill={lxcColor} className="transition-all duration-300" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* LXC Label */}
|
||||||
|
<text
|
||||||
|
x="188"
|
||||||
|
y="22"
|
||||||
|
textAnchor="start"
|
||||||
|
fill={lxcColor}
|
||||||
|
className={cn(
|
||||||
|
"transition-all duration-300",
|
||||||
|
isLxcActive ? "text-[14px] font-bold" : "text-[12px] font-medium"
|
||||||
|
)}
|
||||||
|
style={{ fontFamily: 'system-ui, sans-serif' }}
|
||||||
>
|
>
|
||||||
{/* GPU Chip Icon - LARGE and colored */}
|
LXC
|
||||||
<g transform="translate(0, 8)">
|
</text>
|
||||||
<rect
|
|
||||||
x="2"
|
|
||||||
y="6"
|
|
||||||
width="32"
|
|
||||||
height="24"
|
|
||||||
rx="4"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isLxcActive
|
|
||||||
? "fill-blue-500/20 stroke-blue-500"
|
|
||||||
: isVmActive
|
|
||||||
? "fill-purple-500/20 stroke-purple-500"
|
|
||||||
: "fill-muted-foreground/20 stroke-muted-foreground"
|
|
||||||
)}
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
{/* Chip pins top */}
|
|
||||||
<line x1="9" y1="2" x2="9" y2="6" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="2" />
|
|
||||||
<line x1="18" y1="2" x2="18" y2="6" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="2" />
|
|
||||||
<line x1="27" y1="2" x2="27" y2="6" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="2" />
|
|
||||||
{/* Chip pins bottom */}
|
|
||||||
<line x1="9" y1="30" x2="9" y2="34" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="2" />
|
|
||||||
<line x1="18" y1="30" x2="18" y2="34" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="2" />
|
|
||||||
<line x1="27" y1="30" x2="27" y2="34" className={cn(isLxcActive ? "stroke-blue-500" : isVmActive ? "stroke-purple-500" : "stroke-muted-foreground")} strokeWidth="2" />
|
|
||||||
<text
|
|
||||||
x="18"
|
|
||||||
y="22"
|
|
||||||
textAnchor="middle"
|
|
||||||
className={cn(
|
|
||||||
"text-[10px] font-bold",
|
|
||||||
isLxcActive ? "fill-blue-500" : isVmActive ? "fill-purple-500" : "fill-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
GPU
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
{/* Main connection line */}
|
{/* VM Monitor Icon */}
|
||||||
<line
|
<g transform="translate(138, 65)">
|
||||||
x1="38"
|
{/* Monitor screen */}
|
||||||
y1="26"
|
<rect
|
||||||
x2="70"
|
x="2"
|
||||||
y2="26"
|
y="0"
|
||||||
className={cn(
|
width="28"
|
||||||
"transition-all duration-300",
|
height="18"
|
||||||
isLxcActive ? "stroke-blue-500/60" : isVmActive ? "stroke-purple-500/60" : "stroke-muted-foreground/40"
|
rx="3"
|
||||||
)}
|
fill={isVmActive ? `${vmColor}25` : "transparent"}
|
||||||
strokeWidth="3"
|
stroke={vmColor}
|
||||||
|
strokeWidth={isVmActive ? "2.5" : "1.5"}
|
||||||
|
className="transition-all duration-300"
|
||||||
/>
|
/>
|
||||||
|
{/* Screen inner/shine */}
|
||||||
{/* Switch node */}
|
<rect
|
||||||
<circle
|
x="5"
|
||||||
cx="82"
|
y="3"
|
||||||
cy="26"
|
width="22"
|
||||||
r="10"
|
height="12"
|
||||||
className={cn(
|
rx="1"
|
||||||
"transition-all duration-300",
|
fill={isVmActive ? `${vmColor}30` : `${vmColor}10`}
|
||||||
isEditing
|
className="transition-all duration-300"
|
||||||
? "fill-amber-500/30 stroke-amber-500"
|
|
||||||
: isLxcActive
|
|
||||||
? "fill-blue-500/30 stroke-blue-500"
|
|
||||||
: isVmActive
|
|
||||||
? "fill-purple-500/30 stroke-purple-500"
|
|
||||||
: "fill-muted stroke-muted-foreground/50"
|
|
||||||
)}
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<circle
|
|
||||||
cx="82"
|
|
||||||
cy="26"
|
|
||||||
r="4"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isEditing
|
|
||||||
? "fill-amber-500"
|
|
||||||
: isLxcActive
|
|
||||||
? "fill-blue-500"
|
|
||||||
: isVmActive
|
|
||||||
? "fill-purple-500"
|
|
||||||
: "fill-muted-foreground"
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
{/* Monitor stand */}
|
||||||
|
<line x1="16" y1="18" x2="16" y2="24" stroke={vmColor} strokeWidth={isVmActive ? "2.5" : "1.5"} strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
{/* Monitor base */}
|
||||||
|
<line x1="8" y1="24" x2="24" y2="24" stroke={vmColor} strokeWidth={isVmActive ? "2.5" : "1.5"} strokeLinecap="round" className="transition-all duration-300" />
|
||||||
|
</g>
|
||||||
|
|
||||||
{/* LXC branch */}
|
{/* VM Label */}
|
||||||
<path
|
<text
|
||||||
d="M 92 20 Q 110 10, 130 10"
|
x="188"
|
||||||
fill="none"
|
y="84"
|
||||||
className={cn(
|
textAnchor="start"
|
||||||
"transition-all duration-300",
|
fill={vmColor}
|
||||||
isLxcActive ? "stroke-blue-500" : "stroke-muted-foreground/30"
|
className={cn(
|
||||||
)}
|
"transition-all duration-300",
|
||||||
strokeWidth={isLxcActive ? "3" : "2"}
|
isVmActive ? "text-[14px] font-bold" : "text-[12px] font-medium"
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* VM branch */}
|
|
||||||
<path
|
|
||||||
d="M 92 32 Q 110 42, 130 42"
|
|
||||||
fill="none"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isVmActive ? "stroke-purple-500" : "stroke-muted-foreground/30"
|
|
||||||
)}
|
|
||||||
strokeWidth={isVmActive ? "3" : "2"}
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* LXC Container icon */}
|
|
||||||
<g transform="translate(135, 0)">
|
|
||||||
<rect
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="26"
|
|
||||||
height="20"
|
|
||||||
rx="3"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isLxcActive ? "fill-blue-500/25 stroke-blue-500" : "fill-muted stroke-muted-foreground/30"
|
|
||||||
)}
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
<line x1="0" y1="7" x2="26" y2="7" className={cn(isLxcActive ? "stroke-blue-500" : "stroke-muted-foreground/30")} strokeWidth="1.5" />
|
|
||||||
<line x1="0" y1="13" x2="26" y2="13" className={cn(isLxcActive ? "stroke-blue-500" : "stroke-muted-foreground/30")} strokeWidth="1.5" />
|
|
||||||
<circle cx="5" cy="3.5" r="1.5" className={cn(isLxcActive ? "fill-blue-500" : "fill-muted-foreground/30")} />
|
|
||||||
<circle cx="5" cy="10" r="1.5" className={cn(isLxcActive ? "fill-blue-500" : "fill-muted-foreground/30")} />
|
|
||||||
<circle cx="5" cy="16.5" r="1.5" className={cn(isLxcActive ? "fill-blue-500" : "fill-muted-foreground/30")} />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<text
|
|
||||||
x="178"
|
|
||||||
y="14"
|
|
||||||
textAnchor="middle"
|
|
||||||
className={cn(
|
|
||||||
"text-[10px] font-bold",
|
|
||||||
isLxcActive ? "fill-blue-500" : "fill-muted-foreground/40"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
LXC
|
|
||||||
</text>
|
|
||||||
|
|
||||||
{/* VM Monitor icon */}
|
|
||||||
<g transform="translate(135, 32)">
|
|
||||||
<rect
|
|
||||||
x="2"
|
|
||||||
y="0"
|
|
||||||
width="22"
|
|
||||||
height="14"
|
|
||||||
rx="2"
|
|
||||||
className={cn(
|
|
||||||
"transition-all duration-300",
|
|
||||||
isVmActive ? "fill-purple-500/25 stroke-purple-500" : "fill-muted stroke-muted-foreground/30"
|
|
||||||
)}
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="5"
|
|
||||||
y="3"
|
|
||||||
width="16"
|
|
||||||
height="8"
|
|
||||||
rx="1"
|
|
||||||
className={cn(isVmActive ? "fill-purple-500/30" : "fill-muted-foreground/10")}
|
|
||||||
/>
|
|
||||||
<line x1="13" y1="14" x2="13" y2="18" className={cn(isVmActive ? "stroke-purple-500" : "stroke-muted-foreground/30")} strokeWidth="2" />
|
|
||||||
<line x1="7" y1="18" x2="19" y2="18" className={cn(isVmActive ? "stroke-purple-500" : "stroke-muted-foreground/30")} strokeWidth="2" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<text
|
|
||||||
x="178"
|
|
||||||
y="48"
|
|
||||||
textAnchor="middle"
|
|
||||||
className={cn(
|
|
||||||
"text-[10px] font-bold",
|
|
||||||
isVmActive ? "fill-purple-500" : "fill-muted-foreground/40"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
VM
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
{/* Status */}
|
|
||||||
<div className="flex flex-col items-end gap-1">
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-base font-semibold transition-all duration-300",
|
|
||||||
isLxcActive ? "text-blue-500" : isVmActive ? "text-purple-500" : "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isLxcActive ? "LXC Mode" : isVmActive ? "VM Mode" : "Unknown"}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{isLxcActive ? "Native Driver" : isVmActive ? "VFIO Passthrough" : ""}
|
|
||||||
</span>
|
|
||||||
{isEditing && (
|
|
||||||
<span className="text-xs text-amber-500 font-medium">
|
|
||||||
Click to toggle
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
{hasChanged && (
|
style={{ fontFamily: 'system-ui, sans-serif' }}
|
||||||
<span className="text-xs text-amber-500 font-medium animate-pulse">
|
>
|
||||||
Change pending
|
VM
|
||||||
</span>
|
</text>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Status Text - Large like GPU name */}
|
||||||
|
<div className="flex flex-col gap-1 min-w-0 flex-1">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-base font-semibold transition-all duration-300",
|
||||||
|
isLxcActive ? "text-blue-500" : isVmActive ? "text-purple-500" : "text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
</div>
|
>
|
||||||
|
{isLxcActive
|
||||||
|
? "Ready for LXC containers"
|
||||||
|
: isVmActive
|
||||||
|
? "Ready for VM passthrough"
|
||||||
|
: "Mode unknown"}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{isLxcActive
|
||||||
|
? "Native driver active"
|
||||||
|
: isVmActive
|
||||||
|
? "VFIO-PCI driver active"
|
||||||
|
: "No driver detected"}
|
||||||
|
</span>
|
||||||
|
{hasChanged && (
|
||||||
|
<span className="text-sm text-amber-500 font-medium animate-pulse">
|
||||||
|
Change pending...
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
import { fetchApi } from "@/lib/api-config"
|
import { fetchApi } from "@/lib/api-config"
|
||||||
import { ScriptTerminalModal } from "./script-terminal-modal"
|
import { ScriptTerminalModal } from "./script-terminal-modal"
|
||||||
import { GpuSwitchModeIndicator } from "./gpu-switch-mode-indicator"
|
import { GpuSwitchModeIndicator } from "./gpu-switch-mode-indicator"
|
||||||
import { Pencil, Check, X } from "lucide-react"
|
import { Settings2, CheckCircle2 } from "lucide-react"
|
||||||
|
|
||||||
const parseLsblkSize = (sizeStr: string | undefined): number => {
|
const parseLsblkSize = (sizeStr: string | undefined): number => {
|
||||||
if (!sizeStr) return 0
|
if (!sizeStr) return 0
|
||||||
@@ -884,42 +884,35 @@ export default function Hardware() {
|
|||||||
className="mt-3 pt-3 border-t border-border/30"
|
className="mt-3 pt-3 border-t border-border/30"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||||
Switch Mode
|
Switch Mode
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{editingSwitchModeGpu === fullSlot ? (
|
{editingSwitchModeGpu === fullSlot ? (
|
||||||
<div className="flex items-center gap-1">
|
<>
|
||||||
<Button
|
<button
|
||||||
variant="ghost"
|
className="h-7 px-3 text-xs rounded-md border border-border bg-background hover:bg-muted transition-colors text-muted-foreground"
|
||||||
size="sm"
|
|
||||||
className="h-6 w-6 p-0 text-green-500 hover:text-green-400 hover:bg-green-500/10"
|
|
||||||
onClick={(e) => handleSwitchModeSave(fullSlot, e)}
|
|
||||||
title="Save changes"
|
|
||||||
>
|
|
||||||
<Check className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-6 w-6 p-0 text-red-500 hover:text-red-400 hover:bg-red-500/10"
|
|
||||||
onClick={(e) => handleSwitchModeCancel(fullSlot, e)}
|
onClick={(e) => handleSwitchModeCancel(fullSlot, e)}
|
||||||
title="Cancel"
|
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
<button
|
||||||
|
className="h-7 px-3 text-xs rounded-md bg-blue-600 hover:bg-blue-700 text-white transition-colors flex items-center gap-1.5"
|
||||||
|
onClick={(e) => handleSwitchModeSave(fullSlot, e)}
|
||||||
|
>
|
||||||
|
<CheckCircle2 className="h-3 w-3" />
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<button
|
||||||
variant="outline"
|
className="h-7 px-3 text-xs rounded-md border border-border bg-background hover:bg-muted transition-colors flex items-center gap-1.5"
|
||||||
size="sm"
|
|
||||||
className="h-7 px-2 text-xs flex items-center gap-1"
|
|
||||||
onClick={(e) => handleSwitchModeEdit(fullSlot, e)}
|
onClick={(e) => handleSwitchModeEdit(fullSlot, e)}
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3" />
|
<Settings2 className="h-3 w-3" />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -928,7 +921,6 @@ export default function Hardware() {
|
|||||||
isEditing={editingSwitchModeGpu === fullSlot}
|
isEditing={editingSwitchModeGpu === fullSlot}
|
||||||
pendingMode={pendingSwitchModes[gpu.slot] || null}
|
pendingMode={pendingSwitchModes[gpu.slot] || null}
|
||||||
onToggle={(e) => handleSwitchModeToggle(gpu, e)}
|
onToggle={(e) => handleSwitchModeToggle(gpu, e)}
|
||||||
compact
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user