2 * Custom MeshCentral Terminal Component
3 * Direct WebSocket connection - no iframe needed
4 * Adapted from MeshCentral's client-side terminal code
7import { useEffect, useRef, useState } from 'react';
8import { Terminal } from 'xterm';
9import { FitAddon } from 'xterm-addon-fit';
10import 'xterm/css/xterm.css';
12export default function MeshCentralTerminal({ agentId, sessionToken }) {
13 const terminalRef = useRef(null);
14 const wsRef = useRef(null);
15 const xtermRef = useRef(null);
16 const fitAddonRef = useRef(null);
17 const [status, setStatus] = useState('connecting');
20 if (!agentId || !sessionToken) return;
22 // Initialize xterm.js terminal
23 const terminal = new Terminal({
26 fontFamily: 'Menlo, Monaco, "Courier New", monospace',
28 background: '#1e1e1e',
33 const fitAddon = new FitAddon();
34 terminal.loadAddon(fitAddon);
36 terminal.open(terminalRef.current);
39 xtermRef.current = terminal;
40 fitAddonRef.current = fitAddon;
42 // Connect to backend WebSocket proxy
43 const wsUrl = `${import.meta.env.VITE_WS_URL || 'wss://rmm-psa-backend-t9f7k.ondigitalocean.app'}/api/meshcentral/terminal?sessionToken=${sessionToken}&agentId=${agentId}`;
45 const ws = new WebSocket(wsUrl);
49 console.log('✅ Terminal WebSocket connected');
50 setStatus('connected');
51 terminal.writeln('Terminal connected. Waiting for shell...\r\n');
54 ws.onmessage = (event) => {
56 const message = JSON.parse(event.data);
59 terminal.writeln(`\r\n\x1b[31mError: ${message.error}\x1b[0m\r\n`);
64 // Handle terminal output
65 if (message.type === 'console' && message.data) {
66 // MeshCentral sends base64 encoded terminal data
67 const output = atob(message.data);
68 terminal.write(output);
71 console.error('Error parsing terminal message:', err);
75 ws.onerror = (error) => {
76 console.error('Terminal WebSocket error:', error);
78 terminal.writeln('\r\n\x1b[31mConnection error\x1b[0m\r\n');
82 console.log('Terminal WebSocket closed');
83 setStatus('disconnected');
84 terminal.writeln('\r\n\x1b[33mConnection closed\x1b[0m\r\n');
87 // Send terminal input to MeshCentral
88 terminal.onData((data) => {
89 if (ws.readyState === WebSocket.OPEN) {
90 // Encode input as base64 (MeshCentral protocol)
91 const encoded = btoa(data);
92 ws.send(JSON.stringify({
100 // Handle window resize
101 const handleResize = () => {
103 if (ws.readyState === WebSocket.OPEN) {
104 ws.send(JSON.stringify({
113 window.addEventListener('resize', handleResize);
117 window.removeEventListener('resize', handleResize);
118 if (ws.readyState === WebSocket.OPEN) {
123 }, [agentId, sessionToken]);
126 <div className="meshcentral-terminal">
127 <div className="terminal-header">
128 <span className={`status-indicator status-${status}`}></span>
129 <span className="status-text">
130 {status === 'connecting' && 'Connecting...'}
131 {status === 'connected' && 'Connected'}
132 {status === 'error' && 'Connection Error'}
133 {status === 'disconnected' && 'Disconnected'}
139 className="terminal-container"
140 style={{ height: '500px', width: '100%' }}
144 .meshcentral-terminal {
145 border: 1px solid #ccc;
164 .status-connecting { background: #ffa500; }
165 .status-connected { background: #00ff00; }
166 .status-error { background: #ff0000; }
167 .status-disconnected { background: #888; }
174 .terminal-container {