2021-06-07 17:22:07 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div :style="{ height: height }" id="xterm" class="xterm" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2022-01-21 16:39:38 +08:00
|
|
|
|
<script lang="ts">
|
2021-06-07 17:22:07 +08:00
|
|
|
|
import 'xterm/css/xterm.css';
|
|
|
|
|
|
import { Terminal } from 'xterm';
|
|
|
|
|
|
import { FitAddon } from 'xterm-addon-fit';
|
|
|
|
|
|
import { getSession } from '@/common/utils/storage.ts';
|
2022-01-21 16:39:38 +08:00
|
|
|
|
import config from '@/common/config';
|
|
|
|
|
|
import { useStore } from '@/store/index.ts';
|
2022-08-13 19:31:16 +08:00
|
|
|
|
import { nextTick, toRefs, watch, computed, reactive, defineComponent, onMounted, onBeforeUnmount } from 'vue';
|
2021-06-07 17:22:07 +08:00
|
|
|
|
|
2022-01-21 16:39:38 +08:00
|
|
|
|
export default defineComponent({
|
|
|
|
|
|
name: 'SshTerminal',
|
2021-06-07 17:22:07 +08:00
|
|
|
|
props: {
|
2022-01-21 16:39:38 +08:00
|
|
|
|
machineId: { type: Number },
|
|
|
|
|
|
cmd: { type: String },
|
|
|
|
|
|
height: { type: String },
|
2021-06-07 17:22:07 +08:00
|
|
|
|
},
|
2022-01-21 16:39:38 +08:00
|
|
|
|
setup(props: any) {
|
|
|
|
|
|
const state = reactive({
|
|
|
|
|
|
machineId: 0,
|
|
|
|
|
|
cmd: '',
|
|
|
|
|
|
height: '',
|
|
|
|
|
|
term: null as any,
|
|
|
|
|
|
socket: null as any,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2022-08-13 19:31:16 +08:00
|
|
|
|
const resize = 1;
|
|
|
|
|
|
const data = 2;
|
2022-08-19 21:42:26 +08:00
|
|
|
|
const ping = 3;
|
2022-08-13 19:31:16 +08:00
|
|
|
|
|
2022-01-21 16:39:38 +08:00
|
|
|
|
watch(props, (newValue) => {
|
|
|
|
|
|
state.machineId = newValue.machineId;
|
|
|
|
|
|
state.cmd = newValue.cmd;
|
|
|
|
|
|
state.height = newValue.height;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
state.machineId = props.machineId;
|
|
|
|
|
|
state.height = props.height;
|
|
|
|
|
|
state.cmd = props.cmd;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
closeAll();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const store = useStore();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取布局配置信息
|
|
|
|
|
|
const getThemeConfig: any = computed(() => {
|
|
|
|
|
|
return store.state.themeConfig.themeConfig;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2022-08-13 19:31:16 +08:00
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
initXterm();
|
|
|
|
|
|
initSocket();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2022-01-21 16:39:38 +08:00
|
|
|
|
function initXterm() {
|
|
|
|
|
|
const term: any = new Terminal({
|
2022-01-28 11:30:11 +08:00
|
|
|
|
fontSize: getThemeConfig.value.terminalFontSize || 15,
|
2022-08-13 19:31:16 +08:00
|
|
|
|
fontWeight: getThemeConfig.value.terminalFontWeight || 'normal',
|
|
|
|
|
|
fontFamily: 'JetBrainsMono, monaco, Consolas, Lucida Console, monospace',
|
2021-06-07 17:22:07 +08:00
|
|
|
|
cursorBlink: true,
|
|
|
|
|
|
disableStdin: false,
|
|
|
|
|
|
theme: {
|
2022-08-15 20:14:02 +08:00
|
|
|
|
foreground: getThemeConfig.value.terminalForeground || '#7e9192', //字体
|
|
|
|
|
|
background: getThemeConfig.value.terminalBackground || '#002833', //背景色
|
|
|
|
|
|
cursor: getThemeConfig.value.terminalCursor || '#268F81', //设置光标
|
2022-09-26 18:08:12 +08:00
|
|
|
|
// cursorAccent: "red", // 光标停止颜色
|
2022-01-21 16:39:38 +08:00
|
|
|
|
} as any,
|
2021-06-07 17:22:07 +08:00
|
|
|
|
});
|
|
|
|
|
|
const fitAddon = new FitAddon();
|
|
|
|
|
|
term.loadAddon(fitAddon);
|
|
|
|
|
|
term.open(document.getElementById('xterm'));
|
|
|
|
|
|
fitAddon.fit();
|
|
|
|
|
|
term.focus();
|
2022-01-21 16:39:38 +08:00
|
|
|
|
state.term = term;
|
2021-06-07 17:22:07 +08:00
|
|
|
|
|
2022-01-28 11:30:11 +08:00
|
|
|
|
// 监听窗口resize
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 窗口大小改变时,触发xterm的resize方法使自适应
|
|
|
|
|
|
fitAddon.fit();
|
2022-08-13 19:31:16 +08:00
|
|
|
|
if (state.term) {
|
|
|
|
|
|
state.term.focus();
|
|
|
|
|
|
send({
|
|
|
|
|
|
type: resize,
|
|
|
|
|
|
Cols: parseInt(state.term.cols),
|
|
|
|
|
|
Rows: parseInt(state.term.rows),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2022-01-28 11:30:11 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2021-06-07 17:22:07 +08:00
|
|
|
|
// / **
|
|
|
|
|
|
// *添加事件监听器,用于按下键时的事件。事件值包含
|
|
|
|
|
|
// *将在data事件以及DOM事件中发送的字符串
|
|
|
|
|
|
// *触发了它。
|
|
|
|
|
|
// * @返回一个IDisposable停止监听。
|
|
|
|
|
|
// * /
|
|
|
|
|
|
// / ** 更新:xterm 4.x(新增)
|
|
|
|
|
|
// *为数据事件触发时添加事件侦听器。发生这种情况
|
|
|
|
|
|
// *用户输入或粘贴到终端时的示例。事件值
|
|
|
|
|
|
// *是`string`结果的结果,在典型的设置中,应该通过
|
|
|
|
|
|
// *到支持pty。
|
|
|
|
|
|
// * @返回一个IDisposable停止监听。
|
|
|
|
|
|
// * /
|
|
|
|
|
|
// 支持输入与粘贴方法
|
2022-01-21 16:39:38 +08:00
|
|
|
|
term.onData((key: any) => {
|
|
|
|
|
|
sendCmd(key);
|
2021-06-07 17:22:07 +08:00
|
|
|
|
});
|
2022-01-21 16:39:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-19 21:42:26 +08:00
|
|
|
|
let pingInterval: any;
|
2022-01-21 16:39:38 +08:00
|
|
|
|
function initSocket() {
|
2022-08-13 19:31:16 +08:00
|
|
|
|
state.socket = new WebSocket(
|
|
|
|
|
|
`${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${
|
|
|
|
|
|
state.term.rows
|
|
|
|
|
|
}`
|
|
|
|
|
|
);
|
2022-08-19 21:42:26 +08:00
|
|
|
|
|
2021-06-07 17:22:07 +08:00
|
|
|
|
// 监听socket连接
|
2022-08-19 21:42:26 +08:00
|
|
|
|
state.socket.onopen = () => {
|
|
|
|
|
|
// 如果有初始要执行的命令,则发送执行命令
|
|
|
|
|
|
if (state.cmd) {
|
|
|
|
|
|
sendCmd(state.cmd + ' \r');
|
|
|
|
|
|
}
|
|
|
|
|
|
// 开启心跳
|
|
|
|
|
|
pingInterval = setInterval(() => {
|
|
|
|
|
|
send({ type: ping, msg: 'ping' });
|
|
|
|
|
|
}, 8000);
|
|
|
|
|
|
};
|
2022-01-21 16:39:38 +08:00
|
|
|
|
|
2022-08-19 21:42:26 +08:00
|
|
|
|
// 监听socket错误信息
|
|
|
|
|
|
state.socket.onerror = (e: any) => {
|
|
|
|
|
|
console.log('连接错误', e);
|
|
|
|
|
|
};
|
2022-01-21 16:39:38 +08:00
|
|
|
|
|
2022-08-19 21:42:26 +08:00
|
|
|
|
state.socket.onclose = () => {
|
|
|
|
|
|
if (state.term) {
|
|
|
|
|
|
state.term.writeln('\r\n\x1b[31m提示: 连接已关闭...');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pingInterval) {
|
|
|
|
|
|
clearInterval(pingInterval);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2022-01-21 16:39:38 +08:00
|
|
|
|
|
2022-08-19 21:42:26 +08:00
|
|
|
|
// 发送socket消息
|
|
|
|
|
|
state.socket.onsend = send;
|
2022-01-21 16:39:38 +08:00
|
|
|
|
|
2022-08-19 21:42:26 +08:00
|
|
|
|
// 监听socket消息
|
|
|
|
|
|
state.socket.onmessage = getMessage;
|
2022-01-21 16:39:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-13 19:31:16 +08:00
|
|
|
|
function getMessage(msg: any) {
|
|
|
|
|
|
// msg.data是真正后端返回的数据
|
|
|
|
|
|
state.term.write(msg.data);
|
2022-01-21 16:39:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function send(msg: any) {
|
|
|
|
|
|
state.socket.send(JSON.stringify(msg));
|
|
|
|
|
|
}
|
2021-06-07 17:22:07 +08:00
|
|
|
|
|
2022-01-21 16:39:38 +08:00
|
|
|
|
function sendCmd(key: any) {
|
|
|
|
|
|
send({
|
2022-08-13 19:31:16 +08:00
|
|
|
|
type: data,
|
2021-06-07 17:22:07 +08:00
|
|
|
|
msg: key,
|
|
|
|
|
|
});
|
2022-01-21 16:39:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-19 21:42:26 +08:00
|
|
|
|
function close() {
|
|
|
|
|
|
if (state.socket) {
|
|
|
|
|
|
state.socket.close();
|
|
|
|
|
|
console.log('socket关闭');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-21 16:39:38 +08:00
|
|
|
|
function closeAll() {
|
|
|
|
|
|
close();
|
|
|
|
|
|
if (state.term) {
|
|
|
|
|
|
state.term.dispose();
|
|
|
|
|
|
state.term = null;
|
2021-06-07 17:22:07 +08:00
|
|
|
|
}
|
2022-01-21 16:39:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
...toRefs(state),
|
|
|
|
|
|
};
|
2021-06-07 17:22:07 +08:00
|
|
|
|
},
|
2022-01-21 16:39:38 +08:00
|
|
|
|
});
|
2021-06-07 17:22:07 +08:00
|
|
|
|
</script>
|