mirror of
https://gitee.com/fit2cloud-feizhiyun/1Panel.git
synced 2024-12-05 05:19:08 +08:00
fix: 统一备份接口和前端组件封装
This commit is contained in:
parent
f7749af999
commit
aa2bb73199
@ -237,3 +237,128 @@ func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
|
|||||||
|
|
||||||
helper.SuccessWithData(c, data)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Backup system data
|
||||||
|
// @Description 备份系统数据
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.CommonBackup true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /settings/backup/ [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["type","name","detailName"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"备份 [type] 数据 [name][detailName]","formatEN":"backup [type] data [name][detailName]"}
|
||||||
|
func (b *BaseApi) Backup(c *gin.Context) {
|
||||||
|
var req dto.CommonBackup
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.Type {
|
||||||
|
case "app":
|
||||||
|
if err := backupService.AppBackup(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "mysql":
|
||||||
|
if err := backupService.MysqlBackup(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "website":
|
||||||
|
if err := backupService.WebsiteBackup(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "redis":
|
||||||
|
if err := backupService.RedisBackup(); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Recover system data
|
||||||
|
// @Description 恢复系统数据
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.CommonRecover true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /settings/backup/recover [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"}
|
||||||
|
func (b *BaseApi) Recover(c *gin.Context) {
|
||||||
|
var req dto.CommonRecover
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.Type {
|
||||||
|
case "mysql":
|
||||||
|
if err := backupService.MysqlRecover(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "website":
|
||||||
|
if err := backupService.WebsiteRecover(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Recover system data by upload
|
||||||
|
// @Description 从上传恢复系统数据
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.CommonRecover true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /settings/backup/recover/byupload [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"}
|
||||||
|
func (b *BaseApi) RecoverByUpload(c *gin.Context) {
|
||||||
|
var req dto.CommonRecover
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.Type {
|
||||||
|
case "app":
|
||||||
|
if err := backupService.AppRecover(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "mysql":
|
||||||
|
if err := backupService.MysqlRecoverByUpload(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "website":
|
||||||
|
if err := backupService.WebsiteRecover(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "redis":
|
||||||
|
if err := backupService.RedisRecover(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
@ -210,90 +210,6 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Mysql
|
|
||||||
// @Summary Backup mysql database
|
|
||||||
// @Description 备份 mysql 数据库
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.BackupDB true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /databases/backup [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["mysqlName","dbName"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"备份 mysql 数据库 [mysqlName][dbName]","formatEN":"backup mysql database [mysqlName][dbName]"}
|
|
||||||
func (b *BaseApi) BackupMysql(c *gin.Context) {
|
|
||||||
var req dto.BackupDB
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := global.VALID.Struct(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mysqlService.Backup(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.SuccessWithData(c, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Database Mysql
|
|
||||||
// @Summary Recover mysql database by upload file
|
|
||||||
// @Description Mysql 数据库从上传文件恢复
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.UploadRecover true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /databases/recover/byupload [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["fileDir","fileName","mysqlName","dbName"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"mysql 数据库从 [fileDir]/[fileName] 恢复 [mysqlName][dbName]","formatEN":"mysql database recover [fileDir]/[fileName] from [mysqlName][dbName]"}
|
|
||||||
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 := global.VALID.Struct(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Database Mysql
|
|
||||||
// @Summary Recover mysql database
|
|
||||||
// @Description Mysql 数据库恢复
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.RecoverDB true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /databases/recover [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["mysqlName","dbName","backupName"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"恢复 mysql 数据库 [mysqlName][dbName] [backupName]","formatEN":"恢复 mysql 数据库 [mysqlName][dbName] [backupName]"}
|
|
||||||
func (b *BaseApi) RecoverMysql(c *gin.Context) {
|
|
||||||
var req dto.RecoverDB
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := global.VALID.Struct(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mysqlService.Recover(req); 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 数据库删除前检查
|
||||||
|
@ -139,46 +139,6 @@ func (b *BaseApi) UpdateRedisPersistenceConf(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Redis
|
|
||||||
// @Summary Backup redis
|
|
||||||
// @Description 备份 redis 数据库
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /databases/redis/backup [post]
|
|
||||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"备份 redis 数据库","formatEN":"backup redis database"}
|
|
||||||
func (b *BaseApi) RedisBackup(c *gin.Context) {
|
|
||||||
if err := redisService.Backup(); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Database Redis
|
|
||||||
// @Summary Recover redis
|
|
||||||
// @Description 恢复 redis 数据库
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /databases/redis/recover [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["fileDir","fileName"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"redis 数据库从 [fileDir]/[fileName] 恢复","formatEN":"redis database recover from [fileDir]/[fileName]"}
|
|
||||||
func (b *BaseApi) RedisRecover(c *gin.Context) {
|
|
||||||
var req dto.RedisBackupRecover
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := global.VALID.Struct(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := redisService.Recover(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
// @Summary Page redis backups
|
// @Summary Page redis backups
|
||||||
// @Description 获取 redis 备份记录分页
|
// @Description 获取 redis 备份记录分页
|
||||||
|
@ -272,12 +272,10 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
|||||||
dir := path.Dir(paths[0])
|
dir := path.Dir(paths[0])
|
||||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
if err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("mkdir %s failed, err: %v", dir, err))
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("mkdir %s failed, err: %v", dir, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
success := 0
|
success := 0
|
||||||
failures := make(buserr.MultiErr)
|
failures := make(buserr.MultiErr)
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -113,82 +112,6 @@ func (b *BaseApi) OpWebsite(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Website
|
|
||||||
// @Summary Backup website
|
|
||||||
// @Description 备份网站
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteResourceReq true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /websites/backup [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"备份网站 [domain]","formatEN":"Backup website [domain]"}
|
|
||||||
func (b *BaseApi) BackupWebsite(c *gin.Context) {
|
|
||||||
var req request.WebsiteResourceReq
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := websiteService.Backup(req.ID); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website
|
|
||||||
// @Summary Recover website by upload
|
|
||||||
// @Description 从上传恢复网站
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteRecoverByFile true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /websites/recover/byupload [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["websiteName","fileDir","fileName"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[websiteName] 从上传恢复 [fileDir]/[fileName]","formatEN":"[websiteName] recover from uploads [fileDir]/[fileName]"}
|
|
||||||
func (b *BaseApi) RecoverWebsiteByUpload(c *gin.Context) {
|
|
||||||
var req request.WebsiteRecoverByFile
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := global.VALID.Struct(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := websiteService.RecoverByUpload(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website
|
|
||||||
// @Summary Recover website
|
|
||||||
// @Description 从备份恢复网站
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body request.WebsiteRecover true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /websites/recover [post]
|
|
||||||
// @x-panel-log {"bodyKeys":["websiteName","backupName"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[websiteName] 从备份恢复 [backupName]","formatEN":"[websiteName] recover from backups [backupName]"}
|
|
||||||
func (b *BaseApi) RecoverWebsite(c *gin.Context) {
|
|
||||||
var req request.WebsiteRecover
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := global.VALID.Struct(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := websiteService.Recover(req); err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Website
|
// @Tags Website
|
||||||
// @Summary Delete website
|
// @Summary Delete website
|
||||||
// @Description 删除网站
|
// @Description 删除网站
|
||||||
|
@ -30,6 +30,18 @@ type BackupSearchFile struct {
|
|||||||
Type string `json:"type" validate:"required"`
|
Type string `json:"type" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommonBackup struct {
|
||||||
|
Type string `json:"type" validate:"required,oneof=app mysql redis website"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DetailName string `json:"detailName"`
|
||||||
|
}
|
||||||
|
type CommonRecover struct {
|
||||||
|
Type string `json:"type" validate:"required,oneof=app mysql redis website"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DetailName string `json:"detailName"`
|
||||||
|
File string `json:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
type RecordSearch struct {
|
type RecordSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
Type string `json:"type" validate:"required"`
|
Type string `json:"type" validate:"required"`
|
||||||
|
@ -2,6 +2,7 @@ package repo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
@ -187,16 +187,6 @@ func (a AppInstallService) Operate(req request.AppInstalledOperate) error {
|
|||||||
return nil
|
return nil
|
||||||
case constant.Sync:
|
case constant.Sync:
|
||||||
return syncById(install.ID)
|
return syncById(install.ID)
|
||||||
case constant.Backup:
|
|
||||||
tx, ctx := getTxAndContext()
|
|
||||||
if err := backupInstall(ctx, install); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tx.Commit()
|
|
||||||
return nil
|
|
||||||
case constant.Restore:
|
|
||||||
return restoreInstall(install, req.BackupId)
|
|
||||||
case constant.Update:
|
case constant.Update:
|
||||||
return updateInstall(install.ID, req.DetailId)
|
return updateInstall(install.ID, req.DetailId)
|
||||||
default:
|
default:
|
||||||
|
@ -3,14 +3,12 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||||
@ -24,7 +22,6 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DatabaseOp string
|
type DatabaseOp string
|
||||||
@ -177,11 +174,9 @@ func updateInstall(installId uint, detailId uint) error {
|
|||||||
if install.Version == detail.Version {
|
if install.Version == detail.Version {
|
||||||
return errors.New("two version is same")
|
return errors.New("two version is same")
|
||||||
}
|
}
|
||||||
tx, ctx := getTxAndContext()
|
if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
|
||||||
if err := backupInstall(ctx, install); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tx.Commit()
|
|
||||||
if _, err = compose.Down(install.GetComposePath()); err != nil {
|
if _, err = compose.Down(install.GetComposePath()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -198,118 +193,6 @@ func updateInstall(installId uint, detailId uint) error {
|
|||||||
return appInstallRepo.Save(&install)
|
return appInstallRepo.Save(&install)
|
||||||
}
|
}
|
||||||
|
|
||||||
func backupInstall(ctx context.Context, install model.AppInstall) error {
|
|
||||||
var backup model.AppInstallBackup
|
|
||||||
appPath := install.GetPath()
|
|
||||||
|
|
||||||
backupAccount, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
varMap := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal([]byte(backupAccount.Vars), &varMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dir, ok := varMap["dir"]
|
|
||||||
if !ok {
|
|
||||||
return errors.New("load local backup dir failed")
|
|
||||||
}
|
|
||||||
baseDir, ok := dir.(string)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("load local backup dir failed")
|
|
||||||
}
|
|
||||||
backupDir := path.Join(baseDir, "apps", install.App.Key, install.Name)
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
if !fileOp.Stat(backupDir) {
|
|
||||||
_ = fileOp.CreateDir(backupDir, 0775)
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
day := now.Format("20060102150405")
|
|
||||||
fileName := fmt.Sprintf("%s_%s%s", install.Name, day, ".tar.gz")
|
|
||||||
if err := fileOp.Compress([]string{appPath}, backupDir, fileName, files.TarGz); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
backup.Name = fileName
|
|
||||||
backup.Path = backupDir
|
|
||||||
backup.AppInstallId = install.ID
|
|
||||||
backup.AppDetailId = install.AppDetailId
|
|
||||||
backup.Param = install.Param
|
|
||||||
|
|
||||||
return appInstallBackupRepo.Create(ctx, backup)
|
|
||||||
}
|
|
||||||
|
|
||||||
func restoreInstall(install model.AppInstall, backupId uint) error {
|
|
||||||
backup, err := appInstallBackupRepo.GetFirst(commonRepo.WithByID(backupId))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := compose.Down(install.GetComposePath()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
installKeyDir := path.Join(constant.AppInstallDir, install.App.Key)
|
|
||||||
installDir := path.Join(installKeyDir, install.Name)
|
|
||||||
backupFile := path.Join(backup.Path, backup.Name)
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
if !fileOp.Stat(backupFile) {
|
|
||||||
return errors.New(fmt.Sprintf("%s file is not exist", backup.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
backupDir, err := fileOp.Backup(installDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fileOp.Decompress(backupFile, installKeyDir, files.TarGz); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
composeContent, err := os.ReadFile(install.GetComposePath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
install.DockerCompose = string(composeContent)
|
|
||||||
envContent, err := os.ReadFile(path.Join(installDir, ".env"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
install.Env = string(envContent)
|
|
||||||
envMaps, err := godotenv.Unmarshal(string(envContent))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
install.HttpPort = 0
|
|
||||||
httpPort, ok := envMaps["PANEL_APP_PORT_HTTP"]
|
|
||||||
if ok {
|
|
||||||
httpPortN, _ := strconv.Atoi(httpPort)
|
|
||||||
install.HttpPort = httpPortN
|
|
||||||
}
|
|
||||||
install.HttpsPort = 0
|
|
||||||
httpsPort, ok := envMaps["PANEL_APP_PORT_HTTPS"]
|
|
||||||
if ok {
|
|
||||||
httpsPortN, _ := strconv.Atoi(httpsPort)
|
|
||||||
install.HttpsPort = httpsPortN
|
|
||||||
}
|
|
||||||
|
|
||||||
composeMap := make(map[string]interface{})
|
|
||||||
if err := yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
servicesMap := composeMap["services"].(map[string]interface{})
|
|
||||||
for k, v := range servicesMap {
|
|
||||||
install.ServiceName = k
|
|
||||||
value := v.(map[string]interface{})
|
|
||||||
install.ContainerName = value["container_name"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
install.Param = backup.Param
|
|
||||||
_ = fileOp.DeleteDir(backupDir)
|
|
||||||
if out, err := compose.Up(install.GetComposePath()); err != nil {
|
|
||||||
return handleErr(install, err, out)
|
|
||||||
}
|
|
||||||
install.AppDetailId = backup.AppDetailId
|
|
||||||
install.Version = backup.AppDetail.Version
|
|
||||||
install.Status = constant.Running
|
|
||||||
return appInstallRepo.Save(&install)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getContainerNames(install model.AppInstall) ([]string, error) {
|
func getContainerNames(install model.AppInstall) ([]string, error) {
|
||||||
composeMap := install.DockerCompose
|
composeMap := install.DockerCompose
|
||||||
envMap := make(map[string]string)
|
envMap := make(map[string]string)
|
||||||
|
@ -31,6 +31,20 @@ type IBackupService interface {
|
|||||||
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
||||||
|
|
||||||
ListFiles(req dto.BackupSearchFile) ([]interface{}, error)
|
ListFiles(req dto.BackupSearchFile) ([]interface{}, error)
|
||||||
|
|
||||||
|
MysqlBackup(db dto.CommonBackup) error
|
||||||
|
MysqlRecover(db dto.CommonRecover) error
|
||||||
|
MysqlRecoverByUpload(req dto.CommonRecover) error
|
||||||
|
|
||||||
|
RedisBackup() error
|
||||||
|
RedisRecover(db dto.CommonRecover) error
|
||||||
|
|
||||||
|
WebsiteBackup(db dto.CommonBackup) error
|
||||||
|
WebsiteRecover(req dto.CommonRecover) error
|
||||||
|
WebsiteRecoverByUpload(req dto.CommonRecover) error
|
||||||
|
|
||||||
|
AppBackup(db dto.CommonBackup) error
|
||||||
|
AppRecover(req dto.CommonRecover) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIBackupService() IBackupService {
|
func NewIBackupService() IBackupService {
|
||||||
@ -285,11 +299,9 @@ func loadLocalDir() (string, error) {
|
|||||||
if ok {
|
if ok {
|
||||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
||||||
if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
|
if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("mkdir %s failed, err: %v", baseDir, err)
|
return "", fmt.Errorf("mkdir %s failed, err: %v", baseDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return baseDir, nil
|
return baseDir, nil
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("error type dir: %T", varMap["dir"])
|
return "", fmt.Errorf("error type dir: %T", varMap["dir"])
|
||||||
|
199
backend/app/service/backup_app.go
Normal file
199
backend/app/service/backup_app.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"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/compose"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *BackupService) AppBackup(req dto.CommonBackup) error {
|
||||||
|
localDir, err := loadLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app, err := appRepo.GetFirst(appRepo.WithKey(req.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
install, err := appInstallRepo.GetFirst(commonRepo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timeNow := time.Now().Format("20060102150405")
|
||||||
|
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName)
|
||||||
|
|
||||||
|
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
|
||||||
|
if err := handleAppBackup(&install, backupDir, fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
record := &model.BackupRecord{
|
||||||
|
Type: "app",
|
||||||
|
Name: req.Name,
|
||||||
|
DetailName: req.DetailName,
|
||||||
|
Source: "LOCAL",
|
||||||
|
BackupType: "LOCAL",
|
||||||
|
FileDir: backupDir,
|
||||||
|
FileName: fileName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := backupRepo.CreateRecord(record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) AppRecover(req dto.CommonRecover) error {
|
||||||
|
app, err := appRepo.GetFirst(appRepo.WithKey(req.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
install, err := appInstallRepo.GetFirst(commonRepo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if !fileOp.Stat(req.File) {
|
||||||
|
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||||
|
}
|
||||||
|
if _, err := compose.Down(install.GetComposePath()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := handleAppRecover(&install, req.File); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppInfo struct {
|
||||||
|
AppDetailId uint `json:"appDetailId"`
|
||||||
|
Param string `json:"param"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAppBackup(install *model.AppInstall, backupDir, fileName string) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
||||||
|
if !fileOp.Stat(tmpDir) {
|
||||||
|
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(tmpDir)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var appInfo AppInfo
|
||||||
|
appInfo.Param = install.Param
|
||||||
|
appInfo.AppDetailId = install.AppDetailId
|
||||||
|
appInfo.Version = install.Version
|
||||||
|
remarkInfo, _ := json.Marshal(appInfo)
|
||||||
|
remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir)
|
||||||
|
if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
appPath := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, install.App.Key, install.Name)
|
||||||
|
if err := fileOp.Compress([]string{appPath}, tmpDir, "app.tar.gz", files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fileOp.Compress([]string{tmpDir}, backupDir, fileName, files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAppRecover(install *model.AppInstall, recoverFile string) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if err := fileOp.Decompress(recoverFile, path.Dir(recoverFile), files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !fileOp.Stat(tmpPath+"/app.json") || !fileOp.Stat(tmpPath+"/app.tar.gz") {
|
||||||
|
return errors.New("the wrong recovery package does not have app.json or app.tar.gz files")
|
||||||
|
}
|
||||||
|
appjson, err := os.ReadFile(tmpPath + "/" + "app.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var appInfo AppInfo
|
||||||
|
_ = json.Unmarshal(appjson, &appInfo)
|
||||||
|
|
||||||
|
if err := fileOp.Decompress(tmpPath+"/app.tar.gz", fmt.Sprintf("%s/%s", constant.AppInstallDir, install.App.Key), files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
composeContent, err := os.ReadFile(install.GetComposePath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
install.DockerCompose = string(composeContent)
|
||||||
|
envContent, err := os.ReadFile(fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
install.Env = string(envContent)
|
||||||
|
envMaps, err := godotenv.Unmarshal(string(envContent))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
install.HttpPort = 0
|
||||||
|
httpPort, ok := envMaps["PANEL_APP_PORT_HTTP"]
|
||||||
|
if ok {
|
||||||
|
httpPortN, _ := strconv.Atoi(httpPort)
|
||||||
|
install.HttpPort = httpPortN
|
||||||
|
}
|
||||||
|
install.HttpsPort = 0
|
||||||
|
httpsPort, ok := envMaps["PANEL_APP_PORT_HTTPS"]
|
||||||
|
if ok {
|
||||||
|
httpsPortN, _ := strconv.Atoi(httpsPort)
|
||||||
|
install.HttpsPort = httpsPortN
|
||||||
|
}
|
||||||
|
|
||||||
|
composeMap := make(map[string]interface{})
|
||||||
|
if err := yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
servicesMap := composeMap["services"].(map[string]interface{})
|
||||||
|
for k, v := range servicesMap {
|
||||||
|
install.ServiceName = k
|
||||||
|
value := v.(map[string]interface{})
|
||||||
|
install.ContainerName = value["container_name"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
install.Param = appInfo.Param
|
||||||
|
if out, err := compose.Up(install.GetComposePath()); err != nil {
|
||||||
|
install.Message = err.Error()
|
||||||
|
if len(out) != 0 {
|
||||||
|
install.Message = out
|
||||||
|
}
|
||||||
|
return errors.New(out)
|
||||||
|
}
|
||||||
|
install.AppDetailId = appInfo.AppDetailId
|
||||||
|
install.Version = appInfo.Version
|
||||||
|
install.Status = constant.Running
|
||||||
|
if err := appInstallRepo.Save(install); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
170
backend/app/service/backup_database.go
Normal file
170
backend/app/service/backup_database.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||||
|
localDir, err := loadLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeNow := time.Now().Format("20060102150405")
|
||||||
|
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, req.Name, req.DetailName)
|
||||||
|
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
||||||
|
if err := handleMysqlBackup(app, backupDir, req.DetailName, fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
record := &model.BackupRecord{
|
||||||
|
Type: "mysql",
|
||||||
|
Name: app.Name,
|
||||||
|
DetailName: req.DetailName,
|
||||||
|
Source: "LOCAL",
|
||||||
|
BackupType: "LOCAL",
|
||||||
|
FileDir: backupDir,
|
||||||
|
FileName: fileName,
|
||||||
|
}
|
||||||
|
if err := backupRepo.CreateRecord(record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
|
||||||
|
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if !fileOp.Stat(req.File) {
|
||||||
|
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||||
|
}
|
||||||
|
global.LOG.Infof("recover database %s-%s from backup file %s", req.Name, req.DetailName, req.File)
|
||||||
|
if err := handleMysqlRecover(app, path.Dir(req.File), req.DetailName, path.Base(req.File)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||||
|
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file := req.File
|
||||||
|
fileName := path.Base(req.File)
|
||||||
|
if !strings.HasSuffix(fileName, ".sql") && !strings.HasSuffix(fileName, ".gz") {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
fileNameItem := time.Now().Format("20060102150405")
|
||||||
|
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
|
||||||
|
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var compressType files.CompressType
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(fileName, ".tar.gz"), strings.HasSuffix(fileName, ".tgz"):
|
||||||
|
compressType = files.TarGz
|
||||||
|
case strings.HasSuffix(fileName, ".zip"):
|
||||||
|
compressType = files.Zip
|
||||||
|
}
|
||||||
|
if err := fileOp.Decompress(req.File, dstDir, compressType); err != nil {
|
||||||
|
_ = os.RemoveAll(dstDir)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
|
||||||
|
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
|
||||||
|
fileName = "test.sql"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if !hasTestSql {
|
||||||
|
_ = os.RemoveAll(dstDir)
|
||||||
|
return fmt.Errorf("no such file named test.sql in %s, err: %v", fileName, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(dstDir)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handleMysqlRecover(app, path.Dir(file), req.DetailName, fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Info("recover from uploads successful!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMysqlBackup(app *repo.RootInfo, backupDir, dbName, fileName string) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if !fileOp.Stat(backupDir) {
|
||||||
|
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outfile, _ := os.OpenFile(backupDir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0755)
|
||||||
|
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", backupDir+"/"+fileName)
|
||||||
|
cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, dbName)
|
||||||
|
gzipCmd := exec.Command("gzip", "-cf")
|
||||||
|
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
||||||
|
gzipCmd.Stdout = outfile
|
||||||
|
_ = gzipCmd.Start()
|
||||||
|
_ = cmd.Run()
|
||||||
|
_ = gzipCmd.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMysqlRecover(mysqlInfo *repo.RootInfo, recoverDir, dbName, fileName string) error {
|
||||||
|
file := recoverDir + "/" + fileName
|
||||||
|
fi, _ := os.Open(file)
|
||||||
|
defer fi.Close()
|
||||||
|
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, dbName)
|
||||||
|
if strings.HasSuffix(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
|
||||||
|
}
|
132
backend/app/service/backup_redis.go
Normal file
132
backend/app/service/backup_redis.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *BackupService) RedisBackup() error {
|
||||||
|
localDir, err := loadLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
|
||||||
|
|
||||||
|
timeNow := time.Now().Format("20060102150405")
|
||||||
|
fileName := fmt.Sprintf("%s.rdb", timeNow)
|
||||||
|
if appendonly == "yes" {
|
||||||
|
fileName = fmt.Sprintf("%s.tar.gz", timeNow)
|
||||||
|
}
|
||||||
|
backupDir := fmt.Sprintf("%s/database/redis/%s/", localDir, redisInfo.Name)
|
||||||
|
if err := handleBackupRedis(redisInfo, backupDir, fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
record := &model.BackupRecord{
|
||||||
|
Type: "redis",
|
||||||
|
Source: "LOCAL",
|
||||||
|
BackupType: "LOCAL",
|
||||||
|
FileDir: backupDir,
|
||||||
|
FileName: fileName,
|
||||||
|
}
|
||||||
|
if err := backupRepo.CreateRecord(record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
|
||||||
|
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Infof("recover redis from backup file %s", req.File)
|
||||||
|
if err := handleRecoverRedis(redisInfo, req.File); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBackupRedis(redisInfo *repo.RootInfo, backupDir, fileName string) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if !fileOp.Stat(backupDir) {
|
||||||
|
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout, err := cmd.Execf("docker exec %s redis-cli -a %s --no-auth-warning save", redisInfo.ContainerName, redisInfo.Password)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(string(stdout))
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||||
|
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", constant.AppInstallDir, "redis", redisInfo.Name)
|
||||||
|
if err := handleTar(redisDataDir, backupDir, fileName, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout1, err1 := cmd.Execf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName)
|
||||||
|
if err1 != nil {
|
||||||
|
return errors.New(string(stdout1))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRecoverRedis(redisInfo *repo.RootInfo, recoverFile string) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if !fileOp.Stat(recoverFile) {
|
||||||
|
return errors.New(fmt.Sprintf("%s file is not exist", recoverFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
|
||||||
|
composeDir := fmt.Sprintf("%s/redis/%s", constant.AppInstallDir, redisInfo.Name)
|
||||||
|
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if appendonly == "yes" {
|
||||||
|
redisDataDir := fmt.Sprintf("%s/%s/%s/data/", constant.AppInstallDir, "redis", redisInfo.Name)
|
||||||
|
if err := handleUnTar(recoverFile, redisDataDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input, err := ioutil.ReadFile(recoverFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ioutil.WriteFile(composeDir+"/data/dump.rdb", input, 0640); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := compose.Up(composeDir + "/docker-compose.yml"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
238
backend/app/service/backup_website.go
Normal file
238
backend/app/service/backup_website.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"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/cmd"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||||
|
localDir, err := loadLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeNow := time.Now().Format("20060102150405")
|
||||||
|
backupDir := fmt.Sprintf("%s/website/%s", localDir, req.Name)
|
||||||
|
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow)
|
||||||
|
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
record := &model.BackupRecord{
|
||||||
|
Type: "website",
|
||||||
|
Name: website.PrimaryDomain,
|
||||||
|
DetailName: "",
|
||||||
|
Source: "LOCAL",
|
||||||
|
BackupType: "LOCAL",
|
||||||
|
FileDir: backupDir,
|
||||||
|
FileName: fileName,
|
||||||
|
}
|
||||||
|
if err := backupRepo.CreateRecord(record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) WebsiteRecoverByUpload(req dto.CommonRecover) error {
|
||||||
|
if err := handleUnTar(req.File, path.Dir(req.File)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmpDir := strings.ReplaceAll(req.File, ".tar.gz", "")
|
||||||
|
webJson, err := os.ReadFile(fmt.Sprintf("%s/website.json", tmpDir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var websiteInfo WebsiteInfo
|
||||||
|
if err := json.Unmarshal(webJson, &websiteInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if websiteInfo.WebsiteName != req.Name {
|
||||||
|
return errors.New("the uploaded file does not match the selected website and cannot be recovered")
|
||||||
|
}
|
||||||
|
|
||||||
|
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := handleWebsiteRecover(&website, tmpDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
|
||||||
|
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if !fileOp.Stat(req.File) {
|
||||||
|
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||||
|
}
|
||||||
|
global.LOG.Infof("recover website %s from backup file %s", req.Name, req.File)
|
||||||
|
if err := handleWebsiteRecover(&website, req.File); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWebsiteRecover(website *model.Website, recoverFile string) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
fileDir := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||||
|
if err := fileOp.Decompress(recoverFile, path.Dir(recoverFile), files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(fileDir)
|
||||||
|
}()
|
||||||
|
|
||||||
|
itemDir := fmt.Sprintf("%s/%s", fileDir, website.Alias)
|
||||||
|
if !fileOp.Stat(itemDir+".conf") || !fileOp.Stat(itemDir+".web.tar.gz") {
|
||||||
|
return errors.New("the wrong recovery package does not have .conf or .web.tar.gz files")
|
||||||
|
}
|
||||||
|
if website.Type == constant.Deployment {
|
||||||
|
if !fileOp.Stat(itemDir+".sql.gz") || !fileOp.Stat(itemDir+".app.tar.gz") {
|
||||||
|
return errors.New("the wrong recovery package does not have .sql.gz or .app.tar.gz files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nginxConfPath := fmt.Sprintf("%s/openresty/%s/conf/conf.d", constant.AppInstallDir, nginxInfo.Name)
|
||||||
|
if err := fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", fileDir, website.Alias), nginxConfPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if website.Type == constant.Deployment {
|
||||||
|
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := handleMysqlRecover(mysqlInfo, fileDir, db.Name, fmt.Sprintf("%s.sql.gz", website.Alias)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", fileDir, website.Alias)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, app.App.Key, app.Name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
siteDir := fmt.Sprintf("%s/openresty/%s/www/sites", constant.AppInstallDir, nginxInfo.Name)
|
||||||
|
if err := fileOp.Decompress(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.Alias), siteDir, files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(string(stdout))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsiteInfo struct {
|
||||||
|
WebsiteName string `json:"websiteName"`
|
||||||
|
WebsiteType string `json:"websiteType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWebsiteBackup(website *model.Website, backupDir, fileName string) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
||||||
|
if !fileOp.Stat(tmpDir) {
|
||||||
|
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(tmpDir)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var websiteInfo WebsiteInfo
|
||||||
|
websiteInfo.WebsiteType = website.Type
|
||||||
|
websiteInfo.WebsiteName = website.PrimaryDomain
|
||||||
|
remarkInfo, _ := json.Marshal(websiteInfo)
|
||||||
|
if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Info("put websitejson into tmp dir successful")
|
||||||
|
|
||||||
|
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nginxConfFile := fmt.Sprintf("%s/openresty/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.Alias)
|
||||||
|
if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Info("put openresty conf into tmp dir successful")
|
||||||
|
|
||||||
|
if website.Type == constant.Deployment {
|
||||||
|
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := handleMysqlBackup(mysqlInfo, tmpDir, db.Name, fmt.Sprintf("%s.sql.gz", website.Alias)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := handleAppBackup(&app, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Info("put app tar into tmp dir successful")
|
||||||
|
}
|
||||||
|
websiteDir := fmt.Sprintf("%s/openresty/%s/www/sites/%s", constant.AppInstallDir, nginxInfo.Name, website.Alias)
|
||||||
|
if err := fileOp.Compress([]string{websiteDir}, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
global.LOG.Info("put website tar into tmp dir successful, now start to tar tmp dir")
|
||||||
|
if err := fileOp.Compress([]string{tmpDir}, backupDir, fileName, files.TarGz); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -70,25 +70,19 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
|||||||
|
|
||||||
func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
|
func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
|
||||||
var (
|
var (
|
||||||
baseDir string
|
|
||||||
backupDir string
|
backupDir string
|
||||||
fileName string
|
fileName string
|
||||||
|
record model.BackupRecord
|
||||||
)
|
)
|
||||||
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
global.LOG.Infof("start to backup %s %s to %s", cronjob.Type, cronjob.Name, backup.Type)
|
|
||||||
if cronjob.KeepLocal || cronjob.Type != "LOCAL" {
|
|
||||||
localDir, err := loadLocalDir()
|
localDir, err := loadLocalDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
baseDir = localDir
|
global.LOG.Infof("start to backup %s %s to %s", cronjob.Type, cronjob.Name, backup.Type)
|
||||||
} else {
|
|
||||||
baseDir = global.CONF.System.TmpDir
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cronjob.Type {
|
switch cronjob.Type {
|
||||||
case "database":
|
case "database":
|
||||||
@ -97,44 +91,68 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fileName = fmt.Sprintf("db_%s_%s.sql.gz", cronjob.DBName, startTime.Format("20060102150405"))
|
fileName = fmt.Sprintf("db_%s_%s.sql.gz", cronjob.DBName, startTime.Format("20060102150405"))
|
||||||
backupDir = fmt.Sprintf("database/mysql/%s/%s", app.Name, cronjob.DBName)
|
backupDir = fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, cronjob.DBName)
|
||||||
if err = backupMysql(backup.Type, baseDir, backupDir, app.Name, cronjob.DBName, fileName); err != nil {
|
if err = handleMysqlBackup(app, backupDir, cronjob.DBName, fileName); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
record.Type = "mysql"
|
||||||
|
record.Name = "mysql"
|
||||||
|
record.DetailName = app.Name
|
||||||
case "website":
|
case "website":
|
||||||
fileName = fmt.Sprintf("website_%s_%s", cronjob.Website, startTime.Format("20060102150405"))
|
fileName = fmt.Sprintf("website_%s_%s.tar.gz", cronjob.Website, startTime.Format("20060102150405"))
|
||||||
backupDir = fmt.Sprintf("website/%s", cronjob.Website)
|
backupDir = fmt.Sprintf("%s/website/%s", localDir, cronjob.Website)
|
||||||
if err := handleWebsiteBackup(backup.Type, baseDir, backupDir, cronjob.Website, fileName); err != nil {
|
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(cronjob.Website))
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fileName = fileName + ".tar.gz"
|
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
record.Type = "website"
|
||||||
|
record.Name = website.PrimaryDomain
|
||||||
default:
|
default:
|
||||||
fileName = fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
fileName = fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
||||||
backupDir = fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
|
backupDir = fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
|
||||||
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
||||||
if err := handleTar(cronjob.SourceDir, baseDir+"/"+backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
if err := handleTar(cronjob.SourceDir, localDir+"/"+backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(record.Name) != 0 {
|
||||||
|
record.FileName = fileName
|
||||||
|
record.FileDir = backupDir
|
||||||
|
record.Source = "LOCAL"
|
||||||
|
record.BackupType = backup.Type
|
||||||
|
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||||
|
record.Source = backup.Type
|
||||||
|
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||||
|
}
|
||||||
|
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fullPath := fmt.Sprintf("%s/%s", record.FileDir, fileName)
|
||||||
if backup.Type == "LOCAL" {
|
if backup.Type == "LOCAL" {
|
||||||
u.HandleRmExpired(backup.Type, baseDir, backupDir, cronjob, nil)
|
u.HandleRmExpired(backup.Type, record.FileDir, cronjob, nil)
|
||||||
return baseDir + "/" + backupDir + "/" + fileName, nil
|
return fullPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudFile := baseDir + "/" + backupDir + "/" + fileName
|
|
||||||
if !cronjob.KeepLocal {
|
if !cronjob.KeepLocal {
|
||||||
cloudFile = backupDir + "/" + fileName
|
defer func() {
|
||||||
|
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, fileName))
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
client, err := NewIBackupService().NewClient(&backup)
|
client, err := NewIBackupService().NewClient(&backup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cloudFile, err
|
return fullPath, err
|
||||||
}
|
}
|
||||||
if _, err = client.Upload(baseDir+"/"+backupDir+"/"+fileName, backupDir+"/"+fileName); err != nil {
|
if _, err = client.Upload(backupDir+"/"+fileName, fullPath); err != nil {
|
||||||
return cloudFile, err
|
return fullPath, err
|
||||||
}
|
}
|
||||||
u.HandleRmExpired(backup.Type, baseDir, backupDir, cronjob, client)
|
u.HandleRmExpired(backup.Type, backupDir, cronjob, client)
|
||||||
return cloudFile, nil
|
return fullPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) HandleDelete(id uint) error {
|
func (u *CronjobService) HandleDelete(id uint) error {
|
||||||
@ -156,7 +174,7 @@ func (u *CronjobService) HandleDelete(id uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) HandleRmExpired(backType, baseDir, backupDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
|
func (u *CronjobService) HandleRmExpired(backType, backupDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
|
||||||
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
|
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
|
||||||
if backType != "LOCAL" {
|
if backType != "LOCAL" {
|
||||||
currentObjs, err := backClient.ListObjects(backupDir + "/")
|
currentObjs, err := backClient.ListObjects(backupDir + "/")
|
||||||
@ -171,9 +189,9 @@ func (u *CronjobService) HandleRmExpired(backType, baseDir, backupDir string, cr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
files, err := ioutil.ReadDir(baseDir + "/" + backupDir)
|
files, err := ioutil.ReadDir(backupDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("read dir %s failed, err: %v", baseDir+"/"+backupDir, err)
|
global.LOG.Errorf("read dir %s failed, err: %v", backupDir, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
@ -185,14 +203,14 @@ func (u *CronjobService) HandleRmExpired(backType, baseDir, backupDir string, cr
|
|||||||
if strings.HasPrefix(files[i].Name(), "db_") {
|
if strings.HasPrefix(files[i].Name(), "db_") {
|
||||||
dbCopies++
|
dbCopies++
|
||||||
if dbCopies > cronjob.RetainCopies {
|
if dbCopies > cronjob.RetainCopies {
|
||||||
_ = os.Remove(baseDir + "/" + backupDir + "/" + files[i].Name())
|
_ = os.Remove(backupDir + "/" + files[i].Name())
|
||||||
_ = backupRepo.DeleteRecord(context.Background(), backupRepo.WithByFileName(files[i].Name()))
|
_ = backupRepo.DeleteRecord(context.Background(), backupRepo.WithByFileName(files[i].Name()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < len(files)-int(cronjob.RetainCopies); i++ {
|
for i := 0; i < len(files)-int(cronjob.RetainCopies); i++ {
|
||||||
_ = os.Remove(baseDir + "/" + backupDir + "/" + files[i].Name())
|
_ = os.Remove(backupDir + "/" + files[i].Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)))
|
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)))
|
||||||
|
@ -2,14 +2,12 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"compress/gzip"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -21,7 +19,6 @@ import (
|
|||||||
"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/common"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -38,11 +35,6 @@ type IMysqlService interface {
|
|||||||
UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
|
UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
|
||||||
UpdateConfByFile(info dto.MysqlConfUpdateByFile) error
|
UpdateConfByFile(info dto.MysqlConfUpdateByFile) error
|
||||||
UpdateDescription(req dto.UpdateDescription) error
|
UpdateDescription(req dto.UpdateDescription) error
|
||||||
|
|
||||||
RecoverByUpload(req dto.UploadRecover) error
|
|
||||||
Backup(db dto.BackupDB) error
|
|
||||||
Recover(db dto.RecoverDB) error
|
|
||||||
|
|
||||||
DeleteCheck(id uint) ([]string, error)
|
DeleteCheck(id uint) ([]string, error)
|
||||||
Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
||||||
LoadStatus() (*dto.MysqlStatus, error)
|
LoadStatus() (*dto.MysqlStatus, error)
|
||||||
@ -68,83 +60,6 @@ func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interfa
|
|||||||
return total, dtoMysqls, err
|
return total, dtoMysqls, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *MysqlService) RecoverByUpload(req dto.UploadRecover) error {
|
|
||||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
|
||||||
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/%s", req.FileDir, 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
|
|
||||||
}
|
|
||||||
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.FileDir+"/"+req.FileName)
|
|
||||||
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)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
global.LOG.Info("start to do recover from uploads")
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
global.LOG.Info("recover from uploads successful!")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *MysqlService) ListDBName() ([]string, error) {
|
func (u *MysqlService) ListDBName() ([]string, error) {
|
||||||
mysqls, err := mysqlRepo.List()
|
mysqls, err := mysqlRepo.List()
|
||||||
var dbNames []string
|
var dbNames []string
|
||||||
@ -205,46 +120,6 @@ 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) Backup(db dto.BackupDB) error {
|
|
||||||
localDir, err := loadLocalDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
backupDir := fmt.Sprintf("database/mysql/%s/%s", 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
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *MysqlService) Recover(req dto.RecoverDB) error {
|
|
||||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
global.LOG.Infof("recover database %s-%s from backup file %s", req.MysqlName, req.DBName, req.BackupName)
|
|
||||||
gzipFile, err := os.Open(req.BackupName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer gzipFile.Close()
|
|
||||||
gzipReader, err := gzip.NewReader(gzipFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer gzipReader.Close()
|
|
||||||
cmd := exec.Command("docker", "exec", "-i", app.ContainerName, "mysql", "-uroot", "-p"+app.Password, req.DBName)
|
|
||||||
cmd.Stdin = gzipReader
|
|
||||||
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 (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", "")
|
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||||
@ -301,7 +176,7 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
|||||||
}
|
}
|
||||||
global.LOG.Infof("delete database %s-%s backups successful", app.Name, db.Name)
|
global.LOG.Infof("delete database %s-%s backups successful", app.Name, db.Name)
|
||||||
}
|
}
|
||||||
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("database-mysql"), commonRepo.WithByName(app.Name), backupRepo.WithByDetailName(db.Name))
|
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("mysql"), commonRepo.WithByName(app.Name), backupRepo.WithByDetailName(db.Name))
|
||||||
|
|
||||||
_ = mysqlRepo.Delete(ctx, commonRepo.WithByID(db.ID))
|
_ = mysqlRepo.Delete(ctx, commonRepo.WithByID(db.ID))
|
||||||
return nil
|
return nil
|
||||||
@ -626,49 +501,6 @@ func excuteSql(containerName, password, command string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func backupMysql(backupType, baseDir, backupDir, mysqlName, dbName, fileName string) error {
|
|
||||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fullDir := baseDir + "/" + 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outfile, _ := os.OpenFile(fullDir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0755)
|
|
||||||
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", fullDir+"/"+fileName)
|
|
||||||
cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, dbName)
|
|
||||||
gzipCmd := exec.Command("gzip", "-cf")
|
|
||||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
|
||||||
gzipCmd.Stdout = outfile
|
|
||||||
_ = gzipCmd.Start()
|
|
||||||
_ = cmd.Run()
|
|
||||||
_ = gzipCmd.Wait()
|
|
||||||
|
|
||||||
record := &model.BackupRecord{
|
|
||||||
Type: "database-mysql",
|
|
||||||
Name: app.Name,
|
|
||||||
DetailName: dbName,
|
|
||||||
Source: backupType,
|
|
||||||
BackupType: backupType,
|
|
||||||
FileDir: backupDir,
|
|
||||||
FileName: fileName,
|
|
||||||
}
|
|
||||||
if baseDir != global.CONF.System.TmpDir || backupType == "LOCAL" {
|
|
||||||
record.Source = "LOCAL"
|
|
||||||
record.FileDir = fullDir
|
|
||||||
}
|
|
||||||
if err := backupRepo.CreateRecord(record); err != nil {
|
|
||||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string {
|
func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string {
|
||||||
isOn := false
|
isOn := false
|
||||||
hasGroup := false
|
hasGroup := false
|
||||||
|
@ -9,12 +9,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
)
|
)
|
||||||
@ -30,9 +27,7 @@ type IRedisService interface {
|
|||||||
LoadConf() (*dto.RedisConf, error)
|
LoadConf() (*dto.RedisConf, error)
|
||||||
LoadPersistenceConf() (*dto.RedisPersistence, error)
|
LoadPersistenceConf() (*dto.RedisPersistence, error)
|
||||||
|
|
||||||
Backup() error
|
|
||||||
SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error)
|
SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error)
|
||||||
Recover(req dto.RedisBackupRecover) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIRedisService() IRedisService {
|
func NewIRedisService() IRedisService {
|
||||||
@ -156,89 +151,6 @@ func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) {
|
|||||||
return &item, nil
|
return &item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) Backup() error {
|
|
||||||
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stdout, err := cmd.Execf("docker exec %s redis-cli -a %s --no-auth-warning save", redisInfo.ContainerName, redisInfo.Password)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(string(stdout))
|
|
||||||
}
|
|
||||||
localDir, err := loadLocalDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
backupDir := fmt.Sprintf("database/redis/%s/", redisInfo.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
|
|
||||||
if appendonly == "yes" {
|
|
||||||
redisDataDir := fmt.Sprintf("%s/%s/%s/data", constant.AppInstallDir, "redis", redisInfo.Name)
|
|
||||||
name := fmt.Sprintf("%s.tar.gz", time.Now().Format("20060102150405"))
|
|
||||||
if err := handleTar(redisDataDir+"/appendonlydir", fullDir, name, ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
name := fmt.Sprintf("%s.rdb", time.Now().Format("20060102150405"))
|
|
||||||
stdout1, err1 := cmd.Execf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, fullDir, name)
|
|
||||||
if err1 != nil {
|
|
||||||
return errors.New(string(stdout1))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *RedisService) Recover(req dto.RedisBackupRecover) error {
|
|
||||||
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
|
|
||||||
|
|
||||||
composeDir := fmt.Sprintf("%s/redis/%s", constant.AppInstallDir, redisInfo.Name)
|
|
||||||
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fullName := fmt.Sprintf("%s/%s", req.FileDir, req.FileName)
|
|
||||||
if appendonly == "yes" {
|
|
||||||
redisDataDir := fmt.Sprintf("%s/%s/%s/data/", constant.AppInstallDir, "redis", redisInfo.Name)
|
|
||||||
if err := handleUnTar(fullName, redisDataDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
input, err := ioutil.ReadFile(fullName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = ioutil.WriteFile(composeDir+"/data/dump.rdb", input, 0640); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := compose.Up(composeDir + "/docker-compose.yml"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error) {
|
func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error) {
|
||||||
var (
|
var (
|
||||||
list []dto.DatabaseFileRecords
|
list []dto.DatabaseFileRecords
|
||||||
|
@ -104,10 +104,8 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
|
|||||||
func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
|
func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
|
||||||
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
|
||||||
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
|
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_, _ = os.Create(constant.DaemonJsonPath)
|
_, _ = os.Create(constant.DaemonJsonPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,10 +184,8 @@ func (u *ImageRepoService) CheckConn(host, user, password string) error {
|
|||||||
func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) error {
|
func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) error {
|
||||||
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
|
||||||
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
|
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_, _ = os.Create(constant.DaemonJsonPath)
|
_, _ = os.Create(constant.DaemonJsonPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,19 +3,19 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
@ -33,9 +33,6 @@ type IWebsiteService interface {
|
|||||||
CreateWebsite(ctx context.Context, create request.WebsiteCreate) error
|
CreateWebsite(ctx context.Context, create request.WebsiteCreate) error
|
||||||
OpWebsite(req request.WebsiteOp) error
|
OpWebsite(req request.WebsiteOp) error
|
||||||
GetWebsiteOptions() ([]string, error)
|
GetWebsiteOptions() ([]string, error)
|
||||||
Backup(id uint) error
|
|
||||||
Recover(req request.WebsiteRecover) error
|
|
||||||
RecoverByUpload(req request.WebsiteRecoverByFile) error
|
|
||||||
UpdateWebsite(req request.WebsiteUpdate) error
|
UpdateWebsite(req request.WebsiteUpdate) error
|
||||||
DeleteWebsite(req request.WebsiteDelete) error
|
DeleteWebsite(req request.WebsiteDelete) error
|
||||||
GetWebsite(id uint) (response.WebsiteDTO, error)
|
GetWebsite(id uint) (response.WebsiteDTO, error)
|
||||||
@ -198,74 +195,6 @@ func (w WebsiteService) GetWebsiteOptions() ([]string, error) {
|
|||||||
return datas, nil
|
return datas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) Backup(id uint) error {
|
|
||||||
localDir, err := loadLocalDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(id))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := fmt.Sprintf("%s_%s", website.PrimaryDomain, time.Now().Format("20060102150405"))
|
|
||||||
backupDir := fmt.Sprintf("website/%s", website.PrimaryDomain)
|
|
||||||
if err := handleWebsiteBackup("LOCAL", localDir, backupDir, website.PrimaryDomain, fileName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w WebsiteService) RecoverByUpload(req request.WebsiteRecoverByFile) error {
|
|
||||||
if err := handleUnTar(fmt.Sprintf("%s/%s", req.FileDir, req.FileName), req.FileDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tmpDir := fmt.Sprintf("%s/%s", req.FileDir, strings.ReplaceAll(req.FileName, ".tar.gz", ""))
|
|
||||||
webJson, err := os.ReadFile(fmt.Sprintf("%s/website.json", tmpDir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var websiteInfo WebsiteInfo
|
|
||||||
if err := json.Unmarshal(webJson, &websiteInfo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if websiteInfo.WebsiteName != req.WebsiteName || websiteInfo.WebsiteType != req.Type {
|
|
||||||
return errors.New("上传文件与选中网站不匹配,无法恢复")
|
|
||||||
}
|
|
||||||
|
|
||||||
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.WebsiteName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := handleWebsiteRecover(&website, tmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w WebsiteService) Recover(req request.WebsiteRecover) error {
|
|
||||||
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.WebsiteName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(req.BackupName, "/") {
|
|
||||||
return errors.New("error path of request")
|
|
||||||
}
|
|
||||||
fileDir := path.Dir(req.BackupName)
|
|
||||||
pathName := strings.ReplaceAll(path.Base(req.BackupName), ".tar.gz", "")
|
|
||||||
if err := handleUnTar(req.BackupName, fileDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileDir = fileDir + "/" + pathName
|
|
||||||
|
|
||||||
if err := handleWebsiteRecover(&website, fileDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error {
|
func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error {
|
||||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -16,9 +12,6 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"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/utils/cmd"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
|
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
|
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
|
||||||
@ -391,180 +384,6 @@ func toMapStr(m map[string]interface{}) map[string]string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsiteInfo struct {
|
|
||||||
WebsiteName string `json:"websiteName"`
|
|
||||||
WebsiteType string `json:"websiteType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName string) error {
|
|
||||||
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(domain))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpDir := fmt.Sprintf("%s/%s/%s", baseDir, backupDir, backupName)
|
|
||||||
if _, err := os.Stat(tmpDir); err != nil && os.IsNotExist(err) {
|
|
||||||
if err = os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("mkdir %s failed, err: %v", tmpDir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
global.LOG.Infof("make a tmp dir %s for website files successful", tmpDir)
|
|
||||||
if err := saveWebsiteJson(&website, tmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.LOG.Info("put website into tmp dir successful")
|
|
||||||
|
|
||||||
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.Alias)
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.LOG.Info("put nginx conf into tmp dir successful")
|
|
||||||
|
|
||||||
if website.Type == constant.Deployment {
|
|
||||||
if err := mysqlOperation(&website, "backup", tmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
websiteDir := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, app.App.Key, app.Name)
|
|
||||||
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.LOG.Info("put app tar into tmp dir successful")
|
|
||||||
}
|
|
||||||
websiteDir := path.Join(constant.AppInstallDir, "nginx", nginxInfo.Name, "www", "sites", website.Alias)
|
|
||||||
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.LOG.Info("put website tar into tmp dir successful, now start to tar tmp dir")
|
|
||||||
if err := handleTar(tmpDir, fmt.Sprintf("%s/%s", baseDir, backupDir), backupName+".tar.gz", ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
record := &model.BackupRecord{
|
|
||||||
Type: "website-" + website.Type,
|
|
||||||
Name: website.PrimaryDomain,
|
|
||||||
DetailName: "",
|
|
||||||
Source: backupType,
|
|
||||||
BackupType: backupType,
|
|
||||||
FileDir: backupDir,
|
|
||||||
FileName: fmt.Sprintf("%s.tar.gz", backupName),
|
|
||||||
}
|
|
||||||
if baseDir != global.CONF.System.TmpDir || backupType == "LOCAL" {
|
|
||||||
record.Source = "LOCAL"
|
|
||||||
record.FileDir = fmt.Sprintf("%s/%s", baseDir, backupDir)
|
|
||||||
}
|
|
||||||
if err := backupRepo.CreateRecord(record); err != nil {
|
|
||||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleWebsiteRecover(website *model.Website, fileDir string) error {
|
|
||||||
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nginxConfPath := fmt.Sprintf("%s/nginx/%s/conf/conf.d", constant.AppInstallDir, nginxInfo.Name)
|
|
||||||
if err := files.NewFileOp().CopyFile(path.Join(fileDir, website.Alias+".conf"), nginxConfPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if website.Type == constant.Deployment {
|
|
||||||
if err := mysqlOperation(website, "recover", fileDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
appDir := fmt.Sprintf("%s/%s", constant.AppInstallDir, app.App.Key)
|
|
||||||
if err := handleUnTar(fmt.Sprintf("%s/%s.app.tar.gz", fileDir, website.Alias), appDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := compose.Restart(fmt.Sprintf("%s/%s/docker-compose.yml", appDir, app.Name)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
siteDir := fmt.Sprintf("%s/nginx/%s/www/sites", constant.AppInstallDir, nginxInfo.Name)
|
|
||||||
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.Alias), siteDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(string(stdout))
|
|
||||||
}
|
|
||||||
_ = os.RemoveAll(fileDir)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mysqlOperation(website *model.Website, operation, filePath string) error {
|
|
||||||
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if operation == "backup" {
|
|
||||||
dbFile := fmt.Sprintf("%s/%s.sql", filePath, website.PrimaryDomain)
|
|
||||||
outfile, _ := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE, 0755)
|
|
||||||
defer outfile.Close()
|
|
||||||
cmd := exec.Command("docker", "exec", mysqlInfo.ContainerName, "mysqldump", "-uroot", "-p"+mysqlInfo.Password, db.Name)
|
|
||||||
cmd.Stdout = outfile
|
|
||||||
_ = cmd.Run()
|
|
||||||
_ = cmd.Wait()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, db.Name)
|
|
||||||
sqlfile, err := os.Open(fmt.Sprintf("%s/%s.sql", filePath, website.Alias))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sqlfile.Close()
|
|
||||||
cmd.Stdin = sqlfile
|
|
||||||
stdout, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(string(stdout))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveWebsiteJson(website *model.Website, tmpDir string) error {
|
|
||||||
var websiteInfo WebsiteInfo
|
|
||||||
websiteInfo.WebsiteType = website.Type
|
|
||||||
websiteInfo.WebsiteName = website.PrimaryDomain
|
|
||||||
remarkInfo, _ := json.Marshal(websiteInfo)
|
|
||||||
path := fmt.Sprintf("%s/website.json", tmpDir)
|
|
||||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
write := bufio.NewWriter(file)
|
|
||||||
_, _ = write.WriteString(string(remarkInfo))
|
|
||||||
write.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteWebsiteFolder(nginxInstall model.AppInstall, website *model.Website) error {
|
func deleteWebsiteFolder(nginxInstall model.AppInstall, website *model.Website) error {
|
||||||
nginxFolder := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name)
|
nginxFolder := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name)
|
||||||
siteFolder := path.Join(nginxFolder, "www", "sites", website.Alias)
|
siteFolder := path.Join(nginxFolder, "www", "sites", website.Alias)
|
||||||
|
@ -19,9 +19,6 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
|
|||||||
cmdRouter.POST("", baseApi.CreateMysql)
|
cmdRouter.POST("", baseApi.CreateMysql)
|
||||||
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("/backup", baseApi.BackupMysql)
|
|
||||||
cmdRouter.POST("/recover/byupload", baseApi.RecoverMysqlByUpload)
|
|
||||||
cmdRouter.POST("/recover", baseApi.RecoverMysql)
|
|
||||||
cmdRouter.POST("/del/check", baseApi.DeleteCheckMysql)
|
cmdRouter.POST("/del/check", baseApi.DeleteCheckMysql)
|
||||||
cmdRouter.POST("/del", baseApi.DeleteMysql)
|
cmdRouter.POST("/del", baseApi.DeleteMysql)
|
||||||
cmdRouter.POST("/description/update", baseApi.UpdateMysqlDescription)
|
cmdRouter.POST("/description/update", baseApi.UpdateMysqlDescription)
|
||||||
@ -39,8 +36,6 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
|
|||||||
cmdRouter.GET("/redis/conf", baseApi.LoadRedisConf)
|
cmdRouter.GET("/redis/conf", baseApi.LoadRedisConf)
|
||||||
cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh)
|
cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh)
|
||||||
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
||||||
cmdRouter.POST("/redis/backup", baseApi.RedisBackup)
|
|
||||||
cmdRouter.POST("/redis/recover", baseApi.RedisRecover)
|
|
||||||
cmdRouter.POST("/redis/backup/search", baseApi.RedisBackupList)
|
cmdRouter.POST("/redis/backup/search", baseApi.RedisBackupList)
|
||||||
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
||||||
cmdRouter.POST("/redis/conffile/update", baseApi.UpdateRedisConfByFile)
|
cmdRouter.POST("/redis/conffile/update", baseApi.UpdateRedisConfByFile)
|
||||||
|
@ -35,6 +35,9 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
|||||||
settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription)
|
settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription)
|
||||||
|
|
||||||
settingRouter.GET("/backup/search", baseApi.ListBackup)
|
settingRouter.GET("/backup/search", baseApi.ListBackup)
|
||||||
|
settingRouter.POST("/backup/backup", baseApi.Backup)
|
||||||
|
settingRouter.POST("/backup/recover", baseApi.Recover)
|
||||||
|
settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload)
|
||||||
settingRouter.POST("/backup/search/files", baseApi.LoadFilesFromBackup)
|
settingRouter.POST("/backup/search/files", baseApi.LoadFilesFromBackup)
|
||||||
settingRouter.POST("/backup/buckets", baseApi.ListBuckets)
|
settingRouter.POST("/backup/buckets", baseApi.ListBuckets)
|
||||||
settingRouter.POST("/backup", baseApi.CreateBackup)
|
settingRouter.POST("/backup", baseApi.CreateBackup)
|
||||||
|
@ -25,9 +25,6 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
|
|||||||
groupRouter.POST("/update", baseApi.UpdateWebsite)
|
groupRouter.POST("/update", baseApi.UpdateWebsite)
|
||||||
groupRouter.GET("/:id", baseApi.GetWebsite)
|
groupRouter.GET("/:id", baseApi.GetWebsite)
|
||||||
groupRouter.POST("/del", baseApi.DeleteWebsite)
|
groupRouter.POST("/del", baseApi.DeleteWebsite)
|
||||||
groupRouter.POST("/backup", baseApi.BackupWebsite)
|
|
||||||
groupRouter.POST("/recover", baseApi.RecoverWebsite)
|
|
||||||
groupRouter.POST("/recover/byupload", baseApi.RecoverWebsiteByUpload)
|
|
||||||
groupRouter.POST("/default/server", baseApi.ChangeDefaultServer)
|
groupRouter.POST("/default/server", baseApi.ChangeDefaultServer)
|
||||||
|
|
||||||
groupRouter.GET("/domains/:websiteId", baseApi.GetWebDomains)
|
groupRouter.GET("/domains/:websiteId", baseApi.GetWebDomains)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -84,6 +85,21 @@ func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
|
||||||
|
if !f.Stat(path.Dir(dst)) {
|
||||||
|
_ = f.CreateDir(path.Dir(dst), mode.Perm())
|
||||||
|
}
|
||||||
|
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
write := bufio.NewWriter(file)
|
||||||
|
_, _ = write.WriteString(string(content))
|
||||||
|
write.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f FileOp) Chmod(dst string, mode fs.FileMode) error {
|
func (f FileOp) Chmod(dst string, mode fs.FileMode) error {
|
||||||
return f.Fs.Chmod(dst, mode)
|
return f.Fs.Chmod(dst, mode)
|
||||||
}
|
}
|
||||||
|
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -8,6 +8,7 @@ declare module 'vue' {
|
|||||||
AppLayout: typeof import('./src/components/app-layout/index.vue')['default']
|
AppLayout: typeof import('./src/components/app-layout/index.vue')['default']
|
||||||
AppStatus: typeof import('./src/components/app-status/index.vue')['default']
|
AppStatus: typeof import('./src/components/app-status/index.vue')['default']
|
||||||
BackButton: typeof import('./src/components/back-button/index.vue')['default']
|
BackButton: typeof import('./src/components/back-button/index.vue')['default']
|
||||||
|
Backup: typeof import('./src/components/backup/index.vue')['default']
|
||||||
BreadCrumbs: typeof import('./src/components/bread-crumbs/index.vue')['default']
|
BreadCrumbs: typeof import('./src/components/bread-crumbs/index.vue')['default']
|
||||||
BreadCrumbsItem: typeof import('./src/components/bread-crumbs/bread-crumbs-item.vue')['default']
|
BreadCrumbsItem: typeof import('./src/components/bread-crumbs/bread-crumbs-item.vue')['default']
|
||||||
CardWithHeader: typeof import('./src/components/card-with-header/index.vue')['default']
|
CardWithHeader: typeof import('./src/components/card-with-header/index.vue')['default']
|
||||||
|
@ -43,4 +43,15 @@ export namespace Backup {
|
|||||||
name: string;
|
name: string;
|
||||||
detailName: string;
|
detailName: string;
|
||||||
}
|
}
|
||||||
|
export interface Backup {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
detailName: string;
|
||||||
|
}
|
||||||
|
export interface Recover {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
detailName: string;
|
||||||
|
file: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,6 @@ export namespace Database {
|
|||||||
mysqlName: string;
|
mysqlName: string;
|
||||||
dbName: string;
|
dbName: string;
|
||||||
}
|
}
|
||||||
export interface Backup {
|
|
||||||
mysqlName: string;
|
|
||||||
dbName: string;
|
|
||||||
}
|
|
||||||
export interface Recover {
|
export interface Recover {
|
||||||
mysqlName: string;
|
mysqlName: string;
|
||||||
dbName: string;
|
dbName: string;
|
||||||
|
@ -6,16 +6,6 @@ export const searchMysqlDBs = (params: SearchWithPage) => {
|
|||||||
return http.post<ResPage<Database.MysqlDBInfo>>(`/databases/search`, params);
|
return http.post<ResPage<Database.MysqlDBInfo>>(`/databases/search`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const backup = (params: Database.Backup) => {
|
|
||||||
return http.post(`/databases/backup`, params);
|
|
||||||
};
|
|
||||||
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 addMysqlDB = (params: Database.MysqlDBCreate) => {
|
export const addMysqlDB = (params: Database.MysqlDBCreate) => {
|
||||||
return http.post(`/databases`, params);
|
return http.post(`/databases`, params);
|
||||||
};
|
};
|
||||||
@ -79,9 +69,6 @@ export const updateRedisConf = (params: Database.RedisConfUpdate) => {
|
|||||||
export const updateRedisConfByFile = (params: Database.RedisConfUpdateByFile) => {
|
export const updateRedisConfByFile = (params: Database.RedisConfUpdateByFile) => {
|
||||||
return http.post(`/databases/redis/conffile/update`, params);
|
return http.post(`/databases/redis/conffile/update`, params);
|
||||||
};
|
};
|
||||||
export const backupRedis = () => {
|
|
||||||
return http.post(`/databases/redis/backup`);
|
|
||||||
};
|
|
||||||
export const recoverRedis = (param: Database.RedisRecover) => {
|
export const recoverRedis = (param: Database.RedisRecover) => {
|
||||||
return http.post(`/databases/redis/recover`, param);
|
return http.post(`/databases/redis/recover`, param);
|
||||||
};
|
};
|
||||||
|
@ -51,6 +51,15 @@ export const loadBaseDir = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// backup
|
// backup
|
||||||
|
export const handleBackup = (params: Backup.Backup) => {
|
||||||
|
return http.post(`/settings/backup/backup`, params);
|
||||||
|
};
|
||||||
|
export const handleRecover = (params: Backup.Recover) => {
|
||||||
|
return http.post(`/settings/backup/recover`, params);
|
||||||
|
};
|
||||||
|
export const handleRecoverByUpload = (params: Backup.Recover) => {
|
||||||
|
return http.post(`/settings/backup/recover/byupload`, params);
|
||||||
|
};
|
||||||
export const getBackupList = () => {
|
export const getBackupList = () => {
|
||||||
return http.get<Array<Backup.BackupInfo>>(`/settings/backup/search`);
|
return http.get<Array<Backup.BackupInfo>>(`/settings/backup/search`);
|
||||||
};
|
};
|
||||||
|
@ -23,18 +23,6 @@ export const OpWebsiteLog = (req: Website.WebSiteOpLog) => {
|
|||||||
return http.post<Website.WebSiteLog>(`/websites/log`, req);
|
return http.post<Website.WebSiteLog>(`/websites/log`, req);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BackupWebsite = (req: Website.BackupReq) => {
|
|
||||||
return http.post(`/websites/backup`, req);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RecoverWebsite = (req: Website.WebSiteRecover) => {
|
|
||||||
return http.post(`/websites/recover`, req);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RecoverWebsiteByUpload = (req: Website.WebsiteRecoverByUpload) => {
|
|
||||||
return http.post(`/websites/recover/byupload`, req);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UpdateWebsite = (req: Website.WebSiteUpdateReq) => {
|
export const UpdateWebsite = (req: Website.WebSiteUpdateReq) => {
|
||||||
return http.post<any>(`/websites/update`, req);
|
return http.post<any>(`/websites/update`, req);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-loading="loading">
|
<div>
|
||||||
<el-drawer v-model="backupVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
<el-drawer v-model="backupVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<DrawerHeader :header="$t('database.backup')" :resource="dbName" :back="handleClose" />
|
<DrawerHeader
|
||||||
|
v-if="detailName"
|
||||||
|
:header="$t('database.backup')"
|
||||||
|
:resource="name + '(' + detailName + ')'"
|
||||||
|
:back="handleClose"
|
||||||
|
/>
|
||||||
|
<DrawerHeader v-else :header="$t('database.backup')" :resource="name" :back="handleClose" />
|
||||||
</template>
|
</template>
|
||||||
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" @search="search" :data="data">
|
<ComplexTable
|
||||||
|
v-loading="loading"
|
||||||
|
:pagination-config="paginationConfig"
|
||||||
|
v-model:selects="selects"
|
||||||
|
@search="search"
|
||||||
|
:data="data"
|
||||||
|
>
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<el-button type="primary" @click="onBackup()">
|
<el-button type="primary" @click="onBackup()">
|
||||||
{{ $t('database.backup') }}
|
{{ $t('database.backup') }}
|
||||||
@ -34,7 +46,7 @@ import ComplexTable from '@/components/complex-table/index.vue';
|
|||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { dateFormat } from '@/utils/util';
|
import { dateFormat } from '@/utils/util';
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
import { backup, recover } from '@/api/modules/database';
|
import { handleBackup, handleRecover } from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/setting';
|
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/setting';
|
||||||
@ -52,15 +64,19 @@ const paginationConfig = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const backupVisiable = ref(false);
|
const backupVisiable = ref(false);
|
||||||
const mysqlName = ref();
|
const type = ref();
|
||||||
const dbName = ref();
|
const name = ref();
|
||||||
|
const detailName = ref();
|
||||||
|
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
mysqlName: string;
|
type: string;
|
||||||
dbName: string;
|
name: string;
|
||||||
|
detailName: string;
|
||||||
}
|
}
|
||||||
const acceptParams = (params: DialogProps): void => {
|
const acceptParams = (params: DialogProps): void => {
|
||||||
mysqlName.value = params.mysqlName;
|
type.value = params.type;
|
||||||
dbName.value = params.dbName;
|
name.value = params.name;
|
||||||
|
detailName.value = params.detailName;
|
||||||
backupVisiable.value = true;
|
backupVisiable.value = true;
|
||||||
search();
|
search();
|
||||||
};
|
};
|
||||||
@ -72,9 +88,9 @@ const search = async () => {
|
|||||||
let params = {
|
let params = {
|
||||||
page: paginationConfig.currentPage,
|
page: paginationConfig.currentPage,
|
||||||
pageSize: paginationConfig.pageSize,
|
pageSize: paginationConfig.pageSize,
|
||||||
type: 'database-mysql',
|
type: type.value,
|
||||||
name: mysqlName.value,
|
name: name.value,
|
||||||
detailName: dbName.value,
|
detailName: detailName.value,
|
||||||
};
|
};
|
||||||
const res = await searchBackupRecords(params);
|
const res = await searchBackupRecords(params);
|
||||||
data.value = res.data.items || [];
|
data.value = res.data.items || [];
|
||||||
@ -83,12 +99,14 @@ const search = async () => {
|
|||||||
|
|
||||||
const onBackup = async () => {
|
const onBackup = async () => {
|
||||||
let params = {
|
let params = {
|
||||||
mysqlName: mysqlName.value,
|
type: type.value,
|
||||||
dbName: dbName.value,
|
name: name.value,
|
||||||
|
detailName: detailName.value,
|
||||||
};
|
};
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await backup(params)
|
await handleBackup(params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
console.log(loading.value);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
search();
|
search();
|
||||||
@ -100,12 +118,13 @@ const onBackup = async () => {
|
|||||||
|
|
||||||
const onRecover = async (row: Backup.RecordInfo) => {
|
const onRecover = async (row: Backup.RecordInfo) => {
|
||||||
let params = {
|
let params = {
|
||||||
mysqlName: mysqlName.value,
|
type: type.value,
|
||||||
dbName: dbName.value,
|
name: name.value,
|
||||||
backupName: row.fileDir + '/' + row.fileName,
|
detailName: detailName.value,
|
||||||
|
file: row.fileDir + '/' + row.fileName,
|
||||||
};
|
};
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await recover(params)
|
await handleRecover(params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
@ -1,202 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-drawer
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
v-model="open"
|
|
||||||
size="50%"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:before-close="handleClose"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<Header :header="$t('app.backup')" :resource="installData.appInstallName" :back="handleClose"></Header>
|
|
||||||
</template>
|
|
||||||
<ComplexTable
|
|
||||||
:pagination-config="paginationConfig"
|
|
||||||
:data="data"
|
|
||||||
@search="search"
|
|
||||||
v-loading="loading"
|
|
||||||
v-model:selects="selects"
|
|
||||||
>
|
|
||||||
<template #toolbar>
|
|
||||||
<el-button type="primary" @click="backup">{{ $t('app.backup') }}</el-button>
|
|
||||||
<el-button type="danger" plain :disabled="selects.length === 0" @click="onBatchDelete()">
|
|
||||||
{{ $t('commons.button.delete') }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
<el-table-column type="selection" fix />
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
:label="$t('app.backupName')"
|
|
||||||
min-width="120px"
|
|
||||||
prop="name"
|
|
||||||
show-overflow-tooltip
|
|
||||||
></el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:label="$t('app.backupPath')"
|
|
||||||
min-width="120px"
|
|
||||||
prop="path"
|
|
||||||
show-overflow-tooltip
|
|
||||||
></el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="createdAt"
|
|
||||||
:label="$t('app.backupdate')"
|
|
||||||
:formatter="dateFormat"
|
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<fu-table-operations
|
|
||||||
width="300px"
|
|
||||||
:ellipsis="10"
|
|
||||||
:buttons="buttons"
|
|
||||||
:label="$t('commons.table.operate')"
|
|
||||||
fixed="right"
|
|
||||||
fix
|
|
||||||
/>
|
|
||||||
</ComplexTable>
|
|
||||||
<el-dialog
|
|
||||||
v-model="openRestorePage"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:title="$t('commons.msg.operate')"
|
|
||||||
width="30%"
|
|
||||||
>
|
|
||||||
<el-alert :title="$t('app.restoreWarn')" type="warning" :closable="false" show-icon />
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="openRestorePage = false" :loading="loading">
|
|
||||||
{{ $t('commons.button.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="restore" :loading="loading">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</el-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup name="installBackup">
|
|
||||||
import { DelAppBackups, GetAppBackups, InstalledOp } from '@/api/modules/app';
|
|
||||||
import { reactive, ref } from 'vue';
|
|
||||||
import ComplexTable from '@/components/complex-table/index.vue';
|
|
||||||
import Header from '@/components/drawer-header/index.vue';
|
|
||||||
import { dateFormat } from '@/utils/util';
|
|
||||||
import i18n from '@/lang';
|
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
|
||||||
import { MsgSuccess } from '@/utils/message';
|
|
||||||
|
|
||||||
interface InstallRrops {
|
|
||||||
appInstallId: number;
|
|
||||||
appInstallName: string;
|
|
||||||
}
|
|
||||||
const installData = ref<InstallRrops>({
|
|
||||||
appInstallId: 0,
|
|
||||||
appInstallName: '',
|
|
||||||
});
|
|
||||||
const selects = ref<any>([]);
|
|
||||||
let open = ref(false);
|
|
||||||
let loading = ref(false);
|
|
||||||
let data = ref<any>();
|
|
||||||
let openRestorePage = ref(false);
|
|
||||||
const paginationConfig = reactive({
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
total: 0,
|
|
||||||
});
|
|
||||||
let req = reactive({
|
|
||||||
installId: installData.value.appInstallId,
|
|
||||||
operate: 'restore',
|
|
||||||
backupId: -1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const em = defineEmits(['close']);
|
|
||||||
const handleClose = () => {
|
|
||||||
open.value = false;
|
|
||||||
em('close', open);
|
|
||||||
};
|
|
||||||
|
|
||||||
const acceptParams = (props: InstallRrops) => {
|
|
||||||
installData.value.appInstallId = props.appInstallId;
|
|
||||||
installData.value.appInstallName = props.appInstallName;
|
|
||||||
search();
|
|
||||||
open.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const search = async () => {
|
|
||||||
const req = {
|
|
||||||
page: paginationConfig.currentPage,
|
|
||||||
pageSize: paginationConfig.pageSize,
|
|
||||||
appInstallId: installData.value.appInstallId,
|
|
||||||
};
|
|
||||||
await GetAppBackups(req).then((res) => {
|
|
||||||
data.value = res.data.items;
|
|
||||||
paginationConfig.total = res.data.total;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const backup = async () => {
|
|
||||||
const req = {
|
|
||||||
installId: installData.value.appInstallId,
|
|
||||||
operate: 'backup',
|
|
||||||
};
|
|
||||||
loading.value = true;
|
|
||||||
await InstalledOp(req)
|
|
||||||
.then(() => {
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.backupSuccess'));
|
|
||||||
search();
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const openRestore = (backupId: number) => {
|
|
||||||
openRestorePage.value = true;
|
|
||||||
req.backupId = backupId;
|
|
||||||
req.operate = 'restore';
|
|
||||||
req.installId = installData.value.appInstallId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const restore = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
await InstalledOp(req)
|
|
||||||
.then(() => {
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.restoreSuccess'));
|
|
||||||
openRestorePage.value = false;
|
|
||||||
search();
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteBackup = async (ids: number[]) => {
|
|
||||||
await useDeleteData(DelAppBackups, { ids: ids }, 'commons.msg.delete');
|
|
||||||
search();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBatchDelete = () => {
|
|
||||||
let ids: Array<number> = [];
|
|
||||||
selects.value.forEach((item: any) => {
|
|
||||||
ids.push(item.id);
|
|
||||||
});
|
|
||||||
deleteBackup(ids);
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttons = [
|
|
||||||
{
|
|
||||||
label: i18n.global.t('app.delete'),
|
|
||||||
click: (row: any) => {
|
|
||||||
deleteBackup([row.id]);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: i18n.global.t('app.restore'),
|
|
||||||
click: (row: any) => {
|
|
||||||
openRestore(row.id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
acceptParams,
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -95,7 +95,7 @@
|
|||||||
plain
|
plain
|
||||||
round
|
round
|
||||||
size="small"
|
size="small"
|
||||||
@click="openBackups(installed.id, installed.name)"
|
@click="openBackups(installed.app.key, installed.name)"
|
||||||
v-if="mode === 'installed'"
|
v-if="mode === 'installed'"
|
||||||
>
|
>
|
||||||
{{ $t('app.backup') }}
|
{{ $t('app.backup') }}
|
||||||
@ -163,7 +163,7 @@ import LayoutContent from '@/layout/layout-content.vue';
|
|||||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import Backups from './backup/index.vue';
|
import Backups from '@/components/backup/index.vue';
|
||||||
import AppResources from './check/index.vue';
|
import AppResources from './check/index.vue';
|
||||||
import AppDelete from './delete/index.vue';
|
import AppDelete from './delete/index.vue';
|
||||||
import AppParams from './detail/index.vue';
|
import AppParams from './detail/index.vue';
|
||||||
@ -335,10 +335,11 @@ const buttons = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const openBackups = (installId: number, installName: string) => {
|
const openBackups = (key: string, name: string) => {
|
||||||
let params = {
|
let params = {
|
||||||
appInstallId: installId,
|
type: 'app',
|
||||||
appInstallName: installName,
|
name: key,
|
||||||
|
detailName: name,
|
||||||
};
|
};
|
||||||
backupRef.value.acceptParams(params);
|
backupRef.value.acceptParams(params);
|
||||||
};
|
};
|
||||||
|
@ -140,7 +140,7 @@
|
|||||||
<RemoteAccessDialog ref="remoteAccessRef" />
|
<RemoteAccessDialog ref="remoteAccessRef" />
|
||||||
<UploadDialog ref="uploadRef" />
|
<UploadDialog ref="uploadRef" />
|
||||||
<OperateDialog @search="search" ref="dialogRef" />
|
<OperateDialog @search="search" ref="dialogRef" />
|
||||||
<BackupRecords ref="dialogBackupRef" />
|
<Backups ref="dialogBackupRef" />
|
||||||
|
|
||||||
<AppResources ref="checkRef"></AppResources>
|
<AppResources ref="checkRef"></AppResources>
|
||||||
<DeleteDialog ref="deleteRef" @search="search" />
|
<DeleteDialog ref="deleteRef" @search="search" />
|
||||||
@ -155,11 +155,11 @@ 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/root-password/index.vue';
|
||||||
import RemoteAccessDialog from '@/views/database/mysql/remote/index.vue';
|
import RemoteAccessDialog from '@/views/database/mysql/remote/index.vue';
|
||||||
import BackupRecords from '@/views/database/mysql/backup/index.vue';
|
|
||||||
import UploadDialog from '@/views/database/mysql/upload/index.vue';
|
import UploadDialog from '@/views/database/mysql/upload/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';
|
||||||
|
import Backups from '@/components/backup/index.vue';
|
||||||
import { dateFormat } from '@/utils/util';
|
import { dateFormat } from '@/utils/util';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { deleteCheckMysqlDB, loadRemoteAccess, searchMysqlDBs, updateMysqlDescription } from '@/api/modules/database';
|
import { deleteCheckMysqlDB, loadRemoteAccess, searchMysqlDBs, updateMysqlDescription } from '@/api/modules/database';
|
||||||
@ -205,8 +205,9 @@ const onOpenDialog = async () => {
|
|||||||
const dialogBackupRef = ref();
|
const dialogBackupRef = ref();
|
||||||
const onOpenBackupDialog = async (dbName: string) => {
|
const onOpenBackupDialog = async (dbName: string) => {
|
||||||
let params = {
|
let params = {
|
||||||
mysqlName: mysqlName.value,
|
type: 'mysql',
|
||||||
dbName: dbName,
|
name: mysqlName.value,
|
||||||
|
detailName: dbName,
|
||||||
};
|
};
|
||||||
dialogBackupRef.value!.acceptParams(params);
|
dialogBackupRef.value!.acceptParams(params);
|
||||||
};
|
};
|
||||||
|
@ -65,7 +65,7 @@ import ComplexTable from '@/components/complex-table/index.vue';
|
|||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { computeSize, dateFormatSimple } from '@/utils/util';
|
import { computeSize, dateFormatSimple } from '@/utils/util';
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
import { recoverByUpload } from '@/api/modules/database';
|
import { handleRecoverByUpload } from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
|
import { UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
|
||||||
import { File } from '@/api/interface/file';
|
import { File } from '@/api/interface/file';
|
||||||
@ -116,13 +116,13 @@ const search = async () => {
|
|||||||
|
|
||||||
const onRecover = async (row: File.File) => {
|
const onRecover = async (row: File.File) => {
|
||||||
let params = {
|
let params = {
|
||||||
mysqlName: mysqlName.value,
|
type: 'mysql',
|
||||||
dbName: dbName.value,
|
name: mysqlName.value,
|
||||||
fileDir: baseDir.value,
|
detailName: dbName.value,
|
||||||
fileName: row.name,
|
file: baseDir.value + '/' + row.name,
|
||||||
};
|
};
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await recoverByUpload(params)
|
await handleRecoverByUpload(params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
@ -157,6 +157,7 @@ const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
|||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
uploadRef.value!.clearFiles();
|
uploadRef.value!.clearFiles();
|
||||||
|
upVisiable.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
|
@ -119,12 +119,13 @@ import ComplexTable from '@/components/complex-table/index.vue';
|
|||||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||||
import { Database } from '@/api/interface/database';
|
import { Database } from '@/api/interface/database';
|
||||||
import {
|
import {
|
||||||
backupRedis,
|
|
||||||
recoverRedis,
|
recoverRedis,
|
||||||
redisBackupRedisRecords,
|
redisBackupRedisRecords,
|
||||||
RedisPersistenceConf,
|
RedisPersistenceConf,
|
||||||
updateRedisPersistenceConf,
|
updateRedisPersistenceConf,
|
||||||
} from '@/api/modules/database';
|
} from '@/api/modules/database';
|
||||||
|
|
||||||
|
import { handleBackup } from '@/api/modules/setting';
|
||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { FormInstance } from 'element-plus';
|
import { FormInstance } from 'element-plus';
|
||||||
@ -198,7 +199,7 @@ const loadBackupRecords = async () => {
|
|||||||
};
|
};
|
||||||
const onBackup = async () => {
|
const onBackup = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await backupRedis()
|
await handleBackup({ name: '', detailName: '', type: 'redis' })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
loadBackupRecords();
|
loadBackupRecords();
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-drawer :close-on-click-modal="false" v-model="backupVisiable" size="50%">
|
|
||||||
<template #header>
|
|
||||||
<DrawerHeader :header="$t('database.backup')" :resource="websiteName" :back="handleClose"></DrawerHeader>
|
|
||||||
</template>
|
|
||||||
<ComplexTable
|
|
||||||
v-loading="loading"
|
|
||||||
: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 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="dateFormat"
|
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
|
||||||
</ComplexTable>
|
|
||||||
</el-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
|
||||||
import ComplexTable from '@/components/complex-table/index.vue';
|
|
||||||
import { reactive, ref } from 'vue';
|
|
||||||
import { dateFormat } from '@/utils/util';
|
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
|
||||||
import i18n from '@/lang';
|
|
||||||
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/setting';
|
|
||||||
import { Backup } from '@/api/interface/backup';
|
|
||||||
import { BackupWebsite, RecoverWebsite } from '@/api/modules/website';
|
|
||||||
import { MsgSuccess } from '@/utils/message';
|
|
||||||
import { ElMessageBox } from 'element-plus';
|
|
||||||
|
|
||||||
const selects = ref<any>([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
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 handleClose = () => {
|
|
||||||
backupVisiable.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 onRecover = async (row: Backup.RecordInfo) => {
|
|
||||||
ElMessageBox.confirm(i18n.global.t('website.restoreHelper'), i18n.global.t('commons.button.recover'), {
|
|
||||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
|
||||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
|
||||||
type: 'info',
|
|
||||||
}).then(() => {
|
|
||||||
recover(row);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const recover = async (row: Backup.RecordInfo) => {
|
|
||||||
let params = {
|
|
||||||
websiteName: websiteName.value,
|
|
||||||
type: websiteType.value,
|
|
||||||
backupName: row.fileDir + '/' + row.fileName,
|
|
||||||
};
|
|
||||||
loading.value = true;
|
|
||||||
await RecoverWebsite(params)
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBackup = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
await BackupWebsite({ id: websiteID.value })
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
search();
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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');
|
|
||||||
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>
|
|
@ -143,7 +143,7 @@
|
|||||||
<DeleteWebsite ref="deleteRef" @close="search" />
|
<DeleteWebsite ref="deleteRef" @close="search" />
|
||||||
<WebSiteGroup ref="groupRef" />
|
<WebSiteGroup ref="groupRef" />
|
||||||
<UploadDialog ref="uploadRef" />
|
<UploadDialog ref="uploadRef" />
|
||||||
<BackupRecords ref="dialogBackupRef" />
|
<Backups ref="dialogBackupRef" />
|
||||||
<DefaultServer ref="defaultRef" />
|
<DefaultServer ref="defaultRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -151,7 +151,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import LayoutContent from '@/layout/layout-content.vue';
|
import LayoutContent from '@/layout/layout-content.vue';
|
||||||
import RouterButton from '@/components/router-button/index.vue';
|
import RouterButton from '@/components/router-button/index.vue';
|
||||||
import BackupRecords from '@/views/website/website/backup/index.vue';
|
import Backups from '@/components/backup/index.vue';
|
||||||
import UploadDialog from '@/views/website/website/upload/index.vue';
|
import UploadDialog from '@/views/website/website/upload/index.vue';
|
||||||
import DefaultServer from '@/views/website/website/default/index.vue';
|
import DefaultServer from '@/views/website/website/default/index.vue';
|
||||||
import ComplexTable from '@/components/complex-table/index.vue';
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
@ -312,9 +312,9 @@ const buttons = [
|
|||||||
label: i18n.global.t('database.backupList'),
|
label: i18n.global.t('database.backupList'),
|
||||||
click: (row: Website.Website) => {
|
click: (row: Website.Website) => {
|
||||||
let params = {
|
let params = {
|
||||||
id: row.id,
|
type: 'website',
|
||||||
type: row.type,
|
|
||||||
name: row.primaryDomain,
|
name: row.primaryDomain,
|
||||||
|
detailName: '',
|
||||||
};
|
};
|
||||||
dialogBackupRef.value!.acceptParams(params);
|
dialogBackupRef.value!.acceptParams(params);
|
||||||
},
|
},
|
||||||
|
@ -69,8 +69,7 @@ import i18n from '@/lang';
|
|||||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
|
import { UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
|
||||||
import { File } from '@/api/interface/file';
|
import { File } from '@/api/interface/file';
|
||||||
import { BatchDeleteFile, GetFilesList, UploadFileData } from '@/api/modules/files';
|
import { BatchDeleteFile, GetFilesList, UploadFileData } from '@/api/modules/files';
|
||||||
import { RecoverWebsiteByUpload } from '@/api/modules/website';
|
import { handleRecoverByUpload, loadBaseDir } from '@/api/modules/setting';
|
||||||
import { loadBaseDir } from '@/api/modules/setting';
|
|
||||||
import Header from '@/components/drawer-header/index.vue';
|
import Header from '@/components/drawer-header/index.vue';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
@ -116,13 +115,13 @@ const search = async () => {
|
|||||||
|
|
||||||
const onRecover = async (row: File.File) => {
|
const onRecover = async (row: File.File) => {
|
||||||
let params = {
|
let params = {
|
||||||
websiteName: websiteName.value,
|
name: websiteName.value,
|
||||||
type: websiteType.value,
|
detailName: '',
|
||||||
fileDir: baseDir.value,
|
type: 'website',
|
||||||
fileName: row.name,
|
file: baseDir.value + '/' + row.name,
|
||||||
};
|
};
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await RecoverWebsiteByUpload(params)
|
await handleRecoverByUpload(params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
Loading…
Reference in New Issue
Block a user