// VNC viewer component — wraps noVNC RFB.
//
// Usage (in a side-panel or page):
//
//
// Connects to ws://:9000/vnc/ via the VNC gateway (cmd/vncd).
// Agent host is read from login-gate credentials; falls back to location.hostname.
//
// Attributes:
// vm — VM name (required)
// port — gateway port (default: 9000)
import RFB from '../../vendor/novnc/core/rfb.js'
const RECONNECT_DELAY = 3000 // ms between auto-reconnect attempts
class VncViewer extends HTMLElement {
#rfb = null
#retryTimer = null
connectedCallback() {
this.attachShadow({ mode: 'open' })
this.#render()
this.#connect()
}
disconnectedCallback() {
clearTimeout(this.#retryTimer)
this.#rfb?.disconnect()
this.#rfb = null
}
// ── Render ──────────────────────────────────────────────────────────────────
#render() {
this.shadowRoot.innerHTML = `
Connexion…
`
this.shadowRoot.getElementById('fullscreen-btn')
.addEventListener('click', () => this.#toggleFullscreen())
this.shadowRoot.getElementById('reconnect-btn')
.addEventListener('click', () => { this.#rfb?.disconnect(); this.#connect() })
}
// ── Connection ───────────────────────────────────────────────────────────────
#wsUrl() {
const vm = this.getAttribute('vm')
const port = this.getAttribute('port') ?? '9000'
const creds = document.querySelector('login-gate')?.credentials
const host = creds?.agent_url
? new URL(creds.agent_url).hostname
: location.hostname
return `ws://${host}:${port}/vnc/${vm}`
}
#connect() {
const vm = this.getAttribute('vm')
if (!vm) { this.#setStatus('disconnected', 'Attribut vm manquant'); return }
const url = this.#wsUrl()
this.#setStatus('connecting', `Connexion à ${url}…`)
this.#rfb = new RFB(this.shadowRoot.getElementById('screen'), url)
this.#rfb.scaleViewport = true
this.#rfb.resizeSession = false
this.#rfb.addEventListener('connect', () => {
this.#setStatus('connected', 'Connecté')
})
this.#rfb.addEventListener('disconnect', e => {
const clean = e.detail?.clean ?? false
if (clean) {
this.#setStatus('disconnected', 'Déconnecté')
} else {
this.#setStatus('disconnected', 'Déconnecté — reconnexion dans 3s…')
this.#retryTimer = setTimeout(() => this.#connect(), RECONNECT_DELAY)
}
})
this.#rfb.addEventListener('credentialsrequired', () => {
this.#rfb.sendCredentials({ password: '' })
})
}
// ── Helpers ──────────────────────────────────────────────────────────────────
#setStatus(state, msg) {
const dot = this.shadowRoot.getElementById('dot')
const text = this.shadowRoot.getElementById('status-text')
if (!dot || !text) return
dot.className = `dot ${state}`
text.textContent = msg
}
#toggleFullscreen() {
const screen = this.shadowRoot.getElementById('screen')
if (!document.fullscreenElement) {
screen.requestFullscreen?.()
} else {
document.exitFullscreen?.()
}
}
}
customElements.define('vnc-viewer', VncViewer)