1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-08 01:20:07 +08:00

feat: 数据库支持多 Redis 切换 (#5001)

Refs #3927
This commit is contained in:
ssongliu 2024-05-13 22:40:31 +08:00 committed by GitHub
parent 188d9dd4c7
commit c56435970a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 689 additions and 306 deletions

View File

@ -343,7 +343,7 @@ func (b *BaseApi) Backup(c *gin.Context) {
return return
} }
case "redis": case "redis":
if err := backupService.RedisBackup(); err != nil { if err := backupService.RedisBackup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }

View File

@ -12,11 +12,17 @@ import (
// @Tags Database Redis // @Tags Database Redis
// @Summary Load redis status info // @Summary Load redis status info
// @Description 获取 redis 状态信息 // @Description 获取 redis 状态信息
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.RedisStatus // @Success 200 {object} dto.RedisStatus
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/redis/status [get] // @Router /databases/redis/status [get]
func (b *BaseApi) LoadRedisStatus(c *gin.Context) { func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
data, err := redisService.LoadStatus() var req dto.OperationWithName
if err := helper.CheckBind(&req, c); err != nil {
return
}
data, err := redisService.LoadStatus(req)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@ -28,11 +34,17 @@ func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
// @Tags Database Redis // @Tags Database Redis
// @Summary Load redis conf // @Summary Load redis conf
// @Description 获取 redis 配置信息 // @Description 获取 redis 配置信息
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.RedisConf // @Success 200 {object} dto.RedisConf
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/redis/conf [get] // @Router /databases/redis/conf [get]
func (b *BaseApi) LoadRedisConf(c *gin.Context) { func (b *BaseApi) LoadRedisConf(c *gin.Context) {
data, err := redisService.LoadConf() var req dto.OperationWithName
if err := helper.CheckBind(&req, c); err != nil {
return
}
data, err := redisService.LoadConf(req)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@ -44,11 +56,17 @@ func (b *BaseApi) LoadRedisConf(c *gin.Context) {
// @Tags Database Redis // @Tags Database Redis
// @Summary Load redis persistence conf // @Summary Load redis persistence conf
// @Description 获取 redis 持久化配置 // @Description 获取 redis 持久化配置
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.RedisPersistence // @Success 200 {object} dto.RedisPersistence
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/redis/persistence/conf [get] // @Router /databases/redis/persistence/conf [get]
func (b *BaseApi) LoadPersistenceConf(c *gin.Context) { func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
data, err := redisService.LoadPersistenceConf() var req dto.OperationWithName
if err := helper.CheckBind(&req, c); err != nil {
return
}
data, err := redisService.LoadPersistenceConf(req)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@ -131,29 +149,3 @@ func (b *BaseApi) UpdateRedisPersistenceConf(c *gin.Context) {
} }
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Database Redis
// @Summary Page redis backups
// @Description 获取 redis 备份记录分页
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /databases/redis/backup/search [post]
func (b *BaseApi) RedisBackupList(c *gin.Context) {
var req dto.PageInfo
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := redisService.SearchBackupListWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier" "github.com/1Panel-dev/1Panel/backend/utils/copier"
@ -86,23 +87,24 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return return
} }
redisConf, err := redisService.LoadConf() name := c.Query("name")
if wshandleError(wsConn, errors.WithMessage(err, "load redis container failed")) { redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Type: "redis", Name: name})
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return return
} }
defer wsConn.Close() defer wsConn.Close()
commands := []string{"redis-cli"} commands := []string{"redis-cli"}
if len(redisConf.Requirepass) != 0 { if len(redisInfo.Password) != 0 {
commands = []string{"redis-cli", "-a", redisConf.Requirepass, "--no-auth-warning"} commands = []string{"redis-cli", "-a", redisInfo.Password, "--no-auth-warning"}
} }
pidMap := loadMapFromDockerTop(redisConf.ContainerName) pidMap := loadMapFromDockerTop(redisInfo.Password)
itemCmds := append([]string{"exec", "-it", redisConf.ContainerName}, commands...) itemCmds := append([]string{"exec", "-it", redisInfo.ContainerName}, commands...)
slave, err := terminal.NewCommand(itemCmds) slave, err := terminal.NewCommand(itemCmds)
if wshandleError(wsConn, err) { if wshandleError(wsConn, err) {
return return
} }
defer killBash(redisConf.ContainerName, strings.Join(commands, " "), pidMap) defer killBash(redisInfo.ContainerName, strings.Join(commands, " "), pidMap)
defer slave.Close() defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false) tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)

View File

@ -165,26 +165,26 @@ type MysqlVariablesUpdateHelper struct {
// redis // redis
type ChangeRedisPass struct { type ChangeRedisPass struct {
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"` Value string `json:"value" validate:"required"`
} }
type RedisConfUpdate struct { type RedisConfUpdate struct {
Database string `json:"database" validate:"required"`
Timeout string `json:"timeout"` Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"` Maxclients string `json:"maxclients"`
Maxmemory string `json:"maxmemory"` Maxmemory string `json:"maxmemory"`
} }
type RedisConfPersistenceUpdate struct { type RedisConfPersistenceUpdate struct {
Database string `json:"database" validate:"required"`
Type string `json:"type" validate:"required,oneof=aof rbd"` Type string `json:"type" validate:"required,oneof=aof rbd"`
Appendonly string `json:"appendonly"` Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"` Appendfsync string `json:"appendfsync"`
Save string `json:"save"` Save string `json:"save"`
} }
type RedisConfUpdateByFile struct {
File string `json:"file" validate:"required"`
RestartNow bool `json:"restartNow"`
}
type RedisConf struct { type RedisConf struct {
Database string `json:"database" validate:"required"`
Name string `json:"name"` Name string `json:"name"`
Port int64 `json:"port"` Port int64 `json:"port"`
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
@ -195,12 +195,14 @@ type RedisConf struct {
} }
type RedisPersistence struct { type RedisPersistence struct {
Database string `json:"database" validate:"required"`
Appendonly string `json:"appendonly"` Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"` Appendfsync string `json:"appendfsync"`
Save string `json:"save"` Save string `json:"save"`
} }
type RedisStatus struct { type RedisStatus struct {
Database string `json:"database" validate:"required"`
TcpPort string `json:"tcp_port"` TcpPort string `json:"tcp_port"`
UptimeInDays string `json:"uptime_in_days"` UptimeInDays string `json:"uptime_in_days"`
ConnectedClients string `json:"connected_clients"` ConnectedClients string `json:"connected_clients"`
@ -217,12 +219,14 @@ type RedisStatus struct {
} }
type DatabaseFileRecords struct { type DatabaseFileRecords struct {
Database string `json:"database" validate:"required"`
FileName string `json:"fileName"` FileName string `json:"fileName"`
FileDir string `json:"fileDir"` FileDir string `json:"fileDir"`
CreatedAt string `json:"createdAt"` CreatedAt string `json:"createdAt"`
Size int `json:"size"` Size int `json:"size"`
} }
type RedisBackupRecover struct { type RedisBackupRecover struct {
Database string `json:"database" validate:"required"`
FileName string `json:"fileName"` FileName string `json:"fileName"`
FileDir string `json:"fileDir"` FileDir string `json:"fileDir"`
} }

View File

@ -49,7 +49,7 @@ type IBackupService interface {
MysqlRecoverByUpload(req dto.CommonRecover) error MysqlRecoverByUpload(req dto.CommonRecover) error
PostgresqlRecoverByUpload(req dto.CommonRecover) error PostgresqlRecoverByUpload(req dto.CommonRecover) error
RedisBackup() error RedisBackup(db dto.CommonBackup) error
RedisRecover(db dto.CommonRecover) error RedisRecover(db dto.CommonRecover) error
WebsiteBackup(db dto.CommonBackup) error WebsiteBackup(db dto.CommonBackup) error

View File

@ -20,12 +20,12 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (u *BackupService) RedisBackup() error { func (u *BackupService) RedisBackup(db dto.CommonBackup) error {
localDir, err := loadLocalDir() localDir, err := loadLocalDir()
if err != nil { if err != nil {
return err return err
} }
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "") redisInfo, err := appInstallRepo.LoadBaseInfo("redis", db.Name)
if err != nil { if err != nil {
return err return err
} }
@ -51,6 +51,7 @@ func (u *BackupService) RedisBackup() error {
} }
record := &model.BackupRecord{ record := &model.BackupRecord{
Type: "redis", Type: "redis",
Name: db.Name,
Source: "LOCAL", Source: "LOCAL",
BackupType: "LOCAL", BackupType: "LOCAL",
FileDir: itemDir, FileDir: itemDir,
@ -64,7 +65,7 @@ func (u *BackupService) RedisBackup() error {
} }
func (u *BackupService) RedisRecover(req dto.CommonRecover) error { func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "") redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath"
"strings" "strings"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
@ -23,11 +21,9 @@ type IRedisService interface {
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
ChangePassword(info dto.ChangeRedisPass) error ChangePassword(info dto.ChangeRedisPass) error
LoadStatus() (*dto.RedisStatus, error) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error)
LoadConf() (*dto.RedisConf, error) LoadConf(req dto.OperationWithName) (*dto.RedisConf, error)
LoadPersistenceConf() (*dto.RedisPersistence, error) LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error)
SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error)
} }
func NewIRedisService() IRedisService { func NewIRedisService() IRedisService {
@ -35,7 +31,7 @@ func NewIRedisService() IRedisService {
} }
func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error { func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "") redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
if err != nil { if err != nil {
return err return err
} }
@ -55,7 +51,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
} }
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error { func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
if err := updateInstallInfoInDB("redis", "", "password", req.Value); err != nil { if err := updateInstallInfoInDB("redis", req.Database, "password", req.Value); err != nil {
return err return err
} }
if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil { if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil {
@ -66,7 +62,7 @@ func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
} }
func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error { func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "") redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
if err != nil { if err != nil {
return err return err
} }
@ -88,8 +84,8 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
return nil return nil
} }
func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) { func (u *RedisService) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "") redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -116,8 +112,8 @@ func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) {
return &info, nil return &info, nil
} }
func (u *RedisService) LoadConf() (*dto.RedisConf, error) { func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "") redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -133,8 +129,8 @@ func (u *RedisService) LoadConf() (*dto.RedisConf, error) {
return &item, nil return &item, nil
} }
func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) { func (u *RedisService) LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "") redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -151,46 +147,6 @@ func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) {
return &item, nil return &item, nil
} }
func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error) {
var (
list []dto.DatabaseFileRecords
backDatas []dto.DatabaseFileRecords
)
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
if err != nil {
return 0, nil, err
}
localDir, err := loadLocalDir()
if err != nil {
return 0, nil, err
}
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
_ = filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
list = append(list, dto.DatabaseFileRecords{
CreatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
Size: int(info.Size()),
FileDir: backupDir,
FileName: info.Name(),
})
}
return nil
})
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]dto.DatabaseFileRecords, 0)
} else {
if end >= total {
end = total
}
backDatas = list[start:end]
}
return int64(total), backDatas, nil
}
func configGetStr(containerName, password, param string) (string, error) { func configGetStr(containerName, password, param string) (string, error) {
commands := append(redisExec(containerName, password), []string{"config", "get", param}...) commands := append(redisExec(containerName, password), []string{"config", "get", param}...)
cmd := exec.Command("docker", commands...) cmd := exec.Command("docker", commands...)

View File

@ -35,12 +35,11 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
cmdRouter.POST("/remote", baseApi.LoadRemoteAccess) cmdRouter.POST("/remote", baseApi.LoadRemoteAccess)
cmdRouter.GET("/options", baseApi.ListDBName) cmdRouter.GET("/options", baseApi.ListDBName)
cmdRouter.GET("/redis/persistence/conf", baseApi.LoadPersistenceConf) cmdRouter.POST("/redis/persistence/conf", baseApi.LoadPersistenceConf)
cmdRouter.GET("/redis/status", baseApi.LoadRedisStatus) cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
cmdRouter.GET("/redis/conf", baseApi.LoadRedisConf) cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh) cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh)
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword) cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
cmdRouter.POST("/redis/backup/search", baseApi.RedisBackupList)
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf) cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf) cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf)

View File

@ -5109,42 +5109,6 @@ const docTemplate = `{
} }
} }
}, },
"/databases/redis/backup/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 redis 备份记录分页",
"consumes": [
"application/json"
],
"tags": [
"Database Redis"
],
"summary": "Page redis backups",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageInfo"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.PageResult"
}
}
}
}
},
"/databases/redis/conf": { "/databases/redis/conf": {
"get": { "get": {
"security": [ "security": [
@ -5153,10 +5117,24 @@ const docTemplate = `{
} }
], ],
"description": "获取 redis 配置信息", "description": "获取 redis 配置信息",
"consumes": [
"application/json"
],
"tags": [ "tags": [
"Database Redis" "Database Redis"
], ],
"summary": "Load redis conf", "summary": "Load redis conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -5255,10 +5233,24 @@ const docTemplate = `{
} }
], ],
"description": "获取 redis 持久化配置", "description": "获取 redis 持久化配置",
"consumes": [
"application/json"
],
"tags": [ "tags": [
"Database Redis" "Database Redis"
], ],
"summary": "Load redis persistence conf", "summary": "Load redis persistence conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -5317,10 +5309,24 @@ const docTemplate = `{
} }
], ],
"description": "获取 redis 状态信息", "description": "获取 redis 状态信息",
"consumes": [
"application/json"
],
"tags": [ "tags": [
"Database Redis" "Database Redis"
], ],
"summary": "Load redis status info", "summary": "Load redis status info",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -14323,9 +14329,13 @@ const docTemplate = `{
"dto.ChangeRedisPass": { "dto.ChangeRedisPass": {
"type": "object", "type": "object",
"required": [ "required": [
"database",
"value" "value"
], ],
"properties": { "properties": {
"database": {
"type": "string"
},
"value": { "value": {
"type": "string" "type": "string"
} }
@ -14761,7 +14771,8 @@ const docTemplate = `{
"container", "container",
"image", "image",
"volume", "volume",
"network" "network",
"buildcache"
] ]
}, },
"withTagAll": { "withTagAll": {
@ -17296,10 +17307,16 @@ const docTemplate = `{
}, },
"dto.RedisConf": { "dto.RedisConf": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"containerName": { "containerName": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"maxclients": { "maxclients": {
"type": "string" "type": "string"
}, },
@ -17323,6 +17340,7 @@ const docTemplate = `{
"dto.RedisConfPersistenceUpdate": { "dto.RedisConfPersistenceUpdate": {
"type": "object", "type": "object",
"required": [ "required": [
"database",
"type" "type"
], ],
"properties": { "properties": {
@ -17332,6 +17350,9 @@ const docTemplate = `{
"appendonly": { "appendonly": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"save": { "save": {
"type": "string" "type": "string"
}, },
@ -17346,7 +17367,13 @@ const docTemplate = `{
}, },
"dto.RedisConfUpdate": { "dto.RedisConfUpdate": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"database": {
"type": "string"
},
"maxclients": { "maxclients": {
"type": "string" "type": "string"
}, },
@ -17360,6 +17387,9 @@ const docTemplate = `{
}, },
"dto.RedisPersistence": { "dto.RedisPersistence": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"appendfsync": { "appendfsync": {
"type": "string" "type": "string"
@ -17367,6 +17397,9 @@ const docTemplate = `{
"appendonly": { "appendonly": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"save": { "save": {
"type": "string" "type": "string"
} }
@ -17374,10 +17407,16 @@ const docTemplate = `{
}, },
"dto.RedisStatus": { "dto.RedisStatus": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"connected_clients": { "connected_clients": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"instantaneous_ops_per_sec": { "instantaneous_ops_per_sec": {
"type": "string" "type": "string"
}, },
@ -18578,6 +18617,9 @@ const docTemplate = `{
"dir": { "dir": {
"type": "string" "type": "string"
}, },
"disableCNAME": {
"type": "boolean"
},
"dnsAccount": { "dnsAccount": {
"$ref": "#/definitions/model.WebsiteDnsAccount" "$ref": "#/definitions/model.WebsiteDnsAccount"
}, },
@ -18599,6 +18641,12 @@ const docTemplate = `{
"message": { "message": {
"type": "string" "type": "string"
}, },
"nameserver1": {
"type": "string"
},
"nameserver2": {
"type": "string"
},
"organization": { "organization": {
"type": "string" "type": "string"
}, },
@ -18617,6 +18665,9 @@ const docTemplate = `{
"pushDir": { "pushDir": {
"type": "boolean" "type": "boolean"
}, },
"skipDNS": {
"type": "boolean"
},
"startDate": { "startDate": {
"type": "string" "type": "string"
}, },
@ -20542,7 +20593,6 @@ const docTemplate = `{
"required": [ "required": [
"id", "id",
"match", "match",
"modifier",
"name", "name",
"operate", "operate",
"proxyHost", "proxyHost",
@ -20627,7 +20677,13 @@ const docTemplate = `{
"ID": { "ID": {
"type": "integer" "type": "integer"
}, },
"SkipDNSCheck": { "nameservers": {
"type": "array",
"items": {
"type": "string"
}
},
"skipDNSCheck": {
"type": "boolean" "type": "boolean"
} }
} }
@ -20655,6 +20711,9 @@ const docTemplate = `{
"dir": { "dir": {
"type": "string" "type": "string"
}, },
"disableCNAME": {
"type": "boolean"
},
"dnsAccountId": { "dnsAccountId": {
"type": "integer" "type": "integer"
}, },
@ -20664,6 +20723,12 @@ const docTemplate = `{
"keyType": { "keyType": {
"type": "string" "type": "string"
}, },
"nameserver1": {
"type": "string"
},
"nameserver2": {
"type": "string"
},
"otherDomains": { "otherDomains": {
"type": "string" "type": "string"
}, },
@ -20675,6 +20740,9 @@ const docTemplate = `{
}, },
"pushDir": { "pushDir": {
"type": "boolean" "type": "boolean"
},
"skipDNS": {
"type": "boolean"
} }
} }
}, },
@ -20699,17 +20767,59 @@ const docTemplate = `{
"request.WebsiteSSLUpdate": { "request.WebsiteSSLUpdate": {
"type": "object", "type": "object",
"required": [ "required": [
"id" "acmeAccountId",
"id",
"primaryDomain",
"provider"
], ],
"properties": { "properties": {
"acmeAccountId": {
"type": "integer"
},
"apply": {
"type": "boolean"
},
"autoRenew": { "autoRenew": {
"type": "boolean" "type": "boolean"
}, },
"description": { "description": {
"type": "string" "type": "string"
}, },
"dir": {
"type": "string"
},
"disableCNAME": {
"type": "boolean"
},
"dnsAccountId": {
"type": "integer"
},
"id": { "id": {
"type": "integer" "type": "integer"
},
"keyType": {
"type": "string"
},
"nameserver1": {
"type": "string"
},
"nameserver2": {
"type": "string"
},
"otherDomains": {
"type": "string"
},
"primaryDomain": {
"type": "string"
},
"provider": {
"type": "string"
},
"pushDir": {
"type": "boolean"
},
"skipDNS": {
"type": "boolean"
} }
} }
}, },

View File

@ -5102,42 +5102,6 @@
} }
} }
}, },
"/databases/redis/backup/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 redis 备份记录分页",
"consumes": [
"application/json"
],
"tags": [
"Database Redis"
],
"summary": "Page redis backups",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageInfo"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.PageResult"
}
}
}
}
},
"/databases/redis/conf": { "/databases/redis/conf": {
"get": { "get": {
"security": [ "security": [
@ -5146,10 +5110,24 @@
} }
], ],
"description": "获取 redis 配置信息", "description": "获取 redis 配置信息",
"consumes": [
"application/json"
],
"tags": [ "tags": [
"Database Redis" "Database Redis"
], ],
"summary": "Load redis conf", "summary": "Load redis conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -5248,10 +5226,24 @@
} }
], ],
"description": "获取 redis 持久化配置", "description": "获取 redis 持久化配置",
"consumes": [
"application/json"
],
"tags": [ "tags": [
"Database Redis" "Database Redis"
], ],
"summary": "Load redis persistence conf", "summary": "Load redis persistence conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -5310,10 +5302,24 @@
} }
], ],
"description": "获取 redis 状态信息", "description": "获取 redis 状态信息",
"consumes": [
"application/json"
],
"tags": [ "tags": [
"Database Redis" "Database Redis"
], ],
"summary": "Load redis status info", "summary": "Load redis status info",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -14316,9 +14322,13 @@
"dto.ChangeRedisPass": { "dto.ChangeRedisPass": {
"type": "object", "type": "object",
"required": [ "required": [
"database",
"value" "value"
], ],
"properties": { "properties": {
"database": {
"type": "string"
},
"value": { "value": {
"type": "string" "type": "string"
} }
@ -14754,7 +14764,8 @@
"container", "container",
"image", "image",
"volume", "volume",
"network" "network",
"buildcache"
] ]
}, },
"withTagAll": { "withTagAll": {
@ -17289,10 +17300,16 @@
}, },
"dto.RedisConf": { "dto.RedisConf": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"containerName": { "containerName": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"maxclients": { "maxclients": {
"type": "string" "type": "string"
}, },
@ -17316,6 +17333,7 @@
"dto.RedisConfPersistenceUpdate": { "dto.RedisConfPersistenceUpdate": {
"type": "object", "type": "object",
"required": [ "required": [
"database",
"type" "type"
], ],
"properties": { "properties": {
@ -17325,6 +17343,9 @@
"appendonly": { "appendonly": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"save": { "save": {
"type": "string" "type": "string"
}, },
@ -17339,7 +17360,13 @@
}, },
"dto.RedisConfUpdate": { "dto.RedisConfUpdate": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"database": {
"type": "string"
},
"maxclients": { "maxclients": {
"type": "string" "type": "string"
}, },
@ -17353,6 +17380,9 @@
}, },
"dto.RedisPersistence": { "dto.RedisPersistence": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"appendfsync": { "appendfsync": {
"type": "string" "type": "string"
@ -17360,6 +17390,9 @@
"appendonly": { "appendonly": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"save": { "save": {
"type": "string" "type": "string"
} }
@ -17367,10 +17400,16 @@
}, },
"dto.RedisStatus": { "dto.RedisStatus": {
"type": "object", "type": "object",
"required": [
"database"
],
"properties": { "properties": {
"connected_clients": { "connected_clients": {
"type": "string" "type": "string"
}, },
"database": {
"type": "string"
},
"instantaneous_ops_per_sec": { "instantaneous_ops_per_sec": {
"type": "string" "type": "string"
}, },
@ -18571,6 +18610,9 @@
"dir": { "dir": {
"type": "string" "type": "string"
}, },
"disableCNAME": {
"type": "boolean"
},
"dnsAccount": { "dnsAccount": {
"$ref": "#/definitions/model.WebsiteDnsAccount" "$ref": "#/definitions/model.WebsiteDnsAccount"
}, },
@ -18592,6 +18634,12 @@
"message": { "message": {
"type": "string" "type": "string"
}, },
"nameserver1": {
"type": "string"
},
"nameserver2": {
"type": "string"
},
"organization": { "organization": {
"type": "string" "type": "string"
}, },
@ -18610,6 +18658,9 @@
"pushDir": { "pushDir": {
"type": "boolean" "type": "boolean"
}, },
"skipDNS": {
"type": "boolean"
},
"startDate": { "startDate": {
"type": "string" "type": "string"
}, },
@ -20535,7 +20586,6 @@
"required": [ "required": [
"id", "id",
"match", "match",
"modifier",
"name", "name",
"operate", "operate",
"proxyHost", "proxyHost",
@ -20620,7 +20670,13 @@
"ID": { "ID": {
"type": "integer" "type": "integer"
}, },
"SkipDNSCheck": { "nameservers": {
"type": "array",
"items": {
"type": "string"
}
},
"skipDNSCheck": {
"type": "boolean" "type": "boolean"
} }
} }
@ -20648,6 +20704,9 @@
"dir": { "dir": {
"type": "string" "type": "string"
}, },
"disableCNAME": {
"type": "boolean"
},
"dnsAccountId": { "dnsAccountId": {
"type": "integer" "type": "integer"
}, },
@ -20657,6 +20716,12 @@
"keyType": { "keyType": {
"type": "string" "type": "string"
}, },
"nameserver1": {
"type": "string"
},
"nameserver2": {
"type": "string"
},
"otherDomains": { "otherDomains": {
"type": "string" "type": "string"
}, },
@ -20668,6 +20733,9 @@
}, },
"pushDir": { "pushDir": {
"type": "boolean" "type": "boolean"
},
"skipDNS": {
"type": "boolean"
} }
} }
}, },
@ -20692,17 +20760,59 @@
"request.WebsiteSSLUpdate": { "request.WebsiteSSLUpdate": {
"type": "object", "type": "object",
"required": [ "required": [
"id" "acmeAccountId",
"id",
"primaryDomain",
"provider"
], ],
"properties": { "properties": {
"acmeAccountId": {
"type": "integer"
},
"apply": {
"type": "boolean"
},
"autoRenew": { "autoRenew": {
"type": "boolean" "type": "boolean"
}, },
"description": { "description": {
"type": "string" "type": "string"
}, },
"dir": {
"type": "string"
},
"disableCNAME": {
"type": "boolean"
},
"dnsAccountId": {
"type": "integer"
},
"id": { "id": {
"type": "integer" "type": "integer"
},
"keyType": {
"type": "string"
},
"nameserver1": {
"type": "string"
},
"nameserver2": {
"type": "string"
},
"otherDomains": {
"type": "string"
},
"primaryDomain": {
"type": "string"
},
"provider": {
"type": "string"
},
"pushDir": {
"type": "boolean"
},
"skipDNS": {
"type": "boolean"
} }
} }
}, },

View File

@ -207,9 +207,12 @@ definitions:
type: object type: object
dto.ChangeRedisPass: dto.ChangeRedisPass:
properties: properties:
database:
type: string
value: value:
type: string type: string
required: required:
- database
- value - value
type: object type: object
dto.Clean: dto.Clean:
@ -506,6 +509,7 @@ definitions:
- image - image
- volume - volume
- network - network
- buildcache
type: string type: string
withTagAll: withTagAll:
type: boolean type: boolean
@ -2225,6 +2229,8 @@ definitions:
properties: properties:
containerName: containerName:
type: string type: string
database:
type: string
maxclients: maxclients:
type: string type: string
maxmemory: maxmemory:
@ -2237,6 +2243,8 @@ definitions:
type: string type: string
timeout: timeout:
type: string type: string
required:
- database
type: object type: object
dto.RedisConfPersistenceUpdate: dto.RedisConfPersistenceUpdate:
properties: properties:
@ -2244,6 +2252,8 @@ definitions:
type: string type: string
appendonly: appendonly:
type: string type: string
database:
type: string
save: save:
type: string type: string
type: type:
@ -2252,16 +2262,21 @@ definitions:
- rbd - rbd
type: string type: string
required: required:
- database
- type - type
type: object type: object
dto.RedisConfUpdate: dto.RedisConfUpdate:
properties: properties:
database:
type: string
maxclients: maxclients:
type: string type: string
maxmemory: maxmemory:
type: string type: string
timeout: timeout:
type: string type: string
required:
- database
type: object type: object
dto.RedisPersistence: dto.RedisPersistence:
properties: properties:
@ -2269,13 +2284,19 @@ definitions:
type: string type: string
appendonly: appendonly:
type: string type: string
database:
type: string
save: save:
type: string type: string
required:
- database
type: object type: object
dto.RedisStatus: dto.RedisStatus:
properties: properties:
connected_clients: connected_clients:
type: string type: string
database:
type: string
instantaneous_ops_per_sec: instantaneous_ops_per_sec:
type: string type: string
keyspace_hits: keyspace_hits:
@ -2300,6 +2321,8 @@ definitions:
type: string type: string
used_memory_rss: used_memory_rss:
type: string type: string
required:
- database
type: object type: object
dto.ResourceLimit: dto.ResourceLimit:
properties: properties:
@ -3070,6 +3093,8 @@ definitions:
type: string type: string
dir: dir:
type: string type: string
disableCNAME:
type: boolean
dnsAccount: dnsAccount:
$ref: '#/definitions/model.WebsiteDnsAccount' $ref: '#/definitions/model.WebsiteDnsAccount'
dnsAccountId: dnsAccountId:
@ -3084,6 +3109,10 @@ definitions:
type: string type: string
message: message:
type: string type: string
nameserver1:
type: string
nameserver2:
type: string
organization: organization:
type: string type: string
pem: pem:
@ -3096,6 +3125,8 @@ definitions:
type: string type: string
pushDir: pushDir:
type: boolean type: boolean
skipDNS:
type: boolean
startDate: startDate:
type: string type: string
status: status:
@ -4422,7 +4453,6 @@ definitions:
required: required:
- id - id
- match - match
- modifier
- name - name
- operate - operate
- proxyHost - proxyHost
@ -4446,7 +4476,11 @@ definitions:
properties: properties:
ID: ID:
type: integer type: integer
SkipDNSCheck: nameservers:
items:
type: string
type: array
skipDNSCheck:
type: boolean type: boolean
required: required:
- ID - ID
@ -4463,12 +4497,18 @@ definitions:
type: string type: string
dir: dir:
type: string type: string
disableCNAME:
type: boolean
dnsAccountId: dnsAccountId:
type: integer type: integer
id: id:
type: integer type: integer
keyType: keyType:
type: string type: string
nameserver1:
type: string
nameserver2:
type: string
otherDomains: otherDomains:
type: string type: string
primaryDomain: primaryDomain:
@ -4477,6 +4517,8 @@ definitions:
type: string type: string
pushDir: pushDir:
type: boolean type: boolean
skipDNS:
type: boolean
required: required:
- acmeAccountId - acmeAccountId
- primaryDomain - primaryDomain
@ -4496,14 +4538,43 @@ definitions:
type: object type: object
request.WebsiteSSLUpdate: request.WebsiteSSLUpdate:
properties: properties:
acmeAccountId:
type: integer
apply:
type: boolean
autoRenew: autoRenew:
type: boolean type: boolean
description: description:
type: string type: string
dir:
type: string
disableCNAME:
type: boolean
dnsAccountId:
type: integer
id: id:
type: integer type: integer
keyType:
type: string
nameserver1:
type: string
nameserver2:
type: string
otherDomains:
type: string
primaryDomain:
type: string
provider:
type: string
pushDir:
type: boolean
skipDNS:
type: boolean
required: required:
- acmeAccountId
- id - id
- primaryDomain
- provider
type: object type: object
request.WebsiteSSLUpload: request.WebsiteSSLUpload:
properties: properties:
@ -8277,31 +8348,18 @@ paths:
summary: Page postgresql databases summary: Page postgresql databases
tags: tags:
- Database Postgresql - Database Postgresql
/databases/redis/backup/search: /databases/redis/conf:
post: get:
consumes: consumes:
- application/json - application/json
description: 获取 redis 备份记录分页 description: 获取 redis 配置信息
parameters: parameters:
- description: request - description: request
in: body in: body
name: request name: request
required: true required: true
schema: schema:
$ref: '#/definitions/dto.PageInfo' $ref: '#/definitions/dto.OperationWithName'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.PageResult'
security:
- ApiKeyAuth: []
summary: Page redis backups
tags:
- Database Redis
/databases/redis/conf:
get:
description: 获取 redis 配置信息
responses: responses:
"200": "200":
description: OK description: OK
@ -8366,7 +8424,16 @@ paths:
paramKeys: [] paramKeys: []
/databases/redis/persistence/conf: /databases/redis/persistence/conf:
get: get:
consumes:
- application/json
description: 获取 redis 持久化配置 description: 获取 redis 持久化配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.OperationWithName'
responses: responses:
"200": "200":
description: OK description: OK
@ -8405,7 +8472,16 @@ paths:
paramKeys: [] paramKeys: []
/databases/redis/status: /databases/redis/status:
get: get:
consumes:
- application/json
description: 获取 redis 状态信息 description: 获取 redis 状态信息
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.OperationWithName'
responses: responses:
"200": "200":
description: OK description: OK

View File

@ -221,20 +221,18 @@ export namespace Database {
// redis // redis
export interface RedisConfUpdate { export interface RedisConfUpdate {
database: string;
timeout: string; timeout: string;
maxclients: string; maxclients: string;
maxmemory: string; maxmemory: string;
} }
export interface RedisConfPersistenceUpdate { export interface RedisConfPersistenceUpdate {
database: string;
type: string; type: string;
appendonly: string; appendonly: string;
appendfsync: string; appendfsync: string;
save: string; save: string;
} }
export interface RedisConfUpdateByFile {
file: string;
restartNow: boolean;
}
export interface RedisStatus { export interface RedisStatus {
tcp_port: string; tcp_port: string;
uptime_in_days: string; uptime_in_days: string;
@ -263,16 +261,6 @@ export namespace Database {
appendfsync: string; appendfsync: string;
save: string; save: string;
} }
export interface FileRecord {
fileName: string;
fileDir: string;
createdAt: string;
size: string;
}
export interface RedisRecover {
fileName: string;
fileDir: string;
}
// remote // remote
export interface DatabaseInfo { export interface DatabaseInfo {

View File

@ -108,20 +108,20 @@ export const loadRemoteAccess = (type: string, database: string) => {
}; };
// redis // redis
export const loadRedisStatus = () => { export const loadRedisStatus = (database: string) => {
return http.get<Database.RedisStatus>(`/databases/redis/status`); return http.post<Database.RedisStatus>(`/databases/redis/status`, { name: database });
}; };
export const loadRedisConf = () => { export const loadRedisConf = (database: string) => {
return http.get<Database.RedisConf>(`/databases/redis/conf`); return http.post<Database.RedisConf>(`/databases/redis/conf`, { name: database });
}; };
export const redisPersistenceConf = () => { export const redisPersistenceConf = (database: string) => {
return http.get<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`); return http.post<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`, { name: database });
}; };
export const changeRedisPassword = (value: string) => { export const changeRedisPassword = (database: string, password: string) => {
if (value) { if (password) {
value = Base64.encode(value); password = Base64.encode(password);
} }
return http.post(`/databases/redis/password`, { value: value }); return http.post(`/databases/redis/password`, { database: database, value: password });
}; };
export const updateRedisPersistenceConf = (params: Database.RedisConfPersistenceUpdate) => { export const updateRedisPersistenceConf = (params: Database.RedisConfPersistenceUpdate) => {
return http.post(`/databases/redis/persistence/update`, params); return http.post(`/databases/redis/persistence/update`, params);
@ -129,7 +129,7 @@ export const updateRedisPersistenceConf = (params: Database.RedisConfPersistence
export const updateRedisConf = (params: Database.RedisConfUpdate) => { export const updateRedisConf = (params: Database.RedisConfUpdate) => {
return http.post(`/databases/redis/conf/update`, params); return http.post(`/databases/redis/conf/update`, params);
}; };
export const updateRedisConfByFile = (params: Database.RedisConfUpdateByFile) => { export const updateRedisConfByFile = (params: Database.DBConfUpdate) => {
return http.post(`/databases/redis/conffile/update`, params); return http.post(`/databases/redis/conffile/update`, params);
}; };

View File

@ -167,7 +167,7 @@ let refresh = ref(1);
const httpPort = ref(0); const httpPort = ref(0);
const httpsPort = ref(0); const httpsPort = ref(0);
const em = defineEmits(['setting', 'isExist', 'before', 'update:loading', 'update:maskShow']); const em = defineEmits(['setting', 'isExist', 'before', 'after', 'update:loading', 'update:maskShow']);
const setting = () => { const setting = () => {
em('setting', false); em('setting', false);
}; };
@ -227,6 +227,7 @@ const onOperate = async (operation: string) => {
em('update:loading', false); em('update:loading', false);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
onCheck(); onCheck();
em('after');
}) })
.catch(() => { .catch(() => {
em('update:loading', false); em('update:loading', false);

View File

@ -176,12 +176,14 @@ const onWSReceive = (message: MessageEvent) => {
switch (wsMsg.type) { switch (wsMsg.type) {
case 'cmd': { case 'cmd': {
term.value.element && term.value.focus(); term.value.element && term.value.focus();
if (wsMsg.data) {
let receiveMsg = Base64.decode(wsMsg.data); let receiveMsg = Base64.decode(wsMsg.data);
if (initCmd.value != '') { if (initCmd.value != '') {
receiveMsg = receiveMsg.replace(initCmd.value.trim(), '').trim(); receiveMsg = receiveMsg?.replace(initCmd.value.trim(), '').trim();
initCmd.value = ''; initCmd.value = '';
} }
wsMsg.data && term.value.write(receiveMsg); term.value.write(receiveMsg);
}
break; break;
} }
case 'heartbeat': { case 'heartbeat': {

View File

@ -1,16 +1,51 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<LayoutContent :title="'Redis ' + $t('menu.database')"> <LayoutContent :title="'Redis ' + $t('menu.database')">
<template #app> <template #app v-if="currentDB?.from === 'local'">
<AppStatus <AppStatus
:app-key="'redis'" :app-key="'redis'"
:app-name="appName"
v-model:loading="loading" v-model:loading="loading"
v-model:mask-show="maskShow" v-model:mask-show="maskShow"
@before="onBefore" @before="onBefore"
@after="onAfter"
@setting="onSetting" @setting="onSetting"
@is-exist="checkExist"
></AppStatus> ></AppStatus>
</template> </template>
<template #search v-if="!isOnSetting && redisIsExist">
<el-select v-model="currentDBName" @change="changeDatabase()" class="p-w-200">
<template #prefix>{{ $t('commons.table.type') }}</template>
<el-option-group :label="$t('database.local')">
<div v-for="(item, index) in dbOptionsLocal" :key="index">
<el-option v-if="item.from === 'local'" :value="item.database" class="optionClass">
<span v-if="item.database.length < 25">{{ item.database }}</span>
<el-tooltip v-else :content="item.database" placement="top">
<span>{{ item.database.substring(0, 25) }}...</span>
</el-tooltip>
</el-option>
</div>
<el-button link type="primary" class="jumpAdd" @click="goRouter('app')" icon="Position">
{{ $t('database.goInstall') }}
</el-button>
</el-option-group>
<el-option-group :label="$t('database.remote')">
<div v-for="(item, index) in dbOptionsRemote" :key="index">
<el-option v-if="item.from === 'remote'" :value="item.database" class="optionClass">
<span v-if="item.database.length < 25">{{ item.database }}</span>
<el-tooltip v-else :content="item.database" placement="top">
<span>{{ item.database.substring(0, 25) }}...</span>
</el-tooltip>
<el-tag class="tagClass">
{{ item.type === 'mysql' ? 'MySQL' : 'MariaDB' }}
</el-tag>
</el-option>
</div>
<el-button link type="primary" class="jumpAdd" @click="goRouter('remote')" icon="Position">
{{ $t('database.createRemoteDB') }}
</el-button>
</el-option-group>
</el-select>
</template>
<template #toolbar v-if="!isOnSetting && redisIsExist"> <template #toolbar v-if="!isOnSetting && redisIsExist">
<div :class="{ mask: redisStatus != 'Running' }"> <div :class="{ mask: redisStatus != 'Running' }">
<el-button type="primary" plain @click="onChangePassword"> <el-button type="primary" plain @click="onChangePassword">
@ -24,17 +59,18 @@
:style="{ height: `calc(100vh - ${loadHeight()})` }" :style="{ height: `calc(100vh - ${loadHeight()})` }"
:key="isRefresh" :key="isRefresh"
ref="terminalRef" ref="terminalRef"
v-show="terminalShow" v-show="terminalShow && redisStatus === 'Running'"
/> />
<el-empty
v-if="redisStatus !== 'Running'"
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
:description="$t('commons.service.serviceNotStarted', ['Redis'])"
></el-empty>
</template> </template>
</LayoutContent> </LayoutContent>
<el-card v-if="redisStatus != 'Running' && !isOnSetting && redisIsExist && maskShow" class="mask-prompt">
<span>{{ $t('commons.service.serviceNotStarted', ['Redis']) }}</span>
</el-card>
<Setting ref="settingRef" style="margin-top: 30px" /> <Setting ref="settingRef" style="margin-top: 30px" />
<Password ref="passwordRef" @check-exist="initTerminal" @close-terminal="closeTerminal(true)" /> <Password ref="passwordRef" @check-exist="reOpenTerminal" @close-terminal="closeTerminal(true)" />
<el-dialog <el-dialog
v-model="commandVisible" v-model="commandVisible"
:title="$t('app.checkTitle')" :title="$t('app.checkTitle')"
@ -64,11 +100,12 @@ import Password from '@/views/database/redis/password/index.vue';
import Terminal from '@/components/terminal/index.vue'; import Terminal from '@/components/terminal/index.vue';
import AppStatus from '@/components/app-status/index.vue'; import AppStatus from '@/components/app-status/index.vue';
import PortJumpDialog from '@/components/port-jump/index.vue'; import PortJumpDialog from '@/components/port-jump/index.vue';
import { nextTick, onBeforeUnmount, ref } from 'vue'; import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import { App } from '@/api/interface/app'; import { CheckAppInstalled, GetAppPort } from '@/api/modules/app';
import { GetAppPort } from '@/api/modules/app';
import router from '@/routers'; import router from '@/routers';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { listDatabases } from '@/api/modules/database';
import { Database } from '@/api/interface/database';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
const loading = ref(false); const loading = ref(false);
@ -79,12 +116,18 @@ const settingRef = ref();
const isOnSetting = ref(false); const isOnSetting = ref(false);
const redisIsExist = ref(false); const redisIsExist = ref(false);
const redisStatus = ref(); const redisStatus = ref();
const redisName = ref();
const terminalShow = ref(false); const terminalShow = ref(false);
const redisCommandPort = ref(); const redisCommandPort = ref();
const commandVisible = ref(false); const commandVisible = ref(false);
const appKey = ref('redis');
const appName = ref();
const dbOptionsLocal = ref<Array<Database.DatabaseOption>>([]);
const dbOptionsRemote = ref<Array<Database.DatabaseOption>>([]);
const currentDB = ref<Database.DatabaseOption>();
const currentDBName = ref();
const dialogPortJumpRef = ref(); const dialogPortJumpRef = ref();
const isRefresh = ref(); const isRefresh = ref();
@ -93,11 +136,11 @@ const onSetting = async () => {
isOnSetting.value = true; isOnSetting.value = true;
terminalRef.value?.onClose(false); terminalRef.value?.onClose(false);
terminalShow.value = false; terminalShow.value = false;
settingRef.value!.acceptParams({ status: redisStatus.value, redisName: redisName.value }); settingRef.value!.acceptParams({ status: redisStatus.value, database: currentDBName.value });
}; };
const loadHeight = () => { const loadHeight = () => {
return globalStore.openMenuTabs ? '400px' : '370px'; return globalStore.openMenuTabs ? '470px' : '440px';
}; };
const goDashboard = async () => { const goDashboard = async () => {
@ -118,40 +161,104 @@ const loadDashboardPort = async () => {
const passwordRef = ref(); const passwordRef = ref();
const onChangePassword = async () => { const onChangePassword = async () => {
passwordRef.value!.acceptParams(); passwordRef.value!.acceptParams({ database: currentDBName.value });
}; };
const checkExist = (data: App.CheckInstalled) => { const goRouter = async (target: string) => {
redisIsExist.value = data.isExist; if (target === 'app') {
redisName.value = data.name; router.push({ name: 'AppAll', query: { install: 'redis' } });
redisStatus.value = data.status; return;
loading.value = false;
if (redisStatus.value === 'Running') {
loadDashboardPort();
nextTick(() => {
terminalShow.value = true;
terminalRef.value.acceptParams({
endpoint: '/api/v1/databases/redis/exec',
args: '',
error: '',
initCmd: '',
});
});
} }
router.push({ name: 'Redis-Remote' });
};
const changeDatabase = async () => {
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
appKey.value = item.type;
appName.value = item.database;
reOpenTerminal();
return;
}
}
for (const item of dbOptionsRemote.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
break;
}
}
reOpenTerminal();
};
const loadDBOptions = async () => {
const res = await listDatabases('redis');
let datas = res.data || [];
dbOptionsLocal.value = [];
dbOptionsRemote.value = [];
currentDBName.value = globalStore.currentDB;
for (const item of datas) {
if (currentDBName.value && item.database === currentDBName.value) {
currentDB.value = item;
if (item.from === 'local') {
appKey.value = item.type;
appName.value = item.database;
}
}
if (item.from === 'local') {
dbOptionsLocal.value.push(item);
} else {
dbOptionsRemote.value.push(item);
}
}
if (currentDB.value) {
reOpenTerminal();
return;
}
if (dbOptionsLocal.value.length !== 0) {
currentDB.value = dbOptionsLocal.value[0];
currentDBName.value = dbOptionsLocal.value[0].database;
appKey.value = dbOptionsLocal.value[0].type;
appName.value = dbOptionsLocal.value[0].database;
}
if (!currentDB.value && dbOptionsRemote.value.length !== 0) {
currentDB.value = dbOptionsRemote.value[0];
currentDBName.value = dbOptionsRemote.value[0].database;
}
if (currentDB.value) {
reOpenTerminal();
}
};
const reOpenTerminal = async () => {
closeTerminal(false);
initTerminal();
}; };
const initTerminal = async () => { const initTerminal = async () => {
if (redisStatus.value === 'Running') { loading.value = true;
await CheckAppInstalled('redis', currentDBName.value)
.then((res) => {
redisIsExist.value = res.data.isExist;
redisStatus.value = res.data.status;
loading.value = false;
nextTick(() => { nextTick(() => {
if (res.data.status === 'Running') {
terminalShow.value = true; terminalShow.value = true;
terminalRef.value.acceptParams({ terminalRef.value.acceptParams({
endpoint: '/api/v1/databases/redis/exec', endpoint: '/api/v1/databases/redis/exec',
args: '', args: `name=${currentDBName.value}`,
error: '', error: '',
initCmd: '', initCmd: '',
}); });
});
} }
});
isRefresh.value = !isRefresh.value;
})
.catch(() => {
closeTerminal(false);
loading.value = false;
});
}; };
const closeTerminal = async (isKeepShow: boolean) => { const closeTerminal = async (isKeepShow: boolean) => {
isRefresh.value = !isRefresh.value; isRefresh.value = !isRefresh.value;
@ -159,10 +266,34 @@ const closeTerminal = async (isKeepShow: boolean) => {
terminalShow.value = isKeepShow; terminalShow.value = isKeepShow;
}; };
onMounted(() => {
loadDBOptions();
loadDashboardPort();
});
const onBefore = () => { const onBefore = () => {
closeTerminal(true); closeTerminal(false);
};
const onAfter = () => {
initTerminal();
}; };
onBeforeUnmount(() => { onBeforeUnmount(() => {
closeTerminal(false); closeTerminal(false);
}); });
</script> </script>
<style lang="scss" scoped>
.jumpAdd {
margin-top: 10px;
margin-left: 15px;
margin-bottom: 5px;
font-size: 12px;
}
.tagClass {
float: right;
font-size: 12px;
margin-top: 5px;
}
.optionClass {
min-width: 350px;
}
</style>

View File

@ -78,7 +78,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import { ElForm } from 'element-plus'; import { ElForm } from 'element-plus';
@ -87,19 +87,16 @@ import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { GetAppConnInfo } from '@/api/modules/app'; import { GetAppConnInfo } from '@/api/modules/app';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { App } from '@/api/interface/app';
import { getRandomStr } from '@/utils/util'; import { getRandomStr } from '@/utils/util';
import { getSettingInfo } from '@/api/modules/setting'; import { getSettingInfo } from '@/api/modules/setting';
const loading = ref(false); const loading = ref(false);
const dialogVisible = ref(false); const dialogVisible = ref(false);
const form = ref<App.DatabaseConnInfo>({ const form = reactive({
username: '', database: '',
password: '', password: '',
privilege: false,
containerName: '', containerName: '',
serviceName: '',
systemIP: '', systemIP: '',
port: 0, port: 0,
}); });
@ -111,8 +108,12 @@ const emit = defineEmits(['checkExist', 'closeTerminal']);
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const acceptParams = (): void => { interface DialogProps {
form.value.password = ''; database: string;
}
const acceptParams = (params: DialogProps): void => {
form.database = params.database;
form.password = '';
loadPassword(); loadPassword();
dialogVisible.value = true; dialogVisible.value = true;
}; };
@ -121,20 +122,22 @@ const handleClose = () => {
}; };
const random = async () => { const random = async () => {
form.value.password = getRandomStr(16); form.password = getRandomStr(16);
}; };
const loadPassword = async () => { const loadPassword = async () => {
const res = await GetAppConnInfo('redis', ''); const res = await GetAppConnInfo('redis', form.database);
form.containerName = res.data.containerName;
form.password = res.data.password;
form.port = res.data.port;
const settingInfoRes = await getSettingInfo(); const settingInfoRes = await getSettingInfo();
form.value = res.data; form.systemIP = settingInfoRes.data.systemIP || i18n.global.t('database.localIP');
form.value.systemIP = settingInfoRes.data.systemIP || i18n.global.t('database.localIP');
}; };
const onSubmit = async () => { const onSubmit = async () => {
loading.value = true; loading.value = true;
emit('closeTerminal'); emit('closeTerminal');
await changeRedisPassword(form.value.password) await changeRedisPassword(form.database, form.password)
.then(() => { .then(() => {
loading.value = false; loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));

View File

@ -177,7 +177,7 @@ const persistenceRef = ref();
const useOld = ref(false); const useOld = ref(false);
const redisStatus = ref(); const redisStatus = ref();
const redisName = ref(); const database = ref();
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const redisConf = ref(); const redisConf = ref();
@ -186,7 +186,7 @@ const confirmDialogRef = ref();
const settingShow = ref<boolean>(false); const settingShow = ref<boolean>(false);
interface DialogProps { interface DialogProps {
redisName: string; database: string;
status: string; status: string;
} }
@ -197,14 +197,14 @@ const changeTab = (val: string) => {
loadConfFile(); loadConfFile();
break; break;
case 'persistence': case 'persistence':
persistenceRef.value!.acceptParams({ status: redisStatus.value }); persistenceRef.value!.acceptParams({ status: redisStatus.value, database: database.value });
break; break;
case 'tuning': case 'tuning':
case 'port': case 'port':
loadform(); loadform();
break; break;
case 'status': case 'status':
statusRef.value!.acceptParams({ status: redisStatus.value }); statusRef.value!.acceptParams({ status: redisStatus.value, database: database.value });
break; break;
} }
}; };
@ -215,7 +215,7 @@ const changeLoading = (status: boolean) => {
const acceptParams = (prop: DialogProps): void => { const acceptParams = (prop: DialogProps): void => {
redisStatus.value = prop.status; redisStatus.value = prop.status;
redisName.value = prop.redisName; database.value = prop.database;
settingShow.value = true; settingShow.value = true;
loadConfFile(); loadConfFile();
}; };
@ -280,6 +280,7 @@ const onSubmitForm = async (formEl: FormInstance | undefined) => {
}; };
const submitForm = async () => { const submitForm = async () => {
let param = { let param = {
database: database.value,
timeout: form.timeout + '', timeout: form.timeout + '',
maxclients: form.maxclients + '', maxclients: form.maxclients + '',
maxmemory: form.maxmemory + 'mb', maxmemory: form.maxmemory + 'mb',
@ -320,7 +321,7 @@ const onSaveFile = async () => {
const submitFile = async () => { const submitFile = async () => {
let param = { let param = {
type: 'redis', type: 'redis',
database: redisName.value, database: database.value,
file: redisConf.value, file: redisConf.value,
}; };
loading.value = true; loading.value = true;
@ -336,7 +337,7 @@ const submitFile = async () => {
}; };
const loadform = async () => { const loadform = async () => {
const res = await loadRedisConf(); const res = await loadRedisConf(database.value);
form.name = res.data?.name; form.name = res.data?.name;
form.timeout = Number(res.data?.timeout); form.timeout = Number(res.data?.timeout);
form.maxclients = Number(res.data?.maxclients); form.maxclients = Number(res.data?.maxclients);
@ -347,7 +348,7 @@ const loadform = async () => {
const loadConfFile = async () => { const loadConfFile = async () => {
useOld.value = false; useOld.value = false;
loading.value = true; loading.value = true;
await loadDBFile('redis-conf', redisName.value) await loadDBFile('redis-conf', database.value)
.then((res) => { .then((res) => {
loading.value = false; loading.value = false;
redisConf.value = res.data; redisConf.value = res.data;

View File

@ -139,14 +139,17 @@ const rules = reactive({
appendfsync: [Rules.requiredSelect], appendfsync: [Rules.requiredSelect],
}); });
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const database = ref();
const opRef = ref(); const opRef = ref();
interface DialogProps { interface DialogProps {
database: string;
status: string; status: string;
} }
const persistenceShow = ref(false); const persistenceShow = ref(false);
const acceptParams = (prop: DialogProps): void => { const acceptParams = (prop: DialogProps): void => {
persistenceShow.value = true; persistenceShow.value = true;
database.value = prop.database;
if (prop.status === 'Running') { if (prop.status === 'Running') {
loadform(); loadform();
search(); search();
@ -179,7 +182,7 @@ const handleDelete = (index: number) => {
const search = async () => { const search = async () => {
let params = { let params = {
type: 'redis', type: 'redis',
name: '', name: database.value,
detailName: '', detailName: '',
page: paginationConfig.currentPage, page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize, pageSize: paginationConfig.pageSize,
@ -190,7 +193,7 @@ const search = async () => {
}; };
const onBackup = async () => { const onBackup = async () => {
emit('loading', true); emit('loading', true);
await handleBackup({ name: '', detailName: '', type: 'redis' }) await handleBackup({ name: database.value, detailName: '', type: 'redis' })
.then(() => { .then(() => {
emit('loading', false); emit('loading', false);
search(); search();
@ -204,7 +207,7 @@ const onRecover = async () => {
let param = { let param = {
source: currentRow.value.source, source: currentRow.value.source,
type: 'redis', type: 'redis',
name: '', name: database.value,
detailName: '', detailName: '',
file: currentRow.value.fileDir + '/' + currentRow.value.fileName, file: currentRow.value.fileDir + '/' + currentRow.value.fileName,
}; };
@ -266,6 +269,7 @@ const buttons = [
const onSave = async (formEl: FormInstance | undefined, type: string) => { const onSave = async (formEl: FormInstance | undefined, type: string) => {
let param = {} as Database.RedisConfPersistenceUpdate; let param = {} as Database.RedisConfPersistenceUpdate;
param.database = database.value;
if (type == 'aof') { if (type == 'aof') {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
@ -308,7 +312,7 @@ const onSave = async (formEl: FormInstance | undefined, type: string) => {
const loadform = async () => { const loadform = async () => {
form.saves = []; form.saves = [];
const res = await redisPersistenceConf(); const res = await redisPersistenceConf(database.value);
form.appendonly = res.data?.appendonly; form.appendonly = res.data?.appendonly;
form.appendfsync = res.data?.appendfsync; form.appendfsync = res.data?.appendfsync;
let itemSaves = res.data?.save.split(' '); let itemSaves = res.data?.save.split(' ');

View File

@ -161,20 +161,23 @@ const redisStatus = reactive({
latest_fork_usec: '', latest_fork_usec: '',
}); });
const database = ref();
const statusShow = ref(false); const statusShow = ref(false);
interface DialogProps { interface DialogProps {
database: string;
status: string; status: string;
} }
const acceptParams = (prop: DialogProps): void => { const acceptParams = (prop: DialogProps): void => {
statusShow.value = true; statusShow.value = true;
database.value = prop.database;
if (prop.status === 'Running') { if (prop.status === 'Running') {
loadStatus(); loadStatus();
} }
}; };
const loadStatus = async () => { const loadStatus = async () => {
const res = await loadRedisStatus(); const res = await loadRedisStatus(database.value);
let hit = ( let hit = (
(Number(res.data.keyspace_hits) / (Number(res.data.keyspace_hits) + Number(res.data.keyspace_misses))) * (Number(res.data.keyspace_hits) / (Number(res.data.keyspace_hits) + Number(res.data.keyspace_misses))) *
100 100

View File

@ -42,7 +42,7 @@
<el-table-column type="selection" fix /> <el-table-column type="selection" fix />
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
show-overflow-tooltip="" show-overflow-tooltip
min-width="100" min-width="100"
prop="name" prop="name"
fix fix
@ -57,7 +57,7 @@
/> />
<el-table-column <el-table-column
:label="$t('commons.table.group')" :label="$t('commons.table.group')"
show-overflow-tooltip="" show-overflow-tooltip
min-width="100" min-width="100"
prop="groupBelong" prop="groupBelong"
fix fix