diff --git a/internal/service/ssh.go b/internal/service/ssh.go index e0c0d8f698..584426c66c 100644 --- a/internal/service/ssh.go +++ b/internal/service/ssh.go @@ -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() } diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index be7a35fe58..6cfda0dda2 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -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) } diff --git a/pkg/ssh/turn.go b/pkg/ssh/turn.go index fca3ee53b7..7e8e83e56e 100644 --- a/pkg/ssh/turn.go +++ b/pkg/ssh/turn.go @@ -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 MessageResize struct { + Resize bool `json:"resize"` + Columns int `json:"columns"` + Rows int `json:"rows"` +} type Turn struct { - StdinPipe io.WriteCloser - Session *ssh.Session - WsConn *websocket.Conn + stdin io.WriteCloser + session *ssh.Session + ws *websocket.Conn } -func NewTurn(wsConn *websocket.Conn, sshClient *ssh.Client) (*Turn, error) { - sess, err := sshClient.NewSession() +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 } - } - } -} -func (t *Turn) SessionWait() error { - if err := t.Session.Wait(); err != nil { - return err + if _, err = t.stdin.Write(data); err != nil { + return fmt.Errorf("writing ws message to stdin err: %v", 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() } diff --git a/web/package.json b/web/package.json index 883fe5fffd..f618d4dba8 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 74ac65ba29..0352e2de57 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -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: {} diff --git a/web/src/utils/common/crypto.ts b/web/src/utils/common/crypto.ts deleted file mode 100644 index db2bb49479..0000000000 --- a/web/src/utils/common/crypto.ts +++ /dev/null @@ -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 -} diff --git a/web/src/utils/common/index.ts b/web/src/utils/common/index.ts index 1b918f1a37..c6d8d118fb 100644 --- a/web/src/utils/common/index.ts +++ b/web/src/utils/common/index.ts @@ -1,6 +1,5 @@ export * from './color' export * from './common' -export * from './crypto' export * from './icon' export * from './is' export * from './naiveTools' diff --git a/web/src/views/ssh/IndexView.vue b/web/src/views/ssh/IndexView.vue index 64216a5ed3..672da0c1d8 100644 --- a/web/src/views/ssh/IndexView.vue +++ b/web/src/views/ssh/IndexView.vue @@ -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 + }) ) } })