mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-23 18:49:21 +08:00
parent
14e2ced3da
commit
46e7431c4c
@ -300,6 +300,11 @@ func (b *BaseApi) Backup(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
case constant.AppPostgresql:
|
||||
if err := backupService.PostgresqlBackup(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
case "website":
|
||||
if err := backupService.WebsiteBackup(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
@ -343,6 +348,11 @@ func (b *BaseApi) Recover(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
case constant.AppPostgresql:
|
||||
if err := backupService.PostgresqlRecover(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
case "website":
|
||||
if err := backupService.WebsiteRecover(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
@ -383,6 +393,11 @@ func (b *BaseApi) RecoverByUpload(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
case constant.AppPostgresql:
|
||||
if err := backupService.PostgresqlRecoverByUpload(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
case "app":
|
||||
if err := backupService.AppRecover(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
|
@ -206,3 +206,31 @@ func (b *BaseApi) UpdateDatabase(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Load Database file
|
||||
// @Description 获取数据库文件
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithNameAndType true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/load/file [post]
|
||||
func (b *BaseApi) LoadDatabaseFile(c *gin.Context) {
|
||||
var req dto.OperationWithNameAndType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
var content string
|
||||
var err error
|
||||
switch req.Name {
|
||||
case constant.AppPostgresql:
|
||||
content, err = postgresqlService.LoadDatabaseFile(req)
|
||||
default:
|
||||
content, err = mysqlService.LoadDatabaseFile(req)
|
||||
}
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
@ -327,28 +327,6 @@ func (b *BaseApi) LoadBaseinfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Load Database file
|
||||
// @Description 获取数据库文件
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithNameAndType true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/load/file [post]
|
||||
func (b *BaseApi) LoadDatabaseFile(c *gin.Context) {
|
||||
var req dto.OperationWithNameAndType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content, err := mysqlService.LoadDatabaseFile(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Load mysql remote access
|
||||
// @Description 获取 mysql 远程访问权限
|
||||
@ -362,7 +340,10 @@ func (b *BaseApi) LoadRemoteAccess(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Type == constant.AppPostgresql {
|
||||
helper.SuccessWithData(c, true)
|
||||
return
|
||||
}
|
||||
isRemote, err := mysqlService.LoadRemoteAccess(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
|
321
backend/app/api/v1/database_postgresql.go
Normal file
321
backend/app/api/v1/database_postgresql.go
Normal file
@ -0,0 +1,321 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Create postgresql database
|
||||
// @Description 创建 postgresql 数据库
|
||||
// @Accept json
|
||||
// @Param request body dto.PostgresqlDBCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 postgresql 数据库 [name]","formatEN":"create postgresql database [name]"}
|
||||
func (b *BaseApi) CreatePostgresql(c *gin.Context) {
|
||||
var req dto.PostgresqlDBCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
|
||||
if _, err := postgresqlService.Create(context.Background(), req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Update postgresql database description
|
||||
// @Description 更新 postgresql 数据库库描述信息
|
||||
// @Accept json
|
||||
// @Param request body dto.UpdateDescription true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/description [post]
|
||||
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"postgresql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the postgresql database [name] is modified => [description]"}
|
||||
func (b *BaseApi) UpdatePostgresqlDescription(c *gin.Context) {
|
||||
var req dto.UpdateDescription
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := postgresqlService.UpdateDescription(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Change postgresql password
|
||||
// @Description 修改 postgresql 密码
|
||||
// @Accept json
|
||||
// @Param request body dto.ChangeDBInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/password [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] password"}
|
||||
func (b *BaseApi) ChangePostgresqlPassword(c *gin.Context) {
|
||||
var req dto.ChangeDBInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Value) != 0 {
|
||||
value, err := base64.StdEncoding.DecodeString(req.Value)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Value = string(value)
|
||||
}
|
||||
|
||||
if err := postgresqlService.ChangePassword(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
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
|
||||
// @Summary Update postgresql conf by upload file
|
||||
// @Description 上传替换 postgresql 配置文件
|
||||
// @Accept json
|
||||
// @Param request body dto.PostgresqlConfUpdateByFile true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/conf [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 postgresql 数据库配置信息","formatEN":"update the postgresql database configuration information"}
|
||||
func (b *BaseApi) UpdatePostgresqlConfByFile(c *gin.Context) {
|
||||
var req dto.PostgresqlConfUpdateByFile
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := postgresqlService.UpdateConfByFile(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Page postgresql databases
|
||||
// @Description 获取 postgresql 数据库列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.PostgresqlDBSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/search [post]
|
||||
func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
||||
var req dto.PostgresqlDBSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := postgresqlService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary List postgresql database names
|
||||
// @Description 获取 postgresql 数据库列表
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Success 200 {array} dto.PostgresqlOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/options [get]
|
||||
func (b *BaseApi) ListPostgresqlDBName(c *gin.Context) {
|
||||
list, err := postgresqlService.ListDBOption()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Load postgresql database from remote
|
||||
// @Description 从服务器获取
|
||||
// @Accept json
|
||||
// @Param request body dto.PostgresqlLoadDB true "request"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/load [post]
|
||||
func (b *BaseApi) LoadPostgresqlDBFromRemote(c *gin.Context) {
|
||||
//var req dto.PostgresqlLoadDB
|
||||
//if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//if err := postgresqlService.LoadFromRemote(req); err != nil {
|
||||
// helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
// return
|
||||
//}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Check before delete postgresql database
|
||||
// @Description Postgresql 数据库删除前检查
|
||||
// @Accept json
|
||||
// @Param request body dto.PostgresqlDBDeleteCheck true "request"
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/del/check [post]
|
||||
func (b *BaseApi) DeleteCheckPostgresql(c *gin.Context) {
|
||||
var req dto.PostgresqlDBDeleteCheck
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
apps, err := postgresqlService.DeleteCheck(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, apps)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Delete postgresql database
|
||||
// @Description 删除 postgresql 数据库
|
||||
// @Accept json
|
||||
// @Param request body dto.PostgresqlDBDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/del [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":"删除 postgresql 数据库 [name]","formatEN":"delete postgresql database [name]"}
|
||||
func (b *BaseApi) DeletePostgresql(c *gin.Context) {
|
||||
var req dto.PostgresqlDBDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
tx, ctx := helper.GetTxAndContext()
|
||||
if err := postgresqlService.Delete(ctx, req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Load postgresql base info
|
||||
// @Description 获取 postgresql 基础信息
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithNameAndType true "request"
|
||||
// @Success 200 {object} dto.DBBaseInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/baseinfo [post]
|
||||
func (b *BaseApi) LoadPostgresqlBaseinfo(c *gin.Context) {
|
||||
var req dto.OperationWithNameAndType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := postgresqlService.LoadBaseInfo(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ var (
|
||||
dockerService = service.NewIDockerService()
|
||||
|
||||
mysqlService = service.NewIMysqlService()
|
||||
postgresqlService = service.NewIPostgresqlService()
|
||||
databaseService = service.NewIDatabaseService()
|
||||
redisService = service.NewIRedisService()
|
||||
|
||||
|
@ -26,13 +26,13 @@ type BackupSearchFile struct {
|
||||
}
|
||||
|
||||
type CommonBackup struct {
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"`
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
}
|
||||
type CommonRecover struct {
|
||||
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"`
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
File string `json:"file"`
|
||||
|
@ -146,11 +146,10 @@ type MysqlConfUpdateByFile struct {
|
||||
Database string `json:"database" validate:"required"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
type ChangeDBInfo struct {
|
||||
ID uint `json:"id"`
|
||||
From string `json:"from" validate:"required,oneof=local remote"`
|
||||
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
|
||||
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql"`
|
||||
Database string `json:"database" validate:"required"`
|
||||
Value string `json:"value" validate:"required"`
|
||||
}
|
||||
|
115
backend/app/dto/database_postgresql.go
Normal file
115
backend/app/dto/database_postgresql.go
Normal file
@ -0,0 +1,115 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type PostgresqlDBSearch struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
Database string `json:"database" validate:"required"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
|
||||
type PostgresqlDBInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Name string `json:"name"`
|
||||
From string `json:"from"`
|
||||
PostgresqlName string `json:"postgresqlName"`
|
||||
Format string `json:"format"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission"`
|
||||
BackupCount int `json:"backupCount"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type PostgresqlOption struct {
|
||||
ID uint `json:"id"`
|
||||
From string `json:"from"`
|
||||
Type string `json:"type"`
|
||||
Database string `json:"database"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type PostgresqlDBCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
From string `json:"from" validate:"required,oneof=local remote"`
|
||||
Database string `json:"database" validate:"required"`
|
||||
Format string `json:"format"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Permission string `json:"permission" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type PostgresqlLoadDB struct {
|
||||
From string `json:"from" validate:"required,oneof=local remote"`
|
||||
Type string `json:"type" validate:"required,oneof=postgresql"`
|
||||
Database string `json:"database" validate:"required"`
|
||||
}
|
||||
|
||||
type PostgresqlDBDeleteCheck struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=postgresql"`
|
||||
Database string `json:"database" validate:"required"`
|
||||
}
|
||||
|
||||
type PostgresqlDBDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=postgresql"`
|
||||
Database string `json:"database" validate:"required"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
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 string `json:"type" validate:"required,oneof=postgresql mariadb"`
|
||||
Database string `json:"database" validate:"required"`
|
||||
File string `json:"file"`
|
||||
}
|
@ -72,6 +72,7 @@ type AppInstalledDTO struct {
|
||||
}
|
||||
|
||||
type DatabaseConn struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
Port int64 `json:"port"`
|
||||
|
12
backend/app/model/database_postgresql.go
Normal file
12
backend/app/model/database_postgresql.go
Normal file
@ -0,0 +1,12 @@
|
||||
package model
|
||||
|
||||
type DatabasePostgresql struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"type:varchar(256);not null"`
|
||||
From string `json:"from" gorm:"type:varchar(256);not null;default:local"`
|
||||
PostgresqlName string `json:"postgresqlName" gorm:"type:varchar(64);not null"`
|
||||
Format string `json:"format" gorm:"type:varchar(64);not null"`
|
||||
Username string `json:"username" gorm:"type:varchar(256);not null"`
|
||||
Password string `json:"password" gorm:"type:varchar(256);not null"`
|
||||
Description string `json:"description" gorm:"type:varchar(256);"`
|
||||
}
|
@ -3,6 +3,7 @@ package repo
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
@ -170,6 +171,7 @@ type RootInfo struct {
|
||||
Env string `json:"env"`
|
||||
Key string `json:"key"`
|
||||
Version string `json:"version"`
|
||||
AppPath string `json:"app_path"`
|
||||
}
|
||||
|
||||
func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error) {
|
||||
@ -205,7 +207,7 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
|
||||
if ok {
|
||||
info.Password = password
|
||||
}
|
||||
case "mongodb", "postgresql":
|
||||
case "mongodb", constant.AppPostgresql:
|
||||
user, ok := envMap["PANEL_DB_ROOT_USER"].(string)
|
||||
if ok {
|
||||
info.UserName = user
|
||||
@ -230,5 +232,7 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
|
||||
info.Param = appInstall.Param
|
||||
info.Version = appInstall.Version
|
||||
info.Key = app.Key
|
||||
appInstall.App = app
|
||||
info.AppPath = appInstall.GetAppPath()
|
||||
return &info, nil
|
||||
}
|
||||
|
123
backend/app/repo/databse_postgresql.go
Normal file
123
backend/app/repo/databse_postgresql.go
Normal file
@ -0,0 +1,123 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PostgresqlRepo struct{}
|
||||
|
||||
type IPostgresqlRepo interface {
|
||||
Get(opts ...DBOption) (model.DatabasePostgresql, error)
|
||||
WithByPostgresqlName(postgresqlName string) DBOption
|
||||
WithByFrom(from string) DBOption
|
||||
List(opts ...DBOption) ([]model.DatabasePostgresql, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.DatabasePostgresql, error)
|
||||
Create(ctx context.Context, postgresql *model.DatabasePostgresql) error
|
||||
Delete(ctx context.Context, opts ...DBOption) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
DeleteLocal(ctx context.Context) error
|
||||
}
|
||||
|
||||
func NewIPostgresqlRepo() IPostgresqlRepo {
|
||||
return &PostgresqlRepo{}
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) Get(opts ...DBOption) (model.DatabasePostgresql, error) {
|
||||
var postgresql model.DatabasePostgresql
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
if err := db.First(&postgresql).Error; err != nil {
|
||||
return postgresql, err
|
||||
}
|
||||
|
||||
pass, err := encrypt.StringDecrypt(postgresql.Password)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresql.Name, err)
|
||||
}
|
||||
postgresql.Password = pass
|
||||
return postgresql, err
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) List(opts ...DBOption) ([]model.DatabasePostgresql, error) {
|
||||
var postgresqls []model.DatabasePostgresql
|
||||
db := global.DB.Model(&model.DatabasePostgresql{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
if err := db.Find(&postgresqls).Error; err != nil {
|
||||
return postgresqls, err
|
||||
}
|
||||
for i := 0; i < len(postgresqls); i++ {
|
||||
pass, err := encrypt.StringDecrypt(postgresqls[i].Password)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresqls[i].Name, err)
|
||||
}
|
||||
postgresqls[i].Password = pass
|
||||
}
|
||||
return postgresqls, nil
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) Page(page, size int, opts ...DBOption) (int64, []model.DatabasePostgresql, error) {
|
||||
var postgresqls []model.DatabasePostgresql
|
||||
db := global.DB.Model(&model.DatabasePostgresql{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
if err := db.Limit(size).Offset(size * (page - 1)).Find(&postgresqls).Error; err != nil {
|
||||
return count, postgresqls, err
|
||||
}
|
||||
for i := 0; i < len(postgresqls); i++ {
|
||||
pass, err := encrypt.StringDecrypt(postgresqls[i].Password)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresqls[i].Name, err)
|
||||
}
|
||||
postgresqls[i].Password = pass
|
||||
}
|
||||
return count, postgresqls, nil
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) Create(ctx context.Context, postgresql *model.DatabasePostgresql) error {
|
||||
pass, err := encrypt.StringEncrypt(postgresql.Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt database db %s password failed, err: %v", postgresql.Name, err)
|
||||
}
|
||||
postgresql.Password = pass
|
||||
return getTx(ctx).Create(postgresql).Error
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) Delete(ctx context.Context, opts ...DBOption) error {
|
||||
return getTx(ctx, opts...).Delete(&model.DatabasePostgresql{}).Error
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) DeleteLocal(ctx context.Context) error {
|
||||
return getTx(ctx).Where("`from` = ?", "local").Delete(&model.DatabasePostgresql{}).Error
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.DatabasePostgresql{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) WithByPostgresqlName(postgresqlName string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("postgresql_name = ?", postgresqlName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *PostgresqlRepo) WithByFrom(from string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(from) != 0 {
|
||||
return g.Where("`from` = ?", from)
|
||||
}
|
||||
return g
|
||||
}
|
||||
}
|
@ -184,6 +184,7 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp
|
||||
if err != nil {
|
||||
return data, nil
|
||||
}
|
||||
data.Username = app.UserName
|
||||
data.Password = app.Password
|
||||
data.ServiceName = app.ServiceName
|
||||
data.Port = app.Port
|
||||
@ -781,7 +782,7 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, appKey, appInstall.Name)
|
||||
envPath := fmt.Sprintf("%s/%s/.env", appInstall.AppPath, appInstall.Name)
|
||||
lineBytes, err := os.ReadFile(envPath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -154,7 +154,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
||||
}
|
||||
|
||||
switch app.Key {
|
||||
case "mysql", "mariadb", "postgresql", "mongodb":
|
||||
case "mysql", "mariadb", constant.AppPostgresql, "mongodb":
|
||||
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
database.Password = password.(string)
|
||||
@ -233,28 +233,57 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
||||
}
|
||||
var resourceId uint
|
||||
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
|
||||
iMysqlRepo := repo.NewIMysqlRepo()
|
||||
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldMysqlDb.ID
|
||||
if oldMysqlDb.ID > 0 {
|
||||
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
|
||||
return buserr.New(constant.ErrDbUserNotValid)
|
||||
switch database.Type {
|
||||
case constant.AppPostgresql:
|
||||
iPostgresqlRepo := repo.NewIPostgresqlRepo()
|
||||
oldPostgresqlDb, _ := iPostgresqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iPostgresqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldPostgresqlDb.ID
|
||||
if oldPostgresqlDb.ID > 0 {
|
||||
if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password {
|
||||
return buserr.New(constant.ErrDbUserNotValid)
|
||||
}
|
||||
} else {
|
||||
var createPostgresql dto.PostgresqlDBCreate
|
||||
createPostgresql.Name = dbConfig.DbName
|
||||
createPostgresql.Username = dbConfig.DbUser
|
||||
createPostgresql.Database = database.Name
|
||||
createPostgresql.Format = "UTF8"
|
||||
createPostgresql.Password = dbConfig.Password
|
||||
createPostgresql.From = database.From
|
||||
pgdb, err := NewIPostgresqlService().Create(ctx, createPostgresql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resourceId = pgdb.ID
|
||||
}
|
||||
} else {
|
||||
var createMysql dto.MysqlDBCreate
|
||||
createMysql.Name = dbConfig.DbName
|
||||
createMysql.Username = dbConfig.DbUser
|
||||
createMysql.Database = database.Name
|
||||
createMysql.Format = "utf8mb4"
|
||||
createMysql.Permission = "%"
|
||||
createMysql.Password = dbConfig.Password
|
||||
createMysql.From = database.From
|
||||
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
|
||||
if err != nil {
|
||||
return err
|
||||
break
|
||||
case "mysql", "mariadb":
|
||||
iMysqlRepo := repo.NewIMysqlRepo()
|
||||
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldMysqlDb.ID
|
||||
if oldMysqlDb.ID > 0 {
|
||||
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
|
||||
return buserr.New(constant.ErrDbUserNotValid)
|
||||
}
|
||||
} else {
|
||||
var createMysql dto.MysqlDBCreate
|
||||
createMysql.Name = dbConfig.DbName
|
||||
createMysql.Username = dbConfig.DbUser
|
||||
createMysql.Database = database.Name
|
||||
createMysql.Format = "utf8mb4"
|
||||
createMysql.Permission = "%"
|
||||
createMysql.Password = dbConfig.Password
|
||||
createMysql.From = database.From
|
||||
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resourceId = mysqldb.ID
|
||||
}
|
||||
resourceId = mysqldb.ID
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
var installResource model.AppInstallResource
|
||||
installResource.ResourceId = resourceId
|
||||
@ -364,22 +393,41 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
|
||||
return nil
|
||||
}
|
||||
for _, re := range resources {
|
||||
mysqlService := NewIMysqlService()
|
||||
if (re.Key == constant.AppMysql || re.Key == constant.AppMariaDB) && deleteDB {
|
||||
database, _ := mysqlRepo.Get(commonRepo.WithByID(re.ResourceId))
|
||||
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
|
||||
continue
|
||||
}
|
||||
if err := mysqlService.Delete(ctx, dto.MysqlDBDelete{
|
||||
ID: database.ID,
|
||||
ForceDelete: forceDelete,
|
||||
DeleteBackup: deleteBackup,
|
||||
Type: re.Key,
|
||||
Database: database.MysqlName,
|
||||
}); err != nil && !forceDelete {
|
||||
return err
|
||||
if deleteDB {
|
||||
switch re.Key {
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
mysqlService := NewIMysqlService()
|
||||
database, _ := mysqlRepo.Get(commonRepo.WithByID(re.ResourceId))
|
||||
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
|
||||
continue
|
||||
}
|
||||
if err := mysqlService.Delete(ctx, dto.MysqlDBDelete{
|
||||
ID: database.ID,
|
||||
ForceDelete: forceDelete,
|
||||
DeleteBackup: deleteBackup,
|
||||
Type: re.Key,
|
||||
Database: database.MysqlName,
|
||||
}); err != nil && !forceDelete {
|
||||
return err
|
||||
}
|
||||
case constant.AppPostgresql:
|
||||
pgsqlService := NewIPostgresqlService()
|
||||
database, _ := postgresqlRepo.Get(commonRepo.WithByID(re.ResourceId))
|
||||
if reflect.DeepEqual(database, model.DatabasePostgresql{}) {
|
||||
continue
|
||||
}
|
||||
if err := pgsqlService.Delete(ctx, dto.PostgresqlDBDelete{
|
||||
ID: database.ID,
|
||||
ForceDelete: forceDelete,
|
||||
DeleteBackup: deleteBackup,
|
||||
Type: re.Key,
|
||||
Database: database.PostgresqlName,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
}
|
||||
|
@ -41,8 +41,11 @@ type IBackupService interface {
|
||||
ListFiles(req dto.BackupSearchFile) ([]string, error)
|
||||
|
||||
MysqlBackup(db dto.CommonBackup) error
|
||||
PostgresqlBackup(db dto.CommonBackup) error
|
||||
MysqlRecover(db dto.CommonRecover) error
|
||||
PostgresqlRecover(db dto.CommonRecover) error
|
||||
MysqlRecoverByUpload(req dto.CommonRecover) error
|
||||
PostgresqlRecoverByUpload(req dto.CommonRecover) error
|
||||
|
||||
RedisBackup() error
|
||||
RedisRecover(db dto.CommonRecover) error
|
||||
|
@ -107,7 +107,8 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
|
||||
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
for _, resource := range resources {
|
||||
if resource.Key == "mysql" || resource.Key == "mariadb" {
|
||||
switch resource.Key {
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -115,6 +116,14 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
|
||||
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppPostgresql:
|
||||
db, err := postgresqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handlePostgresqlBackup(db.PostgresqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,6 +200,34 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
||||
return err
|
||||
}
|
||||
}
|
||||
if database.Type == constant.AppPostgresql {
|
||||
db, err := postgresqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newDB, envMap, err := reCreatePostgresqlDB(db.ID, database, oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
|
||||
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", database.Address)
|
||||
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
|
||||
envMap["PANEL_DB_HOST"] = database.Address
|
||||
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
|
||||
|
||||
if err := handlePostgresqlRecover(dto.CommonRecover{
|
||||
Name: newDB.PostgresqlName,
|
||||
DetailName: newDB.Name,
|
||||
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
|
||||
}, true); err != nil {
|
||||
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if database.Type == "mysql" || database.Type == "mariadb" {
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
@ -235,7 +272,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
||||
_ = fileOp.DeleteDir(backPath)
|
||||
|
||||
if len(newEnvFile) != 0 {
|
||||
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name)
|
||||
envPath := fmt.Sprintf("%s/%s/.env", install.GetAppPath(), install.Name)
|
||||
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -257,7 +294,32 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
||||
|
||||
return nil
|
||||
}
|
||||
func reCreatePostgresqlDB(dbID uint, database model.Database, oldEnv string) (*model.DatabasePostgresql, map[string]interface{}, error) {
|
||||
postgresqlService := NewIPostgresqlService()
|
||||
ctx := context.Background()
|
||||
_ = postgresqlService.Delete(ctx, dto.PostgresqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true})
|
||||
|
||||
envMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil {
|
||||
return nil, envMap, err
|
||||
}
|
||||
oldName, _ := envMap["PANEL_DB_NAME"].(string)
|
||||
oldUser, _ := envMap["PANEL_DB_USER"].(string)
|
||||
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
|
||||
createDB, err := postgresqlService.Create(context.Background(), dto.PostgresqlDBCreate{
|
||||
Name: oldName,
|
||||
From: database.From,
|
||||
Database: database.Name,
|
||||
Format: "UTF8",
|
||||
Username: oldUser,
|
||||
Password: oldPassword,
|
||||
Permission: "%",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, envMap, err
|
||||
}
|
||||
return createDB, envMap, nil
|
||||
}
|
||||
func reCreateDB(dbID uint, database model.Database, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
|
||||
mysqlService := NewIMysqlService()
|
||||
ctx := context.Background()
|
||||
|
180
backend/app/service/backup_postgresql.go
Normal file
180
backend/app/service/backup_postgresql.go
Normal file
@ -0,0 +1,180 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
)
|
||||
|
||||
func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
targetDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName))
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
||||
|
||||
if err := handlePostgresqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := &model.BackupRecord{
|
||||
Type: req.Type,
|
||||
Name: req.Name,
|
||||
DetailName: req.DetailName,
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: targetDir,
|
||||
FileName: fileName,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error {
|
||||
if err := handlePostgresqlRecover(req, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
file := req.File
|
||||
fileName := path.Base(req.File)
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
fileNameItem := time.Now().Format("20060102150405")
|
||||
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
|
||||
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
|
||||
}
|
||||
}
|
||||
if err := handleUnTar(req.File, dstDir); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
|
||||
hasTestSql := false
|
||||
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "test.sql" {
|
||||
hasTestSql = true
|
||||
file = path
|
||||
fileName = "test.sql"
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !hasTestSql {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return fmt.Errorf("no such file named test.sql in %s", fileName)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
}()
|
||||
}
|
||||
|
||||
req.File = path.Dir(file) + "/" + fileName
|
||||
if err := handlePostgresqlRecover(req, false); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Info("recover from uploads successful!")
|
||||
return nil
|
||||
}
|
||||
func handlePostgresqlBackup(database, dbName, targetDir, fileName string) error {
|
||||
dbInfo, err := postgresqlRepo.Get(commonRepo.WithByName(dbName), postgresqlRepo.WithByPostgresqlName(database))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadPostgresqlClientByFrom(database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backupInfo := pgclient.BackupInfo{
|
||||
Name: dbName,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: targetDir,
|
||||
FileName: fileName,
|
||||
|
||||
Timeout: 300,
|
||||
}
|
||||
if err := cli.Backup(backupInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
isOk := false
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return buserr.WithName("ErrFileNotFound", req.File)
|
||||
}
|
||||
dbInfo, err := postgresqlRepo.Get(commonRepo.WithByName(req.DetailName), postgresqlRepo.WithByPostgresqlName(req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadPostgresqlClientByFrom(req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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")))
|
||||
if err := cli.Backup(client.BackupInfo{
|
||||
Name: req.DetailName,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: path.Dir(rollbackFile),
|
||||
FileName: path.Base(rollbackFile),
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("backup postgresql db %s for rollback before recover failed, err: %v", req.DetailName, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: rollbackFile,
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
|
||||
}
|
||||
global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: req.File,
|
||||
Username: dbInfo.Username,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
return nil
|
||||
}
|
@ -3,6 +3,8 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
|
||||
client2 "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
@ -78,23 +80,42 @@ func (u *DatabaseService) List(dbType string) ([]dto.DatabaseOption, error) {
|
||||
}
|
||||
|
||||
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
_, err := postgresql.NewPostgresqlClient(client2.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
||||
SSL: req.SSL,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return false
|
||||
SSL: false,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 6,
|
||||
})
|
||||
return err == nil
|
||||
case "mysql", "mariadb":
|
||||
_, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
||||
SSL: req.SSL,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 6,
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
@ -105,22 +126,45 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
}
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
||||
SSL: req.SSL,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return err
|
||||
SSL: req.SSL,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
case "mysql", "mariadb":
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
||||
SSL: req.SSL,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("database type not supported")
|
||||
}
|
||||
|
||||
if err := copier.Copy(&db, &req); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
@ -178,22 +222,43 @@ func (u *DatabaseService) Delete(req dto.DatabaseDelete) error {
|
||||
}
|
||||
|
||||
func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
||||
SSL: req.SSL,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
RootCert: req.RootCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
SSL: req.SSL,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
case "mysql", "mariadb":
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return err
|
||||
SSL: req.SSL,
|
||||
RootCert: req.RootCert,
|
||||
ClientKey: req.ClientKey,
|
||||
ClientCert: req.ClientCert,
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("database type not supported")
|
||||
}
|
||||
|
||||
pass, err := encrypt.StringEncrypt(req.Password)
|
||||
@ -208,6 +273,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
|
||||
upMap["port"] = req.Port
|
||||
upMap["username"] = req.Username
|
||||
upMap["password"] = pass
|
||||
upMap["description"] = req.Description
|
||||
upMap["ssl"] = req.SSL
|
||||
upMap["client_key"] = req.ClientKey
|
||||
upMap["client_cert"] = req.ClientCert
|
||||
|
461
backend/app/service/database_postgresql.go
Normal file
461
backend/app/service/database_postgresql.go
Normal file
@ -0,0 +1,461 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"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/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type PostgresqlService struct{}
|
||||
|
||||
type IPostgresqlService interface {
|
||||
SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error)
|
||||
ListDBOption() ([]dto.PostgresqlOption, error)
|
||||
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
|
||||
LoadFromRemote(req dto.PostgresqlLoadDB) error
|
||||
ChangeAccess(info dto.ChangeDBInfo) error
|
||||
ChangePassword(info dto.ChangeDBInfo) error
|
||||
UpdateVariables(req dto.PostgresqlVariablesUpdate) error
|
||||
UpdateConfByFile(info dto.PostgresqlConfUpdateByFile) error
|
||||
UpdateDescription(req dto.UpdateDescription) error
|
||||
DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, 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)
|
||||
LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error)
|
||||
|
||||
LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error)
|
||||
}
|
||||
|
||||
func NewIPostgresqlService() IPostgresqlService {
|
||||
return &PostgresqlService{}
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error) {
|
||||
total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize,
|
||||
postgresqlRepo.WithByPostgresqlName(search.Database),
|
||||
commonRepo.WithLikeName(search.Info),
|
||||
commonRepo.WithOrderRuleBy(search.OrderBy, search.Order),
|
||||
)
|
||||
var dtoPostgresqls []dto.PostgresqlDBInfo
|
||||
for _, pg := range postgresqls {
|
||||
var item dto.PostgresqlDBInfo
|
||||
if err := copier.Copy(&item, &pg); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
dtoPostgresqls = append(dtoPostgresqls, item)
|
||||
}
|
||||
return total, dtoPostgresqls, err
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) ListDBOption() ([]dto.PostgresqlOption, error) {
|
||||
postgresqls, err := postgresqlRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
databases, err := databaseRepo.GetList(databaseRepo.WithTypeList("postgresql,mariadb"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var dbs []dto.PostgresqlOption
|
||||
for _, pg := range postgresqls {
|
||||
var item dto.PostgresqlOption
|
||||
if err := copier.Copy(&item, &pg); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
item.Database = pg.PostgresqlName
|
||||
for _, database := range databases {
|
||||
if database.Name == item.Database {
|
||||
item.Type = database.Type
|
||||
}
|
||||
}
|
||||
dbs = append(dbs, item)
|
||||
}
|
||||
return dbs, err
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error) {
|
||||
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format) {
|
||||
return nil, buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
pgsql, _ := postgresqlRepo.Get(commonRepo.WithByName(req.Name), postgresqlRepo.WithByPostgresqlName(req.Database), databaseRepo.WithByFrom(req.From))
|
||||
if pgsql.ID != 0 {
|
||||
return nil, constant.ErrRecordExist
|
||||
}
|
||||
|
||||
var createItem model.DatabasePostgresql
|
||||
if err := copier.Copy(&createItem, &req); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
|
||||
if req.From == "local" && req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
}
|
||||
|
||||
cli, version, err := LoadPostgresqlClientByFrom(req.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createItem.PostgresqlName = req.Database
|
||||
defer cli.Close()
|
||||
if err := cli.Create(client.CreateInfo{
|
||||
Name: req.Name,
|
||||
Format: req.Format,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Version: version,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
global.LOG.Infof("create database %s successful!", req.Name)
|
||||
if err := postgresqlRepo.Create(ctx, &createItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &createItem, nil
|
||||
}
|
||||
func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, string, error) {
|
||||
var (
|
||||
dbInfo client.DBInfo
|
||||
version string
|
||||
err error
|
||||
)
|
||||
|
||||
dbInfo.Timeout = 300
|
||||
databaseItem, err := databaseRepo.Get(commonRepo.WithByName(database))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
dbInfo.From = databaseItem.From
|
||||
dbInfo.Database = database
|
||||
if dbInfo.From != "local" {
|
||||
dbInfo.Address = databaseItem.Address
|
||||
dbInfo.Port = databaseItem.Port
|
||||
dbInfo.Username = databaseItem.Username
|
||||
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 {
|
||||
app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
dbInfo.From = "local"
|
||||
dbInfo.Address = app.ContainerName
|
||||
dbInfo.Username = app.UserName
|
||||
dbInfo.Password = app.Password
|
||||
dbInfo.Port = uint(app.Port)
|
||||
}
|
||||
|
||||
cli, err := postgresql.NewPostgresqlClient(dbInfo)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return cli, version, nil
|
||||
}
|
||||
func (u *PostgresqlService) LoadFromRemote(req dto.PostgresqlLoadDB) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) UpdateDescription(req dto.UpdateDescription) error {
|
||||
return postgresqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, error) {
|
||||
var appInUsed []string
|
||||
db, err := postgresqlRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return appInUsed, err
|
||||
}
|
||||
|
||||
if db.From == "local" {
|
||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
|
||||
if err != nil {
|
||||
return appInUsed, err
|
||||
}
|
||||
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
|
||||
for _, app := range apps {
|
||||
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
|
||||
if appInstall.ID != 0 {
|
||||
appInUsed = append(appInUsed, appInstall.Name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID))
|
||||
for _, app := range apps {
|
||||
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
|
||||
if appInstall.ID != 0 {
|
||||
appInUsed = append(appInUsed, appInstall.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return appInUsed, nil
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) Delete(ctx context.Context, req dto.PostgresqlDBDelete) error {
|
||||
db, err := postgresqlRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
cli, version, err := LoadPostgresqlClientByFrom(req.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
if err := cli.Delete(client.DeleteInfo{
|
||||
Name: db.Name,
|
||||
Version: version,
|
||||
Username: db.Username,
|
||||
Permission: "",
|
||||
Timeout: 300,
|
||||
}); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.DeleteBackup {
|
||||
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/database/%s/%s/%s", req.Type, req.Database, db.Name))
|
||||
if _, err := os.Stat(uploadDir); err == nil {
|
||||
_ = os.RemoveAll(uploadDir)
|
||||
}
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, db.PostgresqlName, db.Name))
|
||||
if _, err := os.Stat(backupDir); err == nil {
|
||||
_ = os.RemoveAll(backupDir)
|
||||
}
|
||||
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(req.Type), commonRepo.WithByName(req.Database), backupRepo.WithByDetailName(db.Name))
|
||||
global.LOG.Infof("delete database %s-%s backups successful", req.Database, db.Name)
|
||||
}
|
||||
|
||||
_ = postgresqlRepo.Delete(ctx, commonRepo.WithByID(db.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) ChangePassword(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
|
||||
passwordInfo client.PasswordChangeInfo
|
||||
)
|
||||
passwordInfo.Password = req.Value
|
||||
passwordInfo.Timeout = 300
|
||||
passwordInfo.Version = version
|
||||
|
||||
if req.ID != 0 {
|
||||
postgresqlData, err = postgresqlRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
passwordInfo.Name = postgresqlData.Name
|
||||
passwordInfo.Username = postgresqlData.Username
|
||||
} else {
|
||||
dbItem, err := databaseRepo.Get(commonRepo.WithByType(req.Type), commonRepo.WithByFrom(req.From))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
passwordInfo.Username = dbItem.Username
|
||||
}
|
||||
if err := cli.ChangePassword(passwordInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.ID != 0 {
|
||||
var appRess []model.AppInstallResource
|
||||
if req.From == "local" {
|
||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(postgresqlData.ID))
|
||||
} else {
|
||||
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(postgresqlData.ID))
|
||||
}
|
||||
for _, appRes := range appRess {
|
||||
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(appRes.AppInstallId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appModel, err := appRepo.GetFirst(commonRepo.WithByID(appInstall.AppId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
global.LOG.Info("execute password change sql successful")
|
||||
pass, err := encrypt.StringEncrypt(req.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt database db password failed, err: %v", err)
|
||||
}
|
||||
_ = postgresqlRepo.Update(postgresqlData.ID, map[string]interface{}{"password": pass})
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf := fmt.Sprintf("%s/%s/%s/data/postgresql.conf", constant.AppInstallDir, req.Type, app.Name)
|
||||
file, err := os.OpenFile(conf, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.File)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) UpdateVariables(req dto.PostgresqlVariablesUpdate) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *PostgresqlService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) {
|
||||
var data dto.DBBaseInfo
|
||||
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.ContainerName = app.ContainerName
|
||||
data.Name = app.Name
|
||||
data.Port = int64(app.Port)
|
||||
|
||||
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) {
|
||||
filePath := ""
|
||||
switch req.Type {
|
||||
case "postgresql-conf":
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
@ -13,6 +13,7 @@ var (
|
||||
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
|
||||
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
postgresqlRepo = repo.NewIPostgresqlRepo()
|
||||
databaseRepo = repo.NewIDatabaseRepo()
|
||||
|
||||
imageRepoRepo = repo.NewIImageRepoRepo()
|
||||
|
@ -22,6 +22,7 @@ const (
|
||||
AppOpenresty = "openresty"
|
||||
AppMysql = "mysql"
|
||||
AppMariaDB = "mariadb"
|
||||
AppPostgresql = "postgresql"
|
||||
AppRedis = "redis"
|
||||
|
||||
AppResourceLocal = "local"
|
||||
|
@ -19,6 +19,7 @@ func Init() {
|
||||
migrations.AddTableImageRepo,
|
||||
migrations.AddTableWebsite,
|
||||
migrations.AddTableDatabaseMysql,
|
||||
migrations.AddTableDatabasePostgresql,
|
||||
migrations.AddTableSnap,
|
||||
migrations.AddDefaultGroup,
|
||||
migrations.AddTableRuntime,
|
||||
|
@ -214,7 +214,12 @@ 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 {
|
||||
|
@ -52,5 +52,16 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/db/search", baseApi.SearchDatabase)
|
||||
cmdRouter.POST("/db/del/check", baseApi.DeleteCheckDatabase)
|
||||
cmdRouter.POST("/db/del", baseApi.DeleteDatabase)
|
||||
|
||||
//PGSQL管理系列接口
|
||||
cmdRouter.POST("/pg", baseApi.CreatePostgresql)
|
||||
cmdRouter.POST("/pg/search", baseApi.SearchPostgresql)
|
||||
cmdRouter.POST("/pg/del/check", baseApi.DeleteCheckPostgresql)
|
||||
cmdRouter.POST("/pg/password", baseApi.ChangePostgresqlPassword)
|
||||
cmdRouter.POST("/pg/description", baseApi.UpdatePostgresqlDescription)
|
||||
cmdRouter.POST("/pg/del", baseApi.DeletePostgresql)
|
||||
cmdRouter.POST("/pg/conf", baseApi.UpdatePostgresqlConfByFile)
|
||||
cmdRouter.POST("/pg/status", baseApi.LoadPostgresqlStatus)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,12 @@ func (s webDAVClient) Download(src, target string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
targetStat, err := os.Stat(target)
|
||||
if err == nil {
|
||||
if info.Size() == targetStat.Size() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
file, err := os.Create(target)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
64
backend/utils/postgresql/client.go
Normal file
64
backend/utils/postgresql/client.go
Normal file
@ -0,0 +1,64 @@
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PostgresqlClient interface {
|
||||
Create(info client.CreateInfo) error
|
||||
Delete(info client.DeleteInfo) error
|
||||
ReloadConf()error
|
||||
ChangePassword(info client.PasswordChangeInfo) error
|
||||
ChangeAccess(info client.AccessChangeInfo) error
|
||||
|
||||
Backup(info client.BackupInfo) error
|
||||
Recover(info client.RecoverInfo) error
|
||||
Status() client.Status
|
||||
SyncDB(version string) ([]client.SyncDBInfo, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) {
|
||||
if conn.Port==0 {
|
||||
conn.Port=5432
|
||||
}
|
||||
if conn.From == "local" {
|
||||
conn.Address = "127.0.0.1"
|
||||
}
|
||||
connArgs := fmt.Sprintf("postgres://%s:%s@%s:%d/?sslmode=disable", conn.Username, conn.Password, conn.Address, conn.Port)
|
||||
db, err := sql.Open("pgx", connArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
|
||||
return client.NewRemote(client.Remote{
|
||||
Client: db,
|
||||
Database: conn.Database,
|
||||
User: conn.Username,
|
||||
Password: conn.Password,
|
||||
Address: conn.Address,
|
||||
Port: conn.Port,
|
||||
|
||||
SSL: conn.SSL,
|
||||
RootCert: conn.RootCert,
|
||||
ClientKey: conn.ClientKey,
|
||||
ClientCert: conn.ClientCert,
|
||||
SkipVerify: conn.SkipVerify,
|
||||
}), nil
|
||||
}
|
102
backend/utils/postgresql/client/info.go
Normal file
102
backend/utils/postgresql/client/info.go
Normal file
@ -0,0 +1,102 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
type DBInfo struct {
|
||||
From string `json:"from"`
|
||||
Database string `json:"database"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Username string `json:"userName"`
|
||||
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
|
||||
}
|
||||
|
||||
type CreateInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
Version string `json:"version"`
|
||||
Username string `json:"userName"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type DeleteInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Username string `json:"userName"`
|
||||
Permission string `json:"permission"`
|
||||
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type PasswordChangeInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
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
|
||||
}
|
||||
|
||||
type BackupInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
TargetDir string `json:"targetDir"`
|
||||
FileName string `json:"fileName"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type RecoverInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
SourceFile string `json:"sourceFile"`
|
||||
Username string `json:"username"`
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type SyncDBInfo struct {
|
||||
Name string `json:"name"`
|
||||
From string `json:"from"`
|
||||
PostgresqlName string `json:"postgresqlName"`
|
||||
Format string `json:"format"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
type Status 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"`
|
||||
}
|
232
backend/utils/postgresql/client/remote.go
Normal file
232
backend/utils/postgresql/client/remote.go
Normal file
@ -0,0 +1,232 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
type Remote struct {
|
||||
Client *sql.DB
|
||||
Database string
|
||||
User string
|
||||
Password string
|
||||
Address string
|
||||
Port uint
|
||||
|
||||
SSL bool
|
||||
RootCert string
|
||||
ClientKey string
|
||||
ClientCert string
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
func NewRemote(db Remote) *Remote {
|
||||
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 {
|
||||
createUser := fmt.Sprintf(`CREATE USER "%s" WITH PASSWORD '%s';`, info.Username, info.Password)
|
||||
createDB := fmt.Sprintf(`CREATE DATABASE "%s" OWNER "%s";`, info.Name, info.Username)
|
||||
grant := fmt.Sprintf(`GRANT ALL PRIVILEGES ON DATABASE "%s" TO "%s";`, info.Name, info.Username)
|
||||
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 err
|
||||
}
|
||||
_ = r.ExecSQL(grant, info.Timeout)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) CreateUser(info CreateInfo, withDeleteDB bool) error {
|
||||
sql1 := fmt.Sprintf(`CREATE USER "%s" WITH PASSWORD '%s';
|
||||
GRANT ALL PRIVILEGES ON DATABASE "%s" TO "%s";`, info.Username, info.Password, info.Name, info.Username)
|
||||
err := r.ExecSQL(sql1, info.Timeout)
|
||||
if err != nil {
|
||||
if strings.Contains(strings.ToLower(err.Error()), "already") {
|
||||
return buserr.New(constant.ErrUserIsExist)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) Delete(info DeleteInfo) error {
|
||||
//暂时不支持强制删除,就算附加了 WITH(FORCE) 也会删除失败
|
||||
err := r.ExecSQL(fmt.Sprintf(`DROP DATABASE "%s"`, info.Name), info.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.ExecSQL(fmt.Sprintf(`DROP USER "%s"`, info.Username), info.Timeout)
|
||||
}
|
||||
|
||||
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) ChangeAccess(info AccessChangeInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) 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)
|
||||
}
|
||||
}
|
||||
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
|
||||
|
||||
backupCommand := exec.Command("bash", "-c",
|
||||
fmt.Sprintf("docker run --rm --net=host -i postgres:alpine /bin/bash -c 'PGPASSWORD=%s pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s",
|
||||
r.Password, r.Address, r.Port, r.User, info.Name, fileNameItem))
|
||||
_ = backupCommand.Run()
|
||||
b := make([]byte, 5)
|
||||
n := []byte{80, 71, 68, 77, 80}
|
||||
handle, err := os.OpenFile(fileNameItem, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("backup file not found,err:%v", err)
|
||||
}
|
||||
defer handle.Close()
|
||||
_, _ = handle.Read(b)
|
||||
if string(b) != string(n) {
|
||||
errBytes, _ := os.ReadFile(fileNameItem)
|
||||
return fmt.Errorf("backup failed,err:%s", string(errBytes))
|
||||
}
|
||||
|
||||
gzipCmd := exec.Command("gzip", fileNameItem)
|
||||
stdout, err := gzipCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("gzip file %s failed, stdout: %v, err: %v", strings.TrimSuffix(info.FileName, ".gz"), string(stdout), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) Recover(info RecoverInfo) error {
|
||||
fileName := info.SourceFile
|
||||
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
|
||||
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
|
||||
gzipCmd := exec.Command("gunzip", info.SourceFile)
|
||||
stdout, err := gzipCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err)
|
||||
}
|
||||
defer func() {
|
||||
gzipCmd := exec.Command("gzip", fileName)
|
||||
_, _ = gzipCmd.CombinedOutput()
|
||||
}()
|
||||
}
|
||||
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",
|
||||
r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName))
|
||||
pipe, _ := recoverCommand.StdoutPipe()
|
||||
stderrPipe, _ := recoverCommand.StderrPipe()
|
||||
defer pipe.Close()
|
||||
defer stderrPipe.Close()
|
||||
if err := recoverCommand.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := bufio.NewReader(pipe)
|
||||
for {
|
||||
readString, err := reader.ReadString('\n')
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
all, _ := io.ReadAll(stderrPipe)
|
||||
global.LOG.Errorf("[Postgresql] DB:[%s] Recover Error: %s", info.Name, string(all))
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("[Postgresql] DB:[%s] Restoring: %s", info.Name, readString)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) SyncDB(version string) ([]SyncDBInfo, error) {
|
||||
//如果需要同步数据库,则需要强制修改用户密码,否则无法获取真实密码,后面可考虑改为添加服务器账号,手动将账号/数据库添加到管理列表
|
||||
var datas []SyncDBInfo
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (r *Remote) Close() {
|
||||
_ = r.Client.Close()
|
||||
}
|
||||
|
||||
func (r *Remote) ExecSQL(command string, timeout uint) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := r.Client.ExecContext(ctx, command); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -142,6 +142,7 @@ export namespace App {
|
||||
}
|
||||
|
||||
export interface DatabaseConnInfo {
|
||||
username: string;
|
||||
password: string;
|
||||
privilege: boolean;
|
||||
serviceName: string;
|
||||
|
@ -48,6 +48,7 @@ export namespace Database {
|
||||
permission: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface BindUser {
|
||||
database: string;
|
||||
db: string;
|
||||
@ -55,6 +56,7 @@ export namespace Database {
|
||||
password: string;
|
||||
permission: string;
|
||||
}
|
||||
|
||||
export interface MysqlLoadDB {
|
||||
from: string;
|
||||
type: string;
|
||||
@ -65,6 +67,7 @@ export namespace Database {
|
||||
type: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
export interface MysqlDBDelete {
|
||||
id: number;
|
||||
type: string;
|
||||
@ -145,6 +148,68 @@ export namespace Database {
|
||||
database: string;
|
||||
name: string;
|
||||
}
|
||||
export interface PostgresqlDBDelete {
|
||||
id: number;
|
||||
type: string;
|
||||
database: string;
|
||||
forceDelete: boolean;
|
||||
deleteBackup: boolean;
|
||||
}
|
||||
export interface PostgresqlStatus {
|
||||
uptime: string;
|
||||
version: string;
|
||||
max_connections: string;
|
||||
autovacuum: string;
|
||||
current_connections: string;
|
||||
hit_ratio: string;
|
||||
shared_buffers: string;
|
||||
buffers_clean: string;
|
||||
maxwritten_clean: string;
|
||||
buffers_backend_fsync: string;
|
||||
}
|
||||
export interface PostgresqlDBDeleteCheck {
|
||||
id: number;
|
||||
type: string;
|
||||
database: string;
|
||||
}
|
||||
export interface PostgresqlDBInfo {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
name: string;
|
||||
postgresqlName: string;
|
||||
from: string;
|
||||
format: string;
|
||||
username: string;
|
||||
password: string;
|
||||
description: string;
|
||||
}
|
||||
export interface PostgresqlConfUpdateByFile {
|
||||
type: string;
|
||||
database: string;
|
||||
file: string;
|
||||
}
|
||||
export interface PostgresqlDBCreate {
|
||||
name: string;
|
||||
from: string;
|
||||
database: string;
|
||||
format: string;
|
||||
username: string;
|
||||
password: string;
|
||||
permission: string;
|
||||
description: string;
|
||||
}
|
||||
export interface PostgresqlDBInfo {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
name: string;
|
||||
mysqlName: string;
|
||||
from: string;
|
||||
format: string;
|
||||
username: string;
|
||||
password: string;
|
||||
permission: string;
|
||||
description: string;
|
||||
}
|
||||
export interface ChangeInfo {
|
||||
id: number;
|
||||
from: string;
|
||||
|
@ -8,10 +8,42 @@ import { TimeoutEnum } from '@/enums/http-enum';
|
||||
export const searchMysqlDBs = (params: Database.SearchDBWithPage) => {
|
||||
return http.post<ResPage<Database.MysqlDBInfo>>(`/databases/search`, params);
|
||||
};
|
||||
|
||||
export const loadDatabaseFile = (type: string, database: string) => {
|
||||
return http.post<string>(`/databases/load/file`, { type: type, name: database });
|
||||
};
|
||||
|
||||
export const addPostgresqlDB = (params: Database.PostgresqlDBCreate) => {
|
||||
let request = deepCopy(params) as Database.PostgresqlDBCreate;
|
||||
if (request.password) {
|
||||
request.password = Base64.encode(request.password);
|
||||
}
|
||||
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) => {
|
||||
return http.post(`/databases/pg/conf`, params);
|
||||
};
|
||||
export const searchPostgresqlDBs = (params: Database.SearchDBWithPage) => {
|
||||
return http.post<ResPage<Database.PostgresqlDBInfo>>(`/databases/pg/search`, params);
|
||||
};
|
||||
export const updatePostgresqlDescription = (params: DescriptionUpdate) => {
|
||||
return http.post(`/databases/pg/description`, params);
|
||||
};
|
||||
export const deleteCheckPostgresqlDB = (params: Database.PostgresqlDBDeleteCheck) => {
|
||||
return http.post<Array<string>>(`/databases/pg/del/check`, params);
|
||||
};
|
||||
export const updatePostgresqlPassword = (params: Database.ChangeInfo) => {
|
||||
let request = deepCopy(params) as Database.ChangeInfo;
|
||||
if (request.value) {
|
||||
request.value = Base64.encode(request.value);
|
||||
}
|
||||
return http.post(`/databases/pg/password`, request);
|
||||
};
|
||||
export const deletePostgresqlDB = (params: Database.PostgresqlDBDelete) => {
|
||||
return http.post(`/databases/pg/del`, params);
|
||||
};
|
||||
export const addMysqlDB = (params: Database.MysqlDBCreate) => {
|
||||
let request = deepCopy(params) as Database.MysqlDBCreate;
|
||||
if (request.password) {
|
||||
|
@ -201,6 +201,8 @@ const getTitle = (key: string) => {
|
||||
return i18n.global.t('website.website');
|
||||
case 'mysql':
|
||||
return 'MySQL ' + i18n.global.t('menu.database');
|
||||
case 'postgresql':
|
||||
return 'PostgreSQL ' + i18n.global.t('menu.database');
|
||||
case 'redis':
|
||||
return 'Redis ' + i18n.global.t('menu.database');
|
||||
}
|
||||
|
@ -54,7 +54,9 @@ const handleChange = (label: string) => {
|
||||
onMounted(() => {
|
||||
if (buttonArray.value.length) {
|
||||
let isPathExist = false;
|
||||
const btn = buttonArray.value.find((btn) => btn.path === router.currentRoute.value.path);
|
||||
const btn = buttonArray.value.find((btn) => {
|
||||
return router.currentRoute.value.path.startsWith(btn.path);
|
||||
});
|
||||
if (btn) {
|
||||
isPathExist = true;
|
||||
activeName.value = btn.label;
|
||||
|
@ -352,6 +352,7 @@ const message = {
|
||||
deleteHelper: '" to delete this database',
|
||||
create: 'Create database',
|
||||
noMysql: 'Database service (MySQL or MariaDB)',
|
||||
noPostgresql: 'Database service Postgresql',
|
||||
goUpgrade: 'Go for upgrade',
|
||||
goInstall: 'Go for install',
|
||||
source: 'Source',
|
||||
|
@ -349,6 +349,7 @@ const message = {
|
||||
deleteHelper: '" 刪除此數據庫',
|
||||
create: '創建數據庫',
|
||||
noMysql: '數據庫服務 (MySQL 或 MariaDB)',
|
||||
noPostgresql: '數據庫服務 Postgresql',
|
||||
goUpgrade: '去應用商店升級',
|
||||
goInstall: '去應用商店安裝',
|
||||
source: '來源',
|
||||
|
@ -349,6 +349,7 @@ const message = {
|
||||
deleteHelper: '" 删除此数据库',
|
||||
create: '创建数据库',
|
||||
noMysql: '数据库服务 (MySQL 或 MariaDB)',
|
||||
noPostgresql: '数据库服务 Postgresql',
|
||||
goUpgrade: '去应用列表升级',
|
||||
goInstall: '去应用商店安装',
|
||||
source: '来源',
|
||||
|
@ -48,6 +48,37 @@ const databaseRouter = {
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'postgresql',
|
||||
name: 'PostgreSQL',
|
||||
component: () => import('@/views/database/postgresql/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/databases',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'postgresql/remote',
|
||||
name: 'PostgreSQL-Remote',
|
||||
component: () => import('@/views/database/postgresql/remote/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/databases',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'postgresql/setting/:type/:database',
|
||||
name: 'PostgreSQL-Setting',
|
||||
component: () => import('@/views/database/postgresql/setting/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/databases',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'redis',
|
||||
name: 'Redis',
|
||||
|
@ -13,6 +13,10 @@ const buttons = [
|
||||
label: 'MySQL',
|
||||
path: '/databases/mysql',
|
||||
},
|
||||
{
|
||||
label: 'PostgreSQL',
|
||||
path: '/databases/postgresql',
|
||||
},
|
||||
{
|
||||
label: 'Redis',
|
||||
path: '/databases/redis',
|
||||
|
54
frontend/src/views/database/postgresql/check/index.vue
Normal file
54
frontend/src/views/database/postgresql/check/index.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="$t('app.checkTitle')"
|
||||
width="50%"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="20" :offset="2" v-if="open">
|
||||
<el-alert
|
||||
type="error"
|
||||
:description="$t('app.deleteHelper', [$t('app.database')])"
|
||||
center
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<br />
|
||||
<el-descriptions border :column="1">
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<a href="javascript:void(0);" @click="toApp()">{{ $t('app.app') }}</a>
|
||||
</template>
|
||||
{{ installData.join(',') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
|
||||
interface InstallProps {
|
||||
items: Array<string>;
|
||||
}
|
||||
const installData = ref();
|
||||
let open = ref(false);
|
||||
|
||||
const acceptParams = (props: InstallProps) => {
|
||||
installData.value = props.items;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const toApp = () => {
|
||||
router.push({ name: 'AppInstalled' });
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
222
frontend/src/views/database/postgresql/conn/index.vue
Normal file
222
frontend/src/views/database/postgresql/conn/index.vue
Normal file
@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<el-drawer v-model="dialogVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('database.databaseConnInfo')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top">
|
||||
<el-row type="flex" justify="center" v-if="form.from === 'local'">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('database.containerConn')">
|
||||
<el-tag>
|
||||
{{ form.serviceName + form.port }}
|
||||
</el-tag>
|
||||
<CopyButton :content="form.serviceName + form.port" type="icon" />
|
||||
<span class="input-help">
|
||||
{{ $t('database.containerConnHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.remoteConn')">
|
||||
<el-tooltip v-if="loadConnInfo(true).length > 48" :content="loadConnInfo(true)" placement="top">
|
||||
<el-tag>{{ loadConnInfo(true).substring(0, 48) }}...</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else>{{ loadConnInfo(true) }}</el-tag>
|
||||
<CopyButton :content="form.systemIP + ':' + form.port" type="icon" />
|
||||
<span class="input-help">{{ $t('database.remoteConnHelper2') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider border-style="dashed" />
|
||||
<el-form-item :label="$t('commons.login.username')" prop="username">
|
||||
<el-input type="text" readonly disabled v-model="form.username">
|
||||
<template #append>
|
||||
<el-button-group>
|
||||
<CopyButton :content="form.username" />
|
||||
</el-button-group>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" :rules="Rules.paramComplexity" prop="password">
|
||||
<el-input type="password" show-password clearable v-model="form.password">
|
||||
<template #append>
|
||||
<el-button-group>
|
||||
<CopyButton :content="form.password" />
|
||||
<el-button @click="random">
|
||||
{{ $t('commons.button.random') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="center" v-if="form.from !== 'local'">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('database.remoteConn')">
|
||||
<el-tooltip
|
||||
v-if="loadConnInfo(false).length > 48"
|
||||
:content="loadConnInfo(false)"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag>{{ loadConnInfo(false).substring(0, 48) }}...</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else>{{ loadConnInfo(false) }}</el-tag>
|
||||
<CopyButton :content="form.remoteIP + ':' + form.port" type="icon" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.username')">
|
||||
<el-tag>{{ form.username }}</el-tag>
|
||||
<CopyButton :content="form.username" type="icon" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')">
|
||||
<el-tag>{{ form.password }}</el-tag>
|
||||
<CopyButton :content="form.password" type="icon" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" @cancel="loadPassword"></ConfirmDialog>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="dialogVisible = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { getDatabase, updatePostgresqlPassword } from '@/api/modules/database';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { GetAppConnInfo } from '@/api/modules/app';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { getRandomStr } from '@/utils/util';
|
||||
import { getSettingInfo } from '@/api/modules/setting';
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const form = reactive({
|
||||
systemIP: '',
|
||||
password: '',
|
||||
serviceName: '',
|
||||
privilege: false,
|
||||
port: 0,
|
||||
|
||||
from: '',
|
||||
type: '',
|
||||
database: '',
|
||||
username: '',
|
||||
remoteIP: '',
|
||||
});
|
||||
|
||||
const confirmDialogRef = ref();
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
interface DialogProps {
|
||||
from: string;
|
||||
type: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
const acceptParams = (param: DialogProps): void => {
|
||||
form.password = '';
|
||||
form.from = param.from;
|
||||
form.type = param.type;
|
||||
form.database = param.database;
|
||||
loadAccess();
|
||||
loadPassword();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
function loadConnInfo(isLocal: boolean) {
|
||||
let ip = isLocal ? form.systemIP : form.remoteIP;
|
||||
let info = ip + ':' + form.port;
|
||||
return info;
|
||||
}
|
||||
|
||||
const random = async () => {
|
||||
form.password = getRandomStr(16);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const loadAccess = async () => {
|
||||
if (form.from === 'local') {
|
||||
// const res = await loadRemoteAccess(form.type, form.database);
|
||||
form.privilege = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadSystemIP = async () => {
|
||||
const res = await getSettingInfo();
|
||||
form.systemIP = res.data.systemIP || i18n.global.t('database.localIP');
|
||||
};
|
||||
|
||||
const loadPassword = async () => {
|
||||
if (form.from === 'local') {
|
||||
const res = await GetAppConnInfo(form.type, form.database);
|
||||
form.username = res.data.username || '';
|
||||
form.password = res.data.password || '';
|
||||
form.port = res.data.port || 5432;
|
||||
form.serviceName = res.data.serviceName || '';
|
||||
loadSystemIP();
|
||||
return;
|
||||
}
|
||||
const res = await getDatabase(form.database);
|
||||
form.password = res.data.password || '';
|
||||
form.port = res.data.port || 5432;
|
||||
form.username = res.data.username;
|
||||
form.password = res.data.password;
|
||||
form.remoteIP = res.data.address;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
let param = {
|
||||
id: 0,
|
||||
from: form.from,
|
||||
type: form.type,
|
||||
database: form.database,
|
||||
value: form.password,
|
||||
};
|
||||
loading.value = true;
|
||||
await updatePostgresqlPassword(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let params = {
|
||||
header: i18n.global.t('database.confChange'),
|
||||
operationInfo: i18n.global.t('database.restartNowHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmDialogRef.value!.acceptParams(params);
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
131
frontend/src/views/database/postgresql/create/index.vue
Normal file
131
frontend/src/views/database/postgresql/create/index.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<el-drawer v-model="createVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('database.create')" :back="handleClose" />
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<el-form ref="formRef" label-position="top" :model="form" :rules="rules">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model.trim="form.name" @input="form.username = form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.username')" prop="username">
|
||||
<el-input clearable v-model.trim="form.username" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input type="password" clearable show-password v-model.trim="form.password">
|
||||
<template #append>
|
||||
<el-button @click="random">{{ $t('commons.button.random') }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('commons.table.type')" prop="database">
|
||||
<el-tag>{{ form.database + ' [' + form.type + ']' }}</el-tag>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||
<el-input type="textarea" clearable v-model="form.description" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="createVisible = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { addPostgresqlDB } from '@/api/modules/database';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { getRandomStr } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const createVisible = ref(false);
|
||||
const form = reactive({
|
||||
name: '',
|
||||
from: 'local',
|
||||
type: '',
|
||||
database: '',
|
||||
format: '',
|
||||
username: '',
|
||||
password: '',
|
||||
permission: '',
|
||||
permissionIPs: '',
|
||||
description: '',
|
||||
});
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.dbName],
|
||||
username: [Rules.requiredInput, Rules.name],
|
||||
password: [Rules.paramComplexity],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
interface DialogProps {
|
||||
from: string;
|
||||
type: string;
|
||||
database: string;
|
||||
}
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
form.name = '';
|
||||
form.from = params.from;
|
||||
form.type = params.type;
|
||||
form.database = params.database;
|
||||
form.format = 'UTF8';
|
||||
form.username = '';
|
||||
form.permission = '%';
|
||||
form.permissionIPs = '';
|
||||
form.description = '';
|
||||
random();
|
||||
createVisible.value = true;
|
||||
};
|
||||
const handleClose = () => {
|
||||
createVisible.value = false;
|
||||
};
|
||||
|
||||
const random = async () => {
|
||||
form.password = getRandomStr(16);
|
||||
};
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
|
||||
loading.value = true;
|
||||
await addPostgresqlDB(form)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
createVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
95
frontend/src/views/database/postgresql/delete/index.vue
Normal file
95
frontend/src/views/database/postgresql/delete/index.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('commons.button.delete') + ' - ' + dbName"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteReq.deleteBackup" :label="$t('app.deleteBackup')" />
|
||||
<span class="input-help">
|
||||
{{ $t('database.deleteBackupHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<span style="font-size: 12px">{{ $t('database.delete') }}</span>
|
||||
<span style="font-size: 12px; color: red; font-weight: 500">{{ dbName }}</span>
|
||||
<span style="font-size: 12px">{{ $t('database.deleteHelper') }}</span>
|
||||
</div>
|
||||
<el-input v-model="deleteInfo" :placeholder="dbName"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submit" :disabled="deleteInfo != dbName || loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { deletePostgresqlDB } from '@/api/modules/database';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
let deleteReq = ref({
|
||||
id: 0,
|
||||
type: '',
|
||||
database: '',
|
||||
deleteBackup: false,
|
||||
forceDelete: false,
|
||||
});
|
||||
let dialogVisible = ref(false);
|
||||
let loading = ref(false);
|
||||
let deleteInfo = ref('');
|
||||
let dbName = ref('');
|
||||
|
||||
const deleteForm = ref<FormInstance>();
|
||||
|
||||
interface DialogProps {
|
||||
id: number;
|
||||
type: string;
|
||||
name: string;
|
||||
database: string;
|
||||
}
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const acceptParams = async (prop: DialogProps) => {
|
||||
deleteReq.value = {
|
||||
id: prop.id,
|
||||
type: prop.type,
|
||||
database: prop.database,
|
||||
deleteBackup: false,
|
||||
forceDelete: false,
|
||||
};
|
||||
dbName.value = prop.name;
|
||||
deleteInfo.value = '';
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
loading.value = true;
|
||||
deletePostgresqlDB(deleteReq.value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
520
frontend/src/views/database/postgresql/index.vue
Normal file
520
frontend/src/views/database/postgresql/index.vue
Normal file
@ -0,0 +1,520 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="app-status" style="margin-top: 20px" v-if="currentDB?.from === 'remote'">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">PostgreSQL</el-tag>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<LayoutContent :title="'PostgreSQL ' + $t('menu.database')">
|
||||
<template #app v-if="currentDB?.from === 'local'">
|
||||
<AppStatus
|
||||
:app-key="appKey"
|
||||
:app-name="appName"
|
||||
v-model:loading="loading"
|
||||
v-model:mask-show="maskShow"
|
||||
@setting="onSetting"
|
||||
@is-exist="checkExist"
|
||||
></AppStatus>
|
||||
</template>
|
||||
|
||||
<template #search v-if="currentDB">
|
||||
<el-select v-model="currentDBName" @change="changeDatabase()">
|
||||
<template #prefix>{{ $t('commons.table.type') }}</template>
|
||||
<el-option-group :label="$t('database.local')">
|
||||
<div v-for="(item, index) in dbOptionsLocal" :key="index">
|
||||
<el-option v-if="item.from === 'local'" :value="item.database" class="optionClass">
|
||||
<span v-if="item.database.length < 25">{{ item.database }}</span>
|
||||
<el-tooltip v-else :content="item.database" placement="top">
|
||||
<span>{{ item.database.substring(0, 25) }}...</span>
|
||||
</el-tooltip>
|
||||
</el-option>
|
||||
</div>
|
||||
<el-button link type="primary" class="jumpAdd" @click="goRouter('app')" icon="Position">
|
||||
{{ $t('database.goInstall') }}
|
||||
</el-button>
|
||||
</el-option-group>
|
||||
<el-option-group :label="$t('database.remote')">
|
||||
<div v-for="(item, index) in dbOptionsRemote" :key="index">
|
||||
<el-option v-if="item.from === 'remote'" :value="item.database" class="optionClass">
|
||||
<span v-if="item.database.length < 25">{{ item.database }}</span>
|
||||
<el-tooltip v-else :content="item.database" placement="top">
|
||||
<span>{{ item.database.substring(0, 25) }}...</span>
|
||||
</el-tooltip>
|
||||
</el-option>
|
||||
</div>
|
||||
<el-button link type="primary" class="jumpAdd" @click="goRouter('remote')" icon="Position">
|
||||
{{ $t('database.createRemoteDB') }}
|
||||
</el-button>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<el-button
|
||||
v-if="currentDB && (currentDB.from !== 'local' || postgresqlStatus === 'Running')"
|
||||
type="primary"
|
||||
@click="onOpenDialog()"
|
||||
>
|
||||
{{ $t('database.create') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="currentDB && (currentDB.from !== 'local' || postgresqlStatus === 'Running')"
|
||||
@click="onChangeConn"
|
||||
type="primary"
|
||||
plain
|
||||
>
|
||||
{{ $t('database.databaseConnInfo') }}
|
||||
</el-button>
|
||||
<el-button @click="goRemoteDB" type="primary" plain>
|
||||
{{ $t('database.remoteDB') }}
|
||||
</el-button>
|
||||
<el-button @click="goDashboard()" type="primary" plain>PGAdmin4</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
clearable
|
||||
@clear="search()"
|
||||
suffix-icon="Search"
|
||||
@keyup.enter="search()"
|
||||
@change="search()"
|
||||
:placeholder="$t('commons.button.search')"
|
||||
></el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main v-if="currentDB">
|
||||
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
|
||||
<el-table-column :label="$t('commons.table.name')" prop="name" sortable />
|
||||
<el-table-column :label="$t('commons.login.username')" prop="username" />
|
||||
<el-table-column :label="$t('commons.login.password')" prop="password">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center" v-if="row.password">
|
||||
<div class="star-center" v-if="!row.showPassword">
|
||||
<span>**********</span>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="row.showPassword">
|
||||
{{ row.password }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="!row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = true"
|
||||
icon="View"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<el-button
|
||||
v-if="row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = false"
|
||||
icon="Hide"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<div>
|
||||
<CopyButton :content="row.password" type="icon" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-link @click="onChangePassword(row)">
|
||||
<span style="font-size: 12px">{{ $t('database.passwordHelper') }}</span>
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.description')" prop="description">
|
||||
<template #default="{ row }">
|
||||
<fu-input-rw-switch v-model="row.description" @blur="onChange(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="370px"
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<div v-if="dbOptionsLocal.length === 0 && dbOptionsRemote.length === 0">
|
||||
<LayoutContent :title="'PostgreSQL ' + $t('menu.database')" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div>
|
||||
<span>{{ $t('app.checkInstalledWarn', [$t('database.noPostgresql')]) }}</span>
|
||||
<span @click="goRouter('app')">
|
||||
<el-icon class="ml-2"><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="dashboardVisible"
|
||||
:title="$t('app.checkTitle')"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-alert :closable="false" :title="$t('app.checkInstalledWarn', [dashboardName])" type="info">
|
||||
<el-link icon="Position" @click="getAppDetail" type="primary">
|
||||
{{ $t('database.goInstall') }}
|
||||
</el-link>
|
||||
</el-alert>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dashboardVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<PasswordDialog ref="passwordRef" @search="search" />
|
||||
<RootPasswordDialog ref="connRef" />
|
||||
<UploadDialog ref="uploadRef" />
|
||||
<OperateDialog @search="search" ref="dialogRef" />
|
||||
<Backups ref="dialogBackupRef" />
|
||||
|
||||
<AppResources ref="checkRef"></AppResources>
|
||||
<DeleteDialog ref="deleteRef" @search="search" />
|
||||
|
||||
<PortJumpDialog ref="dialogPortJumpRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import OperateDialog from '@/views/database/postgresql/create/index.vue';
|
||||
import DeleteDialog from '@/views/database/postgresql/delete/index.vue';
|
||||
import PasswordDialog from '@/views/database/postgresql/password/index.vue';
|
||||
import RootPasswordDialog from '@/views/database/postgresql/conn/index.vue';
|
||||
import AppResources from '@/views/database/postgresql/check/index.vue';
|
||||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import Backups from '@/components/backup/index.vue';
|
||||
import UploadDialog from '@/components/upload/index.vue';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import {
|
||||
deleteCheckPostgresqlDB,
|
||||
listDatabases,
|
||||
searchPostgresqlDBs,
|
||||
updatePostgresqlDescription,
|
||||
} from '@/api/modules/database';
|
||||
import i18n from '@/lang';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { GetAppPort } from '@/api/modules/app';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const maskShow = ref(true);
|
||||
|
||||
const appKey = ref('postgresql');
|
||||
const appName = ref();
|
||||
|
||||
const dbOptionsLocal = ref<Array<Database.DatabaseOption>>([]);
|
||||
const dbOptionsRemote = ref<Array<Database.DatabaseOption>>([]);
|
||||
const currentDB = ref<Database.DatabaseOption>();
|
||||
const currentDBName = ref();
|
||||
|
||||
const checkRef = ref();
|
||||
const deleteRef = ref();
|
||||
|
||||
const pgadminPort = ref();
|
||||
const dashboardName = ref();
|
||||
const dashboardKey = ref();
|
||||
const dashboardVisible = ref(false);
|
||||
|
||||
const dialogPortJumpRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'postgresql-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: Number(localStorage.getItem('postgresql-page-size')) || 10,
|
||||
total: 0,
|
||||
orderBy: 'created_at',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
|
||||
const postgresqlContainer = ref();
|
||||
const postgresqlStatus = ref();
|
||||
const postgresqlVersion = ref();
|
||||
|
||||
const dialogRef = ref();
|
||||
const onOpenDialog = async () => {
|
||||
let params = {
|
||||
from: currentDB.value.from,
|
||||
type: currentDB.value.type,
|
||||
database: currentDBName.value,
|
||||
};
|
||||
dialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const dialogBackupRef = ref();
|
||||
|
||||
const uploadRef = ref();
|
||||
|
||||
const connRef = ref();
|
||||
const onChangeConn = async () => {
|
||||
connRef.value!.acceptParams({
|
||||
from: currentDB.value.from,
|
||||
type: currentDB.value.type,
|
||||
database: currentDBName.value,
|
||||
});
|
||||
};
|
||||
|
||||
const goRemoteDB = async () => {
|
||||
if (currentDB.value) {
|
||||
globalStore.setCurrentDB(currentDB.value.database);
|
||||
}
|
||||
router.push({ name: 'PostgreSQL-Remote' });
|
||||
};
|
||||
|
||||
const passwordRef = ref();
|
||||
|
||||
const onSetting = async () => {
|
||||
if (currentDB.value) {
|
||||
globalStore.setCurrentDB(currentDB.value.database);
|
||||
}
|
||||
router.push({
|
||||
name: 'PostgreSQL-Setting',
|
||||
params: { type: currentDB.value.type, database: currentDB.value.database },
|
||||
});
|
||||
};
|
||||
|
||||
const changeDatabase = async () => {
|
||||
for (const item of dbOptionsLocal.value) {
|
||||
if (item.database == currentDBName.value) {
|
||||
currentDB.value = item;
|
||||
appKey.value = item.type;
|
||||
appName.value = item.database;
|
||||
search();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const item of dbOptionsRemote.value) {
|
||||
if (item.database == currentDBName.value) {
|
||||
currentDB.value = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
search();
|
||||
};
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
info: searchName.value,
|
||||
database: currentDB.value.database,
|
||||
orderBy: paginationConfig.orderBy,
|
||||
order: paginationConfig.order,
|
||||
};
|
||||
const res = await searchPostgresqlDBs(params);
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
const goRouter = async (target: string) => {
|
||||
if (target === 'app') {
|
||||
router.push({ name: 'AppAll', query: { install: 'postgresql' } });
|
||||
return;
|
||||
}
|
||||
router.push({ name: 'PostgreSQL-Remote' });
|
||||
};
|
||||
|
||||
const onChange = async (info: any) => {
|
||||
await updatePostgresqlDescription({ id: info.id, description: info.description });
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
};
|
||||
|
||||
const goDashboard = async () => {
|
||||
if (pgadminPort.value === 0) {
|
||||
dashboardName.value = 'PGAdmin4';
|
||||
dashboardKey.value = 'pgadmin4';
|
||||
dashboardVisible.value = true;
|
||||
return;
|
||||
}
|
||||
dialogPortJumpRef.value.acceptParams({ port: pgadminPort.value });
|
||||
return;
|
||||
};
|
||||
|
||||
const getAppDetail = () => {
|
||||
router.push({ name: 'AppAll', query: { install: dashboardKey.value } });
|
||||
};
|
||||
|
||||
const loadPGAdminPort = async () => {
|
||||
const res = await GetAppPort('pgadmin4', '');
|
||||
pgadminPort.value = res.data;
|
||||
};
|
||||
|
||||
const checkExist = (data: App.CheckInstalled) => {
|
||||
postgresqlStatus.value = data.status;
|
||||
postgresqlVersion.value = data.version;
|
||||
postgresqlContainer.value = data.containerName;
|
||||
};
|
||||
|
||||
const loadDBOptions = async () => {
|
||||
const res = await listDatabases('postgresql');
|
||||
let datas = res.data || [];
|
||||
dbOptionsLocal.value = [];
|
||||
dbOptionsRemote.value = [];
|
||||
currentDBName.value = globalStore.currentDB;
|
||||
for (const item of datas) {
|
||||
if (currentDBName.value && item.database === currentDBName.value) {
|
||||
currentDB.value = item;
|
||||
if (item.from === 'local') {
|
||||
appKey.value = item.type;
|
||||
appName.value = item.database;
|
||||
}
|
||||
}
|
||||
if (item.from === 'local') {
|
||||
dbOptionsLocal.value.push(item);
|
||||
} else {
|
||||
dbOptionsRemote.value.push(item);
|
||||
}
|
||||
}
|
||||
if (currentDB.value) {
|
||||
globalStore.setCurrentDB('');
|
||||
search();
|
||||
return;
|
||||
}
|
||||
if (dbOptionsLocal.value.length !== 0) {
|
||||
currentDB.value = dbOptionsLocal.value[0];
|
||||
currentDBName.value = dbOptionsLocal.value[0].database;
|
||||
appKey.value = dbOptionsLocal.value[0].type;
|
||||
appName.value = dbOptionsLocal.value[0].database;
|
||||
}
|
||||
if (!currentDB.value && dbOptionsRemote.value.length !== 0) {
|
||||
currentDB.value = dbOptionsRemote.value[0];
|
||||
currentDBName.value = dbOptionsRemote.value[0].database;
|
||||
}
|
||||
if (currentDB.value) {
|
||||
search();
|
||||
}
|
||||
};
|
||||
const onDelete = async (row: Database.PostgresqlDBInfo) => {
|
||||
let param = {
|
||||
id: row.id,
|
||||
type: currentDB.value.type,
|
||||
database: currentDBName.value,
|
||||
};
|
||||
const res = await deleteCheckPostgresqlDB(param);
|
||||
if (res.data && res.data.length > 0) {
|
||||
checkRef.value.acceptParams({ items: res.data });
|
||||
} else {
|
||||
deleteRef.value.acceptParams({
|
||||
id: row.id,
|
||||
type: currentDB.value.type,
|
||||
database: currentDBName.value,
|
||||
name: row.name,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onChangePassword = async (row: Database.PostgresqlDBInfo) => {
|
||||
let param = {
|
||||
id: row.id,
|
||||
from: row.from,
|
||||
type: currentDB.value.type,
|
||||
database: currentDBName.value,
|
||||
postgresqlName: row.name,
|
||||
operation: 'password',
|
||||
username: row.username,
|
||||
password: row.password,
|
||||
};
|
||||
passwordRef.value.acceptParams(param);
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('database.changePassword'),
|
||||
click: (row: Database.PostgresqlDBInfo) => {
|
||||
onChangePassword(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('database.backupList'),
|
||||
click: (row: Database.PostgresqlDBInfo) => {
|
||||
let params = {
|
||||
type: currentDB.value.type,
|
||||
name: currentDBName.value,
|
||||
detailName: row.name,
|
||||
};
|
||||
dialogBackupRef.value!.acceptParams(params);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('database.loadBackup'),
|
||||
click: (row: Database.PostgresqlDBInfo) => {
|
||||
let params = {
|
||||
type: currentDB.value.type,
|
||||
name: currentDBName.value,
|
||||
detailName: row.name,
|
||||
remark: row.format,
|
||||
};
|
||||
uploadRef.value!.acceptParams(params);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: Database.PostgresqlDBInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
loadDBOptions();
|
||||
loadPGAdminPort();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.iconInTable {
|
||||
margin-left: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.jumpAdd {
|
||||
margin-top: 10px;
|
||||
margin-left: 15px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.tagClass {
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.optionClass {
|
||||
min-width: 350px;
|
||||
}
|
||||
</style>
|
165
frontend/src/views/database/postgresql/password/index.vue
Normal file
165
frontend/src/views/database/postgresql/password/index.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer v-model="changeVisible" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :resource="changeForm.postgresqlName" :back="handleClose" />
|
||||
</template>
|
||||
<el-form v-loading="loading" ref="changeFormRef" :model="changeForm" label-position="top">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<div v-if="changeForm.operation === 'password'">
|
||||
<el-form-item :label="$t('commons.login.username')" prop="userName">
|
||||
<el-input disabled v-model="changeForm.userName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
clearable
|
||||
show-password
|
||||
v-model="changeForm.password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="changeVisible = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="submitChangeInfo(changeFormRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit"></ConfirmDialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { deleteCheckPostgresqlDB, updatePostgresqlPassword } from '@/api/modules/database';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref();
|
||||
const changeVisible = ref(false);
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const changeFormRef = ref<FormInstance>();
|
||||
const title = ref();
|
||||
const changeForm = reactive({
|
||||
id: 0,
|
||||
from: '',
|
||||
type: '',
|
||||
database: '',
|
||||
postgresqlName: '',
|
||||
userName: '',
|
||||
password: '',
|
||||
operation: '',
|
||||
value: '',
|
||||
});
|
||||
const confirmDialogRef = ref();
|
||||
|
||||
interface DialogProps {
|
||||
id: number;
|
||||
from: string;
|
||||
type: string;
|
||||
database: string;
|
||||
postgresqlName: string;
|
||||
username: string;
|
||||
password: string;
|
||||
operation: string;
|
||||
privilege: string;
|
||||
privilegeIPs: string;
|
||||
value: string;
|
||||
}
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
title.value = i18n.global.t('database.changePassword');
|
||||
|
||||
changeForm.id = params.id;
|
||||
changeForm.from = params.from;
|
||||
changeForm.type = params.type;
|
||||
changeForm.database = params.database;
|
||||
changeForm.postgresqlName = params.postgresqlName;
|
||||
changeForm.userName = params.username;
|
||||
changeForm.password = params.password;
|
||||
changeForm.operation = params.operation;
|
||||
changeForm.value = params.value;
|
||||
changeVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const handleClose = () => {
|
||||
changeVisible.value = false;
|
||||
};
|
||||
|
||||
const submitChangeInfo = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let param = {
|
||||
id: changeForm.id,
|
||||
from: changeForm.from,
|
||||
type: changeForm.type,
|
||||
database: changeForm.database,
|
||||
value: '',
|
||||
};
|
||||
if (changeForm.operation === 'password') {
|
||||
const res = await deleteCheckPostgresqlDB(param);
|
||||
if (res.data && res.data.length > 0) {
|
||||
let params = {
|
||||
header: i18n.global.t('database.changePassword'),
|
||||
operationInfo: i18n.global.t('database.changePasswordHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmDialogRef.value!.acceptParams(params);
|
||||
} else {
|
||||
param.value = changeForm.password;
|
||||
loading.value = true;
|
||||
await updatePostgresqlPassword(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
changeVisible.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
let param = {
|
||||
id: changeForm.id,
|
||||
from: changeForm.from,
|
||||
type: changeForm.type,
|
||||
database: changeForm.database,
|
||||
value: changeForm.password,
|
||||
};
|
||||
loading.value = true;
|
||||
await updatePostgresqlPassword(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
changeVisible.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('commons.button.delete') + ' - ' + deleteReq.database"
|
||||
width="30%"
|
||||
: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">
|
||||
{{ $t('database.deleteBackupHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<span style="font-size: 12px">{{ $t('database.delete') }}</span>
|
||||
<span style="font-size: 12px; color: red; font-weight: 500">{{ deleteReq.database }}</span>
|
||||
<span style="font-size: 12px">{{ $t('database.deleteHelper') }}</span>
|
||||
</div>
|
||||
<el-input v-model="deleteInfo" :placeholder="deleteReq.database"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submit" :disabled="deleteInfo != deleteReq.database || loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { deleteDatabase } from '@/api/modules/database';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
let deleteReq = ref({
|
||||
id: 0,
|
||||
database: '',
|
||||
deleteBackup: false,
|
||||
forceDelete: false,
|
||||
});
|
||||
let dialogVisible = ref(false);
|
||||
let loading = ref(false);
|
||||
let deleteInfo = ref('');
|
||||
|
||||
const deleteForm = ref<FormInstance>();
|
||||
|
||||
interface DialogProps {
|
||||
id: number;
|
||||
name: string;
|
||||
database: string;
|
||||
}
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const acceptParams = async (prop: DialogProps) => {
|
||||
deleteReq.value = {
|
||||
id: prop.id,
|
||||
database: prop.database,
|
||||
deleteBackup: false,
|
||||
forceDelete: false,
|
||||
};
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
loading.value = true;
|
||||
deleteDatabase(deleteReq.value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
186
frontend/src/views/database/postgresql/remote/index.vue
Normal file
186
frontend/src/views/database/postgresql/remote/index.vue
Normal file
@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<LayoutContent>
|
||||
<template #title>
|
||||
<back-button name="PostgreSQL" :header="$t('database.remoteDB')" />
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('database.createRemoteDB') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
clearable
|
||||
@clear="search()"
|
||||
suffix-icon="Search"
|
||||
@keyup.enter="search()"
|
||||
@change="search()"
|
||||
:placeholder="$t('commons.button.search')"
|
||||
></el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
|
||||
<el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable />
|
||||
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
|
||||
<el-table-column :label="$t('commons.login.username')" prop="username" />
|
||||
<el-table-column :label="$t('commons.login.password')" prop="password">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<div class="star-center">
|
||||
<span v-if="!row.showPassword">**********</span>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="row.showPassword">
|
||||
{{ row.password }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="!row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = true"
|
||||
icon="View"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<el-button
|
||||
v-if="row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = false"
|
||||
icon="Hide"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<div>
|
||||
<CopyButton :content="row.password" type="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
:label="$t('commons.table.description')"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="170px"
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<AppResources ref="checkRef"></AppResources>
|
||||
<OperateDialog ref="dialogRef" @search="search" />
|
||||
<DeleteDialog ref="deleteRef" @search="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
import AppResources from '@/views/database/postgresql/check/index.vue';
|
||||
import OperateDialog from '@/views/database/postgresql/remote/operate/index.vue';
|
||||
import DeleteDialog from '@/views/database/postgresql/remote/delete/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { Database } from '@/api/interface/database';
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const dialogRef = ref();
|
||||
const checkRef = ref();
|
||||
const deleteRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'postgresql-remote-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
orderBy: 'created_at',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
info: searchName.value,
|
||||
type: 'postgresql',
|
||||
orderBy: paginationConfig.orderBy,
|
||||
order: paginationConfig.order,
|
||||
};
|
||||
const res = await searchDatabases(params);
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
const onOpenDialog = async (
|
||||
title: string,
|
||||
rowData: Partial<Database.DatabaseInfo> = {
|
||||
name: '',
|
||||
type: 'postgresql',
|
||||
version: '16.x',
|
||||
address: '',
|
||||
port: 5432,
|
||||
username: 'postgres',
|
||||
password: '',
|
||||
description: '',
|
||||
},
|
||||
) => {
|
||||
let params = {
|
||||
title,
|
||||
rowData: { ...rowData },
|
||||
};
|
||||
dialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const onDelete = async (row: Database.DatabaseInfo) => {
|
||||
const res = await deleteCheckDatabase(row.id);
|
||||
if (res.data && res.data.length > 0) {
|
||||
checkRef.value.acceptParams({ items: res.data });
|
||||
} else {
|
||||
deleteRef.value.acceptParams({
|
||||
id: row.id,
|
||||
database: row.name,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: (row: Database.DatabaseInfo) => {
|
||||
onOpenDialog('edit', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: Database.DatabaseInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
175
frontend/src/views/database/postgresql/remote/operate/index.vue
Normal file
175
frontend/src/views/database/postgresql/remote/operate/index.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:hideResource="dialogData.title === 'create'"
|
||||
:header="title"
|
||||
:resource="dialogData.rowData?.name"
|
||||
:back="handleClose"
|
||||
/>
|
||||
</template>
|
||||
<el-form ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input
|
||||
v-if="dialogData.title === 'create'"
|
||||
clearable
|
||||
v-model.trim="dialogData.rowData!.name"
|
||||
/>
|
||||
<el-tag v-else>{{ dialogData.rowData!.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.version')" prop="version">
|
||||
<el-radio-group v-model="dialogData.rowData!.version" @change="isOK = false">
|
||||
<el-radio label="16.x" />
|
||||
<el-radio label="15.x" />
|
||||
<el-radio label="14.x" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.address')" prop="address">
|
||||
<el-input @change="isOK = false" clearable v-model.trim="dialogData.rowData!.address" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.port')" prop="port">
|
||||
<el-input @change="isOK = false" clearable v-model.number="dialogData.rowData!.port" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.username')" prop="username">
|
||||
<el-input @change="isOK = false" clearable v-model.trim="dialogData.rowData!.username" />
|
||||
<span class="input-help">{{ $t('database.userHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input
|
||||
@change="isOK = false"
|
||||
type="password"
|
||||
clearable
|
||||
show-password
|
||||
v-model.trim="dialogData.rowData!.password"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.description" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button @click="onSubmit(formRef, 'check')">
|
||||
{{ $t('terminal.testConn') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :disabled="!isOK" @click="onSubmit(formRef, dialogData.title)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { addDatabase, checkDatabase, editDatabase } from '@/api/modules/database';
|
||||
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
rowData?: Database.DatabaseInfo;
|
||||
getTableList?: () => Promise<any>;
|
||||
}
|
||||
const title = ref<string>('');
|
||||
const drawerVisible = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
const isOK = ref(false);
|
||||
const loading = ref();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
dialogData.value.rowData.hasCA = dialogData.value.rowData.rootCert?.length !== 0;
|
||||
title.value = i18n.global.t('database.' + dialogData.value.title + 'RemoteDB');
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput],
|
||||
type: [Rules.requiredSelect],
|
||||
version: [Rules.requiredSelect],
|
||||
address: [Rules.ipV4V6OrDomain],
|
||||
port: [Rules.port],
|
||||
username: [Rules.requiredInput],
|
||||
password: [Rules.requiredInput],
|
||||
|
||||
clientKey: [Rules.requiredInput],
|
||||
clientCert: [Rules.requiredInput],
|
||||
rootCert: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined, operation: string) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
dialogData.value.rowData.from = 'remote';
|
||||
loading.value = true;
|
||||
dialogData.value.rowData.rootCert = dialogData.value.rowData.hasCA ? dialogData.value.rowData.rootCert : '';
|
||||
if (operation === 'check') {
|
||||
await checkDatabase(dialogData.value.rowData)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.data) {
|
||||
isOK.value = true;
|
||||
MsgSuccess(i18n.global.t('terminal.connTestOk'));
|
||||
} else {
|
||||
MsgError(i18n.global.t('terminal.connTestFailed'));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
MsgError(i18n.global.t('terminal.connTestFailed'));
|
||||
});
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
await addDatabase(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
if (operation === 'edit') {
|
||||
await editDatabase(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
281
frontend/src/views/database/postgresql/setting/index.vue
Normal file
281
frontend/src/views/database/postgresql/setting/index.vue
Normal file
@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<LayoutContent>
|
||||
<template #title>
|
||||
<back-button name="PostgreSQL" :header="props.database + ' ' + $t('commons.button.set')">
|
||||
<template #buttons>
|
||||
<el-button type="primary" :plain="activeName !== 'conf'" @click="jumpToConf">
|
||||
{{ $t('database.confChange') }}
|
||||
</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'">
|
||||
{{ $t('commons.table.port') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="postgresqlStatus !== 'Running'"
|
||||
:plain="activeName !== 'log'"
|
||||
@click="activeName = 'log'"
|
||||
>
|
||||
{{ $t('database.log') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</back-button>
|
||||
</template>
|
||||
|
||||
<template #app>
|
||||
<AppStatus :app-key="props.type" :app-name="props.database" v-model:loading="loading" />
|
||||
</template>
|
||||
|
||||
<template #main>
|
||||
<div v-if="activeName === 'conf'">
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
:placeholder="$t('commons.msg.noneData')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="8"
|
||||
style="margin-top: 10px; height: calc(100vh - 375px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="postgresqlConf"
|
||||
/>
|
||||
|
||||
<el-button type="primary" style="margin-top: 10px" @click="onSaveConf">
|
||||
{{ $t('commons.button.save') }}
|
||||
</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>
|
||||
<Status v-show="activeName === 'status'" ref="statusRef" />
|
||||
<div v-show="activeName === 'port'">
|
||||
<el-form :model="baseInfo" ref="panelFormRef" label-position="top">
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('commons.table.port')" prop="port" :rules="Rules.port">
|
||||
<el-input clearable type="number" v-model.number="baseInfo.port" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSavePort(panelFormRef)" icon="Collection">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<ContainerLog v-show="activeName === 'log'" ref="dialogContainerLogRef" />
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<el-dialog
|
||||
v-model="upgradeVisible"
|
||||
:title="$t('app.checkTitle')"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-alert :closable="false" :title="$t('database.confNotFound')" type="info">
|
||||
<el-link icon="Position" @click="goUpgrade()" type="primary">
|
||||
{{ $t('database.goUpgrade') }}
|
||||
</el-link>
|
||||
</el-alert>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="upgradeVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<ConfirmDialog ref="confirmPortRef" @confirm="onSubmitChangePort"></ConfirmDialog>
|
||||
<ConfirmDialog ref="confirmConfRef" @confirm="onSubmitChangeConf"></ConfirmDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance } from 'element-plus';
|
||||
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 { onMounted, reactive, ref } from 'vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { loadDatabaseFile, loadMysqlBaseInfo, updatePostgresqlConfByFile } from '@/api/modules/database';
|
||||
import { ChangePort, CheckAppInstalled } from '@/api/modules/app';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import router from '@/routers';
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
const activeName = ref('conf');
|
||||
|
||||
const baseInfo = reactive({
|
||||
name: '',
|
||||
port: 5432,
|
||||
password: '',
|
||||
remoteConn: false,
|
||||
containerID: '',
|
||||
});
|
||||
const panelFormRef = ref<FormInstance>();
|
||||
const postgresqlConf = ref();
|
||||
const upgradeVisible = ref();
|
||||
|
||||
const useOld = ref(false);
|
||||
|
||||
const statusRef = ref();
|
||||
|
||||
const postgresqlName = ref();
|
||||
const postgresqlStatus = ref();
|
||||
const postgresqlVersion = ref();
|
||||
|
||||
interface DBProps {
|
||||
type: string;
|
||||
database: string;
|
||||
}
|
||||
const props = withDefaults(defineProps<DBProps>(), {
|
||||
type: '',
|
||||
database: '',
|
||||
});
|
||||
|
||||
const dialogContainerLogRef = ref();
|
||||
const jumpToConf = async () => {
|
||||
activeName.value = 'conf';
|
||||
loadPostgresqlConf();
|
||||
};
|
||||
|
||||
const onSubmitChangePort = async () => {
|
||||
let params = {
|
||||
key: props.type,
|
||||
name: props.database,
|
||||
port: baseInfo.port,
|
||||
};
|
||||
loading.value = true;
|
||||
await ChangePort(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const confirmPortRef = ref();
|
||||
const onSavePort = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
const result = await formEl.validateField('port', callback);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
header: i18n.global.t('database.confChange'),
|
||||
operationInfo: i18n.global.t('database.restartNowHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmPortRef.value!.acceptParams(params);
|
||||
return;
|
||||
};
|
||||
function callback(error: any) {
|
||||
if (error) {
|
||||
return error.message;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmitChangeConf = async () => {
|
||||
let param = {
|
||||
type: props.type,
|
||||
database: props.database,
|
||||
file: postgresqlConf.value,
|
||||
};
|
||||
loading.value = true;
|
||||
await updatePostgresqlConfByFile(param)
|
||||
.then(() => {
|
||||
useOld.value = false;
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const confirmConfRef = ref();
|
||||
const onSaveConf = async () => {
|
||||
let params = {
|
||||
header: i18n.global.t('database.confChange'),
|
||||
operationInfo: i18n.global.t('database.restartNowHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmConfRef.value!.acceptParams(params);
|
||||
return;
|
||||
};
|
||||
|
||||
const loadContainerLog = async (containerID: string) => {
|
||||
dialogContainerLogRef.value!.acceptParams({ containerID: containerID, container: containerID });
|
||||
};
|
||||
|
||||
const loadBaseInfo = async () => {
|
||||
const res = await loadMysqlBaseInfo(props.type, props.database);
|
||||
postgresqlName.value = res.data?.name;
|
||||
baseInfo.port = res.data?.port;
|
||||
baseInfo.containerID = res.data?.containerName;
|
||||
loadPostgresqlConf();
|
||||
loadContainerLog(baseInfo.containerID);
|
||||
};
|
||||
|
||||
const loadPostgresqlConf = async () => {
|
||||
useOld.value = false;
|
||||
await loadDatabaseFile(props.type + '-conf', props.database)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
postgresqlConf.value = res.data;
|
||||
})
|
||||
.catch(() => {
|
||||
upgradeVisible.value = true;
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const goUpgrade = () => {
|
||||
router.push({ name: 'AppUpgrade' });
|
||||
};
|
||||
|
||||
const onLoadInfo = async () => {
|
||||
await CheckAppInstalled(props.type, props.database).then((res) => {
|
||||
postgresqlName.value = res.data.name;
|
||||
postgresqlStatus.value = res.data.status;
|
||||
postgresqlVersion.value = res.data.version;
|
||||
loadBaseInfo();
|
||||
if (postgresqlStatus.value === 'Running') {
|
||||
statusRef.value!.acceptParams({ type: props.type, database: props.database });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onLoadInfo();
|
||||
});
|
||||
</script>
|
168
frontend/src/views/database/postgresql/setting/status/index.vue
Normal file
168
frontend/src/views/database/postgresql/setting/status/index.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<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>
|
3
go.mod
3
go.mod
@ -143,6 +143,9 @@ require (
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
|
5
go.sum
5
go.sum
@ -465,10 +465,15 @@ github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki
|
||||
github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
|
||||
github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
|
||||
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
|
||||
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20230719111531-6e53453ccbd3 h1:Iqr8ZWwijosAcawoD9IZ7cwj61WH50Rysm+RQ+ZIB4I=
|
||||
github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20230719111531-6e53453ccbd3/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
|
Loading…
Reference in New Issue
Block a user