diff --git a/backend/utils/mysql/client.go b/backend/utils/mysql/client.go
index ffc5978d9..e5d27e25f 100644
--- a/backend/utils/mysql/client.go
+++ b/backend/utils/mysql/client.go
@@ -17,6 +17,9 @@ type MysqlClient interface {
ChangePassword(info client.PasswordChangeInfo) error
ChangeAccess(info client.AccessChangeInfo) error
+ Backup(info client.BackupInfo) (string, error)
+ Recover(info client.RecoverInfo) error
+
Close()
}
@@ -26,7 +29,7 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
return nil, buserr.New(constant.ErrCmdIllegal)
}
connArgs := []string{"exec", conn.Address, "mysql", "-u" + conn.Username, "-p" + conn.Password, "-e"}
- return client.NewLocal(connArgs, conn.Address), nil
+ return client.NewLocal(connArgs, conn.Address, conn.Password), nil
}
connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", conn.Username, conn.Password, conn.Address, conn.Port)
@@ -37,5 +40,11 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
if err := db.Ping(); err != nil {
return nil, err
}
- return client.NewRemote(db), nil
+ return client.NewRemote(client.Remote{
+ Client: db,
+ User: conn.Username,
+ Password: conn.Password,
+ Address: conn.Address,
+ Port: conn.Port,
+ }), nil
}
diff --git a/backend/utils/mysql/client/info.go b/backend/utils/mysql/client/info.go
index 5eed4f6d7..415b1a8b1 100644
--- a/backend/utils/mysql/client/info.go
+++ b/backend/utils/mysql/client/info.go
@@ -52,6 +52,22 @@ type AccessChangeInfo struct {
Timeout uint `json:"timeout"` // second
}
+type BackupInfo struct {
+ Name string `json:"name"`
+ Format string `json:"format"`
+ TargetDir string `json:"targetDir"`
+
+ Timeout uint `json:"timeout"` // second
+}
+
+type RecoverInfo struct {
+ Name string `json:"name"`
+ Format string `json:"format"`
+ SourceFile string `json:"sourceFile"`
+
+ Timeout uint `json:"timeout"` // second
+}
+
var formatMap = map[string]string{
"utf8": "utf8_general_ci",
"utf8mb4": "utf8mb4_general_ci",
diff --git a/backend/utils/mysql/client/local.go b/backend/utils/mysql/client/local.go
index d0d8ae49e..788776a66 100644
--- a/backend/utils/mysql/client/local.go
+++ b/backend/utils/mysql/client/local.go
@@ -1,9 +1,11 @@
package client
import (
+ "compress/gzip"
"context"
"errors"
"fmt"
+ "os"
"os/exec"
"strings"
"time"
@@ -11,15 +13,17 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
+ "github.com/1Panel-dev/1Panel/backend/utils/files"
)
type Local struct {
PrefixCommand []string
+ Password string
ContainerName string
}
-func NewLocal(command []string, containerName string) *Local {
- return &Local{PrefixCommand: command, ContainerName: containerName}
+func NewLocal(command []string, containerName, password string) *Local {
+ return &Local{PrefixCommand: command, ContainerName: containerName, Password: password}
}
func (r *Local) Create(info CreateInfo) error {
@@ -201,6 +205,54 @@ func (r *Local) ChangeAccess(info AccessChangeInfo) error {
return nil
}
+func (r *Local) Backup(info BackupInfo) (string, error) {
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(info.TargetDir) {
+ if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
+ return "", fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
+ }
+ }
+ fileName := fmt.Sprintf("%s/%s_%s.sql.gz", info.TargetDir, info.Name, time.Now().Format("20060102150405"))
+ outfile, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0755)
+ global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+fileName)
+ cmd := exec.Command("docker", "exec", r.ContainerName, "mysqldump", "-uroot", "-p"+r.Password, info.Name)
+ gzipCmd := exec.Command("gzip", "-cf")
+ gzipCmd.Stdin, _ = cmd.StdoutPipe()
+ gzipCmd.Stdout = outfile
+ _ = gzipCmd.Start()
+ _ = cmd.Run()
+ _ = gzipCmd.Wait()
+ return fileName, nil
+}
+
+func (r *Local) Recover(info RecoverInfo) error {
+ fi, _ := os.Open(info.SourceFile)
+ defer fi.Close()
+ cmd := exec.Command("docker", "exec", "-i", r.ContainerName, "mysql", "-uroot", "-p"+r.Password, info.Name)
+ if strings.HasSuffix(info.SourceFile, ".gz") {
+ gzipFile, err := os.Open(info.SourceFile)
+ if err != nil {
+ return err
+ }
+ defer gzipFile.Close()
+ gzipReader, err := gzip.NewReader(gzipFile)
+ if err != nil {
+ return err
+ }
+ defer gzipReader.Close()
+ cmd.Stdin = gzipReader
+ } else {
+ cmd.Stdin = fi
+ }
+ stdout, err := cmd.CombinedOutput()
+ 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 (r *Local) Close() {}
func (r *Local) ExecSQL(command string, timeout uint) error {
diff --git a/backend/utils/mysql/client/remote.go b/backend/utils/mysql/client/remote.go
index f97a46e86..16e40c978 100644
--- a/backend/utils/mysql/client/remote.go
+++ b/backend/utils/mysql/client/remote.go
@@ -4,20 +4,29 @@ import (
"context"
"database/sql"
"fmt"
+ "os"
+ "path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
+ "github.com/1Panel-dev/1Panel/backend/utils/files"
+
+ "github.com/jarvanstack/mysqldump"
)
type Remote struct {
- Client *sql.DB
+ Client *sql.DB
+ User string
+ Password string
+ Address string
+ Port uint
}
-func NewRemote(client *sql.DB) *Remote {
- return &Remote{Client: client}
+func NewRemote(db Remote) *Remote {
+ return &db
}
func (r *Remote) Create(info CreateInfo) error {
@@ -199,6 +208,55 @@ func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
return nil
}
+func (r *Remote) Backup(info BackupInfo) (string, error) {
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(info.TargetDir) {
+ if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
+ return "", fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
+ }
+ }
+ fileName := fmt.Sprintf("%s/%s_%s.sql", info.TargetDir, info.Name, time.Now().Format("20060102150405"))
+ dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
+
+ f, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0755)
+ defer f.Close()
+ if err := mysqldump.Dump(dns, mysqldump.WithData(), mysqldump.WithWriter(f)); err != nil {
+ return "", err
+ }
+
+ if err := fileOp.Compress([]string{fileName}, info.TargetDir, path.Base(fileName)+".gz", files.Gz); err != nil {
+ return "", err
+ }
+ return fileName, nil
+}
+
+func (r *Remote) Recover(info RecoverInfo) error {
+ fileOp := files.NewFileOp()
+ fileName := info.SourceFile
+ if strings.HasSuffix(info.SourceFile, ".sql.gz") {
+ fileName = strings.TrimSuffix(info.SourceFile, ".gz")
+ if err := fileOp.Decompress(info.SourceFile, fileName, files.Gz); err != nil {
+ return err
+ }
+ }
+ if strings.HasSuffix(info.SourceFile, ".tar.gz") {
+ fileName = strings.TrimSuffix(info.SourceFile, ".tar.gz")
+ if err := fileOp.Decompress(info.SourceFile, fileName, files.TarGz); err != nil {
+ return err
+ }
+ }
+ dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
+ f, err := os.Open(fileName)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ if err := mysqldump.Source(dns, f); err != nil {
+ return err
+ }
+ return nil
+}
+
func (r *Remote) Close() {
_ = r.Client.Close()
}
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index cb75610e0..7cf8ccc46 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -342,6 +342,9 @@ const message = {
localDB: 'Local DB',
address: 'DB address',
version: 'DB version',
+ 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.',
selectFile: 'Select file',
dropHelper: 'You can drag and drop the uploaded file here or',
diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts
index 87c2a775a..19024c342 100644
--- a/frontend/src/lang/modules/tw.ts
+++ b/frontend/src/lang/modules/tw.ts
@@ -337,6 +337,9 @@ const message = {
localDB: '本地數據庫',
address: '數據庫地址',
version: '數據庫版本',
+ versionHelper: '當前僅支持 5.6 5.7 8.0 三個版本',
+ addressHelper: '非 127.0.0.1 的遠程數據庫地址',
+ userHelper: 'root 用戶或者擁有 root 權限的數據庫用戶',
selectFile: '選擇文件',
dropHelper: '將上傳文件拖拽到此處,或者',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 092a13887..f7a6b73aa 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -337,6 +337,9 @@ const message = {
localDB: '本地数据库',
address: '数据库地址',
version: '数据库版本',
+ versionHelper: '当前仅支持 5.6 5.7 8.0 三个版本',
+ addressHelper: '非 127.0.0.1 的远程数据库地址',
+ userHelper: 'root 用户或者拥有 root 权限的数据库用户',
selectFile: '选择文件',
dropHelper: '将上传文件拖拽到此处,或者',
diff --git a/frontend/src/views/database/mysql/create/index.vue b/frontend/src/views/database/mysql/create/index.vue
index 4e18b8b58..2be1cfb1f 100644
--- a/frontend/src/views/database/mysql/create/index.vue
+++ b/frontend/src/views/database/mysql/create/index.vue
@@ -46,7 +46,7 @@
{{ $t('database.remoteHelper') }}
-
+
;
const formRef = ref();
diff --git a/frontend/src/views/database/mysql/index.vue b/frontend/src/views/database/mysql/index.vue
index fc5046f10..e22f6f288 100644
--- a/frontend/src/views/database/mysql/index.vue
+++ b/frontend/src/views/database/mysql/index.vue
@@ -13,7 +13,7 @@
- {{ $t('website.group') }}
+ {{ $t('commons.table.type') }}
@@ -28,19 +28,34 @@
-
-
+
+
-
+
{{ $t('database.create') }}
-
+
{{ $t('database.databaseConnInfo') }}
{{ $t('database.remoteDB') }}
-
+
phpMyAdmin
diff --git a/frontend/src/views/database/mysql/remote/index.vue b/frontend/src/views/database/mysql/remote/index.vue
index 237237ea8..49c4c240d 100644
--- a/frontend/src/views/database/mysql/remote/index.vue
+++ b/frontend/src/views/database/mysql/remote/index.vue
@@ -131,7 +131,7 @@ const onOpenDialog = async (
rowData: Partial = {
name: '',
type: 'mysql',
- version: '5.6.x',
+ version: '5.6',
address: '',
port: 3306,
username: '',
diff --git a/frontend/src/views/database/mysql/remote/operate/index.vue b/frontend/src/views/database/mysql/remote/operate/index.vue
index 5e28e272e..c761a738e 100644
--- a/frontend/src/views/database/mysql/remote/operate/index.vue
+++ b/frontend/src/views/database/mysql/remote/operate/index.vue
@@ -1,5 +1,5 @@
-
+
@@ -11,26 +11,25 @@
-
-
-
+
+
+
+ {{ $t('database.versionHelper') }}
+ {{ $t('database.addressHelper') }}
+ {{ $t('database.userHelper') }}
-
-
- {{ $t('commons.button.random') }}
-
-
+
@@ -57,7 +56,6 @@ 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 {
@@ -93,10 +91,6 @@ const rules = reactive({
type FormInstance = InstanceType;
const formRef = ref();
-const random = async () => {
- dialogData.value.rowData!.password = getRandomStr(16);
-};
-
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
diff --git a/go.mod b/go.mod
index 96f4bc72a..138ca9f49 100644
--- a/go.mod
+++ b/go.mod
@@ -21,11 +21,12 @@ require (
github.com/go-acme/lego/v4 v4.9.0
github.com/go-gormigrate/gormigrate/v2 v2.0.2
github.com/go-playground/validator/v10 v10.14.0
- github.com/go-sql-driver/mysql v1.6.0
+ github.com/go-sql-driver/mysql v1.7.0
github.com/goh-chunlin/go-onedrive v1.1.1
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
+ github.com/jarvanstack/mysqldump v0.7.0
github.com/jinzhu/copier v0.3.5
github.com/joho/godotenv v1.5.1
github.com/klauspost/compress v1.16.5
diff --git a/go.sum b/go.sum
index a80e3cf1d..d4edd4a3a 100644
--- a/go.sum
+++ b/go.sum
@@ -333,8 +333,8 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
-github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@@ -507,6 +507,8 @@ github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
+github.com/jarvanstack/mysqldump v0.7.0 h1:lspwQQhLrpVpCCbNxs5GPNTAcrqYTLVLIO7txUbtYdc=
+github.com/jarvanstack/mysqldump v0.7.0/go.mod h1:NBXaxyEQjiaLwLP9s3pGfal7aZL/5omKJrk+bC1E250=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
@@ -843,8 +845,6 @@ github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9J
github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=
github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
-github.com/swaggo/swag v1.8.4 h1:oGB351qH1JqUqK1tsMYEE5qTBbPk394BhsZxmUfebcI=
-github.com/swaggo/swag v1.8.4/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@@ -1011,8 +1011,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
@@ -1204,8 +1202,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=