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

feat: 完善 PostgreSQL 功能 (#3499)

This commit is contained in:
ssongliu 2024-01-02 17:08:13 +08:00 committed by GitHub
parent 4d19e3904b
commit 9561336934
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2040 additions and 634 deletions

View File

@ -94,50 +94,6 @@ func (b *BaseApi) ChangePostgresqlPassword(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Database Postgresql
// @Summary Change postgresql access
// @Description 修改 postgresql 访问权限
// @Accept json
// @Param request body dto.ChangeDBInfo true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg/change/access [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"}
func (b *BaseApi) ChangePostgresqlAccess(c *gin.Context) {
var req dto.ChangeDBInfo
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := postgresqlService.ChangeAccess(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql
// @Summary Update postgresql variables
// @Description postgresql 性能调优
// @Accept json
// @Param request body dto.PostgresqlVariablesUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg/variables/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整 postgresql 数据库性能参数","formatEN":"adjust postgresql database performance parameters"}
func (b *BaseApi) UpdatePostgresqlVariables(c *gin.Context) {
//var req dto.PostgresqlVariablesUpdate
//if err := helper.CheckBindAndValidate(&req, c); err != nil {
// return
//}
//
//if err := postgresqlService.UpdateVariables(req); err != nil {
// helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
// return
//}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql // @Tags Database Postgresql
// @Summary Update postgresql conf by upload file // @Summary Update postgresql conf by upload file
// @Description 上传替换 postgresql 配置文件 // @Description 上传替换 postgresql 配置文件
@ -213,15 +169,17 @@ func (b *BaseApi) ListPostgresqlDBName(c *gin.Context) {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/pg/load [post] // @Router /databases/pg/load [post]
func (b *BaseApi) LoadPostgresqlDBFromRemote(c *gin.Context) { func (b *BaseApi) LoadPostgresqlDBFromRemote(c *gin.Context) {
//var req dto.PostgresqlLoadDB var req dto.PostgresqlLoadDB
//if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
// return return
//} }
//
//if err := postgresqlService.LoadFromRemote(req); err != nil { if err := postgresqlService.LoadFromRemote(req); err != nil {
// helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
// return return
//} }
helper.SuccessWithData(c, nil)
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
@ -294,28 +252,3 @@ func (b *BaseApi) LoadPostgresqlBaseinfo(c *gin.Context) {
helper.SuccessWithData(c, data) helper.SuccessWithData(c, data)
} }
// @Tags Database Postgresql
// @Summary Load postgresql status info
// @Description 获取 postgresql 状态信息
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {object} dto.PostgresqlStatus
// @Security ApiKeyAuth
// @Router /databases/pg/status [post]
func (b *BaseApi) LoadPostgresqlStatus(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := postgresqlService.LoadStatus(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}

View File

@ -63,51 +63,6 @@ type PostgresqlDBDelete struct {
DeleteBackup bool `json:"deleteBackup"` DeleteBackup bool `json:"deleteBackup"`
} }
type PostgresqlStatus struct {
Uptime string `json:"uptime"`
Version string `json:"version"`
MaxConnections string `json:"max_connections"`
Autovacuum string `json:"autovacuum"`
CurrentConnections string `json:"current_connections"`
HitRatio string `json:"hit_ratio"`
SharedBuffers string `json:"shared_buffers"`
BuffersClean string `json:"buffers_clean"`
MaxwrittenClean string `json:"maxwritten_clean"`
BuffersBackendFsync string `json:"buffers_backend_fsync"`
}
type PostgresqlVariables struct {
BinlogCachSize string `json:"binlog_cache_size"`
InnodbBufferPoolSize string `json:"innodb_buffer_pool_size"`
InnodbLogBufferSize string `json:"innodb_log_buffer_size"`
JoinBufferSize string `json:"join_buffer_size"`
KeyBufferSize string `json:"key_buffer_size"`
MaxConnections string `json:"max_connections"`
MaxHeapTableSize string `json:"max_heap_table_size"`
QueryCacheSize string `json:"query_cache_size"`
QueryCache_type string `json:"query_cache_type"`
ReadBufferSize string `json:"read_buffer_size"`
ReadRndBufferSize string `json:"read_rnd_buffer_size"`
SortBufferSize string `json:"sort_buffer_size"`
TableOpenCache string `json:"table_open_cache"`
ThreadCacheSize string `json:"thread_cache_size"`
ThreadStack string `json:"thread_stack"`
TmpTableSize string `json:"tmp_table_size"`
SlowQueryLog string `json:"slow_query_log"`
LongQueryTime string `json:"long_query_time"`
}
type PostgresqlVariablesUpdate struct {
Type string `json:"type" validate:"required,oneof=postgresql"`
Database string `json:"database" validate:"required"`
Variables []PostgresqlVariablesUpdateHelper `json:"variables"`
}
type PostgresqlVariablesUpdateHelper struct {
Param string `json:"param"`
Value interface{} `json:"value"`
}
type PostgresqlConfUpdateByFile struct { type PostgresqlConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=postgresql mariadb"` Type string `json:"type" validate:"required,oneof=postgresql mariadb"`
Database string `json:"database" validate:"required"` Database string `json:"database" validate:"required"`

View File

@ -256,7 +256,6 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
} }
resourceId = pgdb.ID resourceId = pgdb.ID
} }
break
case "mysql", "mariadb": case "mysql", "mariadb":
iMysqlRepo := repo.NewIMysqlRepo() iMysqlRepo := repo.NewIMysqlRepo()
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal)) oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
@ -280,8 +279,6 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
} }
resourceId = mysqldb.ID resourceId = mysqldb.ID
} }
break
} }
} }

View File

@ -2,14 +2,15 @@ package service
import ( import (
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/buserr"
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
@ -97,18 +98,14 @@ func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
return nil return nil
} }
func handlePostgresqlBackup(database, dbName, targetDir, fileName string) error { func handlePostgresqlBackup(database, dbName, targetDir, fileName string) error {
dbInfo, err := postgresqlRepo.Get(commonRepo.WithByName(dbName), postgresqlRepo.WithByPostgresqlName(database)) cli, err := LoadPostgresqlClientByFrom(database)
if err != nil {
return err
}
cli, _, err := LoadPostgresqlClientByFrom(database)
if err != nil { if err != nil {
return err return err
} }
defer cli.Close()
backupInfo := pgclient.BackupInfo{ backupInfo := pgclient.BackupInfo{
Name: dbName, Name: dbName,
Format: dbInfo.Format,
TargetDir: targetDir, TargetDir: targetDir,
FileName: fileName, FileName: fileName,
@ -130,16 +127,16 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
if err != nil { if err != nil {
return err return err
} }
cli, _, err := LoadPostgresqlClientByFrom(req.Name) cli, err := LoadPostgresqlClientByFrom(req.Name)
if err != nil { if err != nil {
return err return err
} }
defer cli.Close()
if !isRollback { if !isRollback {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405"))) rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
if err := cli.Backup(client.BackupInfo{ if err := cli.Backup(client.BackupInfo{
Name: req.DetailName, Name: req.DetailName,
Format: dbInfo.Format,
TargetDir: path.Dir(rollbackFile), TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile), FileName: path.Base(rollbackFile),
@ -152,7 +149,6 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
global.LOG.Info("recover failed, start to rollback now") global.LOG.Info("recover failed, start to rollback now")
if err := cli.Recover(client.RecoverInfo{ if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName, Name: req.DetailName,
Format: dbInfo.Format,
SourceFile: rollbackFile, SourceFile: rollbackFile,
Timeout: 300, Timeout: 300,
@ -168,10 +164,9 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
} }
if err := cli.Recover(client.RecoverInfo{ if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName, Name: req.DetailName,
Format: dbInfo.Format,
SourceFile: req.File, SourceFile: req.File,
Username: dbInfo.Username, Username: dbInfo.Username,
Timeout: 300, Timeout: 300,
}); err != nil { }); err != nil {
return err return err
} }

View File

@ -3,11 +3,12 @@ package service
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
client2 "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
"os" "os"
"path" "path"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
client2 "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
@ -88,13 +89,7 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
Port: req.Port, Port: req.Port,
Username: req.Username, Username: req.Username,
Password: req.Password, Password: req.Password,
Timeout: 6,
SSL: false,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
}) })
return err == nil return err == nil
case "mysql", "mariadb": case "mysql", "mariadb":
@ -134,13 +129,7 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
Port: req.Port, Port: req.Port,
Username: req.Username, Username: req.Username,
Password: req.Password, Password: req.Password,
Timeout: 6,
SSL: req.SSL,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
}); err != nil { }); err != nil {
return err return err
} }
@ -230,13 +219,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
Port: req.Port, Port: req.Port,
Username: req.Username, Username: req.Username,
Password: req.Password, Password: req.Password,
Timeout: 300,
SSL: req.SSL,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 300,
}); err != nil { }); err != nil {
return err return err
} }

View File

@ -4,6 +4,10 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
@ -17,8 +21,6 @@ import (
_ "github.com/jackc/pgx/v5/stdlib" _ "github.com/jackc/pgx/v5/stdlib"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
"os"
"path"
) )
type PostgresqlService struct{} type PostgresqlService struct{}
@ -28,19 +30,13 @@ type IPostgresqlService interface {
ListDBOption() ([]dto.PostgresqlOption, error) ListDBOption() ([]dto.PostgresqlOption, error)
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error) Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
LoadFromRemote(req dto.PostgresqlLoadDB) error LoadFromRemote(req dto.PostgresqlLoadDB) error
ChangeAccess(info dto.ChangeDBInfo) error
ChangePassword(info dto.ChangeDBInfo) error ChangePassword(info dto.ChangeDBInfo) error
UpdateVariables(req dto.PostgresqlVariablesUpdate) error
UpdateConfByFile(info dto.PostgresqlConfUpdateByFile) error UpdateConfByFile(info dto.PostgresqlConfUpdateByFile) error
UpdateDescription(req dto.UpdateDescription) error UpdateDescription(req dto.UpdateDescription) error
DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, error) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, error)
Delete(ctx context.Context, req dto.PostgresqlDBDelete) error Delete(ctx context.Context, req dto.PostgresqlDBDelete) error
LoadStatus(req dto.OperationWithNameAndType) (*dto.PostgresqlStatus, error)
LoadVariables(req dto.OperationWithNameAndType) (*dto.PostgresqlVariables, error)
LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error)
LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error)
LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error)
} }
@ -111,18 +107,18 @@ func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCrea
return nil, errors.New("Cannot set root as user name") return nil, errors.New("Cannot set root as user name")
} }
cli, version, err := LoadPostgresqlClientByFrom(req.Database) cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cli.Close()
createItem.PostgresqlName = req.Database createItem.PostgresqlName = req.Database
defer cli.Close() defer cli.Close()
if err := cli.Create(client.CreateInfo{ if err := cli.Create(client.CreateInfo{
Name: req.Name, Name: req.Name,
Format: req.Format,
Username: req.Username, Username: req.Username,
Password: req.Password, Password: req.Password,
Version: version,
Timeout: 300, Timeout: 300,
}); err != nil { }); err != nil {
return nil, err return nil, err
@ -134,17 +130,17 @@ func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCrea
} }
return &createItem, nil return &createItem, nil
} }
func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, string, error) {
func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, error) {
var ( var (
dbInfo client.DBInfo dbInfo client.DBInfo
version string err error
err error
) )
dbInfo.Timeout = 300 dbInfo.Timeout = 300
databaseItem, err := databaseRepo.Get(commonRepo.WithByName(database)) databaseItem, err := databaseRepo.Get(commonRepo.WithByName(database))
if err != nil { if err != nil {
return nil, "", err return nil, err
} }
dbInfo.From = databaseItem.From dbInfo.From = databaseItem.From
dbInfo.Database = database dbInfo.Database = database
@ -153,17 +149,10 @@ func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, s
dbInfo.Port = databaseItem.Port dbInfo.Port = databaseItem.Port
dbInfo.Username = databaseItem.Username dbInfo.Username = databaseItem.Username
dbInfo.Password = databaseItem.Password dbInfo.Password = databaseItem.Password
dbInfo.SSL = databaseItem.SSL
dbInfo.ClientKey = databaseItem.ClientKey
dbInfo.ClientCert = databaseItem.ClientCert
dbInfo.RootCert = databaseItem.RootCert
dbInfo.SkipVerify = databaseItem.SkipVerify
version = databaseItem.Version
} else { } else {
app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database) app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database)
if err != nil { if err != nil {
return nil, "", err return nil, err
} }
dbInfo.From = "local" dbInfo.From = "local"
dbInfo.Address = app.ContainerName dbInfo.Address = app.ContainerName
@ -174,12 +163,44 @@ func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, s
cli, err := postgresql.NewPostgresqlClient(dbInfo) cli, err := postgresql.NewPostgresqlClient(dbInfo)
if err != nil { if err != nil {
return nil, "", err return nil, err
} }
return cli, version, nil return cli, nil
} }
func (u *PostgresqlService) LoadFromRemote(req dto.PostgresqlLoadDB) error {
func (u *PostgresqlService) LoadFromRemote(req dto.PostgresqlLoadDB) error {
client, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return err
}
defer client.Close()
databases, err := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(req.Database))
if err != nil {
return err
}
datas, err := client.SyncDB()
if err != nil {
return err
}
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.PostgresqlName, data.PostgresqlName) {
hasOld = true
break
}
}
if !hasOld {
var createItem model.DatabasePostgresql
if err := copier.Copy(&createItem, &data); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if err := postgresqlRepo.Create(context.Background(), &createItem); err != nil {
return err
}
}
}
return nil return nil
} }
@ -224,17 +245,16 @@ func (u *PostgresqlService) Delete(ctx context.Context, req dto.PostgresqlDBDele
if err != nil && !req.ForceDelete { if err != nil && !req.ForceDelete {
return err return err
} }
cli, version, err := LoadPostgresqlClientByFrom(req.Database) cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil { if err != nil {
return err return err
} }
defer cli.Close() defer cli.Close()
if err := cli.Delete(client.DeleteInfo{ if err := cli.Delete(client.DeleteInfo{
Name: db.Name, Name: db.Name,
Version: version, Username: db.Username,
Username: db.Username, ForceDelete: req.ForceDelete,
Permission: "", Timeout: 300,
Timeout: 300,
}); err != nil && !req.ForceDelete { }); err != nil && !req.ForceDelete {
return err return err
} }
@ -264,7 +284,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
if cmd.CheckIllegal(req.Value) { if cmd.CheckIllegal(req.Value) {
return buserr.New(constant.ErrCmdIllegal) return buserr.New(constant.ErrCmdIllegal)
} }
cli, version, err := LoadPostgresqlClientByFrom(req.Database) cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil { if err != nil {
return err return err
} }
@ -275,14 +295,12 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
) )
passwordInfo.Password = req.Value passwordInfo.Password = req.Value
passwordInfo.Timeout = 300 passwordInfo.Timeout = 300
passwordInfo.Version = version
if req.ID != 0 { if req.ID != 0 {
postgresqlData, err = postgresqlRepo.Get(commonRepo.WithByID(req.ID)) postgresqlData, err = postgresqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil { if err != nil {
return err return err
} }
passwordInfo.Name = postgresqlData.Name
passwordInfo.Username = postgresqlData.Username passwordInfo.Username = postgresqlData.Username
} else { } else {
dbItem, err := databaseRepo.Get(commonRepo.WithByType(req.Type), commonRepo.WithByFrom(req.From)) dbItem, err := databaseRepo.Get(commonRepo.WithByType(req.Type), commonRepo.WithByFrom(req.From))
@ -317,7 +335,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
} }
global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name) global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name)
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil { if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "password", true, req.Value); err != nil {
return err return err
} }
} }
@ -336,45 +354,6 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
return nil return nil
} }
func (u *PostgresqlService) ChangeAccess(req dto.ChangeDBInfo) error {
if cmd.CheckIllegal(req.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
cli, version, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
var (
postgresqlData model.DatabasePostgresql
accessInfo client.AccessChangeInfo
)
accessInfo.Permission = req.Value
accessInfo.Timeout = 300
accessInfo.Version = version
if req.ID != 0 {
postgresqlData, err = postgresqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
accessInfo.Name = postgresqlData.Name
accessInfo.Username = postgresqlData.Username
accessInfo.Password = postgresqlData.Password
} else {
accessInfo.Username = "root"
}
if err := cli.ChangeAccess(accessInfo); err != nil {
return err
}
if postgresqlData.ID != 0 {
_ = postgresqlRepo.Update(postgresqlData.ID, map[string]interface{}{"permission": req.Value})
}
return nil
}
func (u *PostgresqlService) UpdateConfByFile(req dto.PostgresqlConfUpdateByFile) error { func (u *PostgresqlService) UpdateConfByFile(req dto.PostgresqlConfUpdateByFile) error {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil { if err != nil {
@ -389,22 +368,12 @@ func (u *PostgresqlService) UpdateConfByFile(req dto.PostgresqlConfUpdateByFile)
write := bufio.NewWriter(file) write := bufio.NewWriter(file)
_, _ = write.WriteString(req.File) _, _ = write.WriteString(req.File)
write.Flush() write.Flush()
cli, _, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, req.Type, app.Name)); err != nil { if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, req.Type, app.Name)); err != nil {
return err return err
} }
return nil return nil
} }
func (u *PostgresqlService) UpdateVariables(req dto.PostgresqlVariablesUpdate) error {
return nil
}
func (u *PostgresqlService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) { func (u *PostgresqlService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) {
var data dto.DBBaseInfo var data dto.DBBaseInfo
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
@ -418,32 +387,6 @@ func (u *PostgresqlService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto
return &data, nil return &data, nil
} }
func (u *PostgresqlService) LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error) {
return true, nil
}
func (u *PostgresqlService) LoadVariables(req dto.OperationWithNameAndType) (*dto.PostgresqlVariables, error) {
return nil, nil
}
func (u *PostgresqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.PostgresqlStatus, error) {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
cli, _, err := LoadPostgresqlClientByFrom(app.Name)
if err != nil {
return nil, err
}
defer cli.Close()
status := cli.Status()
postgresqlStatus := dto.PostgresqlStatus{}
copier.Copy(&postgresqlStatus,&status)
return &postgresqlStatus, nil
}
func (u *PostgresqlService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) { func (u *PostgresqlService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
filePath := "" filePath := ""
switch req.Type { switch req.Type {

View File

@ -53,15 +53,13 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
cmdRouter.POST("/db/del/check", baseApi.DeleteCheckDatabase) cmdRouter.POST("/db/del/check", baseApi.DeleteCheckDatabase)
cmdRouter.POST("/db/del", baseApi.DeleteDatabase) cmdRouter.POST("/db/del", baseApi.DeleteDatabase)
//PGSQL管理系列接口
cmdRouter.POST("/pg", baseApi.CreatePostgresql) cmdRouter.POST("/pg", baseApi.CreatePostgresql)
cmdRouter.POST("/pg/search", baseApi.SearchPostgresql) cmdRouter.POST("/pg/search", baseApi.SearchPostgresql)
cmdRouter.POST("/pg/load", baseApi.LoadPostgresqlDBFromRemote)
cmdRouter.POST("/pg/del/check", baseApi.DeleteCheckPostgresql) cmdRouter.POST("/pg/del/check", baseApi.DeleteCheckPostgresql)
cmdRouter.POST("/pg/password", baseApi.ChangePostgresqlPassword) cmdRouter.POST("/pg/password", baseApi.ChangePostgresqlPassword)
cmdRouter.POST("/pg/description", baseApi.UpdatePostgresqlDescription) cmdRouter.POST("/pg/description", baseApi.UpdatePostgresqlDescription)
cmdRouter.POST("/pg/del", baseApi.DeletePostgresql) cmdRouter.POST("/pg/del", baseApi.DeletePostgresql)
cmdRouter.POST("/pg/conf", baseApi.UpdatePostgresqlConfByFile) cmdRouter.POST("/pg/conf", baseApi.UpdatePostgresqlConfByFile)
cmdRouter.POST("/pg/status", baseApi.LoadPostgresqlStatus)
} }
} }

View File

@ -63,13 +63,15 @@ func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error {
if strings.Contains(strings.ToLower(err.Error()), "error 1396") { if strings.Contains(strings.ToLower(err.Error()), "error 1396") {
return buserr.New(constant.ErrUserIsExist) return buserr.New(constant.ErrUserIsExist)
} }
_ = r.Delete(DeleteInfo{ if withDeleteDB {
Name: info.Name, _ = r.Delete(DeleteInfo{
Version: info.Version, Name: info.Name,
Username: info.Username, Version: info.Version,
Permission: info.Permission, Username: info.Username,
ForceDelete: true, Permission: info.Permission,
Timeout: 300}) ForceDelete: true,
Timeout: 300})
}
return err return err
} }
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", info.Name, user) grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", info.Name, user)
@ -82,13 +84,15 @@ func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error {
grantStr = grantStr + " with grant option;" grantStr = grantStr + " with grant option;"
} }
if err := r.ExecSQL(grantStr, info.Timeout); err != nil { if err := r.ExecSQL(grantStr, info.Timeout); err != nil {
_ = r.Delete(DeleteInfo{ if withDeleteDB {
Name: info.Name, _ = r.Delete(DeleteInfo{
Version: info.Version, Name: info.Name,
Username: info.Username, Version: info.Version,
Permission: info.Permission, Username: info.Username,
ForceDelete: true, Permission: info.Permission,
Timeout: 300}) ForceDelete: true,
Timeout: 300})
}
return err return err
} }
} }

View File

@ -4,34 +4,32 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql/client" "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
_ "github.com/jackc/pgx/v5/stdlib" _ "github.com/jackc/pgx/v5/stdlib"
"time"
) )
type PostgresqlClient interface { type PostgresqlClient interface {
Create(info client.CreateInfo) error Create(info client.CreateInfo) error
Delete(info client.DeleteInfo) error Delete(info client.DeleteInfo) error
ReloadConf()error ReloadConf() error
ChangePassword(info client.PasswordChangeInfo) error ChangePassword(info client.PasswordChangeInfo) error
ChangeAccess(info client.AccessChangeInfo) error
Backup(info client.BackupInfo) error Backup(info client.BackupInfo) error
Recover(info client.RecoverInfo) error Recover(info client.RecoverInfo) error
Status() client.Status SyncDB() ([]client.SyncDBInfo, error)
SyncDB(version string) ([]client.SyncDBInfo, error)
Close() Close()
} }
func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) { func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) {
if conn.Port==0 {
conn.Port=5432
}
if conn.From == "local" { if conn.From == "local" {
conn.Address = "127.0.0.1" connArgs := []string{"exec", conn.Address, "psql", "-U", conn.Username, "-c"}
return client.NewLocal(connArgs, conn.Address, conn.Username, conn.Password, conn.Database), nil
} }
connArgs := fmt.Sprintf("postgres://%s:%s@%s:%d/?sslmode=disable", conn.Username, conn.Password, conn.Address, conn.Port) connArgs := fmt.Sprintf("postgres://%s:%s@%s:%d/?sslmode=disable", conn.Username, conn.Password, conn.Address, conn.Port)
db, err := sql.Open("pgx", connArgs) db, err := sql.Open("pgx", connArgs)
if err != nil { if err != nil {
@ -54,11 +52,5 @@ func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) {
Password: conn.Password, Password: conn.Password,
Address: conn.Address, Address: conn.Address,
Port: conn.Port, Port: conn.Port,
SSL: conn.SSL,
RootCert: conn.RootCert,
ClientKey: conn.ClientKey,
ClientCert: conn.ClientCert,
SkipVerify: conn.SkipVerify,
}), nil }), nil
} }

View File

@ -12,60 +12,34 @@ type DBInfo struct {
Username string `json:"userName"` Username string `json:"userName"`
Password string `json:"password"` Password string `json:"password"`
SSL bool `json:"ssl"`
RootCert string `json:"rootCert"`
ClientKey string `json:"clientKey"`
ClientCert string `json:"clientCert"`
SkipVerify bool `json:"skipVerify"`
Timeout uint `json:"timeout"` // second Timeout uint `json:"timeout"` // second
} }
type CreateInfo struct { type CreateInfo struct {
Name string `json:"name"` Name string `json:"name"`
Format string `json:"format"` Username string `json:"userName"`
Version string `json:"version"` Password string `json:"password"`
Username string `json:"userName"`
Password string `json:"password"`
Permission string `json:"permission"`
Timeout uint `json:"timeout"` // second Timeout uint `json:"timeout"` // second
} }
type DeleteInfo struct { type DeleteInfo struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Username string `json:"userName"`
Username string `json:"userName"`
Permission string `json:"permission"`
ForceDelete bool `json:"forceDelete"` ForceDelete bool `json:"forceDelete"`
Timeout uint `json:"timeout"` // second Timeout uint `json:"timeout"` // second
} }
type PasswordChangeInfo struct { type PasswordChangeInfo struct {
Name string `json:"name"` Username string `json:"userName"`
Version string `json:"version"` Password string `json:"password"`
Username string `json:"userName"`
Password string `json:"password"`
Permission string `json:"permission"`
Timeout uint `json:"timeout"` // second
}
type AccessChangeInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Username string `json:"userName"`
Password string `json:"password"`
OldPermission string `json:"oldPermission"`
Permission string `json:"permission"`
Timeout uint `json:"timeout"` // second Timeout uint `json:"timeout"` // second
} }
type BackupInfo struct { type BackupInfo struct {
Name string `json:"name"` Name string `json:"name"`
Format string `json:"format"`
TargetDir string `json:"targetDir"` TargetDir string `json:"targetDir"`
FileName string `json:"fileName"` FileName string `json:"fileName"`
@ -74,19 +48,16 @@ type BackupInfo struct {
type RecoverInfo struct { type RecoverInfo struct {
Name string `json:"name"` Name string `json:"name"`
Format string `json:"format"`
SourceFile string `json:"sourceFile"` SourceFile string `json:"sourceFile"`
Username string `json:"username"` Username string `json:"username"`
Timeout uint `json:"timeout"` // second
Timeout uint `json:"timeout"` // second
} }
type SyncDBInfo struct { type SyncDBInfo struct {
Name string `json:"name"` Name string `json:"name"`
From string `json:"from"` From string `json:"from"`
PostgresqlName string `json:"postgresqlName"` PostgresqlName string `json:"postgresqlName"`
Format string `json:"format"`
Username string `json:"username"`
Password string `json:"password"`
} }
type Status struct { type Status struct {
Uptime string `json:"uptime"` Uptime string `json:"uptime"`

View File

@ -0,0 +1,201 @@
package client
import (
"compress/gzip"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/files"
)
type Local struct {
PrefixCommand []string
Database string
Username string
Password string
ContainerName string
}
func NewLocal(command []string, containerName, username, password, database string) *Local {
return &Local{PrefixCommand: command, ContainerName: containerName, Username: username, Password: password, Database: database}
}
func (r *Local) Create(info CreateInfo) error {
createSql := fmt.Sprintf("CREATE DATABASE %s", info.Name)
if err := r.ExecSQL(createSql, info.Timeout); err != nil {
if strings.Contains(strings.ToLower(err.Error()), "already exists") {
return buserr.New(constant.ErrDatabaseIsExist)
}
return err
}
if err := r.CreateUser(info, true); err != nil {
_ = r.ExecSQL(fmt.Sprintf("DROP DATABASE %s", info.Name), info.Timeout)
return err
}
return nil
}
func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error {
createSql := fmt.Sprintf("CREATE USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Username)
if err := r.ExecSQL(createSql, info.Timeout); err != nil {
if strings.Contains(strings.ToLower(err.Error()), "already exists") {
return buserr.New(constant.ErrUserIsExist)
}
if withDeleteDB {
_ = r.Delete(DeleteInfo{
Name: info.Name,
Username: info.Username,
ForceDelete: true,
Timeout: 300})
}
return err
}
grantStr := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", info.Name, info.Username)
if err := r.ExecSQL(grantStr, info.Timeout); err != nil {
if withDeleteDB {
_ = r.Delete(DeleteInfo{
Name: info.Name,
Username: info.Username,
ForceDelete: true,
Timeout: 300})
}
return err
}
return nil
}
func (r *Local) Delete(info DeleteInfo) error {
if len(info.Name) != 0 {
dropSql := fmt.Sprintf("DROP DATABASE %s", info.Name)
if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete {
return err
}
}
dropSql := fmt.Sprintf("DROP ROLE %s", info.Username)
if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete {
if strings.Contains(strings.ToLower(err.Error()), "depend on it") {
return buserr.WithDetail(constant.ErrInUsed, info.Username, nil)
}
return err
}
return nil
}
func (r *Local) ChangePassword(info PasswordChangeInfo) error {
changeSql := fmt.Sprintf("ALTER USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Password)
if err := r.ExecSQL(changeSql, info.Timeout); err != nil {
return err
}
return nil
}
func (r *Local) Backup(info BackupInfo) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(info.TargetDir) {
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
}
}
outfile, _ := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, 0755)
global.LOG.Infof("start to pg_dump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
cmd := exec.Command("docker", "exec", r.ContainerName, "pg_dump", "-F", "c", "-U", r.Username, "-d", info.Name)
gzipCmd := exec.Command("gzip", "-cf")
gzipCmd.Stdin, _ = cmd.StdoutPipe()
gzipCmd.Stdout = outfile
_ = gzipCmd.Start()
_ = cmd.Run()
_ = gzipCmd.Wait()
return nil
}
func (r *Local) Recover(info RecoverInfo) error {
fi, _ := os.Open(info.SourceFile)
defer fi.Close()
cmd := exec.Command("docker", "exec", "-i", r.ContainerName, "pg_restore", "-F", "c", "-c", "-U", r.Username, "-d", info.Name)
if strings.HasSuffix(info.SourceFile, ".gz") {
gzipFile, err := os.Open(info.SourceFile)
if err != nil {
return err
}
defer gzipFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
return err
}
defer gzipReader.Close()
cmd.Stdin = gzipReader
} else {
cmd.Stdin = fi
}
stdout, err := cmd.CombinedOutput()
if err != nil || strings.HasPrefix(string(stdout), "ERROR ") {
return errors.New(string(stdout))
}
return nil
}
func (r *Local) ReloadConf() error {
return nil
}
func (r *Local) SyncDB() ([]SyncDBInfo, error) {
var datas []SyncDBInfo
lines, err := r.ExecSQLForRows("SELECT datname FROM pg_database", 300)
if err != nil {
return datas, err
}
for _, line := range lines {
if line == "postgres" || line == "template1" || line == "template0" || line == r.Username {
continue
}
datas = append(datas, SyncDBInfo{Name: line, From: "local", PostgresqlName: r.Database})
}
return datas, nil
}
func (r *Local) Close() {}
func (r *Local) ExecSQL(command string, timeout uint) error {
itemCommand := r.PrefixCommand[:]
itemCommand = append(itemCommand, command)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", itemCommand...)
stdout, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return buserr.New(constant.ErrExecTimeOut)
}
if err != nil || strings.HasPrefix(string(stdout), "ERROR ") {
return errors.New(string(stdout))
}
return nil
}
func (r *Local) ExecSQLForRows(command string, timeout uint) ([]string, error) {
itemCommand := r.PrefixCommand[:]
itemCommand = append(itemCommand, command)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", itemCommand...)
stdout, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return nil, buserr.New(constant.ErrExecTimeOut)
}
if err != nil || strings.HasPrefix(string(stdout), "ERROR ") {
return nil, errors.New(string(stdout))
}
return strings.Split(string(stdout), "\n"), nil
}

View File

@ -5,14 +5,15 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/pkg/errors"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
@ -21,115 +22,83 @@ import (
type Remote struct { type Remote struct {
Client *sql.DB Client *sql.DB
From string
Database string Database string
User string User string
Password string Password string
Address string Address string
Port uint Port uint
SSL bool
RootCert string
ClientKey string
ClientCert string
SkipVerify bool
} }
func NewRemote(db Remote) *Remote { func NewRemote(db Remote) *Remote {
return &db return &db
} }
func (r *Remote) Status() Status {
status := Status{}
var i int64
var s string
var f float64
_ = r.Client.QueryRow("select count(*) from pg_stat_activity WHERE client_addr is not NULL;").Scan(&i)
status.CurrentConnections = fmt.Sprintf("%d",i)
_ = r.Client.QueryRow("SELECT current_timestamp - pg_postmaster_start_time();").Scan(&s)
before,_, _ := strings.Cut(s, ".")
status.Uptime = before
_ = r.Client.QueryRow("select sum(blks_hit)*100/sum(blks_hit+blks_read) as hit_ratio from pg_stat_database;").Scan(&f)
status.HitRatio = fmt.Sprintf("%0.2f",f)
var a1,a2,a3 int64
_ = r.Client.QueryRow("select buffers_clean, maxwritten_clean, buffers_backend_fsync from pg_stat_bgwriter;").Scan(&a1, &a2, &a3)
status.BuffersClean = fmt.Sprintf("%d",a1)
status.MaxwrittenClean = fmt.Sprintf("%d",a2)
status.BuffersBackendFsync= fmt.Sprintf("%d",a3)
rows, err := r.Client.Query("SHOW ALL;")
if err == nil {
defer rows.Close()
for rows.Next() {
var k,v string
err := rows.Scan(&k, &v,&s)
if err != nil {
continue
}
if k == "autovacuum" {
status.Autovacuum = v
}
if k == "max_connections" {
status.MaxConnections = v
}
if k == "server_version" {
status.Version = v
}
if k == "shared_buffers" {
status.SharedBuffers = v
}
}
}
return status
}
func (r *Remote) Create(info CreateInfo) error { func (r *Remote) Create(info CreateInfo) error {
createUser := fmt.Sprintf(`CREATE USER "%s" WITH PASSWORD '%s';`, info.Username, info.Password) createSql := fmt.Sprintf("CREATE DATABASE %s", info.Name)
createDB := fmt.Sprintf(`CREATE DATABASE "%s" OWNER "%s";`, info.Name, info.Username) if err := r.ExecSQL(createSql, info.Timeout); err != nil {
grant := fmt.Sprintf(`GRANT ALL PRIVILEGES ON DATABASE "%s" TO "%s";`, info.Name, info.Username) if strings.Contains(strings.ToLower(err.Error()), "already exists") {
if err := r.ExecSQL(createUser, info.Timeout); err != nil {
if strings.Contains(strings.ToLower(err.Error()), "already") {
return buserr.New(constant.ErrUserIsExist)
}
return err
}
if err := r.ExecSQL(createDB, info.Timeout); err != nil {
if strings.Contains(strings.ToLower(err.Error()), "already") {
_ = r.ExecSQL(fmt.Sprintf(`DROP DATABASE "%s"`, info.Name), info.Timeout)
return buserr.New(constant.ErrDatabaseIsExist) return buserr.New(constant.ErrDatabaseIsExist)
} }
return err return err
} }
_ = r.ExecSQL(grant, info.Timeout) if err := r.CreateUser(info, true); err != nil {
return err
}
return nil return nil
} }
func (r *Remote) CreateUser(info CreateInfo, withDeleteDB bool) error { func (r *Remote) CreateUser(info CreateInfo, withDeleteDB bool) error {
sql1 := fmt.Sprintf(`CREATE USER "%s" WITH PASSWORD '%s'; createSql := fmt.Sprintf("CREATE USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Password)
GRANT ALL PRIVILEGES ON DATABASE "%s" TO "%s";`, info.Username, info.Password, info.Name, info.Username) if err := r.ExecSQL(createSql, info.Timeout); err != nil {
err := r.ExecSQL(sql1, info.Timeout) if strings.Contains(strings.ToLower(err.Error()), "already exists") {
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "already") {
return buserr.New(constant.ErrUserIsExist) return buserr.New(constant.ErrUserIsExist)
} }
if withDeleteDB {
_ = r.Delete(DeleteInfo{
Name: info.Name,
Username: info.Username,
ForceDelete: true,
Timeout: 300})
}
return err
}
grantSql := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", info.Name, info.Username)
if err := r.ExecSQL(grantSql, info.Timeout); err != nil {
if withDeleteDB {
_ = r.Delete(DeleteInfo{
Name: info.Name,
Username: info.Username,
ForceDelete: true,
Timeout: 300})
}
return err
} }
return nil return nil
} }
func (r *Remote) Delete(info DeleteInfo) error { func (r *Remote) Delete(info DeleteInfo) error {
//暂时不支持强制删除,就算附加了 WITH(FORCE) 也会删除失败 if len(info.Name) != 0 {
err := r.ExecSQL(fmt.Sprintf(`DROP DATABASE "%s"`, info.Name), info.Timeout) dropSql := fmt.Sprintf("DROP DATABASE %s", info.Name)
if err != nil { if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete {
return err
}
}
dropSql := fmt.Sprintf("DROP ROLE %s", info.Username)
if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete {
if strings.Contains(strings.ToLower(err.Error()), "depend on it") {
return buserr.WithDetail(constant.ErrInUsed, info.Username, nil)
}
return err return err
} }
return r.ExecSQL(fmt.Sprintf(`DROP USER "%s"`, info.Username), info.Timeout) return nil
} }
func (r *Remote) ChangePassword(info PasswordChangeInfo) error { func (r *Remote) ChangePassword(info PasswordChangeInfo) error {
return r.ExecSQL(fmt.Sprintf(`ALTER USER "%s" WITH ENCRYPTED PASSWORD '%s';`, info.Username, info.Password), info.Timeout) return r.ExecSQL(fmt.Sprintf("ALTER USER \"%s\" WITH ENCRYPTED PASSWORD '%s'", info.Username, info.Password), info.Timeout)
} }
func (r *Remote) ReloadConf()error { func (r *Remote) ReloadConf() error {
return r.ExecSQL("SELECT pg_reload_conf();",5) return r.ExecSQL("SELECT pg_reload_conf()", 5)
}
func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
return nil
} }
func (r *Remote) Backup(info BackupInfo) error { func (r *Remote) Backup(info BackupInfo) error {
@ -173,7 +142,7 @@ func (r *Remote) Recover(info RecoverInfo) error {
gzipCmd := exec.Command("gunzip", info.SourceFile) gzipCmd := exec.Command("gunzip", info.SourceFile)
stdout, err := gzipCmd.CombinedOutput() stdout, err := gzipCmd.CombinedOutput()
if err != nil { if err != nil {
return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err) return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err)
} }
defer func() { defer func() {
gzipCmd := exec.Command("gzip", fileName) gzipCmd := exec.Command("gzip", fileName)
@ -207,9 +176,29 @@ func (r *Remote) Recover(info RecoverInfo) error {
return nil return nil
} }
func (r *Remote) SyncDB(version string) ([]SyncDBInfo, error) { func (r *Remote) SyncDB() ([]SyncDBInfo, error) {
//如果需要同步数据库,则需要强制修改用户密码,否则无法获取真实密码,后面可考虑改为添加服务器账号,手动将账号/数据库添加到管理列表 ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
defer cancel()
var datas []SyncDBInfo var datas []SyncDBInfo
rows, err := r.Client.Query("SELECT datname FROM pg_database;")
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var dbName string
if err := rows.Scan(&dbName); err != nil {
continue
}
if dbName == "postgres" || dbName == "template1" || dbName == "template0" || dbName == r.User {
continue
}
datas = append(datas, SyncDBInfo{Name: dbName, From: r.From, PostgresqlName: r.Database})
}
if ctx.Err() == context.DeadlineExceeded {
return nil, buserr.New(constant.ErrExecTimeOut)
}
return datas, nil return datas, nil
} }

View File

@ -4642,6 +4642,421 @@ const docTemplate = `{
} }
} }
}, },
"/databases/pg": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建 postgresql 数据库",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Create postgresql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBCreate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"name"
],
"formatEN": "create postgresql database [name]",
"formatZH": "创建 postgresql 数据库 [name]",
"paramKeys": []
}
}
},
"/databases/pg/baseinfo": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 postgresql 基础信息",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Load postgresql base info",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithNameAndType"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.DBBaseInfo"
}
}
}
}
},
"/databases/pg/conf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "上传替换 postgresql 配置文件",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Update postgresql conf by upload file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlConfUpdateByFile"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "update the postgresql database configuration information",
"formatZH": "更新 postgresql 数据库配置信息",
"paramKeys": []
}
}
},
"/databases/pg/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "删除 postgresql 数据库",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Delete postgresql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBDelete"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "database_postgresqls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "delete postgresql database [name]",
"formatZH": "删除 postgresql 数据库 [name]",
"paramKeys": []
}
}
},
"/databases/pg/del/check": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Postgresql 数据库删除前检查",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Check before delete postgresql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBDeleteCheck"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"/databases/pg/description": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新 postgresql 数据库库描述信息",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Update postgresql database description",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateDescription"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "database_postgresqls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id",
"description"
],
"formatEN": "The description of the postgresql database [name] is modified =\u003e [description]",
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]",
"paramKeys": []
}
}
},
"/databases/pg/load": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从服务器获取",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Load postgresql database from remote",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlLoadDB"
}
}
],
"responses": {}
}
},
"/databases/pg/options": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 postgresql 数据库列表",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "List postgresql database names",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageInfo"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.PostgresqlOption"
}
}
}
}
}
},
"/databases/pg/password": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改 postgresql 密码",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Change postgresql password",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ChangeDBInfo"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "database_postgresqls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update database [name] password",
"formatZH": "更新数据库 [name] 密码",
"paramKeys": []
}
}
},
"/databases/pg/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 postgresql 数据库列表分页",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Page postgresql databases",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBSearch"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.PageResult"
}
}
}
}
},
"/databases/redis/backup/search": { "/databases/redis/backup/search": {
"post": { "post": {
"security": [ "security": [
@ -13734,7 +14149,8 @@ const docTemplate = `{
"type": "string", "type": "string",
"enum": [ "enum": [
"mysql", "mysql",
"mariadb" "mariadb",
"postgresql"
] ]
}, },
"value": { "value": {
@ -13871,7 +14287,8 @@ const docTemplate = `{
"mysql", "mysql",
"mariadb", "mariadb",
"redis", "redis",
"website" "website",
"postgresql"
] ]
} }
} }
@ -13913,7 +14330,8 @@ const docTemplate = `{
"mysql", "mysql",
"mariadb", "mariadb",
"redis", "redis",
"website" "website",
"postgresql"
] ]
} }
} }
@ -16388,6 +16806,193 @@ const docTemplate = `{
} }
} }
}, },
"dto.PostgresqlConfUpdateByFile": {
"type": "object",
"required": [
"database",
"type"
],
"properties": {
"database": {
"type": "string"
},
"file": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"postgresql",
"mariadb"
]
}
}
},
"dto.PostgresqlDBCreate": {
"type": "object",
"required": [
"database",
"from",
"name",
"password",
"permission",
"username"
],
"properties": {
"database": {
"type": "string"
},
"description": {
"type": "string"
},
"format": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"local",
"remote"
]
},
"name": {
"type": "string"
},
"password": {
"type": "string"
},
"permission": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"dto.PostgresqlDBDelete": {
"type": "object",
"required": [
"database",
"id",
"type"
],
"properties": {
"database": {
"type": "string"
},
"deleteBackup": {
"type": "boolean"
},
"forceDelete": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"type": {
"type": "string",
"enum": [
"postgresql"
]
}
}
},
"dto.PostgresqlDBDeleteCheck": {
"type": "object",
"required": [
"database",
"id",
"type"
],
"properties": {
"database": {
"type": "string"
},
"id": {
"type": "integer"
},
"type": {
"type": "string",
"enum": [
"postgresql"
]
}
}
},
"dto.PostgresqlDBSearch": {
"type": "object",
"required": [
"database",
"page",
"pageSize"
],
"properties": {
"database": {
"type": "string"
},
"info": {
"type": "string"
},
"order": {
"type": "string"
},
"orderBy": {
"type": "string"
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.PostgresqlLoadDB": {
"type": "object",
"required": [
"database",
"from",
"type"
],
"properties": {
"database": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"local",
"remote"
]
},
"type": {
"type": "string",
"enum": [
"postgresql"
]
}
}
},
"dto.PostgresqlOption": {
"type": "object",
"properties": {
"database": {
"type": "string"
},
"from": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"dto.RecordSearch": { "dto.RecordSearch": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -4635,6 +4635,421 @@
} }
} }
}, },
"/databases/pg": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建 postgresql 数据库",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Create postgresql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBCreate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"name"
],
"formatEN": "create postgresql database [name]",
"formatZH": "创建 postgresql 数据库 [name]",
"paramKeys": []
}
}
},
"/databases/pg/baseinfo": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 postgresql 基础信息",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Load postgresql base info",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithNameAndType"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.DBBaseInfo"
}
}
}
}
},
"/databases/pg/conf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "上传替换 postgresql 配置文件",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Update postgresql conf by upload file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlConfUpdateByFile"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "update the postgresql database configuration information",
"formatZH": "更新 postgresql 数据库配置信息",
"paramKeys": []
}
}
},
"/databases/pg/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "删除 postgresql 数据库",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Delete postgresql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBDelete"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "database_postgresqls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "delete postgresql database [name]",
"formatZH": "删除 postgresql 数据库 [name]",
"paramKeys": []
}
}
},
"/databases/pg/del/check": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Postgresql 数据库删除前检查",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Check before delete postgresql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBDeleteCheck"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"/databases/pg/description": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新 postgresql 数据库库描述信息",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Update postgresql database description",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateDescription"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "database_postgresqls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id",
"description"
],
"formatEN": "The description of the postgresql database [name] is modified =\u003e [description]",
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]",
"paramKeys": []
}
}
},
"/databases/pg/load": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从服务器获取",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Load postgresql database from remote",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlLoadDB"
}
}
],
"responses": {}
}
},
"/databases/pg/options": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 postgresql 数据库列表",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "List postgresql database names",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageInfo"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.PostgresqlOption"
}
}
}
}
}
},
"/databases/pg/password": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改 postgresql 密码",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Change postgresql password",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ChangeDBInfo"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "database_postgresqls",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update database [name] password",
"formatZH": "更新数据库 [name] 密码",
"paramKeys": []
}
}
},
"/databases/pg/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 postgresql 数据库列表分页",
"consumes": [
"application/json"
],
"tags": [
"Database Postgresql"
],
"summary": "Page postgresql databases",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PostgresqlDBSearch"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.PageResult"
}
}
}
}
},
"/databases/redis/backup/search": { "/databases/redis/backup/search": {
"post": { "post": {
"security": [ "security": [
@ -13727,7 +14142,8 @@
"type": "string", "type": "string",
"enum": [ "enum": [
"mysql", "mysql",
"mariadb" "mariadb",
"postgresql"
] ]
}, },
"value": { "value": {
@ -13864,7 +14280,8 @@
"mysql", "mysql",
"mariadb", "mariadb",
"redis", "redis",
"website" "website",
"postgresql"
] ]
} }
} }
@ -13906,7 +14323,8 @@
"mysql", "mysql",
"mariadb", "mariadb",
"redis", "redis",
"website" "website",
"postgresql"
] ]
} }
} }
@ -16381,6 +16799,193 @@
} }
} }
}, },
"dto.PostgresqlConfUpdateByFile": {
"type": "object",
"required": [
"database",
"type"
],
"properties": {
"database": {
"type": "string"
},
"file": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"postgresql",
"mariadb"
]
}
}
},
"dto.PostgresqlDBCreate": {
"type": "object",
"required": [
"database",
"from",
"name",
"password",
"permission",
"username"
],
"properties": {
"database": {
"type": "string"
},
"description": {
"type": "string"
},
"format": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"local",
"remote"
]
},
"name": {
"type": "string"
},
"password": {
"type": "string"
},
"permission": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"dto.PostgresqlDBDelete": {
"type": "object",
"required": [
"database",
"id",
"type"
],
"properties": {
"database": {
"type": "string"
},
"deleteBackup": {
"type": "boolean"
},
"forceDelete": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"type": {
"type": "string",
"enum": [
"postgresql"
]
}
}
},
"dto.PostgresqlDBDeleteCheck": {
"type": "object",
"required": [
"database",
"id",
"type"
],
"properties": {
"database": {
"type": "string"
},
"id": {
"type": "integer"
},
"type": {
"type": "string",
"enum": [
"postgresql"
]
}
}
},
"dto.PostgresqlDBSearch": {
"type": "object",
"required": [
"database",
"page",
"pageSize"
],
"properties": {
"database": {
"type": "string"
},
"info": {
"type": "string"
},
"order": {
"type": "string"
},
"orderBy": {
"type": "string"
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.PostgresqlLoadDB": {
"type": "object",
"required": [
"database",
"from",
"type"
],
"properties": {
"database": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"local",
"remote"
]
},
"type": {
"type": "string",
"enum": [
"postgresql"
]
}
}
},
"dto.PostgresqlOption": {
"type": "object",
"properties": {
"database": {
"type": "string"
},
"from": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"dto.RecordSearch": { "dto.RecordSearch": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -178,6 +178,7 @@ definitions:
enum: enum:
- mysql - mysql
- mariadb - mariadb
- postgresql
type: string type: string
value: value:
type: string type: string
@ -272,6 +273,7 @@ definitions:
- mariadb - mariadb
- redis - redis
- website - website
- postgresql
type: string type: string
required: required:
- type - type
@ -303,6 +305,7 @@ definitions:
- mariadb - mariadb
- redis - redis
- website - website
- postgresql
type: string type: string
required: required:
- source - source
@ -1980,6 +1983,134 @@ definitions:
required: required:
- serverPort - serverPort
type: object type: object
dto.PostgresqlConfUpdateByFile:
properties:
database:
type: string
file:
type: string
type:
enum:
- postgresql
- mariadb
type: string
required:
- database
- type
type: object
dto.PostgresqlDBCreate:
properties:
database:
type: string
description:
type: string
format:
type: string
from:
enum:
- local
- remote
type: string
name:
type: string
password:
type: string
permission:
type: string
username:
type: string
required:
- database
- from
- name
- password
- permission
- username
type: object
dto.PostgresqlDBDelete:
properties:
database:
type: string
deleteBackup:
type: boolean
forceDelete:
type: boolean
id:
type: integer
type:
enum:
- postgresql
type: string
required:
- database
- id
- type
type: object
dto.PostgresqlDBDeleteCheck:
properties:
database:
type: string
id:
type: integer
type:
enum:
- postgresql
type: string
required:
- database
- id
- type
type: object
dto.PostgresqlDBSearch:
properties:
database:
type: string
info:
type: string
order:
type: string
orderBy:
type: string
page:
type: integer
pageSize:
type: integer
required:
- database
- page
- pageSize
type: object
dto.PostgresqlLoadDB:
properties:
database:
type: string
from:
enum:
- local
- remote
type: string
type:
enum:
- postgresql
type: string
required:
- database
- from
- type
type: object
dto.PostgresqlOption:
properties:
database:
type: string
from:
type: string
id:
type: integer
name:
type: string
type:
type: string
type: object
dto.RecordSearch: dto.RecordSearch:
properties: properties:
detailName: detailName:
@ -7729,6 +7860,270 @@ paths:
summary: List mysql database names summary: List mysql database names
tags: tags:
- Database Mysql - Database Mysql
/databases/pg:
post:
consumes:
- application/json
description: 创建 postgresql 数据库
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PostgresqlDBCreate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Create postgresql database
tags:
- Database Postgresql
x-panel-log:
BeforeFunctions: []
bodyKeys:
- name
formatEN: create postgresql database [name]
formatZH: 创建 postgresql 数据库 [name]
paramKeys: []
/databases/pg/baseinfo:
post:
consumes:
- application/json
description: 获取 postgresql 基础信息
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.OperationWithNameAndType'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.DBBaseInfo'
security:
- ApiKeyAuth: []
summary: Load postgresql base info
tags:
- Database Postgresql
/databases/pg/conf:
post:
consumes:
- application/json
description: 上传替换 postgresql 配置文件
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PostgresqlConfUpdateByFile'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update postgresql conf by upload file
tags:
- Database Postgresql
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: update the postgresql database configuration information
formatZH: 更新 postgresql 数据库配置信息
paramKeys: []
/databases/pg/del:
post:
consumes:
- application/json
description: 删除 postgresql 数据库
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PostgresqlDBDelete'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Delete postgresql database
tags:
- Database Postgresql
x-panel-log:
BeforeFunctions:
- db: database_postgresqls
input_column: id
input_value: id
isList: false
output_column: name
output_value: name
bodyKeys:
- id
formatEN: delete postgresql database [name]
formatZH: 删除 postgresql 数据库 [name]
paramKeys: []
/databases/pg/del/check:
post:
consumes:
- application/json
description: Postgresql 数据库删除前检查
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PostgresqlDBDeleteCheck'
responses:
"200":
description: OK
schema:
items:
type: string
type: array
security:
- ApiKeyAuth: []
summary: Check before delete postgresql database
tags:
- Database Postgresql
/databases/pg/description:
post:
consumes:
- application/json
description: 更新 postgresql 数据库库描述信息
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.UpdateDescription'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update postgresql database description
tags:
- Database Postgresql
x-panel-log:
BeforeFunctions:
- db: database_postgresqls
input_column: id
input_value: id
isList: false
output_column: name
output_value: name
bodyKeys:
- id
- description
formatEN: The description of the postgresql database [name] is modified =>
[description]
formatZH: postgresql 数据库 [name] 描述信息修改 [description]
paramKeys: []
/databases/pg/load:
post:
consumes:
- application/json
description: 从服务器获取
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PostgresqlLoadDB'
responses: {}
security:
- ApiKeyAuth: []
summary: Load postgresql database from remote
tags:
- Database Postgresql
/databases/pg/options:
get:
consumes:
- application/json
description: 获取 postgresql 数据库列表
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PageInfo'
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/dto.PostgresqlOption'
type: array
security:
- ApiKeyAuth: []
summary: List postgresql database names
tags:
- Database Postgresql
/databases/pg/password:
post:
consumes:
- application/json
description: 修改 postgresql 密码
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.ChangeDBInfo'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Change postgresql password
tags:
- Database Postgresql
x-panel-log:
BeforeFunctions:
- db: database_postgresqls
input_column: id
input_value: id
isList: false
output_column: name
output_value: name
bodyKeys:
- id
formatEN: Update database [name] password
formatZH: 更新数据库 [name] 密码
paramKeys: []
/databases/pg/search:
post:
consumes:
- application/json
description: 获取 postgresql 数据库列表分页
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PostgresqlDBSearch'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.PageResult'
security:
- ApiKeyAuth: []
summary: Page postgresql databases
tags:
- Database Postgresql
/databases/redis/backup/search: /databases/redis/backup/search:
post: post:
consumes: consumes:

View File

@ -148,6 +148,11 @@ export namespace Database {
database: string; database: string;
name: string; name: string;
} }
export interface PgLoadDB {
from: string;
type: string;
database: string;
}
export interface PostgresqlDBDelete { export interface PostgresqlDBDelete {
id: number; id: number;
type: string; type: string;

View File

@ -19,9 +19,6 @@ export const addPostgresqlDB = (params: Database.PostgresqlDBCreate) => {
} }
return http.post(`/databases/pg`, request); return http.post(`/databases/pg`, request);
}; };
export const loadPostgresqlStatus = (type: string, database: string) => {
return http.post<Database.PostgresqlStatus>(`/databases/pg/status`, { type: type, name: database });
};
export const updatePostgresqlConfByFile = (params: Database.PostgresqlConfUpdateByFile) => { export const updatePostgresqlConfByFile = (params: Database.PostgresqlConfUpdateByFile) => {
return http.post(`/databases/pg/conf`, params); return http.post(`/databases/pg/conf`, params);
}; };
@ -31,6 +28,9 @@ export const searchPostgresqlDBs = (params: Database.SearchDBWithPage) => {
export const updatePostgresqlDescription = (params: DescriptionUpdate) => { export const updatePostgresqlDescription = (params: DescriptionUpdate) => {
return http.post(`/databases/pg/description`, params); return http.post(`/databases/pg/description`, params);
}; };
export const loadPgFromRemote = (params: Database.PgLoadDB) => {
return http.post(`/databases/pg/load`, params);
};
export const deleteCheckPostgresqlDB = (params: Database.PostgresqlDBDeleteCheck) => { export const deleteCheckPostgresqlDB = (params: Database.PostgresqlDBDeleteCheck) => {
return http.post<Array<string>>(`/databases/pg/del/check`, params); return http.post<Array<string>>(`/databases/pg/del/check`, params);
}; };
@ -44,6 +44,8 @@ export const updatePostgresqlPassword = (params: Database.ChangeInfo) => {
export const deletePostgresqlDB = (params: Database.PostgresqlDBDelete) => { export const deletePostgresqlDB = (params: Database.PostgresqlDBDelete) => {
return http.post(`/databases/pg/del`, params); return http.post(`/databases/pg/del`, params);
}; };
// mysql
export const addMysqlDB = (params: Database.MysqlDBCreate) => { export const addMysqlDB = (params: Database.MysqlDBCreate) => {
let request = deepCopy(params) as Database.MysqlDBCreate; let request = deepCopy(params) as Database.MysqlDBCreate;
if (request.password) { if (request.password) {

View File

@ -70,6 +70,14 @@
> >
{{ $t('database.databaseConnInfo') }} {{ $t('database.databaseConnInfo') }}
</el-button> </el-button>
<el-button
v-if="currentDB && (currentDB.from !== 'local' || postgresqlStatus === 'Running')"
@click="loadDB"
type="primary"
plain
>
{{ $t('database.loadFromRemote') }}
</el-button>
<el-button @click="goRemoteDB" type="primary" plain> <el-button @click="goRemoteDB" type="primary" plain>
{{ $t('database.remoteDB') }} {{ $t('database.remoteDB') }}
</el-button> </el-button>
@ -218,6 +226,7 @@ import { onMounted, reactive, ref } from 'vue';
import { import {
deleteCheckPostgresqlDB, deleteCheckPostgresqlDB,
listDatabases, listDatabases,
loadPgFromRemote,
searchPostgresqlDBs, searchPostgresqlDBs,
updatePostgresqlDescription, updatePostgresqlDescription,
} from '@/api/modules/database'; } from '@/api/modules/database';
@ -343,6 +352,29 @@ const search = async (column?: any) => {
paginationConfig.total = res.data.total; paginationConfig.total = res.data.total;
}; };
const loadDB = async () => {
ElMessageBox.confirm(i18n.global.t('database.loadFromRemoteHelper'), i18n.global.t('commons.msg.infoTitle'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
let params = {
from: currentDB.value.from,
type: currentDB.value.type,
database: currentDBName.value,
};
await loadPgFromRemote(params)
.then(() => {
loading.value = false;
search();
})
.catch(() => {
loading.value = false;
});
});
};
const goRouter = async (target: string) => { const goRouter = async (target: string) => {
if (target === 'app') { if (target === 'app') {
router.push({ name: 'AppAll', query: { install: 'postgresql' } }); router.push({ name: 'AppAll', query: { install: 'postgresql' } });

View File

@ -7,14 +7,6 @@
<el-button type="primary" :plain="activeName !== 'conf'" @click="jumpToConf"> <el-button type="primary" :plain="activeName !== 'conf'" @click="jumpToConf">
{{ $t('database.confChange') }} {{ $t('database.confChange') }}
</el-button> </el-button>
<el-button
type="primary"
:disabled="postgresqlStatus !== 'Running'"
:plain="activeName !== 'status'"
@click="activeName = 'status'"
>
{{ $t('database.currentStatus') }}
</el-button>
<el-button type="primary" :plain="activeName !== 'port'" @click="activeName = 'port'"> <el-button type="primary" :plain="activeName !== 'port'" @click="activeName = 'port'">
{{ $t('commons.table.port') }} {{ $t('commons.table.port') }}
</el-button> </el-button>
@ -49,23 +41,10 @@
:extensions="extensions" :extensions="extensions"
v-model="postgresqlConf" v-model="postgresqlConf"
/> />
<el-button type="primary" style="margin-top: 10px" @click="onSaveConf"> <el-button type="primary" style="margin-top: 10px" @click="onSaveConf">
{{ $t('commons.button.save') }} {{ $t('commons.button.save') }}
</el-button> </el-button>
<el-row>
<el-col :span="8">
<el-alert
v-if="useOld"
style="margin-top: 10px"
:title="$t('app.defaultConfigHelper')"
type="info"
:closable="false"
></el-alert>
</el-col>
</el-row>
</div> </div>
<Status v-show="activeName === 'status'" ref="statusRef" />
<div v-show="activeName === 'port'"> <div v-show="activeName === 'port'">
<el-form :model="baseInfo" ref="panelFormRef" label-position="top"> <el-form :model="baseInfo" ref="panelFormRef" label-position="top">
<el-row> <el-row>
@ -114,7 +93,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import ContainerLog from '@/components/container-log/index.vue'; import ContainerLog from '@/components/container-log/index.vue';
import Status from '@/views/database/postgresql/setting/status/index.vue';
import ConfirmDialog from '@/components/confirm-dialog/index.vue'; import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { Codemirror } from 'vue-codemirror'; import { Codemirror } from 'vue-codemirror';
@ -143,10 +121,6 @@ const panelFormRef = ref<FormInstance>();
const postgresqlConf = ref(); const postgresqlConf = ref();
const upgradeVisible = ref(); const upgradeVisible = ref();
const useOld = ref(false);
const statusRef = ref();
const postgresqlName = ref(); const postgresqlName = ref();
const postgresqlStatus = ref(); const postgresqlStatus = ref();
const postgresqlVersion = ref(); const postgresqlVersion = ref();
@ -214,7 +188,6 @@ const onSubmitChangeConf = async () => {
loading.value = true; loading.value = true;
await updatePostgresqlConfByFile(param) await updatePostgresqlConfByFile(param)
.then(() => { .then(() => {
useOld.value = false;
loading.value = false; loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
}) })
@ -247,7 +220,6 @@ const loadBaseInfo = async () => {
}; };
const loadPostgresqlConf = async () => { const loadPostgresqlConf = async () => {
useOld.value = false;
await loadDatabaseFile(props.type + '-conf', props.database) await loadDatabaseFile(props.type + '-conf', props.database)
.then((res) => { .then((res) => {
loading.value = false; loading.value = false;
@ -269,9 +241,6 @@ const onLoadInfo = async () => {
postgresqlStatus.value = res.data.status; postgresqlStatus.value = res.data.status;
postgresqlVersion.value = res.data.version; postgresqlVersion.value = res.data.version;
loadBaseInfo(); loadBaseInfo();
if (postgresqlStatus.value === 'Running') {
statusRef.value!.acceptParams({ type: props.type, database: props.database });
}
}); });
}; };

View File

@ -1,168 +0,0 @@
<template>
<div>
<el-form label-position="top">
<span class="title">{{ $t('database.baseParam') }}</span>
<el-divider class="divider" />
<el-row type="flex" justify="center" style="margin-left: 50px" :gutter="20">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">{{ $t('database.runTime') }}</span>
</template>
<span class="status-count">{{ postgresqlStatus.uptime }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">{{ $t('database.connections') }}</span>
</template>
<span class="status-count">{{ postgresqlStatus.max_connections }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">{{ $t('database.version') }}</span>
</template>
<span class="status-count">{{ postgresqlStatus.version }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">AUTOVACUUM</span>
</template>
<span class="status-count">{{ postgresqlStatus.autovacuum }}</span>
</el-form-item>
</el-col>
</el-row>
<span class="title">{{ $t('database.performanceParam') }}</span>
<el-divider class="divider" />
<el-row type="flex" style="margin-left: 50px" justify="center" :gutter="20">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">{{ $t('database.connInfo') }}</span>
</template>
<span class="status-count">{{ postgresqlStatus.current_connections }}</span>
<span class="input-help">{{ $t('database.connInfoHelper') }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">{{ $t('database.cacheHit') }}</span>
</template>
<span class="status-count">{{ postgresqlStatus.hit_ratio }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item style="width: 25%"></el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item style="width: 25%"></el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">SHARED_BUFFERS</span>
</template>
<span class="status-count">{{ postgresqlStatus.shared_buffers }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">BUFFERS_CLEAN</span>
</template>
<span class="status-count">{{ postgresqlStatus.buffers_clean }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">MAXWRITTEN_CLEAN</span>
</template>
<span class="status-count">{{ postgresqlStatus.maxwritten_clean }}</span>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">BUFFERS_BACKEND_FSYNC</span>
</template>
<span class="status-count">{{ postgresqlStatus.buffers_backend_fsync }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { loadPostgresqlStatus } from '@/api/modules/database';
import { reactive } from 'vue';
const postgresqlStatus = reactive({
uptime: '',
version: '',
max_connections: '',
autovacuum: '',
current_connections: '',
hit_ratio: '',
shared_buffers: '',
buffers_clean: '',
maxwritten_clean: '',
buffers_backend_fsync: '',
});
const currentDB = reactive({
type: '',
database: '',
});
interface DialogProps {
type: string;
database: string;
}
const acceptParams = (params: DialogProps): void => {
currentDB.type = params.type;
currentDB.database = params.database;
loadStatus();
};
const loadStatus = async () => {
const res = await loadPostgresqlStatus(currentDB.type, currentDB.database);
postgresqlStatus.uptime = res.data.uptime;
postgresqlStatus.version = res.data.version;
postgresqlStatus.max_connections = res.data.max_connections;
postgresqlStatus.autovacuum = res.data.autovacuum;
postgresqlStatus.current_connections = res.data.current_connections;
postgresqlStatus.hit_ratio = res.data.hit_ratio;
postgresqlStatus.shared_buffers = res.data.shared_buffers;
postgresqlStatus.buffers_clean = res.data.buffers_clean;
postgresqlStatus.maxwritten_clean = res.data.maxwritten_clean;
postgresqlStatus.buffers_backend_fsync = res.data.buffers_backend_fsync;
};
defineExpose({
acceptParams,
});
</script>
<style lang="scss" scoped>
.divider {
display: block;
height: 1px;
width: 100%;
margin: 12px 0;
border-top: 1px var(--el-border-color) var(--el-border-style);
}
.title {
font-size: 20px;
font-weight: 500;
margin-left: 50px;
}
</style>