diff --git a/backend/app/api/v1/container.go b/backend/app/api/v1/container.go index 13c5048ab..2d78af06a 100644 --- a/backend/app/api/v1/container.go +++ b/backend/app/api/v1/container.go @@ -1,16 +1,10 @@ package v1 import ( - "context" - "strconv" - "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" - "github.com/1Panel-dev/1Panel/backend/utils/docker" - "github.com/1Panel-dev/1Panel/backend/utils/terminal" - "github.com/docker/docker/api/types" "github.com/gin-gonic/gin" "github.com/pkg/errors" ) @@ -231,63 +225,6 @@ func (b *BaseApi) Inspect(c *gin.Context) { helper.SuccessWithData(c, result) } -func (b *BaseApi) ContainerExec(c *gin.Context) { - containerID := c.Query("containerid") - command := c.Query("command") - user := c.Query("user") - cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) - return - } - rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) - return - } - - wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) - if err != nil { - global.LOG.Errorf("gin context http handler failed, err: %v", err) - return - } - defer wsConn.Close() - - client, err := docker.NewDockerClient() - if wshandleError(wsConn, errors.WithMessage(err, "New docker client failed.")) { - return - } - - conf := types.ExecConfig{Tty: true, Cmd: []string{command}, AttachStderr: true, AttachStdin: true, AttachStdout: true} - if len(user) != 0 { - conf.User = user - } - ir, err := client.ContainerExecCreate(context.TODO(), containerID, conf) - if wshandleError(wsConn, errors.WithMessage(err, "failed to set exec conf.")) { - return - } - hr, err := client.ContainerExecAttach(c, ir.ID, types.ExecStartCheck{Detach: false, Tty: true}) - if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection.")) { - return - } - defer hr.Close() - - sws, err := terminal.NewExecConn(cols, rows, wsConn, hr.Conn) - if wshandleError(wsConn, err) { - return - } - - quitChan := make(chan bool, 3) - ctx, cancel := context.WithCancel(context.Background()) - sws.Start(ctx, quitChan) - <-quitChan - cancel() - - if wshandleError(wsConn, err) { - return - } -} - // @Tags Container // @Summary Container logs // @Description 容器日志 diff --git a/backend/app/api/v1/database_redis.go b/backend/app/api/v1/database_redis.go index 3fdc59265..7cb5517dc 100644 --- a/backend/app/api/v1/database_redis.go +++ b/backend/app/api/v1/database_redis.go @@ -2,21 +2,15 @@ package v1 import ( "bufio" - "context" "fmt" "os" - "strconv" "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/utils/compose" - "github.com/1Panel-dev/1Panel/backend/utils/docker" - "github.com/1Panel-dev/1Panel/backend/utils/terminal" - "github.com/docker/docker/api/types" "github.com/gin-gonic/gin" - "github.com/pkg/errors" ) // @Tags Database Redis @@ -253,64 +247,3 @@ func (b *BaseApi) UpdateRedisConfByFile(c *gin.Context) { helper.SuccessWithData(c, nil) } - -func (b *BaseApi) RedisExec(c *gin.Context) { - redisConf, err := redisService.LoadConf() - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) - return - } - - cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) - return - } - rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) - return - } - - wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) - if err != nil { - global.LOG.Errorf("gin context http handler failed, err: %v", err) - return - } - defer wsConn.Close() - - client, err := docker.NewDockerClient() - if wshandleError(wsConn, errors.WithMessage(err, "New docker client failed.")) { - return - } - - auth := "redis-cli" - if len(redisConf.Requirepass) != 0 { - auth = fmt.Sprintf("redis-cli -a %s --no-auth-warning", redisConf.Requirepass) - } - conf := types.ExecConfig{Tty: true, Cmd: []string{"bash"}, AttachStderr: true, AttachStdin: true, AttachStdout: true, User: "root"} - ir, err := client.ContainerExecCreate(context.TODO(), redisConf.ContainerName, conf) - if wshandleError(wsConn, errors.WithMessage(err, "failed to set exec conf.")) { - return - } - hr, err := client.ContainerExecAttach(c, ir.ID, types.ExecStartCheck{Detach: false, Tty: true}) - if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection.")) { - return - } - defer hr.Close() - - sws, err := terminal.NewExecConn(cols, rows, wsConn, hr.Conn, auth) - if wshandleError(wsConn, err) { - return - } - - quitChan := make(chan bool, 3) - ctx, cancel := context.WithCancel(context.Background()) - sws.Start(ctx, quitChan) - <-quitChan - cancel() - - if wshandleError(wsConn, err) { - return - } -} diff --git a/backend/app/api/v1/terminal.go b/backend/app/api/v1/terminal.go index cb49e8a13..b09723df5 100644 --- a/backend/app/api/v1/terminal.go +++ b/backend/app/api/v1/terminal.go @@ -103,11 +103,63 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) { return } defer wsConn.Close() - auth := "" + commands := fmt.Sprintf("docker exec -it %s redis-cli", redisConf.ContainerName) if len(redisConf.Requirepass) != 0 { - auth = fmt.Sprintf("-a %s --no-auth-warning", redisConf.Requirepass) + commands = fmt.Sprintf("docker exec -it %s redis-cli -a %s --no-auth-warning", redisConf.ContainerName, redisConf.Requirepass) } - slave, err := terminal.NewCommand(redisConf.ContainerName, auth) + slave, err := terminal.NewCommand(commands) + if wshandleError(wsConn, err) { + return + } + defer slave.Close() + + tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave) + if wshandleError(wsConn, err) { + return + } + + quitChan := make(chan bool, 3) + tty.Start(quitChan) + go slave.Wait(quitChan) + + <-quitChan + + global.LOG.Info("websocket finished") + if wshandleError(wsConn, err) { + return + } +} + +func (b *BaseApi) ContainerWsSsh(c *gin.Context) { + containerID := c.Query("containerid") + command := c.Query("command") + user := c.Query("user") + if len(command) == 0 || len(containerID) == 0 { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error param of command or containerID")) + return + } + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + commands := fmt.Sprintf("docker exec -it %s %s", containerID, command) + if len(user) != 0 { + commands = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command) + } + slave, err := terminal.NewCommand(commands) if wshandleError(wsConn, err) { return } diff --git a/backend/app/service/host.go b/backend/app/service/host.go index 81e2816ea..a313351f6 100644 --- a/backend/app/service/host.go +++ b/backend/app/service/host.go @@ -92,10 +92,7 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e } func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) { - host, _ := hostRepo.Get(hostRepo.WithByAddr(req.Addr)) - if host.ID != 0 { - return nil, constant.ErrRecordExist - } + var host model.Host if err := copier.Copy(&host, &req); err != nil { return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) } diff --git a/backend/utils/terminal/exec.go b/backend/utils/terminal/exec.go deleted file mode 100644 index 83812dc66..000000000 --- a/backend/utils/terminal/exec.go +++ /dev/null @@ -1,104 +0,0 @@ -package terminal - -import ( - "context" - "encoding/base64" - "encoding/json" - "fmt" - "net" - "sync" - - "github.com/1Panel-dev/1Panel/backend/global" - "github.com/gorilla/websocket" - "github.com/pkg/errors" -) - -type ExecWsSession struct { - conn net.Conn - wsConn *websocket.Conn - - writeMutex sync.Mutex -} - -func NewExecConn(cols, rows int, wsConn *websocket.Conn, hijacked net.Conn, commands ...string) (*ExecWsSession, error) { - _, _ = hijacked.Write([]byte(fmt.Sprintf("stty cols %d rows %d && clear \r", cols, rows))) - for _, command := range commands { - _, _ = hijacked.Write([]byte(fmt.Sprintf("%s \r", command))) - } - - return &ExecWsSession{ - conn: hijacked, - wsConn: wsConn, - }, nil -} - -func (sws *ExecWsSession) Start(ctx context.Context, quitChan chan bool) { - go sws.handleSlaveEvent(ctx, quitChan) - go sws.receiveWsMsg(ctx, quitChan) -} - -func (sws *ExecWsSession) handleSlaveEvent(ctx context.Context, exitCh chan bool) { - defer setQuit(exitCh) - - buffer := make([]byte, 1024) - for { - n, err := sws.conn.Read(buffer) - if err != nil && errors.Is(err, net.ErrClosed) { - return - } - - if err := sws.masterWrite(buffer[:n]); err != nil { - if errors.Is(err, websocket.ErrCloseSent) { - return - } - } - } -} - -func (sws *ExecWsSession) masterWrite(data []byte) error { - sws.writeMutex.Lock() - defer sws.writeMutex.Unlock() - err := sws.wsConn.WriteMessage(websocket.TextMessage, data) - if err != nil { - return errors.Wrapf(err, "failed to write to master") - } - - return nil -} - -func (sws *ExecWsSession) receiveWsMsg(ctx context.Context, exitCh chan bool) { - wsConn := sws.wsConn - defer setQuit(exitCh) - for { - _, wsData, err := wsConn.ReadMessage() - if err != nil { - return - } - msgObj := wsMsg{} - _ = json.Unmarshal(wsData, &msgObj) - switch msgObj.Type { - case wsMsgResize: - if msgObj.Cols > 0 && msgObj.Rows > 0 { - sws.ResizeTerminal(msgObj.Rows, msgObj.Cols) - } - case wsMsgCmd: - decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd) - if err != nil { - global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) - return - } - sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) - case wsMsgClose: - _, _ = sws.conn.Write([]byte("exit\r")) - return - } - } -} - -func (sws *ExecWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { - _, _ = sws.conn.Write(cmdBytes) -} - -func (sws *ExecWsSession) ResizeTerminal(rows int, cols int) { - _, _ = sws.conn.Write([]byte(fmt.Sprintf("stty cols %d rows %d && clear \r", cols, rows))) -} diff --git a/backend/utils/terminal/local_cmd.go b/backend/utils/terminal/local_cmd.go index 2e041ebb6..2d3a77549 100644 --- a/backend/utils/terminal/local_cmd.go +++ b/backend/utils/terminal/local_cmd.go @@ -1,7 +1,6 @@ package terminal import ( - "fmt" "os" "os/exec" "syscall" @@ -27,8 +26,8 @@ type LocalCommand struct { ptyClosed chan struct{} } -func NewCommand(containerName string, auth string) (*LocalCommand, error) { - cmd := exec.Command("sh", "-c", fmt.Sprintf("docker exec -it %s redis-cli %s", containerName, auth)) +func NewCommand(commands string) (*LocalCommand, error) { + cmd := exec.Command("sh", "-c", commands) pty, err := pty.Start(cmd) if err != nil { diff --git a/backend/utils/terminal/ws_local_session.go b/backend/utils/terminal/ws_local_session.go index 136658ebc..0ecbabb7f 100644 --- a/backend/utils/terminal/ws_local_session.go +++ b/backend/utils/terminal/ws_local_session.go @@ -35,6 +35,7 @@ func (sws *LocalWsSession) Start(quitChan chan bool) { func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) { defer setQuit(exitCh) + defer global.LOG.Debug("thread of handle slave event has exited now") buffer := make([]byte, 1024) for { @@ -62,6 +63,7 @@ func (sws *LocalWsSession) masterWrite(data []byte) error { func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) { wsConn := sws.wsConn defer setQuit(exitCh) + defer global.LOG.Debug("thread of receive ws msg has exited now") for { select { case <-exitCh: diff --git a/backend/utils/terminal/ws_session.go b/backend/utils/terminal/ws_session.go index 74200f493..dde12a5dd 100644 --- a/backend/utils/terminal/ws_session.go +++ b/backend/utils/terminal/ws_session.go @@ -37,7 +37,6 @@ func (w *safeBuffer) Reset() { const ( wsMsgCmd = "cmd" wsMsgResize = "resize" - wsMsgClose = "close" ) type wsMsg struct { diff --git a/frontend/src/views/container/container/terminal/index.vue b/frontend/src/views/container/container/terminal/index.vue index c4ce5b45d..213ea6381 100644 --- a/frontend/src/views/container/container/terminal/index.vue +++ b/frontend/src/views/container/container/terminal/index.vue @@ -34,11 +34,11 @@ - + {{ $t('commons.button.conn') }} - {{ $t('commons.button.disconn') }} -
+ {{ $t('commons.button.disconn') }} +
@@ -180,13 +180,13 @@ const isWsOpen = () => { }; function handleClose() { - terminalVisiable.value = false; - terminalOpen.value = false; window.removeEventListener('resize', changeTerminalSize); if (isWsOpen()) { terminalSocket && terminalSocket.close(); term.dispose(); } + terminalVisiable.value = false; + terminalOpen.value = false; } function changeTerminalSize() { diff --git a/frontend/src/views/host/terminal/index.vue b/frontend/src/views/host/terminal/index.vue index 6891343e2..ead85b09e 100644 --- a/frontend/src/views/host/terminal/index.vue +++ b/frontend/src/views/host/terminal/index.vue @@ -60,7 +60,7 @@ onUnmounted(() => {