feat: 完成 mysql 从上传恢复功能

pull/34/head
ssongliu 2022-11-09 15:08:38 +08:00 committed by ssongliu
parent c49d2ef243
commit 581c940336
18 changed files with 295 additions and 196 deletions

View File

@ -1,10 +1,7 @@
package v1
import (
"bufio"
"errors"
"fmt"
"os"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@ -118,21 +115,10 @@ func (b *BaseApi) UpdateMysqlConfByFile(c *gin.Context) {
return
}
mysqlInfo, err := mysqlService.LoadBaseInfo(req.MysqlName)
if err != nil {
if err := mysqlService.UpdateConfByFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
path := fmt.Sprintf("/opt/1Panel/data/apps/%s/%s/conf/my.cnf", mysqlInfo.MysqlKey, mysqlInfo.Name)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.File)
write.Flush()
helper.SuccessWithData(c, nil)
}
@ -206,6 +192,21 @@ func (b *BaseApi) BackupMysql(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) RecoverMysqlByUpload(c *gin.Context) {
var req dto.UploadRecover
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := mysqlService.RecoverByUpload(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) RecoverMysql(c *gin.Context) {
var req dto.RecoverDB
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -131,6 +131,13 @@ type RecoverDB struct {
BackupName string `json:"backupName" validate:"required"`
}
type UploadRecover struct {
MysqlName string `json:"mysqlName" validate:"required"`
DBName string `json:"dbName" validate:"required"`
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
}
// redis
type RedisConfUpdate struct {
Timeout string `json:"timeout"`
@ -181,7 +188,7 @@ type RedisStatus struct {
LatestForkUsec string `json:"latest_fork_usec"`
}
type RedisBackupRecords struct {
type DatabaseFileRecords struct {
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
CreatedAt string `json:"createdAt"`

View File

@ -201,7 +201,11 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
return backClient, nil
}
func loadLocalDir(backup model.BackupAccount) (string, error) {
func loadLocalDir() (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return "", err
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return "", err

View File

@ -83,11 +83,7 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
return "", err
}
if cronjob.KeepLocal || cronjob.Type != "LOCAL" {
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return "", err
}
localDir, err := loadLocalDir(backupLocal)
localDir, err := loadLocalDir()
if err != nil {
return "", err
}

View File

@ -1,9 +1,11 @@
package service
import (
"bufio"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"os"
@ -19,6 +21,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/files"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@ -33,8 +36,10 @@ type IMysqlService interface {
Create(mysqlDto dto.MysqlDBCreate) error
ChangeInfo(info dto.ChangeDBInfo) error
UpdateVariables(mysqlName string, updatas []dto.MysqlVariablesUpdate) error
UpdateConfByFile(info dto.MysqlConfUpdateByFile) error
UpFile(mysqlName string, files []*multipart.FileHeader) error
RecoverByUpload(req dto.UploadRecover) error
SearchUpListWithPage(req dto.SearchDBWithPage) (int64, interface{}, error)
Backup(db dto.BackupDB) error
Recover(db dto.RecoverDB) error
@ -65,28 +70,23 @@ func (u *MysqlService) SearchWithPage(search dto.SearchDBWithPage) (int64, inter
func (u *MysqlService) SearchUpListWithPage(req dto.SearchDBWithPage) (int64, interface{}, error) {
var (
list []dto.RedisBackupRecords
backDatas []dto.RedisBackupRecords
list []dto.DatabaseFileRecords
backDatas []dto.DatabaseFileRecords
)
redisInfo, err := mysqlRepo.LoadBaseInfoByName(req.MysqlName)
localDir, appKey, err := loadBackupDirAndKey(req.MysqlName)
if err != nil {
return 0, nil, err
return 0, list, nil
}
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return 0, nil, err
}
localDir, err := loadLocalDir(backupLocal)
if err != nil {
return 0, nil, err
}
uploadDir := fmt.Sprintf("%s/database/%s/%s/upload", localDir, redisInfo.Key, redisInfo.Name)
uploadDir := fmt.Sprintf("%s/database/%s/%s/upload", localDir, appKey, req.MysqlName)
if _, err := os.Stat(uploadDir); err != nil {
return 0, list, nil
}
_ = filepath.Walk(uploadDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
list = append(list, dto.RedisBackupRecords{
list = append(list, dto.DatabaseFileRecords{
CreatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
Size: int(info.Size()),
FileDir: uploadDir,
@ -97,7 +97,7 @@ func (u *MysqlService) SearchUpListWithPage(req dto.SearchDBWithPage) (int64, in
})
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]dto.RedisBackupRecords, 0)
backDatas = make([]dto.DatabaseFileRecords, 0)
} else {
if end >= total {
end = total
@ -108,19 +108,11 @@ func (u *MysqlService) SearchUpListWithPage(req dto.SearchDBWithPage) (int64, in
}
func (u *MysqlService) UpFile(mysqlName string, files []*multipart.FileHeader) error {
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
localDir, appKey, err := loadBackupDirAndKey(mysqlName)
if err != nil {
return err
}
app, err := mysqlRepo.LoadBaseInfoByName(mysqlName)
if err != nil {
return err
}
localDir, err := loadLocalDir(backupLocal)
if err != nil {
return err
}
dstDir := fmt.Sprintf("%s/database/%s/%s/upload", localDir, app.Key, mysqlName)
dstDir := fmt.Sprintf("%s/database/%s/%s/upload", localDir, appKey, mysqlName)
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
if err != nil {
@ -139,6 +131,85 @@ func (u *MysqlService) UpFile(mysqlName string, files []*multipart.FileHeader) e
return err
}
defer out.Close()
_, _ = io.Copy(out, src)
}
return nil
}
func (u *MysqlService) RecoverByUpload(req dto.UploadRecover) error {
app, err := mysqlRepo.LoadBaseInfoByName(req.MysqlName)
if err != nil {
return err
}
localDir, err := loadLocalDir()
if err != nil {
return err
}
file := req.FileDir + "/" + req.FileName
if !strings.HasSuffix(req.FileName, ".sql") && !strings.HasSuffix(req.FileName, ".gz") {
fileOp := files.NewFileOp()
fileNameItem := time.Now().Format("20060102150405")
dstDir := fmt.Sprintf("%s/database/%s/%s/upload/tmp/%s", localDir, app.Key, req.MysqlName, fileNameItem)
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
if err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
}
}
}
var compressType files.CompressType
switch {
case strings.HasSuffix(req.FileName, ".tar.gz"), strings.HasSuffix(req.FileName, ".tgz"):
compressType = files.TarGz
case strings.HasSuffix(req.FileName, ".zip"):
compressType = files.Zip
}
if err := fileOp.Decompress(req.FileDir+"/"+req.FileName, dstDir, compressType); err != nil {
_ = os.RemoveAll(dstDir)
return err
}
hasTestSql := false
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() && info.Name() == "test.sql" {
hasTestSql = true
file = path
}
return nil
})
if !hasTestSql {
_ = os.RemoveAll(dstDir)
return fmt.Errorf("no such file named test.sql in %s, err: %v", req.FileName, err)
}
defer func() {
_ = os.RemoveAll(dstDir)
}()
}
fi, _ := os.Open(file)
defer fi.Close()
cmd := exec.Command("docker", "exec", "-i", app.ContainerName, "mysql", "-uroot", "-p"+app.Password, req.DBName)
if strings.HasSuffix(req.FileName, ".gz") {
gzipFile, err := os.Open(file)
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
}
@ -175,7 +246,11 @@ func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error {
if mysqlDto.Username == "root" {
return errors.New("Cannot set root as user name")
}
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(mysqlDto.Name))
app, err := mysqlRepo.LoadBaseInfoByName(mysqlDto.MysqlName)
if err != nil {
return err
}
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(mysqlDto.Name), mysqlRepo.WithByMysqlName(app.Key))
if mysql.ID != 0 {
return constant.ErrRecordExist
}
@ -183,15 +258,11 @@ func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
app, err := mysqlRepo.LoadBaseInfoByName(mysqlDto.MysqlName)
if err != nil {
return err
}
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create database if not exists %s character set=%s", mysqlDto.Name, mysqlDto.Format)); err != nil {
return err
}
tmpPermission := mysqlDto.Permission
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create user if not exists '%s'@'%s' identified by '%s';", mysqlDto.Name, tmpPermission, mysqlDto.Password)); err != nil {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create user if not exists '%s'@'%s' identified by '%s';", mysqlDto.Username, tmpPermission, mysqlDto.Password)); err != nil {
return err
}
grantStr := fmt.Sprintf("grant all privileges on %s.* to '%s'@'%s'", mysqlDto.Name, mysqlDto.Username, tmpPermission)
@ -208,19 +279,11 @@ func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error {
}
func (u *MysqlService) Backup(db dto.BackupDB) error {
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
localDir, appKey, err := loadBackupDirAndKey(db.MysqlName)
if err != nil {
return err
}
app, err := mysqlRepo.LoadBaseInfoByName(db.MysqlName)
if err != nil {
return err
}
localDir, err := loadLocalDir(backupLocal)
if err != nil {
return err
}
backupDir := fmt.Sprintf("database/%s/%s/%s", app.Key, db.MysqlName, db.DBName)
backupDir := fmt.Sprintf("database/%s/%s/%s", appKey, db.MysqlName, db.DBName)
fileName := fmt.Sprintf("%s_%s.sql.gz", db.DBName, time.Now().Format("20060102150405"))
if err := backupMysql("LOCAL", localDir, backupDir, db.MysqlName, db.DBName, fileName); err != nil {
return err
@ -235,12 +298,12 @@ func (u *MysqlService) Recover(db dto.RecoverDB) error {
}
gzipFile, err := os.Open(db.BackupName)
if err != nil {
fmt.Println(err)
return err
}
defer gzipFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
fmt.Println(err)
return err
}
defer gzipReader.Close()
cmd := exec.Command("docker", "exec", "-i", app.ContainerName, "mysql", "-uroot", "-p"+app.Password, db.DBName)
@ -356,6 +419,26 @@ func (u *MysqlService) ChangeInfo(info dto.ChangeDBInfo) error {
return nil
}
func (u *MysqlService) UpdateConfByFile(info dto.MysqlConfUpdateByFile) error {
app, err := mysqlRepo.LoadBaseInfoByName(info.MysqlName)
if err != nil {
return err
}
path := fmt.Sprintf("%s/%s/%s/conf/my.cnf", constant.AppInstallDir, app.Key, app.Name)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(info.File)
write.Flush()
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, app.Key, app.Name)); err != nil {
return err
}
return nil
}
func (u *MysqlService) UpdateVariables(mysqlName string, updatas []dto.MysqlVariablesUpdate) error {
app, err := mysqlRepo.LoadBaseInfoByName(mysqlName)
if err != nil {
@ -568,12 +651,16 @@ func backupMysql(backupType, baseDir, backupDir, mysqlName, dbName, fileName str
func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string {
isOn := false
hasGroup := false
hasKey := false
regItem, _ := regexp.Compile(`\[*\]`)
var newFiles []string
i := 0
for _, line := range oldFiles {
i++
if strings.HasPrefix(line, group) {
isOn = true
hasGroup = true
newFiles = append(newFiles, line)
continue
}
@ -586,19 +673,31 @@ func updateMyCnf(oldFiles []string, group string, param string, value interface{
hasKey = true
continue
}
isDeadLine := regItem.Match([]byte(line))
if !isDeadLine {
if regItem.Match([]byte(line)) || i == len(oldFiles) {
isOn = false
if !hasKey {
newFiles = append(newFiles, fmt.Sprintf("%s=%v", param, value))
}
newFiles = append(newFiles, line)
continue
}
if !hasKey {
newFiles = append(newFiles, fmt.Sprintf("%s=%v\n", param, value))
newFiles = append(newFiles, line)
}
newFiles = append(newFiles, line)
}
if !isOn {
if !hasGroup {
newFiles = append(newFiles, group+"\n")
newFiles = append(newFiles, fmt.Sprintf("%s=%v\n", param, value))
}
return newFiles
}
func loadBackupDirAndKey(mysqlName string) (string, string, error) {
app, err := mysqlRepo.LoadBaseInfoByName(mysqlName)
if err != nil {
return "", "", err
}
localDir, err := loadLocalDir()
if err != nil {
return "", "", err
}
return localDir, app.Key, nil
}

View File

@ -178,11 +178,7 @@ func (u *RedisService) Backup() error {
if stdout, err := cmd.CombinedOutput(); err != nil {
return errors.New(string(stdout))
}
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return err
}
localDir, err := loadLocalDir(backupLocal)
localDir, err := loadLocalDir()
if err != nil {
return err
}
@ -255,25 +251,24 @@ func (u *RedisService) Recover(req dto.RedisBackupRecover) error {
func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error) {
var (
list []dto.RedisBackupRecords
backDatas []dto.RedisBackupRecords
list []dto.DatabaseFileRecords
backDatas []dto.DatabaseFileRecords
)
redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if err != nil {
return 0, nil, err
}
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return 0, nil, err
}
localDir, err := loadLocalDir(backupLocal)
localDir, err := loadLocalDir()
if err != nil {
return 0, nil, err
}
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
_ = filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
list = append(list, dto.RedisBackupRecords{
list = append(list, dto.DatabaseFileRecords{
CreatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
Size: int(info.Size()),
FileDir: backupDir,
@ -284,7 +279,7 @@ func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interf
})
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]dto.RedisBackupRecords, 0)
backDatas = make([]dto.DatabaseFileRecords, 0)
} else {
if end >= total {
end = total

View File

@ -1,65 +0,0 @@
package service
import (
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
"testing"
)
func TestMysql(t *testing.T) {
path := "/Users/slooop/go/src/github.com/1Panel/apps/mysql/5.7.39/conf/my.cnf"
var lines []string
lineBytes, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println(err)
} else {
lines = strings.Split(string(lineBytes), "\n")
}
var newLines []string
start := "[mysqld]"
isOn := false
hasKey := false
regItem, _ := regexp.Compile(`^\[*\]`)
i := 0
for _, line := range lines {
i++
if strings.HasPrefix(line, start) {
isOn = true
newLines = append(newLines, line)
continue
}
if !isOn {
newLines = append(newLines, line)
continue
}
if strings.HasPrefix(line, "user") || strings.HasPrefix(line, "# user") {
newLines = append(newLines, "user="+"ON")
hasKey = true
continue
}
isDeadLine := regItem.Match([]byte(line))
if !isDeadLine {
newLines = append(newLines, line)
continue
}
if !hasKey {
newLines = append(newLines, "user="+"ON \n")
newLines = append(newLines, line)
}
}
file, err := os.OpenFile(path, os.O_WRONLY, 0666)
if err != nil {
fmt.Println(err)
}
defer file.Close()
_, err = file.WriteString(strings.Join(newLines, "\n"))
if err != nil {
fmt.Println(err)
}
}

View File

@ -2,12 +2,16 @@ package service
import (
"context"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
)
type dbStr string
func getTxAndContext() (tx *gorm.DB, ctx context.Context) {
db := dbStr("db")
tx = global.DB.Begin()
ctx = context.WithValue(context.Background(), "db", tx)
ctx = context.WithValue(context.Background(), db, tx)
return
}

View File

@ -26,6 +26,7 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
withRecordRouter.POST("/backup", baseApi.BackupMysql)
withRecordRouter.POST("/uplist", baseApi.MysqlUpList)
withRecordRouter.POST("/uplist/upload/:mysqlName", baseApi.UploadMysqlFiles)
withRecordRouter.POST("/recover/byupload", baseApi.RecoverMysqlByUpload)
withRecordRouter.POST("/recover", baseApi.RecoverMysql)
withRecordRouter.POST("/backups/search", baseApi.SearchDBBackups)
withRecordRouter.POST("/del", baseApi.DeleteMysql)

View File

@ -17,6 +17,12 @@ export namespace Database {
dbName: string;
backupName: string;
}
export interface RecoverByUpload {
mysqlName: string;
dbName: string;
fileName: string;
fileDir: string;
}
export interface MysqlDBInfo {
id: number;
createdAt: Date;
@ -167,7 +173,7 @@ export namespace Database {
createdAt: string;
size: string;
}
export interface RedisBackupDelete {
export interface FileRecordDelete {
fileDir: string;
names: Array<string>;
}

View File

@ -23,6 +23,9 @@ export const backup = (params: Database.Backup) => {
export const recover = (params: Database.Recover) => {
return http.post(`/databases/recover`, params);
};
export const recoverByUpload = (params: Database.RecoverByUpload) => {
return http.post(`/databases/recover/byupload`, params);
};
export const searchBackupRecords = (params: Database.SearchBackupRecord) => {
return http.post<ResPage<Backup.RecordInfo>>(`/databases/backups/search`, params);
};
@ -84,6 +87,6 @@ export const recoverRedis = (param: Database.RedisRecover) => {
export const redisBackupRedisRecords = (param: ReqPage) => {
return http.post<ResPage<Database.FileRecord>>(`/databases/redis/backup/records`, param);
};
export const deleteBackupRedis = (param: Database.RedisBackupDelete) => {
export const deleteDatabaseFile = (param: Database.FileRecordDelete) => {
return http.post(`/databases/redis/backup/del`, param);
};

View File

@ -25,6 +25,10 @@ export default {
handle: 'Handle',
expand: 'Expand',
log: 'Log',
back: 'Back',
recover: 'Recover',
upload: 'Upload',
download: 'Download',
saveAndEnable: 'Save and enable',
},
search: {
@ -174,6 +178,13 @@ export default {
portHelper:
'This port is the exposed port of the container. You need to save the modification separately and restart the container!',
unSupportType: 'Current file type is not supported!',
unSupportSize: 'The uploaded file exceeds 10M, please confirm!',
selectFile: 'Select file',
supportUpType: 'Only sql, zip, sql.gz, and (tar.gz gz tgz) files within 10 MB are supported',
zipFormat:
'zip, tar.gz compressed package structure: test.zip or test.tar.gz compressed package must contain test.sql',
currentStatus: 'Current state',
runTime: 'Startup time',
connections: 'Total connections',

View File

@ -28,6 +28,7 @@ export default {
log: '',
back: '',
recover: '',
upload: '',
download: '',
saveAndEnable: '',
},
@ -174,7 +175,11 @@ export default {
confChange: '',
portHelper: '',
unSupportType: '',
unSupportType: '',
unSupportSize: ' 10M',
selectFile: '',
supportUpType: ' 10M sqlzipsql.gz(tar.gz gz tgz) ',
zipFormat: 'ziptar.gz test.zip test.tar.gz test.sql',
currentStatus: '',
runTime: '',

View File

@ -91,7 +91,7 @@ const onRecover = async (row: Backup.RecordInfo) => {
let params = {
mysqlName: mysqlName.value,
dbName: dbName.value,
backupName: row.fileDir + row.fileName,
backupName: row.fileDir + '/' + row.fileName,
};
await recover(params);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));

View File

@ -36,7 +36,7 @@
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" @search="search" :data="data">
<template #toolbar>
<el-button type="primary" @click="onOpenDialog()">{{ $t('commons.button.create') }}</el-button>
<el-button @click="onOpenDialog()">phpMyAdmin</el-button>
<el-button>phpMyAdmin</el-button>
<el-button type="danger" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
@ -44,7 +44,26 @@
<el-table-column type="selection" fix />
<el-table-column :label="$t('commons.table.name')" prop="name" />
<el-table-column :label="$t('auth.username')" prop="username" />
<el-table-column :label="$t('auth.password')" prop="password" />
<el-table-column :label="$t('auth.password')" prop="password">
<template #default="{ row }">
<div v-if="!row.showPassword">
<span style="float: left">***********</span>
<div style="margin-top: 2px; cursor: pointer">
<el-icon style="margin-left: 5px" @click="row.showPassword = true" :size="16">
<View />
</el-icon>
</div>
</div>
<div v-else>
<span style="float: left">{{ row.password }}</span>
<div style="margin-top: 4px; cursor: pointer">
<el-icon style="margin-left: 5px" @click="row.showPassword = false" :size="16">
<Hide />
</el-icon>
</div>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.description')" prop="description" />
<el-table-column
prop="createdAt"
@ -274,9 +293,10 @@ const buttons = [
},
{
label: i18n.global.t('database.loadBackup'),
click: () => {
click: (row: Database.MysqlDBInfo) => {
let params = {
mysqlName: mysqlName.value,
dbName: row.name,
};
uploadRef.value!.acceptParams(params);
},

View File

@ -71,7 +71,7 @@ const acceptParams = (params: DialogProps): void => {
variables.long_query_time = Number(params.variables.long_query_time);
if (variables.slow_query_log === 'ON') {
let path = `/opt/1Panel/data/apps/${mysqlKey.value}/${mysqlName.value}/data/onepanel-slow.log`;
let path = `/opt/1Panel/data/apps/${mysqlKey.value}/${mysqlName.value}/data/1Panel-slow.log`;
loadMysqlSlowlogs(path);
}
oldVariables.value = { ...variables };
@ -91,10 +91,10 @@ const onSave = async () => {
if (variables.slow_query_log !== oldVariables.value.slow_query_log) {
param.push({ param: 'slow_query_log', value: variables.slow_query_log });
}
if (variables.long_query_time !== oldVariables.value.long_query_time) {
if (variables.slow_query_log === 'ON') {
param.push({ param: 'long_query_time', value: variables.long_query_time });
param.push({ param: 'slow_query_log_file', value: '/var/lib/mysql/1Panel-slow.log' });
}
param.push({ param: 'slow_query_log_file', value: '/var/lib/mysql/onepanel-slow.log' });
await updateMysqlVariables(mysqlName.value, param);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
};

View File

@ -14,13 +14,17 @@
:auto-upload="false"
>
<template #trigger>
<el-button>选择文件</el-button>
<el-button type="primary" plain>{{ $t('database.selectFile') }}</el-button>
</template>
<el-button style="margin-left: 10px" @click="onSubmit"></el-button>
<el-button style="margin-left: 10px" icon="Upload" @click="onSubmit">
{{ $t('commons.button.upload') }}
</el-button>
</el-upload>
<div style="margin-left: 10px">
<span class="input-help">仅支持sqlzipsql.gz(tar.gz|gz|tgz)</span>
<span class="input-help">ziptar.gz压缩包结构test.zip或test.tar.gz压缩包内必需包含test.sql</span>
<span class="input-help">{{ $t('database.supportUpType') }}</span>
<span class="input-help">
{{ $t('database.zipFormat') }}
</span>
</div>
<el-divider />
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
@ -61,11 +65,10 @@ import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue';
import { computeSize } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
import { recover, searchUpList, uploadFile } from '@/api/modules/database';
import { deleteDatabaseFile, recoverByUpload, searchUpList, uploadFile } from '@/api/modules/database';
import i18n from '@/lang';
import { ElMessage, UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
import { deleteBackupRecord } from '@/api/modules/backup';
import { Backup } from '@/api/interface/backup';
import { Database } from '@/api/interface/database';
const selects = ref<any>([]);
@ -101,13 +104,14 @@ const search = async () => {
paginationConfig.total = res.data.total;
};
const onRecover = async (row: Backup.RecordInfo) => {
const onRecover = async (row: Database.FileRecord) => {
let params = {
mysqlName: mysqlName.value,
dbName: dbName.value,
backupName: row.fileDir + row.fileName,
fileDir: row.fileDir,
fileName: row.fileName,
};
await recover(params);
await recoverByUpload(params);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
};
@ -115,9 +119,17 @@ const uploaderFiles = ref<UploadFiles>([]);
const uploadRef = ref<UploadInstance>();
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
if (rawFile.name.endsWith('.sql') || rawFile.name.endsWith('gz') || rawFile.name.endsWith('.zip')) {
if (
rawFile.name.endsWith('.sql') ||
rawFile.name.endsWith('.gz') ||
rawFile.name.endsWith('.zip') ||
rawFile.name.endsWith('.tgz')
) {
ElMessage.error(i18n.global.t('database.unSupportType'));
return false;
} else if (rawFile.size / 1024 / 1024 > 10) {
ElMessage.error(i18n.global.t('database.unSupportSize'));
return false;
}
return true;
};
@ -145,29 +157,32 @@ const onSubmit = () => {
});
};
const onBatchDelete = async (row: Backup.RecordInfo | null) => {
let ids: Array<number> = [];
const onBatchDelete = async (row: Database.FileRecord | null) => {
let names: Array<string> = [];
let fileDir: string = '';
if (row) {
ids.push(row.id);
fileDir = row.fileDir;
names.push(row.fileName);
} else {
selects.value.forEach((item: Backup.RecordInfo) => {
ids.push(item.id);
selects.value.forEach((item: Database.FileRecord) => {
fileDir = item.fileDir;
names.push(item.fileName);
});
}
await useDeleteData(deleteBackupRecord, { ids: ids }, 'commons.msg.delete', true);
await useDeleteData(deleteDatabaseFile, { fileDir: fileDir, names: names }, 'commons.msg.delete', true);
search();
};
const buttons = [
{
label: i18n.global.t('commons.button.recover'),
click: (row: Backup.RecordInfo) => {
click: (row: Database.FileRecord) => {
onRecover(row);
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: Backup.RecordInfo) => {
click: (row: Database.FileRecord) => {
onBatchDelete(row);
},
},

View File

@ -124,7 +124,7 @@ import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { Database } from '@/api/interface/database';
import {
backupRedis,
deleteBackupRedis,
deleteDatabaseFile,
recoverRedis,
redisBackupRedisRecords,
RedisPersistenceConf,
@ -166,7 +166,6 @@ const data = ref();
const selects = ref<any>([]);
const currentRow = ref();
const confirmDialogRef = ref();
const submitInput = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
@ -199,35 +198,33 @@ const onBackup = async () => {
loadBackupRecords();
};
const onRecover = async () => {
if (submitInput.value === i18n.global.t('database.submitIt')) {
let param = {
fileName: currentRow.value.fileName,
fileDir: currentRow.value.fileDir,
};
await recoverRedis(param);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
}
let param = {
fileName: currentRow.value.fileName,
fileDir: currentRow.value.fileDir,
};
await recoverRedis(param);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
};
const onBatchDelete = async (row: Database.RedisBackupRecord | null) => {
const onBatchDelete = async (row: Database.FileRecord | null) => {
let names: Array<string> = [];
let fileDir: string = '';
if (row) {
fileDir = row.fileDir;
names.push(row.fileName);
} else {
selects.value.forEach((item: Database.RedisBackupRecord) => {
selects.value.forEach((item: Database.FileRecord) => {
fileDir = item.fileDir;
names.push(item.fileName);
});
}
await useDeleteData(deleteBackupRedis, { fileDir: fileDir, names: names }, 'commons.msg.delete', true);
await useDeleteData(deleteDatabaseFile, { fileDir: fileDir, names: names }, 'commons.msg.delete', true);
loadBackupRecords();
};
const buttons = [
{
label: i18n.global.t('commons.button.recover'),
click: (row: Database.RedisBackupRecord) => {
click: (row: Database.FileRecord) => {
currentRow.value = row;
let params = {
header: i18n.global.t('commons.button.recover'),
@ -239,7 +236,7 @@ const buttons = [
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: Database.RedisBackupRecord) => {
click: (row: Database.FileRecord) => {
onBatchDelete(row);
},
},