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

feat: 数据库实现远程服务器获取功能 (#1775)

This commit is contained in:
ssongliu 2023-07-27 16:07:27 +08:00 committed by GitHub
parent e83e592e0a
commit 40aaa1ceb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 440 additions and 77 deletions

View File

@ -229,6 +229,25 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
helper.SuccessWithData(c, list) helper.SuccessWithData(c, list)
} }
// @Tags Database Mysql
// @Summary Load mysql database from remote
// @Description 从服务器获取
// @Security ApiKeyAuth
// @Router /databases/load/:from [get]
func (b *BaseApi) LoadDBFromRemote(c *gin.Context) {
from, err := helper.GetStrParamByKey(c, "from")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := mysqlService.LoadFromRemote(from); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql // @Tags Database Mysql
// @Summary Check before delete mysql database // @Summary Check before delete mysql database
// @Description Mysql 数据库删除前检查 // @Description Mysql 数据库删除前检查

View File

@ -63,7 +63,7 @@ func (b *BaseApi) SearchRemoteDB(c *gin.Context) {
// @Tags Database // @Tags Database
// @Summary List remote databases // @Summary List remote databases
// @Description 获取快速命令列表 // @Description 获取远程数据库列表
// @Success 200 {array} dto.RemoteDBOption // @Success 200 {array} dto.RemoteDBOption
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/remote/list/:type [get] // @Router /databases/remote/list/:type [get]
@ -82,6 +82,27 @@ func (b *BaseApi) ListRemoteDB(c *gin.Context) {
helper.SuccessWithData(c, list) helper.SuccessWithData(c, list)
} }
// @Tags Database
// @Summary Get remote databases
// @Description 获取远程数据库
// @Success 200 dto.RemoteDBOption
// @Security ApiKeyAuth
// @Router /databases/remote/:name [get]
func (b *BaseApi) GetRemoteDB(c *gin.Context) {
name, err := helper.GetStrParamByKey(c, "name")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := remoteDBService.Get(name)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Database // @Tags Database
// @Summary Delete remote database // @Summary Delete remote database
// @Description 删除远程数据库 // @Description 删除远程数据库

View File

@ -3,7 +3,7 @@ package model
type DatabaseMysql struct { type DatabaseMysql struct {
BaseModel BaseModel
Name string `json:"name" gorm:"type:varchar(256);not null"` Name string `json:"name" gorm:"type:varchar(256);not null"`
From string `json:"type" gorm:"type:varchar(256);not null;default:'local'"` From string `json:"from" gorm:"type:varchar(256);not null;default:local"`
MysqlName string `json:"mysqlName" gorm:"type:varchar(64);not null"` MysqlName string `json:"mysqlName" gorm:"type:varchar(64);not null"`
Format string `json:"format" 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"` Username string `json:"username" gorm:"type:varchar(256);not null"`

View File

@ -33,6 +33,7 @@ type IMysqlService interface {
SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error) SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error)
ListDBName() ([]string, error) ListDBName() ([]string, error)
Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error)
LoadFromRemote(from string) error
ChangeAccess(info dto.ChangeDBInfo) error ChangeAccess(info dto.ChangeDBInfo) error
ChangePassword(info dto.ChangeDBInfo) error ChangePassword(info dto.ChangeDBInfo) error
UpdateVariables(updates []dto.MysqlVariablesUpdate) error UpdateVariables(updates []dto.MysqlVariablesUpdate) error
@ -89,9 +90,6 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
if req.From == "local" && req.Username == "root" { if req.From == "local" && req.Username == "root" {
return nil, errors.New("Cannot set root as user name") return nil, errors.New("Cannot set root as user name")
} }
if req.From == "127.0.0.1" {
return nil, errors.New("Cannot set 127.0.0.1 as address")
}
cli, version, err := LoadMysqlClientByFrom(req.From) cli, version, err := LoadMysqlClientByFrom(req.From)
if err != nil { if err != nil {
@ -127,29 +125,74 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
return &createItem, nil return &createItem, nil
} }
func (u *MysqlService) LoadFromRemote(from string) error {
client, version, err := LoadMysqlClientByFrom(from)
if err != nil {
return err
}
databases, err := mysqlRepo.List(remoteDBRepo.WithByFrom(from))
if err != nil {
return err
}
datas, err := client.SyncDB(version)
if err != nil {
return err
}
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if oldData.Name == data.Name {
hasOld = true
break
}
}
if !hasOld {
var createItem model.DatabaseMysql
if err := copier.Copy(&createItem, &data); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if err := mysqlRepo.Create(context.Background(), &createItem); err != nil {
return err
}
}
}
return nil
}
func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error { func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
return mysqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description}) return mysqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
} }
func (u *MysqlService) DeleteCheck(id uint) ([]string, error) { func (u *MysqlService) DeleteCheck(id uint) ([]string, error) {
var appInUsed []string var appInUsed []string
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return appInUsed, err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(id)) db, err := mysqlRepo.Get(commonRepo.WithByID(id))
if err != nil { if err != nil {
return appInUsed, err return appInUsed, err
} }
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID)) if db.From == "local" {
for _, app := range apps { app, err := appInstallRepo.LoadBaseInfo("mysql", "")
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId)) if err != nil {
if appInstall.ID != 0 { return appInUsed, err
appInUsed = append(appInUsed, appInstall.Name) }
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 return appInUsed, nil
} }

View File

@ -1,6 +1,8 @@
package service package service
import ( import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/mysql" "github.com/1Panel-dev/1Panel/backend/utils/mysql"
@ -12,6 +14,7 @@ import (
type RemoteDBService struct{} type RemoteDBService struct{}
type IRemoteDBService interface { type IRemoteDBService interface {
Get(name string) (dto.RemoteDBInfo, error)
SearchWithPage(search dto.RemoteDBSearch) (int64, interface{}, error) SearchWithPage(search dto.RemoteDBSearch) (int64, interface{}, error)
Create(req dto.RemoteDBCreate) error Create(req dto.RemoteDBCreate) error
Update(req dto.RemoteDBUpdate) error Update(req dto.RemoteDBUpdate) error
@ -40,6 +43,18 @@ func (u *RemoteDBService) SearchWithPage(search dto.RemoteDBSearch) (int64, inte
return total, datas, err return total, datas, err
} }
func (u *RemoteDBService) Get(name string) (dto.RemoteDBInfo, error) {
var data dto.RemoteDBInfo
remote, err := remoteDBRepo.Get(commonRepo.WithByName(name))
if err != nil {
return data, err
}
if err := copier.Copy(&data, &remote); err != nil {
return data, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
return data, nil
}
func (u *RemoteDBService) List(dbType string) ([]dto.RemoteDBOption, error) { func (u *RemoteDBService) List(dbType string) ([]dto.RemoteDBOption, error) {
dbs, err := remoteDBRepo.GetList(commonRepo.WithByType(dbType)) dbs, err := remoteDBRepo.GetList(commonRepo.WithByType(dbType))
var datas []dto.RemoteDBOption var datas []dto.RemoteDBOption
@ -82,7 +97,15 @@ func (u *RemoteDBService) Delete(id uint) error {
if db.ID == 0 { if db.ID == 0 {
return constant.ErrRecordNotFound return constant.ErrRecordNotFound
} }
return remoteDBRepo.Delete(commonRepo.WithByID(id)) if err := remoteDBRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
if db.From != "local" {
if err := mysqlRepo.Delete(context.Background(), remoteDBRepo.WithByFrom(db.Name)); err != nil {
return err
}
}
return nil
} }
func (u *RemoteDBService) Update(req dto.RemoteDBUpdate) error { func (u *RemoteDBService) Update(req dto.RemoteDBUpdate) error {

View File

@ -17,6 +17,7 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi baseApi := v1.ApiGroupApp.BaseApi
{ {
cmdRouter.POST("", baseApi.CreateMysql) cmdRouter.POST("", baseApi.CreateMysql)
cmdRouter.GET("load/:from", baseApi.LoadDBFromRemote)
cmdRouter.POST("/change/access", baseApi.ChangeMysqlAccess) cmdRouter.POST("/change/access", baseApi.ChangeMysqlAccess)
cmdRouter.POST("/change/password", baseApi.ChangeMysqlPassword) cmdRouter.POST("/change/password", baseApi.ChangeMysqlPassword)
cmdRouter.POST("/del/check", baseApi.DeleteCheckMysql) cmdRouter.POST("/del/check", baseApi.DeleteCheckMysql)
@ -42,6 +43,7 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf) cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf)
cmdRouter.POST("/remote", baseApi.CreateRemoteDB) cmdRouter.POST("/remote", baseApi.CreateRemoteDB)
cmdRouter.GET("/remote/:name", baseApi.GetRemoteDB)
cmdRouter.GET("/remote/list/:type", baseApi.ListRemoteDB) cmdRouter.GET("/remote/list/:type", baseApi.ListRemoteDB)
cmdRouter.POST("/remote/update", baseApi.UpdateRemoteDB) cmdRouter.POST("/remote/update", baseApi.UpdateRemoteDB)
cmdRouter.POST("/remote/search", baseApi.SearchRemoteDB) cmdRouter.POST("/remote/search", baseApi.SearchRemoteDB)

View File

@ -20,6 +20,7 @@ type MysqlClient interface {
Backup(info client.BackupInfo) error Backup(info client.BackupInfo) error
Recover(info client.RecoverInfo) error Recover(info client.RecoverInfo) error
SyncDB(version string) ([]client.SyncDBInfo, error)
Close() Close()
} }
@ -29,7 +30,7 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
return nil, buserr.New(constant.ErrCmdIllegal) 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, conn.Password), nil return client.NewLocal(connArgs, conn.Address, conn.Password, conn.From), nil
} }
connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", conn.Username, conn.Password, conn.Address, conn.Port) connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", conn.Username, conn.Password, conn.Address, conn.Port)
@ -42,6 +43,7 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
} }
return client.NewRemote(client.Remote{ return client.NewRemote(client.Remote{
Client: db, Client: db,
From: conn.From,
User: conn.Username, User: conn.Username,
Password: conn.Password, Password: conn.Password,
Address: conn.Address, Address: conn.Address,

View File

@ -69,6 +69,16 @@ type RecoverInfo struct {
Timeout uint `json:"timeout"` // second Timeout uint `json:"timeout"` // second
} }
type SyncDBInfo struct {
Name string `json:"name"`
From string `json:"from"`
MysqlName string `json:"mysqlName"`
Format string `json:"format"`
Username string `json:"username"`
Password string `json:"password"`
Permission string `json:"permission"`
}
var formatMap = map[string]string{ var formatMap = map[string]string{
"utf8": "utf8_general_ci", "utf8": "utf8_general_ci",
"utf8mb4": "utf8mb4_general_ci", "utf8mb4": "utf8mb4_general_ci",

View File

@ -13,17 +13,19 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
) )
type Local struct { type Local struct {
PrefixCommand []string PrefixCommand []string
From string
Password string Password string
ContainerName string ContainerName string
} }
func NewLocal(command []string, containerName, password string) *Local { func NewLocal(command []string, containerName, password, from string) *Local {
return &Local{PrefixCommand: command, ContainerName: containerName, Password: password} return &Local{PrefixCommand: command, ContainerName: containerName, Password: password, From: from}
} }
func (r *Local) Create(info CreateInfo) error { func (r *Local) Create(info CreateInfo) error {
@ -252,6 +254,81 @@ func (r *Local) Recover(info RecoverInfo) error {
return nil return nil
} }
func (r *Local) SyncDB(version string) ([]SyncDBInfo, error) {
var datas []SyncDBInfo
lines, err := r.ExecSQLForRows("SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA", 300)
if err != nil {
return datas, err
}
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) != 2 {
continue
}
if parts[0] == "SCHEMA_NAME" || parts[0] == "information_schema" || parts[0] == "mysql" || parts[0] == "performance_schema" || parts[0] == "sys" {
continue
}
dataItem := SyncDBInfo{
Name: parts[0],
From: r.From,
MysqlName: r.From,
Format: parts[1],
}
userLines, err := r.ExecSQLForRows(fmt.Sprintf("SELECT USER,HOST FROM mysql.DB WHERE DB = '%s'", parts[0]), 300)
if err != nil {
return datas, err
}
var permissionItem []string
isLocal := true
i := 0
for _, userline := range userLines {
userparts := strings.Fields(userline)
if len(userparts) != 2 {
continue
}
if userparts[0] == "root" {
continue
}
if i == 0 {
dataItem.Username = userparts[0]
}
dataItem.Username = userparts[0]
if dataItem.Username == userparts[0] && userparts[1] == "%" {
isLocal = false
dataItem.Permission = "%"
} else if dataItem.Username == userparts[0] && userparts[1] != "localhost" {
isLocal = false
permissionItem = append(permissionItem, userparts[1])
}
}
if len(dataItem.Username) == 0 {
if err := r.CreateUser(CreateInfo{
Name: parts[0],
Format: parts[1],
Version: version,
Username: parts[0],
Password: common.RandStr(16),
Permission: "%",
Timeout: 300,
}); err != nil {
global.LOG.Errorf("sync from remote server failed, err: create user failed %v", err)
}
dataItem.Username = parts[0]
dataItem.Permission = "%"
} else {
if isLocal {
dataItem.Permission = "localhost"
}
if len(dataItem.Permission) == 0 {
dataItem.Permission = strings.Join(permissionItem, ",")
}
}
datas = append(datas, dataItem)
}
return datas, nil
}
func (r *Local) Close() {} func (r *Local) Close() {}
func (r *Local) ExecSQL(command string, timeout uint) error { func (r *Local) ExecSQL(command string, timeout uint) error {

View File

@ -12,6 +12,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/jarvanstack/mysqldump" "github.com/jarvanstack/mysqldump"
@ -19,6 +20,7 @@ import (
type Remote struct { type Remote struct {
Client *sql.DB Client *sql.DB
From string
User string User string
Password string Password string
Address string Address string
@ -251,6 +253,86 @@ func (r *Remote) Recover(info RecoverInfo) error {
return nil return nil
} }
func (r *Remote) SyncDB(version string) ([]SyncDBInfo, error) {
var datas []SyncDBInfo
rows, err := r.Client.Query("SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA")
if err != nil {
return datas, err
}
defer rows.Close()
for rows.Next() {
var dbName, charsetName string
if err = rows.Scan(&dbName, &charsetName); err != nil {
return datas, err
}
if dbName == "information_schema" || dbName == "mysql" || dbName == "performance_schema" || dbName == "sys" {
continue
}
dataItem := SyncDBInfo{
Name: dbName,
From: r.From,
MysqlName: r.From,
Format: charsetName,
}
userRows, err := r.Client.Query("SELECT USER,HOST FROM mysql.DB WHERE DB = ?", dbName)
if err != nil {
return datas, err
}
var permissionItem []string
isLocal := true
i := 0
for userRows.Next() {
var user, host string
if err = userRows.Scan(&user, &host); err != nil {
return datas, err
}
if user == "root" {
continue
}
if i == 0 {
dataItem.Username = user
}
if dataItem.Username == user && host == "%" {
isLocal = false
dataItem.Permission = "%"
} else if dataItem.Username == user && host != "localhost" {
isLocal = false
permissionItem = append(permissionItem, host)
}
i++
}
if len(dataItem.Username) == 0 {
if err := r.CreateUser(CreateInfo{
Name: dbName,
Format: charsetName,
Version: version,
Username: dbName,
Password: common.RandStr(16),
Permission: "%",
Timeout: 300,
}); err != nil {
global.LOG.Errorf("sync from remote server failed, err: create user failed %v", err)
}
dataItem.Username = dbName
dataItem.Permission = "%"
} else {
if isLocal {
dataItem.Permission = "localhost"
}
if len(dataItem.Permission) == 0 {
dataItem.Permission = strings.Join(permissionItem, ",")
}
}
datas = append(datas, dataItem)
}
if err = rows.Err(); err != nil {
return datas, err
}
return datas, nil
}
func (r *Remote) Close() { func (r *Remote) Close() {
_ = r.Client.Close() _ = r.Client.Close()
} }

View File

@ -15,6 +15,9 @@ export const addMysqlDB = (params: Database.MysqlDBCreate) => {
} }
return http.post(`/databases`, reqest); return http.post(`/databases`, reqest);
}; };
export const loadDBFromRemote = (from: string) => {
return http.get(`/databases/load/${from}`);
};
export const updateMysqlAccess = (params: Database.ChangeInfo) => { export const updateMysqlAccess = (params: Database.ChangeInfo) => {
return http.post(`/databases/change/access`, params); return http.post(`/databases/change/access`, params);
}; };
@ -85,6 +88,9 @@ export const updateRedisConfByFile = (params: Database.RedisConfUpdateByFile) =>
}; };
// remote // remote
export const getRemoteDB = (name: string) => {
return http.get<Database.RemoteDBInfo>(`/databases/remote/${name}`);
};
export const searchRemoteDBs = (params: Database.SearchRemoteDBPage) => { export const searchRemoteDBs = (params: Database.SearchRemoteDBPage) => {
return http.post<ResPage<Database.RemoteDBInfo>>(`/databases/remote/search`, params); return http.post<ResPage<Database.RemoteDBInfo>>(`/databases/remote/search`, params);
}; };

View File

@ -335,15 +335,16 @@ const message = {
portHelper: portHelper:
'This port is the exposed port of the container. You need to save the modification separately and restart the container!', 'This port is the exposed port of the container. You need to save the modification separately and restart the container!',
loadFromRemote: 'load from Server',
passwordHelper: 'Unable to retrieve, please modify',
remote: 'Remote', remote: 'Remote',
remoteDB: 'Remote DB', remoteDB: 'Remote DB',
createRemoteDB: 'Create Remote DB', createRemoteDB: 'Create Remote Server',
editRemoteDB: 'Edit Remote DB', editRemoteDB: 'Edit Remote Server',
localDB: 'Local DB', localDB: 'Local DB',
address: 'DB address', address: 'DB address',
version: 'DB version', version: 'DB version',
versionHelper: 'Currently, only versions 5.6, 5.7, and 8.0 are supported', versionHelper: 'Currently, only versions 5.6, 5.7, and 8.0 are supported',
addressHelper: 'The remote database address except 127.0.0.1.',
userHelper: 'The root user or a database user with root privileges can access the remote database.', userHelper: 'The root user or a database user with root privileges can access the remote database.',
selectFile: 'Select file', selectFile: 'Select file',

View File

@ -330,15 +330,16 @@ const message = {
confChange: '配置修改', confChange: '配置修改',
loadFromRemote: '從服務器獲取',
passwordHelper: '無法獲取密碼請修改',
remote: '遠程', remote: '遠程',
remoteDB: '遠程數據庫', remoteDB: '遠程服務器',
createRemoteDB: '創建遠程數據庫', createRemoteDB: '添加遠程服務器',
editRemoteDB: '編輯遠程數據庫', editRemoteDB: '編輯遠程服務器',
localDB: '本地數據庫', localDB: '本地數據庫',
address: '數據庫地址', address: '數據庫地址',
version: '數據庫版本', version: '數據庫版本',
versionHelper: '當前僅支持 5.6 5.7 8.0 三個版本', versionHelper: '當前僅支持 5.6 5.7 8.0 三個版本',
addressHelper: ' 127.0.0.1 的遠程數據庫地址',
userHelper: 'root 用戶或者擁有 root 權限的數據庫用戶', userHelper: 'root 用戶或者擁有 root 權限的數據庫用戶',
selectFile: '選擇文件', selectFile: '選擇文件',

View File

@ -330,15 +330,16 @@ const message = {
confChange: '配置修改', confChange: '配置修改',
loadFromRemote: '从服务器获取',
passwordHelper: '无法获取密码请修改',
remote: '远程', remote: '远程',
remoteDB: '远程数据库', remoteDB: '远程服务器',
createRemoteDB: '创建远程数据库', createRemoteDB: '添加远程服务器',
editRemoteDB: '编辑远程数据库', editRemoteDB: '编辑远程服务器',
localDB: '本地数据库', localDB: '本地数据库',
address: '数据库地址', address: '数据库地址',
version: '数据库版本', version: '数据库版本',
versionHelper: '当前仅支持 5.6 5.7 8.0 三个版本', versionHelper: '当前仅支持 5.6 5.7 8.0 三个版本',
addressHelper: ' 127.0.0.1 的远程数据库地址',
userHelper: 'root 用户或者拥有 root 权限的数据库用户', userHelper: 'root 用户或者拥有 root 权限的数据库用户',
selectFile: '选择文件', selectFile: '选择文件',

View File

@ -454,7 +454,7 @@ const loadContainers = async () => {
const checkMysqlInstalled = async () => { const checkMysqlInstalled = async () => {
const data = await loadDBNames(); const data = await loadDBNames();
mysqlInfo.dbNames = data.data; mysqlInfo.dbNames = data.data || [];
}; };
function isBackup() { function isBackup() {

View File

@ -4,7 +4,7 @@
<DrawerHeader :header="$t('database.databaseConnInfo')" :back="handleClose" /> <DrawerHeader :header="$t('database.databaseConnInfo')" :back="handleClose" />
</template> </template>
<el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top"> <el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center" v-if="form.from === 'local'">
<el-col :span="22"> <el-col :span="22">
<el-form-item :label="$t('database.containerConn')"> <el-form-item :label="$t('database.containerConn')">
<el-tag> <el-tag>
@ -39,6 +39,19 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </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-tag>{{ form.remoteIP + ':' + form.port }}</el-tag>
</el-form-item>
<el-form-item :label="$t('commons.login.username')">
<el-tag>{{ form.username }}</el-tag>
</el-form-item>
<el-form-item :label="$t('commons.login.password')">
<el-tag>{{ form.password }}</el-tag>
</el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" @cancel="loadPassword"></ConfirmDialog> <ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" @cancel="loadPassword"></ConfirmDialog>
@ -58,28 +71,31 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import { ElForm } from 'element-plus'; import { ElForm } from 'element-plus';
import { loadRemoteAccess, updateMysqlAccess, updateMysqlPassword } from '@/api/modules/database'; import { getRemoteDB, loadRemoteAccess, updateMysqlAccess, updateMysqlPassword } from '@/api/modules/database';
import ConfirmDialog from '@/components/confirm-dialog/index.vue'; import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { GetAppConnInfo } from '@/api/modules/app'; import { GetAppConnInfo } from '@/api/modules/app';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';
import { getRandomStr } from '@/utils/util'; import { getRandomStr } from '@/utils/util';
import { App } from '@/api/interface/app';
import useClipboard from 'vue-clipboard3'; import useClipboard from 'vue-clipboard3';
const { toClipboard } = useClipboard(); const { toClipboard } = useClipboard();
const loading = ref(false); const loading = ref(false);
const dialogVisiable = ref(false); const dialogVisiable = ref(false);
const form = ref<App.DatabaseConnInfo>({ const form = reactive({
password: '', password: '',
serviceName: '', serviceName: '',
privilege: false, privilege: false,
port: 0, port: 0,
from: '',
username: '',
remoteIP: '',
}); });
const confirmDialogRef = ref(); const confirmDialogRef = ref();
@ -88,15 +104,24 @@ const confirmAccessDialogRef = ref();
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const acceptParams = (): void => { interface DialogProps {
form.value.password = ''; from: string;
remoteIP: string;
}
const acceptParams = (param: DialogProps): void => {
form.password = '';
form.from = param.from;
if (form.from !== 'local') {
loadRemoteInfo();
}
loadPassword(); loadPassword();
loadAccess(); loadAccess();
dialogVisiable.value = true; dialogVisiable.value = true;
}; };
const random = async () => { const random = async () => {
form.value.password = getRandomStr(16); form.password = getRandomStr(16);
}; };
const onCopy = async (value: string) => { const onCopy = async (value: string) => {
@ -114,18 +139,27 @@ const handleClose = () => {
const loadAccess = async () => { const loadAccess = async () => {
const res = await loadRemoteAccess(); const res = await loadRemoteAccess();
form.value.privilege = res.data; form.privilege = res.data;
};
const loadRemoteInfo = async () => {
const res = await getRemoteDB(form.from);
form.remoteIP = res.data.address;
form.username = res.data.username;
form.password = res.data.password;
}; };
const loadPassword = async () => { const loadPassword = async () => {
const res = await GetAppConnInfo('mysql'); const res = await GetAppConnInfo('mysql');
form.value = res.data; form.password = res.data.password || '';
form.port = res.data.port || 3306;
form.serviceName = res.data.serviceName || '';
}; };
const onSubmit = async () => { const onSubmit = async () => {
let param = { let param = {
id: 0, id: 0,
value: form.value.password, value: form.password,
}; };
loading.value = true; loading.value = true;
await updateMysqlPassword(param) await updateMysqlPassword(param)
@ -155,7 +189,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
const onSubmitAccess = async () => { const onSubmitAccess = async () => {
let param = { let param = {
id: 0, id: 0,
value: form.value.privilege ? '%' : 'localhost', value: form.privilege ? '%' : 'localhost',
}; };
loading.value = true; loading.value = true;
await updateMysqlAccess(param) await updateMysqlAccess(param)

View File

@ -38,13 +38,16 @@
> >
{{ $t('database.create') }} {{ $t('database.create') }}
</el-button> </el-button>
<el-button @click="onChangeConn" type="primary" plain>
{{ $t('database.databaseConnInfo') }}
</el-button>
<el-button <el-button
v-if="mysqlIsExist && mysqlStatus === 'Running' && isLocal()" v-if="(mysqlIsExist && mysqlStatus === 'Running') || !isLocal()"
@click="onChangeRootPassword" @click="loadDB"
type="primary" type="primary"
plain plain
> >
{{ $t('database.databaseConnInfo') }} {{ $t('database.loadFromRemote') }}
</el-button> </el-button>
<el-button @click="goRemoteDB" type="primary" plain> <el-button @click="goRemoteDB" type="primary" plain>
{{ $t('database.remoteDB') }} {{ $t('database.remoteDB') }}
@ -83,15 +86,10 @@
:class="{ mask: mysqlStatus != 'Running' && isLocal() }" :class="{ mask: mysqlStatus != 'Running' && isLocal() }"
> >
<el-table-column :label="$t('commons.table.name')" prop="name" sortable /> <el-table-column :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column :label="$t('commons.login.username')" prop="from">
<template #default="{ row }">
<span>{{ row.from === 'local' ? $t('database.localDB') : row.from }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.login.username')" prop="username" /> <el-table-column :label="$t('commons.login.username')" prop="username" />
<el-table-column :label="$t('commons.login.password')" prop="password"> <el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }"> <template #default="{ row }">
<div> <div v-if="row.password">
<span style="float: left; line-height: 25px" v-if="!row.showPassword">***********</span> <span style="float: left; line-height: 25px" v-if="!row.showPassword">***********</span>
<div style="cursor: pointer; float: left" v-if="!row.showPassword"> <div style="cursor: pointer; float: left" v-if="!row.showPassword">
<el-icon <el-icon
@ -104,20 +102,21 @@
</div> </div>
<span style="float: left" v-if="row.showPassword">{{ row.password }}</span> <span style="float: left" v-if="row.showPassword">{{ row.password }}</span>
<div style="cursor: pointer; float: left" v-if="row.showPassword"> <div style="cursor: pointer; float: left" v-if="row.showPassword">
<el-icon <el-icon class="iconInTable" @click="row.showPassword = false" :size="16">
style="margin-left: 5px; margin-top: 3px"
@click="row.showPassword = false"
:size="16"
>
<Hide /> <Hide />
</el-icon> </el-icon>
</div> </div>
<div style="cursor: pointer; float: left"> <div style="cursor: pointer; float: left">
<el-icon style="margin-left: 5px; margin-top: 3px" :size="16" @click="onCopy(row)"> <el-icon class="iconInTable" :size="16" @click="onCopy(row)">
<DocumentCopy /> <DocumentCopy />
</el-icon> </el-icon>
</div> </div>
</div> </div>
<div v-else>
<el-link @click="onChangePassword(row)">
<span style="font-size: 12px">{{ $t('database.passwordHelper') }}</span>
</el-link>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.description')" prop="description"> <el-table-column :label="$t('commons.table.description')" prop="description">
@ -191,7 +190,7 @@
</el-dialog> </el-dialog>
<PasswordDialog ref="passwordRef" @search="search" /> <PasswordDialog ref="passwordRef" @search="search" />
<RootPasswordDialog ref="rootPasswordRef" /> <RootPasswordDialog ref="connRef" />
<UploadDialog ref="uploadRef" /> <UploadDialog ref="uploadRef" />
<OperateDialog @search="search" ref="dialogRef" /> <OperateDialog @search="search" ref="dialogRef" />
<Backups ref="dialogBackupRef" /> <Backups ref="dialogBackupRef" />
@ -207,7 +206,7 @@
import OperateDialog from '@/views/database/mysql/create/index.vue'; import OperateDialog from '@/views/database/mysql/create/index.vue';
import DeleteDialog from '@/views/database/mysql/delete/index.vue'; import DeleteDialog from '@/views/database/mysql/delete/index.vue';
import PasswordDialog from '@/views/database/mysql/password/index.vue'; import PasswordDialog from '@/views/database/mysql/password/index.vue';
import RootPasswordDialog from '@/views/database/mysql/root-password/index.vue'; import RootPasswordDialog from '@/views/database/mysql/conn/index.vue';
import AppResources from '@/views/database/mysql/check/index.vue'; import AppResources from '@/views/database/mysql/check/index.vue';
import Setting from '@/views/database/mysql/setting/index.vue'; import Setting from '@/views/database/mysql/setting/index.vue';
import AppStatus from '@/components/app-status/index.vue'; import AppStatus from '@/components/app-status/index.vue';
@ -216,7 +215,13 @@ import UploadDialog from '@/components/upload/index.vue';
import PortJumpDialog from '@/components/port-jump/index.vue'; import PortJumpDialog from '@/components/port-jump/index.vue';
import { dateFormat } from '@/utils/util'; import { dateFormat } from '@/utils/util';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { deleteCheckMysqlDB, listRemoteDBs, searchMysqlDBs, updateMysqlDescription } from '@/api/modules/database'; import {
deleteCheckMysqlDB,
listRemoteDBs,
loadDBFromRemote,
searchMysqlDBs,
updateMysqlDescription,
} from '@/api/modules/database';
import i18n from '@/lang'; import i18n from '@/lang';
import { Database } from '@/api/interface/database'; import { Database } from '@/api/interface/database';
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
@ -268,9 +273,9 @@ const dialogBackupRef = ref();
const uploadRef = ref(); const uploadRef = ref();
const rootPasswordRef = ref(); const connRef = ref();
const onChangeRootPassword = async () => { const onChangeConn = async () => {
rootPasswordRef.value!.acceptParams(); connRef.value!.acceptParams({ from: paginationConfig.from });
}; };
const goRemoteDB = async () => { const goRemoteDB = async () => {
@ -308,6 +313,18 @@ const search = async (column?: any) => {
paginationConfig.total = res.data.total; paginationConfig.total = res.data.total;
}; };
const loadDB = async () => {
loading.value = true;
await loadDBFromRemote(paginationConfig.from)
.then(() => {
loading.value = false;
search();
})
.catch(() => {
loading.value = false;
});
};
const goRouter = async () => { const goRouter = async () => {
router.push({ name: 'AppDetail', params: { appKey: 'mysql' } }); router.push({ name: 'AppDetail', params: { appKey: 'mysql' } });
}; };
@ -376,23 +393,30 @@ const onDelete = async (row: Database.MysqlDBInfo) => {
} }
}; };
const onChangePassword = async (row: Database.MysqlDBInfo) => {
let param = {
id: row.id,
from: row.from,
mysqlName: row.name,
operation: 'password',
username: row.username,
password: row.password,
};
passwordRef.value.acceptParams(param);
};
const buttons = [ const buttons = [
{ {
label: i18n.global.t('database.changePassword'), label: i18n.global.t('database.changePassword'),
click: (row: Database.MysqlDBInfo) => { click: (row: Database.MysqlDBInfo) => {
let param = { onChangePassword(row);
id: row.id,
from: row.from,
mysqlName: row.name,
operation: 'password',
username: row.username,
password: row.password,
};
passwordRef.value.acceptParams(param);
}, },
}, },
{ {
label: i18n.global.t('database.permission'), label: i18n.global.t('database.permission'),
disabled: (row: Database.MysqlDBInfo) => {
return !row.password;
},
click: (row: Database.MysqlDBInfo) => { click: (row: Database.MysqlDBInfo) => {
let param = { let param = {
id: row.id, id: row.id,
@ -403,7 +427,7 @@ const buttons = [
privilegeIPs: '', privilegeIPs: '',
password: '', password: '',
}; };
if (row.permission === '%') { if (row.permission === '%' || row.permission === 'localhost') {
param.privilege = row.permission; param.privilege = row.permission;
} else { } else {
param.privilegeIPs = row.permission; param.privilegeIPs = row.permission;
@ -446,3 +470,10 @@ onMounted(() => {
loadDBOptions(); loadDBOptions();
}); });
</script> </script>
<style lang="scss" scoped>
.iconInTable {
margin-left: 5px;
margin-top: 3px;
}
</style>

View File

@ -29,6 +29,11 @@
<el-form-item :label="$t('database.permission')" prop="privilege"> <el-form-item :label="$t('database.permission')" prop="privilege">
<el-select style="width: 100%" v-model="changeForm.privilege"> <el-select style="width: 100%" v-model="changeForm.privilege">
<el-option value="%" :label="$t('database.permissionAll')" /> <el-option value="%" :label="$t('database.permissionAll')" />
<el-option
v-if="changeForm.from !== 'local'"
value="localhost"
:label="$t('terminal.localhost')"
/>
<el-option value="ip" :label="$t('database.permissionForIP')" /> <el-option value="ip" :label="$t('database.permissionForIP')" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -110,6 +115,7 @@ const acceptParams = (params: DialogProps): void => {
: i18n.global.t('database.permission'); : i18n.global.t('database.permission');
changeForm.id = params.id; changeForm.id = params.id;
changeForm.from = params.from; changeForm.from = params.from;
console.log(changeForm.from);
changeForm.mysqlName = params.mysqlName; changeForm.mysqlName = params.mysqlName;
changeForm.userName = params.username; changeForm.userName = params.username;
changeForm.password = params.password; changeForm.password = params.password;

View File

@ -1,5 +1,5 @@
<template> <template>
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%"> <el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header> <template #header>
<DrawerHeader :header="title" :resource="dialogData.rowData?.name" :back="handleClose" /> <DrawerHeader :header="title" :resource="dialogData.rowData?.name" :back="handleClose" />
</template> </template>
@ -7,7 +7,12 @@
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-col :span="22"> <el-col :span="22">
<el-form-item :label="$t('commons.table.name')" prop="name"> <el-form-item :label="$t('commons.table.name')" prop="name">
<el-input clearable v-model.trim="dialogData.rowData!.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>
<el-form-item :label="$t('database.version')" prop="version"> <el-form-item :label="$t('database.version')" prop="version">
<el-select v-model="dialogData.rowData!.version"> <el-select v-model="dialogData.rowData!.version">
@ -19,7 +24,6 @@
</el-form-item> </el-form-item>
<el-form-item :label="$t('database.address')" prop="address"> <el-form-item :label="$t('database.address')" prop="address">
<el-input clearable v-model.trim="dialogData.rowData!.address" /> <el-input clearable v-model.trim="dialogData.rowData!.address" />
<span class="input-help">{{ $t('database.addressHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item :label="$t('commons.table.port')" prop="port"> <el-form-item :label="$t('commons.table.port')" prop="port">
<el-input clearable v-model.trim="dialogData.rowData!.port" /> <el-input clearable v-model.trim="dialogData.rowData!.port" />