mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 完成网站上传与恢复功能
This commit is contained in:
parent
e66ce1a9f2
commit
0d95cee924
@ -63,6 +63,24 @@ func (b *BaseApi) BackupWebsite(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) RecoverWebsiteByUpload(c *gin.Context) {
|
||||||
|
var req dto.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)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseApi) RecoverWebsite(c *gin.Context) {
|
func (b *BaseApi) RecoverWebsite(c *gin.Context) {
|
||||||
var req dto.WebSiteRecover
|
var req dto.WebSiteRecover
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
@ -57,6 +57,13 @@ type WebSiteRecover struct {
|
|||||||
BackupName string `json:"backupName" validate:"required"`
|
BackupName string `json:"backupName" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebSiteRecoverByFile struct {
|
||||||
|
WebsiteName string `json:"websiteName" validate:"required"`
|
||||||
|
Type string `json:"type" validate:"required"`
|
||||||
|
FileDir string `json:"fileDir" validate:"required"`
|
||||||
|
FileName string `json:"fileName" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type WebSiteDTO struct {
|
type WebSiteDTO struct {
|
||||||
model.WebSite
|
model.WebSite
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
|
|||||||
}
|
}
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
if record.Source == "LOCAL" {
|
if record.Source == "LOCAL" {
|
||||||
if err := os.Remove(record.FileDir + record.FileName); err != nil {
|
if err := os.Remove(record.FileDir + "/" + record.FileName); err != nil {
|
||||||
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
|
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,6 +34,7 @@ type IWebsiteService interface {
|
|||||||
GetWebsiteOptions() ([]string, error)
|
GetWebsiteOptions() ([]string, error)
|
||||||
Backup(domain string) error
|
Backup(domain string) error
|
||||||
Recover(req dto.WebSiteRecover) error
|
Recover(req dto.WebSiteRecover) error
|
||||||
|
RecoverByUpload(req dto.WebSiteRecoverByFile) error
|
||||||
UpdateWebsite(req dto.WebSiteUpdate) error
|
UpdateWebsite(req dto.WebSiteUpdate) error
|
||||||
DeleteWebSite(req dto.WebSiteDel) error
|
DeleteWebSite(req dto.WebSiteDel) error
|
||||||
}
|
}
|
||||||
@ -149,15 +150,40 @@ func (w WebsiteService) Backup(domain string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w WebsiteService) RecoverByUpload(req dto.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.WithByDomain(req.WebsiteName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := handleWebsiteRecover(&website, tmpDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w WebsiteService) Recover(req dto.WebSiteRecover) error {
|
func (w WebsiteService) Recover(req dto.WebSiteRecover) error {
|
||||||
website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName))
|
website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !strings.Contains(req.BackupName, "/") {
|
if !strings.Contains(req.BackupName, "/") {
|
||||||
return errors.New("error path of request")
|
return errors.New("error path of request")
|
||||||
}
|
}
|
||||||
@ -167,71 +193,9 @@ func (w WebsiteService) Recover(req dto.WebSiteRecover) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fileDir = fileDir + "/" + fileName
|
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 := handleWebsiteRecover(&website, fileDir); err != nil {
|
||||||
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
|
return err
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
@ -534,26 +498,6 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey(resource.Key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpDir := fmt.Sprintf("%s/%s/%s", baseDir, backupDir, backupName)
|
tmpDir := fmt.Sprintf("%s/%s/%s", baseDir, backupDir, backupName)
|
||||||
if _, err := os.Stat(tmpDir); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(tmpDir); err != nil && os.IsNotExist(err) {
|
||||||
@ -563,21 +507,28 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := saveNginxConf(nginxInfo.Name, website.PrimaryDomain, tmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := saveWebsiteJson(&website, tmpDir); err != nil {
|
if err := saveWebsiteJson(&website, tmpDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if website.Type == "deployment" {
|
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
|
||||||
dbFile := fmt.Sprintf("%s/%s.sql", tmpDir, website.PrimaryDomain)
|
if err != nil {
|
||||||
outfile, _ := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE, 0755)
|
return err
|
||||||
cmd := exec.Command("docker", "exec", mysqlInfo.ContainerName, "mysqldump", "-uroot", "-p"+mysqlInfo.Password, db.Name)
|
}
|
||||||
cmd.Stdout = outfile
|
nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
|
||||||
_ = cmd.Run()
|
if err := copyConf(nginxConfFile, fmt.Sprintf("%s/%s.conf", tmpDir, website.PrimaryDomain)); err != nil {
|
||||||
_ = cmd.Wait()
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if website.Type == "deployment" {
|
||||||
|
if err := mysqlOpration(&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)
|
websiteDir := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, app.App.Key, app.Name)
|
||||||
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.PrimaryDomain), ""); err != nil {
|
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.PrimaryDomain), ""); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -612,6 +563,85 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleWebsiteRecover(website *model.WebSite, fileDir string) error {
|
||||||
|
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
|
||||||
|
if err := copyConf(fmt.Sprintf("%s/%s.conf", fileDir, website.PrimaryDomain), nginxConfFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if website.Type == "deployment" {
|
||||||
|
if err := mysqlOpration(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.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))
|
||||||
|
}
|
||||||
|
_ = os.RemoveAll(fileDir)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mysqlOpration(website *model.WebSite, operation, filePath string) error {
|
||||||
|
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey("mysql")
|
||||||
|
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.PrimaryDomain))
|
||||||
|
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 {
|
func saveWebsiteJson(website *model.WebSite, tmpDir string) error {
|
||||||
var WebSiteInfo WebSiteInfo
|
var WebSiteInfo WebSiteInfo
|
||||||
WebSiteInfo.WebsiteType = website.Type
|
WebSiteInfo.WebsiteType = website.Type
|
||||||
@ -629,19 +659,20 @@ func saveWebsiteJson(website *model.WebSite, tmpDir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveNginxConf(nginxName, websiteDomain, tmpDir string) error {
|
func copyConf(srcPath, dstPath string) error {
|
||||||
nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxName, websiteDomain)
|
if _, err := os.Stat(srcPath); err != nil {
|
||||||
src, err := os.OpenFile(nginxConfFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
return err
|
||||||
|
}
|
||||||
|
src, err := os.OpenFile(srcPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer src.Close()
|
defer src.Close()
|
||||||
out, err := os.Create(fmt.Sprintf("%s/%s.conf", tmpDir, websiteDomain))
|
out, err := os.Create(dstPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
_, _ = io.Copy(out, src)
|
_, _ = io.Copy(out, src)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ var AddTableBackupAccount = &gormigrate.Migration{
|
|||||||
}
|
}
|
||||||
item := &model.BackupAccount{
|
item := &model.BackupAccount{
|
||||||
Type: "LOCAL",
|
Type: "LOCAL",
|
||||||
Vars: "{\"dir\":\"/opt/1Panel/backup\"}",
|
Vars: "{\"dir\":\"/opt/1Panel/data/backup\"}",
|
||||||
}
|
}
|
||||||
if err := tx.Create(item).Error; err != nil {
|
if err := tx.Create(item).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -19,6 +19,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
|
|||||||
groupRouter.GET("/options", baseApi.GetWebsiteOptions)
|
groupRouter.GET("/options", baseApi.GetWebsiteOptions)
|
||||||
groupRouter.POST("/backup/:domain", baseApi.BackupWebsite)
|
groupRouter.POST("/backup/:domain", baseApi.BackupWebsite)
|
||||||
groupRouter.POST("/recover", baseApi.RecoverWebsite)
|
groupRouter.POST("/recover", baseApi.RecoverWebsite)
|
||||||
|
groupRouter.POST("/recover/byupload", baseApi.RecoverWebsiteByUpload)
|
||||||
groupRouter.POST("/update", baseApi.UpdateWebSite)
|
groupRouter.POST("/update", baseApi.UpdateWebSite)
|
||||||
groupRouter.GET("/:id", baseApi.GetWebSite)
|
groupRouter.GET("/:id", baseApi.GetWebSite)
|
||||||
groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx)
|
groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx)
|
||||||
|
@ -43,6 +43,12 @@ export namespace WebSite {
|
|||||||
type: string;
|
type: string;
|
||||||
backupName: string;
|
backupName: string;
|
||||||
}
|
}
|
||||||
|
export interface WebsiteRecoverByUpload {
|
||||||
|
websiteName: string;
|
||||||
|
type: string;
|
||||||
|
fileDir: string;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WebSiteDel {
|
export interface WebSiteDel {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -17,6 +17,9 @@ export const BackupWebsite = (id: number) => {
|
|||||||
export const RecoverWebsite = (req: WebSite.WebSiteRecover) => {
|
export const RecoverWebsite = (req: WebSite.WebSiteRecover) => {
|
||||||
return http.post(`/websites/recover`, req);
|
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);
|
||||||
|
@ -695,6 +695,9 @@ export default {
|
|||||||
author: 'author',
|
author: 'author',
|
||||||
source: 'source',
|
source: 'source',
|
||||||
sync: 'sync',
|
sync: 'sync',
|
||||||
|
supportUpType: '.tar.gz files are supported only',
|
||||||
|
zipFormat:
|
||||||
|
'.tar.gz compressed package structure: test.tar.gz compressed package must contain the website.json file',
|
||||||
appName: 'App Name',
|
appName: 'App Name',
|
||||||
status: 'status',
|
status: 'status',
|
||||||
container: 'Container',
|
container: 'Container',
|
||||||
|
@ -739,6 +739,8 @@ export default {
|
|||||||
type: '类型',
|
type: '类型',
|
||||||
static: '静态网站',
|
static: '静态网站',
|
||||||
deployment: '反向代理',
|
deployment: '反向代理',
|
||||||
|
supportUpType: '仅支持 tar.gz 文件',
|
||||||
|
zipFormat: 'tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 website.json 文件',
|
||||||
proxy: '反向代理',
|
proxy: '反向代理',
|
||||||
alias: '代号',
|
alias: '代号',
|
||||||
remark: '备注',
|
remark: '备注',
|
||||||
|
@ -6,7 +6,13 @@
|
|||||||
<span>{{ $t('database.backup') }} - {{ websiteName }}</span>
|
<span>{{ $t('database.backup') }} - {{ websiteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</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') }}
|
||||||
@ -43,6 +49,7 @@ import { Backup } from '@/api/interface/backup';
|
|||||||
import { BackupWebsite, RecoverWebsite } from '@/api/modules/website';
|
import { BackupWebsite, RecoverWebsite } from '@/api/modules/website';
|
||||||
|
|
||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
@ -88,14 +95,28 @@ const onRecover = async (row: Backup.RecordInfo) => {
|
|||||||
type: websiteType.value,
|
type: websiteType.value,
|
||||||
backupName: row.fileDir + '/' + row.fileName,
|
backupName: row.fileDir + '/' + row.fileName,
|
||||||
};
|
};
|
||||||
await RecoverWebsite(params);
|
loading.value = true;
|
||||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
await RecoverWebsite(params)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBackup = async () => {
|
const onBackup = async () => {
|
||||||
await BackupWebsite(websiteName.value);
|
loading.value = true;
|
||||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
await BackupWebsite(websiteName.value)
|
||||||
search();
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownload = async (row: Backup.RecordInfo) => {
|
const onDownload = async (row: Backup.RecordInfo) => {
|
||||||
|
@ -138,8 +138,8 @@ const buttons = [
|
|||||||
label: i18n.global.t('database.loadBackup'),
|
label: i18n.global.t('database.loadBackup'),
|
||||||
click: (row: WebSite.WebSite) => {
|
click: (row: WebSite.WebSite) => {
|
||||||
let params = {
|
let params = {
|
||||||
mysqlName: 'test',
|
websiteName: row.primaryDomain,
|
||||||
dbName: row.primaryDomain,
|
websiteType: row.type,
|
||||||
};
|
};
|
||||||
uploadRef.value!.acceptParams(params);
|
uploadRef.value!.acceptParams(params);
|
||||||
},
|
},
|
||||||
|
@ -1,64 +1,66 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div :v-loading="loading">
|
||||||
<el-dialog v-model="upVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="70%">
|
<el-dialog v-model="upVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="70%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>{{ $t('commons.button.import') }}</span>
|
<span>{{ $t('commons.button.import') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-upload
|
<div v-loading="loading">
|
||||||
ref="uploadRef"
|
<el-upload
|
||||||
:on-change="fileOnChange"
|
ref="uploadRef"
|
||||||
:before-upload="beforeAvatarUpload"
|
:on-change="fileOnChange"
|
||||||
class="upload-demo"
|
:before-upload="beforeAvatarUpload"
|
||||||
:auto-upload="false"
|
class="upload-demo"
|
||||||
>
|
:auto-upload="false"
|
||||||
<template #trigger>
|
>
|
||||||
<el-button type="primary" plain>{{ $t('database.selectFile') }}</el-button>
|
<template #trigger>
|
||||||
</template>
|
<el-button type="primary" plain>{{ $t('database.selectFile') }}</el-button>
|
||||||
<el-button style="margin-left: 10px" icon="Upload" @click="onSubmit">
|
</template>
|
||||||
{{ $t('commons.button.upload') }}
|
<el-button style="margin-left: 10px" icon="Upload" @click="onSubmit">
|
||||||
</el-button>
|
{{ $t('commons.button.upload') }}
|
||||||
</el-upload>
|
|
||||||
<div style="margin-left: 10px">
|
|
||||||
<span class="input-help">{{ $t('database.supportUpType') }}</span>
|
|
||||||
<span class="input-help">
|
|
||||||
{{ $t('database.zipFormat') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<el-divider />
|
|
||||||
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
|
|
||||||
<template #toolbar>
|
|
||||||
<el-button
|
|
||||||
style="margin-left: 10px"
|
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
:disabled="selects.length === 0"
|
|
||||||
@click="onBatchDelete(null)"
|
|
||||||
>
|
|
||||||
{{ $t('commons.button.delete') }}
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</el-upload>
|
||||||
<el-table-column type="selection" fix />
|
<div style="margin-left: 10px">
|
||||||
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip prop="name" />
|
<span class="input-help">{{ $t('website.supportUpType') }}</span>
|
||||||
<el-table-column :label="$t('file.size')" prop="size">
|
<span class="input-help">
|
||||||
<template #default="{ row }">
|
{{ $t('website.zipFormat') }}
|
||||||
{{ computeSize(row.size) }}
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-divider />
|
||||||
|
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
|
||||||
|
<template #toolbar>
|
||||||
|
<el-button
|
||||||
|
style="margin-left: 10px"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:disabled="selects.length === 0"
|
||||||
|
@click="onBatchDelete(null)"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
<el-table-column type="selection" fix />
|
||||||
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
|
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip prop="name" />
|
||||||
<template #default="{ row }">
|
<el-table-column :label="$t('file.size')" prop="size">
|
||||||
{{ dateFromat(0, 0, row.modTime) }}
|
<template #default="{ row }">
|
||||||
</template>
|
{{ computeSize(row.size) }}
|
||||||
</el-table-column>
|
</template>
|
||||||
<fu-table-operations
|
</el-table-column>
|
||||||
width="300px"
|
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
|
||||||
:buttons="buttons"
|
<template #default="{ row }">
|
||||||
:ellipsis="10"
|
{{ dateFromat(0, 0, row.modTime) }}
|
||||||
:label="$t('commons.table.operate')"
|
</template>
|
||||||
fix
|
</el-table-column>
|
||||||
/>
|
<fu-table-operations
|
||||||
</ComplexTable>
|
width="300px"
|
||||||
|
:buttons="buttons"
|
||||||
|
:ellipsis="10"
|
||||||
|
:label="$t('commons.table.operate')"
|
||||||
|
fix
|
||||||
|
/>
|
||||||
|
</ComplexTable>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -68,14 +70,15 @@ import ComplexTable from '@/components/complex-table/index.vue';
|
|||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { computeSize, dateFromat } from '@/utils/util';
|
import { computeSize, dateFromat } from '@/utils/util';
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
import { recoverByUpload } from '@/api/modules/database';
|
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElMessage, UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
|
import { ElMessage, 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';
|
||||||
|
|
||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
const baseDir = '/opt/1Panel/data/uploads/website/';
|
const baseDir = ref();
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
@ -85,16 +88,18 @@ const paginationConfig = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const upVisiable = ref(false);
|
const upVisiable = ref(false);
|
||||||
const mysqlName = ref();
|
const websiteName = ref();
|
||||||
const dbName = ref();
|
const websiteType = ref();
|
||||||
|
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
mysqlName: string;
|
websiteName: string;
|
||||||
dbName: string;
|
websiteType: string;
|
||||||
}
|
}
|
||||||
const acceptParams = (params: DialogProps): void => {
|
const acceptParams = (params: DialogProps): void => {
|
||||||
mysqlName.value = params.mysqlName;
|
websiteName.value = params.websiteName;
|
||||||
dbName.value = params.dbName;
|
websiteType.value = params.websiteType;
|
||||||
upVisiable.value = true;
|
upVisiable.value = true;
|
||||||
|
baseDir.value = '/opt/1Panel/data/uploads/website/' + websiteName.value;
|
||||||
search();
|
search();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,7 +107,7 @@ const search = async () => {
|
|||||||
let params = {
|
let params = {
|
||||||
page: paginationConfig.currentPage,
|
page: paginationConfig.currentPage,
|
||||||
pageSize: paginationConfig.pageSize,
|
pageSize: paginationConfig.pageSize,
|
||||||
path: baseDir,
|
path: baseDir.value,
|
||||||
expand: true,
|
expand: true,
|
||||||
};
|
};
|
||||||
const res = await GetFilesList(params);
|
const res = await GetFilesList(params);
|
||||||
@ -112,30 +117,29 @@ const search = async () => {
|
|||||||
|
|
||||||
const onRecover = async (row: File.File) => {
|
const onRecover = async (row: File.File) => {
|
||||||
let params = {
|
let params = {
|
||||||
mysqlName: mysqlName.value,
|
websiteName: websiteName.value,
|
||||||
dbName: dbName.value,
|
type: websiteType.value,
|
||||||
fileDir: baseDir,
|
fileDir: baseDir.value,
|
||||||
fileName: row.name,
|
fileName: row.name,
|
||||||
};
|
};
|
||||||
await recoverByUpload(params);
|
loading.value = true;
|
||||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
await RecoverWebsiteByUpload(params)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploaderFiles = ref<UploadFiles>([]);
|
const uploaderFiles = ref<UploadFiles>([]);
|
||||||
const uploadRef = ref<UploadInstance>();
|
const uploadRef = ref<UploadInstance>();
|
||||||
|
|
||||||
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||||
if (
|
if (rawFile.name.endsWith('.tar.gz')) {
|
||||||
rawFile.name.endsWith('.sql') ||
|
|
||||||
rawFile.name.endsWith('.gz') ||
|
|
||||||
rawFile.name.endsWith('.zip') ||
|
|
||||||
rawFile.name.endsWith('.tgz')
|
|
||||||
) {
|
|
||||||
ElMessage.error(i18n.global.t('database.unSupportType'));
|
ElMessage.error(i18n.global.t('database.unSupportType'));
|
||||||
return false;
|
return false;
|
||||||
} else if (rawFile.size / 1024 / 1024 > 10) {
|
|
||||||
ElMessage.error(i18n.global.t('database.unSupportSize'));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@ -156,21 +160,27 @@ const onSubmit = () => {
|
|||||||
if (uploaderFiles.value[0]!.raw != undefined) {
|
if (uploaderFiles.value[0]!.raw != undefined) {
|
||||||
formData.append('file', uploaderFiles.value[0]!.raw);
|
formData.append('file', uploaderFiles.value[0]!.raw);
|
||||||
}
|
}
|
||||||
formData.append('path', baseDir);
|
formData.append('path', baseDir.value + '/');
|
||||||
UploadFileData(formData, {}).then(() => {
|
loading.value = true;
|
||||||
ElMessage.success(i18n.global.t('file.uploadSuccess'));
|
UploadFileData(formData, {})
|
||||||
handleClose();
|
.then(() => {
|
||||||
search();
|
loading.value = false;
|
||||||
});
|
ElMessage.success(i18n.global.t('file.uploadSuccess'));
|
||||||
|
handleClose();
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBatchDelete = async (row: File.File | null) => {
|
const onBatchDelete = async (row: File.File | null) => {
|
||||||
let files: Array<string> = [];
|
let files: Array<string> = [];
|
||||||
if (row) {
|
if (row) {
|
||||||
files.push(baseDir + row.name);
|
files.push(baseDir.value + '/' + row.name);
|
||||||
} else {
|
} else {
|
||||||
selects.value.forEach((item: File.File) => {
|
selects.value.forEach((item: File.File) => {
|
||||||
files.push(baseDir + item.name);
|
files.push(baseDir.value + '/' + item.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await useDeleteData(BatchDeleteFile, { isDir: false, paths: files }, 'commons.msg.delete', true);
|
await useDeleteData(BatchDeleteFile, { isDir: false, paths: files }, 'commons.msg.delete', true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user