react 版 xterm+node 实现webssh
2024-01-02 本文已影响0人
小碎步快跑
前端使用xterm通过socket.io-client和后端通信,后端使用nodejs+utf8+socket.io+ssh2
实现效果如下:
22.png前端代码:
前端主要依赖包:xterm、 xterm-addon-fit、 socket.io-client
react组件:
import "xterm/css/xterm.css";
import io from "socket.io-client";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { Form, Input, Button } from "antd";
import "./index.less";
function WebShell({ host }) {
const { Item } = Form;
const [term, setTerm] = useState();
const [socket, setSocket] = useState();
const fitAddonRef = useRef(null);
const addTerm = (val) => {
if (!val.username) {
return;
}
setTerm(null);
const termInstance = new Terminal({
cursorBlink: true,
scrollback: 50,
});
setTerm(termInstance);
const fitAddon = new FitAddon();
fitAddonRef.current = fitAddon;
document.querySelector(".termbox").innerHTML = "";
termInstance.open(document.querySelector(".termbox"));
termInstance.loadAddon(fitAddon);
fitAddon.fit();
termInstance.focus();
socket.emit("createNewServer", {
msgId: "termbox",
ip: host,
username: val.username,
password: val.password,
cols: 100,
rows: 20,
});
termInstance.onData((data) => {
console.log(data);
socket.emit("termbox", data);
});
socket.on("termbox", (data) => {
termInstance.write(data);
});
// window.addEventListener(
// "resize",
// () => {
// fitAddon.fit();
// socket.emit("resize", {
// cols: 100,
// rows: 20,
// });
// },
// false
// );
};
useEffect(() => {
const socketInstance = io("http://XXXX:5000");
setSocket(socketInstance);
}, []);
return (
<div className="app-container">
<Form
className="query-form"
labelCol={{ span: 4 }}
wrapperCol={{ span: 16 }}
onFinish={addTerm}
initialValues={{
host: host, // 设置默认值
}}
>
<Item label="地址" name="host">
<Input disabled />
</Item>
<Item label="用户名" name="username">
<Input />
</Item>
<Item label="密码" name="password">
<Input.Password />
</Item>
<Button type="primary" htmlType="submit">
连接
</Button>
</Form>
<div
style={{
flex: 1,
display: "flex",
paddingBottom: "10px",
marginTop: "10px",
}}
>
<div
style={{ paddingLeft: "10px", paddingRight: "10px" }}
ref={fitAddonRef}
className="termbox"
></div>
</div>
</div>
);
}
export default WebShell;
index.less
.termbox {
flex: 1;
width: 100%;
height: 100%;
padding: 0 1rem;
}
后端代码
1、先创建一个文件夹npm init 一个新项目
2、安装依赖express 、utf8、socket.io、ssh2
3、index.js里面代码如下
4、启动服务 node ./index.js
var app = require('express')();
/**用来实现多个webssh功能**/
const http = require('http').Server(app);
const io = require('socket.io')(http, {cors: true});
const utf8 = require('utf8');
const SSHClient = require('ssh2').Client;
function createNewServer(machineConfig, socket) {
var ssh = new SSHClient();
let {msgId, ip, username, password} = machineConfig;
ssh
.on('ready', function () {
socket.emit(msgId, '\r\n*** ' + ip + ' SSH CONNECTION ESTABLISHED ***\r\n');
// ssh设置cols和rows处理界面输入字符过长显示问题
ssh.shell({cols: machineConfig.cols, rows: machineConfig.rows}, function (err, stream) {
if (err) {
return socket.emit(msgId, '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n');
}
socket.on(msgId, function (data) {
stream.write(data);
});
stream.on('data', function (d) {
socket.emit(msgId, utf8.decode(d.toString('binary')));
}).on('close', function () {
ssh.end();
});
socket.on('resize', function socketOnResize (data) {
stream.setWindow(data.rows, data.cols);
});
})
})
.on('close', function () {
socket.emit(msgId, '\r\n*** SSH CONNECTION CLOSED ***\r\n');
})
.on('error', function (err) {
console.log(err);
socket.emit(msgId, '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n');
}).connect({
host: ip,
port: 22,
username: username,
password: password
});
}
io.on('connection', function (socket) {
socket.on('createNewServer', function (machineConfig) {
// 新建一个ssh连接
console.log("createNewServer")
createNewServer(machineConfig, socket);
})
socket.on('disconnect', function () {
console.log('user disconnected');
});
})
http.listen(5000, function () {
console.log('listening on * 5000');
});