mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 00:09:16 +08:00
feat: 计划任务增加 pg 备份 (#3520)
This commit is contained in:
parent
9713b32ba8
commit
6130d4e026
@ -112,6 +112,27 @@ func (b *BaseApi) ListDatabase(c *gin.Context) {
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary List databases
|
||||
// @Description 获取数据库列表
|
||||
// @Success 200 {array} dto.DatabaseItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/db/item/:type [get]
|
||||
func (b *BaseApi) LoadDatabaseItems(c *gin.Context) {
|
||||
dbType, err := helper.GetStrParamByKey(c, "type")
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
list, err := databaseService.LoadItems(dbType)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Get databases
|
||||
// @Description 获取远程数据库
|
||||
@ -233,4 +254,4 @@ func (b *BaseApi) LoadDatabaseFile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ type CronjobCreate struct {
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
@ -40,6 +41,7 @@ type CronjobUpdate struct {
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
@ -84,6 +86,7 @@ type CronjobInfo struct {
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
|
@ -263,6 +263,13 @@ type DatabaseOption struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type DatabaseItem struct {
|
||||
ID uint `json:"id"`
|
||||
From string `json:"from"`
|
||||
Database string `json:"database"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type DatabaseCreate struct {
|
||||
Name string `json:"name" validate:"required,max=256"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
|
@ -19,6 +19,7 @@ type Cronjob struct {
|
||||
Script string `gorm:"longtext" json:"script"`
|
||||
Website string `gorm:"type:varchar(64)" json:"website"`
|
||||
AppID string `gorm:"type:varchar(64)" json:"appID"`
|
||||
DBType string `gorm:"type:varchar(64)" json:"dbType"`
|
||||
DBName string `gorm:"type:varchar(64)" json:"dbName"`
|
||||
URL string `gorm:"type:varchar(256)" json:"url"`
|
||||
SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"`
|
||||
|
@ -794,7 +794,7 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
|
||||
envKey := ""
|
||||
switch param {
|
||||
case "password":
|
||||
if appKey == "mysql" || appKey == "mariadb" {
|
||||
if appKey == "mysql" || appKey == "mariadb" || appKey == "postgresql" {
|
||||
envKey = "PANEL_DB_ROOT_PASSWORD="
|
||||
} else {
|
||||
envKey = "PANEL_REDIS_ROOT_PASSWORD="
|
||||
|
@ -264,6 +264,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
upMap["app_id"] = req.AppID
|
||||
upMap["website"] = req.Website
|
||||
upMap["exclusion_rules"] = req.ExclusionRules
|
||||
upMap["db_type"] = req.DBType
|
||||
upMap["db_name"] = req.DBName
|
||||
upMap["url"] = req.URL
|
||||
upMap["source_dir"] = req.SourceDir
|
||||
|
@ -3,8 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
@ -12,6 +10,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@ -273,19 +274,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, backup model.Back
|
||||
return paths, err
|
||||
}
|
||||
|
||||
var dbs []model.DatabaseMysql
|
||||
if cronjob.DBName == "all" {
|
||||
dbs, err = mysqlRepo.List()
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
} else {
|
||||
itemID, _ := (strconv.Atoi(cronjob.DBName))
|
||||
dbs, err = mysqlRepo.List(commonRepo.WithByID(uint(itemID)))
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
dbs := loadDbsForJob(cronjob)
|
||||
|
||||
var client cloud_storage.CloudStorageClient
|
||||
if backup.Type != "LOCAL" {
|
||||
@ -297,17 +286,21 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, backup model.Back
|
||||
|
||||
for _, dbInfo := range dbs {
|
||||
var record model.BackupRecord
|
||||
|
||||
database, _ := databaseRepo.Get(commonRepo.WithByName(dbInfo.MysqlName))
|
||||
record.Type = database.Type
|
||||
record.Type = dbInfo.DBType
|
||||
record.Source = "LOCAL"
|
||||
record.BackupType = backup.Type
|
||||
|
||||
record.Name = dbInfo.MysqlName
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", database.Type, record.Name, dbInfo.Name))
|
||||
record.Name = dbInfo.Database
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405"))
|
||||
if err = handleMysqlBackup(dbInfo.MysqlName, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
if err = handleMysqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
} else {
|
||||
if err = handlePostgresqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
|
||||
record.DetailName = dbInfo.Name
|
||||
@ -717,3 +710,52 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
|
||||
func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log"
|
||||
}
|
||||
|
||||
type databaseHelper struct {
|
||||
DBType string
|
||||
Database string
|
||||
Name string
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||
var dbs []databaseHelper
|
||||
if cronjob.DBName == "all" {
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItems, _ := mysqlRepo.List()
|
||||
for _, mysql := range mysqlItems {
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysql.MysqlName,
|
||||
Name: mysql.Name,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
pgItems, _ := postgresqlRepo.List()
|
||||
for _, pg := range pgItems {
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pg.PostgresqlName,
|
||||
Name: pg.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
itemID, _ := (strconv.Atoi(cronjob.DBName))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ type IDatabaseService interface {
|
||||
DeleteCheck(id uint) ([]string, error)
|
||||
Delete(req dto.DatabaseDelete) error
|
||||
List(dbType string) ([]dto.DatabaseOption, error)
|
||||
LoadItems(dbType string) ([]dto.DatabaseItem, error)
|
||||
}
|
||||
|
||||
func NewIDatabaseService() IDatabaseService {
|
||||
@ -80,6 +81,35 @@ func (u *DatabaseService) List(dbType string) ([]dto.DatabaseOption, error) {
|
||||
return datas, err
|
||||
}
|
||||
|
||||
func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) {
|
||||
dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType))
|
||||
var datas []dto.DatabaseItem
|
||||
for _, db := range dbs {
|
||||
if dbType == "postgresql" {
|
||||
items, _ := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(db.Name))
|
||||
for _, item := range items {
|
||||
var dItem dto.DatabaseItem
|
||||
if err := copier.Copy(&dItem, &item); err != nil {
|
||||
continue
|
||||
}
|
||||
dItem.Database = db.Name
|
||||
datas = append(datas, dItem)
|
||||
}
|
||||
} else {
|
||||
items, _ := mysqlRepo.List(mysqlRepo.WithByMysqlName(db.Name))
|
||||
for _, item := range items {
|
||||
var dItem dto.DatabaseItem
|
||||
if err := copier.Copy(&dItem, &item); err != nil {
|
||||
continue
|
||||
}
|
||||
dItem.Database = db.Name
|
||||
datas = append(datas, dItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return datas, err
|
||||
}
|
||||
|
||||
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
|
@ -335,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)
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ func Init() {
|
||||
migrations.AddTableImageRepo,
|
||||
migrations.AddTableWebsite,
|
||||
migrations.AddTableDatabaseMysql,
|
||||
migrations.AddTableDatabasePostgresql,
|
||||
migrations.AddTableSnap,
|
||||
migrations.AddDefaultGroup,
|
||||
migrations.AddTableRuntime,
|
||||
@ -64,6 +63,7 @@ func Init() {
|
||||
migrations.UpdateWebsiteBackupRecord,
|
||||
|
||||
migrations.AddTablePHPExtensions,
|
||||
migrations.AddTableDatabasePostgresql,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -214,12 +214,6 @@ var AddTableDatabaseMysql = &gormigrate.Migration{
|
||||
return tx.AutoMigrate(&model.DatabaseMysql{})
|
||||
},
|
||||
}
|
||||
var AddTableDatabasePostgresql = &gormigrate.Migration{
|
||||
ID: "20231224-add-table-database_postgresql",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.DatabasePostgresql{})
|
||||
},
|
||||
}
|
||||
var AddTableWebsite = &gormigrate.Migration{
|
||||
ID: "20201009-add-table-website",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
@ -116,3 +116,33 @@ var AddTablePHPExtensions = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddTableDatabasePostgresql = &gormigrate.Migration{
|
||||
ID: "20231225-add-table-database_postgresql",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.DatabasePostgresql{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.AutoMigrate(&model.Cronjob{}); err != nil {
|
||||
return err
|
||||
}
|
||||
var jobs []model.Cronjob
|
||||
if err := tx.Where("type == ?", "database").Find(&jobs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, job := range jobs {
|
||||
var db model.DatabaseMysql
|
||||
if err := tx.Where("id == ?", job.DBName).First(&db).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var database model.Database
|
||||
if err := tx.Where("name == ?", db.MysqlName).First(&database).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.Cronjob{}).Where("id = ?", job.ID).Update("db_type", database.Type).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/db", baseApi.CreateDatabase)
|
||||
cmdRouter.GET("/db/:name", baseApi.GetDatabase)
|
||||
cmdRouter.GET("/db/list/:type", baseApi.ListDatabase)
|
||||
cmdRouter.GET("/db/item/:type", baseApi.LoadDatabaseItems)
|
||||
cmdRouter.POST("/db/update", baseApi.UpdateDatabase)
|
||||
cmdRouter.POST("/db/search", baseApi.SearchDatabase)
|
||||
cmdRouter.POST("/db/del/check", baseApi.DeleteCheckDatabase)
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
type PostgresqlClient interface {
|
||||
Create(info client.CreateInfo) error
|
||||
Delete(info client.DeleteInfo) error
|
||||
ReloadConf() error
|
||||
ChangePassword(info client.PasswordChangeInfo) error
|
||||
|
||||
Backup(info client.BackupInfo) error
|
||||
@ -26,7 +25,7 @@ type PostgresqlClient interface {
|
||||
|
||||
func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) {
|
||||
if conn.From == "local" {
|
||||
connArgs := []string{"exec", conn.Address, "psql", "-U", conn.Username, "-c"}
|
||||
connArgs := []string{"exec", conn.Address, "psql", "-t", "-U", conn.Username, "-c"}
|
||||
return client.NewLocal(connArgs, conn.Address, conn.Username, conn.Password, conn.Database), nil
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ func NewLocal(command []string, containerName, username, password, database stri
|
||||
}
|
||||
|
||||
func (r *Local) Create(info CreateInfo) error {
|
||||
createSql := fmt.Sprintf("CREATE DATABASE %s", info.Name)
|
||||
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)
|
||||
@ -39,7 +39,7 @@ func (r *Local) Create(info CreateInfo) error {
|
||||
}
|
||||
|
||||
if err := r.CreateUser(info, true); err != nil {
|
||||
_ = r.ExecSQL(fmt.Sprintf("DROP DATABASE %s", info.Name), info.Timeout)
|
||||
_ = r.ExecSQL(fmt.Sprintf("DROP DATABASE \"%s\"", info.Name), info.Timeout)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ func (r *Local) Create(info CreateInfo) error {
|
||||
}
|
||||
|
||||
func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error {
|
||||
createSql := fmt.Sprintf("CREATE USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Username)
|
||||
createSql := fmt.Sprintf("CREATE USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Password)
|
||||
if err := r.ExecSQL(createSql, info.Timeout); err != nil {
|
||||
if strings.Contains(strings.ToLower(err.Error()), "already exists") {
|
||||
return buserr.New(constant.ErrUserIsExist)
|
||||
@ -61,7 +61,7 @@ func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
grantStr := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", info.Name, info.Username)
|
||||
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{
|
||||
@ -77,12 +77,12 @@ func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error {
|
||||
|
||||
func (r *Local) Delete(info DeleteInfo) error {
|
||||
if len(info.Name) != 0 {
|
||||
dropSql := fmt.Sprintf("DROP DATABASE %s", info.Name)
|
||||
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)
|
||||
dropSql := fmt.Sprintf("DROP USER \"%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)
|
||||
@ -147,10 +147,6 @@ func (r *Local) Recover(info RecoverInfo) error {
|
||||
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)
|
||||
@ -158,10 +154,11 @@ func (r *Local) SyncDB() ([]SyncDBInfo, error) {
|
||||
return datas, err
|
||||
}
|
||||
for _, line := range lines {
|
||||
if line == "postgres" || line == "template1" || line == "template0" || line == r.Username {
|
||||
itemLine := strings.TrimLeft(line, " ")
|
||||
if len(itemLine) == 0 || itemLine == "postgres" || itemLine == "template1" || itemLine == "template0" || itemLine == r.Username {
|
||||
continue
|
||||
}
|
||||
datas = append(datas, SyncDBInfo{Name: line, From: "local", PostgresqlName: r.Database})
|
||||
datas = append(datas, SyncDBInfo{Name: itemLine, From: "local", PostgresqlName: r.Database})
|
||||
}
|
||||
return datas, nil
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func NewRemote(db Remote) *Remote {
|
||||
return &db
|
||||
}
|
||||
func (r *Remote) Create(info CreateInfo) error {
|
||||
createSql := fmt.Sprintf("CREATE DATABASE %s", info.Name)
|
||||
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)
|
||||
@ -62,7 +62,7 @@ func (r *Remote) CreateUser(info CreateInfo, withDeleteDB bool) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
grantSql := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", info.Name, info.Username)
|
||||
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{
|
||||
@ -79,12 +79,12 @@ func (r *Remote) CreateUser(info CreateInfo, withDeleteDB bool) error {
|
||||
|
||||
func (r *Remote) Delete(info DeleteInfo) error {
|
||||
if len(info.Name) != 0 {
|
||||
dropSql := fmt.Sprintf("DROP DATABASE %s", info.Name)
|
||||
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)
|
||||
dropSql := fmt.Sprintf("DROP USER \"%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)
|
||||
@ -97,9 +97,6 @@ func (r *Remote) Delete(info DeleteInfo) 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)
|
||||
}
|
||||
func (r *Remote) ReloadConf() error {
|
||||
return r.ExecSQL("SELECT pg_reload_conf()", 5)
|
||||
}
|
||||
|
||||
func (r *Remote) Backup(info BackupInfo) error {
|
||||
fileOp := files.NewFileOp()
|
||||
@ -150,7 +147,7 @@ func (r *Remote) Recover(info RecoverInfo) error {
|
||||
}()
|
||||
}
|
||||
recoverCommand := exec.Command("bash", "-c",
|
||||
fmt.Sprintf("docker run --rm --net=host -i postgres:alpine /bin/bash -c 'PGPASSWORD=%s pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -U %s -d %s --role=%s' < %s",
|
||||
fmt.Sprintf("docker run --rm --net=host -i postgres:16.1-alpine /bin/bash -c 'PGPASSWORD=%s pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -U %s -d %s --role=%s' < %s",
|
||||
r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName))
|
||||
pipe, _ := recoverCommand.StdoutPipe()
|
||||
stderrPipe, _ := recoverCommand.StderrPipe()
|
||||
@ -191,7 +188,7 @@ func (r *Remote) SyncDB() ([]SyncDBInfo, error) {
|
||||
if err := rows.Scan(&dbName); err != nil {
|
||||
continue
|
||||
}
|
||||
if dbName == "postgres" || dbName == "template1" || dbName == "template0" || dbName == r.User {
|
||||
if len(dbName) == 0 || dbName == "postgres" || dbName == "template1" || dbName == "template0" || dbName == r.User {
|
||||
continue
|
||||
}
|
||||
datas = append(datas, SyncDBInfo{Name: dbName, From: r.From, PostgresqlName: r.Database})
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@ -4296,6 +4296,31 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/db/item/:type": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取数据库列表",
|
||||
"tags": [
|
||||
"Database"
|
||||
],
|
||||
"summary": "List databases",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.DatabaseItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/db/list/:type": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -14889,6 +14914,9 @@ const docTemplate = `{
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -14974,6 +15002,9 @@ const docTemplate = `{
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -15393,6 +15424,23 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.DatabaseItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.DatabaseOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -4289,6 +4289,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/db/item/:type": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取数据库列表",
|
||||
"tags": [
|
||||
"Database"
|
||||
],
|
||||
"summary": "List databases",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.DatabaseItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/db/list/:type": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -14882,6 +14907,9 @@
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -14967,6 +14995,9 @@
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -15386,6 +15417,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.DatabaseItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.DatabaseOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -586,6 +586,8 @@ definitions:
|
||||
type: integer
|
||||
dbName:
|
||||
type: string
|
||||
dbType:
|
||||
type: string
|
||||
exclusionRules:
|
||||
type: string
|
||||
hour:
|
||||
@ -644,6 +646,8 @@ definitions:
|
||||
type: integer
|
||||
dbName:
|
||||
type: string
|
||||
dbType:
|
||||
type: string
|
||||
exclusionRules:
|
||||
type: string
|
||||
hour:
|
||||
@ -928,6 +932,17 @@ definitions:
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
dto.DatabaseItem:
|
||||
properties:
|
||||
database:
|
||||
type: string
|
||||
from:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
dto.DatabaseOption:
|
||||
properties:
|
||||
address:
|
||||
@ -7695,6 +7710,21 @@ paths:
|
||||
formatEN: delete database [names]
|
||||
formatZH: 删除远程数据库 [names]
|
||||
paramKeys: []
|
||||
/databases/db/item/:type:
|
||||
get:
|
||||
description: 获取数据库列表
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.DatabaseItem'
|
||||
type: array
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: List databases
|
||||
tags:
|
||||
- Database
|
||||
/databases/db/list/:type:
|
||||
get:
|
||||
description: 获取远程数据库列表
|
||||
|
@ -18,6 +18,7 @@ export namespace Cronjob {
|
||||
appID: string;
|
||||
website: string;
|
||||
exclusionRules: string;
|
||||
dbType: string;
|
||||
dbName: string;
|
||||
url: string;
|
||||
sourceDir: string;
|
||||
@ -40,6 +41,7 @@ export namespace Cronjob {
|
||||
script: string;
|
||||
website: string;
|
||||
exclusionRules: string;
|
||||
dbType: string;
|
||||
dbName: string;
|
||||
url: string;
|
||||
sourceDir: string;
|
||||
@ -59,6 +61,7 @@ export namespace Cronjob {
|
||||
script: string;
|
||||
website: string;
|
||||
exclusionRules: string;
|
||||
dbType: string;
|
||||
dbName: string;
|
||||
url: string;
|
||||
sourceDir: string;
|
||||
|
@ -141,12 +141,10 @@ export namespace Database {
|
||||
File: string;
|
||||
Position: number;
|
||||
}
|
||||
export interface MysqlOption {
|
||||
id: number;
|
||||
export interface PgLoadDB {
|
||||
from: string;
|
||||
type: string;
|
||||
database: string;
|
||||
name: string;
|
||||
}
|
||||
export interface PgLoadDB {
|
||||
from: string;
|
||||
@ -316,6 +314,12 @@ export namespace Database {
|
||||
version: string;
|
||||
address: string;
|
||||
}
|
||||
export interface DbItem {
|
||||
id: number;
|
||||
from: string;
|
||||
database: string;
|
||||
name: string;
|
||||
}
|
||||
export interface DatabaseCreate {
|
||||
name: string;
|
||||
version: string;
|
||||
|
@ -101,9 +101,6 @@ export const loadMysqlStatus = (type: string, database: string) => {
|
||||
export const loadRemoteAccess = (type: string, database: string) => {
|
||||
return http.post<boolean>(`/databases/remote`, { type: type, name: database });
|
||||
};
|
||||
export const loadDBOptions = () => {
|
||||
return http.get<Array<Database.MysqlOption>>(`/databases/options`);
|
||||
};
|
||||
|
||||
// redis
|
||||
export const loadRedisStatus = () => {
|
||||
@ -141,6 +138,9 @@ export const searchDatabases = (params: Database.SearchDatabasePage) => {
|
||||
export const listDatabases = (type: string) => {
|
||||
return http.get<Array<Database.DatabaseOption>>(`/databases/db/list/${type}`);
|
||||
};
|
||||
export const listDbItems = (type: string) => {
|
||||
return http.get<Array<Database.DbItem>>(`/databases/db/item/${type}`);
|
||||
};
|
||||
export const checkDatabase = (params: Database.DatabaseCreate) => {
|
||||
let request = deepCopy(params) as Database.DatabaseCreate;
|
||||
if (request.ssl) {
|
||||
|
@ -136,7 +136,11 @@
|
||||
prop="website"
|
||||
>
|
||||
<el-select class="selectClass" v-model="dialogData.rowData!.website">
|
||||
<el-option :label="$t('commons.table.all')" value="all" />
|
||||
<el-option
|
||||
:disabled="websiteOptions.length === 0"
|
||||
:label="$t('commons.table.all')"
|
||||
value="all"
|
||||
/>
|
||||
<el-option v-for="item in websiteOptions" :key="item" :value="item" :label="item" />
|
||||
</el-select>
|
||||
<span class="input-help" v-if="dialogData.rowData!.type === 'cutWebsiteLog'">
|
||||
@ -147,7 +151,11 @@
|
||||
<div v-if="dialogData.rowData!.type === 'app'">
|
||||
<el-form-item :label="$t('cronjob.app')" prop="appID">
|
||||
<el-select class="selectClass" clearable v-model="dialogData.rowData!.appID">
|
||||
<el-option :label="$t('commons.table.all')" value="all" />
|
||||
<el-option
|
||||
:disabled="appOptions.length === 0"
|
||||
:label="$t('commons.table.all')"
|
||||
value="all"
|
||||
/>
|
||||
<div v-for="item in appOptions" :key="item.id">
|
||||
<el-option :value="item.id + ''" :label="item.name">
|
||||
<span>{{ item.name }}</span>
|
||||
@ -161,11 +169,22 @@
|
||||
</div>
|
||||
|
||||
<div v-if="dialogData.rowData!.type === 'database'">
|
||||
<el-form-item :label="$t('cronjob.database')">
|
||||
<el-radio-group v-model="dialogData.rowData!.dbType" @change="loadDatabases">
|
||||
<el-radio label="mysql">MySQL</el-radio>
|
||||
<el-radio label="mariadb">Mariadb</el-radio>
|
||||
<el-radio label="postgresql">PostgreSQL</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('cronjob.database')" prop="dbName">
|
||||
<el-select class="selectClass" clearable v-model="dialogData.rowData!.dbName">
|
||||
<el-option :label="$t('commons.table.all')" value="all" />
|
||||
<el-option
|
||||
v-for="item in mysqlInfo.dbs"
|
||||
:disabled="dbInfo.dbs.length === 0"
|
||||
:label="$t('commons.table.all')"
|
||||
value="all"
|
||||
/>
|
||||
<el-option
|
||||
v-for="item in dbInfo.dbs"
|
||||
:key="item.id"
|
||||
:value="item.id + ''"
|
||||
:label="item.name"
|
||||
@ -174,9 +193,6 @@
|
||||
<el-tag class="tagClass">
|
||||
{{ item.from === 'local' ? $t('database.local') : $t('database.remote') }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ item.type === 'mysql' ? 'MySQL' : 'MariaDB' }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@ -277,7 +293,7 @@ import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
import { addCronjob, editCronjob } from '@/api/modules/cronjob';
|
||||
import { loadDBOptions } from '@/api/modules/database';
|
||||
import { listDbItems } from '@/api/modules/database';
|
||||
import { GetWebsiteOptions } from '@/api/modules/website';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
@ -297,10 +313,12 @@ const drawerVisible = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
if (dialogData.value.title === 'create') {
|
||||
changeType();
|
||||
dialogData.value.rowData.dbType = 'mysql';
|
||||
}
|
||||
title.value = i18n.global.t('cronjob.' + dialogData.value.title);
|
||||
if (dialogData.value?.rowData?.exclusionRules) {
|
||||
@ -310,11 +328,11 @@ const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value.rowData.inContainer = true;
|
||||
}
|
||||
drawerVisible.value = true;
|
||||
checkMysqlInstalled();
|
||||
loadBackups();
|
||||
loadAppInstalls();
|
||||
loadWebsites();
|
||||
loadContainers();
|
||||
loadDatabases();
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
@ -333,11 +351,11 @@ const websiteOptions = ref();
|
||||
const backupOptions = ref();
|
||||
const appOptions = ref();
|
||||
|
||||
const mysqlInfo = reactive({
|
||||
const dbInfo = reactive({
|
||||
isExist: false,
|
||||
name: '',
|
||||
version: '',
|
||||
dbs: [] as Array<Database.MysqlOption>,
|
||||
dbs: [] as Array<Database.DbItem>,
|
||||
});
|
||||
|
||||
const verifySpec = (rule: any, value: any, callback: any) => {
|
||||
@ -459,6 +477,11 @@ const hasHour = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const loadDatabases = async () => {
|
||||
const data = await listDbItems(dialogData.value.rowData.dbType);
|
||||
dbInfo.dbs = data.data || [];
|
||||
};
|
||||
|
||||
const changeType = () => {
|
||||
switch (dialogData.value.rowData!.type) {
|
||||
case 'shell':
|
||||
@ -545,11 +568,6 @@ const loadContainers = async () => {
|
||||
containerOptions.value = res.data || [];
|
||||
};
|
||||
|
||||
const checkMysqlInstalled = async () => {
|
||||
const data = await loadDBOptions();
|
||||
mysqlInfo.dbs = data.data || [];
|
||||
};
|
||||
|
||||
function isBackup() {
|
||||
return (
|
||||
dialogData.value.rowData!.type === 'app' ||
|
||||
|
@ -406,7 +406,7 @@ import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
import { loadDBOptions } from '@/api/modules/database';
|
||||
import { listDbItems } from '@/api/modules/database';
|
||||
import { ListAppInstalled } from '@/api/modules/app';
|
||||
|
||||
const loading = ref();
|
||||
@ -440,7 +440,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
recordShow.value = true;
|
||||
dialogData.value = params;
|
||||
if (dialogData.value.rowData.type === 'database') {
|
||||
const data = await loadDBOptions();
|
||||
const data = await listDbItems('mysql,mariadb,postgresql');
|
||||
let itemDBs = data.data || [];
|
||||
for (const item of itemDBs) {
|
||||
if (item.id == dialogData.value.rowData.dbName) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('database.containerConn')">
|
||||
<el-tag>
|
||||
{{ form.serviceName + form.port }}
|
||||
{{ form.serviceName + ':' + form.port }}
|
||||
</el-tag>
|
||||
<CopyButton :content="form.serviceName + ':' + form.port" type="icon" />
|
||||
<span class="input-help">
|
||||
|
@ -6,6 +6,12 @@
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('app.forceDelete')" />
|
||||
<span class="input-help">
|
||||
{{ $t('app.forceDeleteHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteReq.deleteBackup" :label="$t('app.deleteBackup')" />
|
||||
<span class="input-help">
|
||||
|
Loading…
x
Reference in New Issue
Block a user