mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-01-03 05:06:36 +08:00
refactor: code rewiew&功能小优化
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
"countup.js": "^2.0.7",
|
"countup.js": "^2.0.7",
|
||||||
"cropperjs": "^1.5.11",
|
"cropperjs": "^1.5.11",
|
||||||
"echarts": "^5.3.3",
|
"echarts": "^5.3.3",
|
||||||
"element-plus": "^2.2.12",
|
"element-plus": "^2.2.13",
|
||||||
"jsencrypt": "^3.2.1",
|
"jsencrypt": "^3.2.1",
|
||||||
"jsoneditor": "^9.9.0",
|
"jsoneditor": "^9.9.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface ThemeConfigState {
|
|||||||
terminalBackground: string;
|
terminalBackground: string;
|
||||||
terminalCursor: string;
|
terminalCursor: string;
|
||||||
terminalFontSize: number;
|
terminalFontSize: number;
|
||||||
|
terminalFontWeight: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
|||||||
// ssh终端cursor色
|
// ssh终端cursor色
|
||||||
terminalCursor: '#f0cc09',
|
terminalCursor: '#f0cc09',
|
||||||
terminalFontSize: 15,
|
terminalFontSize: 15,
|
||||||
|
terminalFontWeight: 'normal',
|
||||||
|
|
||||||
|
|
||||||
/* 后端控制路由
|
/* 后端控制路由
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
</el-input-number>
|
</el-input-number>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-select @change="setLocalThemeConfig" v-model="getThemeConfig.terminalFontWeight" size="small" style="width: 90px">
|
<el-select @change="setLocalThemeConfig" v-model="getThemeConfig.terminalFontWeight" size="small" style="width: 90px">
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<el-option label="bold" value="bold"> </el-option>
|
<el-option label="bold" value="bold"> </el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<!-- 全局主题 -->
|
<!-- 全局主题 -->
|
||||||
<el-divider content-position="left">全局主题</el-divider>
|
<el-divider content-position="left">全局主题</el-divider>
|
||||||
|
|||||||
@@ -97,12 +97,12 @@
|
|||||||
v-if="terminalDialog.visible"
|
v-if="terminalDialog.visible"
|
||||||
title="终端"
|
title="终端"
|
||||||
v-model="terminalDialog.visible"
|
v-model="terminalDialog.visible"
|
||||||
width="70%"
|
width="80%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
:modal="false"
|
:modal="false"
|
||||||
@close="closeTermnial"
|
@close="closeTermnial"
|
||||||
>
|
>
|
||||||
<ssh-terminal ref="terminal" :cmd="terminalDialog.cmd" :machineId="terminalDialog.machineId" height="600px" />
|
<ssh-terminal ref="terminal" :cmd="terminalDialog.cmd" :machineId="terminalDialog.machineId" height="560px" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<script-edit
|
<script-edit
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { FitAddon } from 'xterm-addon-fit';
|
|||||||
import { getSession } from '@/common/utils/storage.ts';
|
import { getSession } from '@/common/utils/storage.ts';
|
||||||
import config from '@/common/config';
|
import config from '@/common/config';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { useStore } from '@/store/index.ts';
|
||||||
import { toRefs, watch, computed, reactive, defineComponent, onMounted, onBeforeUnmount } from 'vue';
|
import { nextTick, toRefs, watch, computed, reactive, defineComponent, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SshTerminal',
|
name: 'SshTerminal',
|
||||||
@@ -27,22 +27,19 @@ export default defineComponent({
|
|||||||
socket: null as any,
|
socket: null as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const resize = 1;
|
||||||
|
const data = 2;
|
||||||
|
|
||||||
watch(props, (newValue) => {
|
watch(props, (newValue) => {
|
||||||
state.machineId = newValue.machineId;
|
state.machineId = newValue.machineId;
|
||||||
state.cmd = newValue.cmd;
|
state.cmd = newValue.cmd;
|
||||||
state.height = newValue.height;
|
state.height = newValue.height;
|
||||||
if (state.machineId) {
|
|
||||||
initSocket();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
state.machineId = props.machineId;
|
state.machineId = props.machineId;
|
||||||
state.height = props.height;
|
state.height = props.height;
|
||||||
state.cmd = props.cmd;
|
state.cmd = props.cmd;
|
||||||
if (state.machineId) {
|
|
||||||
initSocket();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -56,14 +53,19 @@ export default defineComponent({
|
|||||||
return store.state.themeConfig.themeConfig;
|
return store.state.themeConfig.themeConfig;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
initXterm();
|
||||||
|
initSocket();
|
||||||
|
});
|
||||||
|
|
||||||
function initXterm() {
|
function initXterm() {
|
||||||
const term: any = new Terminal({
|
const term: any = new Terminal({
|
||||||
fontSize: getThemeConfig.value.terminalFontSize || 15,
|
fontSize: getThemeConfig.value.terminalFontSize || 15,
|
||||||
// fontWeight: getThemeConfig.value.terminalFontWeight || 'normal',
|
fontWeight: getThemeConfig.value.terminalFontWeight || 'normal',
|
||||||
fontFamily: 'JetBrainsMono, Consolas, Menlo, Monaco',
|
fontFamily: 'JetBrainsMono, monaco, Consolas, Lucida Console, monospace',
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
// cursorStyle: 'underline', //光标样式
|
|
||||||
disableStdin: false,
|
disableStdin: false,
|
||||||
|
letterSpacing: -1,
|
||||||
theme: {
|
theme: {
|
||||||
foreground: getThemeConfig.value.terminalForeground || '#c5c8c6', //字体
|
foreground: getThemeConfig.value.terminalForeground || '#c5c8c6', //字体
|
||||||
background: getThemeConfig.value.terminalBackground || '#121212', //背景色
|
background: getThemeConfig.value.terminalBackground || '#121212', //背景色
|
||||||
@@ -82,6 +84,14 @@ export default defineComponent({
|
|||||||
try {
|
try {
|
||||||
// 窗口大小改变时,触发xterm的resize方法使自适应
|
// 窗口大小改变时,触发xterm的resize方法使自适应
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
|
if (state.term) {
|
||||||
|
state.term.focus();
|
||||||
|
send({
|
||||||
|
type: resize,
|
||||||
|
Cols: parseInt(state.term.cols),
|
||||||
|
Rows: parseInt(state.term.rows),
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
@@ -104,20 +114,14 @@ export default defineComponent({
|
|||||||
term.onData((key: any) => {
|
term.onData((key: any) => {
|
||||||
sendCmd(key);
|
sendCmd(key);
|
||||||
});
|
});
|
||||||
// 为解决窗体resize方法才会向后端发送列数和行数,所以页面加载时也要触发此方法
|
|
||||||
send({
|
|
||||||
type: 'resize',
|
|
||||||
Cols: parseInt(term.cols),
|
|
||||||
Rows: parseInt(term.rows),
|
|
||||||
});
|
|
||||||
// 如果有初始要执行的命令,则发送执行命令
|
|
||||||
if (state.cmd) {
|
|
||||||
sendCmd(state.cmd + ' \r');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSocket() {
|
function initSocket() {
|
||||||
state.socket = new WebSocket(`${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}`);
|
state.socket = new WebSocket(
|
||||||
|
`${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${
|
||||||
|
state.term.rows
|
||||||
|
}`
|
||||||
|
);
|
||||||
// 监听socket连接
|
// 监听socket连接
|
||||||
state.socket.onopen = open;
|
state.socket.onopen = open;
|
||||||
// 监听socket错误信息
|
// 监听socket错误信息
|
||||||
@@ -129,8 +133,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
console.log('socket连接成功');
|
// 如果有初始要执行的命令,则发送执行命令
|
||||||
initXterm();
|
if (state.cmd) {
|
||||||
|
sendCmd(state.cmd + ' \r');
|
||||||
|
}
|
||||||
//开启心跳
|
//开启心跳
|
||||||
// this.start();
|
// this.start();
|
||||||
}
|
}
|
||||||
@@ -151,20 +157,9 @@ export default defineComponent({
|
|||||||
// this.reconnect()
|
// this.reconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessage(msg: string) {
|
function getMessage(msg: any) {
|
||||||
// console.log(msg)
|
// msg.data是真正后端返回的数据
|
||||||
state.term.write(msg['data']);
|
state.term.write(msg.data);
|
||||||
//msg是返回的数据
|
|
||||||
// msg = JSON.parse(msg.data);
|
|
||||||
// this.socket.send("ping");//有事没事ping一下,看看ws还活着没
|
|
||||||
// //switch用于处理返回的数据,根据返回数据的格式去判断
|
|
||||||
// switch (msg["operation"]) {
|
|
||||||
// case "stdout":
|
|
||||||
// this.term.write(msg["data"]);//这里write也许不是固定的,失败后找后端看一下该怎么往term里面write
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// console.error("Unexpected message type:", msg);//但是错误是固定的。。。。
|
|
||||||
// }
|
|
||||||
//收到服务器信息,心跳重置
|
//收到服务器信息,心跳重置
|
||||||
// this.reset();
|
// this.reset();
|
||||||
}
|
}
|
||||||
@@ -175,7 +170,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
function sendCmd(key: any) {
|
function sendCmd(key: any) {
|
||||||
send({
|
send({
|
||||||
type: 'cmd',
|
type: data,
|
||||||
msg: key,
|
msg: key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -633,10 +633,10 @@ echarts@^5.3.3:
|
|||||||
tslib "2.3.0"
|
tslib "2.3.0"
|
||||||
zrender "5.3.2"
|
zrender "5.3.2"
|
||||||
|
|
||||||
element-plus@^2.2.12:
|
element-plus@^2.2.13:
|
||||||
version "2.2.12"
|
version "2.2.13"
|
||||||
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.12.tgz#b6c4e298e02ba9b904d70daa54def27b2de8c43c"
|
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.13.tgz#9ec3a9fa6587c93a87bb0d30c200ac8ee4f69c8b"
|
||||||
integrity sha512-g/hIHj3b+dND2R3YRvyvCJtJhQvR7lWvXqhJaoxaQmajjNWedoe4rttxG26fOSv9YCC2wN4iFDcJHs70YFNgrA==
|
integrity sha512-dKQ7BPZC8deUPhv+6s4GgOL0GyGj3KpUarywxm6s1nWnHjH6FqeZlUcxPqBvJd7W/d81POayx3B13GP+rfkG9g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ctrl/tinycolor" "^3.4.1"
|
"@ctrl/tinycolor" "^3.4.1"
|
||||||
"@element-plus/icons-vue" "^2.0.6"
|
"@element-plus/icons-vue" "^2.0.6"
|
||||||
|
|||||||
@@ -160,21 +160,16 @@ func (m *Machine) WsSSH(g *gin.Context) {
|
|||||||
panic(biz.NewBizErr("\033[1;31m您没有权限操作该机器终端,请重新登录后再试~\033[0m"))
|
panic(biz.NewBizErr("\033[1;31m您没有权限操作该机器终端,请重新登录后再试~\033[0m"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cols := ginx.QueryInt(g, "cols", 80)
|
|
||||||
rows := ginx.QueryInt(g, "rows", 40)
|
|
||||||
|
|
||||||
cli := m.MachineApp.GetCli(GetMachineId(g))
|
cli := m.MachineApp.GetCli(GetMachineId(g))
|
||||||
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
|
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
|
||||||
|
|
||||||
sws, err := machine.NewLogicSshWsSession(cols, rows, cli, wsConn)
|
cols := ginx.QueryInt(g, "cols", 80)
|
||||||
biz.ErrIsNilAppendErr(err, "\033[1;31m连接失败:%s\033[0m")
|
rows := ginx.QueryInt(g, "rows", 40)
|
||||||
defer sws.Close()
|
|
||||||
|
|
||||||
quitChan := make(chan bool, 3)
|
mts, err := machine.NewTerminalSession(utils.RandString(16), wsConn, cli, rows, cols)
|
||||||
sws.Start(quitChan)
|
biz.ErrIsNilAppendErr(err, "\033[1;31m连接失败: %s\033[0m")
|
||||||
go sws.Wait(quitChan)
|
mts.Start()
|
||||||
|
defer mts.Stop()
|
||||||
<-quitChan
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMachineId(g *gin.Context) uint64 {
|
func GetMachineId(g *gin.Context) uint64 {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package machine
|
|
||||||
|
|
||||||
const StatsShell = `
|
|
||||||
cat /proc/uptime
|
|
||||||
echo '-----'
|
|
||||||
/bin/hostname -f
|
|
||||||
echo '-----'
|
|
||||||
cat /proc/loadavg
|
|
||||||
echo '-----'
|
|
||||||
cat /proc/meminfo
|
|
||||||
echo '-----'
|
|
||||||
df -B1
|
|
||||||
echo '-----'
|
|
||||||
/sbin/ip -o addr
|
|
||||||
echo '-----'
|
|
||||||
/bin/cat /proc/net/dev
|
|
||||||
echo '-----'
|
|
||||||
top -b -n 1 | grep Cpu
|
|
||||||
`
|
|
||||||
@@ -53,6 +53,24 @@ type Stats struct {
|
|||||||
CPU CPUInfo // or []CPUInfo to get all the cpu-core's stats?
|
CPU CPUInfo // or []CPUInfo to get all the cpu-core's stats?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StatsShell = `
|
||||||
|
cat /proc/uptime
|
||||||
|
echo '-----'
|
||||||
|
/bin/hostname -f
|
||||||
|
echo '-----'
|
||||||
|
cat /proc/loadavg
|
||||||
|
echo '-----'
|
||||||
|
cat /proc/meminfo
|
||||||
|
echo '-----'
|
||||||
|
df -B1
|
||||||
|
echo '-----'
|
||||||
|
/sbin/ip -o addr
|
||||||
|
echo '-----'
|
||||||
|
/bin/cat /proc/net/dev
|
||||||
|
echo '-----'
|
||||||
|
top -b -n 1 | grep Cpu
|
||||||
|
`
|
||||||
|
|
||||||
func (c *Cli) GetAllStats() *Stats {
|
func (c *Cli) GetAllStats() *Stats {
|
||||||
res, _ := c.Run(StatsShell)
|
res, _ := c.Run(StatsShell)
|
||||||
infos := strings.Split(*res, "-----")
|
infos := strings.Split(*res, "-----")
|
||||||
|
|||||||
74
server/internal/devops/infrastructure/machine/terminal.go
Normal file
74
server/internal/devops/infrastructure/machine/terminal.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Terminal struct {
|
||||||
|
SshSession *ssh.Session
|
||||||
|
StdinPipe io.WriteCloser
|
||||||
|
StdoutReader *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建机器ssh终端
|
||||||
|
func NewTerminal(cli *Cli) (*Terminal, error) {
|
||||||
|
sshSession, err := cli.GetSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutPipe, err := sshSession.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stdoutReader := bufio.NewReader(stdoutPipe)
|
||||||
|
|
||||||
|
stdinPipe, err := sshSession.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal := Terminal{
|
||||||
|
SshSession: sshSession,
|
||||||
|
StdinPipe: stdinPipe,
|
||||||
|
StdoutReader: stdoutReader,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &terminal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) Write(p []byte) (int, error) {
|
||||||
|
return t.StdinPipe.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) ReadRune() (r rune, size int, err error) {
|
||||||
|
return t.StdoutReader.ReadRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) Close() error {
|
||||||
|
if t.SshSession != nil {
|
||||||
|
return t.SshSession.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) WindowChange(h int, w int) error {
|
||||||
|
return t.SshSession.WindowChange(h, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) RequestPty(term string, h, w int) error {
|
||||||
|
modes := ssh.TerminalModes{
|
||||||
|
ssh.ECHO: 1,
|
||||||
|
ssh.TTY_OP_ISPEED: 14400,
|
||||||
|
ssh.TTY_OP_OSPEED: 14400,
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.SshSession.RequestPty(term, h, w, modes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) Shell() error {
|
||||||
|
return t.SshSession.Shell()
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"mayfly-go/pkg/global"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Resize = 1
|
||||||
|
Data = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type TerminalSession struct {
|
||||||
|
ID string
|
||||||
|
wsConn *websocket.Conn
|
||||||
|
terminal *Terminal
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
dataChan chan rune
|
||||||
|
tick *time.Ticker
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTerminalSession(sessionId string, ws *websocket.Conn, cli *Cli, rows, cols int) (*TerminalSession, error) {
|
||||||
|
terminal, err := NewTerminal(cli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = terminal.RequestPty("xterm-256color", rows, cols)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = terminal.Shell()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
tick := time.NewTicker(time.Millisecond * time.Duration(60))
|
||||||
|
ts := &TerminalSession{
|
||||||
|
ID: sessionId,
|
||||||
|
wsConn: ws,
|
||||||
|
terminal: terminal,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
dataChan: make(chan rune),
|
||||||
|
tick: tick,
|
||||||
|
}
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r TerminalSession) Start() {
|
||||||
|
go r.readFormTerminal()
|
||||||
|
go r.writeToWebsocket()
|
||||||
|
r.receiveWsMsg()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r TerminalSession) Stop() {
|
||||||
|
global.Log.Debug("close machine ssh terminal session")
|
||||||
|
r.tick.Stop()
|
||||||
|
r.cancel()
|
||||||
|
if r.terminal != nil {
|
||||||
|
if err := r.terminal.Close(); err != nil {
|
||||||
|
global.Log.Errorf("关闭机器ssh终端失败: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts TerminalSession) readFormTerminal() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ts.ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
rn, size, err := ts.terminal.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
global.Log.Error("机器ssh终端读取消息失败: ", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if size > 0 {
|
||||||
|
ts.dataChan <- rn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts TerminalSession) writeToWebsocket() {
|
||||||
|
var buf []byte
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ts.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ts.tick.C:
|
||||||
|
if len(buf) > 0 {
|
||||||
|
s := string(buf)
|
||||||
|
if err := WriteMessage(ts.wsConn, s); err != nil {
|
||||||
|
global.Log.Error("机器ssh终端发送消息至websocket失败: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf = []byte{}
|
||||||
|
}
|
||||||
|
case data := <-ts.dataChan:
|
||||||
|
if data != utf8.RuneError {
|
||||||
|
p := make([]byte, utf8.RuneLen(data))
|
||||||
|
utf8.EncodeRune(p, data)
|
||||||
|
buf = append(buf, p...)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, []byte("@")...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WsMsg struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Cols int `json:"cols"`
|
||||||
|
Rows int `json:"rows"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TerminalSession) receiveWsMsg() {
|
||||||
|
wsConn := ts.wsConn
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ts.ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// read websocket msg
|
||||||
|
_, wsData, err := wsConn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
global.Log.Debug("机器ssh终端读取websocket消息失败: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 解析消息
|
||||||
|
msgObj := WsMsg{}
|
||||||
|
if err := json.Unmarshal(wsData, &msgObj); err != nil {
|
||||||
|
global.Log.Error("机器ssh终端消息解析失败: ", err)
|
||||||
|
}
|
||||||
|
switch msgObj.Type {
|
||||||
|
case Resize:
|
||||||
|
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
||||||
|
if err := ts.terminal.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
|
||||||
|
global.Log.Error("ssh pty change windows size failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Data:
|
||||||
|
_, err := ts.terminal.Write([]byte(msgObj.Msg))
|
||||||
|
if err != nil {
|
||||||
|
global.Log.Debug("机器ssh终端写入消息失败: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteMessage(ws *websocket.Conn, msg string) error {
|
||||||
|
return ws.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||||
|
}
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
package machine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"mayfly-go/pkg/global"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type safeBuffer struct {
|
|
||||||
buffer bytes.Buffer
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *safeBuffer) Write(p []byte) (int, error) {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
return w.buffer.Write(p)
|
|
||||||
}
|
|
||||||
func (w *safeBuffer) Bytes() []byte {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
return w.buffer.Bytes()
|
|
||||||
}
|
|
||||||
func (w *safeBuffer) Reset() {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
w.buffer.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
wsMsgCmd = "cmd"
|
|
||||||
wsMsgResize = "resize"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WsMsg struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Msg string `json:"msg"`
|
|
||||||
Cols int `json:"cols"`
|
|
||||||
Rows int `json:"rows"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicSshWsSession struct {
|
|
||||||
stdinPipe io.WriteCloser
|
|
||||||
comboOutput *safeBuffer //ssh 终端混合输出
|
|
||||||
inputFilterBuff *safeBuffer //用来过滤输入的命令和ssh_filter配置对比的
|
|
||||||
session *ssh.Session
|
|
||||||
wsConn *websocket.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogicSshWsSession(cols, rows int, cli *Cli, wsConn *websocket.Conn) (*LogicSshWsSession, error) {
|
|
||||||
sshSession, err := cli.GetSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stdinP, err := sshSession.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
comboWriter := new(safeBuffer)
|
|
||||||
inputBuf := new(safeBuffer)
|
|
||||||
//ssh.stdout and stderr will write output into comboWriter
|
|
||||||
sshSession.Stdout = comboWriter
|
|
||||||
sshSession.Stderr = comboWriter
|
|
||||||
|
|
||||||
modes := ssh.TerminalModes{
|
|
||||||
ssh.ECHO: 1, // disable echo
|
|
||||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
|
||||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
|
||||||
}
|
|
||||||
// Request pseudo terminal
|
|
||||||
if err := sshSession.RequestPty("xterm-256color", rows, cols, modes); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Start remote shell
|
|
||||||
if err := sshSession.Shell(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &LogicSshWsSession{
|
|
||||||
stdinPipe: stdinP,
|
|
||||||
comboOutput: comboWriter,
|
|
||||||
inputFilterBuff: inputBuf,
|
|
||||||
session: sshSession,
|
|
||||||
wsConn: wsConn,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//Close 关闭
|
|
||||||
func (sws *LogicSshWsSession) Close() {
|
|
||||||
if sws.session != nil {
|
|
||||||
sws.session.Close()
|
|
||||||
}
|
|
||||||
if sws.comboOutput != nil {
|
|
||||||
sws.comboOutput = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws *LogicSshWsSession) Start(quitChan chan bool) {
|
|
||||||
go sws.receiveWsMsg(quitChan)
|
|
||||||
go sws.sendComboOutput(quitChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
//receiveWsMsg receive websocket msg do some handling then write into ssh.session.stdin
|
|
||||||
func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
|
||||||
wsConn := sws.wsConn
|
|
||||||
//tells other go routine quit
|
|
||||||
defer setQuit(exitCh)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-exitCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
//read websocket msg
|
|
||||||
_, wsData, err := wsConn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
global.Log.Error("reading webSocket message failed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//unmashal bytes into struct
|
|
||||||
msgObj := WsMsg{}
|
|
||||||
if err := json.Unmarshal(wsData, &msgObj); err != nil {
|
|
||||||
global.Log.Error("unmarshal websocket message failed:", err)
|
|
||||||
}
|
|
||||||
switch msgObj.Type {
|
|
||||||
case wsMsgResize:
|
|
||||||
//handle xterm.js size change
|
|
||||||
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
|
||||||
if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
|
|
||||||
global.Log.Error("ssh pty change windows size failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case wsMsgCmd:
|
|
||||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe([]byte(msgObj.Msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//sendWebsocketInputCommandToSshSessionStdinPipe
|
|
||||||
func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
|
|
||||||
if _, err := sws.stdinPipe.Write(cmdBytes); err != nil {
|
|
||||||
global.Log.Error("ws cmd bytes write to ssh.stdin pipe failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
|
|
||||||
wsConn := sws.wsConn
|
|
||||||
//todo 优化成一个方法
|
|
||||||
//tells other go routine quit
|
|
||||||
defer setQuit(exitCh)
|
|
||||||
|
|
||||||
//every 120ms write combine output bytes into websocket response
|
|
||||||
tick := time.NewTicker(time.Millisecond * time.Duration(60))
|
|
||||||
//for range time.Tick(120 * time.Millisecond){}
|
|
||||||
defer tick.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-tick.C:
|
|
||||||
if sws.comboOutput == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bs := sws.comboOutput.Bytes()
|
|
||||||
if len(bs) > 0 {
|
|
||||||
err := wsConn.WriteMessage(websocket.TextMessage, bs)
|
|
||||||
if err != nil {
|
|
||||||
global.Log.Error("ssh sending combo output to webSocket failed")
|
|
||||||
}
|
|
||||||
sws.comboOutput.buffer.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-exitCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws *LogicSshWsSession) Wait(quitChan chan bool) {
|
|
||||||
if err := sws.session.Wait(); err != nil {
|
|
||||||
setQuit(quitChan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setQuit(ch chan bool) {
|
|
||||||
ch <- true
|
|
||||||
}
|
|
||||||
@@ -56,7 +56,7 @@ func checkConn() {
|
|||||||
|
|
||||||
// 删除ws连接
|
// 删除ws连接
|
||||||
func Delete(userid uint64) {
|
func Delete(userid uint64) {
|
||||||
global.Log.Info("移除websocket连接:uid = ", userid)
|
global.Log.Debug("移除websocket连接:uid = ", userid)
|
||||||
conn := conns[userid]
|
conn := conns[userid]
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
|||||||
Reference in New Issue
Block a user