diff --git a/agent/app/api/v2/container.go b/agent/app/api/v2/container.go index bc70abdc1..aeb64d29e 100644 --- a/agent/app/api/v2/container.go +++ b/agent/app/api/v2/container.go @@ -343,24 +343,6 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) { helper.SuccessWithOutData(c) } -// @Tags Container -// @Summary Load container log -// @Accept json -// @Param request body dto.OperationWithNameAndType true "request" -// @Success 200 {string} content -// @Security ApiKeyAuth -// @Security Timestamp -// @Router /containers/load/log [post] -func (b *BaseApi) LoadContainerLog(c *gin.Context) { - var req dto.OperationWithNameAndType - if err := helper.CheckBindAndValidate(&req, c); err != nil { - return - } - - content := containerService.LoadContainerLogs(req) - helper.SuccessWithData(c, content) -} - // @Tags Container // @Summary Rename Container // @Accept json diff --git a/agent/app/api/v2/terminal.go b/agent/app/api/v2/terminal.go index 360861dfe..193c7353d 100644 --- a/agent/app/api/v2/terminal.go +++ b/agent/app/api/v2/terminal.go @@ -10,7 +10,6 @@ import ( "time" "github.com/1Panel-dev/1Panel/agent/app/dto" - "github.com/1Panel-dev/1Panel/agent/app/service" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/terminal" @@ -19,7 +18,7 @@ import ( "github.com/pkg/errors" ) -func (b *BaseApi) RedisWsSsh(c *gin.Context) { +func (b *BaseApi) ContainerWsSSH(c *gin.Context) { wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) if err != nil { global.LOG.Errorf("gin context http handler failed, err: %v", err) @@ -41,193 +40,105 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) { if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { return } + source := c.Query("source") + var containerID string + var initCmd string + switch source { + case "redis": + containerID, initCmd, err = loadRedisInitCmd(c) + case "ollama": + containerID, initCmd, err = loadOllamaInitCmd(c) + case "container": + containerID, initCmd, err = loadContainerInitCmd(c) + default: + if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) { + return + } + } + if wshandleError(wsConn, err) { + return + } + pidMap := loadMapFromDockerTop(containerID) + slave, err := terminal.NewCommand("clear && " + initCmd) + if wshandleError(wsConn, err) { + return + } + defer killBash(containerID, strings.ReplaceAll(initCmd, fmt.Sprintf("docker exec -it %s ", containerID), ""), pidMap) + defer slave.Close() + + tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false) + 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 loadRedisInitCmd(c *gin.Context) (string, string, error) { name := c.Query("name") from := c.Query("from") - commands := []string{"redis-cli"} + commands := "redis-cli" database, err := databaseService.Get(name) - if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) { - return + if err != nil { + return "", "", fmt.Errorf("no such database in db, err: %v", err) } if from == "local" { redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"}) - if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) { - return + if err != nil { + return "", "", fmt.Errorf("no such app in db, err: %v", err) } name = redisInfo.ContainerName if len(database.Password) != 0 { - commands = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"} + commands = "redis-cli -a " + database.Password + " --no-auth-warning" } } else { - itemPort := fmt.Sprintf("%v", database.Port) - commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort} + commands = fmt.Sprintf("redis-cli -h %s -p %v", database.Address, database.Port) if len(database.Password) != 0 { - commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort, "-a", database.Password, "--no-auth-warning"} + commands = fmt.Sprintf("redis-cli -h %s -p %v -a %s --no-auth-warning", database.Address, database.Port, database.Password) } name = "1Panel-redis-cli-tools" } - - pidMap := loadMapFromDockerTop(name) - itemCmds := append([]string{"exec", "-it", name}, commands...) - slave, err := terminal.NewCommand(itemCmds) - if wshandleError(wsConn, err) { - return - } - defer killBash(name, strings.Join(commands, " "), pidMap) - defer slave.Close() - - tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false) - 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 - } + return name, fmt.Sprintf("docker exec -it %s %s", name, commands), nil } -func (b *BaseApi) ContainerWsSsh(c *gin.Context) { - wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) +func loadOllamaInitCmd(c *gin.Context) (string, string, error) { + name := c.Query("name") + if cmd.CheckIllegal(name) { + return "", "", fmt.Errorf("ollama model %s contains illegal characters", name) + } + ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"}) if err != nil { - global.LOG.Errorf("gin context http handler failed, err: %v", err) - return - } - defer wsConn.Close() - - if global.CONF.Base.IsDemo { - if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { - return - } + return "", "", fmt.Errorf("no such app in db, err: %v", err) } + containerName := ollamaInfo.ContainerName + return containerName, fmt.Sprintf("docker exec -it %s ollama run %s", containerName, name), nil +} +func loadContainerInitCmd(c *gin.Context) (string, string, error) { containerID := c.Query("containerid") command := c.Query("command") user := c.Query("user") - if len(command) == 0 || len(containerID) == 0 { - if wshandleError(wsConn, errors.New("error param of command or containerID")) { - return - } - } - cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) - if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { - return - } - rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) - if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { - return - } - - cmds := []string{"exec", containerID, command} - if len(user) != 0 { - cmds = []string{"exec", "-u", user, containerID, command} - } if cmd.CheckIllegal(user, containerID, command) { - if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) { - return - } + return "", "", fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID) } - stdout, err := cmd.ExecWithCheck("docker", cmds...) - if wshandleError(wsConn, errors.WithMessage(err, stdout)) { - return + if len(command) == 0 || len(containerID) == 0 { + return "", "", fmt.Errorf("error param of command: %s or containerID: %s", command, containerID) } - - commands := []string{"exec", "-it", containerID, command} + command = fmt.Sprintf("docker exec -it %s %s", containerID, command) if len(user) != 0 { - commands = []string{"exec", "-it", "-u", user, containerID, command} - } - pidMap := loadMapFromDockerTop(containerID) - slave, err := terminal.NewCommand(commands) - if wshandleError(wsConn, err) { - return - } - defer killBash(containerID, command, pidMap) - defer slave.Close() - - tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true) - if wshandleError(wsConn, err) { - return + command = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command) } - 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) OllamaWsSsh(c *gin.Context) { - 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() - - if global.CONF.Base.IsDemo { - if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { - return - } - } - - cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) - if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { - return - } - rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) - if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { - return - } - name := c.Query("name") - if cmd.CheckIllegal(name) { - if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) { - return - } - } - container, err := service.LoadContainerName() - if wshandleError(wsConn, errors.WithMessage(err, " load container name for ollama failed")) { - return - } - commands := []string{"ollama", "run", name} - - pidMap := loadMapFromDockerTop(container) - fmt.Println("pidMap") - for k, v := range pidMap { - fmt.Println(k, v) - } - itemCmds := append([]string{"exec", "-it", container}, commands...) - slave, err := terminal.NewCommand(itemCmds) - if wshandleError(wsConn, err) { - return - } - defer killBash(container, strings.Join(commands, " "), pidMap) - defer slave.Close() - - tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false) - 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 - } + return containerID, command, nil } func wshandleError(ws *websocket.Conn, err error) bool { diff --git a/agent/app/dto/firewall.go b/agent/app/dto/firewall.go index ab1d446ee..9d8830ddc 100644 --- a/agent/app/dto/firewall.go +++ b/agent/app/dto/firewall.go @@ -31,7 +31,8 @@ type PortRuleOperate struct { } type ForwardRuleOperate struct { - Rules []struct { + ForceDelete bool `json:"forceDelete"` + Rules []struct { Operation string `json:"operation" validate:"required,oneof=add remove"` Num string `json:"num"` Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"` diff --git a/agent/app/service/backup_mysql.go b/agent/app/service/backup_mysql.go index c01734431..1dc0f65f1 100644 --- a/agent/app/service/backup_mysql.go +++ b/agent/app/service/backup_mysql.go @@ -207,8 +207,9 @@ func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback Timeout: 300, }); err != nil { global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err) + } else { + global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile) } - global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile) _ = os.RemoveAll(rollbackFile) } else { _ = os.RemoveAll(rollbackFile) @@ -224,6 +225,7 @@ func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback Timeout: 300, }); err != nil { + global.LOG.Errorf("recover mysql db %s from %s failed, err: %v", req.DetailName, req.File, err) return err } isOk = true diff --git a/agent/app/service/backup_postgresql.go b/agent/app/service/backup_postgresql.go index bc7b0f4ff..1138aa3db 100644 --- a/agent/app/service/backup_postgresql.go +++ b/agent/app/service/backup_postgresql.go @@ -190,8 +190,9 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol Timeout: 300, }); err != nil { global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err) + } else { + global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile) } - global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile) _ = os.RemoveAll(rollbackFile) } else { _ = os.RemoveAll(rollbackFile) @@ -204,6 +205,7 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol Username: dbInfo.Username, Timeout: 300, }); err != nil { + global.LOG.Errorf("recover postgresql db %s from %s failed, err: %v", req.DetailName, req.File, err) return err } isOk = true diff --git a/agent/app/service/container.go b/agent/app/service/container.go index c4fc57de2..3f1f69f3f 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -83,8 +83,6 @@ type IContainerService interface { ComposeUpdate(req dto.ComposeUpdate) error Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) - LoadContainerLogs(req dto.OperationWithNameAndType) string - StreamLogs(ctx *gin.Context, params dto.StreamLog) } @@ -359,6 +357,43 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { switch req.Type { case "container": inspectInfo, err = client.ContainerInspect(context.Background(), req.ID) + case "compose": + filePath := "" + cli, err := docker.NewDockerClient() + if err != nil { + return "", err + } + defer cli.Close() + options := container.ListOptions{All: true} + options.Filters = filters.NewArgs() + options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.ID)) + containers, err := cli.ContainerList(context.Background(), options) + if err != nil { + return "", err + } + for _, container := range containers { + config := container.Labels[composeConfigLabel] + workdir := container.Labels[composeWorkdirLabel] + if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) { + filePath = config + break + } else { + filePath = workdir + break + } + } + if len(containers) == 0 { + composeItem, _ := composeRepo.GetRecord(repo.WithByName(req.ID)) + filePath = composeItem.Path + } + if _, err := os.Stat(filePath); err != nil { + return "", err + } + content, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + return string(content), nil case "image": inspectInfo, _, err = client.ImageInspectWithRaw(context.Background(), req.ID) case "network": @@ -1022,47 +1057,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error return &data, nil } -func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) string { - filePath := "" - if req.Type == "compose-detail" { - cli, err := docker.NewDockerClient() - if err != nil { - return "" - } - defer cli.Close() - options := container.ListOptions{All: true} - options.Filters = filters.NewArgs() - options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name)) - containers, err := cli.ContainerList(context.Background(), options) - if err != nil { - return "" - } - for _, container := range containers { - config := container.Labels[composeConfigLabel] - workdir := container.Labels[composeWorkdirLabel] - if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) { - filePath = config - break - } else { - filePath = workdir - break - } - } - if len(containers) == 0 { - composeItem, _ := composeRepo.GetRecord(repo.WithByName(req.Name)) - filePath = composeItem.Path - } - } - if _, err := os.Stat(filePath); err != nil { - return "" - } - content, err := os.ReadFile(filePath) - if err != nil { - return "" - } - return string(content) -} - func stringsToMap(list []string) map[string]string { var labelMap = make(map[string]string) for _, label := range list { @@ -1089,16 +1083,22 @@ func calculateCPUPercentUnix(stats *container.StatsResponse) float64 { } func calculateMemPercentUnix(memStats container.MemoryStats) float64 { memPercent := 0.0 - memUsage := float64(memStats.Usage) - if memStats.Stats["inactive_file"] > 0 { - memUsage = memUsage - float64(memStats.Stats["inactive_file"]) - } + memUsage := calculateMemUsageUnixNoCache(memStats) memLimit := float64(memStats.Limit) if memUsage > 0.0 && memLimit > 0.0 { - memPercent = (memUsage / memLimit) * 100.0 + memPercent = (float64(memUsage) / float64(memLimit)) * 100.0 } return memPercent } +func calculateMemUsageUnixNoCache(mem container.MemoryStats) uint64 { + if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage { + return mem.Usage - v + } + if v := mem.Stats["inactive_file"]; v < mem.Usage { + return mem.Usage - v + } + return mem.Usage +} func calculateBlockIO(blkio container.BlkioStats) (blkRead float64, blkWrite float64) { for _, bioEntry := range blkio.IoServiceBytesRecursive { switch strings.ToLower(bioEntry.Op) { @@ -1195,8 +1195,8 @@ func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerLis data.CPUPercent = calculateCPUPercentUnix(stats) data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage) - data.MemoryCache = stats.MemoryStats.Stats["inactive_file"] - data.MemoryUsage = stats.MemoryStats.Usage - data.MemoryCache + data.MemoryCache = stats.MemoryStats.Stats["cache"] + data.MemoryUsage = calculateMemUsageUnixNoCache(stats.MemoryStats) data.MemoryLimit = stats.MemoryStats.Limit data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats) @@ -1319,9 +1319,10 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types for _, volume := range req.Volumes { if volume.Type == "volume" { hostConf.Mounts = append(hostConf.Mounts, mount.Mount{ - Type: mount.Type(volume.Type), - Source: volume.SourceDir, - Target: volume.ContainerDir, + Type: mount.Type(volume.Type), + Source: volume.SourceDir, + Target: volume.ContainerDir, + ReadOnly: volume.Mode == "ro", }) config.Volumes[volume.ContainerDir] = struct{}{} } else { diff --git a/agent/app/service/container_compose.go b/agent/app/service/container_compose.go index 19e4d1b93..a5d310d0f 100644 --- a/agent/app/service/container_compose.go +++ b/agent/app/service/container_compose.go @@ -233,16 +233,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error { return errors.New(string(stdout)) } if req.WithFile { - _ = composeRepo.DeleteRecord(repo.WithByName(req.Name)) _ = os.RemoveAll(path.Dir(req.Path)) - } else { - composeItem, _ := composeRepo.GetRecord(repo.WithByName(req.Name)) - if composeItem.Path == "" { - upMap := make(map[string]interface{}) - upMap["path"] = req.Path - _ = composeRepo.UpdateRecord(req.Name, upMap) - } } + _ = composeRepo.DeleteRecord(repo.WithByName(req.Name)) return nil } if req.Operation == "up" { diff --git a/agent/app/service/database_common.go b/agent/app/service/database_common.go index f7cce94e6..f16340fff 100644 --- a/agent/app/service/database_common.go +++ b/agent/app/service/database_common.go @@ -50,10 +50,6 @@ func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (st filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name)) case "redis-conf": filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name)) - case "mysql-slow-logs": - filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name)) - case "mariadb-slow-logs": - filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name)) } if _, err := os.Stat(filePath); err != nil { return "", buserr.New("ErrHttpReqNotFound") diff --git a/agent/app/service/database_mysql.go b/agent/app/service/database_mysql.go index 5897cbaec..987217228 100644 --- a/agent/app/service/database_mysql.go +++ b/agent/app/service/database_mysql.go @@ -533,7 +533,11 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS info.File = "OFF" info.Position = "OFF" - rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, "show master status;") + masterStatus := "show master status;" + if common.CompareAppVersion(app.Version, "8.4.0") { + masterStatus = "show binary log status;" + } + rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, masterStatus) if err != nil { return nil, err } diff --git a/agent/app/service/file.go b/agent/app/service/file.go index e4de757c1..bc2b99b10 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -481,6 +481,10 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi return nil, err } logFilePath = taskModel.LogFile + case "mysql-slow-logs": + logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name)) + case "mariadb-slow-logs": + logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name)) } lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest) diff --git a/agent/app/service/firewall.go b/agent/app/service/firewall.go index b3975c6d0..7b090b9b8 100644 --- a/agent/app/service/firewall.go +++ b/agent/app/service/firewall.go @@ -378,6 +378,10 @@ func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error { TargetIP: r.TargetIP, TargetPort: r.TargetPort, }, r.Operation); err != nil { + if req.ForceDelete { + global.LOG.Error(err) + continue + } return err } } diff --git a/agent/router/ai.go b/agent/router/ai.go index 2ade7abc0..703739294 100644 --- a/agent/router/ai.go +++ b/agent/router/ai.go @@ -13,7 +13,6 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { baseApi := v1.ApiGroupApp.BaseApi { - aiToolsRouter.GET("/ollama/exec", baseApi.OllamaWsSsh) aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel) aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel) aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel) diff --git a/agent/router/ro_container.go b/agent/router/ro_container.go index abd4269ca..dbd4f9e60 100644 --- a/agent/router/ro_container.go +++ b/agent/router/ro_container.go @@ -11,7 +11,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) { baRouter := Router.Group("containers") baseApi := v2.ApiGroupApp.BaseApi { - baRouter.GET("/exec", baseApi.ContainerWsSsh) + baRouter.GET("/exec", baseApi.ContainerWsSSH) baRouter.GET("/stats/:id", baseApi.ContainerStats) baRouter.POST("", baseApi.ContainerCreate) @@ -27,7 +27,6 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) { baRouter.POST("/download/log", baseApi.DownloadContainerLogs) baRouter.GET("/limit", baseApi.LoadResourceLimit) baRouter.POST("/clean/log", baseApi.CleanContainerLog) - baRouter.POST("/load/log", baseApi.LoadContainerLog) baRouter.POST("/inspect", baseApi.Inspect) baRouter.POST("/rename", baseApi.ContainerRename) baRouter.POST("/commit", baseApi.ContainerCommit) diff --git a/agent/router/ro_database.go b/agent/router/ro_database.go index 8141a945c..5ef31bfcc 100644 --- a/agent/router/ro_database.go +++ b/agent/router/ro_database.go @@ -33,7 +33,6 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) { cmdRouter.POST("/redis/persistence/conf", baseApi.LoadPersistenceConf) cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus) cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf) - cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh) cmdRouter.GET("/redis/check", baseApi.CheckHasCli) cmdRouter.POST("/redis/install/cli", baseApi.InstallCli) cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword) diff --git a/agent/utils/mysql/client/local.go b/agent/utils/mysql/client/local.go index 3736c43b1..268500713 100644 --- a/agent/utils/mysql/client/local.go +++ b/agent/utils/mysql/client/local.go @@ -234,7 +234,7 @@ func (r *Local) Backup(info BackupInfo) error { dumpCmd = "mariadb-dump" } global.LOG.Infof("start to %s | gzip > %s.gzip", dumpCmd, info.TargetDir+"/"+info.FileName) - cmd := exec.Command("docker", "exec", r.ContainerName, dumpCmd, "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name) + cmd := exec.Command("docker", "exec", r.ContainerName, dumpCmd, "--routines", "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name) var stderr bytes.Buffer cmd.Stderr = &stderr diff --git a/agent/utils/mysql/client/remote.go b/agent/utils/mysql/client/remote.go index 91bd0323f..016601bd9 100644 --- a/agent/utils/mysql/client/remote.go +++ b/agent/utils/mysql/client/remote.go @@ -249,7 +249,7 @@ func (r *Remote) Backup(info BackupInfo) error { if err != nil { return err } - backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'", + backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s --routines -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'", image, dumpCmd, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version, r.Type), info.Format, info.Name) global.LOG.Debug(strings.ReplaceAll(backupCmd, r.Password, "******")) diff --git a/agent/utils/terminal/local_cmd.go b/agent/utils/terminal/local_cmd.go index e1beb36bb..20c5c07c9 100644 --- a/agent/utils/terminal/local_cmd.go +++ b/agent/utils/terminal/local_cmd.go @@ -25,13 +25,22 @@ type LocalCommand struct { pty *os.File } -func NewCommand(commands []string) (*LocalCommand, error) { - cmd := exec.Command("docker", commands...) +func NewCommand(initCmd string) (*LocalCommand, error) { + cmd := exec.Command("bash") + if term := os.Getenv("TERM"); term != "" { + cmd.Env = append(os.Environ(), "TERM="+term) + } else { + cmd.Env = append(os.Environ(), "TERM=xterm") + } pty, err := pty.Start(cmd) if err != nil { return nil, errors.Wrapf(err, "failed to start command") } + if len(initCmd) != 0 { + time.Sleep(100 * time.Millisecond) + _, _ = pty.Write([]byte(initCmd + "\n")) + } lcmd := &LocalCommand{ closeSignal: DefaultCloseSignal, diff --git a/agent/utils/terminal/ws_local_session.go b/agent/utils/terminal/ws_local_session.go index b59024ef1..2669cc232 100644 --- a/agent/utils/terminal/ws_local_session.go +++ b/agent/utils/terminal/ws_local_session.go @@ -53,6 +53,11 @@ func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) { } func (sws *LocalWsSession) masterWrite(data []byte) error { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("A panic occurred during write ws message to master, error message: %v", r) + } + }() sws.writeMutex.Lock() defer sws.writeMutex.Unlock() wsData, err := json.Marshal(WsMsg{ @@ -72,6 +77,7 @@ func (sws *LocalWsSession) masterWrite(data []byte) error { func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) { defer func() { if r := recover(); r != nil { + setQuit(exitCh) global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r) } }() diff --git a/core/app/api/v2/setting.go b/core/app/api/v2/setting.go index b71f2daf4..e1ff0f952 100644 --- a/core/app/api/v2/setting.go +++ b/core/app/api/v2/setting.go @@ -3,8 +3,11 @@ package v2 import ( "encoding/base64" "errors" + "fmt" + "net/http" "os" "path" + "regexp" "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" "github.com/1Panel-dev/1Panel/core/app/dto" @@ -74,6 +77,10 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) { } if req.Key == "SecurityEntrance" { entranceValue := base64.StdEncoding.EncodeToString([]byte(req.Value)) + if !checkEntrancePattern(entranceValue) { + helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", fmt.Errorf("the format of the security entrance %s is incorrect.", entranceValue)) + return + } c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true) } helper.SuccessWithOutData(c) @@ -434,3 +441,11 @@ func (b *BaseApi) UpdateApiConfig(c *gin.Context) { } helper.SuccessWithOutData(c) } + +func checkEntrancePattern(val string) bool { + if len(val) == 0 { + return true + } + result, _ := regexp.MatchString("^[a-zA-Z0-9]{5,116}$", val) + return result +} diff --git a/core/utils/terminal/ws_local_session.go b/core/utils/terminal/ws_local_session.go index 2fd14e0ce..313537504 100644 --- a/core/utils/terminal/ws_local_session.go +++ b/core/utils/terminal/ws_local_session.go @@ -53,6 +53,11 @@ func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) { } func (sws *LocalWsSession) masterWrite(data []byte) error { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("A panic occurred during write ws message to master, error message: %v", r) + } + }() sws.writeMutex.Lock() defer sws.writeMutex.Unlock() wsData, err := json.Marshal(WsMsg{ @@ -72,6 +77,7 @@ func (sws *LocalWsSession) masterWrite(data []byte) error { func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) { defer func() { if r := recover(); r != nil { + setQuit(exitCh) global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r) } }() diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index 56c1ae9ea..9c1f2d0d4 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -36,9 +36,6 @@ export const loadContainerInfo = (name: string) => { export const cleanContainerLog = (containerName: string) => { return http.post(`/containers/clean/log`, { name: containerName }); }; -export const loadContainerLog = (type: string, name: string) => { - return http.post(`/containers/load/log`, { type: type, name: name }); -}; export const containerListStats = () => { return http.get>(`/containers/list/stats`); }; diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index 901d26ffa..a06276185 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -16,7 +16,7 @@ export const operateFire = (operation: string) => { export const operatePortRule = (params: Host.RulePort) => { return http.post(`/hosts/firewall/port`, params, TimeoutEnum.T_40S); }; -export const operateForwardRule = (params: { rules: Host.RuleForward[] }) => { +export const operateForwardRule = (params: { rules: Host.RuleForward[]; forceDelete: boolean }) => { return http.post(`/hosts/firewall/forward`, params, TimeoutEnum.T_40S); }; export const operateIPRule = (params: Host.RuleIP) => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 4a766a738..29e902d4d 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -864,9 +864,8 @@ const message = { composeHelper: 'The composition created through 1Panel editor or template will be saved in the {0}/docker/compose directory.', deleteFile: 'Delete file', - allDelete: 'Permanently Delete', deleteComposeHelper: - '1. Delete container orchestration records \n2. Delete all container orchestration files, including configuration and persistent files', + 'Delete all files related to container compose, including configuration files and persistent files. Please proceed with caution!', deleteCompose: '"Delete this composition"', createCompose: 'Create', composeDirectory: 'Compose Directory', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 0f64b687f..1933802fb 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -840,7 +840,6 @@ const message = { composeHelper: '1パネルの編集者またはテンプレートを介して作成された構成は、{0}/docker/composeディレクトリに保存されます。', deleteFile: 'ファイルを削除します', - allDelete: '永久に削除します', deleteComposeHelper: '構成ファイルや永続的なファイルを含む、コンテナに関連するすべてのファイルを削除します。注意して進めてください!', deleteCompose: 'この構成を削除します。', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index b5a183cb2..047c6eea1 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -831,7 +831,6 @@ const message = { composePathHelper: '구성 파일 저장 경로: {0}', composeHelper: '1Panel 에디터나 템플릿을 통해 생성된 컴포지션은 {0}/docker/compose 디렉토리에 저장됩니다.', deleteFile: '파일 삭제', - allDelete: '영구 삭제', deleteComposeHelper: '이 작업은 컴포즈와 관련된 모든 파일을 삭제합니다. 구성을 포함한 지속적인 파일도 포함됩니다. 신중히 진행해 주세요!', deleteCompose: '" 이 컴포즈를 삭제하시겠습니까?', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 83fc5a5b6..70e90624e 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -859,7 +859,6 @@ const message = { composeHelper: 'Komposisi yang dicipta melalui editor atau templat 1Panel akan disimpan dalam direktori {0}/docker/compose.', deleteFile: 'Padam fail', - allDelete: 'Padam secara Kekal', deleteComposeHelper: 'Padam semua fail berkaitan komposisi kontena, termasuk fail konfigurasi dan fail berterusan. Sila berhati-hati!', deleteCompose: 'Padam komposisi ini.', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 13dc05bc4..6bda32961 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -853,7 +853,6 @@ const message = { composeHelper: 'A composição criada através do editor ou template do 1Panel será salva no diretório {0}/docker/compose.', deleteFile: 'Excluir arquivo', - allDelete: 'Excluir permanentemente', deleteComposeHelper: 'Excluir todos os arquivos relacionados à composição do container, incluindo arquivos de configuração e arquivos persistentes. Prossiga com cautela!', deleteCompose: 'Excluir esta composição.', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index db3a53826..ddff600cd 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -856,7 +856,6 @@ const message = { composeHelper: 'Композиция, созданная через редактор 1Panel или шаблон, будет сохранена в директории {0}/docker/compose.', deleteFile: 'Удалить файл', - allDelete: 'Удалить навсегда', deleteComposeHelper: 'Удалить все файлы, связанные с compose контейнера, включая файлы конфигурации и постоянные файлы. Пожалуйста, действуйте с осторожностью!', deleteCompose: '" Удалить эту композицию.', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index f1be40bb2..38a7500e8 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -131,7 +131,6 @@ const message = { }, msg: { noneData: '暫無數據', - disConn: '請直接點選斷開按鈕斷開終端連接,避免使用 {0} 等退出指令。', delete: '刪除 操作不可回滾,是否繼續?', clean: '清空 操作不可回滾,是否繼續?', deleteSuccess: '刪除成功', @@ -828,8 +827,7 @@ const message = { composePathHelper: '配置文件保存路徑: {0}', composeHelper: '通過 1Panel 編輯或者模版創建的編排,將保存在 {0}/docker/compose 路徑下', deleteFile: '刪除文件', - allDelete: '徹底刪除', - deleteComposeHelper: '1. 刪除容器編排記錄 \n2. 刪除容器編排的所有文件,包括配置文件和持久化文件', + deleteComposeHelper: '刪除容器編排的所有檔案,包括配置文件和持久化文件,請謹慎操作!', deleteCompose: '" 刪除此編排', createCompose: '創建編排', composeDirectory: '編排目錄', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 9f1e6898a..0360c302f 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -130,7 +130,6 @@ const message = { Rollbacking: '快照回滚中,请稍候...', }, msg: { - disConn: '请直接点击断开按钮断开终端连接,避免使用 {0} 等退出命令', noneData: '暂无数据', delete: '删除 操作不可回滚,是否继续?', clean: '清空 操作不可回滚,是否继续?', @@ -826,8 +825,7 @@ const message = { composePathHelper: '配置文件保存路径: {0}', composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下', deleteFile: '删除文件', - allDelete: '彻底删除', - deleteComposeHelper: '1. 删除容器编排记录 \n2. 删除容器编排的所有文件,包括配置文件和持久化文件', + deleteComposeHelper: '删除容器编排的所有文件,包括配置文件和持久化文件,请谨慎操作!', deleteCompose: '" 删除此编排', createCompose: '创建编排', composeDirectory: '编排目录', diff --git a/frontend/src/views/ai/model/terminal/index.vue b/frontend/src/views/ai/model/terminal/index.vue index 6029004e9..7441684cb 100644 --- a/frontend/src/views/ai/model/terminal/index.vue +++ b/frontend/src/views/ai/model/terminal/index.vue @@ -6,12 +6,7 @@ :resource="title" :size="globalStore.isFullScreen ? 'full' : 'large'" > - - - - +