2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-07 18:13:13 +08:00

refactor: 重写ssh

This commit is contained in:
耗子
2024-10-20 04:38:27 +08:00
parent 1a7f679fca
commit 47b92a8b2a
8 changed files with 163 additions and 180 deletions

View File

@@ -1,7 +1,6 @@
package service
import (
"bytes"
"context"
"net/http"
"sync"
@@ -9,7 +8,9 @@ import (
"github.com/gorilla/websocket"
"github.com/spf13/cast"
"go.uber.org/zap"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
@@ -74,11 +75,9 @@ func (s *SSHService) Session(w http.ResponseWriter, r *http.Request) {
cast.ToString(info["password"]),
)
client, err := ssh.NewSSHClient(config)
if err != nil {
_ = ws.WriteControl(websocket.CloseMessage,
[]byte(err.Error()), time.Now().Add(time.Second))
ErrorSystem(w)
return
}
defer client.Close()
@@ -87,38 +86,28 @@ func (s *SSHService) Session(w http.ResponseWriter, r *http.Request) {
if err != nil {
_ = ws.WriteControl(websocket.CloseMessage,
[]byte(err.Error()), time.Now().Add(time.Second))
ErrorSystem(w)
return
}
defer turn.Close()
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
var logBuff = bufPool.Get().(*bytes.Buffer)
logBuff.Reset()
defer bufPool.Put(logBuff)
sshCtx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
if err = turn.LoopRead(logBuff, sshCtx); err != nil {
ErrorSystem(w)
if err = turn.Handle(ctx); err != nil {
app.Logger.Error("读取 ssh 数据失败", zap.Error(err))
return
}
}()
go func() {
defer wg.Done()
if err = turn.SessionWait(); err != nil {
ErrorSystem(w)
return
if err = turn.Wait(); err != nil {
app.Logger.Error("保持 ssh 会话失败", zap.Error(err))
}
cancel()
}()
wg.Wait()
wg.Wait()
}

View File

@@ -1,11 +1,10 @@
package ssh
import (
"os"
"time"
"golang.org/x/crypto/ssh"
"github.com/TheTNB/panel/pkg/io"
)
type AuthMethod int8
@@ -45,11 +44,12 @@ func ClientConfigPublicKey(hostAddr, user, keyPath string) *ClientConfig {
}
func NewSSHClient(conf *ClientConfig) (*ssh.Client, error) {
config := &ssh.ClientConfig{
Timeout: conf.Timeout,
User: conf.User,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
config := &ssh.ClientConfig{}
config.SetDefaults()
config.Timeout = conf.Timeout
config.User = conf.User
config.HostKeyCallback = ssh.InsecureIgnoreHostKey()
switch conf.AuthMethod {
case PASSWORD:
config.Auth = []ssh.AuthMethod{ssh.Password(conf.Password)}
@@ -60,18 +60,19 @@ func NewSSHClient(conf *ClientConfig) (*ssh.Client, error) {
}
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
}
c, err := ssh.Dial("tcp", conf.HostAddr, config)
c, err := ssh.Dial("tcp", conf.HostAddr, config) // TODO support ipv6
if err != nil {
return nil, err
}
return c, nil
}
func getKey(keyPath string) (ssh.Signer, error) {
key, err := io.Read(keyPath)
key, err := os.ReadFile(keyPath)
if err != nil {
return nil, err
}
return ssh.ParsePrivateKey([]byte(key))
return ssh.ParsePrivateKey(key)
}

View File

@@ -1,9 +1,7 @@
package ssh
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -13,29 +11,30 @@ import (
"golang.org/x/crypto/ssh"
)
const (
MsgData = '1'
MsgResize = '2'
)
type Turn struct {
StdinPipe io.WriteCloser
Session *ssh.Session
WsConn *websocket.Conn
type MessageResize struct {
Resize bool `json:"resize"`
Columns int `json:"columns"`
Rows int `json:"rows"`
}
func NewTurn(wsConn *websocket.Conn, sshClient *ssh.Client) (*Turn, error) {
sess, err := sshClient.NewSession()
type Turn struct {
stdin io.WriteCloser
session *ssh.Session
ws *websocket.Conn
}
func NewTurn(ws *websocket.Conn, client *ssh.Client) (*Turn, error) {
sess, err := client.NewSession()
if err != nil {
return nil, err
}
stdinPipe, err := sess.StdinPipe()
stdin, err := sess.StdinPipe()
if err != nil {
return nil, err
}
turn := &Turn{StdinPipe: stdinPipe, Session: sess, WsConn: wsConn}
turn := &Turn{stdin: stdin, session: sess, ws: ws}
sess.Stdout = turn
sess.Stderr = turn
@@ -44,10 +43,10 @@ func NewTurn(wsConn *websocket.Conn, sshClient *ssh.Client) (*Turn, error) {
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := sess.RequestPty("xterm", 150, 30, modes); err != nil {
if err = sess.RequestPty("xterm", 150, 80, modes); err != nil {
return nil, err
}
if err := sess.Shell(); err != nil {
if err = sess.Shell(); err != nil {
return nil, err
}
@@ -55,7 +54,7 @@ func NewTurn(wsConn *websocket.Conn, sshClient *ssh.Client) (*Turn, error) {
}
func (t *Turn) Write(p []byte) (n int, err error) {
writer, err := t.WsConn.NextWriter(websocket.BinaryMessage)
writer, err := t.ws.NextWriter(websocket.BinaryMessage)
if err != nil {
return 0, err
}
@@ -65,76 +64,42 @@ func (t *Turn) Write(p []byte) (n int, err error) {
}
func (t *Turn) Close() error {
if t.Session != nil {
t.Session.Close()
if t.session != nil {
_ = t.session.Close()
}
return t.WsConn.Close()
return t.ws.Close()
}
func (t *Turn) Read(p []byte) (n int, err error) {
for {
msgType, reader, err := t.WsConn.NextReader()
if err != nil {
return 0, err
}
if msgType != websocket.BinaryMessage {
continue
}
return reader.Read(p)
}
}
func (t *Turn) LoopRead(logBuff *bytes.Buffer, context context.Context) error {
func (t *Turn) Handle(context context.Context) error {
var resize MessageResize
for {
select {
case <-context.Done():
return errors.New("LoopRead exit")
return errors.New("ssh context done exit")
default:
_, wsData, err := t.WsConn.ReadMessage()
_, data, err := t.ws.ReadMessage()
if err != nil {
return fmt.Errorf("reading webSocket message err:%s", err)
return fmt.Errorf("reading ws message err: %v", err)
}
body := decode(wsData[1:])
switch wsData[0] {
case MsgResize:
var args Resize
err := json.Unmarshal(body, &args)
if err != nil {
return fmt.Errorf("ssh pty resize windows err:%s", err)
}
if args.Columns > 0 && args.Rows > 0 {
if err := t.Session.WindowChange(args.Rows, args.Columns); err != nil {
return fmt.Errorf("ssh pty resize windows err:%s", err)
// 判断是否是 resize 消息
if err = json.Unmarshal(data, &resize); err == nil {
if resize.Resize && resize.Columns > 0 && resize.Rows > 0 {
if err = t.session.WindowChange(resize.Rows, resize.Columns); err != nil {
return fmt.Errorf("change window size err: %v", err)
}
}
case MsgData:
if _, err := t.StdinPipe.Write(body); err != nil {
return fmt.Errorf("StdinPipe write err:%s", err)
}
if _, err := logBuff.Write(body); err != nil {
return fmt.Errorf("logBuff write err:%s", err)
}
continue
}
if _, err = t.stdin.Write(data); err != nil {
return fmt.Errorf("writing ws message to stdin err: %v", err)
}
}
}
}
func (t *Turn) SessionWait() error {
if err := t.Session.Wait(); err != nil {
return err
}
return nil
}
func decode(p []byte) []byte {
decodeString, _ := base64.StdEncoding.DecodeString(string(p))
return decodeString
}
type Resize struct {
Columns int
Rows int
func (t *Turn) Wait() error {
return t.session.Wait()
}

View File

@@ -22,10 +22,13 @@
"dependencies": {
"@guolao/vue-monaco-editor": "^1.5.4",
"@vueuse/core": "^11.1.0",
"@xterm/addon-attach": "^0.11.0",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"axios": "^1.7.7",
"crypto-js": "^4.2.0",
"echarts": "^5.5.1",
"install": "^0.13.0",
"lodash-es": "^4.17.21",
@@ -44,7 +47,6 @@
"@iconify/vue": "^4.1.2",
"@rushstack/eslint-patch": "^1.10.4",
"@tsconfig/node20": "^20.1.4",
"@types/crypto-js": "^4.2.2",
"@types/lodash-es": "^4.17.12",
"@types/luxon": "^3.4.2",
"@types/node": "^20.16.11",

84
web/pnpm-lock.yaml generated
View File

@@ -14,18 +14,27 @@ importers:
'@vueuse/core':
specifier: ^11.1.0
version: 11.1.0(vue@3.5.12(typescript@5.6.3))
'@xterm/addon-attach':
specifier: ^0.11.0
version: 0.11.0(@xterm/xterm@5.5.0)
'@xterm/addon-clipboard':
specifier: ^0.1.0
version: 0.1.0(@xterm/xterm@5.5.0)
'@xterm/addon-fit':
specifier: ^0.10.0
version: 0.10.0(@xterm/xterm@5.5.0)
'@xterm/addon-web-links':
specifier: ^0.11.0
version: 0.11.0(@xterm/xterm@5.5.0)
'@xterm/addon-webgl':
specifier: ^0.18.0
version: 0.18.0(@xterm/xterm@5.5.0)
'@xterm/xterm':
specifier: ^5.5.0
version: 5.5.0
axios:
specifier: ^1.7.7
version: 1.7.7
crypto-js:
specifier: ^4.2.0
version: 4.2.0
echarts:
specifier: ^5.5.1
version: 5.5.1
@@ -75,9 +84,6 @@ importers:
'@tsconfig/node20':
specifier: ^20.1.4
version: 20.1.4
'@types/crypto-js':
specifier: ^4.2.2
version: 4.2.2
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
@@ -833,30 +839,35 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-glibc@2.4.1':
resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.4.1':
resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.4.1':
resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.4.1':
resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.4.1':
resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==}
@@ -924,46 +935,55 @@ packages:
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.24.0':
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.24.0':
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.24.0':
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.24.0':
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.24.0':
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.24.0':
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
@@ -990,9 +1010,6 @@ packages:
'@tsconfig/node20@20.1.4':
resolution: {integrity: sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==}
'@types/crypto-js@4.2.2':
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
@@ -1290,11 +1307,31 @@ packages:
'@vueuse/shared@11.1.0':
resolution: {integrity: sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==}
'@xterm/addon-attach@0.11.0':
resolution: {integrity: sha512-JboCN0QAY6ZLY/SSB/Zl2cQ5zW1Eh4X3fH7BnuR1NB7xGRhzbqU2Npmpiw/3zFlxDaU88vtKzok44JKi2L2V2Q==}
peerDependencies:
'@xterm/xterm': ^5.0.0
'@xterm/addon-clipboard@0.1.0':
resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==}
peerDependencies:
'@xterm/xterm': ^5.4.0
'@xterm/addon-fit@0.10.0':
resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==}
peerDependencies:
'@xterm/xterm': ^5.0.0
'@xterm/addon-web-links@0.11.0':
resolution: {integrity: sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==}
peerDependencies:
'@xterm/xterm': ^5.0.0
'@xterm/addon-webgl@0.18.0':
resolution: {integrity: sha512-xCnfMBTI+/HKPdRnSOHaJDRqEpq2Ugy8LEj9GiY4J3zJObo3joylIFaMvzBwbYRg8zLtkO0KQaStCeSfoaI2/w==}
peerDependencies:
'@xterm/xterm': ^5.0.0
'@xterm/xterm@5.5.0':
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
@@ -1535,9 +1572,6 @@ packages:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
css-render@0.15.14:
resolution: {integrity: sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==}
@@ -2178,6 +2212,9 @@ packages:
resolution: {integrity: sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==}
hasBin: true
js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -4126,8 +4163,6 @@ snapshots:
'@tsconfig/node20@20.1.4': {}
'@types/crypto-js@4.2.2': {}
'@types/estree@1.0.6': {}
'@types/katex@0.16.7': {}
@@ -4577,10 +4612,27 @@ snapshots:
- '@vue/composition-api'
- vue
'@xterm/addon-attach@0.11.0(@xterm/xterm@5.5.0)':
dependencies:
'@xterm/xterm': 5.5.0
'@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)':
dependencies:
'@xterm/xterm': 5.5.0
js-base64: 3.7.7
'@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)':
dependencies:
'@xterm/xterm': 5.5.0
'@xterm/addon-web-links@0.11.0(@xterm/xterm@5.5.0)':
dependencies:
'@xterm/xterm': 5.5.0
'@xterm/addon-webgl@0.18.0(@xterm/xterm@5.5.0)':
dependencies:
'@xterm/xterm': 5.5.0
'@xterm/xterm@5.5.0': {}
acorn-jsx@5.3.2(acorn@8.12.1):
@@ -4839,8 +4891,6 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crypto-js@4.2.0: {}
css-render@0.15.14:
dependencies:
'@emotion/hash': 0.8.0
@@ -5600,6 +5650,8 @@ snapshots:
jiti@2.3.3: {}
js-base64@3.7.7: {}
js-tokens@4.0.0: {}
js-tokens@9.0.0: {}

View File

@@ -1,24 +0,0 @@
import CryptoJS from 'crypto-js'
const CryptoSecret = '__SecretKey__'
/**
* 加密数据
* @param data - 数据
*/
export function encrypto(data: any) {
const newData = JSON.stringify(data)
return CryptoJS.AES.encrypt(newData, CryptoSecret).toString()
}
/**
* 解密数据
* @param cipherText - 密文
*/
export function decrypto(cipherText: string) {
const bytes = CryptoJS.AES.decrypt(cipherText, CryptoSecret)
const originalText = bytes.toString(CryptoJS.enc.Utf8)
if (originalText) return JSON.parse(originalText)
return null
}

View File

@@ -1,6 +1,5 @@
export * from './color'
export * from './common'
export * from './crypto'
export * from './icon'
export * from './is'
export * from './naiveTools'

View File

@@ -3,19 +3,19 @@ defineOptions({
name: 'ssh-index'
})
import { AttachAddon } from '@xterm/addon-attach'
import { ClipboardAddon } from '@xterm/addon-clipboard'
import { FitAddon } from '@xterm/addon-fit'
import { WebLinksAddon } from '@xterm/addon-web-links'
import { WebglAddon } from '@xterm/addon-webgl'
import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
import CryptoJS from 'crypto-js'
import { useI18n } from 'vue-i18n'
import ssh from '@/api/panel/ssh'
const { t } = useI18n()
const msgData = '1'
const msgResize = '2'
const model = ref({
host: '',
port: 22,
@@ -42,32 +42,39 @@ const getInfo = () => {
const openSession = () => {
const term = new Terminal({
fontSize: 15,
cursorBlink: true, // 光标闪烁
theme: {
foreground: '#ECECEC', // 字体
background: '#000000', //背景色
cursor: 'help' // 设置光标
}
lineHeight: 1.2,
fontSize: 14,
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
cursorBlink: true,
cursorStyle: 'underline',
scrollback: 1000,
scrollSensitivity: 15,
tabStopWidth: 4,
theme: { background: '#111', foreground: '#fff' }
})
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
const ws = new WebSocket(`${protocol}://${window.location.host}/api/ssh/session`)
ws.binaryType = 'arraybuffer'
const enc = new TextDecoder('utf-8')
ws.onmessage = (event) => {
term.write(enc.decode(event.data))
}
const attachAddon = new AttachAddon(ws)
term.loadAddon(attachAddon)
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
const clipboardAddon = new ClipboardAddon()
term.loadAddon(clipboardAddon)
const webLinksAddon = new WebLinksAddon()
term.loadAddon(webLinksAddon)
const webglAddon = new WebglAddon()
term.loadAddon(webglAddon)
webglAddon.onContextLoss(() => {
webglAddon.dispose()
})
ws.onopen = () => {
term.open(document.getElementById('terminal') as HTMLElement)
fitAddon.fit()
term.write('\r\n欢迎来到耗子面板SSH连接成功。')
term.write('\r\nWelcome to HaoZiPanel SSH. Connection success.\r\n')
term.write('\r\n欢迎来到耗子面板 SSH连接成功。')
term.write('\r\nWelcome to Rat Panel SSH. Connection success.\r\n')
term.focus()
}
@@ -83,22 +90,14 @@ const openSession = () => {
ws.close()
}
term.onData((data) => {
ws.send(msgData + CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(data)))
})
term.onResize(({ cols, rows }) => {
if (ws.readyState === 1) {
ws.send(
msgResize +
CryptoJS.enc.Base64.stringify(
CryptoJS.enc.Utf8.parse(
JSON.stringify({
columns: cols,
rows: rows
})
)
)
JSON.stringify({
resize: true,
columns: cols,
rows: rows
})
)
}
})