Update terminal panel

This commit is contained in:
MacRimi
2026-03-15 16:16:03 +01:00
parent 793b3dde12
commit 65add36b2f
5 changed files with 61 additions and 69 deletions

View File

@@ -31,8 +31,8 @@ import {
import { DialogHeader, DialogDescription } from "@/components/ui/dialog" import { DialogHeader, DialogDescription } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Dialog as SearchDialog, DialogContent as SearchDialogContent, DialogTitle as SearchDialogTitle } from "@/components/ui/dialog" import { Dialog as SearchDialog, DialogContent as SearchDialogContent, DialogTitle as SearchDialogTitle } from "@/components/ui/dialog"
import "xterm/css/xterm.css" import "@xterm/xterm/css/xterm.css"
import { API_PORT, fetchApi } from "@/lib/api-config" import { fetchApi, getWebSocketUrl } from "@/lib/api-config"
interface LxcTerminalModalProps { interface LxcTerminalModalProps {
open: boolean open: boolean
@@ -75,21 +75,7 @@ const proxmoxCommands = [
{ cmd: "clear", desc: "Clear terminal screen" }, { cmd: "clear", desc: "Clear terminal screen" },
] ]
function getWebSocketUrl(): string { // Use centralized getWebSocketUrl from api-config
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`
}
}
export function LxcTerminalModal({ export function LxcTerminalModal({
open: isOpen, open: isOpen,
@@ -169,8 +155,8 @@ export function LxcTerminalModal({
const initTerminal = async () => { const initTerminal = async () => {
const [TerminalClass, FitAddonClass] = await Promise.all([ const [TerminalClass, FitAddonClass] = await Promise.all([
import("xterm").then((mod) => mod.Terminal), import("@xterm/xterm").then((mod) => mod.Terminal),
import("xterm-addon-fit").then((mod) => mod.FitAddon), import("@xterm/addon-fit").then((mod) => mod.FitAddon),
]) ])
const fontSize = window.innerWidth < 768 ? 12 : 16 const fontSize = window.innerWidth < 768 ? 12 : 16
@@ -222,7 +208,7 @@ export function LxcTerminalModal({
fitAddonRef.current = fitAddon fitAddonRef.current = fitAddon
// Connect WebSocket to host terminal // Connect WebSocket to host terminal
const wsUrl = getWebSocketUrl() const wsUrl = getWebSocketUrl("/ws/terminal")
const ws = new WebSocket(wsUrl) const ws = new WebSocket(wsUrl)
wsRef.current = ws wsRef.current = ws

View File

@@ -25,8 +25,8 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuLabel, DropdownMenuLabel,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import "xterm/css/xterm.css" import "@xterm/xterm/css/xterm.css"
import { API_PORT } from "@/lib/api-config" import { getWebSocketUrl } from "@/lib/api-config"
interface WebInteraction { interface WebInteraction {
type: "yesno" | "menu" | "msgbox" | "input" | "inputbox" type: "yesno" | "menu" | "msgbox" | "input" | "inputbox"
@@ -96,7 +96,7 @@ export function ScriptTerminalModal({
wsRef.current.close() wsRef.current.close()
} }
const wsUrl = getScriptWebSocketUrl(sessionIdRef.current) const wsUrl = getScriptWebSocketUrlLocal(sessionIdRef.current)
const ws = new WebSocket(wsUrl) const ws = new WebSocket(wsUrl)
wsRef.current = ws wsRef.current = ws
@@ -202,9 +202,9 @@ export function ScriptTerminalModal({
const initializeTerminal = async () => { const initializeTerminal = async () => {
const [TerminalClass, FitAddonClass] = await Promise.all([ const [TerminalClass, FitAddonClass] = await Promise.all([
import("xterm").then((mod) => mod.Terminal), import("@xterm/xterm").then((mod) => mod.Terminal),
import("xterm-addon-fit").then((mod) => mod.FitAddon), import("@xterm/addon-fit").then((mod) => mod.FitAddon),
import("xterm/css/xterm.css"), import("@xterm/xterm/css/xterm.css"),
]) ])
const fontSize = window.innerWidth < 768 ? 12 : 16 const fontSize = window.innerWidth < 768 ? 12 : 16
@@ -259,7 +259,7 @@ export function ScriptTerminalModal({
} }
}, 100) }, 100)
const wsUrl = getScriptWebSocketUrl(sessionIdRef.current) const wsUrl = getScriptWebSocketUrlLocal(sessionIdRef.current)
const ws = new WebSocket(wsUrl) const ws = new WebSocket(wsUrl)
wsRef.current = ws wsRef.current = ws
@@ -493,20 +493,9 @@ export function ScriptTerminalModal({
} }
}, [isOpen, isComplete, attemptReconnect]) }, [isOpen, isComplete, attemptReconnect])
const getScriptWebSocketUrl = (sid: string): string => { // Use centralized getWebSocketUrl from api-config
if (typeof window === "undefined") { const getScriptWebSocketUrlLocal = (sid: string): string => {
return `ws://localhost:${API_PORT}/ws/script/${sid}` return getWebSocketUrl(`/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}`
}
} }
const handleInteractionResponse = (value: string) => { const handleInteractionResponse = (value: string) => {

View File

@@ -2,7 +2,7 @@
import type React from "react" import type React from "react"
import { useEffect, useRef, useState } 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 { import {
Activity, Activity,
Trash2, Trash2,
@@ -46,31 +46,13 @@ interface TerminalInstance {
pingInterval?: ReturnType<typeof setInterval> | null // Heartbeat interval to keep connection alive pingInterval?: ReturnType<typeof setInterval> | null // Heartbeat interval to keep connection alive
} }
// Use centralized functions from api-config.ts for SSL/proxy support
function getWebSocketUrl(): string { function getWebSocketUrl(): string {
if (typeof window === "undefined") { return getWebSocketUrlFromConfig("/ws/terminal")
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`
}
} }
function getApiUrl(endpoint?: string): string { function getApiUrl(endpoint?: string): string {
if (typeof window === "undefined") { return getApiUrlFromConfig(endpoint || "")
return "http://localhost:8008"
}
const { protocol, hostname } = window.location
const apiProtocol = protocol === "https:" ? "https:" : "http:"
return `${apiProtocol}//${hostname}:${API_PORT}${endpoint || ""}`
} }
const proxmoxCommands = [ const proxmoxCommands = [
@@ -472,9 +454,9 @@ export const TerminalPanel: React.FC<TerminalPanelProps> = ({ websocketUrl, onCl
const initializeTerminal = async (terminal: TerminalInstance, container: HTMLDivElement) => { const initializeTerminal = async (terminal: TerminalInstance, container: HTMLDivElement) => {
const [TerminalClass, FitAddonClass] = await Promise.all([ const [TerminalClass, FitAddonClass] = await Promise.all([
import("xterm").then((mod) => mod.Terminal), import("@xterm/xterm").then((mod) => mod.Terminal),
import("xterm-addon-fit").then((mod) => mod.FitAddon), import("@xterm/addon-fit").then((mod) => mod.FitAddon),
import("xterm/css/xterm.css"), import("@xterm/xterm/css/xterm.css"),
]).then(([Terminal, FitAddon]) => [Terminal, FitAddon]) ]).then(([Terminal, FitAddon]) => [Terminal, FitAddon])
const fontSize = window.innerWidth < 768 ? 12 : 16 const fontSize = window.innerWidth < 768 ? 12 : 16

View File

@@ -50,6 +50,41 @@ export function getApiUrl(endpoint: string): string {
return `${baseUrl}${normalizedEndpoint}` 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 * Gets the JWT token from localStorage
* *

View File

@@ -47,7 +47,7 @@
"geist": "^1.3.1", "geist": "^1.3.1",
"input-otp": "1.4.1", "input-otp": "1.4.1",
"lucide-react": "^0.454.0", "lucide-react": "^0.454.0",
"next": "15.1.6", "next": "15.2.4",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19", "react": "^19",
"react-day-picker": "9.8.0", "react-day-picker": "9.8.0",
@@ -61,8 +61,8 @@
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9", "vaul": "^0.9.9",
"xterm": "^5.3.0", "@xterm/xterm": "^5.5.0",
"xterm-addon-fit": "^0.8.0", "@xterm/addon-fit": "^0.10.0",
"zod": "3.25.67" "zod": "3.25.67"
}, },
"devDependencies": { "devDependencies": {