"use client" import type React from "react" import { useEffect, useRef, useState, useCallback } from "react" import { API_PORT } from "@/lib/api-config" import { Activity, Trash2, X, Search, Send, Lightbulb, Terminal, Plus } from "lucide-react" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import type { CheatSheetResult } from "@/lib/cheat-sheet-result" // Declare CheatSheetResult here type TerminalPanelProps = { terminals: TerminalInstance[] onAddTerminal: (terminal: TerminalInstance) => void onRemoveTerminal: (id: string) => void onClearTerminal: (id: string) => void } interface TerminalInstance { id: string title: string term: any ws: WebSocket | null isConnected: boolean // containerRef: React.RefObject // This is no longer needed as we use callback refs } 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` } } function getApiUrl(): 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}` } const proxmoxCommands = [ { cmd: "pvesh get /nodes", desc: "List all Proxmox nodes" }, { cmd: "pvesh get /nodes/{node}/qemu", desc: "List VMs on a node" }, { cmd: "pvesh get /nodes/{node}/lxc", desc: "List LXC containers on a node" }, { cmd: "pvesh get /nodes/{node}/storage", desc: "List storage on a node" }, { cmd: "pvesh get /nodes/{node}/network", desc: "List network interfaces" }, { cmd: "qm list", desc: "List all QEMU/KVM virtual machines" }, { cmd: "qm start ", desc: "Start a virtual machine" }, { cmd: "qm stop ", desc: "Stop a virtual machine" }, { cmd: "qm shutdown ", desc: "Shutdown a virtual machine gracefully" }, { cmd: "qm status ", desc: "Show VM status" }, { cmd: "qm config ", desc: "Show VM configuration" }, { cmd: "qm snapshot ", desc: "Create VM snapshot" }, { cmd: "pct list", desc: "List all LXC containers" }, { cmd: "pct start ", desc: "Start LXC container" }, { cmd: "pct stop ", desc: "Stop LXC container" }, { cmd: "pct enter ", desc: "Enter LXC container console" }, { cmd: "pct config ", desc: "Show container configuration" }, { cmd: "pvesm status", desc: "Show storage status" }, { cmd: "pvesm list ", desc: "List storage content" }, { cmd: "pveperf", desc: "Test Proxmox system performance" }, { cmd: "pveversion", desc: "Show Proxmox VE version" }, { cmd: "systemctl status pve-cluster", desc: "Check cluster status" }, { cmd: "pvecm status", desc: "Show cluster status" }, { cmd: "pvecm nodes", desc: "List cluster nodes" }, { cmd: "zpool status", desc: "Show ZFS pool status" }, { cmd: "zpool list", desc: "List all ZFS pools" }, { cmd: "zfs list", desc: "List all ZFS datasets" }, { cmd: "ls -la", desc: "List all files with details" }, { cmd: "cd /path/to/dir", desc: "Change directory" }, { cmd: "mkdir dirname", desc: "Create new directory" }, { cmd: "rm -rf dirname", desc: "Remove directory recursively" }, { cmd: "cp source dest", desc: "Copy files or directories" }, { cmd: "mv source dest", desc: "Move or rename files" }, { cmd: "cat filename", desc: "Display file contents" }, { cmd: "grep 'pattern' file", desc: "Search for pattern in file" }, { cmd: "find . -name 'file'", desc: "Find files by name" }, { cmd: "chmod 755 file", desc: "Change file permissions" }, { cmd: "chown user:group file", desc: "Change file owner" }, { cmd: "tar -xzf file.tar.gz", desc: "Extract tar.gz archive" }, { cmd: "tar -czf archive.tar.gz dir/", desc: "Create tar.gz archive" }, { cmd: "df -h", desc: "Show disk usage" }, { cmd: "du -sh *", desc: "Show directory sizes" }, { cmd: "free -h", desc: "Show memory usage" }, { cmd: "top", desc: "Show running processes" }, { cmd: "ps aux | grep process", desc: "Find running process" }, { cmd: "kill -9 PID", desc: "Force kill process" }, { cmd: "systemctl status service", desc: "Check service status" }, { cmd: "systemctl start service", desc: "Start a service" }, { cmd: "systemctl stop service", desc: "Stop a service" }, { cmd: "systemctl restart service", desc: "Restart a service" }, { cmd: "apt update && apt upgrade", desc: "Update Debian/Ubuntu packages" }, { cmd: "apt install package", desc: "Install package on Debian/Ubuntu" }, { cmd: "apt remove package", desc: "Remove package" }, { cmd: "docker ps", desc: "List running containers" }, { cmd: "docker images", desc: "List Docker images" }, { cmd: "docker exec -it container bash", desc: "Enter container shell" }, { cmd: "ip addr show", desc: "Show IP addresses" }, { cmd: "ping host", desc: "Test network connectivity" }, { cmd: "curl -I url", desc: "Get HTTP headers" }, { cmd: "wget url", desc: "Download file from URL" }, { cmd: "ssh user@host", desc: "Connect via SSH" }, { cmd: "scp file user@host:/path", desc: "Copy file via SSH" }, { cmd: "tail -f /var/log/syslog", desc: "Follow log file in real-time" }, { cmd: "history", desc: "Show command history" }, { cmd: "clear", desc: "Clear terminal screen" }, ] export const TerminalPanel: React.FC = ({ terminals, onAddTerminal, onRemoveTerminal, onClearTerminal, }) => { const [activeTerminalId, setActiveTerminalId] = useState("") const [isMobile, setIsMobile] = useState(false) const [searchModalOpen, setSearchModalOpen] = useState(false) const [searchQuery, setSearchQuery] = useState("") const [filteredCommands, setFilteredCommands] = useState>(proxmoxCommands) const [lastKeyPressed, setLastKeyPressed] = useState(null) const [isSearching, setIsSearching] = useState(false) const [searchResults, setSearchResults] = useState([]) const [useOnline, setUseOnline] = useState(true) const terminalRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}) const setTerminalRef = useCallback( (id: string) => (el: HTMLDivElement | null) => { terminalRefs.current[id] = el }, [], ) useEffect(() => { setIsMobile(window.innerWidth < 768) const handleResize = () => setIsMobile(window.innerWidth < 768) window.addEventListener("resize", handleResize) return () => window.removeEventListener("resize", handleResize) }, []) useEffect(() => { if (terminals.length === 0) { onAddTerminal({ id: `terminal-${Date.now()}`, title: `Terminal 1`, term: null, ws: null, isConnected: false, }) } }, []) useEffect(() => { const searchCheatSh = async (query: string) => { if (!query.trim()) { setSearchResults([]) setFilteredCommands(proxmoxCommands) return } try { setIsSearching(true) const apiUrl = getApiUrl() const response = await fetch(`${apiUrl}/api/terminal/search-command?q=${encodeURIComponent(query)}`, { method: "GET", headers: { Accept: "application/json", }, signal: AbortSignal.timeout(10000), }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const data = await response.json() if (!data.success || !data.examples || data.examples.length === 0) { throw new Error("No examples found") } console.log("[v0] Received parsed examples from server:", data.examples.length) // Usar los ejemplos ya parseados del servidor const formattedResults: CheatSheetResult[] = data.examples.map((example: any) => ({ command: example.command, description: example.description || "", examples: [example.command], // El comando limpio como ejemplo })) setUseOnline(true) setSearchResults(formattedResults) } catch (error) { console.log("[v0] Error fetching from cheat.sh proxy, using offline commands:", error) // Mostrar resultados offline si falla const filtered = proxmoxCommands.filter( (item) => item.cmd.toLowerCase().includes(query.toLowerCase()) || item.desc.toLowerCase().includes(query.toLowerCase()), ) setFilteredCommands(filtered) setSearchResults([]) setUseOnline(false) } finally { setIsSearching(false) } } const debounce = setTimeout(() => { if (searchQuery && searchQuery.length >= 2) { searchCheatSh(searchQuery) } else { setSearchResults([]) setFilteredCommands(proxmoxCommands) } }, 800) return () => clearTimeout(debounce) }, [searchQuery]) const handleKeyButton = (key: string) => { const activeTerminal = terminals.find((t) => t.id === activeTerminalId) if (!activeTerminal || !activeTerminal.ws || activeTerminal.ws.readyState !== WebSocket.OPEN) return let seq = "" switch (key) { case "UP": seq = "\x1b[A" break case "DOWN": seq = "\x1b[B" break case "RIGHT": seq = "\x1b[C" break case "LEFT": seq = "\x1b[D" break case "ESC": seq = "\x1b" break case "TAB": seq = "\t" break case "CTRL_C": seq = "\x03" break default: break } activeTerminal.ws.send(seq) if (key) { setLastKeyPressed(key) setTimeout(() => setLastKeyPressed(null), 2000) } } const handleClose = () => { terminals.forEach((terminal) => { if (terminal.ws) terminal.ws.close() if (terminal.term) terminal.term.dispose() }) onRemoveTerminal(activeTerminalId) } const sendToActiveTerminal = (command: string) => { const activeTerminal = terminals.find((t) => t.id === activeTerminalId) if (activeTerminal?.ws && activeTerminal.ws.readyState === WebSocket.OPEN) { // Solo enviamos el comando sin el \n para que no se ejecute automáticamente // El usuario puede editar el comando y presionar Enter cuando esté listo activeTerminal.ws.send(command) // Pequeño delay antes de cerrar el modal para asegurar que el comando se envió setTimeout(() => { setSearchModalOpen(false) }, 100) } } const sendSequence = (seq: string, keyName?: string) => { const activeTerminal = terminals.find((t) => t.id === activeTerminalId) if (activeTerminal?.ws && activeTerminal.ws.readyState === WebSocket.OPEN) { activeTerminal.ws.send(seq) if (keyName) { setLastKeyPressed(keyName) setTimeout(() => setLastKeyPressed(null), 2000) } } } const getLayoutClass = () => { const count = terminals.length if (isMobile || count === 1) return "grid grid-cols-1" if (count === 2) return "grid grid-cols-2" if (count >= 3) return "grid grid-cols-2 grid-rows-2" return "grid grid-cols-1" } const activeTerminal = terminals.find((t) => t.id === activeTerminalId) const renderTerminals = () => { if (terminals.length === 0) { return (

No terminals open. Click "New" to create one.

) } if (terminals.length === 1 || isMobile) { // Single terminal mode - fill entire container return (
Terminal {terminals[0].id}
(terminalRefs.current[terminals[0].id] = el)} className="flex-1 w-full bg-black" />
) } if (terminals.length === 2) { // Split view - two terminals side by side return (
{terminals.map((terminal) => (
Terminal {terminal.id}
(terminalRefs.current[terminal.id] = el)} className="flex-1 w-full bg-black" />
))}
) } // Grid view for 3+ terminals return (
{terminals.map((terminal) => (
Terminal {terminal.id}
(terminalRefs.current[terminal.id] = el)} className="flex-1 w-full bg-black" />
))}
) } return (
{terminals.length} terminals
{renderTerminals()}
{isMobile && (
{lastKeyPressed && ( Sent: {lastKeyPressed} )}
)} Search Commands
Search for Linux and Proxmox commands
setSearchQuery(e.target.value)} className="pl-10 bg-zinc-900 border-zinc-700 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 text-base" autoCapitalize="none" autoComplete="off" autoCorrect="off" spellCheck={false} />
{isSearching && (

Searching cheat.sh...

)}
{searchResults.length > 0 ? ( <> {searchResults.map((result, index) => (
{result.description && (

# {result.description}

)}
sendToActiveTerminal(result.command)} className="flex items-start justify-between gap-2 cursor-pointer group hover:bg-zinc-800/50 rounded p-2 -m-2" > {result.command}
))} {/* Powered by cheat.sh */}

Powered by cheat.sh

) : filteredCommands.length > 0 && !useOnline ? ( filteredCommands.map((item, index) => (
sendToActiveTerminal(item.cmd)} className="p-3 rounded-lg border border-zinc-700 bg-zinc-800/50 hover:bg-zinc-800 hover:border-blue-500 cursor-pointer transition-colors" >
{item.cmd}

{item.desc}

)) ) : !isSearching && !searchQuery && !useOnline ? ( proxmoxCommands.map((item, index) => (
sendToActiveTerminal(item.cmd)} className="p-3 rounded-lg border border-zinc-700 bg-zinc-800/50 hover:bg-zinc-800 hover:border-blue-500 cursor-pointer transition-colors" >
{item.cmd}

{item.desc}

)) ) : !isSearching ? (
{searchQuery ? ( <>

No results found for "{searchQuery}"

Try a different command or check your spelling

) : ( <>

Search for any command

Try searching for:

{["tar", "grep", "docker ps", "qm list", "systemctl"].map((cmd) => ( setSearchQuery(cmd)} className="px-2 py-1 bg-zinc-800 rounded text-blue-400 cursor-pointer hover:bg-zinc-700" > {cmd} ))}
{useOnline && (
Powered by cheat.sh
)} )}
) : null}
Tip: Search for any Linux command (tar, grep, docker, etc.) or Proxmox commands (qm, pct, pvesh)
{useOnline && searchResults.length > 0 && Powered by cheat.sh}
) }