1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 08:19:15 +08:00

feat: 完成网站备份功能

This commit is contained in:
ssongliu 2022-11-29 17:39:10 +08:00 committed by ssongliu
parent 1f717bea0e
commit 7b21bcbe7f
28 changed files with 413 additions and 165 deletions

View File

@ -61,6 +61,25 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
var req dto.RecordSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := backupService.SearchRecordsWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
func (b *BaseApi) DownloadRecord(c *gin.Context) {
var req dto.DownloadRecord
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -1,16 +0,0 @@
package v1
import (
"fmt"
"testing"
"github.com/shirou/gopsutil/net"
)
func TestCopu(t *testing.T) {
fmt.Println(net.IOCounters(false))
fmt.Println(net.IOCounters(true))
}
// [{"name":"all","bytesSent":15498384367,"bytesRecv":18415197440,"packetsSent":11608058,"packetsRecv":12976591,"errin":0,"errout":21,"dropin":0,"dropout":1181,"fifoin":0,"fifoout":0}] <nil>
// [{"name":"lo0","bytesSent":12947010452,"bytesRecv":12947010452,"packetsSent":6519135,"packetsRecv":6519135,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"gif0","bytesSent":0,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"stf0","bytesSent":0,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"ap1","bytesSent":0,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"en0","bytesSent":2531548007,"bytesRecv":5443547837,"packetsSent":5019082,"packetsRecv":6374381,"errin":0,"errout":0,"dropin":0,"dropout":1065,"fifoin":0,"fifoout":0} {"name":"en1","bytesSent":0,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"en2","bytesSent":0,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"bridge0","bytesSent":0,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"awdl0","bytesSent":103717,"bytesRecv":157244,"packetsSent":412,"packetsRecv":460,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"llw0","bytesSent":0,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"utun0","bytesSent":21810,"bytesRecv":0,"packetsSent":121,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"utun1","bytesSent":21810,"bytesRecv":0,"packetsSent":121,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"utun2","bytesSent":21810,"bytesRecv":0,"packetsSent":121,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0} {"name":"en5","bytesSent":6093969,"bytesRecv":6264468,"packetsSent":47985,"packetsRecv":48051,"errin":0,"errout":21,"dropin":0,"dropout":116,"fifoin":0,"fifoout":0} {"name":"utun3","bytesSent":13576933,"bytesRecv":18231580,"packetsSent":21129,"packetsRecv":34612,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0}] <nil>

View File

@ -146,25 +146,6 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
helper.SuccessWithData(c, list)
}
func (b *BaseApi) SearchDBBackups(c *gin.Context) {
var req dto.SearchBackupsWithPage
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := mysqlService.SearchBackupsWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
func (b *BaseApi) BackupMysql(c *gin.Context) {
var req dto.BackupDB
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -38,6 +38,19 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) BackupWebsite(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.Backup(id); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) DeleteWebSite(c *gin.Context) {
var req dto.WebSiteDel
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -24,6 +24,13 @@ type BackupSearch struct {
DetailName string `json:"detailName"`
}
type RecordSearch struct {
PageInfo
Type string `json:"type" validate:"required"`
Name string `json:"name" validate:"required"`
DetailName string `json:"detailName"`
}
type BackupRecords struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`

View File

@ -112,12 +112,6 @@ type SearchDBWithPage struct {
MysqlName string `json:"mysqlName" validate:"required"`
}
type SearchBackupsWithPage struct {
PageInfo
MysqlName string `json:"mysqlName" validate:"required"`
DBName string `json:"dbName" validate:"required"`
}
type BackupDB struct {
MysqlName string `json:"mysqlName" validate:"required"`
DBName string `json:"dbName" validate:"required"`

View File

@ -2,7 +2,10 @@ package repo
import (
"context"
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
)
@ -96,3 +99,48 @@ func (a AppInstallRepo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOpt
}
return db.Debug().Updates(&maps).Error
}
type RootInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Port int64 `json:"port"`
Password string `json:"password"`
ContainerName string `json:"containerName"`
Param string `json:"param"`
Env string `json:"env"`
Key string `json:"key"`
Version string `json:"version"`
}
func (u *AppInstallRepo) LoadBaseInfoByKey(key string) (*RootInfo, error) {
var (
app model.App
appInstall model.AppInstall
info RootInfo
)
if err := global.DB.Where("key = ?", key).First(&app).Error; err != nil {
return nil, err
}
if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil {
return nil, err
}
envMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(appInstall.Env), &envMap); err != nil {
return nil, err
}
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
port, ok := envMap["PANEL_APP_PORT_HTTP"].(float64)
if ok {
info.Port = int64(port)
}
info.ID = appInstall.ID
info.ContainerName = appInstall.ContainerName
info.Name = appInstall.Name
info.Env = appInstall.Env
info.Param = appInstall.Param
info.Version = appInstall.Version
return &info, nil
}

View File

@ -2,6 +2,7 @@ package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
@ -26,6 +27,16 @@ func (a AppInstallResourceRpo) GetBy(opts ...DBOption) ([]model.AppInstallResour
return resources, err
}
func (a AppInstallResourceRpo) GetFirst(opts ...DBOption) (model.AppInstallResource, error) {
db := global.DB.Model(&model.AppInstallResource{})
var resources model.AppInstallResource
for _, opt := range opts {
db = opt(db)
}
err := db.First(&resources).Error
return resources, err
}
func (a AppInstallResourceRpo) Create(ctx context.Context, resource *model.AppInstallResource) error {
db := getTx(ctx).Model(&model.AppInstallResource{})
return db.Create(&resource).Error

View File

@ -2,8 +2,6 @@ package repo
import (
"context"
"encoding/json"
"errors"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
@ -20,7 +18,6 @@ type IMysqlRepo interface {
Create(ctx context.Context, mysql *model.DatabaseMysql) error
Delete(ctx context.Context, opts ...DBOption) error
Update(id uint, vars map[string]interface{}) error
LoadBaseInfoByKey(key string) (*RootInfo, error)
UpdateDatabaseInfo(id uint, vars map[string]interface{}) error
}
@ -60,55 +57,6 @@ func (u *MysqlRepo) Page(page, size int, opts ...DBOption) (int64, []model.Datab
return count, users, err
}
type RootInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Port int64 `json:"port"`
Password string `json:"password"`
ContainerName string `json:"containerName"`
Param string `json:"param"`
Env string `json:"env"`
Key string `json:"key"`
Version string `json:"version"`
}
func (u *MysqlRepo) LoadBaseInfoByKey(key string) (*RootInfo, error) {
var (
app model.App
appInstall model.AppInstall
info RootInfo
)
if err := global.DB.Where("key = ?", key).First(&app).Error; err != nil {
return nil, err
}
if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil {
return nil, err
}
envMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(appInstall.Env), &envMap); err != nil {
return nil, err
}
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
} else {
return nil, errors.New("error password in db")
}
port, ok := envMap["PANEL_APP_PORT_HTTP"].(float64)
if ok {
info.Port = int64(port)
} else {
return nil, errors.New("error port in db")
}
info.ID = appInstall.ID
info.ContainerName = appInstall.ContainerName
info.Name = appInstall.Name
info.Env = appInstall.Env
info.Param = appInstall.Param
info.Version = appInstall.Version
return &info, nil
}
func (u *MysqlRepo) Create(ctx context.Context, mysql *model.DatabaseMysql) error {
return getTx(ctx).Create(mysql).Error
}

View File

@ -3,7 +3,6 @@ package service
import (
"context"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"io/ioutil"
"os"
"path"
@ -11,6 +10,8 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
@ -247,7 +248,7 @@ func (a AppInstallService) ChangeAppPort(req dto.PortUpdate) error {
files []string
newFiles []string
)
app, err := mysqlRepo.LoadBaseInfoByKey(req.Key)
app, err := appInstallRepo.LoadBaseInfoByKey(req.Key)
if err != nil {
return err
}

View File

@ -18,7 +18,7 @@ type BackupService struct{}
type IBackupService interface {
List() ([]dto.BackupInfo, error)
SearchRecordWithPage(search dto.BackupSearch) (int64, []dto.BackupRecords, error)
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
DownloadRecord(info dto.DownloadRecord) (string, error)
Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
@ -45,7 +45,7 @@ func (u *BackupService) List() ([]dto.BackupInfo, error) {
return dtobas, err
}
func (u *BackupService) SearchRecordWithPage(search dto.BackupSearch) (int64, []dto.BackupRecords, error) {
func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) {
total, records, err := backupRepo.PageRecord(
search.Page, search.PageSize,
commonRepo.WithOrderBy("created_at desc"),

View File

@ -78,10 +78,6 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
if err != nil {
return "", err
}
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return "", err
}
if cronjob.KeepLocal || cronjob.Type != "LOCAL" {
localDir, err := loadLocalDir()
if err != nil {
@ -93,6 +89,10 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
}
if cronjob.Type == "database" {
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return "", err
}
fileName = fmt.Sprintf("db_%s_%s.sql.gz", cronjob.DBName, time.Now().Format("20060102150405"))
backupDir = fmt.Sprintf("database/mysql/%s/%s", app.Name, cronjob.DBName)
err = backupMysql(backup.Type, baseDir, backupDir, app.Name, cronjob.DBName, fileName)

View File

@ -33,7 +33,6 @@ type MysqlService struct{}
type IMysqlService interface {
SearchWithPage(search dto.PageInfo) (int64, interface{}, error)
ListDBName() ([]string, error)
SearchBackupsWithPage(search dto.SearchBackupsWithPage) (int64, interface{}, error)
Create(mysqlDto dto.MysqlDBCreate) error
ChangeInfo(info dto.ChangeDBInfo) error
UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
@ -137,7 +136,7 @@ func (u *MysqlService) UpFile(mysqlName string, files []*multipart.FileHeader) e
}
func (u *MysqlService) RecoverByUpload(req dto.UploadRecover) error {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
@ -223,26 +222,11 @@ func (u *MysqlService) ListDBName() ([]string, error) {
return dbNames, err
}
func (u *MysqlService) SearchBackupsWithPage(search dto.SearchBackupsWithPage) (int64, interface{}, error) {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return 0, nil, err
}
searchDto := dto.BackupSearch{
Type: "database-mysql",
PageInfo: search.PageInfo,
Name: app.Name,
DetailName: search.DBName,
}
return NewIBackupService().SearchRecordWithPage(searchDto)
}
func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error {
if mysqlDto.Username == "root" {
return errors.New("Cannot set root as user name")
}
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
@ -289,7 +273,7 @@ func (u *MysqlService) Backup(db dto.BackupDB) error {
}
func (u *MysqlService) Recover(db dto.RecoverDB) error {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
@ -314,7 +298,7 @@ func (u *MysqlService) Recover(db dto.RecoverDB) error {
}
func (u *MysqlService) Delete(ids []uint) error {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
@ -349,7 +333,7 @@ func (u *MysqlService) ChangeInfo(info dto.ChangeDBInfo) error {
return err
}
}
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
@ -417,7 +401,7 @@ func (u *MysqlService) ChangeInfo(info dto.ChangeDBInfo) error {
}
func (u *MysqlService) UpdateConfByFile(info dto.MysqlConfUpdateByFile) error {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
@ -437,7 +421,7 @@ func (u *MysqlService) UpdateConfByFile(info dto.MysqlConfUpdateByFile) error {
}
func (u *MysqlService) UpdateVariables(updatas []dto.MysqlVariablesUpdate) error {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
@ -479,7 +463,7 @@ func (u *MysqlService) UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
func (u *MysqlService) LoadBaseInfo() (*dto.DBBaseInfo, error) {
var data dto.DBBaseInfo
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return nil, err
}
@ -502,7 +486,7 @@ func (u *MysqlService) LoadBaseInfo() (*dto.DBBaseInfo, error) {
}
func (u *MysqlService) LoadVariables() (*dto.MysqlVariables, error) {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return nil, err
}
@ -520,7 +504,7 @@ func (u *MysqlService) LoadVariables() (*dto.MysqlVariables, error) {
}
func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return nil, err
}
@ -604,7 +588,7 @@ func excuteSql(containerName, password, command string) error {
}
func backupMysql(backupType, baseDir, backupDir, mysqlName, dbName, fileName string) error {
app, err := mysqlRepo.LoadBaseInfoByKey("mysql")
app, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}

View File

@ -37,7 +37,7 @@ func NewIRedisService() IRedisService {
}
func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return err
}
@ -70,7 +70,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
}
func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error {
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return err
}
@ -97,7 +97,7 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
}
func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) {
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return nil, err
}
@ -125,7 +125,7 @@ func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) {
}
func (u *RedisService) LoadConf() (*dto.RedisConf, error) {
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return nil, err
}
@ -150,7 +150,7 @@ func (u *RedisService) LoadConf() (*dto.RedisConf, error) {
}
func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) {
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return nil, err
}
@ -169,7 +169,7 @@ func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) {
}
func (u *RedisService) Backup() error {
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return err
}
@ -214,7 +214,7 @@ func (u *RedisService) Backup() error {
}
func (u *RedisService) Recover(req dto.RedisBackupRecover) error {
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return err
}
@ -254,7 +254,7 @@ func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interf
list []dto.DatabaseFileRecords
backDatas []dto.DatabaseFileRecords
)
redisInfo, err := mysqlRepo.LoadBaseInfoByKey("redis")
redisInfo, err := appInstallRepo.LoadBaseInfoByKey("redis")
if err != nil {
return 0, nil, err
}

View File

@ -1,13 +1,15 @@
package service
import (
"errors"
"fmt"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"path"
"strings"
)
type NginxService struct {
@ -57,7 +59,7 @@ func (n NginxService) GetStatus() (dto.NginxStatus, error) {
}
res, err := cmd.Exec(fmt.Sprintf("docker exec -i %s curl http://127.0.0.1/nginx_status", nginxInstall.ContainerName))
if err != nil {
return dto.NginxStatus{}, err
return dto.NginxStatus{}, errors.New(res)
}
var status dto.NginxStatus
resArray := strings.Split(res, " ")

View File

@ -5,16 +5,21 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
"gorm.io/gorm"
"io"
"os"
"os/exec"
"path"
"reflect"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type WebsiteService struct {
@ -101,6 +106,101 @@ func (w WebsiteService) CreateWebsite(create dto.WebSiteCreate) error {
return nil
}
func (w WebsiteService) Backup(id uint) error {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return err
}
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
if err != nil {
return err
}
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey(resource.Key)
if err != nil {
return err
}
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
localDir, err := loadLocalDir()
if err != nil {
return err
}
name := fmt.Sprintf("%s_%s", website.PrimaryDomain, time.Now().Format("20060102150405"))
backupDir := fmt.Sprintf("website/%s/%s", website.PrimaryDomain, name)
fullDir := fmt.Sprintf("%s/%s", localDir, backupDir)
if _, err := os.Stat(fullDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(fullDir, os.ModePerm); err != nil {
if err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", fullDir, err)
}
}
}
nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
src, err := os.OpenFile(nginxConfFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
return err
}
defer src.Close()
out, err := os.Create(fmt.Sprintf("%s/%s.conf", fullDir, website.PrimaryDomain))
if err != nil {
return err
}
defer out.Close()
_, _ = io.Copy(out, src)
if website.Type == "deployment" {
dbFile := fmt.Sprintf("%s/%s.sql", fullDir, website.PrimaryDomain)
outfile, _ := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE, 0755)
cmd := exec.Command("docker", "exec", mysqlInfo.ContainerName, "mysqldump", "-uroot", "-p"+mysqlInfo.Password, db.Name)
cmd.Stdout = outfile
_ = cmd.Run()
_ = cmd.Wait()
websiteDir := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, app.App.Key, app.Name)
if err := handleTar(websiteDir, fullDir, fmt.Sprintf("%s.web.tar.gz", website.PrimaryDomain), ""); err != nil {
return err
}
} else {
websiteDir := fmt.Sprintf("%s/nginx/%s/www/%s", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
if err := handleTar(websiteDir, fullDir, fmt.Sprintf("%s.web.tar.gz", website.PrimaryDomain), ""); err != nil {
return err
}
}
tarDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
tarName := fmt.Sprintf("%s.tar.gz", name)
itemDir := strings.ReplaceAll(fullDir[strings.LastIndex(fullDir, "/"):], "/", "")
aheadDir := strings.ReplaceAll(fullDir, itemDir, "")
tarcmd := exec.Command("tar", "zcvf", fullDir+".tar.gz", "-C", aheadDir, itemDir)
stdout, err := tarcmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
record := &model.BackupRecord{
Type: "website-" + website.Type,
Name: website.PrimaryDomain,
DetailName: "",
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: tarDir,
FileName: tarName,
}
if err := backupRepo.CreateRecord(record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
}
return nil
}
func (w WebsiteService) UpdateWebsite(req dto.WebSiteUpdate) error {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {

View File

@ -25,6 +25,7 @@ func (s *BackupRouter) InitBackupRouter(Router *gin.RouterGroup) {
baRouter.POST("/buckets", baseApi.ListBuckets)
withRecordRouter.POST("", baseApi.CreateBackup)
withRecordRouter.POST("/del", baseApi.DeleteBackup)
withRecordRouter.POST("/record/search", baseApi.SearchBackupRecords)
withRecordRouter.POST("/record/download", baseApi.DownloadRecord)
withRecordRouter.POST("/record/del", baseApi.DeleteBackupRecord)
withRecordRouter.PUT(":id", baseApi.UpdateBackup)

View File

@ -28,7 +28,6 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
cmdRouter.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)
withRecordRouter.POST("/variables/update", baseApi.UpdateMysqlVariables)
withRecordRouter.POST("/conf/update/byfile", baseApi.UpdateMysqlConfByFile)

View File

@ -16,6 +16,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.POST("", baseApi.CreateWebsite)
groupRouter.POST("/backup/:id", baseApi.BackupWebsite)
groupRouter.POST("/update", baseApi.UpdateWebSite)
groupRouter.GET("/:id", baseApi.GetWebSite)
groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx)

View File

@ -1,3 +1,5 @@
import { ReqPage } from '.';
export namespace Backup {
export interface BackupInfo {
id: number;
@ -31,4 +33,9 @@ export namespace Backup {
credential: string;
vars: string;
}
export interface SearchBackupRecord extends ReqPage {
type: string;
name: string;
detailName: string;
}
}

View File

@ -1,5 +1,6 @@
import http from '@/api';
import { Backup } from '../interface/backup';
import { ResPage } from '../interface';
export const getBackupList = () => {
return http.get<Array<Backup.BackupInfo>>(`/backups/search`);
@ -23,6 +24,9 @@ export const downloadBackupRecord = (params: Backup.RecordDownload) => {
export const deleteBackupRecord = (params: { ids: number[] }) => {
return http.post(`/backups/record/del`, params);
};
export const searchBackupRecords = (params: Backup.SearchBackupRecord) => {
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search`, params);
};
export const listBucket = (params: Backup.ForBucket) => {
return http.post(`/backups/buckets`, params);

View File

@ -1,6 +1,5 @@
import http from '@/api';
import { ReqPage, ResPage } from '../interface';
import { Backup } from '../interface/backup';
import { Database } from '../interface/database';
import { File } from '@/api/interface/file';
@ -23,9 +22,6 @@ export const recover = (params: Database.Recover) => {
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);
};
export const addMysqlDB = (params: Database.MysqlDBCreate) => {
return http.post(`/databases`, params);

View File

@ -11,6 +11,10 @@ export const CreateWebsite = (req: WebSite.WebSiteCreateReq) => {
return http.post<any>(`/websites`, req);
};
export const BackupWebsite = (id: number) => {
return http.post(`/websites/backup/${id}`);
};
export const UpdateWebsite = (req: WebSite.WebSiteUpdateReq) => {
return http.post<any>(`/websites/update`, req);
};

View File

@ -2,7 +2,7 @@
<el-card v-loading="loading">
<el-row :gutter="5">
<el-col :span="2">
<el-button @click="sync" type="primary" plain="true">{{ $t('app.sync') }}</el-button>
<el-button @click="sync" type="primary" :plain="true">{{ $t('app.sync') }}</el-button>
</el-col>
<el-col :span="22">
<div style="float: right">

View File

@ -4,7 +4,7 @@
<template #toolbar>
<el-row>
<el-col :span="18">
<el-button @click="sync" type="primary" plain="true">{{ $t('app.sync') }}</el-button>
<el-button @click="sync" type="primary" :plain="true">{{ $t('app.sync') }}</el-button>
</el-col>
<el-col :span="6">
<div style="float: right">

View File

@ -36,10 +36,10 @@ import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue';
import { dateFromat } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
import { backup, recover, searchBackupRecords } from '@/api/modules/database';
import { backup, recover } from '@/api/modules/database';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import { deleteBackupRecord, downloadBackupRecord } from '@/api/modules/backup';
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/backup';
import { Backup } from '@/api/interface/backup';
const selects = ref<any>([]);
@ -69,8 +69,9 @@ const search = async () => {
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
mysqlName: mysqlName.value,
dbName: dbName.value,
type: 'database-mysql',
name: mysqlName.value,
detailName: dbName.value,
};
const res = await searchBackupRecords(params);
data.value = res.data.items || [];

View File

@ -0,0 +1,144 @@
<template>
<div>
<el-dialog v-model="backupVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="50%">
<template #header>
<div class="card-header">
<span>{{ $t('database.backup') }} - {{ websiteName }}</span>
</div>
</template>
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" @search="search" :data="data">
<template #toolbar>
<el-button type="primary" @click="onBackup()">
{{ $t('database.backup') }}
</el-button>
<el-button type="danger" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
<el-table-column type="selection" fix />
<el-table-column :label="$t('commons.table.name')" prop="fileName" show-overflow-tooltip />
<el-table-column :label="$t('database.source')" prop="backupType" />
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
</ComplexTable>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue';
import { dateFromat } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/backup';
import { Backup } from '@/api/interface/backup';
import { BackupWebsite } from '@/api/modules/website';
const selects = ref<any>([]);
const data = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const backupVisiable = ref(false);
const websiteName = ref();
const websiteID = ref();
const websiteType = ref();
interface DialogProps {
id: string;
type: string;
name: string;
}
const acceptParams = (params: DialogProps): void => {
websiteName.value = params.name;
websiteID.value = params.id;
websiteType.value = params.type;
backupVisiable.value = true;
search();
};
const search = async () => {
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
type: 'website-' + websiteType.value,
name: websiteName.value,
detailName: '',
};
const res = await searchBackupRecords(params);
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
};
const onBackup = async () => {
await BackupWebsite(websiteID.value);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
search();
};
const onDownload = async (row: Backup.RecordInfo) => {
let params = {
source: row.source,
fileDir: row.fileDir,
fileName: row.fileName,
};
const res = await downloadBackupRecord(params);
const downloadUrl = window.URL.createObjectURL(new Blob([res]));
const a = document.createElement('a');
a.style.display = 'none';
a.href = downloadUrl;
a.download = row.fileName;
const event = new MouseEvent('click');
a.dispatchEvent(event);
};
const onBatchDelete = async (row: Backup.RecordInfo | null) => {
let ids: Array<number> = [];
if (row) {
ids.push(row.id);
} else {
selects.value.forEach((item: Backup.RecordInfo) => {
ids.push(item.id);
});
}
await useDeleteData(deleteBackupRecord, { ids: ids }, 'commons.msg.delete', true);
search();
};
const buttons = [
{
label: i18n.global.t('commons.button.delete'),
click: (row: Backup.RecordInfo) => {
onBatchDelete(row);
},
},
// {
// label: i18n.global.t('commons.button.recover'),
// click: (row: Backup.RecordInfo) => {
// onRecover(row);
// },
// },
{
label: i18n.global.t('commons.button.download'),
click: (row: Backup.RecordInfo) => {
onDownload(row);
},
},
];
defineExpose({
acceptParams,
});
</script>

View File

@ -57,7 +57,7 @@
<script lang="ts" setup>
import LayoutContent from '@/layout/layout-content.vue';
import BackupRecords from '@/views/database/mysql/backup/index.vue';
import BackupRecords from '@/views/website/website/backup/index.vue';
import UploadDialog from '@/views/database/mysql/upload/index.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { onMounted, reactive, ref } from '@vue/runtime-core';
@ -110,13 +110,7 @@ const openConfig = (id: number) => {
const uploadRef = ref();
const dialogBackupRef = ref();
const onOpenBackupDialog = async (dbName: string) => {
let params = {
mysqlName: 'test',
dbName: dbName,
};
dialogBackupRef.value!.acceptParams(params);
};
const buttons = [
{
label: i18n.global.t('website.config'),
@ -127,7 +121,12 @@ const buttons = [
{
label: i18n.global.t('database.backupList'),
click: (row: WebSite.WebSite) => {
onOpenBackupDialog(row.primaryDomain);
let params = {
id: row.id,
type: row.type,
name: row.primaryDomain,
};
dialogBackupRef.value!.acceptParams(params);
},
},
{