Skip to content
This repository was archived by the owner on Jun 26, 2026. It is now read-only.

Commit ca15435

Browse files
kvapsclaude
andcommitted
fix(console): don't open VNC when the VM is not running
VncTab connected the websocket whenever the app was a VMInstance, regardless of power state, so a stopped VM showed a dead console. Poll the VirtualMachine printableStatus and only attach when it is Running; otherwise show a 'not running' notice. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
1 parent d59b625 commit ca15435

1 file changed

Lines changed: 52 additions & 2 deletions

File tree

apps/console/src/routes/detail/VncTab.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useRef, useState } from "react"
22
import { Monitor } from "lucide-react"
33
import { Section, Spinner } from "@cozystack/ui"
4+
import { useK8sGet, type K8sResource } from "@cozystack/k8s-client"
45
import type { ApplicationDefinition, ApplicationInstance } from "@cozystack/types"
56

67
interface VncTabProps {
@@ -15,14 +16,35 @@ export function VncTab({ ad, instance }: VncTabProps) {
1516
// VirtualMachineInstance named "<release.prefix><name>"
1617
// (e.g. "vm-instance-demo-vm"), which is what the subresource path needs.
1718
const vmName = `${ad.spec?.release?.prefix ?? ""}${instance.metadata.name}`
19+
// Don't open a VNC websocket unless the VM is actually running — there is no
20+
// VirtualMachineInstance to attach to otherwise, and the socket would just
21+
// error out. Poll the VirtualMachine power state.
22+
const { data: vm, isLoading: vmLoading } = useK8sGet<
23+
K8sResource<unknown, { printableStatus?: string }>
24+
>(
25+
{
26+
apiGroup: "kubevirt.io",
27+
apiVersion: "v1",
28+
plural: "virtualmachines",
29+
name: vmName,
30+
namespace: ns ?? "",
31+
},
32+
{
33+
enabled: appKind === "VMInstance" && !!vmName && !!ns,
34+
refetchInterval: 5000,
35+
},
36+
)
37+
const powerStatus = vm?.status?.printableStatus
38+
const isRunning = powerStatus === "Running"
1839
const [error, setError] = useState<string | null>(null)
1940
const [connecting, setConnecting] = useState(true)
2041
const [connected, setConnected] = useState(false)
2142
const vncContainerRef = useRef<HTMLDivElement>(null)
2243
const rfbRef = useRef<any>(null)
2344

2445
useEffect(() => {
25-
if (appKind !== "VMInstance" || !vncContainerRef.current) return
46+
if (appKind !== "VMInstance" || !isRunning || !vncContainerRef.current)
47+
return
2648

2749
let mounted = true
2850

@@ -96,7 +118,7 @@ export function VncTab({ ad, instance }: VncTabProps) {
96118
rfbRef.current = null
97119
}
98120
}
99-
}, [appKind, ns, vmName])
121+
}, [appKind, ns, vmName, isRunning])
100122

101123
if (appKind !== "VMInstance") {
102124
return (
@@ -108,6 +130,34 @@ export function VncTab({ ad, instance }: VncTabProps) {
108130
)
109131
}
110132

133+
if (vmLoading) {
134+
return (
135+
<div className="flex items-center gap-2 p-6 text-sm text-slate-500">
136+
<Spinner /> Loading…
137+
</div>
138+
)
139+
}
140+
141+
if (!isRunning) {
142+
return (
143+
<div className="p-6">
144+
<Section
145+
title={
146+
<span className="inline-flex items-center gap-2">
147+
<Monitor className="size-4 text-slate-500" /> VNC Console
148+
</span>
149+
}
150+
>
151+
<p className="text-sm text-slate-500">
152+
The virtual machine is not running
153+
{powerStatus ? ` (status: ${powerStatus})` : ""}. Start it to use
154+
the VNC console.
155+
</p>
156+
</Section>
157+
</div>
158+
)
159+
}
160+
111161
const handleCtrlAltDel = () => {
112162
if (rfbRef.current) {
113163
rfbRef.current.sendCtrlAltDel()

0 commit comments

Comments
 (0)