203 lines
6.0 KiB
JavaScript
203 lines
6.0 KiB
JavaScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { Terminal } from '@xterm/xterm';
|
|
import 'xterm/css/xterm.css';
|
|
import { FitAddon } from '@xterm/addon-fit';
|
|
import './App.css';
|
|
|
|
const App = () => {
|
|
const terminalRef = useRef(null);
|
|
const terminal = useRef(null);
|
|
const fitAddon = useRef(null);
|
|
const socket = useRef(null);
|
|
const [host, setHost] = useState('');
|
|
const [port, setPort] = useState('22');
|
|
const [username, setUsername] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
const [isSideBarHidden, setIsSideBarHidden] = useState(false);
|
|
|
|
useEffect(() => {
|
|
terminal.current = new Terminal({
|
|
cursorBlink: true,
|
|
theme: {
|
|
background: '#1e1e1e',
|
|
foreground: '#ffffff',
|
|
},
|
|
macOptionIsMeta: true,
|
|
allowProposedApi: true,
|
|
scrollback: 5000,
|
|
});
|
|
|
|
fitAddon.current = new FitAddon();
|
|
terminal.current.loadAddon(fitAddon.current);
|
|
|
|
terminal.current.open(terminalRef.current);
|
|
fitAddon.current.fit();
|
|
|
|
// Fit the terminal and send the size when needed
|
|
const fitAndNotifyResize = () => {
|
|
fitAddon.current.fit();
|
|
if (socket.current && socket.current.readyState === WebSocket.OPEN) {
|
|
socket.current.send(JSON.stringify({
|
|
cols: terminal.current.cols,
|
|
rows: terminal.current.rows,
|
|
}));
|
|
}
|
|
};
|
|
window.addEventListener('resize', fitAndNotifyResize);
|
|
|
|
terminal.current.onResize(({ cols, rows }) => {
|
|
console.log(`Terminal resized to cols:${cols}, rows:${rows}`);
|
|
fitAndNotifyResize();
|
|
});
|
|
|
|
const handleConnectionEstablished = () => {
|
|
fitAndNotifyResize();
|
|
};
|
|
|
|
window.addEventListener('connection-established', handleConnectionEstablished);
|
|
|
|
// Monitor terminal data (activity)
|
|
terminal.current.onData((data) => {
|
|
if (socket.current && socket.current.readyState === WebSocket.OPEN) {
|
|
socket.current.send(data);
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
terminal.current.dispose();
|
|
if (socket.current) {
|
|
socket.current.close();
|
|
}
|
|
window.removeEventListener('resize', fitAndNotifyResize);
|
|
};
|
|
}, []);
|
|
|
|
const handleConnect = () => {
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const wsUrl = `${protocol}//${window.location.host}/ws/`; // Use current host and "/ws/" endpoint
|
|
|
|
if (!host || !username || !password) {
|
|
terminal.current.writeln('Please fill in all fields.');
|
|
return;
|
|
}
|
|
|
|
socket.current = new WebSocket("ws://localhost:8081");
|
|
|
|
socket.current.onopen = () => {
|
|
terminal.current.writeln(`Connected to WebSocket server at ${wsUrl}`);
|
|
socket.current.send(
|
|
JSON.stringify({
|
|
host,
|
|
port,
|
|
username,
|
|
password,
|
|
rows: terminal.current.rows,
|
|
cols: terminal.current.cols
|
|
})
|
|
);
|
|
|
|
// Dispatch a custom event when connection is open
|
|
const event = new Event('connection-established');
|
|
window.dispatchEvent(event);
|
|
|
|
setIsConnected(true);
|
|
};
|
|
|
|
socket.current.onmessage = (event) => {
|
|
terminal.current.write(event.data);
|
|
};
|
|
|
|
socket.current.onerror = (error) => {
|
|
terminal.current.writeln(`WebSocket error: ${error.message}`);
|
|
};
|
|
|
|
socket.current.onclose = () => {
|
|
terminal.current.writeln('Disconnected from WebSocket server.');
|
|
setIsConnected(false);
|
|
};
|
|
};
|
|
|
|
const handleInputChange = (event, setState) => {
|
|
setState(event.target.value);
|
|
};
|
|
|
|
const handleSideBarHiding = () => {
|
|
setIsSideBarHidden((prevState) => {
|
|
const newState = !prevState;
|
|
if (newState) {
|
|
setTimeout(() => {
|
|
// Add a delay to ensure layout settles before resize action
|
|
fitAddon.current.fit();
|
|
if (socket.current && socket.current.readyState === WebSocket.OPEN) {
|
|
socket.current.send(JSON.stringify({
|
|
cols: terminal.current.cols,
|
|
rows: terminal.current.rows,
|
|
}));
|
|
}
|
|
}, 100); // Delay of 100 milliseconds
|
|
} else {
|
|
setTimeout(() => {
|
|
// Refit terminal when showing sidebar as well
|
|
fitAddon.current.fit();
|
|
if (socket.current && socket.current.readyState === WebSocket.OPEN) {
|
|
socket.current.send(JSON.stringify({
|
|
cols: terminal.current.cols,
|
|
rows: terminal.current.rows,
|
|
}));
|
|
}
|
|
}, 100); // Delay of 100 milliseconds
|
|
}
|
|
return newState;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="app-container">
|
|
<div className="main-content">
|
|
<div className={`sidebar ${isSideBarHidden ? 'hidden' : ''}`}>
|
|
<h2>Connection Details</h2>
|
|
<input
|
|
type="text"
|
|
placeholder="Host"
|
|
value={host}
|
|
onChange={(e) => handleInputChange(e, setHost)}
|
|
/>
|
|
<input
|
|
type="text"
|
|
placeholder="Port"
|
|
value={port}
|
|
onChange={(e) => handleInputChange(e, setPort)}
|
|
/>
|
|
<input
|
|
type="text"
|
|
placeholder="Username"
|
|
value={username}
|
|
onChange={(e) => handleInputChange(e, setUsername)}
|
|
/>
|
|
<input
|
|
type="password"
|
|
placeholder="Password"
|
|
value={password}
|
|
onChange={(e) => handleInputChange(e, setPassword)}
|
|
/>
|
|
<button onClick={handleConnect} disabled={isConnected}>
|
|
{isConnected ? 'Connected' : 'Start Session'}
|
|
</button>
|
|
</div>
|
|
|
|
<div ref={terminalRef} className="terminal-container"></div>
|
|
</div>
|
|
|
|
{/* Hide button always positioned in the bottom-right corner */}
|
|
<button
|
|
className="hide-sidebar-button"
|
|
onClick={handleSideBarHiding}
|
|
>
|
|
{isSideBarHidden ? '+' : '-'}
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App; |