forked from Gitlink/forgeplus-react
219 lines
6.3 KiB
JavaScript
219 lines
6.3 KiB
JavaScript
import React, { useRef, useEffect, useState } from 'react';
|
||
import { Base64 } from 'js-base64';
|
||
|
||
import { Terminal } from 'xterm';
|
||
import 'xterm/css/xterm.css';
|
||
import mediator from './mediator';
|
||
import ResizeObserver from 'resize-observer-polyfill';
|
||
|
||
function getColsAndRows(width, height, term) {
|
||
let w = term._core._renderService.dimensions.actualCellWidth || 9.5;
|
||
let h = term._core._renderService.dimensions.actualCellHeight || 18;
|
||
const rows = Math.floor(height / h);
|
||
const cols = Math.floor(width / w);
|
||
return [cols, rows];
|
||
}
|
||
|
||
function onLayout(term, el) {
|
||
const ro = new ResizeObserver(entries => {
|
||
console.log(entries);
|
||
for (let entry of entries) {
|
||
if (entry.target.offsetHeight > 0 || entry.target.offsetWidth > 0) {
|
||
const [cols, rows] = getColsAndRows(
|
||
entry.target.offsetWidth,
|
||
entry.target.offsetHeight,
|
||
term,
|
||
);
|
||
console.log('cols, rows', cols, rows);
|
||
term.resize(cols, rows);
|
||
mediator.publish('ssh-xterm-resize', {
|
||
columns: cols,
|
||
rows: rows,
|
||
width: entry.target.offsetWidth,
|
||
height: entry.target.offsetHeight,
|
||
});
|
||
}
|
||
}
|
||
});
|
||
ro.observe(el);
|
||
return ro;
|
||
}
|
||
|
||
const TimeTicket = 30000;
|
||
|
||
//建立 websockt 来交互
|
||
//根据容器大小计算行数和列数并做到自适应
|
||
//socket 与 term 需要分开初始化 因为socket 可能重置连接
|
||
//mediator 监听消息,如果和id匹配,则建立连接,重置,或关闭连接
|
||
|
||
export default ({ sshConfigData, sid }) => {
|
||
const [term, setTerm] = useState(null);
|
||
|
||
const { ws_url, password, port, secret } = sshConfigData;
|
||
const el = useRef();
|
||
const socket = useRef();
|
||
const isFirstConnected = useRef(false);
|
||
|
||
//term init
|
||
useEffect(() => {
|
||
if (el.current && ws_url) {
|
||
const term = new Terminal({ fontSize: 16, rendererType: 'dom' });
|
||
term.open(el.current);
|
||
|
||
term.onData(data => {
|
||
if (socket.current) {
|
||
if (socket.current.readyState === 1) {
|
||
socket.current.send(JSON.stringify({ tp: 'client', data: data }));
|
||
mediator.publish('on-operating-ssh'); //有操作则自动延时
|
||
} else {
|
||
//断开连接后重连
|
||
// socket.current = null
|
||
// mediator.publish('create-socket', sid)
|
||
}
|
||
}
|
||
});
|
||
term.write('Connecting...');
|
||
setTerm(term);
|
||
const ro = onLayout(term, el.current);
|
||
return () => {
|
||
term.dispose();
|
||
ro.unobserve(el.current);
|
||
};
|
||
}
|
||
}, [ws_url, el.current]);
|
||
|
||
useEffect(() => {
|
||
if (term && ws_url) {
|
||
function createSocket() {
|
||
const socketInstance = new WebSocket(ws_url);
|
||
socket.current = socketInstance;
|
||
|
||
socketInstance.onopen = () => {
|
||
let container = term.element.parentElement;
|
||
if (container) {
|
||
let width = container.offsetWidth;
|
||
let height = container.offsetHeight;
|
||
console.log('init', {
|
||
tp: 'init',
|
||
data: {
|
||
...sshConfigData,
|
||
secret: secret,
|
||
width,
|
||
height,
|
||
rows: term.rows,
|
||
columns: term.cols,
|
||
},
|
||
});
|
||
socketInstance.send(
|
||
JSON.stringify({
|
||
tp: 'init',
|
||
data: {
|
||
...sshConfigData,
|
||
secret: secret,
|
||
width,
|
||
height,
|
||
rows: term.rows,
|
||
columns: term.cols,
|
||
},
|
||
}),
|
||
);
|
||
}
|
||
term.focus();
|
||
};
|
||
socketInstance.onerror = error => {
|
||
console.log(
|
||
'------in socket error----',
|
||
error,
|
||
socketInstance,
|
||
ws_url,
|
||
);
|
||
//连接报错后,重新请求资源
|
||
// mediator.publish('on-recreate-socket')
|
||
};
|
||
socketInstance.onmessage = event => {
|
||
if (!isFirstConnected.current) {
|
||
term.write('\r');
|
||
// term.focus()
|
||
setTimeout(() => {
|
||
// term.clear();
|
||
}, 1000);
|
||
}
|
||
isFirstConnected.current = true;
|
||
console.log('event:', event);
|
||
|
||
const data = Base64.decode(event.data.toString());
|
||
let w = term._core._renderService.dimensions.actualCellWidth || 9.5;
|
||
|
||
console.log('data:', data, w, term);
|
||
term.write(data);
|
||
};
|
||
|
||
socketInstance.onclose = evt => {
|
||
if (tid) {
|
||
clearInterval(tid);
|
||
}
|
||
term.write('\r\nconnection closed');
|
||
};
|
||
}
|
||
|
||
const tid = setInterval(() => {
|
||
if (socket.current) {
|
||
socket.current.send(JSON.stringify({ tp: 'h' }));
|
||
}
|
||
}, TimeTicket);
|
||
|
||
const unSubCreate = mediator.subscribe('create-socket', id => {
|
||
if (sid === id) {
|
||
if (socket.current && socket.current.readyState === 1) {
|
||
term.focus();
|
||
} else {
|
||
createSocket();
|
||
}
|
||
term.focus();
|
||
}
|
||
});
|
||
|
||
const unSubClose = mediator.subscribe('close-socket', id => {
|
||
if (sid === id) {
|
||
if (socket.current) {
|
||
socket.current.close();
|
||
isFirstConnected.current = false;
|
||
term.clear();
|
||
}
|
||
socket.current = null;
|
||
}
|
||
});
|
||
|
||
const unSubResize = mediator.subscribe('ssh-xterm-resize', option => {
|
||
if (socket.current && socket.current.readyState === 1) {
|
||
socket.current.send(
|
||
JSON.stringify({ tp: 'resize', data: { ...option } }),
|
||
);
|
||
}
|
||
});
|
||
|
||
const unSubAddTime = mediator.subscribe('ssh-add-connect-time', () => {
|
||
if (socket.current && socket.current.readyState === 1) {
|
||
socket.current.send(JSON.stringify({ tp: 'overtime' }));
|
||
}
|
||
});
|
||
|
||
return () => {
|
||
unSubClose();
|
||
unSubCreate();
|
||
unSubResize();
|
||
unSubAddTime();
|
||
if (socket.current) {
|
||
socket.current.close();
|
||
isFirstConnected.current = false;
|
||
}
|
||
};
|
||
}
|
||
}, [term, ws_url, port]);
|
||
|
||
return (
|
||
<div ref={el} className="xterm-panel" style={{height:"100%"}}>
|
||
{!ws_url ? <p style={{ color: '#fff' }}>正在连接命令行服务...</p> : null}
|
||
</div>
|
||
);
|
||
}; |