xterm是一個(gè)使用TypeScript編寫(xiě)的前端終端組件。
1、demo主要介紹
本文demo主要實(shí)現(xiàn)一個(gè)頁(yè)面一個(gè)webssh窗口,前端部分主要利用xterm通過(guò)原生ws和后端通信,后端部分使用nodejs+utf8+ws+ssh2。

頁(yè)面效果

簡(jiǎn)要流程
2、前端實(shí)現(xiàn)
基于vue項(xiàng)目,前端主要依賴包:xterm xterm-addon-fit,使用前請(qǐng)install。
<template>
<div id="xterm"/>
</template>
<script>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm';
import { FitAddon } from "xterm-addon-fit";
export default {
name: 'WebShell',
data() {
return {
socketURI: 'ws://localhost:2000/'
}
},
mounted() {
this.initSocket()
},
beforeDestroy() {
this.socket.close()
this.term && this.term.dispose()
},
methods: {
initTerm() {
let element = document.getElementById('xterm');
// 設(shè)置了cols或者fitAddon.fit(); 當(dāng)一行字符超過(guò)80個(gè)過(guò)會(huì)替換現(xiàn)在的內(nèi)容,否則換行
const term = new Terminal({
cursorBlink: true, // 關(guān)標(biāo)閃爍
cursorStyle: "underline", // 光標(biāo)樣式 'block' | 'underline' | 'bar'
scrollback: 100, // 當(dāng)行的滾動(dòng)超過(guò)初始值時(shí)保留的行視窗,越大回滾能看的內(nèi)容越多,
});
this.term = term;
const fitAddon = new FitAddon();
this.term.loadAddon(fitAddon);
this.fitAddon = fitAddon;
term.open(element);
// 自適應(yīng)大小(使終端的尺寸和幾何尺寸適合于終端容器的尺寸),初始化的時(shí)候?qū)捀叨际菍?duì)的
fitAddon.fit();
term.focus();
this.term.onData(data => {
this.socket.send(data);
});
window.addEventListener('resize', this.resizeTerm);
},
getColsAndRows(element) {
// 暫時(shí)不用
element = element || document.getElementById('xterm');
return {
rows: parseInt((element.clientHeight - 0) / 18),
cols: 10 // parseInt(element.clientWidth / 8)
};
},
resizeTerm() {
this.fitAddon.fit();
this.term.scrollToBottom();
},
initSocket() {
this.socket = new WebSocket(this.socketURI);
this.socketOnClose();
this.socketOnOpen();
this.socketOnError();
this.socketOnMessage();
},
socketOnOpen() {
this.socket.onopen = () => {
// 連接成功后
this.initTerm()
}
},
socketOnMessage() {
this.socket.onmessage = (event) => {
// 接收推送的消息
this.term.write(event.data.toString());
}
},
socketOnClose() {
this.socket.onclose = () => {
console.log('close socket')
}
},
socketOnError() {
this.socket.onerror = () => {
console.log('socket error')
}
}
}
}
</script>
<style scoped>
#xterm {
padding: 15px 0;
}
</style>
3、后端實(shí)現(xiàn)
前端主要依賴包:utf8 ssh2 ws,使用前請(qǐng)install。
ssh2用來(lái)實(shí)現(xiàn)nodejs和服務(wù)器進(jìn)行連接和通信。
utf8用來(lái)實(shí)現(xiàn)服務(wù)器返回的命令執(zhí)行結(jié)果解碼。
ws用來(lái)實(shí)現(xiàn)后端和前端ws全雙工通信。
/**用來(lái)實(shí)現(xiàn)單個(gè)webssh功能**/
const utf8 = require("utf8");
const SSHClient = require("ssh2").Client;
const Server = require("ws").Server;
const wss = new Server({
port: 2000
});
const serverInfo = {
host: '192.168.18.141',
port: 22,
username: 'root',
password: '密碼不告訴你.'
};
function createSocket(ws) {
const ssh = new SSHClient();
ssh
.on("ready", function () {
ws.send("\r\n*** SSH CONNECTION ESTABLISHED ***\r\n");
ssh.shell(function (err, stream) {
if (err) {
return ws.emit("\r\n*** SSH SHELL ERROR: " + err.message + " ***\r\n");
}
ws.on("message", function (data) {
stream.write(data);
});
ws.on("close", function () {
console.log("close websocket。");
ssh.end();
});
stream
.on("data", function (d) {
let data = utf8.decode(d.toString("binary"));
ws.send(data);
})
.on("close", function () {
ssh.end();
});
});
})
.on("close", function () {
ws.close();
})
.on("error", function (err) {
console.log("\r\n*** SSH CONNECTION ERROR: " + err.message + " ***\r\n");
ws.close();
})
.connect(serverInfo);
}
wss.on("connection", function (ws) {
createSocket(ws);
});
4、遺留問(wèn)題
1、瀏覽器resize后,webshell窗口寬高自適應(yīng)、命令顯示的問(wèn)題;
2、設(shè)置了cols或者fitAddon.fit(); 當(dāng)一行字符超過(guò)80個(gè)后,會(huì)替換現(xiàn)在的內(nèi)容問(wèn)題。