fix: 封装导入备份组件

This commit is contained in:
ssongliu 2023-03-02 13:54:07 +08:00 committed by ssongliu
parent 025b98b4f1
commit ae295794d3
44 changed files with 1494 additions and 2042 deletions

View File

@ -20,7 +20,7 @@ import (
// @Param request body request.AppInstalledSearch true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /apps/installed [post]
// @Router /apps/installed/search [post]
func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
var req request.AppInstalledSearch
if err := c.ShouldBindJSON(&req); err != nil {
@ -151,31 +151,6 @@ func (b *BaseApi) SyncInstalled(c *gin.Context) {
helper.SuccessWithData(c, "")
}
// @Tags App
// @Summary Page installed backups
// @Description 查询已安装备份列表分页
// @Accept json
// @Param request body request.AppBackupSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /apps/installed/backups [post]
func (b *BaseApi) SearchInstalledBackup(c *gin.Context) {
var req request.AppBackupSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := appInstallService.PageInstallBackups(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags App
// @Summary Operate installed app
// @Description 操作已安装应用
@ -198,28 +173,6 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags App
// @Summary Delete app backup record
// @Description 删除应用备份记录
// @Accept json
// @Param request body request.AppBackupDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /apps/installed/backups/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"app_install_backups","output_colume":"name","output_value":"names"}],"formatZH":"删除应用备份 [names]","formatEN":"Deleting an Application Backup [names]"}
func (b *BaseApi) DeleteAppBackup(c *gin.Context) {
var req request.AppBackupDelete
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := appInstallService.DeleteBackup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags App
// @Summary Search app service by key
// @Description 通过 key 获取应用 service

View File

@ -354,6 +354,11 @@ func (b *BaseApi) RecoverByUpload(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case "app":
if err := backupService.AppRecover(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)

View File

@ -44,6 +44,35 @@ func (b *BaseApi) ListFiles(c *gin.Context) {
helper.SuccessWithData(c, files)
}
// @Tags File
// @Summary Page file
// @Description 分页获取上传文件
// @Accept json
// @Param request body request.SearchUploadWithPage true "request"
// @Success 200 {anrry} response.FileInfo
// @Security ApiKeyAuth
// @Router /files/upload/search [post]
func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
var req request.SearchUploadWithPage
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
}
total, files, err := fileService.SearchUploadWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: files,
Total: total,
})
}
// @Tags File
// @Summary Load files tree
// @Description 加载文件树

View File

@ -1,11 +1,19 @@
package request
import "github.com/1Panel-dev/1Panel/backend/utils/files"
import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/utils/files"
)
type FileOption struct {
files.FileOption
}
type SearchUploadWithPage struct {
dto.PageInfo
Path string `json:"path" validate:"required"`
}
type FileCreate struct {
Path string `json:"path" validate:"required"`
Content string `json:"content"`

View File

@ -1,11 +1,19 @@
package response
import "github.com/1Panel-dev/1Panel/backend/utils/files"
import (
"github.com/1Panel-dev/1Panel/backend/utils/files"
)
type FileInfo struct {
files.FileInfo
}
type UploadInfo struct {
Name string `json:"name"`
Size int `json:"size"`
CreatedAt string `json:"createdAt"`
}
type FileTree struct {
ID string `json:"id"`
Name string `json:"name"`

View File

@ -1,28 +1,28 @@
package model
import (
"github.com/1Panel-dev/1Panel/backend/constant"
"path"
"github.com/1Panel-dev/1Panel/backend/constant"
)
type AppInstall struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(64);not null;UNIQUE"`
AppId uint `json:"appId" gorm:"type:integer;not null"`
AppDetailId uint `json:"appDetailId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Param string `json:"param" gorm:"type:longtext;"`
Env string `json:"env" gorm:"type:longtext;"`
DockerCompose string `json:"dockerCompose" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(256);not null"`
Description string `json:"description" gorm:"type:varchar(256);"`
Message string `json:"message" gorm:"type:longtext;"`
ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"`
ServiceName string `json:"serviceName" gorm:"type:varchar(256);not null"`
HttpPort int `json:"httpPort" gorm:"type:integer;not null"`
HttpsPort int `json:"httpsPort" gorm:"type:integer;not null"`
App App `json:"app" gorm:"-:migration"`
Backups []AppInstallBackup `json:"backups" gorm:"-:migration"`
Name string `json:"name" gorm:"type:varchar(64);not null;UNIQUE"`
AppId uint `json:"appId" gorm:"type:integer;not null"`
AppDetailId uint `json:"appDetailId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Param string `json:"param" gorm:"type:longtext;"`
Env string `json:"env" gorm:"type:longtext;"`
DockerCompose string `json:"dockerCompose" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(256);not null"`
Description string `json:"description" gorm:"type:varchar(256);"`
Message string `json:"message" gorm:"type:longtext;"`
ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"`
ServiceName string `json:"serviceName" gorm:"type:varchar(256);not null"`
HttpPort int `json:"httpPort" gorm:"type:integer;not null"`
HttpsPort int `json:"httpsPort" gorm:"type:integer;not null"`
App App `json:"app" gorm:"-:migration"`
}
func (i *AppInstall) GetPath() string {

View File

@ -1,11 +0,0 @@
package model
type AppInstallBackup struct {
BaseModel
Name string `gorm:"type:varchar(64);not null" json:"name"`
Path string `gorm:"type:varchar(64);not null" json:"path"`
Param string `gorm:"type:longtext;" json:"param"`
AppDetailId uint `gorm:"type:integer;not null" json:"app_detail_id"`
AppInstallId uint `gorm:"type:integer;not null" json:"app_install_id"`
AppDetail AppDetail `json:"-" gorm:"-:migration"`
}

View File

@ -1,49 +0,0 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type AppInstallBackupRepo struct {
}
func (a AppInstallBackupRepo) WithAppInstallID(appInstallID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("app_install_id = ?", appInstallID)
}
}
func (a AppInstallBackupRepo) Create(ctx context.Context, backup model.AppInstallBackup) error {
return getTx(ctx).Omit(clause.Associations).Create(&backup).Error
}
func (a AppInstallBackupRepo) Delete(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Omit(clause.Associations).Delete(&model.AppInstallBackup{}).Error
}
func (a AppInstallBackupRepo) GetBy(opts ...DBOption) ([]model.AppInstallBackup, error) {
var backups []model.AppInstallBackup
if err := getDb(opts...).Preload("AppDetail").Find(&backups); err != nil {
return backups, nil
}
return backups, nil
}
func (a AppInstallBackupRepo) GetFirst(opts ...DBOption) (model.AppInstallBackup, error) {
var backup model.AppInstallBackup
db := getDb(opts...).Model(&model.AppInstallBackup{})
err := db.Preload("AppDetail").First(&backup).Error
return backup, err
}
func (a AppInstallBackupRepo) Page(page, size int, opts ...DBOption) (int64, []model.AppInstallBackup, error) {
var backups []model.AppInstallBackup
db := getDb(opts...).Model(&model.AppInstallBackup{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Preload("AppDetail").Find(&backups).Error
return count, backups, err
}

View File

@ -8,7 +8,6 @@ type RepoGroup struct {
AppDetailRepo
AppInstallRepo
AppInstallResourceRpo
AppInstallBackupRepo
ImageRepoRepo
ComposeTemplateRepo
MysqlRepo

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@ -20,6 +21,7 @@ type IWebsiteRepo interface {
GetFirst(opts ...DBOption) (model.Website, error)
GetBy(opts ...DBOption) ([]model.Website, error)
Save(ctx context.Context, app *model.Website) error
SaveWithoutCtx(app *model.Website) error
DeleteBy(ctx context.Context, opts ...DBOption) error
Create(ctx context.Context, app *model.Website) error
}
@ -108,6 +110,10 @@ func (w *WebsiteRepo) Save(ctx context.Context, app *model.Website) error {
return getTx(ctx).Omit(clause.Associations).Save(app).Error
}
func (w *WebsiteRepo) SaveWithoutCtx(website *model.Website) error {
return global.DB.Save(website).Error
}
func (w *WebsiteRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.Website{}).Error
}

View File

@ -1,7 +1,6 @@
package service
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
@ -23,7 +22,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
)
@ -96,9 +94,6 @@ func (a AppInstallService) CheckExist(key string) (*response.AppInstalledCheck,
res.AppInstallID = appInstall.ID
res.IsExist = true
res.InstallPath = path.Join(constant.AppInstallDir, app.Key, appInstall.Name)
if len(appInstall.Backups) > 0 {
res.LastBackupAt = appInstall.Backups[0].CreatedAt.Format("2006-01-02 15:04:05")
}
return res, nil
}
@ -224,34 +219,6 @@ func (a AppInstallService) SyncAll() error {
return nil
}
func (a AppInstallService) PageInstallBackups(req request.AppBackupSearch) (int64, []model.AppInstallBackup, error) {
return appInstallBackupRepo.Page(req.Page, req.PageSize, appInstallBackupRepo.WithAppInstallID(req.AppInstallID))
}
func (a AppInstallService) DeleteBackup(req request.AppBackupDelete) error {
backups, err := appInstallBackupRepo.GetBy(commonRepo.WithIdsIn(req.Ids))
if err != nil {
return err
}
fileOp := files.NewFileOp()
var errStr strings.Builder
for _, backup := range backups {
dst := path.Join(backup.Path, backup.Name)
if err := fileOp.DeleteFile(dst); err != nil {
errStr.WriteString(err.Error())
continue
}
if err := appInstallBackupRepo.Delete(context.TODO(), commonRepo.WithIdsIn(req.Ids)); err != nil {
errStr.WriteString(err.Error())
}
}
if errStr.String() != "" {
return errors.New(errStr.String())
}
return nil
}
func (a AppInstallService) GetServices(key string) ([]response.AppService, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {

View File

@ -3,6 +3,7 @@ package service
import (
"context"
"encoding/json"
"fmt"
"math"
"os"
"path"
@ -128,15 +129,23 @@ func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBacku
if err := deleteLink(ctx, &install); err != nil && !forceDelete {
return err
}
uploadDir := fmt.Sprintf("%s/1panel/uploads/app/%s/%s", global.CONF.System.BaseDir, install.App.Key, install.Name)
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
if deleteBackup {
backups, _ := appInstallBackupRepo.GetBy(appInstallBackupRepo.WithAppInstallID(install.ID))
for _, backup := range backups {
_ = op.DeleteDir(backup.Path)
}
if err := appInstallBackupRepo.Delete(ctx, appInstallBackupRepo.WithAppInstallID(install.ID)); err != nil && !forceDelete {
localDir, err := loadLocalDir()
if err != nil && !forceDelete {
return err
}
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name)
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
global.LOG.Infof("delete app %s-%s backups successful", install.App.Key, install.Name)
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
return nil
}

View File

@ -41,7 +41,6 @@ type IBackupService interface {
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

View File

@ -6,7 +6,6 @@ import (
"io/fs"
"os"
"path"
"strconv"
"strings"
"time"
@ -16,9 +15,7 @@ import (
"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 {
@ -82,12 +79,6 @@ func (u *BackupService) AppRecover(req dto.CommonRecover) error {
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", ""))
@ -100,11 +91,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
_ = os.RemoveAll(tmpDir)
}()
var appInfo AppInfo
appInfo.Param = install.Param
appInfo.AppDetailId = install.AppDetailId
appInfo.Version = install.Version
remarkInfo, _ := json.Marshal(appInfo)
remarkInfo, _ := json.Marshal(install)
remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir)
if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil {
return err
@ -114,6 +101,22 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
if err := fileOp.Compress([]string{appPath}, tmpDir, "app.tar.gz", files.TarGz); err != nil {
return err
}
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
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", install.Name)); err != nil {
return err
}
}
if err := fileOp.Compress([]string{tmpDir}, backupDir, fileName, files.TarGz); err != nil {
return err
}
@ -123,17 +126,30 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback bool) error {
isOk := false
fileOp := files.NewFileOp()
if err := fileOp.Decompress(recoverFile, path.Dir(recoverFile), files.TarGz); err != nil {
if err := handleUnTar(recoverFile, path.Dir(recoverFile)); err != nil {
return err
}
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
defer func() {
_, _ = compose.Up(install.GetComposePath())
_ = 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")
}
var oldInstall model.AppInstall
appjson, err := os.ReadFile(tmpPath + "/app.json")
if err != nil {
return err
}
if err := json.Unmarshal(appjson, &oldInstall); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
}
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name || oldInstall.Version != install.Version || oldInstall.ID != install.ID {
return errors.New("the current backup file does not match the application")
}
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/app/%s_%s.tar.gz", global.CONF.System.BaseDir, install.Name, time.Now().Format("20060102150405"))
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
@ -143,6 +159,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
if !isOk {
if err := handleAppRecover(install, rollbackFile, true); err != nil {
global.LOG.Errorf("rollback app %s from %s failed, err: %v", install.Name, rollbackFile, err)
return
}
global.LOG.Infof("rollback app %s from %s successful", install.Name, rollbackFile)
_ = os.RemoveAll(rollbackFile)
@ -152,65 +169,26 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}()
}
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
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 && install.App.Key != "mysql" {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handleMysqlRecover(mysqlInfo, tmpPath, db.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
return err
}
return errors.New(out)
}
install.AppDetailId = appInfo.AppDetailId
install.Version = appInfo.Version
install.Status = constant.Running
if err := handleUnTar(tmpPath+"/app.tar.gz", fmt.Sprintf("%s/%s", constant.AppInstallDir, install.App.Key)); err != nil {
return err
}
oldInstall.Status = constant.Running
if err := appInstallRepo.Save(install); err != nil {
return err
}

View File

@ -152,6 +152,7 @@ func handleMysqlRecover(mysqlInfo *repo.RootInfo, recoverDir, dbName, fileName s
if !isOk {
if err := handleMysqlRecover(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile), true); err != nil {
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", dbName, rollbackFile, err)
return
}
global.LOG.Infof("rollback mysql db %s from %s successful", dbName, rollbackFile)
_ = os.RemoveAll(rollbackFile)

View File

@ -113,26 +113,25 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
isOk := false
if !isRollback {
suffix := "tar.gz"
if appendonly != "yes" {
suffix = "rdb"
}
rollbackFile := fmt.Sprintf("%s/original/database/redis/%s_%s.%s", global.CONF.System.BaseDir, redisInfo.Name, time.Now().Format("20060102150405"), suffix)
if err := handleRedisBackup(redisInfo, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
global.LOG.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err)
}
defer func() {
suffix := "tar.gz"
if appendonly != "yes" {
suffix = "rdb"
}
rollbackFile := fmt.Sprintf("%s/original/database/redis/%s_%s.%s", global.CONF.System.BaseDir, redisInfo.Name, time.Now().Format("20060102150405"), suffix)
if err := handleRedisBackup(redisInfo, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
global.LOG.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err)
}
defer func() {
if !isOk {
if err := handleRedisRecover(redisInfo, rollbackFile, true); err != nil {
global.LOG.Errorf("rollback redis from %s failed, err: %v", rollbackFile, err)
}
global.LOG.Infof("rollback redis from %s successful", rollbackFile)
_ = os.RemoveAll(rollbackFile)
} else {
_ = os.RemoveAll(rollbackFile)
if !isOk {
if err := handleRedisRecover(redisInfo, rollbackFile, true); err != nil {
global.LOG.Errorf("rollback redis from %s failed, err: %v", rollbackFile, err)
return
}
}()
global.LOG.Infof("rollback redis from %s successful", rollbackFile)
_ = os.RemoveAll(rollbackFile)
} else {
_ = os.RemoveAll(rollbackFile)
}
}()
}
composeDir := fmt.Sprintf("%s/redis/%s", constant.AppInstallDir, redisInfo.Name)

View File

@ -52,34 +52,6 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
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, false); 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 {
@ -98,23 +70,35 @@ func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool) error {
fileOp := files.NewFileOp()
fileDir := strings.ReplaceAll(recoverFile, ".tar.gz", "")
if err := fileOp.Decompress(recoverFile, path.Dir(recoverFile), files.TarGz); err != nil {
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
if err := handleUnTar(recoverFile, path.Dir(recoverFile)); err != nil {
return err
}
defer func() {
_ = os.RemoveAll(fileDir)
_ = os.RemoveAll(tmpPath)
}()
itemDir := fmt.Sprintf("%s/%s", fileDir, website.Alias)
if !fileOp.Stat(itemDir+".conf") || !fileOp.Stat(itemDir+".web.tar.gz") {
temPathWithName := tmpPath + "/" + website.Alias
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".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")
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
return errors.New("the wrong recovery package does not have .app.tar.gz files")
}
}
var oldWebsite model.Website
websiteJson, err := os.ReadFile(tmpPath + "/website.json")
if err != nil {
return err
}
if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
}
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type || oldWebsite.ID != website.ID {
return errors.New("the current backup file does not match the application")
}
isOk := false
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/website/%s_%s.tar.gz", global.CONF.System.BaseDir, website.Alias, time.Now().Format("20060102150405"))
@ -125,6 +109,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
if !isOk {
if err := handleWebsiteRecover(website, rollbackFile, true); err != nil {
global.LOG.Errorf("rollback website %s from %s failed, err: %v", website.Alias, rollbackFile, err)
return
}
global.LOG.Infof("rollback website %s from %s successful", website.Alias, rollbackFile)
_ = os.RemoveAll(rollbackFile)
@ -139,31 +124,16 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
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 {
if err := fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, 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), isRollback); 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), isRollback); err != nil {
if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true); 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 {
@ -171,7 +141,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
}
}
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 {
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), siteDir); err != nil {
return err
}
stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName)
@ -179,14 +149,12 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
return errors.New(string(stdout))
}
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
return err
}
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", ""))
@ -199,14 +167,11 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err
_ = os.RemoveAll(tmpDir)
}()
var websiteInfo WebsiteInfo
websiteInfo.WebsiteType = website.Type
websiteInfo.WebsiteName = website.PrimaryDomain
remarkInfo, _ := json.Marshal(websiteInfo)
remarkInfo, _ := json.Marshal(website)
if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil {
return err
}
global.LOG.Info("put websitejson into tmp dir successful")
global.LOG.Info("put website.json into tmp dir successful")
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
if err != nil {
@ -219,21 +184,6 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) 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
@ -241,13 +191,13 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) 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")
global.LOG.Info("put app.tar.gz 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")
global.LOG.Info("put web.tar.gz into tmp dir successful, now start to tar tmp dir")
if err := fileOp.Compress([]string{tmpDir}, backupDir, fileName, files.TarGz); err != nil {
return err
}

View File

@ -161,7 +161,7 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
}
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
uploadDir := fmt.Sprintf("%s/uploads/database/mysql/%s/%s", constant.DataDir, app.Name, db.Name)
uploadDir := fmt.Sprintf("%s/1panel/uploads/database/mysql/%s/%s", global.CONF.System.BaseDir, app.Name, db.Name)
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}

View File

@ -46,7 +46,6 @@ var ServiceGroupApp = new(ServiceGroup)
var (
commonRepo = repo.RepoGroupApp.CommonRepo
appInstallBackupRepo = repo.RepoGroupApp.AppInstallBackupRepo
appRepo = repo.RepoGroupApp.AppRepo
appTagRepo = repo.RepoGroupApp.AppTagRepo
appDetailRepo = repo.RepoGroupApp.AppDetailRepo

View File

@ -2,16 +2,17 @@ package service
import (
"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/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"io/fs"
"os"
"path/filepath"
"strings"
"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/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
@ -35,6 +36,37 @@ func (f FileService) GetFileList(op request.FileOption) (response.FileInfo, erro
return fileInfo, nil
}
func (f FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error) {
var (
files []response.UploadInfo
backDatas []response.UploadInfo
)
_ = filepath.Walk(req.Path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
files = append(files, response.UploadInfo{
CreatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
Size: int(info.Size()),
Name: info.Name(),
})
}
return nil
})
total, start, end := len(files), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]response.UploadInfo, 0)
} else {
if end >= total {
end = total
}
backDatas = files[start:end]
}
return int64(total), backDatas, nil
}
func (f FileService) GetFileTree(op request.FileOption) ([]response.FileTree, error) {
var treeArray []response.FileTree
info, err := files.NewFileInfo(op.FileOption)

View File

@ -15,6 +15,7 @@ import (
"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/global"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
@ -257,18 +258,23 @@ func (w WebsiteService) DeleteWebsite(ctx context.Context, req request.WebsiteDe
}
}
}
if req.DeleteBackup {
backups, _ := backupRepo.ListRecord(backupRepo.WithByType("website-"+website.Type), commonRepo.WithByName(website.PrimaryDomain))
if len(backups) > 0 {
fileOp := files.NewFileOp()
for _, b := range backups {
_ = fileOp.DeleteDir(b.FileDir)
}
if err := backupRepo.DeleteRecord(ctx, backupRepo.WithByType("website-"+website.Type), commonRepo.WithByName(website.PrimaryDomain)); err != nil {
return err
}
}
uploadDir := fmt.Sprintf("%s/1panel/uploads/website/%s", global.CONF.System.BaseDir, website.Alias)
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
if req.DeleteBackup {
localDir, err := loadLocalDir()
if err != nil && !req.ForceDelete {
return err
}
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.Alias)
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
global.LOG.Infof("delete website %s backups successful", website.Alias)
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("website"), commonRepo.WithByName(website.Alias))
if err := websiteRepo.DeleteBy(ctx, commonRepo.WithByID(req.ID)); err != nil {
return err

View File

@ -178,7 +178,7 @@ var AddTableCronjob = &gormigrate.Migration{
var AddTableApp = &gormigrate.Migration{
ID: "20200921-add-table-app",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{}, &model.AppInstallBackup{})
return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{})
},
}

View File

@ -29,8 +29,6 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.POST("/installed/search", baseApi.SearchAppInstalled)
appRouter.POST("/installed/op", baseApi.OperateInstalled)
appRouter.POST("/installed/sync", baseApi.SyncInstalled)
appRouter.POST("/installed/backups", baseApi.SearchInstalledBackup)
appRouter.POST("/installed/backups/del", baseApi.DeleteAppBackup)
appRouter.POST("/installed/port/change", baseApi.ChangeAppPort)
appRouter.GET("/services/:key", baseApi.GetServices)
appRouter.GET("/installed/conf/:key", baseApi.GetDefaultConfig)

View File

@ -15,6 +15,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
fileRouter.POST("/search", baseApi.ListFiles)
fileRouter.POST("/upload/search", baseApi.SearchUploadWithPage)
fileRouter.POST("/tree", baseApi.GetFileTree)
fileRouter.POST("", baseApi.CreateFile)
fileRouter.POST("/del", baseApi.DeleteFile)

View File

@ -3117,49 +3117,6 @@ var doc = `{
}
}
},
"/databases/backup": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份 mysql 数据库",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Backup mysql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BackupDB"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"mysqlName",
"dbName"
],
"formatEN": "backup mysql database [mysqlName][dbName]",
"formatZH": "备份 mysql 数据库 [mysqlName][dbName]",
"paramKeys": []
}
}
},
"/databases/baseinfo": {
"get": {
"security": [
@ -3499,121 +3456,6 @@ var doc = `{
}
}
},
"/databases/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Mysql 数据库恢复",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Recover mysql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RecoverDB"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"mysqlName",
"dbName",
"backupName"
],
"formatEN": "恢复 mysql 数据库 [mysqlName][dbName] [backupName]",
"formatZH": "恢复 mysql 数据库 [mysqlName][dbName] [backupName]",
"paramKeys": []
}
}
},
"/databases/recover/byupload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Mysql 数据库从上传文件恢复",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Recover mysql database by upload file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UploadRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"fileDir",
"fileName",
"mysqlName",
"dbName"
],
"formatEN": "mysql database recover [fileDir]/[fileName] from [mysqlName][dbName]",
"formatZH": "mysql 数据库从 [fileDir]/[fileName] 恢复 [mysqlName][dbName]",
"paramKeys": []
}
}
},
"/databases/redis/backup": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份 redis 数据库",
"tags": [
"Database Redis"
],
"summary": "Backup redis",
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [],
"formatEN": "backup redis database",
"formatZH": "备份 redis 数据库",
"paramKeys": []
}
}
},
"/databases/redis/backup/search": {
"post": {
"security": [
@ -3854,35 +3696,6 @@ var doc = `{
}
}
},
"/databases/redis/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "恢复 redis 数据库",
"tags": [
"Database Redis"
],
"summary": "Recover redis",
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"fileDir",
"fileName"
],
"formatEN": "redis database recover from [fileDir]/[fileName]",
"formatZH": "redis 数据库从 [fileDir]/[fileName] 恢复",
"paramKeys": []
}
}
},
"/databases/redis/status": {
"get": {
"security": [
@ -4711,6 +4524,42 @@ var doc = `{
}
}
},
"/files/upload/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "分页获取上传文件",
"consumes": [
"application/json"
],
"tags": [
"File"
],
"summary": "Page file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.SearchUploadWithPage"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
}
},
"/files/wget": {
"post": {
"security": [
@ -5046,7 +4895,7 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperateByID"
"$ref": "#/definitions/dto.BatchDeleteReq"
}
}
],
@ -5060,17 +4909,17 @@ var doc = `{
{
"db": "hosts",
"input_colume": "id",
"input_value": "id",
"isList": false,
"input_value": "ids",
"isList": true,
"output_colume": "addr",
"output_value": "addr"
"output_value": "addrs"
}
],
"bodyKeys": [
"id"
"ids"
],
"formatEN": "delete host [addr]",
"formatZH": "删除主机 [addr]",
"formatEN": "delete host [addrs]",
"formatZH": "删除主机 [addrs]",
"paramKeys": []
}
}
@ -5255,14 +5104,14 @@ var doc = `{
"ApiKeyAuth": []
}
],
"description": "加载主机树",
"description": "获取主机列表分页",
"consumes": [
"application/json"
],
"tags": [
"Host"
],
"summary": "Load host tree",
"summary": "Page host",
"parameters": [
{
"description": "request",
@ -5270,7 +5119,7 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SearchForTree"
"$ref": "#/definitions/dto.SearchHostWithPage"
}
}
],
@ -5351,21 +5200,21 @@ var doc = `{
}
}
},
"/hosts/update": {
"/hosts/tree": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新主机",
"description": "加载主机树",
"consumes": [
"application/json"
],
"tags": [
"Host"
],
"summary": "Update host",
"summary": "Load host tree",
"parameters": [
{
"description": "request",
@ -5373,7 +5222,43 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.HostOperate"
"$ref": "#/definitions/dto.SearchForTree"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
}
},
"/hosts/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "切换分组",
"consumes": [
"application/json"
],
"tags": [
"Host"
],
"summary": "Update host group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ChangeHostGroup"
}
}
],
@ -5383,13 +5268,22 @@ var doc = `{
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name",
"addr"
"BeforeFuntions": [
{
"db": "hosts",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "addr",
"output_value": "addr"
}
],
"formatEN": "update host [name][addr]",
"formatZH": "更新主机信息 [name][addr]",
"bodyKeys": [
"id",
"group"
],
"formatEN": "change host [addr] group =\u003e [group]",
"formatZH": "切换主机[addr]分组 =\u003e [group]",
"paramKeys": []
}
}
@ -5724,6 +5618,50 @@ var doc = `{
}
}
},
"/settings/backup/": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份系统数据",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "Backup system data",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.CommonBackup"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type",
"name",
"detailName"
],
"formatEN": "backup [type] data [name][detailName]",
"formatZH": "备份 [type] 数据 [name][detailName]",
"paramKeys": []
}
}
},
"/settings/backup/del": {
"post": {
"security": [
@ -5902,6 +5840,96 @@ var doc = `{
}
}
},
"/settings/backup/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "恢复系统数据",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "Recover system data",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.CommonRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type",
"name",
"detailName",
"file"
],
"formatEN": "recover [type] data [name][detailName] from [file]",
"formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]",
"paramKeys": []
}
}
},
"/settings/backup/recover/byupload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从上传恢复系统数据",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "Recover system data by upload",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.CommonRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type",
"name",
"detailName",
"file"
],
"formatEN": "recover [type] data [name][detailName] from [file]",
"formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]",
"paramKeys": []
}
}
},
"/settings/backup/search": {
"get": {
"security": [
@ -7098,57 +7126,6 @@ var doc = `{
}
}
},
"/websites/backup": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份网站",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Backup website",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Backup website [domain]",
"formatZH": "备份网站 [domain]",
"paramKeys": []
}
}
},
"/websites/check": {
"post": {
"security": [
@ -8033,93 +8010,6 @@ var doc = `{
}
}
},
"/websites/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从备份恢复网站",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Recover website",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"websiteName",
"backupName"
],
"formatEN": "[websiteName] recover from backups [backupName]",
"formatZH": "[websiteName] 从备份恢复 [backupName]",
"paramKeys": []
}
}
},
"/websites/recover/byupload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从上传恢复网站",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Recover website by upload",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteRecoverByFile"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"websiteName",
"fileDir",
"fileName"
],
"formatEN": "[websiteName] recover from uploads [fileDir]/[fileName]",
"formatZH": "[websiteName] 从上传恢复 [fileDir]/[fileName]",
"paramKeys": []
}
}
},
"/websites/search": {
"post": {
"security": [
@ -8565,21 +8455,6 @@ var doc = `{
}
},
"definitions": {
"dto.BackupDB": {
"type": "object",
"required": [
"dbName",
"mysqlName"
],
"properties": {
"dbName": {
"type": "string"
},
"mysqlName": {
"type": "string"
}
}
},
"dto.BackupOperate": {
"type": "object",
"required": [
@ -8671,6 +8546,21 @@ var doc = `{
}
}
},
"dto.ChangeHostGroup": {
"type": "object",
"required": [
"group",
"id"
],
"properties": {
"group": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"dto.CleanLog": {
"type": "object",
"required": [
@ -8718,6 +8608,55 @@ var doc = `{
}
}
},
"dto.CommonBackup": {
"type": "object",
"required": [
"type"
],
"properties": {
"detailName": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"app",
"mysql",
"redis",
"website"
]
}
}
},
"dto.CommonRecover": {
"type": "object",
"required": [
"type"
],
"properties": {
"detailName": {
"type": "string"
},
"file": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"app",
"mysql",
"redis",
"website"
]
}
}
},
"dto.ComposeCreate": {
"type": "object",
"required": [
@ -10183,25 +10122,6 @@ var doc = `{
}
}
},
"dto.RecoverDB": {
"type": "object",
"required": [
"backupName",
"dbName",
"mysqlName"
],
"properties": {
"backupName": {
"type": "string"
},
"dbName": {
"type": "string"
},
"mysqlName": {
"type": "string"
}
}
},
"dto.RedisConf": {
"type": "object",
"properties": {
@ -10343,6 +10263,27 @@ var doc = `{
}
}
},
"dto.SearchHostWithPage": {
"type": "object",
"required": [
"page",
"pageSize"
],
"properties": {
"group": {
"type": "string"
},
"info": {
"type": "string"
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.SearchLgLogWithPage": {
"type": "object",
"required": [
@ -10608,27 +10549,6 @@ var doc = `{
}
}
},
"dto.UploadRecover": {
"type": "object",
"required": [
"dbName",
"mysqlName"
],
"properties": {
"dbName": {
"type": "string"
},
"fileDir": {
"type": "string"
},
"fileName": {
"type": "string"
},
"mysqlName": {
"type": "string"
}
}
},
"dto.UserLoginInfo": {
"type": "object",
"properties": {
@ -11565,6 +11485,25 @@ var doc = `{
}
}
},
"request.SearchUploadWithPage": {
"type": "object",
"required": [
"page",
"pageSize",
"path"
],
"properties": {
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
},
"path": {
"type": "string"
}
}
},
"request.WebsiteAcmeAccountCreate": {
"type": "object",
"required": [
@ -11890,48 +11829,6 @@ var doc = `{
}
}
},
"request.WebsiteRecover": {
"type": "object",
"required": [
"backupName",
"type",
"websiteName"
],
"properties": {
"backupName": {
"type": "string"
},
"type": {
"type": "string"
},
"websiteName": {
"type": "string"
}
}
},
"request.WebsiteRecoverByFile": {
"type": "object",
"required": [
"fileDir",
"fileName",
"type",
"websiteName"
],
"properties": {
"fileDir": {
"type": "string"
},
"fileName": {
"type": "string"
},
"type": {
"type": "string"
},
"websiteName": {
"type": "string"
}
}
},
"request.WebsiteResourceReq": {
"type": "object",
"required": [

View File

@ -3103,49 +3103,6 @@
}
}
},
"/databases/backup": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份 mysql 数据库",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Backup mysql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BackupDB"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"mysqlName",
"dbName"
],
"formatEN": "backup mysql database [mysqlName][dbName]",
"formatZH": "备份 mysql 数据库 [mysqlName][dbName]",
"paramKeys": []
}
}
},
"/databases/baseinfo": {
"get": {
"security": [
@ -3485,121 +3442,6 @@
}
}
},
"/databases/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Mysql 数据库恢复",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Recover mysql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RecoverDB"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"mysqlName",
"dbName",
"backupName"
],
"formatEN": "恢复 mysql 数据库 [mysqlName][dbName] [backupName]",
"formatZH": "恢复 mysql 数据库 [mysqlName][dbName] [backupName]",
"paramKeys": []
}
}
},
"/databases/recover/byupload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Mysql 数据库从上传文件恢复",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Recover mysql database by upload file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UploadRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"fileDir",
"fileName",
"mysqlName",
"dbName"
],
"formatEN": "mysql database recover [fileDir]/[fileName] from [mysqlName][dbName]",
"formatZH": "mysql 数据库从 [fileDir]/[fileName] 恢复 [mysqlName][dbName]",
"paramKeys": []
}
}
},
"/databases/redis/backup": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份 redis 数据库",
"tags": [
"Database Redis"
],
"summary": "Backup redis",
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [],
"formatEN": "backup redis database",
"formatZH": "备份 redis 数据库",
"paramKeys": []
}
}
},
"/databases/redis/backup/search": {
"post": {
"security": [
@ -3840,35 +3682,6 @@
}
}
},
"/databases/redis/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "恢复 redis 数据库",
"tags": [
"Database Redis"
],
"summary": "Recover redis",
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"fileDir",
"fileName"
],
"formatEN": "redis database recover from [fileDir]/[fileName]",
"formatZH": "redis 数据库从 [fileDir]/[fileName] 恢复",
"paramKeys": []
}
}
},
"/databases/redis/status": {
"get": {
"security": [
@ -4697,6 +4510,42 @@
}
}
},
"/files/upload/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "分页获取上传文件",
"consumes": [
"application/json"
],
"tags": [
"File"
],
"summary": "Page file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.SearchUploadWithPage"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
}
},
"/files/wget": {
"post": {
"security": [
@ -5032,7 +4881,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperateByID"
"$ref": "#/definitions/dto.BatchDeleteReq"
}
}
],
@ -5046,17 +4895,17 @@
{
"db": "hosts",
"input_colume": "id",
"input_value": "id",
"isList": false,
"input_value": "ids",
"isList": true,
"output_colume": "addr",
"output_value": "addr"
"output_value": "addrs"
}
],
"bodyKeys": [
"id"
"ids"
],
"formatEN": "delete host [addr]",
"formatZH": "删除主机 [addr]",
"formatEN": "delete host [addrs]",
"formatZH": "删除主机 [addrs]",
"paramKeys": []
}
}
@ -5241,14 +5090,14 @@
"ApiKeyAuth": []
}
],
"description": "加载主机树",
"description": "获取主机列表分页",
"consumes": [
"application/json"
],
"tags": [
"Host"
],
"summary": "Load host tree",
"summary": "Page host",
"parameters": [
{
"description": "request",
@ -5256,7 +5105,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SearchForTree"
"$ref": "#/definitions/dto.SearchHostWithPage"
}
}
],
@ -5337,21 +5186,21 @@
}
}
},
"/hosts/update": {
"/hosts/tree": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新主机",
"description": "加载主机树",
"consumes": [
"application/json"
],
"tags": [
"Host"
],
"summary": "Update host",
"summary": "Load host tree",
"parameters": [
{
"description": "request",
@ -5359,7 +5208,43 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.HostOperate"
"$ref": "#/definitions/dto.SearchForTree"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
}
},
"/hosts/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "切换分组",
"consumes": [
"application/json"
],
"tags": [
"Host"
],
"summary": "Update host group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ChangeHostGroup"
}
}
],
@ -5369,13 +5254,22 @@
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name",
"addr"
"BeforeFuntions": [
{
"db": "hosts",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "addr",
"output_value": "addr"
}
],
"formatEN": "update host [name][addr]",
"formatZH": "更新主机信息 [name][addr]",
"bodyKeys": [
"id",
"group"
],
"formatEN": "change host [addr] group =\u003e [group]",
"formatZH": "切换主机[addr]分组 =\u003e [group]",
"paramKeys": []
}
}
@ -5710,6 +5604,50 @@
}
}
},
"/settings/backup/": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份系统数据",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "Backup system data",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.CommonBackup"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type",
"name",
"detailName"
],
"formatEN": "backup [type] data [name][detailName]",
"formatZH": "备份 [type] 数据 [name][detailName]",
"paramKeys": []
}
}
},
"/settings/backup/del": {
"post": {
"security": [
@ -5888,6 +5826,96 @@
}
}
},
"/settings/backup/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "恢复系统数据",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "Recover system data",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.CommonRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type",
"name",
"detailName",
"file"
],
"formatEN": "recover [type] data [name][detailName] from [file]",
"formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]",
"paramKeys": []
}
}
},
"/settings/backup/recover/byupload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从上传恢复系统数据",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "Recover system data by upload",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.CommonRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"type",
"name",
"detailName",
"file"
],
"formatEN": "recover [type] data [name][detailName] from [file]",
"formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]",
"paramKeys": []
}
}
},
"/settings/backup/search": {
"get": {
"security": [
@ -7084,57 +7112,6 @@
}
}
},
"/websites/backup": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "备份网站",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Backup website",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Backup website [domain]",
"formatZH": "备份网站 [domain]",
"paramKeys": []
}
}
},
"/websites/check": {
"post": {
"security": [
@ -8019,93 +7996,6 @@
}
}
},
"/websites/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从备份恢复网站",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Recover website",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteRecover"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"websiteName",
"backupName"
],
"formatEN": "[websiteName] recover from backups [backupName]",
"formatZH": "[websiteName] 从备份恢复 [backupName]",
"paramKeys": []
}
}
},
"/websites/recover/byupload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从上传恢复网站",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Recover website by upload",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteRecoverByFile"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"websiteName",
"fileDir",
"fileName"
],
"formatEN": "[websiteName] recover from uploads [fileDir]/[fileName]",
"formatZH": "[websiteName] 从上传恢复 [fileDir]/[fileName]",
"paramKeys": []
}
}
},
"/websites/search": {
"post": {
"security": [
@ -8551,21 +8441,6 @@
}
},
"definitions": {
"dto.BackupDB": {
"type": "object",
"required": [
"dbName",
"mysqlName"
],
"properties": {
"dbName": {
"type": "string"
},
"mysqlName": {
"type": "string"
}
}
},
"dto.BackupOperate": {
"type": "object",
"required": [
@ -8657,6 +8532,21 @@
}
}
},
"dto.ChangeHostGroup": {
"type": "object",
"required": [
"group",
"id"
],
"properties": {
"group": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"dto.CleanLog": {
"type": "object",
"required": [
@ -8704,6 +8594,55 @@
}
}
},
"dto.CommonBackup": {
"type": "object",
"required": [
"type"
],
"properties": {
"detailName": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"app",
"mysql",
"redis",
"website"
]
}
}
},
"dto.CommonRecover": {
"type": "object",
"required": [
"type"
],
"properties": {
"detailName": {
"type": "string"
},
"file": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"app",
"mysql",
"redis",
"website"
]
}
}
},
"dto.ComposeCreate": {
"type": "object",
"required": [
@ -10169,25 +10108,6 @@
}
}
},
"dto.RecoverDB": {
"type": "object",
"required": [
"backupName",
"dbName",
"mysqlName"
],
"properties": {
"backupName": {
"type": "string"
},
"dbName": {
"type": "string"
},
"mysqlName": {
"type": "string"
}
}
},
"dto.RedisConf": {
"type": "object",
"properties": {
@ -10329,6 +10249,27 @@
}
}
},
"dto.SearchHostWithPage": {
"type": "object",
"required": [
"page",
"pageSize"
],
"properties": {
"group": {
"type": "string"
},
"info": {
"type": "string"
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.SearchLgLogWithPage": {
"type": "object",
"required": [
@ -10594,27 +10535,6 @@
}
}
},
"dto.UploadRecover": {
"type": "object",
"required": [
"dbName",
"mysqlName"
],
"properties": {
"dbName": {
"type": "string"
},
"fileDir": {
"type": "string"
},
"fileName": {
"type": "string"
},
"mysqlName": {
"type": "string"
}
}
},
"dto.UserLoginInfo": {
"type": "object",
"properties": {
@ -11551,6 +11471,25 @@
}
}
},
"request.SearchUploadWithPage": {
"type": "object",
"required": [
"page",
"pageSize",
"path"
],
"properties": {
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
},
"path": {
"type": "string"
}
}
},
"request.WebsiteAcmeAccountCreate": {
"type": "object",
"required": [
@ -11876,48 +11815,6 @@
}
}
},
"request.WebsiteRecover": {
"type": "object",
"required": [
"backupName",
"type",
"websiteName"
],
"properties": {
"backupName": {
"type": "string"
},
"type": {
"type": "string"
},
"websiteName": {
"type": "string"
}
}
},
"request.WebsiteRecoverByFile": {
"type": "object",
"required": [
"fileDir",
"fileName",
"type",
"websiteName"
],
"properties": {
"fileDir": {
"type": "string"
},
"fileName": {
"type": "string"
},
"type": {
"type": "string"
},
"websiteName": {
"type": "string"
}
}
},
"request.WebsiteResourceReq": {
"type": "object",
"required": [

View File

@ -1,15 +1,5 @@
basePath: /api/v1
definitions:
dto.BackupDB:
properties:
dbName:
type: string
mysqlName:
type: string
required:
- dbName
- mysqlName
type: object
dto.BackupOperate:
properties:
accessKey:
@ -69,6 +59,16 @@ definitions:
required:
- value
type: object
dto.ChangeHostGroup:
properties:
group:
type: string
id:
type: integer
required:
- group
- id
type: object
dto.CleanLog:
properties:
logType:
@ -100,6 +100,40 @@ definitions:
- command
- name
type: object
dto.CommonBackup:
properties:
detailName:
type: string
name:
type: string
type:
enum:
- app
- mysql
- redis
- website
type: string
required:
- type
type: object
dto.CommonRecover:
properties:
detailName:
type: string
file:
type: string
name:
type: string
type:
enum:
- app
- mysql
- redis
- website
type: string
required:
- type
type: object
dto.ComposeCreate:
properties:
file:
@ -1089,19 +1123,6 @@ definitions:
- pageSize
- type
type: object
dto.RecoverDB:
properties:
backupName:
type: string
dbName:
type: string
mysqlName:
type: string
required:
- backupName
- dbName
- mysqlName
type: object
dto.RedisConf:
properties:
containerName:
@ -1194,6 +1215,20 @@ definitions:
info:
type: string
type: object
dto.SearchHostWithPage:
properties:
group:
type: string
info:
type: string
page:
type: integer
pageSize:
type: integer
required:
- page
- pageSize
type: object
dto.SearchLgLogWithPage:
properties:
ip:
@ -1369,20 +1404,6 @@ definitions:
releaseNote:
type: string
type: object
dto.UploadRecover:
properties:
dbName:
type: string
fileDir:
type: string
fileName:
type: string
mysqlName:
type: string
required:
- dbName
- mysqlName
type: object
dto.UserLoginInfo:
properties:
mfaSecret:
@ -2003,6 +2024,19 @@ definitions:
port:
type: integer
type: object
request.SearchUploadWithPage:
properties:
page:
type: integer
pageSize:
type: integer
path:
type: string
required:
- page
- pageSize
- path
type: object
request.WebsiteAcmeAccountCreate:
properties:
email:
@ -2221,35 +2255,6 @@ definitions:
required:
- id
type: object
request.WebsiteRecover:
properties:
backupName:
type: string
type:
type: string
websiteName:
type: string
required:
- backupName
- type
- websiteName
type: object
request.WebsiteRecoverByFile:
properties:
fileDir:
type: string
fileName:
type: string
type:
type: string
websiteName:
type: string
required:
- fileDir
- fileName
- type
- websiteName
type: object
request.WebsiteResourceReq:
properties:
id:
@ -4593,34 +4598,6 @@ paths:
formatEN: create mysql database [name]
formatZH: 创建 mysql 数据库 [name]
paramKeys: []
/databases/backup:
post:
consumes:
- application/json
description: 备份 mysql 数据库
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.BackupDB'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Backup mysql database
tags:
- Database Mysql
x-panel-log:
BeforeFuntions: []
bodyKeys:
- mysqlName
- dbName
formatEN: backup mysql database [mysqlName][dbName]
formatZH: 备份 mysql 数据库 [mysqlName][dbName]
paramKeys: []
/databases/baseinfo:
get:
description: 获取 mysql 基础信息
@ -4837,82 +4814,6 @@ paths:
summary: List mysql database names
tags:
- Database Mysql
/databases/recover:
post:
consumes:
- application/json
description: Mysql 数据库恢复
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.RecoverDB'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Recover mysql database
tags:
- Database Mysql
x-panel-log:
BeforeFuntions: []
bodyKeys:
- mysqlName
- dbName
- backupName
formatEN: 恢复 mysql 数据库 [mysqlName][dbName] [backupName]
formatZH: 恢复 mysql 数据库 [mysqlName][dbName] [backupName]
paramKeys: []
/databases/recover/byupload:
post:
consumes:
- application/json
description: Mysql 数据库从上传文件恢复
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.UploadRecover'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Recover mysql database by upload file
tags:
- Database Mysql
x-panel-log:
BeforeFuntions: []
bodyKeys:
- fileDir
- fileName
- mysqlName
- dbName
formatEN: mysql database recover [fileDir]/[fileName] from [mysqlName][dbName]
formatZH: mysql 数据库从 [fileDir]/[fileName] 恢复 [mysqlName][dbName]
paramKeys: []
/databases/redis/backup:
post:
description: 备份 redis 数据库
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Backup redis
tags:
- Database Redis
x-panel-log:
BeforeFuntions: []
bodyKeys: []
formatEN: backup redis database
formatZH: 备份 redis 数据库
paramKeys: []
/databases/redis/backup/search:
post:
consumes:
@ -5065,25 +4966,6 @@ paths:
formatEN: redis database persistence configuration update
formatZH: redis 数据库持久化配置更新
paramKeys: []
/databases/redis/recover:
post:
description: 恢复 redis 数据库
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Recover redis
tags:
- Database Redis
x-panel-log:
BeforeFuntions: []
bodyKeys:
- fileDir
- fileName
formatEN: redis database recover from [fileDir]/[fileName]
formatZH: redis 数据库从 [fileDir]/[fileName] 恢复
paramKeys: []
/databases/redis/status:
get:
description: 获取 redis 状态信息
@ -5610,6 +5492,28 @@ paths:
formatEN: Upload file [path]
formatZH: 上传文件 [path]
paramKeys: []
/files/upload/search:
post:
consumes:
- application/json
description: 分页获取上传文件
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.SearchUploadWithPage'
responses:
"200":
description: OK
schema:
type: anrry
security:
- ApiKeyAuth: []
summary: Page file
tags:
- File
/files/wget:
post:
consumes:
@ -5821,7 +5725,7 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/dto.OperateByID'
$ref: '#/definitions/dto.BatchDeleteReq'
responses:
"200":
description: ""
@ -5834,14 +5738,14 @@ paths:
BeforeFuntions:
- db: hosts
input_colume: id
input_value: id
isList: false
input_value: ids
isList: true
output_colume: addr
output_value: addr
output_value: addrs
bodyKeys:
- id
formatEN: delete host [addr]
formatZH: 删除主机 [addr]
- ids
formatEN: delete host [addrs]
formatZH: 删除主机 [addrs]
paramKeys: []
/hosts/group:
post:
@ -5958,14 +5862,14 @@ paths:
post:
consumes:
- application/json
description: 加载主机树
description: 获取主机列表分页
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SearchForTree'
$ref: '#/definitions/dto.SearchHostWithPage'
responses:
"200":
description: OK
@ -5973,7 +5877,7 @@ paths:
type: anrry
security:
- ApiKeyAuth: []
summary: Load host tree
summary: Page host
tags:
- Host
/hosts/test/byid/:id:
@ -6017,33 +5921,61 @@ paths:
summary: Test host conn by info
tags:
- Host
/hosts/update:
/hosts/tree:
post:
consumes:
- application/json
description: 更新主机
description: 加载主机树
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.HostOperate'
$ref: '#/definitions/dto.SearchForTree'
responses:
"200":
description: OK
schema:
type: anrry
security:
- ApiKeyAuth: []
summary: Load host tree
tags:
- Host
/hosts/update:
post:
consumes:
- application/json
description: 切换分组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.ChangeHostGroup'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update host
summary: Update host group
tags:
- Host
x-panel-log:
BeforeFuntions: []
BeforeFuntions:
- db: hosts
input_colume: id
input_value: id
isList: false
output_colume: addr
output_value: addr
bodyKeys:
- name
- addr
formatEN: update host [name][addr]
formatZH: 更新主机信息 [name][addr]
- id
- group
formatEN: change host [addr] group => [group]
formatZH: 切换主机[addr]分组 => [group]
paramKeys: []
/logs/clean:
post:
@ -6252,6 +6184,35 @@ paths:
formatEN: create backup account [type]
formatZH: 创建备份账号 [type]
paramKeys: []
/settings/backup/:
post:
consumes:
- application/json
description: 备份系统数据
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.CommonBackup'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Backup system data
tags:
- Backup Account
x-panel-log:
BeforeFuntions: []
bodyKeys:
- type
- name
- detailName
formatEN: backup [type] data [name][detailName]
formatZH: 备份 [type] 数据 [name][detailName]
paramKeys: []
/settings/backup/del:
post:
consumes:
@ -6366,6 +6327,66 @@ paths:
summary: Page backup records
tags:
- Backup Account
/settings/backup/recover:
post:
consumes:
- application/json
description: 恢复系统数据
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.CommonRecover'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Recover system data
tags:
- Backup Account
x-panel-log:
BeforeFuntions: []
bodyKeys:
- type
- name
- detailName
- file
formatEN: recover [type] data [name][detailName] from [file]
formatZH: 从 [file] 恢复 [type] 数据 [name][detailName]
paramKeys: []
/settings/backup/recover/byupload:
post:
consumes:
- application/json
description: 从上传恢复系统数据
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.CommonRecover'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Recover system data by upload
tags:
- Backup Account
x-panel-log:
BeforeFuntions: []
bodyKeys:
- type
- name
- detailName
- file
formatEN: recover [type] data [name][detailName] from [file]
formatZH: 从 [file] 恢复 [type] 数据 [name][detailName]
paramKeys: []
/settings/backup/search:
get:
description: 获取备份账号列表
@ -7126,39 +7147,6 @@ paths:
summary: Page website acme accounts
tags:
- Website Acme
/websites/backup:
post:
consumes:
- application/json
description: 备份网站
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteResourceReq'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Backup website
tags:
- Website
x-panel-log:
BeforeFuntions:
- db: websites
input_colume: id
input_value: id
isList: false
output_colume: primary_domain
output_value: domain
bodyKeys:
- id
formatEN: Backup website [domain]
formatZH: 备份网站 [domain]
paramKeys: []
/websites/check:
post:
consumes:
@ -7723,63 +7711,6 @@ paths:
summary: List website names
tags:
- Website
/websites/recover:
post:
consumes:
- application/json
description: 从备份恢复网站
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteRecover'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Recover website
tags:
- Website
x-panel-log:
BeforeFuntions: []
bodyKeys:
- websiteName
- backupName
formatEN: '[websiteName] recover from backups [backupName]'
formatZH: '[websiteName] 从备份恢复 [backupName]'
paramKeys: []
/websites/recover/byupload:
post:
consumes:
- application/json
description: 从上传恢复网站
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteRecoverByFile'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Recover website by upload
tags:
- Website
x-panel-log:
BeforeFuntions: []
bodyKeys:
- websiteName
- fileDir
- fileName
formatEN: '[websiteName] recover from uploads [fileDir]/[fileName]'
formatZH: '[websiteName] 从上传恢复 [fileDir]/[fileName]'
paramKeys: []
/websites/search:
post:
consumes:

View File

@ -83,6 +83,7 @@ declare module 'vue' {
SubItem: typeof import('./src/components/app-layout/menu/components/sub-item.vue')['default']
SvgIcon: typeof import('./src/components/svg-icon/svg-icon.vue')['default']
TableSetting: typeof import('./src/components/table-setting/index.vue')['default']
Upload: typeof import('./src/components/upload/index.vue')['default']
}
}

View File

@ -126,21 +126,6 @@ export namespace App {
config?: Object;
}
export interface AppBackupReq extends ReqPage {
appInstallId: number;
}
export interface AppBackupDelReq {
ids: number[];
}
export interface AppBackup extends CommonModel {
name: string;
path: string;
appInstallId: string;
appDetail: AppDetail;
}
export interface VersionDetail {
version: string;
detailId: number;

View File

@ -5,17 +5,6 @@ export namespace Database {
mysqlName: string;
dbName: string;
}
export interface Recover {
mysqlName: string;
dbName: string;
backupName: string;
}
export interface RecoverByUpload {
mysqlName: string;
dbName: string;
fileName: string;
fileDir: string;
}
export interface MysqlDBInfo {
id: number;
createdAt: Date;

View File

@ -30,6 +30,15 @@ export namespace File {
containSub?: boolean;
}
export interface SearchUploadInfo extends ReqPage {
path: string;
}
export interface UploadInfo {
name: string;
size: number;
createdAt: string;
}
export interface FileTree {
id: string;
name: string;

View File

@ -34,18 +34,6 @@ export namespace Website {
websiteGroupId: number;
}
export interface WebSiteRecover {
websiteName: string;
type: string;
backupName: string;
}
export interface WebsiteRecoverByUpload {
websiteName: string;
type: string;
fileDir: string;
fileName: string;
}
export interface WebSiteDel {
id: number;
deleteApp: boolean;
@ -267,10 +255,6 @@ export namespace Website {
id: number;
}
export interface BackupReq {
id: number;
}
export interface NginxUpdate {
id: number;
content: string;

View File

@ -66,14 +66,6 @@ export const GetAppService = (key: string | undefined) => {
return http.get<App.AppService[]>(`apps/services/${key}`);
};
export const GetAppBackups = (info: App.AppBackupReq) => {
return http.post<ResPage<App.AppBackup>>('apps/installed/backups', info);
};
export const DelAppBackups = (req: App.AppBackupDelReq) => {
return http.post<any>('apps/installed/backups/del', req);
};
export const GetAppUpdateVersions = (id: number) => {
return http.get<any>(`apps/installed/${id}/versions`);
};

View File

@ -1,11 +1,16 @@
import { File } from '@/api/interface/file';
import http from '@/api';
import { AxiosRequestConfig } from 'axios';
import { ResPage } from '../interface';
export const GetFilesList = (params: File.ReqFile) => {
return http.post<File.File>('files/search', params, 200000);
};
export const GetUploadList = (params: File.SearchUploadInfo) => {
return http.post<ResPage<File.UploadInfo>>('files/upload/search', params);
};
export const GetFilesTree = (params: File.ReqFile) => {
return http.post<File.FileTree[]>('files/tree', params);
};

View File

@ -27,7 +27,9 @@
</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 :label="$t('database.source')" prop="backupType">
<template #default="{ row }">{{ $t('setting.' + row.source) }}</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"

View File

@ -0,0 +1,252 @@
<template>
<div>
<el-drawer v-model="upVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header>
<DrawerHeader :header="$t('commons.button.import')" :resource="title" :back="handleClose" />
</template>
<div v-loading="loading">
<el-upload
ref="uploadRef"
drag
:on-change="fileOnChange"
:before-upload="beforeAvatarUpload"
class="upload-demo"
:auto-upload="false"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
{{ $t('database.dropHelper') }}
<em>{{ $t('database.clickHelper') }}</em>
</div>
<template #tip>
<div v-if="type === 'mysql'" class="el-upload__tip">
<span class="input-help">{{ $t('database.supportUpType') }}</span>
<span class="input-help">
{{ $t('database.zipFormat') }}
</span>
</div>
<div v-else class="el-upload__tip">
<span class="input-help">{{ $t('website.supportUpType') }}</span>
<span class="input-help">
{{ $t('website.zipFormat') }}
</span>
</div>
</template>
</el-upload>
<el-button v-if="uploaderFiles.length === 1" icon="Upload" @click="onSubmit">
{{ $t('commons.button.upload') }}
</el-button>
<el-divider />
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
<template #toolbar>
<el-button
style="margin-left: 10px"
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')" show-overflow-tooltip prop="name" />
<el-table-column :label="$t('file.size')" prop="size">
<template #default="{ row }">
{{ computeSize(row.size) }}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
<template #default="{ row }">
{{ row.createdAt }}
</template>
</el-table-column>
<fu-table-operations
width="300px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</div>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue';
import { computeSize } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
import { handleRecoverByUpload } from '@/api/modules/setting';
import i18n from '@/lang';
import { UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
import { File } from '@/api/interface/file';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { BatchDeleteFile, GetUploadList, UploadFileData } from '@/api/modules/files';
import { loadBaseDir } from '@/api/modules/setting';
import { MsgError, MsgSuccess } from '@/utils/message';
const loading = ref();
const selects = ref<any>([]);
const baseDir = ref();
const data = ref();
const title = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const upVisiable = ref(false);
const type = ref();
const name = ref();
const detailName = ref();
interface DialogProps {
type: string;
name: string;
detailName: string;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
type.value = params.type;
name.value = params.name;
detailName.value = params.detailName;
const pathRes = await loadBaseDir();
if (type.value === 'mysql') {
title.value = name.value + ' [ ' + detailName.value + ' ]';
baseDir.value = `${pathRes.data}/uploads/database/mysql/${name.value}/${detailName.value}/`;
}
if (type.value === 'website' || type.value === 'app') {
title.value = name.value;
baseDir.value = `${pathRes.data}/uploads/${type.value}/${name.value}/`;
}
upVisiable.value = true;
search();
};
const search = async () => {
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
path: baseDir.value,
};
const res = await GetUploadList(params);
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
};
const onRecover = async (row: File.File) => {
let params = {
type: type.value,
name: name.value,
detailName: detailName.value,
file: baseDir.value + row.name,
};
loading.value = true;
await handleRecoverByUpload(params)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
};
const uploaderFiles = ref<UploadFiles>([]);
const uploadRef = ref<UploadInstance>();
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
if (type.value === 'app' || type.value === 'website') {
if (rawFile.name.endsWith('.tar.gz')) {
MsgError(i18n.global.t('database.unSupportType'));
return false;
} else if (rawFile.size / 1024 / 1024 > 50) {
MsgError(i18n.global.t('database.unSupportSize'));
return false;
}
return true;
}
if (
rawFile.name.endsWith('.sql') ||
rawFile.name.endsWith('.gz') ||
rawFile.name.endsWith('.zip') ||
rawFile.name.endsWith('.tgz')
) {
MsgError(i18n.global.t('database.unSupportType'));
return false;
} else if (rawFile.size / 1024 / 1024 > 10) {
MsgError(i18n.global.t('database.unSupportSize'));
return false;
}
return true;
};
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
uploaderFiles.value = uploadFiles;
};
const handleClose = () => {
uploaderFiles.value = [];
uploadRef.value!.clearFiles();
upVisiable.value = false;
};
const onSubmit = () => {
const formData = new FormData();
if (uploaderFiles.value.length !== 1) {
return;
}
if (uploaderFiles.value[0]!.raw != undefined) {
formData.append('file', uploaderFiles.value[0]!.raw);
}
formData.append('path', baseDir.value);
loading.value = true;
UploadFileData(formData, {})
.then(() => {
loading.value = false;
uploadRef.value?.clearFiles();
uploaderFiles.value = [];
MsgSuccess(i18n.global.t('file.uploadSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
};
const onBatchDelete = async (row: File.File | null) => {
let files: Array<string> = [];
if (row) {
files.push(baseDir.value + row.name);
} else {
selects.value.forEach((item: File.File) => {
files.push(baseDir.value + item.name);
});
}
await useDeleteData(BatchDeleteFile, { paths: files, isDir: false }, 'commons.msg.delete');
search();
};
const buttons = [
{
label: i18n.global.t('commons.button.recover'),
click: (row: File.File) => {
onRecover(row);
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: File.File) => {
onBatchDelete(row);
},
},
];
defineExpose({
acceptParams,
});
</script>

View File

@ -922,8 +922,9 @@ export default {
type: 'Type',
static: 'Static',
deployment: 'Deployment',
supportUpType: 'Only support tar.gz files',
zipFormat: 'tar.gz compressed package structure: test.tar.gz compressed package must contain website.json file',
supportUpType: 'Only .tar.gz files within 50 MB are supported',
zipFormat:
'.tar.gz compressed package structure: test.tar.gz compressed package must contain website.json file',
proxy: 'Reverse Proxy',
alias: 'Path Name',
remark: 'Remark',

View File

@ -296,6 +296,8 @@ export default {
unSupportType: '不支持当前文件类型',
unSupportSize: '上传文件超过 10M请确认',
selectFile: '选择文件',
dropHelper: '将上传文件拖拽到此处或者',
clickHelper: '点击上传',
supportUpType: '仅支持 10M 以内 sqlzipsql.gz(tar.gz gz tgz) 文件',
zipFormat: 'ziptar.gz 压缩包结构test.zip test.tar.gz 压缩包内必需包含 test.sql',
@ -930,8 +932,8 @@ export default {
type: '类型',
static: '静态网站',
deployment: '一键部署',
supportUpType: '仅支持 tar.gz 文件',
zipFormat: 'tar.gz 压缩包结构test.tar.gz 压缩包内必需包含 website.json 文件',
supportUpType: '仅支持 50M 以内 .tar.gz 文件',
zipFormat: '.tar.gz 压缩包结构test.tar.gz 压缩包内必需包含 website.json 文件',
proxy: '反向代理',
alias: '代号',
remark: '备注',

View File

@ -89,6 +89,17 @@
</span>
</span>
<el-button
class="h-button"
type="primary"
plain
round
size="small"
@click="openUploads(installed.app.key, installed.name)"
v-if="mode === 'installed'"
>
{{ $t('database.loadBackup') }}
</el-button>
<el-button
class="h-button"
type="primary"
@ -145,6 +156,7 @@
</template>
</LayoutContent>
<Backups ref="backupRef" @close="search" />
<Uploads ref="uploadRef" />
<AppResources ref="checkRef" />
<AppDelete ref="deleteRef" @close="search" />
<AppParams ref="appParamRef" />
@ -164,6 +176,7 @@ import { onMounted, onUnmounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { ElMessageBox } from 'element-plus';
import Backups from '@/components/backup/index.vue';
import Uploads from '@/components/upload/index.vue';
import AppResources from './check/index.vue';
import AppDelete from './delete/index.vue';
import AppParams from './detail/index.vue';
@ -189,6 +202,7 @@ let operateReq = reactive({
detailId: 0,
});
const backupRef = ref();
const uploadRef = ref();
const checkRef = ref();
const deleteRef = ref();
const appParamRef = ref();
@ -350,6 +364,15 @@ const openBackups = (key: string, name: string) => {
backupRef.value.acceptParams(params);
};
const openUploads = (key: string, name: string) => {
let params = {
type: 'app',
name: key,
detailName: name,
};
uploadRef.value.acceptParams(params);
};
const openParam = (installId: number) => {
appParamRef.value.acceptParams({ id: installId });
};

View File

@ -155,11 +155,11 @@ import DeleteDialog from '@/views/database/mysql/delete/index.vue';
import PasswordDialog from '@/views/database/mysql/password/index.vue';
import RootPasswordDialog from '@/views/database/mysql/root-password/index.vue';
import RemoteAccessDialog from '@/views/database/mysql/remote/index.vue';
import UploadDialog from '@/views/database/mysql/upload/index.vue';
import AppResources from '@/views/database/mysql/check/index.vue';
import Setting from '@/views/database/mysql/setting/index.vue';
import AppStatus from '@/components/app-status/index.vue';
import Backups from '@/components/backup/index.vue';
import UploadDialog from '@/components/upload/index.vue';
import { dateFormat } from '@/utils/util';
import { reactive, ref } from 'vue';
import { deleteCheckMysqlDB, loadRemoteAccess, searchMysqlDBs, updateMysqlDescription } from '@/api/modules/database';
@ -350,8 +350,9 @@ const buttons = [
label: i18n.global.t('database.loadBackup'),
click: (row: Database.MysqlDBInfo) => {
let params = {
mysqlName: mysqlName.value,
dbName: row.name,
type: 'mysql',
name: mysqlName.value,
detailName: row.name,
};
uploadRef.value!.acceptParams(params);
},

View File

@ -1,210 +0,0 @@
<template>
<div v-loading="loading">
<el-drawer v-model="upVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header>
<DrawerHeader :header="$t('commons.button.import')" :back="handleClose" />
</template>
<el-upload
ref="uploadRef"
:on-change="fileOnChange"
:before-upload="beforeAvatarUpload"
class="upload-demo"
:auto-upload="false"
>
<template #trigger>
<el-button type="primary" plain>{{ $t('database.selectFile') }}</el-button>
</template>
</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-button style="margin-top: 10px" v-if="uploaderFiles.length === 1" icon="Upload" @click="onSubmit">
{{ $t('commons.button.upload') }}
</el-button>
<el-divider />
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
<template #toolbar>
<el-button
style="margin-left: 10px"
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')" show-overflow-tooltip prop="name" />
<el-table-column :label="$t('file.size')" prop="size">
<template #default="{ row }">
{{ computeSize(row.size) }}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
<template #default="{ row }">
{{ dateFormatSimple(row.modTime) }}
</template>
</el-table-column>
<fu-table-operations
width="300px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue';
import { computeSize, dateFormatSimple } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
import { handleRecoverByUpload } from '@/api/modules/setting';
import i18n from '@/lang';
import { UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
import { File } from '@/api/interface/file';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { BatchDeleteFile, GetFilesList, UploadFileData } from '@/api/modules/files';
import { loadBaseDir } from '@/api/modules/setting';
import { MsgError, MsgSuccess } from '@/utils/message';
const loading = ref();
const selects = ref<any>([]);
const baseDir = ref();
const data = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const upVisiable = ref(false);
const mysqlName = ref();
const dbName = ref();
interface DialogProps {
mysqlName: string;
dbName: string;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
mysqlName.value = params.mysqlName;
dbName.value = params.dbName;
const pathRes = await loadBaseDir();
baseDir.value = `${pathRes.data}/uploads/database/mysql/${mysqlName.value}/${dbName.value}/`;
upVisiable.value = true;
search();
};
const search = async () => {
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
path: baseDir.value,
expand: true,
};
const res = await GetFilesList(params);
data.value = res.data.items || [];
paginationConfig.total = res.data.itemTotal;
};
const onRecover = async (row: File.File) => {
let params = {
type: 'mysql',
name: mysqlName.value,
detailName: dbName.value,
file: baseDir.value + '/' + row.name,
};
loading.value = true;
await handleRecoverByUpload(params)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
};
const uploaderFiles = ref<UploadFiles>([]);
const uploadRef = ref<UploadInstance>();
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
if (
rawFile.name.endsWith('.sql') ||
rawFile.name.endsWith('.gz') ||
rawFile.name.endsWith('.zip') ||
rawFile.name.endsWith('.tgz')
) {
MsgError(i18n.global.t('database.unSupportType'));
return false;
} else if (rawFile.size / 1024 / 1024 > 10) {
MsgError(i18n.global.t('database.unSupportSize'));
return false;
}
return true;
};
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
uploaderFiles.value = uploadFiles;
};
const handleClose = () => {
uploadRef.value!.clearFiles();
upVisiable.value = false;
};
const onSubmit = () => {
const formData = new FormData();
if (uploaderFiles.value.length !== 1) {
return;
}
if (uploaderFiles.value[0]!.raw != undefined) {
formData.append('file', uploaderFiles.value[0]!.raw);
}
formData.append('path', baseDir.value);
UploadFileData(formData, {}).then(() => {
MsgSuccess(i18n.global.t('file.uploadSuccess'));
handleClose();
search();
});
};
const onBatchDelete = async (row: File.File | null) => {
let files: Array<string> = [];
if (row) {
files.push(baseDir.value + row.name);
} else {
selects.value.forEach((item: File.File) => {
files.push(baseDir.value + item.name);
});
}
await useDeleteData(BatchDeleteFile, { paths: files, isDir: false }, 'commons.msg.delete');
search();
};
const buttons = [
{
label: i18n.global.t('commons.button.recover'),
click: (row: File.File) => {
onRecover(row);
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: File.File) => {
onBatchDelete(row);
},
},
];
defineExpose({
acceptParams,
});
</script>

View File

@ -1,30 +1,40 @@
<template>
<el-drawer v-model="open" :before-close="handleClose" size="40%">
<template #header>
<DrawerHeader :header="$t('file.upload')" :back="handleClose" />
</template>
<el-upload
action="#"
:auto-upload="false"
ref="uploadRef"
:multiple="true"
:on-change="fileOnChange"
v-loading="loading"
>
<template #trigger>
<el-button type="primary">{{ $t('file.selectFile') }}</el-button>
<div>
<el-drawer v-model="open" :before-close="handleClose" size="40%">
<template #header>
<DrawerHeader :header="$t('file.upload')" :back="handleClose" />
</template>
</el-upload>
<el-progress v-if="loading" :text-inside="true" :stroke-width="26" :percentage="uploadPrecent"></el-progress>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit()" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
<el-upload
action="#"
drag
:auto-upload="false"
ref="uploadRef"
:multiple="true"
:on-change="fileOnChange"
v-loading="loading"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
{{ $t('database.dropHelper') }}
<em>{{ $t('database.clickHelper') }}</em>
</div>
</el-upload>
<el-progress
v-if="loading"
:text-inside="true"
:stroke-width="26"
:percentage="uploadPrecent"
></el-progress>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit()" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">

View File

@ -152,7 +152,7 @@
import LayoutContent from '@/layout/layout-content.vue';
import RouterButton from '@/components/router-button/index.vue';
import Backups from '@/components/backup/index.vue';
import UploadDialog from '@/views/website/website/upload/index.vue';
import UploadDialog from '@/components/upload/index.vue';
import DefaultServer from '@/views/website/website/default/index.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { onMounted, reactive, ref } from '@vue/runtime-core';
@ -323,8 +323,9 @@ const buttons = [
label: i18n.global.t('database.loadBackup'),
click: (row: Website.Website) => {
let params = {
websiteName: row.primaryDomain,
websiteType: row.type,
type: 'website',
name: row.primaryDomain,
detailName: '',
};
uploadRef.value!.acceptParams(params);
},

View File

@ -1,207 +0,0 @@
<template>
<el-drawer :close-on-click-modal="false" v-model="upVisiable" size="50%">
<template #header>
<Header :header="$t('commons.button.import')" :resource="websiteName" :back="handleClose"></Header>
</template>
<div v-loading="loading">
<el-upload
ref="uploadRef"
:on-change="fileOnChange"
:before-upload="beforeAvatarUpload"
class="upload-demo"
:auto-upload="false"
>
<template #trigger>
<el-button type="primary" plain>{{ $t('database.selectFile') }}</el-button>
</template>
<el-button style="margin-left: 10px" icon="Upload" @click="onSubmit">
{{ $t('commons.button.upload') }}
</el-button>
</el-upload>
<div style="margin-left: 10px">
<span class="input-help">{{ $t('website.supportUpType') }}</span>
<span class="input-help">
{{ $t('website.zipFormat') }}
</span>
</div>
<el-divider />
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
<template #toolbar>
<el-button
style="margin-left: 10px"
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')" show-overflow-tooltip prop="name" />
<el-table-column :label="$t('file.size')" prop="size">
<template #default="{ row }">
{{ computeSize(row.size) }}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
<template #default="{ row }">
{{ dateFormatSimple(row.modTime) }}
</template>
</el-table-column>
<fu-table-operations
width="300px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</div>
</el-drawer>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue';
import { computeSize, dateFormatSimple } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
import i18n from '@/lang';
import { UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
import { File } from '@/api/interface/file';
import { BatchDeleteFile, GetFilesList, UploadFileData } from '@/api/modules/files';
import { handleRecoverByUpload, loadBaseDir } from '@/api/modules/setting';
import Header from '@/components/drawer-header/index.vue';
import { MsgError, MsgSuccess } from '@/utils/message';
const selects = ref<any>([]);
const baseDir = ref();
const loading = ref(false);
const data = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const upVisiable = ref(false);
const websiteName = ref();
const websiteType = ref();
interface DialogProps {
websiteName: string;
websiteType: string;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
websiteName.value = params.websiteName;
websiteType.value = params.websiteType;
upVisiable.value = true;
const pathRes = await loadBaseDir();
baseDir.value = `${pathRes.data}/uploads/website/${websiteName.value}`;
search();
};
const search = async () => {
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
path: baseDir.value,
expand: true,
};
const res = await GetFilesList(params);
data.value = res.data.items || [];
paginationConfig.total = res.data.itemTotal;
};
const onRecover = async (row: File.File) => {
let params = {
name: websiteName.value,
detailName: '',
type: 'website',
file: baseDir.value + '/' + row.name,
};
loading.value = true;
await handleRecoverByUpload(params)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.finally(() => {
loading.value = false;
});
};
const uploaderFiles = ref<UploadFiles>([]);
const uploadRef = ref<UploadInstance>();
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
if (rawFile.name.endsWith('.tar.gz')) {
MsgError(i18n.global.t('database.unSupportType'));
return false;
}
return true;
};
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
uploaderFiles.value = uploadFiles;
};
const handleClose = () => {
uploadRef.value!.clearFiles();
upVisiable.value = false;
};
const onSubmit = () => {
const formData = new FormData();
if (uploaderFiles.value.length !== 1) {
return;
}
if (uploaderFiles.value[0]!.raw != undefined) {
formData.append('file', uploaderFiles.value[0]!.raw);
}
formData.append('path', baseDir.value + '/');
loading.value = true;
UploadFileData(formData, {})
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('file.uploadSuccess'));
handleClose();
search();
})
.finally(() => {
loading.value = false;
});
};
const onBatchDelete = async (row: File.File | null) => {
let files: Array<string> = [];
if (row) {
files.push(baseDir.value + '/' + row.name);
} else {
selects.value.forEach((item: File.File) => {
files.push(baseDir.value + '/' + item.name);
});
}
await useDeleteData(BatchDeleteFile, { isDir: false, paths: files }, 'commons.msg.delete');
search();
};
const buttons = [
{
label: i18n.global.t('commons.button.recover'),
click: (row: File.File) => {
onRecover(row);
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: File.File) => {
onBatchDelete(row);
},
},
];
defineExpose({
acceptParams,
});
</script>