diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index 0683a8446..ca02b8a9c 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -21,8 +21,9 @@ var ( imageService = service.NewIImageService() dockerService = service.NewIDockerService() - mysqlService = service.NewIMysqlService() - redisService = service.NewIRedisService() + mysqlService = service.NewIMysqlService() + remoteDBService = service.NewIRemoteDBService() + redisService = service.NewIRedisService() cronjobService = service.NewICronjobService() diff --git a/backend/app/api/v1/remote_db.go b/backend/app/api/v1/remote_db.go new file mode 100644 index 000000000..413a5a8e6 --- /dev/null +++ b/backend/app/api/v1/remote_db.go @@ -0,0 +1,133 @@ +package v1 + +import ( + "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/1Panel-dev/1Panel/backend/global" + "github.com/gin-gonic/gin" +) + +// @Tags Database +// @Summary Create remote database +// @Description 创建远程数据库 +// @Accept json +// @Param request body dto.DatabaseCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /databases/remote [post] +// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建远程数据库 [name][type]","formatEN":"create remote database [name][type]"} +func (b *BaseApi) CreateRemoteDB(c *gin.Context) { + var req dto.RemoteDBCreate + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := remoteDBService.Create(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags Database +// @Summary Page remote databases +// @Description 获取远程数据库列表分页 +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Router /databases/remote/search [post] +func (b *BaseApi) SearchRemoteDB(c *gin.Context) { + var req dto.RemoteDBSearch + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + total, list, err := remoteDBService.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 +// @Summary List remote databases +// @Description 获取快速命令列表 +// @Success 200 {array} dto.RemoteDBOption +// @Security ApiKeyAuth +// @Router /databases/remote/list/:type [get] +func (b *BaseApi) ListRemoteDB(c *gin.Context) { + dbType := c.Query("type") + list, err := remoteDBService.List(dbType) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Database +// @Summary Delete remote database +// @Description 删除远程数据库 +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /databases/remote/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"databases","output_column":"name","output_value":"names"}],"formatZH":"删除远程数据库 [names]","formatEN":"delete remote database [names]"} +func (b *BaseApi) DeleteRemoteDB(c *gin.Context) { + var req dto.OperateByID + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := remoteDBService.Delete(req.ID); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags Database +// @Summary Update remote database +// @Description 更新远程数据库 +// @Accept json +// @Param request body dto.DatabaseUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /databases/remote/update [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新远程数据库 [name]","formatEN":"update remote database [name]"} +func (b *BaseApi) UpdateRemoteDB(c *gin.Context) { + var req dto.RemoteDBUpdate + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := remoteDBService.Update(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/dto/database.go b/backend/app/dto/database.go index 86bdbdfeb..e204b65d9 100644 --- a/backend/app/dto/database.go +++ b/backend/app/dto/database.go @@ -16,6 +16,7 @@ type MysqlDBInfo struct { type MysqlDBCreate struct { Name string `json:"name" validate:"required"` + From string `json:"from" validate:"required"` Format string `json:"format" validate:"required,oneof=utf8mb4 utf8 gbk big5"` Username string `json:"username" validate:"required"` Password string `json:"password" validate:"required"` @@ -100,6 +101,7 @@ type MysqlConfUpdateByFile struct { type ChangeDBInfo struct { ID uint `json:"id"` + From string `json:"from" validate:"required"` Value string `json:"value" validate:"required"` } diff --git a/backend/app/dto/remote_db.go b/backend/app/dto/remote_db.go new file mode 100644 index 000000000..aa859973e --- /dev/null +++ b/backend/app/dto/remote_db.go @@ -0,0 +1,52 @@ +package dto + +import "time" + +type RemoteDBSearch struct { + PageInfo + Info string `json:"info"` + Type string `json:"type"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` +} + +type RemoteDBInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + From string `json:"from"` + Version string `json:"version"` + Address string `json:"address"` + Port uint `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + Description string `json:"description"` +} + +type RemoteDBOption struct { + ID uint `json:"id"` + Name string `json:"name"` + Address string `json:"address"` +} + +type RemoteDBCreate struct { + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required,oneof=mysql"` + From string `json:"from" validate:"required,oneof=local remote"` + Version string `json:"version" validate:"required"` + Address string `json:"address"` + Port uint `json:"port"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + Description string `json:"description"` +} + +type RemoteDBUpdate struct { + ID uint `json:"id"` + Version string `json:"version" validate:"required"` + Address string `json:"address"` + Port uint `json:"port"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + Description string `json:"description"` +} diff --git a/backend/app/model/database_mysql.go b/backend/app/model/database_mysql.go index bc699aac4..9a5137e12 100644 --- a/backend/app/model/database_mysql.go +++ b/backend/app/model/database_mysql.go @@ -3,6 +3,7 @@ package model type DatabaseMysql struct { BaseModel Name string `json:"name" gorm:"type:varchar(256);not null"` + From string `json:"type" gorm:"type:varchar(256);not null"` MysqlName string `json:"mysqlName" 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"` diff --git a/backend/app/model/database.go b/backend/app/model/remote_db.go similarity index 75% rename from backend/app/model/database.go rename to backend/app/model/remote_db.go index 58bd8accb..8e442b37e 100644 --- a/backend/app/model/database.go +++ b/backend/app/model/remote_db.go @@ -1,13 +1,14 @@ package model -type Database struct { +type RemoteDB struct { BaseModel Name string `json:"name" gorm:"type:varchar(64);not null"` Type string `json:"type" gorm:"type:varchar(64);not null"` + Version string `json:"version" gorm:"type:varchar(64);not null"` + From string `json:"from" gorm:"type:varchar(64);not null"` Address string `json:"address" gorm:"type:varchar(64);not null"` Port uint `json:"port" gorm:"type:decimal;not null"` Username string `json:"username" gorm:"type:varchar(64)"` Password string `json:"password" gorm:"type:varchar(64)"` - Format string `json:"format" gorm:"type:varchar(64)"` Description string `json:"description" gorm:"type:varchar(256);"` } diff --git a/backend/app/repo/remote_db.go b/backend/app/repo/remote_db.go new file mode 100644 index 000000000..a49270760 --- /dev/null +++ b/backend/app/repo/remote_db.go @@ -0,0 +1,77 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/global" + "gorm.io/gorm" +) + +type RemoteDBRepo struct{} + +type IRemoteDBRepo interface { + GetList(opts ...DBOption) ([]model.RemoteDB, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.RemoteDB, error) + Create(database *model.RemoteDB) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error + Get(opts ...DBOption) (model.RemoteDB, error) + WithoutByFrom(from string) DBOption +} + +func NewIRemoteDBRepo() IRemoteDBRepo { + return &RemoteDBRepo{} +} + +func (u *RemoteDBRepo) Get(opts ...DBOption) (model.RemoteDB, error) { + var database model.RemoteDB + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&database).Error + return database, err +} + +func (u *RemoteDBRepo) Page(page, size int, opts ...DBOption) (int64, []model.RemoteDB, error) { + var users []model.RemoteDB + db := global.DB.Model(&model.RemoteDB{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *RemoteDBRepo) GetList(opts ...DBOption) ([]model.RemoteDB, error) { + var databases []model.RemoteDB + db := global.DB.Model(&model.RemoteDB{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&databases).Error + return databases, err +} + +func (c *RemoteDBRepo) WithoutByFrom(from string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`from` != ?", from) + } +} + +func (u *RemoteDBRepo) Create(database *model.RemoteDB) error { + return global.DB.Create(database).Error +} + +func (u *RemoteDBRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.RemoteDB{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *RemoteDBRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.RemoteDB{}).Error +} diff --git a/backend/app/service/database_mysql.go b/backend/app/service/database_mysql.go index dde139887..89edd3e88 100644 --- a/backend/app/service/database_mysql.go +++ b/backend/app/service/database_mysql.go @@ -20,6 +20,8 @@ import ( "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/compose" + "github.com/1Panel-dev/1Panel/backend/utils/mysql" + "github.com/1Panel-dev/1Panel/backend/utils/mysql/client" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/copier" "github.com/pkg/errors" @@ -70,50 +72,69 @@ func (u *MysqlService) ListDBName() ([]string, error) { return dbNames, err } -var formatMap = map[string]string{ - "utf8": "utf8_general_ci", - "utf8mb4": "utf8mb4_general_ci", - "gbk": "gbk_chinese_ci", - "big5": "big5_chinese_ci", -} - func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) { if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Permission) { return nil, buserr.New(constant.ErrCmdIllegal) } - if req.Username == "root" { - return nil, errors.New("Cannot set root as user name") - } - app, err := appInstallRepo.LoadBaseInfo("mysql", "") - if err != nil { - return nil, err - } - mysql, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name)) - if mysql.ID != 0 { - return nil, constant.ErrRecordExist - } - if err := copier.Copy(&mysql, &req); err != nil { + var createItem model.DatabaseMysql + if err := copier.Copy(&createItem, &req); err != nil { return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) } - createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", req.Name, req.Format, formatMap[req.Format]) - if err := excSQL(app.ContainerName, app.Password, createSql); err != nil { - if strings.Contains(err.Error(), "ERROR 1007") { - return nil, buserr.New(constant.ErrDatabaseIsExist) + if req.Username == "root" { + return nil, errors.New("Cannot set root as user name") + } + + dbInfo := client.DBInfo{ + From: req.From, + Timeout: 300, + } + version := "" + if req.From == "local" { + app, err := appInstallRepo.LoadBaseInfo("mysql", "") + if err != nil { + return nil, err } + mysqlData, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name)) + if mysqlData.ID != 0 { + return nil, constant.ErrRecordExist + } + dbInfo.Address = app.ContainerName + dbInfo.Username = "root" + dbInfo.Password = app.Password + version = app.Version + createItem.MysqlName = app.Name + } else { + mysqlData, err := remoteDBRepo.Get(commonRepo.WithByName(req.From)) + if err != nil { + return nil, err + } + dbInfo.Address = mysqlData.Address + dbInfo.Port = mysqlData.Port + dbInfo.Username = mysqlData.Username + dbInfo.Password = mysqlData.Password + version = mysqlData.Version + createItem.MysqlName = mysqlData.Name + } + + cli, err := mysql.NewMysqlClient(dbInfo) + if err != nil { return nil, err } - if err := u.createUser(app.ContainerName, app.Password, app.Version, req); err != nil { + if err := cli.Create(client.CreateInfo{ + Name: req.Name, + Format: req.Format, + Version: version, + }); err != nil { return nil, err } global.LOG.Infof("create database %s successful!", req.Name) - mysql.MysqlName = app.Name - if err := mysqlRepo.Create(ctx, &mysql); err != nil { + if err := mysqlRepo.Create(ctx, &createItem); err != nil { return nil, err } - return &mysql, nil + return &createItem, nil } func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error { @@ -143,29 +164,28 @@ func (u *MysqlService) DeleteCheck(id uint) ([]string, error) { } func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error { - app, err := appInstallRepo.LoadBaseInfo("mysql", "") - if err != nil && !req.ForceDelete { + cli, version, err := loadClientByID(req.ID) + if err != nil { return err } - db, err := mysqlRepo.Get(commonRepo.WithByID(req.ID)) if err != nil && !req.ForceDelete { return err } - - if strings.HasPrefix(app.Version, "5.6") { - if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete { - return err - } - } else { - if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete { - return err - } - } - if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete { + if err := cli.Delete(client.DeleteInfo{ + Name: db.Name, + Version: version, + Username: db.Username, + Permission: db.Permission, + Timeout: 300, + }); err != nil { + return err + } + + app, err := appInstallRepo.LoadBaseInfo("mysql", "") + if err != nil && !req.ForceDelete { return err } - global.LOG.Info("execute delete database sql successful, now start to drop uploads and records") uploadDir := fmt.Sprintf("%s/1panel/uploads/database/mysql/%s/%s", global.CONF.System.BaseDir, app.Name, db.Name) if _, err := os.Stat(uploadDir); err == nil { @@ -192,66 +212,55 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error { if cmd.CheckIllegal(info.Value) { return buserr.New(constant.ErrCmdIllegal) } - + cli, version, err := loadClientByID(info.ID) + if err != nil { + return err + } var ( - mysql model.DatabaseMysql - err error + mysqlData model.DatabaseMysql + passwordInfo client.PasswordChangeInfo ) + passwordInfo.Password = info.Value + passwordInfo.Timeout = 300 + passwordInfo.Version = version + if info.ID != 0 { - mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID)) + mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(info.ID)) if err != nil { return err } + passwordInfo.Name = mysqlData.Name + passwordInfo.Username = mysqlData.Username + passwordInfo.Permission = mysqlData.Permission + } else { + passwordInfo.Username = "root" } - app, err := appInstallRepo.LoadBaseInfo("mysql", "") - if err != nil { + if err := cli.ChangePassword(passwordInfo); err != nil { return err } - passwordChangeCMD := fmt.Sprintf("set password for '%s'@'%s' = password('%s')", mysql.Username, mysql.Permission, info.Value) - if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") { - passwordChangeCMD = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED WITH mysql_native_password BY '%s';", mysql.Username, mysql.Permission, info.Value) - } if info.ID != 0 { - appRess, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysql.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 - } + // appRess, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysqlData.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 mysql password used by app %s-%s", appModel.Key, appInstall.Name) - if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, info.Value); err != nil { - return err - } - } - if err := excuteSql(app.ContainerName, app.Password, passwordChangeCMD); err != nil { - return err - } + // global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name) + // if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, info.Value); err != nil { + // return err + // } + // } global.LOG.Info("excute password change sql successful") - _ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"password": info.Value}) + _ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"password": info.Value}) return nil } - hosts, err := excuteSqlForRows(app.ContainerName, app.Password, "select host from mysql.user where user='root';") - if err != nil { - return err - } - for _, host := range hosts { - if host == "%" || host == "localhost" { - passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Value) - if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") { - passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Value) - } - if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil { - return err - } - } - } if err := updateInstallInfoInDB("mysql", "", "password", false, info.Value); err != nil { return err } @@ -265,71 +274,36 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error { if cmd.CheckIllegal(info.Value) { return buserr.New(constant.ErrCmdIllegal) } - var ( - mysql model.DatabaseMysql - err error - ) - if info.ID != 0 { - mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID)) - if err != nil { - return err - } - if info.Value == mysql.Permission { - return nil - } - } - app, err := appInstallRepo.LoadBaseInfo("mysql", "") + cli, version, err := loadClientByID(info.ID) if err != nil { return err } - if info.ID == 0 { - mysql.Name = "*" - mysql.Username = "root" - mysql.Permission = "%" - mysql.Password = app.Password - } + var ( + mysqlData model.DatabaseMysql + accessInfo client.AccessChangeInfo + ) + accessInfo.Permission = info.Value + accessInfo.Timeout = 300 + accessInfo.Version = version - if info.Value != mysql.Permission { - var userlist []string - if strings.Contains(mysql.Permission, ",") { - userlist = strings.Split(mysql.Permission, ",") - } else { - userlist = append(userlist, mysql.Permission) - } - for _, user := range userlist { - if len(user) != 0 { - if strings.HasPrefix(app.Version, "5.6") { - if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", mysql.Username, user)); err != nil { - return err - } - } else { - if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil { - return err - } - } - } - } - if info.ID == 0 { - return nil + if info.ID != 0 { + mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(info.ID)) + if err != nil { + return err } + accessInfo.Name = mysqlData.Name + accessInfo.Username = mysqlData.Username + accessInfo.OldPermission = mysqlData.Permission + } else { + accessInfo.Username = "root" } - - if err := u.createUser(app.ContainerName, app.Password, app.Version, dto.MysqlDBCreate{ - Username: mysql.Username, - Name: mysql.Name, - Permission: info.Value, - Password: mysql.Password, - }); err != nil { + if err := cli.ChangeAccess(accessInfo); err != nil { return err } - if err := excuteSql(app.ContainerName, app.Password, "flush privileges"); err != nil { - return err - } - if info.ID == 0 { - return nil - } - _ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"permission": info.Value}) + if mysqlData.ID != 0 { + _ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"permission": info.Value}) + } return nil } @@ -493,53 +467,6 @@ func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) { return &info, nil } -func (u *MysqlService) createUser(container, password, version string, req dto.MysqlDBCreate) error { - var userlist []string - if strings.Contains(req.Permission, ",") { - ips := strings.Split(req.Permission, ",") - for _, ip := range ips { - if len(ip) != 0 { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", req.Username, ip)) - } - } - } else { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", req.Username, req.Permission)) - } - - for _, user := range userlist { - if err := excSQL(container, password, fmt.Sprintf("create user %s identified by '%s';", user, req.Password)); err != nil { - if strings.Contains(err.Error(), "ERROR 1396") { - handleCreateError(container, password, req.Name, userlist, false) - return buserr.New(constant.ErrUserIsExist) - } - handleCreateError(container, password, req.Name, userlist, true) - return err - } - grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", req.Name, user) - if req.Name == "*" { - grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user) - } - if strings.HasPrefix(version, "5.7") || strings.HasPrefix(version, "5.6") { - grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password) - } - if err := excSQL(container, password, grantStr); err != nil { - handleCreateError(container, password, req.Name, userlist, true) - return err - } - } - return nil -} -func handleCreateError(contaienr, password, dbName string, userlist []string, dropUser bool) { - _ = excSQL(contaienr, password, fmt.Sprintf("drop database `%s`", dbName)) - if dropUser { - for _, user := range userlist { - if err := excSQL(contaienr, password, fmt.Sprintf("drop user if exists %s", user)); err != nil { - global.LOG.Errorf("drop user failed, err: %v", err) - } - } - } -} - func excuteSqlForMaps(containerName, password, command string) (map[string]string, error) { cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command) stdout, err := cmd.CombinedOutput() @@ -569,31 +496,6 @@ func excuteSqlForRows(containerName, password, command string) ([]string, error) return strings.Split(stdStr, "\n"), nil } -func excuteSql(containerName, password, command string) error { - cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command) - stdout, err := cmd.CombinedOutput() - stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") - if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") { - return errors.New(stdStr) - } - return nil -} - -func excSQL(containerName, password, command string) error { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - cmd := exec.CommandContext(ctx, "docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command) - stdout, err := cmd.CombinedOutput() - if ctx.Err() == context.DeadlineExceeded { - return buserr.New(constant.ErrExecTimeOut) - } - stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") - if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") { - return errors.New(stdStr) - } - return nil -} - func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string { isOn := false hasGroup := false @@ -634,3 +536,46 @@ func updateMyCnf(oldFiles []string, group string, param string, value interface{ } return newFiles } + +func loadClientByID(id uint) (mysql.MysqlClient, string, error) { + var ( + mysqlData model.DatabaseMysql + dbInfo client.DBInfo + version string + err error + ) + if id != 0 { + mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(id)) + if err != nil { + return nil, "", err + } + } + + if mysqlData.From != "local" { + databaseItem, err := remoteDBRepo.Get(commonRepo.WithByName(mysqlData.From)) + if err != nil { + return nil, "", err + } + dbInfo.Address = databaseItem.Address + dbInfo.Port = databaseItem.Port + dbInfo.Username = databaseItem.Username + dbInfo.Password = databaseItem.Password + version = databaseItem.Version + + } else { + app, err := appInstallRepo.LoadBaseInfo("mysql", "") + if err != nil { + return nil, "", err + } + dbInfo.Address = app.ContainerName + dbInfo.Username = "root" + dbInfo.Password = app.Password + version = app.Version + } + + cli, err := mysql.NewMysqlClient(dbInfo) + if err != nil { + return nil, "", err + } + return cli, version, nil +} diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index 64f3bf25e..fa8f9f238 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -12,7 +12,8 @@ var ( appInstallRepo = repo.NewIAppInstallRepo() appInstallResourceRepo = repo.NewIAppInstallResourceRpo() - mysqlRepo = repo.NewIMysqlRepo() + mysqlRepo = repo.NewIMysqlRepo() + remoteDBRepo = repo.NewIRemoteDBRepo() imageRepoRepo = repo.NewIImageRepoRepo() composeRepo = repo.NewIComposeTemplateRepo() diff --git a/backend/app/service/remote_db.go b/backend/app/service/remote_db.go new file mode 100644 index 000000000..b0e2962c9 --- /dev/null +++ b/backend/app/service/remote_db.go @@ -0,0 +1,108 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/utils/mysql" + "github.com/1Panel-dev/1Panel/backend/utils/mysql/client" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type RemoteDBService struct{} + +type IRemoteDBService interface { + SearchWithPage(search dto.RemoteDBSearch) (int64, interface{}, error) + Create(req dto.RemoteDBCreate) error + Update(req dto.RemoteDBUpdate) error + Delete(id uint) error + List(dbType string) ([]dto.RemoteDBOption, error) +} + +func NewIRemoteDBService() IRemoteDBService { + return &RemoteDBService{} +} + +func (u *RemoteDBService) SearchWithPage(search dto.RemoteDBSearch) (int64, interface{}, error) { + total, dbs, err := remoteDBRepo.Page(search.Page, search.PageSize, + commonRepo.WithByType(search.Type), + commonRepo.WithLikeName(search.Info), + remoteDBRepo.WithoutByFrom("local"), + ) + var datas []dto.RemoteDBInfo + for _, db := range dbs { + var item dto.RemoteDBInfo + if err := copier.Copy(&item, &db); err != nil { + return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + datas = append(datas, item) + } + return total, datas, err +} + +func (u *RemoteDBService) List(dbType string) ([]dto.RemoteDBOption, error) { + dbs, err := remoteDBRepo.GetList(commonRepo.WithByType(dbType), remoteDBRepo.WithoutByFrom("local")) + var datas []dto.RemoteDBOption + for _, db := range dbs { + var item dto.RemoteDBOption + if err := copier.Copy(&item, &db); err != nil { + return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + datas = append(datas, item) + } + return datas, err +} + +func (u *RemoteDBService) Create(req dto.RemoteDBCreate) error { + db, _ := remoteDBRepo.Get(commonRepo.WithByName(req.Name)) + if db.ID != 0 { + return constant.ErrRecordExist + } + 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 + } + if err := copier.Copy(&db, &req); err != nil { + return errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + if err := remoteDBRepo.Create(&db); err != nil { + return err + } + return nil +} + +func (u *RemoteDBService) Delete(id uint) error { + db, _ := remoteDBRepo.Get(commonRepo.WithByID(id)) + if db.ID == 0 { + return constant.ErrRecordNotFound + } + return remoteDBRepo.Delete(commonRepo.WithByID(id)) +} + +func (u *RemoteDBService) Update(req dto.RemoteDBUpdate) error { + 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 + } + + upMap := make(map[string]interface{}) + upMap["version"] = req.Version + upMap["address"] = req.Address + upMap["port"] = req.Port + upMap["username"] = req.Username + upMap["password"] = req.Password + upMap["description"] = req.Description + return remoteDBRepo.Update(req.ID, upMap) +} diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index 9bd5c3ec1..f7d65bcd2 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -481,9 +481,9 @@ var EncryptHostPassword = &gormigrate.Migration{ } var AddRemoteDB = &gormigrate.Migration{ - ID: "20230718-add-remote-db", + ID: "20230720-add-remote-db", Migrate: func(tx *gorm.DB) error { - if err := tx.AutoMigrate(&model.Database{}); err != nil { + if err := tx.AutoMigrate(&model.RemoteDB{}); err != nil { return err } return nil diff --git a/backend/router/ro_database.go b/backend/router/ro_database.go index 8e2b9b7e6..1d32e48d7 100644 --- a/backend/router/ro_database.go +++ b/backend/router/ro_database.go @@ -40,5 +40,11 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) { cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf) cmdRouter.POST("/redis/conffile/update", baseApi.UpdateRedisConfByFile) cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf) + + cmdRouter.POST("/remote", baseApi.CreateRemoteDB) + cmdRouter.POST("/remote/list/:type", baseApi.ListRemoteDB) + cmdRouter.POST("/remote/update", baseApi.UpdateRemoteDB) + cmdRouter.POST("/remote/search", baseApi.SearchRemoteDB) + cmdRouter.POST("/remote/del", baseApi.DeleteRemoteDB) } } diff --git a/backend/utils/mysql/client.go b/backend/utils/mysql/client.go index 9d064fb95..82e204c86 100644 --- a/backend/utils/mysql/client.go +++ b/backend/utils/mysql/client.go @@ -22,19 +22,22 @@ type MysqlClient interface { } func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) { - if conn.Type == "remote" { - connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", conn.UserName, conn.Password, conn.Address, conn.Port) + if conn.From == "remote" { + connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", conn.Username, conn.Password, conn.Address, conn.Port) db, err := sql.Open("mysql", connArgs) if err != nil { return nil, err } + if err := db.Ping(); err != nil { + return nil, err + } return client.NewRemote(db), nil } - if conn.Type == "local" { - if cmd.CheckIllegal(conn.Address, conn.UserName, conn.Password) { + if conn.From == "local" { + if cmd.CheckIllegal(conn.Address, conn.Username, conn.Password) { return nil, buserr.New(constant.ErrCmdIllegal) } - connArgs := []string{"exec", conn.Address, "mysql", "-u" + conn.UserName, "-p" + conn.Password + "-e"} + connArgs := []string{"exec", conn.Address, "mysql", "-u" + conn.Username, "-p" + conn.Password + "-e"} return client.NewLocal(connArgs, conn.Address), nil } return nil, errors.New("no such type") diff --git a/backend/utils/mysql/client/info.go b/backend/utils/mysql/client/info.go index 8d478d32c..85e2c8195 100644 --- a/backend/utils/mysql/client/info.go +++ b/backend/utils/mysql/client/info.go @@ -1,12 +1,11 @@ package client type DBInfo struct { - Type string `json:"type"` // local remote + From string `json:"from"` // local remote Address string `json:"address"` Port uint `json:"port"` - UserName string `json:"userName"` + Username string `json:"userName"` Password string `json:"password"` - Format string `json:"format"` Timeout uint `json:"timeout"` // second } @@ -15,7 +14,7 @@ type CreateInfo struct { Name string `json:"name"` Format string `json:"format"` Version string `json:"version"` - UserName string `json:"userName"` + Username string `json:"userName"` Password string `json:"password"` Permission string `json:"permission"` @@ -25,7 +24,7 @@ type CreateInfo struct { type DeleteInfo struct { Name string `json:"name"` Version string `json:"version"` - UserName string `json:"userName"` + Username string `json:"userName"` Permission string `json:"permission"` ForceDelete bool `json:"forceDelete"` @@ -35,7 +34,7 @@ type DeleteInfo struct { type PasswordChangeInfo struct { Name string `json:"name"` Version string `json:"version"` - UserName string `json:"userName"` + Username string `json:"userName"` Password string `json:"password"` Permission string `json:"permission"` @@ -45,7 +44,7 @@ type PasswordChangeInfo struct { type AccessChangeInfo struct { Name string `json:"name"` Version string `json:"version"` - UserName string `json:"userName"` + Username string `json:"userName"` OldPermission string `json:"oldPermission"` Permission string `json:"permission"` diff --git a/backend/utils/mysql/client/local.go b/backend/utils/mysql/client/local.go index 0c61e0a23..4508a6d56 100644 --- a/backend/utils/mysql/client/local.go +++ b/backend/utils/mysql/client/local.go @@ -31,7 +31,7 @@ func (r *Local) Create(info CreateInfo) error { return err } - if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, UserName: info.UserName, Permission: info.Permission, Timeout: info.Timeout}); err != nil { + if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, Username: info.Username, Permission: info.Permission, Timeout: info.Timeout}); err != nil { return err } @@ -44,11 +44,11 @@ func (r *Local) CreateUser(info CreateInfo) error { ips := strings.Split(info.Permission, ",") for _, ip := range ips { if len(ip) != 0 { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, ip)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) } } } else { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, info.Permission)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) } for _, user := range userlist { @@ -56,7 +56,7 @@ func (r *Local) CreateUser(info CreateInfo) error { _ = r.Delete(DeleteInfo{ Name: info.Name, Version: info.Version, - UserName: info.UserName, + Username: info.Username, Permission: info.Permission, ForceDelete: true, Timeout: 300}) @@ -76,7 +76,7 @@ func (r *Local) CreateUser(info CreateInfo) error { _ = r.Delete(DeleteInfo{ Name: info.Name, Version: info.Version, - UserName: info.UserName, + Username: info.Username, Permission: info.Permission, ForceDelete: true, Timeout: 300}) @@ -92,11 +92,11 @@ func (r *Local) Delete(info DeleteInfo) error { ips := strings.Split(info.Permission, ",") for _, ip := range ips { if len(ip) != 0 { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, ip)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) } } } else { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, info.Permission)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) } for _, user := range userlist { @@ -123,17 +123,17 @@ func (r *Local) Delete(info DeleteInfo) error { } func (r *Local) ChangePassword(info PasswordChangeInfo) error { - if info.UserName != "root" { + if info.Username != "root" { var userlist []string if strings.Contains(info.Permission, ",") { ips := strings.Split(info.Permission, ",") for _, ip := range ips { if len(ip) != 0 { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, ip)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) } } } else { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, info.Permission)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) } for _, user := range userlist { @@ -168,24 +168,27 @@ func (r *Local) ChangePassword(info PasswordChangeInfo) error { } func (r *Local) ChangeAccess(info AccessChangeInfo) error { - if info.UserName == "root" { + if info.Username == "root" { info.OldPermission = "%" info.Name = "*" } if info.Permission != info.OldPermission { if err := r.Delete(DeleteInfo{ Version: info.Version, - UserName: info.UserName, + Username: info.Username, Permission: info.OldPermission, ForceDelete: true, Timeout: 300}); err != nil { return err } - if info.UserName == "root" { + if info.Username == "root" { return nil } } - if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, UserName: info.UserName, Permission: info.Permission, Timeout: info.Timeout}); err != nil { + if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, Username: info.Username, Permission: info.Permission, Timeout: info.Timeout}); err != nil { + return err + } + if err := r.ExecSQL("flush privileges", 300); err != nil { return err } return nil diff --git a/backend/utils/mysql/client/remote.go b/backend/utils/mysql/client/remote.go index 80a2158fe..083242cfd 100644 --- a/backend/utils/mysql/client/remote.go +++ b/backend/utils/mysql/client/remote.go @@ -29,7 +29,7 @@ func (r *Remote) Create(info CreateInfo) error { return err } - if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, UserName: info.UserName, Permission: info.Permission, Timeout: info.Timeout}); err != nil { + if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, Username: info.Username, Permission: info.Permission, Timeout: info.Timeout}); err != nil { return err } @@ -42,11 +42,11 @@ func (r *Remote) CreateUser(info CreateInfo) error { ips := strings.Split(info.Permission, ",") for _, ip := range ips { if len(ip) != 0 { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, ip)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) } } } else { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, info.Permission)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) } for _, user := range userlist { @@ -54,7 +54,7 @@ func (r *Remote) CreateUser(info CreateInfo) error { _ = r.Delete(DeleteInfo{ Name: info.Name, Version: info.Version, - UserName: info.UserName, + Username: info.Username, Permission: info.Permission, ForceDelete: true, Timeout: 300}) @@ -74,7 +74,7 @@ func (r *Remote) CreateUser(info CreateInfo) error { _ = r.Delete(DeleteInfo{ Name: info.Name, Version: info.Version, - UserName: info.UserName, + Username: info.Username, Permission: info.Permission, ForceDelete: true, Timeout: 300}) @@ -90,11 +90,11 @@ func (r *Remote) Delete(info DeleteInfo) error { ips := strings.Split(info.Permission, ",") for _, ip := range ips { if len(ip) != 0 { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, ip)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) } } } else { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, info.Permission)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) } for _, user := range userlist { @@ -121,17 +121,17 @@ func (r *Remote) Delete(info DeleteInfo) error { } func (r *Remote) ChangePassword(info PasswordChangeInfo) error { - if info.UserName != "root" { + if info.Username != "root" { var userlist []string if strings.Contains(info.Permission, ",") { ips := strings.Split(info.Permission, ",") for _, ip := range ips { if len(ip) != 0 { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, ip)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) } } } else { - userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.UserName, info.Permission)) + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) } for _, user := range userlist { @@ -166,24 +166,27 @@ func (r *Remote) ChangePassword(info PasswordChangeInfo) error { } func (r *Remote) ChangeAccess(info AccessChangeInfo) error { - if info.UserName == "root" { + if info.Username == "root" { info.OldPermission = "%" info.Name = "*" } if info.Permission != info.OldPermission { if err := r.Delete(DeleteInfo{ Version: info.Version, - UserName: info.UserName, + Username: info.Username, Permission: info.OldPermission, ForceDelete: true, Timeout: 300}); err != nil { return err } - if info.UserName == "root" { + if info.Username == "root" { return nil } } - if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, UserName: info.UserName, Permission: info.Permission, Timeout: info.Timeout}); err != nil { + if err := r.CreateUser(CreateInfo{Name: info.Name, Version: info.Version, Username: info.Username, Permission: info.Permission, Timeout: info.Timeout}); err != nil { + return err + } + if err := r.ExecSQL("flush privileges", 300); err != nil { return err } return nil diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index ed05a71d3..61e456e26 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -131,6 +131,7 @@ export namespace App { export interface DatabaseConnInfo { password: string; + privilege: boolean; serviceName: string; port: number; } diff --git a/frontend/src/api/interface/database.ts b/frontend/src/api/interface/database.ts index 757f130a4..22fe0520d 100644 --- a/frontend/src/api/interface/database.ts +++ b/frontend/src/api/interface/database.ts @@ -167,6 +167,40 @@ export namespace Database { createdAt: Date; name: string; type: string; + version: string; + from: string; + address: string; + port: number; + username: string; + password: string; + description: string; + } + export interface SearchRemoteDBPage { + info: string; + type: string; + page: number; + pageSize: number; + orderBy?: string; + order?: string; + } + export interface RemoteDBOption { + id: number; + name: string; + address: string; + } + export interface RemoteDBCreate { + name: string; + version: string; + from: string; + address: string; + port: number; + username: string; + password: string; + description: string; + } + export interface RemoteDBUpdate { + id: number; + version: string; address: string; port: number; username: string; diff --git a/frontend/src/api/modules/database.ts b/frontend/src/api/modules/database.ts index 6039668fb..609884c6a 100644 --- a/frontend/src/api/modules/database.ts +++ b/frontend/src/api/modules/database.ts @@ -85,13 +85,16 @@ export const updateRedisConfByFile = (params: Database.RedisConfUpdateByFile) => }; // remote -export const searchRemoteDBs = (params: SearchWithPage) => { +export const searchRemoteDBs = (params: Database.SearchRemoteDBPage) => { return http.post<ResPage<Database.RemoteDBInfo>>(`/databases/remote/search`, params); }; -export const addRemoteDB = (params: Database.RemoteDBInfo) => { +export const listRemoteDBs = (type: string) => { + return http.get<Array<Database.RemoteDBOption>>(`/databases/remote/list/${type}`); +}; +export const addRemoteDB = (params: Database.RemoteDBCreate) => { return http.post(`/databases/remote`, params); }; -export const editRemoteDB = (params: Database.RemoteDBInfo) => { +export const editRemoteDB = (params: Database.RemoteDBUpdate) => { return http.post(`/databases/remote/update`, params); }; export const deleteRemoteDB = (id: number) => { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index aecaaa148..9d849ee2d 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -330,6 +330,13 @@ const message = { confChange: '配置修改', + remoteDB: '远程数据库', + createRemoteDB: '创建远程数据库', + editRemoteDB: '编辑远程数据库', + localDB: '本地数据库', + address: '数据库地址', + version: '数据库版本', + selectFile: '选择文件', dropHelper: '将上传文件拖拽到此处,或者', clickHelper: '点击上传', diff --git a/frontend/src/routers/modules/database.ts b/frontend/src/routers/modules/database.ts index d46f46c24..efdb36754 100644 --- a/frontend/src/routers/modules/database.ts +++ b/frontend/src/routers/modules/database.ts @@ -27,6 +27,16 @@ const databaseRouter = { requiresAuth: false, }, }, + { + path: 'mysql/remote', + name: 'MySQL-Remote', + component: () => import('@/views/database/mysql/remote/index.vue'), + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + }, + }, { path: 'redis', name: 'Redis', diff --git a/frontend/src/views/database/mysql/index.vue b/frontend/src/views/database/mysql/index.vue index a23e7a6fd..2d31106f5 100644 --- a/frontend/src/views/database/mysql/index.vue +++ b/frontend/src/views/database/mysql/index.vue @@ -20,8 +20,8 @@ <el-button @click="onChangeRootPassword" type="primary" plain> {{ $t('database.databaseConnInfo') }} </el-button> - <el-button @click="onChangeAccess" type="primary" plain> - {{ $t('database.remoteAccess') }} + <el-button @click="goRemoteDB" type="primary" plain> + {{ $t('database.remoteDB') }} </el-button> <el-button @click="goDashboard" icon="Position" type="primary" plain>phpMyAdmin</el-button> </el-col> @@ -134,7 +134,6 @@ <PasswordDialog ref="passwordRef" @search="search" /> <RootPasswordDialog ref="rootPasswordRef" /> - <RemoteAccessDialog ref="remoteAccessRef" /> <UploadDialog ref="uploadRef" /> <OperateDialog @search="search" ref="dialogRef" /> <Backups ref="dialogBackupRef" /> @@ -151,7 +150,6 @@ import OperateDialog from '@/views/database/mysql/create/index.vue'; import DeleteDialog from '@/views/database/mysql/delete/index.vue'; import PasswordDialog from '@/views/database/mysql/password/index.vue'; import RootPasswordDialog from '@/views/database/mysql/root-password/index.vue'; -import RemoteAccessDialog from '@/views/database/mysql/remote/index.vue'; import AppResources from '@/views/database/mysql/check/index.vue'; import Setting from '@/views/database/mysql/setting/index.vue'; import AppStatus from '@/components/app-status/index.vue'; @@ -160,7 +158,7 @@ import UploadDialog from '@/components/upload/index.vue'; import PortJumpDialog from '@/components/port-jump/index.vue'; import { dateFormat } from '@/utils/util'; import { reactive, ref } from 'vue'; -import { deleteCheckMysqlDB, loadRemoteAccess, searchMysqlDBs, updateMysqlDescription } from '@/api/modules/database'; +import { deleteCheckMysqlDB, searchMysqlDBs, updateMysqlDescription } from '@/api/modules/database'; import i18n from '@/lang'; import { Database } from '@/api/interface/database'; import { App } from '@/api/interface/app'; @@ -222,13 +220,8 @@ const onChangeRootPassword = async () => { rootPasswordRef.value!.acceptParams(); }; -const remoteAccessRef = ref(); -const onChangeAccess = async () => { - const res = await loadRemoteAccess(); - let param = { - privilege: res.data, - }; - remoteAccessRef.value!.acceptParams(param); +const goRemoteDB = async () => { + router.push({ name: 'MySQL-Remote' }); }; const passwordRef = ref(); diff --git a/frontend/src/views/database/mysql/remote/index.vue b/frontend/src/views/database/mysql/remote/index.vue index 3de162327..237237ea8 100644 --- a/frontend/src/views/database/mysql/remote/index.vue +++ b/frontend/src/views/database/mysql/remote/index.vue @@ -1,98 +1,181 @@ <template> - <el-drawer v-model="dialogVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%"> - <template #header> - <DrawerHeader :header="$t('database.remoteAccess')" :back="handleClose" /> - </template> - <el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top"> - <el-row type="flex" justify="center"> - <el-col :span="22"> - <el-form-item :label="$t('database.remoteAccess')" :rules="Rules.requiredInput" prop="privilege"> - <el-switch v-model="form.privilege" /> - <span class="input-help">{{ $t('database.remoteConnHelper') }}</span> - </el-form-item> - </el-col> - </el-row> - </el-form> + <div v-loading="loading"> + <LayoutContent> + <template #title> + <back-button name="MySQL" :header="'MySQL ' + $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 :label="$t('commons.table.name')" prop="name" sortable /> + <el-table-column :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> + <span style="float: left; line-height: 25px" v-if="!row.showPassword">***********</span> + <div style="cursor: pointer; float: left" v-if="!row.showPassword"> + <el-icon + style="margin-left: 5px; margin-top: 3px" + @click="row.showPassword = true" + :size="16" + > + <View /> + </el-icon> + </div> + <span style="float: left" v-if="row.showPassword">{{ row.password }}</span> + <div style="cursor: pointer; float: left" v-if="row.showPassword"> + <el-icon + style="margin-left: 5px; margin-top: 3px" + @click="row.showPassword = false" + :size="16" + > + <Hide /> + </el-icon> + </div> + <div style="cursor: pointer; float: left"> + <el-icon style="margin-left: 5px; margin-top: 3px" :size="16" @click="onCopy(row)"> + <DocumentCopy /> + </el-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="370px" + :buttons="buttons" + :ellipsis="10" + :label="$t('commons.table.operate')" + fix + /> + </ComplexTable> + </template> + </LayoutContent> - <ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit"></ConfirmDialog> - - <template #footer> - <span class="dialog-footer"> - <el-button @click="dialogVisiable = false">{{ $t('commons.button.cancel') }}</el-button> - <el-button type="primary" @click="onSave(formRef)"> - {{ $t('commons.button.confirm') }} - </el-button> - </span> - </template> - </el-drawer> + <OperateDialog ref="dialogRef" @search="search" /> + </div> </template> <script lang="ts" setup> -import { reactive, ref } from 'vue'; -import { Rules } from '@/global/form-rules'; +import { dateFormat } from '@/utils/util'; +import { onMounted, reactive, ref } from 'vue'; +import { deleteRemoteDB, searchRemoteDBs } from '@/api/modules/database'; +import OperateDialog from '@/views/database/mysql/remote/operate/index.vue'; import i18n from '@/lang'; -import { ElForm } from 'element-plus'; -import { updateMysqlAccess } from '@/api/modules/database'; -import ConfirmDialog from '@/components/confirm-dialog/index.vue'; -import DrawerHeader from '@/components/drawer-header/index.vue'; -import { MsgSuccess } from '@/utils/message'; +import { MsgError, MsgSuccess } from '@/utils/message'; +import useClipboard from 'vue-clipboard3'; +import { Database } from '@/api/interface/database'; +import { useDeleteData } from '@/hooks/use-delete-data'; +const { toClipboard } = useClipboard(); const loading = ref(false); -const dialogVisiable = ref(false); -const form = reactive({ - privilege: false, +const dialogRef = ref(); + +const data = ref(); +const paginationConfig = reactive({ + currentPage: 1, + pageSize: 10, + total: 0, }); +const searchName = ref(); -const confirmDialogRef = ref(); - -type FormInstance = InstanceType<typeof ElForm>; -const formRef = ref<FormInstance>(); - -interface DialogProps { - privilege: boolean; -} - -const acceptParams = (prop: DialogProps): void => { - form.privilege = prop.privilege; - dialogVisiable.value = true; -}; - -const handleClose = () => { - dialogVisiable.value = false; -}; - -const onSubmit = async () => { - let param = { - id: 0, - value: form.privilege ? '%' : 'localhost', +const search = async (column?: any) => { + let params = { + page: paginationConfig.currentPage, + pageSize: paginationConfig.pageSize, + info: searchName.value, + type: 'mysql', + orderBy: column?.order ? column.prop : 'created_at', + order: column?.order ? column.order : 'null', }; - loading.value = true; - await updateMysqlAccess(param) - .then(() => { - loading.value = false; - MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - dialogVisiable.value = false; - }) - .catch(() => { - loading.value = false; - }); + const res = await searchRemoteDBs(params); + data.value = res.data.items || []; + paginationConfig.total = res.data.total; }; -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); - }); +const onOpenDialog = async ( + title: string, + rowData: Partial<Database.RemoteDBInfo> = { + name: '', + type: 'mysql', + version: '5.6.x', + address: '', + port: 3306, + username: '', + password: '', + description: '', + }, +) => { + let params = { + title, + rowData: { ...rowData }, + }; + dialogRef.value!.acceptParams(params); }; -defineExpose({ - acceptParams, +const onCopy = async (row: any) => { + try { + await toClipboard(row.password); + MsgSuccess(i18n.global.t('commons.msg.copySuccess')); + } catch (e) { + MsgError(i18n.global.t('commons.msg.copyfailed')); + } +}; + +const onDelete = async (row: Database.RemoteDBInfo) => { + await useDeleteData(deleteRemoteDB, row.id, 'commons.msg.delete'); + search(); +}; + +const buttons = [ + { + label: i18n.global.t('commons.button.edit'), + click: (row: Database.RemoteDBInfo) => { + onOpenDialog('edit', row); + }, + }, + { + label: i18n.global.t('commons.button.delete'), + click: (row: Database.RemoteDBInfo) => { + onDelete(row); + }, + }, +]; + +onMounted(() => { + search(); }); </script> diff --git a/frontend/src/views/database/remote-db/operate/index.vue b/frontend/src/views/database/mysql/remote/operate/index.vue similarity index 50% rename from frontend/src/views/database/remote-db/operate/index.vue rename to frontend/src/views/database/mysql/remote/operate/index.vue index b5b8db0a2..5e28e272e 100644 --- a/frontend/src/views/database/remote-db/operate/index.vue +++ b/frontend/src/views/database/mysql/remote/operate/index.vue @@ -6,33 +6,33 @@ <el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules"> <el-row type="flex" justify="center"> <el-col :span="22"> - <el-form-item :label="$t('cronjob.taskName')" prop="name"> - <el-input - :disabled="dialogData.title === 'edit'" - clearable - v-model.trim="dialogData.rowData!.name" - /> + <el-form-item :label="$t('commons.table.name')" prop="name"> + <el-input clearable v-model.trim="dialogData.rowData!.name" /> </el-form-item> - <el-form-item :label="$t('cronjob.taskType')" prop="type"> - <el-select v-model="dialogData.rowData!.type"> - <el-option value="Mysql" label="Mysql" /> - <el-option value="Redis" label="Redis" /> + <el-form-item :label="$t('database.version')" prop="version"> + <el-select v-model="dialogData.rowData!.version"> + <el-option value="5.6.x" label="5.6.x" /> + <el-option value="5.7.x" label="5.7.x" /> + <el-option value="8.0.x" label="8.0.x" /> </el-select> </el-form-item> - - <el-form-item :label="$t('cronjob.taskName')" prop="address"> + <el-form-item :label="$t('database.address')" prop="address"> <el-input clearable v-model.trim="dialogData.rowData!.address" /> </el-form-item> - <el-form-item :label="$t('cronjob.taskName')" prop="port"> - <el-input clearable v-model.number="dialogData.rowData!.port" /> + <el-form-item :label="$t('commons.table.port')" prop="port"> + <el-input clearable v-model.trim="dialogData.rowData!.port" /> </el-form-item> - <el-form-item :label="$t('cronjob.taskName')" prop="username"> + <el-form-item :label="$t('commons.login.username')" prop="username"> <el-input clearable v-model.trim="dialogData.rowData!.username" /> </el-form-item> - <el-form-item :label="$t('cronjob.taskName')" prop="password"> - <el-input clearable v-model.trim="dialogData.rowData!.password" /> + <el-form-item :label="$t('commons.login.password')" prop="password"> + <el-input type="password" clearable show-password v-model.trim="dialogData.rowData!.password"> + <template #append> + <el-button @click="random">{{ $t('commons.button.random') }}</el-button> + </template> + </el-input> </el-form-item> - <el-form-item :label="$t('cronjob.description')" prop="description"> + <el-form-item :label="$t('commons.table.description')" prop="description"> <el-input clearable v-model.trim="dialogData.rowData!.description" /> </el-form-item> </el-col> @@ -53,14 +53,16 @@ import { reactive, ref } from 'vue'; import i18n from '@/lang'; import { ElForm } from 'element-plus'; -import { Cronjob } from '@/api/interface/cronjob'; -import { addCronjob, editCronjob } from '@/api/modules/cronjob'; +import { Database } from '@/api/interface/database'; import DrawerHeader from '@/components/drawer-header/index.vue'; import { MsgSuccess } from '@/utils/message'; +import { Rules } from '@/global/form-rules'; +import { getRandomStr } from '@/utils/util'; +import { addRemoteDB, editRemoteDB } from '@/api/modules/database'; interface DialogProps { title: string; - rowData?: Cronjob.CronjobInfo; + rowData?: Database.RemoteDBInfo; getTableList?: () => Promise<any>; } const title = ref<string>(''); @@ -70,7 +72,7 @@ const dialogData = ref<DialogProps>({ }); const acceptParams = (params: DialogProps): void => { dialogData.value = params; - title.value = i18n.global.t('cronjob.' + dialogData.value.title); + title.value = i18n.global.t('database.' + dialogData.value.title + 'RemoteDB'); drawerVisiable.value = true; }; const emit = defineEmits<{ (e: 'search'): void }>(); @@ -81,23 +83,49 @@ const handleClose = () => { const rules = reactive({ name: [Rules.requiredInput], - type: [Rules.requiredSelect], - address: [Rules.requiredInput], + version: [Rules.requiredSelect], + address: [Rules.ip], port: [Rules.port], + username: [Rules.requiredInput], + password: [Rules.requiredInput], }); type FormInstance = InstanceType<typeof ElForm>; const formRef = ref<FormInstance>(); +const random = async () => { + dialogData.value.rowData!.password = getRandomStr(16); +}; + const onSubmit = async (formEl: FormInstance | undefined) => { if (!formEl) return; formEl.validate(async (valid) => { if (!valid) return; if (dialogData.value.title === 'create') { - await addCronjob(dialogData.value.rowData); + let param = { + name: dialogData.value.rowData.name, + type: 'mysql', + version: dialogData.value.rowData.version, + from: 'remote', + address: dialogData.value.rowData.address, + port: dialogData.value.rowData.port, + username: dialogData.value.rowData.username, + password: dialogData.value.rowData.password, + description: dialogData.value.rowData.description, + }; + await addRemoteDB(param); } if (dialogData.value.title === 'edit') { - await editCronjob(dialogData.value.rowData); + let param = { + id: dialogData.value.rowData.id, + version: dialogData.value.rowData.version, + address: dialogData.value.rowData.address, + port: dialogData.value.rowData.port, + username: dialogData.value.rowData.username, + password: dialogData.value.rowData.password, + description: dialogData.value.rowData.description, + }; + await editRemoteDB(param); } MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); diff --git a/frontend/src/views/database/mysql/root-password/index.vue b/frontend/src/views/database/mysql/root-password/index.vue index 7536e5aa3..cae9893f7 100644 --- a/frontend/src/views/database/mysql/root-password/index.vue +++ b/frontend/src/views/database/mysql/root-password/index.vue @@ -6,22 +6,6 @@ <el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top"> <el-row type="flex" justify="center"> <el-col :span="22"> - <el-form-item :label="$t('database.rootPassword')" :rules="Rules.requiredInput" prop="password"> - <el-input type="password" show-password clearable v-model="form.password"> - <template #append> - <el-button @click="onCopy(form.password)">{{ $t('commons.button.copy') }}</el-button> - <el-divider direction="vertical" /> - <el-button @click="random"> - {{ $t('commons.button.random') }} - </el-button> - </template> - </el-input> - </el-form-item> - <el-form-item :label="$t('database.serviceName')" prop="serviceName"> - <el-tag>{{ form.serviceName }}</el-tag> - <el-button @click="onCopy(form.serviceName)" icon="DocumentCopy" link></el-button> - <span class="input-help">{{ $t('database.serviceNameHelper') }}</span> - </el-form-item> <el-form-item :label="$t('database.containerConn')"> <el-tag> {{ form.serviceName + ':3306' }} @@ -35,11 +19,30 @@ <el-tag>{{ $t('database.localIP') + ':' + form.port }}</el-tag> <span class="input-help">{{ $t('database.remoteConnHelper2') }}</span> </el-form-item> + + <el-divider border-style="dashed" /> + + <el-form-item :label="$t('database.remoteAccess')" prop="privilege"> + <el-switch v-model="form.privilege" @change="onSaveAccess" /> + <span class="input-help">{{ $t('database.remoteConnHelper') }}</span> + </el-form-item> + <el-form-item :label="$t('database.rootPassword')" :rules="Rules.requiredInput" prop="password"> + <el-input type="password" show-password clearable v-model="form.password"> + <template #append> + <el-button @click="onCopy(form.password)">{{ $t('commons.button.copy') }}</el-button> + <el-divider direction="vertical" /> + <el-button @click="random"> + {{ $t('commons.button.random') }} + </el-button> + </template> + </el-input> + </el-form-item> </el-col> </el-row> </el-form> - <ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit"></ConfirmDialog> + <ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" @cancel="loadPassword"></ConfirmDialog> + <ConfirmDialog ref="confirmAccessDialogRef" @confirm="onSubmitAccess" @cancel="loadAccess"></ConfirmDialog> <template #footer> <span class="dialog-footer"> @@ -59,7 +62,7 @@ import { ref } from 'vue'; import { Rules } from '@/global/form-rules'; import i18n from '@/lang'; import { ElForm } from 'element-plus'; -import { updateMysqlPassword } from '@/api/modules/database'; +import { loadRemoteAccess, updateMysqlAccess, updateMysqlPassword } 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'; @@ -75,10 +78,12 @@ const dialogVisiable = ref(false); const form = ref<App.DatabaseConnInfo>({ password: '', serviceName: '', + privilege: false, port: 0, }); const confirmDialogRef = ref(); +const confirmAccessDialogRef = ref(); type FormInstance = InstanceType<typeof ElForm>; const formRef = ref<FormInstance>(); @@ -86,6 +91,7 @@ const formRef = ref<FormInstance>(); const acceptParams = (): void => { form.value.password = ''; loadPassword(); + loadAccess(); dialogVisiable.value = true; }; @@ -106,6 +112,11 @@ const handleClose = () => { dialogVisiable.value = false; }; +const loadAccess = async () => { + const res = await loadRemoteAccess(); + form.value.privilege = res.data; +}; + const loadPassword = async () => { const res = await GetAppConnInfo('mysql'); form.value = res.data; @@ -141,6 +152,32 @@ const onSave = async (formEl: FormInstance | undefined) => { }); }; +const onSubmitAccess = async () => { + let param = { + id: 0, + value: form.value.privilege ? '%' : 'localhost', + }; + loading.value = true; + await updateMysqlAccess(param) + .then(() => { + loading.value = false; + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + dialogVisiable.value = false; + }) + .catch(() => { + loading.value = false; + }); +}; + +const onSaveAccess = () => { + let params = { + header: i18n.global.t('database.confChange'), + operationInfo: i18n.global.t('database.restartNowHelper'), + submitInputInfo: i18n.global.t('database.restartNow'), + }; + confirmAccessDialogRef.value!.acceptParams(params); +}; + defineExpose({ acceptParams, }); diff --git a/frontend/src/views/database/remote-db/index.vue b/frontend/src/views/database/remote-db/index.vue deleted file mode 100644 index 77c3a6c6a..000000000 --- a/frontend/src/views/database/remote-db/index.vue +++ /dev/null @@ -1,152 +0,0 @@ -<template> - <div v-loading="loading"> - <LayoutContent :title="'MySQL ' + $t('menu.database')"> - <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.create') }} - </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 :label="$t('commons.table.name')" prop="name" sortable /> - <el-table-column :label="$t('commons.login.username')" 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> - <span style="float: left; line-height: 25px" v-if="!row.showPassword">***********</span> - <div style="cursor: pointer; float: left" v-if="!row.showPassword"> - <el-icon - style="margin-left: 5px; margin-top: 3px" - @click="row.showPassword = true" - :size="16" - > - <View /> - </el-icon> - </div> - <span style="float: left" v-if="row.showPassword">{{ row.password }}</span> - <div style="cursor: pointer; float: left" v-if="row.showPassword"> - <el-icon - style="margin-left: 5px; margin-top: 3px" - @click="row.showPassword = false" - :size="16" - > - <Hide /> - </el-icon> - </div> - <div style="cursor: pointer; float: left"> - <el-icon style="margin-left: 5px; margin-top: 3px" :size="16" @click="onCopy(row)"> - <DocumentCopy /> - </el-icon> - </div> - </div> - </template> - </el-table-column> - <el-table-column - prop="createdAt" - :label="$t('commons.table.date')" - :formatter="dateFormat" - show-overflow-tooltip - /> - </ComplexTable> - </template> - </LayoutContent> - - <OperateDialog ref="dialogRef" @search="search" /> - </div> -</template> - -<script lang="ts" setup> -import { dateFormat } from '@/utils/util'; -import { reactive, ref } from 'vue'; -import { searchRemoteDBs } from '@/api/modules/database'; -import i18n from '@/lang'; -import { MsgError, MsgSuccess } from '@/utils/message'; -import useClipboard from 'vue-clipboard3'; -import { Database } from '@/api/interface/database'; -const { toClipboard } = useClipboard(); - -const loading = ref(false); - -const dialogRef = ref(); - -const data = ref(); -const paginationConfig = reactive({ - currentPage: 1, - pageSize: 10, - total: 0, -}); -const searchName = ref(); - -const search = async (column?: any) => { - let params = { - page: paginationConfig.currentPage, - pageSize: paginationConfig.pageSize, - info: searchName.value, - orderBy: column?.order ? column.prop : 'created_at', - order: column?.order ? column.order : 'null', - }; - const res = await searchRemoteDBs(params); - data.value = res.data.items || []; - paginationConfig.total = res.data.total; -}; - -const onOpenDialog = async ( - title: string, - rowData: Partial<Database.RemoteDBInfo> = { - name: '', - type: 'Mysql', - address: '', - port: 3306, - username: '', - password: '', - description: '', - }, -) => { - let params = { - title, - rowData: { ...rowData }, - }; - dialogRef.value!.acceptParams(params); -}; - -const onCopy = async (row: any) => { - try { - await toClipboard(row.password); - MsgSuccess(i18n.global.t('commons.msg.copySuccess')); - } catch (e) { - MsgError(i18n.global.t('commons.msg.copyfailed')); - } -}; - -// const onDelete = async (row: Database.MysqlDBInfo) => { -// const res = await deleteCheckMysqlDB(row.id); -// deleteRef.value.acceptParams({ id: row.id, name: row.name }); -// }; - -// const buttons = [ -// { -// label: i18n.global.t('commons.button.delete'), -// click: (row: Database.MysqlDBInfo) => { -// onDelete(row); -// }, -// }, -// ]; -</script>