import { useEffect, useRef } from "react"; import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; import "@xterm/xterm/css/xterm.css"; import io from "socket.io-client"; import PropTypes from "prop-types"; export function NewTerminal({ hostConfig }) { const terminalRef = useRef(null); const socketRef = useRef(null); useEffect(() => { if (!hostConfig || !terminalRef.current) return; // Initialize terminal const terminal = new Terminal({ cursorBlink: true, theme: { background: "#0f0f0f", foreground: "#ffffff", cursor: "#ffffff", }, fontSize: 14, scrollback: 1000, rendererType: "canvas", allowTransparency: true, }); // Initialize FitAddon for auto-sizing const fitAddon = new FitAddon(); terminal.loadAddon(fitAddon); // Open terminal in the container terminal.open(terminalRef.current); // Resize function const resizeTerminal = () => { const terminalContainer = terminalRef.current; const topbarHeight = 65; const availableWidth = window.innerWidth; const availableHeight = window.innerHeight - topbarHeight; terminalContainer.style.width = `${availableWidth}px`; terminalContainer.style.height = `${availableHeight}px`; fitAddon.fit(); const { cols, rows } = terminal; if (socketRef.current) { socketRef.current.emit("resize", { cols, rows }); console.log(`Terminal resized: cols=${cols}, rows=${rows}`); } }; // Ensure correct sizing on start setTimeout(() => { fitAddon.fit(); resizeTerminal(); }, 50); // Small delay to ensure proper initialization // Focus on terminal after initialization terminal.focus(); // Listen for window resize events window.addEventListener("resize", resizeTerminal); // Write initial connection message terminal.write("\r\n*** Connecting to backend ***\r\n"); const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; let ioUrl = `${protocol}//${window.location.hostname}:${window.location.port}/socket.io/`; if (window.location.hostname === "localhost") { ioUrl = "http://localhost:8081"; } const socket = io(ioUrl); socketRef.current = socket; socket.off("connect"); socket.off("data"); socket.off("disconnect"); socket.on("connect", () => { fitAddon.fit(); resizeTerminal(); // Ensure proper size on connection const { cols, rows } = terminal; socket.emit("connectToHost", cols, rows, hostConfig); terminal.write("\r\n*** Connected to backend ***\r\n"); }); socket.on("data", (data) => { terminal.write(data); }); socket.on("disconnect", () => { terminal.write("\r\n*** Disconnected from backend ***\r\n"); }); // Capture and send keystrokes terminal.onKey(({ key }) => { socket.emit("data", key); }); // Handle socket errors socket.on("connect_error", (err) => { terminal.write(`\r\n*** Error: ${err.message} ***\r\n`); }); // Cleanup on component unmount return () => { terminal.dispose(); window.removeEventListener("resize", resizeTerminal); socket.disconnect(); }; }, [hostConfig]); return (
); } // Prop validation using PropTypes NewTerminal.propTypes = { hostConfig: PropTypes.shape({ ip: PropTypes.string.isRequired, user: PropTypes.string.isRequired, password: PropTypes.string.isRequired, port: PropTypes.string.isRequired, }).isRequired, };