// 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)