Update SSHTerminal.tsx
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import {useEffect, useRef, useState, useImperativeHandle, forwardRef} from 'react';
|
import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react';
|
||||||
import {useXTerm} from 'react-xtermjs';
|
import { useXTerm } from 'react-xtermjs';
|
||||||
import {FitAddon} from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import {ClipboardAddon} from '@xterm/addon-clipboard';
|
import { ClipboardAddon } from '@xterm/addon-clipboard';
|
||||||
|
|
||||||
interface SSHTerminalProps {
|
interface SSHTerminalProps {
|
||||||
hostConfig: any;
|
hostConfig: any;
|
||||||
@@ -12,42 +12,39 @@ interface SSHTerminalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
||||||
{hostConfig, isVisible, splitScreen = false},
|
{ hostConfig, isVisible, splitScreen = false },
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
const {instance: terminal, ref: xtermRef} = useXTerm();
|
const { instance: terminal, ref: xtermRef } = useXTerm();
|
||||||
const fitAddonRef = useRef<FitAddon | null>(null);
|
const fitAddonRef = useRef<FitAddon | null>(null);
|
||||||
const webSocketRef = useRef<WebSocket | null>(null);
|
const webSocketRef = useRef<WebSocket | null>(null);
|
||||||
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const wasDisconnectedBySSH = useRef(false);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
disconnect: () => {
|
disconnect: () => {
|
||||||
if (webSocketRef.current) {
|
webSocketRef.current?.close();
|
||||||
webSocketRef.current.close();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
fit: () => {
|
fit: () => {
|
||||||
if (fitAddonRef.current) {
|
fitAddonRef.current?.fit();
|
||||||
fitAddonRef.current.fit();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
sendInput: (data: string) => {
|
sendInput: (data: string) => {
|
||||||
if (webSocketRef.current && webSocketRef.current.readyState === 1) {
|
if (webSocketRef.current?.readyState === 1) {
|
||||||
webSocketRef.current.send(JSON.stringify({type: 'input', data}));
|
webSocketRef.current.send(JSON.stringify({ type: 'input', data }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleWindowResize() {
|
|
||||||
fitAddonRef.current?.fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('resize', handleWindowResize);
|
window.addEventListener('resize', handleWindowResize);
|
||||||
return () => window.removeEventListener('resize', handleWindowResize);
|
return () => window.removeEventListener('resize', handleWindowResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
function handleWindowResize() {
|
||||||
|
fitAddonRef.current?.fit();
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!terminal || !xtermRef.current || !hostConfig) return;
|
if (!terminal || !xtermRef.current || !hostConfig) return;
|
||||||
|
|
||||||
@@ -70,78 +67,55 @@ export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTermina
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResize = () => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
if (!xtermRef.current) return;
|
|
||||||
const {width, height} = xtermRef.current.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (width < 100 || height < 50) return;
|
|
||||||
|
|
||||||
if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
|
if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
|
||||||
resizeTimeout.current = setTimeout(() => {
|
resizeTimeout.current = setTimeout(() => {
|
||||||
fitAddonRef.current?.fit();
|
fitAddonRef.current?.fit();
|
||||||
|
|
||||||
const cols = terminal.cols + 1;
|
const cols = terminal.cols + 1;
|
||||||
const rows = terminal.rows;
|
const rows = terminal.rows;
|
||||||
|
webSocketRef.current?.send(JSON.stringify({ type: 'resize', data: { cols, rows } }));
|
||||||
webSocketRef.current?.send(JSON.stringify({
|
|
||||||
type: 'resize',
|
|
||||||
data: {cols, rows}
|
|
||||||
}));
|
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
});
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(onResize);
|
|
||||||
resizeObserver.observe(xtermRef.current);
|
resizeObserver.observe(xtermRef.current);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
|
|
||||||
const cols = terminal.cols + 1;
|
const cols = terminal.cols + 1;
|
||||||
const rows = terminal.rows;
|
const rows = terminal.rows;
|
||||||
|
|
||||||
const wsUrl = window.location.hostname === 'localhost'
|
const wsUrl = window.location.hostname === 'localhost'
|
||||||
? 'ws://localhost:8082'
|
? 'ws://localhost:8082'
|
||||||
: `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/ssh/websocket/`;
|
: `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/ssh/websocket/`;
|
||||||
|
|
||||||
const ws = new WebSocket(wsUrl);
|
const ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
webSocketRef.current = ws;
|
webSocketRef.current = ws;
|
||||||
|
wasDisconnectedBySSH.current = false;
|
||||||
|
|
||||||
ws.addEventListener('open', () => {
|
ws.addEventListener('open', () => {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({ type: 'connectToHost', data: { cols, rows, hostConfig } }));
|
||||||
type: 'connectToHost',
|
|
||||||
data: {
|
|
||||||
cols,
|
|
||||||
rows,
|
|
||||||
hostConfig: hostConfig
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
terminal.onData((data) => {
|
terminal.onData((data) => {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({ type: 'input', data }));
|
||||||
type: 'input',
|
|
||||||
data
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener('message', (event) => {
|
ws.addEventListener('message', (event) => {
|
||||||
try {
|
try {
|
||||||
const msg = JSON.parse(event.data);
|
const msg = JSON.parse(event.data);
|
||||||
|
if (msg.type === 'data') terminal.write(msg.data);
|
||||||
if (msg.type === 'data') {
|
else if (msg.type === 'error') terminal.writeln(`\r\n[ERROR] ${msg.message}`);
|
||||||
terminal.write(msg.data);
|
else if (msg.type === 'connected') {}
|
||||||
} else if (msg.type === 'error') {
|
else if (msg.type === 'disconnected') {
|
||||||
terminal.writeln(`\r\n[ERROR] ${msg.message}`);
|
wasDisconnectedBySSH.current = true;
|
||||||
} else if (msg.type === 'connected') {
|
terminal.writeln(`\r\n[${msg.message || 'Disconnected'}]`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (_) {}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener('close', () => {
|
ws.addEventListener('close', () => {
|
||||||
terminal.writeln('\r\n[Connection closed]');
|
if (!wasDisconnectedBySSH.current) {
|
||||||
|
terminal.writeln('\r\n[Connection closed]');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener('error', () => {
|
ws.addEventListener('error', () => {
|
||||||
@@ -167,7 +141,7 @@ export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTermina
|
|||||||
ref={xtermRef}
|
ref={xtermRef}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: splitScreen ? 0 : 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
@@ -197,4 +171,4 @@ style.innerHTML = `
|
|||||||
scrollbar-color: rgba(180,180,180,0.7) transparent;
|
scrollbar-color: rgba(180,180,180,0.7) transparent;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
|
|||||||
Reference in New Issue
Block a user