mirror of
https://github.com/acepanel/panel.git
synced 2026-02-07 18:13:13 +08:00
refactor: 重写ssh
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
111
pkg/ssh/turn.go
111
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 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()
|
||||
}
|
||||
|
||||
@@ -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
84
web/pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from './color'
|
||||
export * from './common'
|
||||
export * from './crypto'
|
||||
export * from './icon'
|
||||
export * from './is'
|
||||
export * from './naiveTools'
|
||||
|
||||
@@ -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
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user