fix: Merge dev code up to pr #8141 (#8147)

This commit is contained in:
ssongliu 2025-03-14 10:08:31 +08:00 committed by GitHub
parent 4ff06401d9
commit 47d8c405dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 296 additions and 398 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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"`

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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" {

View file

@ -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")

View file

@ -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
}

View file

@ -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)

View file

@ -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
}
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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, "******"))

View file

@ -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,

View file

@ -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)
}
}()

View file

@ -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
}

View file

@ -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)
}
}()

View file

@ -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<string>(`/containers/load/log`, { type: type, name: name });
};
export const containerListStats = () => {
return http.get<Array<Container.ContainerListStats>>(`/containers/list/stats`);
};

View file

@ -16,7 +16,7 @@ export const operateFire = (operation: string) => {
export const operatePortRule = (params: Host.RulePort) => {
return http.post<Host.RulePort>(`/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<Host.RulePort>(`/hosts/firewall/forward`, params, TimeoutEnum.T_40S);
};
export const operateIPRule = (params: Host.RuleIP) => {

View file

@ -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',

View file

@ -840,7 +840,6 @@ const message = {
composeHelper:
'1パネルの編集者またはテンプレートを介して作成された構成は{0}/docker/composeディレクトリに保存されます',
deleteFile: 'ファイルを削除します',
allDelete: '永久に削除します',
deleteComposeHelper:
'構成ファイルや永続的なファイルを含むコンテナに関連するすべてのファイルを削除します注意して進めてください',
deleteCompose: 'この構成を削除します',

View file

@ -831,7 +831,6 @@ const message = {
composePathHelper: '구성 파일 저장 경로: {0}',
composeHelper: '1Panel 에디터나 템플릿을 통해 생성된 컴포지션은 {0}/docker/compose 디렉토리에 저장됩니다.',
deleteFile: '파일 삭제',
allDelete: '영구 삭제',
deleteComposeHelper:
' 작업은 컴포즈와 관련된 모든 파일을 삭제합니다. 구성을 포함한 지속적인 파일도 포함됩니다. 신중히 진행해 주세요!',
deleteCompose: '" 컴포즈를 삭제하시겠습니까?',

View file

@ -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.',

View file

@ -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.',

View file

@ -856,7 +856,6 @@ const message = {
composeHelper:
'Композиция, созданная через редактор 1Panel или шаблон, будет сохранена в директории {0}/docker/compose.',
deleteFile: 'Удалить файл',
allDelete: 'Удалить навсегда',
deleteComposeHelper:
'Удалить все файлы, связанные с compose контейнера, включая файлы конфигурации и постоянные файлы. Пожалуйста, действуйте с осторожностью!',
deleteCompose: '" Удалить эту композицию.',

View file

@ -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: '編排目錄',

View file

@ -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: '编排目录',

View file

@ -6,12 +6,7 @@
:resource="title"
:size="globalStore.isFullScreen ? 'full' : 'large'"
>
<el-alert type="error" :closable="false">
<template #title>
<span>{{ $t('commons.msg.disConn', ['/bye exit']) }}</span>
</template>
</el-alert>
<Terminal class="mt-2" style="height: calc(100vh - 225px)" ref="terminalRef"></Terminal>
<Terminal class="mt-2" style="height: calc(100vh - 175px)" ref="terminalRef"></Terminal>
<template #footer>
<span class="dialog-footer">
@ -47,8 +42,8 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
const initTerm = () => {
nextTick(() => {
terminalRef.value.acceptParams({
endpoint: '/api/v2/ai/ollama/exec',
args: `name=${itemName.value}`,
endpoint: '/api/v2/ai/containers/exec',
args: `source=ollama&name=${itemName.value}`,
error: '',
initCmd: '',
});

View file

@ -2,7 +2,7 @@
<DialogPro v-model="open" :title="$t('commons.button.delete') + ' - ' + composeName" size="small">
<el-form ref="deleteForm" v-loading="loading">
<el-form-item>
<el-checkbox v-model="deleteFile" :label="$t('container.allDelete')" />
<el-checkbox v-model="deleteFile" :label="$t('container.deleteFile')" />
<span class="input-help whitespace-break-spaces">
{{ $t('container.deleteComposeHelper') }}
</span>

View file

@ -92,7 +92,7 @@ import EditDialog from '@/views/container/compose/edit/index.vue';
import CreateDialog from '@/views/container/compose/create/index.vue';
import DeleteDialog from '@/views/container/compose/delete/index.vue';
import ComposeDetail from '@/views/container/compose/detail/index.vue';
import { loadContainerLog, searchCompose } from '@/api/modules/container';
import { inspect, searchCompose } from '@/api/modules/container';
import DockerStatus from '@/views/container/docker-status/index.vue';
import i18n from '@/lang';
import { Container } from '@/api/interface/container';
@ -179,7 +179,7 @@ const onDelete = async (row: Container.ComposeInfo) => {
const dialogEditRef = ref();
const onEdit = async (row: Container.ComposeInfo) => {
const res = await loadContainerLog('compose-detail', row.name);
const res = await inspect({ id: row.name, type: 'compose' });
let params = {
name: row.name,
path: row.path,

View file

@ -7,13 +7,7 @@
size="large"
>
<template #content>
<el-alert type="error" :closable="false">
<template #title>
<span>{{ $t('commons.msg.disConn', ['exit']) }}</span>
</template>
</el-alert>
<el-form ref="formRef" class="mt-2" :model="form" label-position="top">
<el-form ref="formRef" :model="form" label-position="top">
<el-form-item :label="$t('commons.table.user')" prop="user">
<el-input placeholder="root" clearable v-model="form.user" />
</el-form-item>
@ -49,7 +43,7 @@
</el-button>
<el-button v-else @click="onClose()">{{ $t('commons.button.disConn') }}</el-button>
<Terminal
style="height: calc(100vh - 355px); margin-top: 18px"
style="height: calc(100vh - 312px); margin-top: 18px"
ref="terminalRef"
v-if="terminalOpen"
></Terminal>
@ -102,7 +96,7 @@ const initTerm = (formEl: FormInstance | undefined) => {
await nextTick();
terminalRef.value!.acceptParams({
endpoint: '/api/v2/containers/exec',
args: `containerid=${form.containerID}&user=${form.user}&command=${form.command}`,
args: `source=container&containerid=${form.containerID}&user=${form.user}&command=${form.command}`,
error: '',
initCmd: '',
});

View file

@ -7,12 +7,7 @@
size="large"
>
<template #content>
<el-alert type="error" :closable="false">
<template #title>
<span>{{ $t('commons.msg.disConn', ['exit']) }}</span>
</template>
</el-alert>
<Terminal style="height: calc(100vh - 235px); margin-top: 18px" ref="terminalRef"></Terminal>
<Terminal style="height: calc(100vh - 175px); margin-top: 18px" ref="terminalRef"></Terminal>
</template>
<template #footer>
<span class="dialog-footer">

View file

@ -156,7 +156,7 @@ const buttons = [
},
},
{
label: i18n.global.t('commons.button.delete'),
label: i18n.global.t('commons.button.unbind'),
click: (row: Database.DatabaseInfo) => {
onDelete(row);
},

View file

@ -4,14 +4,15 @@ export const planOptions = [
title: '1-2GB',
data: {
version: '',
key_buffer_size: 128,
query_cache_size: 64,
tmp_table_size: 64,
innodb_buffer_pool_size: 256,
sort_buffer_size: 768,
read_buffer_size: 768,
read_rnd_buffer_size: 512,
join_buffer_size: 1024,
key_buffer_size: 32,
query_cache_size: 32,
tmp_table_size: 32,
innodb_buffer_pool_size: 64,
innodb_log_buffer_size: 64,
sort_buffer_size: 256,
read_buffer_size: 256,
read_rnd_buffer_size: 256,
join_buffer_size: 512,
thread_stack: 256,
binlog_cache_size: 64,
thread_cache_size: 64,
@ -24,14 +25,15 @@ export const planOptions = [
title: '2-4GB',
data: {
version: '',
key_buffer_size: 256,
query_cache_size: 128,
tmp_table_size: 384,
innodb_buffer_pool_size: 384,
sort_buffer_size: 768,
read_buffer_size: 768,
key_buffer_size: 64,
query_cache_size: 64,
tmp_table_size: 64,
innodb_buffer_pool_size: 128,
innodb_log_buffer_size: 64,
sort_buffer_size: 512,
read_buffer_size: 512,
read_rnd_buffer_size: 512,
join_buffer_size: 2048,
join_buffer_size: 1024,
thread_stack: 256,
binlog_cache_size: 64,
thread_cache_size: 96,
@ -44,10 +46,11 @@ export const planOptions = [
title: '4-8GB',
data: {
version: '',
key_buffer_size: 384,
query_cache_size: 192,
tmp_table_size: 512,
innodb_buffer_pool_size: 512,
key_buffer_size: 128,
query_cache_size: 128,
tmp_table_size: 128,
innodb_buffer_pool_size: 256,
innodb_log_buffer_size: 64,
sort_buffer_size: 1024,
read_buffer_size: 1024,
read_rnd_buffer_size: 768,
@ -64,14 +67,15 @@ export const planOptions = [
title: '8-16GB',
data: {
version: '',
key_buffer_size: 512,
key_buffer_size: 256,
query_cache_size: 256,
tmp_table_size: 1024,
innodb_buffer_pool_size: 1024,
sort_buffer_size: 2048,
tmp_table_size: 256,
innodb_buffer_pool_size: 512,
innodb_log_buffer_size: 64,
sort_buffer_size: 1024,
read_buffer_size: 2048,
read_rnd_buffer_size: 1024,
join_buffer_size: 4096,
join_buffer_size: 2048,
thread_stack: 384,
binlog_cache_size: 192,
thread_cache_size: 192,
@ -86,13 +90,13 @@ export const planOptions = [
version: '',
key_buffer_size: 1024,
query_cache_size: 384,
tmp_table_size: 2048,
innodb_buffer_pool_size: 4096,
innodb_log_buffer_size: 32,
tmp_table_size: 1024,
innodb_buffer_pool_size: 1024,
innodb_log_buffer_size: 64,
sort_buffer_size: 4096,
read_buffer_size: 4096,
read_rnd_buffer_size: 2048,
join_buffer_size: 8192,
join_buffer_size: 4096,
thread_stack: 512,
binlog_cache_size: 256,
thread_cache_size: 256,

View file

@ -1,7 +1,11 @@
<template>
<div v-loading="loading">
<LayoutContent backName="MySQL" :title="props.database + ' ' + $t('commons.button.set')">
<LayoutContent backName="MySQL">
<template #leftToolBar>
<el-text class="mx-1">
{{ props.database }}
</el-text>
<el-divider direction="vertical" />
<el-button type="primary" :plain="activeName !== 'conf'" @click="jumpToConf">
{{ $t('database.confChange') }}
</el-button>
@ -155,7 +159,6 @@ const mysqlName = ref();
const mysqlStatus = ref();
const mysqlVersion = ref();
const variables = ref();
const containerID = ref('');
interface DBProps {
type: string;
@ -175,11 +178,6 @@ const changeTab = (tab: string) => {
activeName.value = tab;
switch (tab) {
case 'log':
nextTick(() => {
loadContainerLog(baseInfo.containerID);
});
break;
case 'slowLog':
nextTick(() => {
loadSlowLogs();
@ -275,10 +273,6 @@ const onSaveConf = async () => {
return;
};
const loadContainerLog = async (conID: string) => {
containerID.value = conID;
};
const loadBaseInfo = async () => {
const res = await loadDBBaseInfo(props.type, props.database);
mysqlName.value = res.data?.name;

View file

@ -13,42 +13,29 @@
<div class="float-left">
<el-input type="number" v-model.number="variables.long_query_time" />
</div>
<el-button class="float-left ml-5" @click="changeSlowLogs">
<el-button class="float-left ml-2" @click="changeSlowLogs">
{{ $t('commons.button.save') }}
</el-button>
<div class="float-left ml-10">
<el-checkbox :disabled="!currentStatus" border v-model="isWatch">
{{ $t('commons.button.watch') }}
</el-checkbox>
</div>
<el-button :disabled="!currentStatus" class="ml-20" @click="onDownload" icon="Download">
{{ $t('commons.button.download') }}
</el-button>
</el-form-item>
</el-form>
<HighlightLog v-model="slowLogs" />
<LogFile v-if="variables.slow_query_log === 'ON'" :config="config" />
<ConfirmDialog @cancel="onCancel" ref="confirmDialogRef" @confirm="onSave"></ConfirmDialog>
</div>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { Database } from '@/api/interface/database';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { loadDBFile, updateMysqlVariables } from '@/api/modules/database';
import { dateFormatForName, downloadWithContent } from '@/utils/util';
import { updateMysqlVariables } from '@/api/modules/database';
import LogFile from '@/components/log/file/index.vue';
import i18n from '@/lang';
import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message';
import HighlightLog from '@/components/log/hightlight-log/index.vue';
import { MsgError, MsgSuccess } from '@/utils/message';
const slowLogs = ref();
const detailShow = ref();
const currentStatus = ref();
const config = ref();
const confirmDialogRef = ref();
const isWatch = ref();
let timer: NodeJS.Timer | null = null;
const variables = reactive({
slow_query_log: 'OFF',
long_query_time: 10,
@ -72,12 +59,11 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
if (variables.slow_query_log === 'ON') {
currentStatus.value = true;
detailShow.value = true;
loadMysqlSlowlogs();
timer = setInterval(() => {
if (variables.slow_query_log === 'ON' && isWatch.value) {
loadMysqlSlowlogs();
}
}, 1000 * 5);
config.value = {
type: params.type + '-slow-logs',
name: params.database,
tail: true,
};
} else {
detailShow.value = false;
}
@ -86,6 +72,11 @@ const emit = defineEmits(['loading']);
const handleSlowLogs = async () => {
if (variables.slow_query_log === 'ON') {
config.value = {
type: currentDB.type + '-slow-logs',
name: currentDB.database,
tail: true,
};
detailShow.value = true;
return;
}
@ -140,24 +131,6 @@ const onSave = async () => {
});
};
const onDownload = async () => {
if (!slowLogs.value) {
MsgInfo(i18n.global.t('database.noData'));
return;
}
downloadWithContent(slowLogs.value, currentDB.database + '-slowlogs-' + dateFormatForName(new Date()) + '.log');
};
const loadMysqlSlowlogs = async () => {
const res = await loadDBFile(currentDB.type + '-slow-logs', currentDB.database);
slowLogs.value = res.data || '';
};
onBeforeUnmount(() => {
clearInterval(Number(timer));
timer = null;
});
defineExpose({
acceptParams,
});

View file

@ -206,10 +206,12 @@ const emit = defineEmits(['loading']);
const changePlan = async () => {
for (const item of planOptions) {
if (item.id === plan.value) {
variableFormRef.value.resetFields();
mysqlVariables.key_buffer_size = item.data.key_buffer_size;
mysqlVariables.query_cache_size = item.data.query_cache_size;
mysqlVariables.tmp_table_size = item.data.tmp_table_size;
mysqlVariables.innodb_buffer_pool_size = item.data.innodb_buffer_pool_size;
mysqlVariables.innodb_log_buffer_size = item.data.innodb_log_buffer_size;
mysqlVariables.sort_buffer_size = item.data.sort_buffer_size;
mysqlVariables.read_buffer_size = item.data.read_buffer_size;

View file

@ -156,7 +156,7 @@ const buttons = [
},
},
{
label: i18n.global.t('commons.button.delete'),
label: i18n.global.t('commons.button.unbind'),
click: (row: Database.DatabaseInfo) => {
onDelete(row);
},

View file

@ -46,7 +46,7 @@
</el-row>
</el-form>
</div>
<ContainerLog v-if="activeName === 'log'" :container="containerID" :highlightDiff="350" />
<ContainerLog v-if="activeName === 'log'" :container="baseInfo.containerID" :highlightDiff="350" />
</template>
</LayoutContent>
@ -109,7 +109,6 @@ const props = withDefaults(defineProps<DBProps>(), {
database: '',
});
const containerID = ref('');
const jumpToConf = async () => {
activeName.value = 'conf';
loadPostgresqlConf();
@ -181,17 +180,12 @@ const onSaveConf = async () => {
return;
};
const loadContainerLog = async (conID: string) => {
containerID.value = conID;
};
const loadBaseInfo = async () => {
const res = await loadDBBaseInfo(props.type, props.database);
postgresqlName.value = res.data?.name;
baseInfo.port = res.data?.port;
baseInfo.containerID = res.data?.containerName;
loadPostgresqlConf();
loadContainerLog(baseInfo.containerID);
};
const loadPostgresqlConf = async () => {

View file

@ -297,8 +297,8 @@ const initTerminal = async () => {
terminalShow.value = true;
redisStatus.value = 'Running';
terminalRef.value.acceptParams({
endpoint: '/api/v2/databases/redis/exec',
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
endpoint: '/api/v2/containers/exec',
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '',
initCmd: '',
});
@ -315,8 +315,8 @@ const initTerminal = async () => {
if (res.data.status === 'Running') {
terminalShow.value = true;
terminalRef.value.acceptParams({
endpoint: '/api/v2/databases/redis/exec',
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
endpoint: '/api/v2/containers/exec',
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '',
initCmd: '',
});

View file

@ -75,7 +75,18 @@
</div>
</div>
<OpDialog ref="opRef" @search="search" />
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()">
<template #content>
<el-form class="mt-4 mb-1" ref="deleteForm" label-position="left">
<el-form-item>
<el-checkbox v-model="forceDelete" :label="$t('website.forceDelete')" />
<span class="input-help">
{{ $t('website.forceDeleteHelper') }}
</span>
</el-form-item>
</el-form>
</template>
</OpDialog>
<OperateDialog @search="search" ref="dialogRef" />
</div>
</template>
@ -89,6 +100,7 @@ import { operateForwardRule, searchFireRule } from '@/api/modules/host';
import { Host } from '@/api/interface/host';
import i18n from '@/lang';
import { GlobalStore } from '@/store';
import { MsgSuccess } from '@/utils/message';
const globalStore = GlobalStore();
const loading = ref();
@ -104,6 +116,8 @@ const fireName = ref();
const fireStatusRef = ref();
const opRef = ref();
const forceDelete = ref(false);
const operateRules = ref();
const data = ref();
const paginationConfig = reactive({
@ -177,6 +191,7 @@ const onDelete = async (row: Host.RuleForward | null) => {
});
}
}
operateRules.value = rules;
opRef.value.acceptParams({
title: i18n.global.t('commons.button.delete'),
names: names,
@ -184,10 +199,22 @@ const onDelete = async (row: Host.RuleForward | null) => {
i18n.global.t('firewall.forwardRule'),
i18n.global.t('commons.button.delete'),
]),
api: operateForwardRule,
params: { rules: rules },
api: null,
params: null,
});
};
const onSubmitDelete = async () => {
loading.value = true;
await operateForwardRule({ rules: operateRules.value, forceDelete: forceDelete.value })
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
};
const buttons = [
{
@ -205,6 +232,7 @@ const buttons = [
];
onMounted(() => {
forceDelete.value = false;
if (fireName.value !== '-') {
loading.value = true;
fireStatusRef.value.acceptParams();

View file

@ -133,7 +133,6 @@ const globalStore = GlobalStore();
const loading = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'backup-page-size',
currentPage: 1,
pageSize: 10,
total: 0,