mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-06 05:24:33 +08:00
parent
4ff06401d9
commit
47d8c405dd
45 changed files with 296 additions and 398 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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, "******"))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -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`);
|
||||
};
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -840,7 +840,6 @@ const message = {
|
|||
composeHelper:
|
||||
'1パネルの編集者またはテンプレートを介して作成された構成は、{0}/docker/composeディレクトリに保存されます。',
|
||||
deleteFile: 'ファイルを削除します',
|
||||
allDelete: '永久に削除します',
|
||||
deleteComposeHelper:
|
||||
'構成ファイルや永続的なファイルを含む、コンテナに関連するすべてのファイルを削除します。注意して進めてください!',
|
||||
deleteCompose: 'この構成を削除します。',
|
||||
|
|
|
@ -831,7 +831,6 @@ const message = {
|
|||
composePathHelper: '구성 파일 저장 경로: {0}',
|
||||
composeHelper: '1Panel 에디터나 템플릿을 통해 생성된 컴포지션은 {0}/docker/compose 디렉토리에 저장됩니다.',
|
||||
deleteFile: '파일 삭제',
|
||||
allDelete: '영구 삭제',
|
||||
deleteComposeHelper:
|
||||
'이 작업은 컴포즈와 관련된 모든 파일을 삭제합니다. 구성을 포함한 지속적인 파일도 포함됩니다. 신중히 진행해 주세요!',
|
||||
deleteCompose: '" 이 컴포즈를 삭제하시겠습니까?',
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -856,7 +856,6 @@ const message = {
|
|||
composeHelper:
|
||||
'Композиция, созданная через редактор 1Panel или шаблон, будет сохранена в директории {0}/docker/compose.',
|
||||
deleteFile: 'Удалить файл',
|
||||
allDelete: 'Удалить навсегда',
|
||||
deleteComposeHelper:
|
||||
'Удалить все файлы, связанные с compose контейнера, включая файлы конфигурации и постоянные файлы. Пожалуйста, действуйте с осторожностью!',
|
||||
deleteCompose: '" Удалить эту композицию.',
|
||||
|
|
|
@ -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: '編排目錄',
|
||||
|
|
|
@ -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: '编排目录',
|
||||
|
|
|
@ -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: '',
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: '',
|
||||
});
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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: '',
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue