mirror of https://github.com/1Panel-dev/1Panel
feat: 完成网站恢复功能
parent
7b21bcbe7f
commit
f50656320b
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
@ -51,6 +52,24 @@ func (b *BaseApi) BackupWebsite(c *gin.Context) {
|
|||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) RecoverWebsite(c *gin.Context) {
|
||||
var req dto.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)
|
||||
}
|
||||
|
||||
func (b *BaseApi) DeleteWebSite(c *gin.Context) {
|
||||
var req dto.WebSiteDel
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
|
|
|
@ -51,6 +51,12 @@ type WebSiteDel struct {
|
|||
DeleteBackup bool `json:"deleteBackup"`
|
||||
}
|
||||
|
||||
type WebSiteRecover struct {
|
||||
WebsiteName string `json:"websiteName" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
BackupName string `json:"backupName" validate:"required"`
|
||||
}
|
||||
|
||||
type WebSiteDTO struct {
|
||||
model.WebSite
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
@ -16,6 +17,12 @@ func (w WebSiteRepo) WithAppInstallId(appInstallId uint) DBOption {
|
|||
}
|
||||
}
|
||||
|
||||
func (w WebSiteRepo) WithByDomain(domain string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("primary_domain = ?", domain)
|
||||
}
|
||||
}
|
||||
|
||||
func (w WebSiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSite, error) {
|
||||
var websites []model.WebSite
|
||||
db := getDb(opts...).Model(&model.WebSite{})
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"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/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
|
@ -185,6 +186,7 @@ func (w WebsiteService) Backup(id uint) error {
|
|||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
_ = os.RemoveAll(fullDir)
|
||||
|
||||
record := &model.BackupRecord{
|
||||
Type: "website-" + website.Type,
|
||||
|
@ -201,6 +203,93 @@ func (w WebsiteService) Backup(id uint) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) Recover(req dto.WebSiteRecover) error {
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(req.BackupName, "/") {
|
||||
return errors.New("error path of request")
|
||||
}
|
||||
fileDir := req.BackupName[:strings.LastIndex(req.BackupName, "/")]
|
||||
fileName := strings.ReplaceAll(req.BackupName[strings.LastIndex(req.BackupName, "/"):], ".tar.gz", "")
|
||||
if err := handleUnTar(req.BackupName, fileDir); err != nil {
|
||||
return err
|
||||
}
|
||||
fileDir = fileDir + "/" + fileName
|
||||
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey("mysql")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := os.OpenFile(fmt.Sprintf("%s/%s.conf", fileDir, website.PrimaryDomain), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
var out *os.File
|
||||
nginxConfDir := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
|
||||
if _, err := os.Stat(nginxConfDir); err != nil {
|
||||
out, err = os.Create(fmt.Sprintf("%s/%s.conf", nginxConfDir, website.PrimaryDomain))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out, err = os.OpenFile(nginxConfDir, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer out.Close()
|
||||
_, _ = io.Copy(out, src)
|
||||
if website.Type == "deployment" {
|
||||
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, db.Name)
|
||||
sql, err := os.OpenFile(fmt.Sprintf("%s/%s.sql", fileDir, website.PrimaryDomain), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stdin = sql
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
|
||||
appDir := fmt.Sprintf("%s/%s", constant.AppInstallDir, app.App.Key)
|
||||
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := compose.Restart(fmt.Sprintf("%s/%s/docker-compose.yml", appDir, app.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
appDir := fmt.Sprintf("%s/nginx/%s/www", constant.AppInstallDir, nginxInfo.Name)
|
||||
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cmd := exec.Command("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload")
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateWebsite(req dto.WebSiteUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
|
|
|
@ -17,6 +17,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
|
|||
{
|
||||
groupRouter.POST("", baseApi.CreateWebsite)
|
||||
groupRouter.POST("/backup/:id", baseApi.BackupWebsite)
|
||||
groupRouter.POST("/recover", baseApi.RecoverWebsite)
|
||||
groupRouter.POST("/update", baseApi.UpdateWebSite)
|
||||
groupRouter.GET("/:id", baseApi.GetWebSite)
|
||||
groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx)
|
||||
|
|
|
@ -38,6 +38,12 @@ export namespace WebSite {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface WebSiteRecover {
|
||||
websiteName: string;
|
||||
type: string;
|
||||
backupName: string;
|
||||
}
|
||||
|
||||
export interface WebSiteDel {
|
||||
id: number;
|
||||
deleteApp: boolean;
|
||||
|
|
|
@ -14,6 +14,9 @@ export const CreateWebsite = (req: WebSite.WebSiteCreateReq) => {
|
|||
export const BackupWebsite = (id: number) => {
|
||||
return http.post(`/websites/backup/${id}`);
|
||||
};
|
||||
export const RecoverWebsite = (req: WebSite.WebSiteRecover) => {
|
||||
return http.post(`/websites/recover`, req);
|
||||
};
|
||||
|
||||
export const UpdateWebsite = (req: WebSite.WebSiteUpdateReq) => {
|
||||
return http.post<any>(`/websites/update`, req);
|
||||
|
|
|
@ -40,7 +40,7 @@ import i18n from '@/lang';
|
|||
import { ElMessage } from 'element-plus';
|
||||
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/backup';
|
||||
import { Backup } from '@/api/interface/backup';
|
||||
import { BackupWebsite } from '@/api/modules/website';
|
||||
import { BackupWebsite, RecoverWebsite } from '@/api/modules/website';
|
||||
|
||||
const selects = ref<any>([]);
|
||||
|
||||
|
@ -82,6 +82,16 @@ const search = async () => {
|
|||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
const onRecover = async (row: Backup.RecordInfo) => {
|
||||
let params = {
|
||||
websiteName: websiteName.value,
|
||||
type: websiteType.value,
|
||||
backupName: row.fileDir + '/' + row.fileName,
|
||||
};
|
||||
await RecoverWebsite(params);
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
};
|
||||
|
||||
const onBackup = async () => {
|
||||
await BackupWebsite(websiteID.value);
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
|
@ -124,12 +134,12 @@ const buttons = [
|
|||
onBatchDelete(row);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// label: i18n.global.t('commons.button.recover'),
|
||||
// click: (row: Backup.RecordInfo) => {
|
||||
// onRecover(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) => {
|
||||
|
|
Loading…
Reference in New Issue