From 65add36b2fe1a88bf01c5cc15f51d50bb2a4f93f Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 15 Mar 2026 16:16:03 +0100 Subject: [PATCH] Update terminal panel --- AppImage/components/lxc-terminal-modal.tsx | 26 ++++---------- AppImage/components/script-terminal-modal.tsx | 31 ++++++---------- AppImage/components/terminal-panel.tsx | 32 ++++------------- AppImage/lib/api-config.ts | 35 +++++++++++++++++++ AppImage/package.json | 6 ++-- 5 files changed, 61 insertions(+), 69 deletions(-) diff --git a/AppImage/components/lxc-terminal-modal.tsx b/AppImage/components/lxc-terminal-modal.tsx index b0e5c705..fde97047 100644 --- a/AppImage/components/lxc-terminal-modal.tsx +++ b/AppImage/components/lxc-terminal-modal.tsx @@ -31,8 +31,8 @@ import { import { DialogHeader, DialogDescription } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Dialog as SearchDialog, DialogContent as SearchDialogContent, DialogTitle as SearchDialogTitle } from "@/components/ui/dialog" -import "xterm/css/xterm.css" -import { API_PORT, fetchApi } from "@/lib/api-config" +import "@xterm/xterm/css/xterm.css" +import { fetchApi, getWebSocketUrl } from "@/lib/api-config" interface LxcTerminalModalProps { open: boolean @@ -75,21 +75,7 @@ const proxmoxCommands = [ { cmd: "clear", desc: "Clear terminal screen" }, ] -function getWebSocketUrl(): string { - if (typeof window === "undefined") { - return "ws://localhost:8008/ws/terminal" - } - - const { protocol, hostname, port } = window.location - const isStandardPort = port === "" || port === "80" || port === "443" - const wsProtocol = protocol === "https:" ? "wss:" : "ws:" - - if (isStandardPort) { - return `${wsProtocol}//${hostname}/ws/terminal` - } else { - return `${wsProtocol}//${hostname}:${API_PORT}/ws/terminal` - } -} +// Use centralized getWebSocketUrl from api-config export function LxcTerminalModal({ open: isOpen, @@ -169,8 +155,8 @@ export function LxcTerminalModal({ const initTerminal = async () => { const [TerminalClass, FitAddonClass] = await Promise.all([ - import("xterm").then((mod) => mod.Terminal), - import("xterm-addon-fit").then((mod) => mod.FitAddon), + import("@xterm/xterm").then((mod) => mod.Terminal), + import("@xterm/addon-fit").then((mod) => mod.FitAddon), ]) const fontSize = window.innerWidth < 768 ? 12 : 16 @@ -222,7 +208,7 @@ export function LxcTerminalModal({ fitAddonRef.current = fitAddon // Connect WebSocket to host terminal - const wsUrl = getWebSocketUrl() + const wsUrl = getWebSocketUrl("/ws/terminal") const ws = new WebSocket(wsUrl) wsRef.current = ws diff --git a/AppImage/components/script-terminal-modal.tsx b/AppImage/components/script-terminal-modal.tsx index 11d891e0..f9f6844b 100644 --- a/AppImage/components/script-terminal-modal.tsx +++ b/AppImage/components/script-terminal-modal.tsx @@ -25,8 +25,8 @@ import { DropdownMenuSeparator, DropdownMenuLabel, } from "@/components/ui/dropdown-menu" -import "xterm/css/xterm.css" -import { API_PORT } from "@/lib/api-config" +import "@xterm/xterm/css/xterm.css" +import { getWebSocketUrl } from "@/lib/api-config" interface WebInteraction { type: "yesno" | "menu" | "msgbox" | "input" | "inputbox" @@ -96,7 +96,7 @@ export function ScriptTerminalModal({ wsRef.current.close() } - const wsUrl = getScriptWebSocketUrl(sessionIdRef.current) + const wsUrl = getScriptWebSocketUrlLocal(sessionIdRef.current) const ws = new WebSocket(wsUrl) wsRef.current = ws @@ -202,9 +202,9 @@ export function ScriptTerminalModal({ const initializeTerminal = async () => { const [TerminalClass, FitAddonClass] = await Promise.all([ - import("xterm").then((mod) => mod.Terminal), - import("xterm-addon-fit").then((mod) => mod.FitAddon), - import("xterm/css/xterm.css"), + import("@xterm/xterm").then((mod) => mod.Terminal), + import("@xterm/addon-fit").then((mod) => mod.FitAddon), + import("@xterm/xterm/css/xterm.css"), ]) const fontSize = window.innerWidth < 768 ? 12 : 16 @@ -259,7 +259,7 @@ export function ScriptTerminalModal({ } }, 100) - const wsUrl = getScriptWebSocketUrl(sessionIdRef.current) + const wsUrl = getScriptWebSocketUrlLocal(sessionIdRef.current) const ws = new WebSocket(wsUrl) wsRef.current = ws @@ -493,20 +493,9 @@ export function ScriptTerminalModal({ } }, [isOpen, isComplete, attemptReconnect]) - const getScriptWebSocketUrl = (sid: string): string => { - if (typeof window === "undefined") { - return `ws://localhost:${API_PORT}/ws/script/${sid}` - } - - const { protocol, hostname, port } = window.location - const isStandardPort = port === "" || port === "80" || port === "443" - const wsProtocol = protocol === "https:" ? "wss:" : "ws:" - - if (isStandardPort) { - return `${wsProtocol}//${hostname}/ws/script/${sid}` - } else { - return `${wsProtocol}//${hostname}:${API_PORT}/ws/script/${sid}` - } + // Use centralized getWebSocketUrl from api-config + const getScriptWebSocketUrlLocal = (sid: string): string => { + return getWebSocketUrl(`/ws/script/${sid}`) } const handleInteractionResponse = (value: string) => { diff --git a/AppImage/components/terminal-panel.tsx b/AppImage/components/terminal-panel.tsx index 94ec32a0..62404ca9 100644 --- a/AppImage/components/terminal-panel.tsx +++ b/AppImage/components/terminal-panel.tsx @@ -2,7 +2,7 @@ import type React from "react" import { useEffect, useRef, useState } from "react" -import { API_PORT, fetchApi } from "@/lib/api-config" // Unificando importaciones de api-config en una sola lĂ­nea con alias @/ +import { API_PORT, fetchApi, getWebSocketUrl as getWebSocketUrlFromConfig, getApiUrl as getApiUrlFromConfig } from "@/lib/api-config" import { Activity, Trash2, @@ -46,31 +46,13 @@ interface TerminalInstance { pingInterval?: ReturnType | null // Heartbeat interval to keep connection alive } +// Use centralized functions from api-config.ts for SSL/proxy support function getWebSocketUrl(): string { - if (typeof window === "undefined") { - return "ws://localhost:8008/ws/terminal" - } - - const { protocol, hostname, port } = window.location - const isStandardPort = port === "" || port === "80" || port === "443" - - const wsProtocol = protocol === "https:" ? "wss:" : "ws:" - - if (isStandardPort) { - return `${wsProtocol}//${hostname}/ws/terminal` - } else { - return `${wsProtocol}//${hostname}:${API_PORT}/ws/terminal` - } + return getWebSocketUrlFromConfig("/ws/terminal") } function getApiUrl(endpoint?: string): string { - if (typeof window === "undefined") { - return "http://localhost:8008" - } - - const { protocol, hostname } = window.location - const apiProtocol = protocol === "https:" ? "https:" : "http:" - return `${apiProtocol}//${hostname}:${API_PORT}${endpoint || ""}` + return getApiUrlFromConfig(endpoint || "") } const proxmoxCommands = [ @@ -472,9 +454,9 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl const initializeTerminal = async (terminal: TerminalInstance, container: HTMLDivElement) => { const [TerminalClass, FitAddonClass] = await Promise.all([ - import("xterm").then((mod) => mod.Terminal), - import("xterm-addon-fit").then((mod) => mod.FitAddon), - import("xterm/css/xterm.css"), + import("@xterm/xterm").then((mod) => mod.Terminal), + import("@xterm/addon-fit").then((mod) => mod.FitAddon), + import("@xterm/xterm/css/xterm.css"), ]).then(([Terminal, FitAddon]) => [Terminal, FitAddon]) const fontSize = window.innerWidth < 768 ? 12 : 16 diff --git a/AppImage/lib/api-config.ts b/AppImage/lib/api-config.ts index 3bb0a36b..880b4222 100644 --- a/AppImage/lib/api-config.ts +++ b/AppImage/lib/api-config.ts @@ -50,6 +50,41 @@ export function getApiUrl(endpoint: string): string { return `${baseUrl}${normalizedEndpoint}` } +/** + * Gets the base URL for WebSocket connections + * Automatically handles ws:// vs wss:// based on page protocol (SSL support) + * + * @param path - WebSocket endpoint path (e.g., '/ws/terminal') + * @returns Full WebSocket URL + */ +export function getWebSocketUrl(path: string): string { + if (typeof window === "undefined") { + return `ws://localhost:${API_PORT}${path}` + } + + const { protocol, hostname, port } = window.location + const isStandardPort = port === "" || port === "80" || port === "443" + + // Use wss:// when page is served over https:// + const wsProtocol = protocol === "https:" ? "wss:" : "ws:" + + // Ensure path starts with / + const normalizedPath = path.startsWith("/") ? path : `/${path}` + + let wsUrl: string + if (isStandardPort) { + // Behind a proxy - WebSocket goes through same host + wsUrl = `${wsProtocol}//${hostname}${normalizedPath}` + } else { + // Direct access - use API port + wsUrl = `${wsProtocol}//${hostname}:${API_PORT}${normalizedPath}` + } + + console.log(`[v0] getWebSocketUrl: protocol=${protocol}, hostname=${hostname}, port=${port}, isStandardPort=${isStandardPort}, wsUrl=${wsUrl}`) + + return wsUrl +} + /** * Gets the JWT token from localStorage * diff --git a/AppImage/package.json b/AppImage/package.json index 4b0acc0b..2746213a 100644 --- a/AppImage/package.json +++ b/AppImage/package.json @@ -47,7 +47,7 @@ "geist": "^1.3.1", "input-otp": "1.4.1", "lucide-react": "^0.454.0", - "next": "15.1.6", + "next": "15.2.4", "next-themes": "^0.4.6", "react": "^19", "react-day-picker": "9.8.0", @@ -61,8 +61,8 @@ "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.9", - "xterm": "^5.3.0", - "xterm-addon-fit": "^0.8.0", + "@xterm/xterm": "^5.5.0", + "@xterm/addon-fit": "^0.10.0", "zod": "3.25.67" }, "devDependencies": {