From d76b7a99b822cea018793d86ed9c341fdd5dc963 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sat, 6 Dec 2025 22:40:24 +0100 Subject: [PATCH] Apdate AppImage --- AppImage/components/script-terminal-modal.tsx | 354 ++---------------- AppImage/components/terminal-panel.tsx | 21 +- 2 files changed, 47 insertions(+), 328 deletions(-) diff --git a/AppImage/components/script-terminal-modal.tsx b/AppImage/components/script-terminal-modal.tsx index d7a269e..8a0a6be 100644 --- a/AppImage/components/script-terminal-modal.tsx +++ b/AppImage/components/script-terminal-modal.tsx @@ -1,13 +1,13 @@ "use client" import type React from "react" -import { useState, useEffect, useRef } from "react" +import { useState, useRef } from "react" import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { CheckCircle2, XCircle, Loader2, Activity, GripHorizontal } from "lucide-react" -import { API_PORT } from "@/lib/api-config" +import { CheckCircle2, XCircle, GripHorizontal } from "lucide-react" +import { TerminalPanel } from "./terminal-panel" import { useIsMobile } from "@/hooks/use-mobile" interface WebInteraction { @@ -38,322 +38,36 @@ export function ScriptTerminalModal({ title, description, }: ScriptTerminalModalProps) { - const termRef = useRef(null) - const wsRef = useRef(null) - const fitAddonRef = useRef(null) - const sessionIdRef = useRef(Math.random().toString(36).substring(2, 8)) - - const [isConnected, setIsConnected] = useState(false) const [isComplete, setIsComplete] = useState(false) const [exitCode, setExitCode] = useState(null) const [currentInteraction, setCurrentInteraction] = useState(null) const [interactionInput, setInteractionInput] = useState("") - const checkConnectionInterval = useRef(null) const isMobile = useIsMobile() - const [isWaitingNextInteraction, setIsWaitingNextInteraction] = useState(false) - const waitingTimeoutRef = useRef(null) - const [modalHeight, setModalHeight] = useState(80) const [isResizing, setIsResizing] = useState(false) const startYRef = useRef(0) const startHeightRef = useRef(80) - const terminalContainerRef = useRef(null) - - useEffect(() => { - if (!open) { - if (checkConnectionInterval.current) { - clearInterval(checkConnectionInterval.current) - } - if (waitingTimeoutRef.current) { - clearTimeout(waitingTimeoutRef.current) - } - if (wsRef.current) { - wsRef.current.close() - wsRef.current = null - } - if (termRef.current) { - termRef.current.dispose() - termRef.current = null - } - sessionIdRef.current = Math.random().toString(36).substring(2, 8) - setIsComplete(false) - setExitCode(null) - setInteractionInput("") - setCurrentInteraction(null) - setIsWaitingNextInteraction(false) - setIsConnected(false) - } - }, [open]) - - useEffect(() => { - const container = terminalContainerRef.current - - console.log("[v0] Script modal useEffect triggered:", { - open, - hasContainer: !!container, - hasExistingTerm: !!termRef.current, - scriptPath, - sessionId: sessionIdRef.current, - }) - - if (!open || !container || termRef.current) { - console.log("[v0] Skipping terminal init") - return - } - - console.log("[v0] Starting terminal initialization...") - - const initializeTerminal = async () => { - console.log("[v0] Loading xterm modules...") - 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"), - ]) - - console.log("[v0] Creating terminal instance...") - const fontSize = window.innerWidth < 768 ? 12 : 16 - - const term = new TerminalClass({ - rendererType: "dom", - fontFamily: '"Courier", "Courier New", "Liberation Mono", "DejaVu Sans Mono", monospace', - fontSize: fontSize, - lineHeight: 1, - cursorBlink: true, - scrollback: 2000, - disableStdin: false, - customGlyphs: true, - fontWeight: "500", - fontWeightBold: "700", - theme: { - background: "#000000", - foreground: "#ffffff", - cursor: "#ffffff", - cursorAccent: "#000000", - black: "#2e3436", - red: "#cc0000", - green: "#4e9a06", - yellow: "#c4a000", - blue: "#3465a4", - magenta: "#75507b", - cyan: "#06989a", - white: "#d3d7cf", - brightBlack: "#555753", - brightRed: "#ef2929", - brightGreen: "#8ae234", - brightYellow: "#fce94f", - brightBlue: "#729fcf", - brightMagenta: "#ad7fa8", - brightCyan: "#34e2e2", - brightWhite: "#eeeeec", - }, - }) - - const fitAddon = new FitAddonClass() - term.loadAddon(fitAddon) - console.log("[v0] Opening terminal in container...") - term.open(container) - - termRef.current = term - fitAddonRef.current = fitAddon - - setTimeout(() => { - try { - fitAddon.fit() - console.log("[v0] Terminal fitted, cols:", term.cols, "rows:", term.rows) - } catch (err) { - console.log("[v0] Fit error:", err) - } - }, 50) - - const wsUrl = getScriptWebSocketUrl(sessionIdRef.current) - console.log("[v0] Connecting to WebSocket:", wsUrl) - const ws = new WebSocket(wsUrl) - wsRef.current = ws - - ws.onopen = () => { - console.log("[v0] WebSocket connected!") - setIsConnected(true) - - const initMessage = { - script_path: scriptPath, - params: { - EXECUTION_MODE: "web", - ...params, - }, - } - - console.log("[v0] Sending init message:", initMessage) - ws.send(JSON.stringify(initMessage)) - - setTimeout(() => { - try { - fitAddon.fit() - const cols = term.cols - const rows = term.rows - console.log("[v0] Sending resize:", { cols, rows }) - ws.send( - JSON.stringify({ - type: "resize", - cols: cols, - rows: rows, - }), - ) - } catch (err) { - console.log("[v0] Resize error:", err) - } - }, 100) - } - - ws.onmessage = (event) => { - console.log("[v0] WebSocket message received:", event.data.substring(0, 100)) - try { - const msg = JSON.parse(event.data) - - if (msg.type === "web_interaction" && msg.interaction) { - console.log("[v0] Web interaction detected:", msg.interaction.type) - setIsWaitingNextInteraction(false) - if (waitingTimeoutRef.current) { - clearTimeout(waitingTimeoutRef.current) - } - setCurrentInteraction({ - type: msg.interaction.type, - id: msg.interaction.id, - title: msg.interaction.title || "", - message: msg.interaction.message || "", - options: msg.interaction.options, - default: msg.interaction.default, - }) - return - } - - if (msg.type === "error") { - console.log("[v0] Error message:", msg.message) - term.writeln(`\x1b[31m${msg.message}\x1b[0m`) - return - } - } catch { - // Not JSON, es output normal de terminal - } - - term.write(event.data) - - setIsWaitingNextInteraction(false) - if (waitingTimeoutRef.current) { - clearTimeout(waitingTimeoutRef.current) - } - } - - ws.onerror = (error) => { - console.log("[v0] WebSocket error:", error) - setIsConnected(false) - term.writeln("\x1b[31mWebSocket error occurred\x1b[0m") - } - - ws.onclose = (event) => { - console.log("[v0] WebSocket closed:", event.code, event.reason) - setIsConnected(false) - term.writeln("\x1b[33mConnection closed\x1b[0m") - - if (!isComplete) { - setIsComplete(true) - setExitCode(event.code === 1000 ? 0 : 1) - } - } - - term.onData((data) => { - if (ws.readyState === WebSocket.OPEN) { - ws.send(data) - } - }) - - checkConnectionInterval.current = setInterval(() => { - if (ws) { - setIsConnected(ws.readyState === WebSocket.OPEN) - } - }, 500) - - let resizeTimeout: NodeJS.Timeout | null = null - - const resizeObserver = new ResizeObserver(() => { - if (resizeTimeout) clearTimeout(resizeTimeout) - resizeTimeout = setTimeout(() => { - if (fitAddon && term && ws?.readyState === WebSocket.OPEN) { - try { - fitAddon.fit() - ws.send( - JSON.stringify({ - type: "resize", - cols: term.cols, - rows: term.rows, - }), - ) - } catch (err) { - // Ignore - } - } - }, 100) - }) - - resizeObserver.observe(container) - } - - initializeTerminal() - }, [open]) - - const getScriptWebSocketUrl = (sid: string): string => { - if (typeof window === "undefined") { - return `ws://localhost:${API_PORT}/ws/script/${sid}` - } - - const { hostname, protocol } = window.location - const wsProtocol = protocol === "https:" ? "wss:" : "ws:" - return `${wsProtocol}//${hostname}:${API_PORT}/ws/script/${sid}` + const initMessage = { + script_path: scriptPath, + params: { + EXECUTION_MODE: "web", + ...params, + }, } const handleInteractionResponse = (value: string) => { - if (!wsRef.current || !currentInteraction) { - return - } - if (value === "cancel" || value === "") { setCurrentInteraction(null) setInteractionInput("") - handleCloseModal() + onClose() return } - const response = JSON.stringify({ - type: "interaction_response", - id: currentInteraction.id, - value: value, - }) - - if (wsRef.current.readyState === WebSocket.OPEN) { - wsRef.current.send(response) - } - + // TerminalPanel manejará el envío de la respuesta setCurrentInteraction(null) setInteractionInput("") - - waitingTimeoutRef.current = setTimeout(() => { - setIsWaitingNextInteraction(true) - }, 300) - } - - const handleCloseModal = () => { - if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { - wsRef.current.close() - } - if (checkConnectionInterval.current) { - clearInterval(checkConnectionInterval.current) - } - if (termRef.current) { - termRef.current.dispose() - } - onClose() } const handleResizeStart = (e: React.MouseEvent | React.TouchEvent) => { @@ -406,17 +120,22 @@ export function ScriptTerminalModal({ -
-
- - {isWaitingNextInteraction && !currentInteraction && ( -
-
- -

Processing...

-
-
- )} +
+ { + setCurrentInteraction({ + type: interaction.type as any, + id: interaction.id, + title: interaction.title || "", + message: interaction.message || "", + options: interaction.options, + default: interaction.default, + }) + }} + onClose={onClose} + />
{!isMobile && ( @@ -430,25 +149,6 @@ export function ScriptTerminalModal({
)} - -
-
- -
- {isConnected ? "Online" : "Offline"} -
- - -
diff --git a/AppImage/components/terminal-panel.tsx b/AppImage/components/terminal-panel.tsx index d03ec7b..e743428 100644 --- a/AppImage/components/terminal-panel.tsx +++ b/AppImage/components/terminal-panel.tsx @@ -26,6 +26,9 @@ import type { CheatSheetResult } from "@/lib/cheat-sheet-result" // Declare Chea type TerminalPanelProps = { websocketUrl?: string onClose?: () => void + isScriptModal?: boolean + initMessage?: { script_path: string; params: Record } + onWebInteraction?: (interaction: any) => void } interface TerminalInstance { @@ -132,7 +135,13 @@ const proxmoxCommands = [ { cmd: "clear", desc: "Clear terminal screen" }, ] -export const TerminalPanel: React.FC = ({ websocketUrl, onClose }) => { +export const TerminalPanel: React.FC = ({ + websocketUrl, + onClose, + isScriptModal, + initMessage, + onWebInteraction, +}) => { const [terminals, setTerminals] = useState([]) const [activeTerminalId, setActiveTerminalId] = useState("") const [layout, setLayout] = useState<"single" | "grid">("grid") @@ -570,6 +579,16 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl const activeTerminal = terminals.find((t) => t.id === activeTerminalId) + useEffect(() => { + if (initMessage && activeTerminal?.ws && activeTerminal.ws.readyState === WebSocket.OPEN) { + const message = JSON.stringify(initMessage) + activeTerminal.ws.send(message) + if (onWebInteraction) { + onWebInteraction({ type: "script_init", message }) + } + } + }, [initMessage, activeTerminal]) + return (