Обновление файла

This commit is contained in:
2026-07-24 22:07:49 +03:00
parent 2b0b543f69
commit c280be25ae
+36 -6
View File
@@ -1,7 +1,32 @@
import React, { useEffect, useRef, useState } from "react"; """VNC-консоль через noVNC.
noVNC — UMD/IIFE-библиотека, она не экспортирует ES-модули, поэтому
подключаем её через <script> (лежит в /novnc/rfb.js, копируется из
node_modules/@novnc/novnc/lib на этапе сборки Docker-образа).
"""
import { useEffect, useRef, useState } from "react";
import { api } from "../api.js"; import { api } from "../api.js";
const NOVNC_CDN = "https://cdn.jsdelivr.net/npm/@novnc/novnc@1.4.0/lib/rfb.js"; const NOVNC_SCRIPT = "/novnc/rfb.js";
let scriptPromise = null;
function loadNoVNC() {
if (typeof window !== "undefined" && window.RFB) {
return Promise.resolve(window.RFB);
}
if (scriptPromise) return scriptPromise;
scriptPromise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = NOVNC_SCRIPT;
script.async = true;
script.onload = () => (window.RFB ? resolve(window.RFB) : reject(new Error("RFB не найден после загрузки скрипта")));
script.onerror = () => reject(new Error(`Не удалось загрузить ${NOVNC_SCRIPT}`));
document.head.appendChild(script);
});
return scriptPromise;
}
export default function ConsoleViewer({ instanceId }) { export default function ConsoleViewer({ instanceId }) {
const containerRef = useRef(null); const containerRef = useRef(null);
@@ -15,14 +40,17 @@ export default function ConsoleViewer({ instanceId }) {
async function connect() { async function connect() {
try { try {
const info = await api.getConsole(instanceId); const info = await api.getConsole(instanceId);
const { default: RFB } = await import(/* @vite-ignore */ NOVNC_CDN); const RFB = await loadNoVNC();
if (cancelled) return; if (cancelled) return;
const proto = window.location.protocol === "https:" ? "wss" : "ws"; const proto = window.location.protocol === "https:" ? "wss" : "ws";
const wsUrl = const wsUrl =
`${proto}://${window.location.host}/api/console/ws?` + `${proto}://${window.location.host}/api/console/ws?` +
`node=${encodeURIComponent(info.node)}&vmid=${info.vmid}&guest_type=${info.guest_type}` + `node=${encodeURIComponent(info.node)}` +
`&port=${info.port}&ticket=${encodeURIComponent(info.ticket)}`; `&vmid=${info.vmid}` +
`&guest_type=${info.guest_type}` +
`&port=${info.port}` +
`&ticket=${encodeURIComponent(info.ticket)}`;
const rfb = new RFB(containerRef.current, wsUrl); const rfb = new RFB(containerRef.current, wsUrl);
rfb.addEventListener("connect", () => !cancelled && setStatus("Подключено")); rfb.addEventListener("connect", () => !cancelled && setStatus("Подключено"));
@@ -36,7 +64,9 @@ export default function ConsoleViewer({ instanceId }) {
connect(); connect();
return () => { return () => {
cancelled = true; cancelled = true;
rfbRef.current?.disconnect?.(); if (rfbRef.current && typeof rfbRef.current.disconnect === "function") {
try { rfbRef.current.disconnect(); } catch { /* noop */ }
}
}; };
}, [instanceId]); }, [instanceId]);