mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-25 03:29:13 +08:00
fix: 解决容器终端连接失败的问题
This commit is contained in:
parent
95a5db141f
commit
b7324d14dc
@ -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 容器日志
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -37,7 +37,6 @@ func (w *safeBuffer) Reset() {
|
||||
const (
|
||||
wsMsgCmd = "cmd"
|
||||
wsMsgResize = "resize"
|
||||
wsMsgClose = "close"
|
||||
)
|
||||
|
||||
type wsMsg struct {
|
||||
|
@ -34,11 +34,11 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-button type="primary" v-if="!terminalOpen" @click="initTerm(formRef)">
|
||||
<el-button v-if="!terminalOpen" @click="initTerm(formRef)">
|
||||
{{ $t('commons.button.conn') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-else @click="handleClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||
<div style="height: calc(100vh - 290px)" :id="'terminal-exec'"></div>
|
||||
<el-button v-else @click="handleClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||
<div style="height: calc(100vh - 302px)" :id="'terminal-exec'"></div>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@ -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() {
|
||||
|
@ -60,7 +60,7 @@ onUnmounted(() => {
|
||||
<style lang="scss">
|
||||
.router_card {
|
||||
--el-card-border-radius: 8px;
|
||||
--el-card-padding: 0;
|
||||
--el-card-padding: 0 !important;
|
||||
padding: 0px;
|
||||
padding-bottom: 2px;
|
||||
padding-top: 2px;
|
||||
|
@ -154,7 +154,8 @@ const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
|
||||
if (res.data.name.length !== 0) {
|
||||
title = res.data.name + '-' + title;
|
||||
}
|
||||
emit('on-conn-terminal', title, res.data.id, false);
|
||||
let isLocal = hostInfo.addr === '127.0.0.1';
|
||||
emit('on-conn-terminal', title, res.data.id, isLocal);
|
||||
emit('load-host-tree');
|
||||
}
|
||||
});
|
||||
|
@ -6,6 +6,7 @@
|
||||
style="background-color: #efefef; margin-top: 20px"
|
||||
v-model="terminalValue"
|
||||
:before-leave="beforeLeave"
|
||||
@tab-change="quickCmd = ''"
|
||||
@edit="handleTabsRemove"
|
||||
>
|
||||
<el-tab-pane
|
||||
@ -305,19 +306,16 @@ const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('走到了这里');
|
||||
terminalTabs.value.push({
|
||||
index: tabIndex,
|
||||
title: title,
|
||||
wsID: wsID,
|
||||
status: res.data ? 'online' : 'closed',
|
||||
});
|
||||
console.log(terminalTabs.value);
|
||||
terminalValue.value = tabIndex;
|
||||
if (!res.data && isLocal) {
|
||||
dialogRef.value!.acceptParams({ isLocal: true });
|
||||
}
|
||||
console.log(terminalValue.value);
|
||||
nextTick(() => {
|
||||
ctx.refs[`t-${terminalValue.value}`] &&
|
||||
ctx.refs[`t-${terminalValue.value}`][0].acceptParams({
|
||||
|
Loading…
Reference in New Issue
Block a user