mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 00:09:16 +08:00
feat: 增加应用备份功能
This commit is contained in:
parent
7f4e8a25ea
commit
9f55523972
@ -5,7 +5,6 @@ system:
|
||||
data_dir: /opt/1Panel/data
|
||||
resource_dir: /opt/1Panel/data/resource
|
||||
app_dir: /opt/1Panel/data/apps
|
||||
app_oss:
|
||||
|
||||
mysql:
|
||||
path: localhost
|
||||
|
@ -75,6 +75,21 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) DeleteAppBackup(c *gin.Context) {
|
||||
var req dto.AppBackupDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := appService.DeleteBackup(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) SearchInstalled(c *gin.Context) {
|
||||
var req dto.AppInstalledRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@ -93,6 +108,24 @@ func (b *BaseApi) SearchInstalled(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (b *BaseApi) SearchInstalledBackup(c *gin.Context) {
|
||||
var req dto.AppBackupRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
total, list, err := appService.PageInstallBackups(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func (b *BaseApi) OperateInstalled(c *gin.Context) {
|
||||
var req dto.AppInstallOperate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
|
@ -92,6 +92,15 @@ type AppInstalledRequest struct {
|
||||
PageInfo
|
||||
}
|
||||
|
||||
type AppBackupRequest struct {
|
||||
PageInfo
|
||||
AppInstallID uint `json:"appInstallID"`
|
||||
}
|
||||
|
||||
type AppBackupDeleteRequest struct {
|
||||
Ids []uint `json:"ids"`
|
||||
}
|
||||
|
||||
type AppOperate string
|
||||
|
||||
var (
|
||||
@ -100,6 +109,7 @@ var (
|
||||
Restart AppOperate = "restart"
|
||||
Delete AppOperate = "delete"
|
||||
Sync AppOperate = "sync"
|
||||
Backup AppOperate = "backup"
|
||||
)
|
||||
|
||||
type AppInstallOperate struct {
|
||||
|
@ -1,34 +1,35 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"path"
|
||||
)
|
||||
|
||||
type AppInstall struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"type:varchar(64);not null"`
|
||||
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;"`
|
||||
CanUpdate bool `json:"canUpdate"`
|
||||
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:"-"`
|
||||
Name string `json:"name" gorm:"type:varchar(64);not null"`
|
||||
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;"`
|
||||
CanUpdate bool `json:"canUpdate"`
|
||||
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"`
|
||||
Backups []AppInstallBackup `json:"backups"`
|
||||
}
|
||||
|
||||
func (i *AppInstall) GetPath() string {
|
||||
return path.Join(global.CONF.System.AppDir, i.App.Key, i.Name)
|
||||
return path.Join(constant.AppInstallDir, i.App.Key, i.Name)
|
||||
}
|
||||
|
||||
func (i *AppInstall) GetComposePath() string {
|
||||
return path.Join(global.CONF.System.AppDir, i.App.Key, i.Name, "docker-compose.yml")
|
||||
return path.Join(constant.AppInstallDir, i.App.Key, i.Name, "docker-compose.yml")
|
||||
}
|
||||
|
9
backend/app/model/app_install_backup.go
Normal file
9
backend/app/model/app_install_backup.go
Normal file
@ -0,0 +1,9 @@
|
||||
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"`
|
||||
AppDetailId uint `gorm:"type:varchar(64);not null" json:"app_detail_id"`
|
||||
AppInstallId uint `gorm:"type:integer;not null" json:"app_install_id"`
|
||||
}
|
@ -40,14 +40,14 @@ func (a AppInstallRepo) WithServiceName(serviceName string) DBOption {
|
||||
func (a AppInstallRepo) GetBy(opts ...DBOption) ([]model.AppInstall, error) {
|
||||
var install []model.AppInstall
|
||||
db := getDb(opts...).Model(&model.AppInstall{})
|
||||
err := db.Preload("App").Find(&install).Error
|
||||
err := db.Preload("App").Preload("Backups").Find(&install).Error
|
||||
return install, err
|
||||
}
|
||||
|
||||
func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
|
||||
var install model.AppInstall
|
||||
db := getDb(opts...).Model(&model.AppInstall{})
|
||||
err := db.Preload("App").First(&install).Error
|
||||
err := db.Preload("App").Preload("Backups").First(&install).Error
|
||||
return install, err
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ func (a AppInstallRepo) Page(page, size int, opts ...DBOption) (int64, []model.A
|
||||
db := getDb(opts...).Model(&model.AppInstall{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("App").Find(&apps).Error
|
||||
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("App").Preload("Backups").Find(&apps).Error
|
||||
return count, apps, err
|
||||
}
|
||||
|
||||
|
41
backend/app/repo/app_install_backup.go
Normal file
41
backend/app/repo/app_install_backup.go
Normal file
@ -0,0 +1,41 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/1Panel-dev/1Panel/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(opts ...DBOption) error {
|
||||
return getDb(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...).Find(&backups); err != nil {
|
||||
return backups, nil
|
||||
}
|
||||
return backups, nil
|
||||
}
|
||||
|
||||
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.Debug().Limit(size).Offset(size * (page - 1)).Find(&backups).Error
|
||||
return count, backups, err
|
||||
}
|
@ -16,6 +16,7 @@ type RepoGroup struct {
|
||||
AppInstallRepo
|
||||
AppInstallResourceRpo
|
||||
DatabaseRepo
|
||||
AppInstallBackupRepo
|
||||
}
|
||||
|
||||
var RepoGroupApp = new(RepoGroup)
|
||||
|
@ -117,8 +117,6 @@ func (a AppService) PageInstalled(req dto.AppInstalledRequest) (int64, []dto.App
|
||||
for _, in := range installed {
|
||||
installDto := dto.AppInstalled{
|
||||
AppInstall: in,
|
||||
AppName: in.App.Name,
|
||||
Icon: in.App.Icon,
|
||||
}
|
||||
installDTOs = append(installDTOs, installDto)
|
||||
}
|
||||
@ -145,6 +143,10 @@ func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO,
|
||||
return appDetailDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) PageInstallBackups(req dto.AppBackupRequest) (int64, []model.AppInstallBackup, error) {
|
||||
return appInstallBackupRepo.Page(req.Page, req.PageSize, appInstallBackupRepo.WithAppInstallID(req.AppInstallID))
|
||||
}
|
||||
|
||||
func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
|
||||
if err != nil {
|
||||
@ -186,9 +188,7 @@ func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
|
||||
}
|
||||
}
|
||||
|
||||
tx := global.DB.Begin()
|
||||
ctx := context.WithValue(context.Background(), "db", tx)
|
||||
|
||||
tx, ctx := getTxAndContext()
|
||||
if err := appInstallRepo.Delete(ctx, install); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
@ -204,6 +204,13 @@ func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case dto.Backup:
|
||||
tx, ctx := getTxAndContext()
|
||||
if err := backupInstall(ctx, install); err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
tx.Commit()
|
||||
return nil
|
||||
default:
|
||||
return errors.New("operate not support")
|
||||
}
|
||||
@ -284,8 +291,7 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int
|
||||
return err
|
||||
}
|
||||
|
||||
tx := global.DB.Begin()
|
||||
ctx := context.WithValue(context.Background(), "db", tx)
|
||||
tx, ctx := getTxAndContext()
|
||||
if err := appInstallRepo.Create(ctx, &appInstall); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
@ -314,6 +320,24 @@ func (a AppService) SyncAllInstalled() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AppService) DeleteBackup(req dto.AppBackupDeleteRequest) error {
|
||||
|
||||
backups, err := appInstallBackupRepo.GetBy(commonRepo.WithIdsIn(req.Ids))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
|
||||
for _, backup := range backups {
|
||||
dst := path.Join(backup.Path, backup.Name)
|
||||
if err := fileOp.DeleteFile(dst); err != nil {
|
||||
continue
|
||||
}
|
||||
appInstallBackupRepo.Delete(commonRepo.WithIdsIn(req.Ids))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AppService) GetServices(key string) ([]dto.AppService, error) {
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
||||
if err != nil {
|
||||
@ -425,7 +449,7 @@ func (a AppService) SyncInstalled(installId uint) error {
|
||||
func (a AppService) SyncAppList() error {
|
||||
//TODO 从 oss 拉取最新列表
|
||||
|
||||
appDir := path.Join(global.CONF.System.ResourceDir, "apps")
|
||||
appDir := constant.AppResourceDir
|
||||
iconDir := path.Join(appDir, "icons")
|
||||
listFile := path.Join(appDir, "list.json")
|
||||
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/utils/compose"
|
||||
@ -18,6 +17,7 @@ import (
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DatabaseOp string
|
||||
@ -52,8 +52,8 @@ func getSqlStr(key string, operate DatabaseOp, exec dto.ContainerExec) string {
|
||||
switch key {
|
||||
case "mysql":
|
||||
if operate == Add {
|
||||
str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"CREATE USER '%s'@'%%' IDENTIFIED BY '%s';\" -e \"create database %s;\" -e \"GRANT ALL ON %s.* TO '%s'@'%%';\"",
|
||||
exec.ContainerName, exec.Auth.RootPassword, param.DbUser, param.Password, param.DbName, param.DbName, param.DbUser)
|
||||
str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"CREATE USER '%s'@'%%' IDENTIFIED BY '%s';\" -e \"create database %s;\" -e \"GRANT ALL ON %s.* TO '%s'@'%%' IDENTIFIED BY '%s';\"",
|
||||
exec.ContainerName, exec.Auth.RootPassword, param.DbUser, param.Password, param.DbName, param.DbName, param.DbUser, param.Password)
|
||||
}
|
||||
if operate == Delete {
|
||||
str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"drop database %s;\" -e \"drop user %s;\" ",
|
||||
@ -160,6 +160,28 @@ func deleteLink(ctx context.Context, install *model.AppInstall) error {
|
||||
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
}
|
||||
|
||||
func backupInstall(ctx context.Context, install model.AppInstall) error {
|
||||
var backup model.AppInstallBackup
|
||||
appPath := install.GetPath()
|
||||
backupDir := path.Join(constant.BackupDir, install.App.Key, install.Name)
|
||||
fileOp := files.NewFileOp()
|
||||
now := time.Now()
|
||||
day := now.Format("2006-01-02-15-04")
|
||||
fileName := fmt.Sprintf("%s-%s-%s%s", install.Name, install.Version, day, ".tar.gz")
|
||||
if err := fileOp.Compress([]string{appPath}, backupDir, fileName, files.TarGz); err != nil {
|
||||
return err
|
||||
}
|
||||
backup.Name = fileName
|
||||
backup.Path = backupDir
|
||||
backup.AppInstallId = install.ID
|
||||
backup.AppDetailId = install.AppDetailId
|
||||
|
||||
if err := appInstallBackupRepo.Create(ctx, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainerNames(install model.AppInstall) ([]string, error) {
|
||||
composeMap := install.DockerCompose
|
||||
envMap := make(map[string]string)
|
||||
@ -220,8 +242,8 @@ func checkRequiredAndLimit(app model.App) error {
|
||||
}
|
||||
|
||||
func copyAppData(key, version, installName string, params map[string]interface{}) (err error) {
|
||||
resourceDir := path.Join(global.CONF.System.ResourceDir, "apps", key, version)
|
||||
installDir := path.Join(global.CONF.System.AppDir, key)
|
||||
resourceDir := path.Join(constant.AppResourceDir, key, version)
|
||||
installDir := path.Join(constant.AppInstallDir, key)
|
||||
installVersionDir := path.Join(installDir, version)
|
||||
fileOp := files.NewFileOp()
|
||||
if err = fileOp.Copy(resourceDir, installVersionDir); err != nil {
|
||||
|
@ -33,4 +33,5 @@ var (
|
||||
appInstallRepo = repo.RepoGroupApp.AppInstallRepo
|
||||
appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
|
||||
dataBaseRepo = repo.RepoGroupApp.DatabaseRepo
|
||||
appInstallBackupRepo = repo.RepoGroupApp.AppInstallBackupRepo
|
||||
)
|
||||
|
13
backend/app/service/helper.go
Normal file
13
backend/app/service/helper.go
Normal file
@ -0,0 +1,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func getTxAndContext() (tx *gorm.DB, ctx context.Context) {
|
||||
tx = global.DB.Begin()
|
||||
ctx = context.WithValue(context.Background(), "db", tx)
|
||||
return
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
package configs
|
||||
|
||||
type System struct {
|
||||
Port int `mapstructure:"port"`
|
||||
DbType string `mapstructure:"db_type"`
|
||||
Level string `mapstructure:"level"`
|
||||
DataDir string `mapstructure:"data_dir"`
|
||||
ResourceDir string `mapstructure:"resource_dir"`
|
||||
AppDir string `mapstructure:"app_dir"`
|
||||
AppOss string `mapstructure:"app_oss"`
|
||||
Port int `mapstructure:"port"`
|
||||
DbType string `mapstructure:"db_type"`
|
||||
Level string `mapstructure:"level"`
|
||||
DataDir string `mapstructure:"data_dir"`
|
||||
AppOss string `mapstructure:"app_oss"`
|
||||
}
|
||||
|
14
backend/constant/dir.go
Normal file
14
backend/constant/dir.go
Normal file
@ -0,0 +1,14 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultDataDir = "/opt/1Panel/data"
|
||||
ResourceDir = path.Join(DefaultDataDir, "resource")
|
||||
AppResourceDir = path.Join(ResourceDir, "apps")
|
||||
AppInstallDir = path.Join(DefaultDataDir, "apps")
|
||||
BackupDir = path.Join(DefaultDataDir, "backup")
|
||||
AppBackupDir = path.Join(BackupDir, "apps")
|
||||
)
|
@ -150,6 +150,6 @@ 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.Database{})
|
||||
return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{}, &model.Database{}, &model.AppInstallBackup{})
|
||||
},
|
||||
}
|
||||
|
@ -2,9 +2,10 @@ package viper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/configs"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"path"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
@ -30,4 +31,16 @@ func Init() {
|
||||
panic(err)
|
||||
}
|
||||
global.CONF = serverConfig
|
||||
InitDir()
|
||||
}
|
||||
|
||||
func InitDir() {
|
||||
constant.DefaultDataDir = "/opt/1Panel/data"
|
||||
constant.ResourceDir = path.Join(constant.DefaultDataDir, "resource")
|
||||
constant.AppResourceDir = path.Join(constant.ResourceDir, "apps")
|
||||
constant.AppInstallDir = path.Join(constant.DefaultDataDir, "apps")
|
||||
constant.BackupDir = path.Join(constant.DefaultDataDir, "backup")
|
||||
constant.AppBackupDir = path.Join(constant.BackupDir, "apps")
|
||||
|
||||
//TODO 创建文件夹
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
|
||||
appRouter.POST("/installed", baseApi.SearchInstalled)
|
||||
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.GET("/services/:key", baseApi.GetServices)
|
||||
}
|
||||
}
|
||||
|
@ -341,6 +341,10 @@ func (f FileOp) Compress(srcRiles []string, dst string, name string, cType Compr
|
||||
fileMaps[s] = base
|
||||
}
|
||||
|
||||
if !f.Stat(dst) {
|
||||
_ = f.CreateDir(dst, 0755)
|
||||
}
|
||||
|
||||
files, err := archiver.FilesFromDisk(nil, fileMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -66,12 +66,12 @@ export namespace App {
|
||||
name: string;
|
||||
appId: string;
|
||||
appDetailId: string;
|
||||
params: string;
|
||||
env: string;
|
||||
status: string;
|
||||
description: string;
|
||||
message: string;
|
||||
appName: string;
|
||||
icon: string;
|
||||
canUpdate: boolean;
|
||||
app: App;
|
||||
}
|
||||
|
||||
@ -84,4 +84,19 @@ export namespace App {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -37,3 +37,11 @@ export const SyncInstalledApp = () => {
|
||||
export const GetAppService = (key: string | undefined) => {
|
||||
return http.get<any>(`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);
|
||||
};
|
||||
|
@ -418,5 +418,9 @@ export default {
|
||||
deleteWarn:
|
||||
'Delete the operation data and delete the operation data. This operation cannot be rolled back. Do you want to continue?',
|
||||
canUpdate: 'CanUpdate',
|
||||
backup: 'Backup',
|
||||
backupName: 'Filename',
|
||||
backupPath: 'Filepath',
|
||||
backupdate: 'Backup Date',
|
||||
},
|
||||
};
|
||||
|
@ -410,5 +410,9 @@ export default {
|
||||
deleteWarn: '删除操作会把数据一并删除,此操作不可回滚,是否继续?',
|
||||
syncSuccess: '同步成功',
|
||||
canUpdate: '可更新',
|
||||
backup: '备份',
|
||||
backupName: '文件名称',
|
||||
backupPath: '文件路径',
|
||||
backupdate: '备份时间',
|
||||
},
|
||||
};
|
||||
|
109
frontend/src/views/app-store/installed/backups.vue
Normal file
109
frontend/src/views/app-store/installed/backups.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<el-dialog v-model="open" :title="$t('app.backup')" width="70%" :before-close="handleClose">
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" plain @click="backup">{{ $t('app.backup') }}</el-button>
|
||||
</template>
|
||||
<el-table-column :label="$t('app.backupName')" prop="name"></el-table-column>
|
||||
<el-table-column :label="$t('app.backupPath')" prop="path"></el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('app.backupdate')"
|
||||
:formatter="dateFromat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="300px"
|
||||
:ellipsis="10"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="installBackup">
|
||||
import { DelAppBackups, GetAppBackups, InstalledOp } from '@/api/modules/app';
|
||||
import { reactive, ref } from 'vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
|
||||
interface InstallRrops {
|
||||
appInstallId: number;
|
||||
}
|
||||
const installData = ref<InstallRrops>({
|
||||
appInstallId: 0,
|
||||
});
|
||||
let open = ref(false);
|
||||
let loading = ref(false);
|
||||
let data = ref<any>();
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const acceptParams = (props: InstallRrops) => {
|
||||
installData.value.appInstallId = props.appInstallId;
|
||||
search();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
const req = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
appInstallId: installData.value.appInstallId,
|
||||
};
|
||||
await GetAppBackups(req).then((res) => {
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
});
|
||||
};
|
||||
|
||||
const backup = async () => {
|
||||
const req = {
|
||||
installId: installData.value.appInstallId,
|
||||
operate: 'backup',
|
||||
};
|
||||
loading.value = true;
|
||||
await InstalledOp(req)
|
||||
.then(() => {
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteBackup = async (ids: number[]) => {
|
||||
const req = {
|
||||
ids: ids,
|
||||
};
|
||||
await useDeleteData(DelAppBackups, req, 'commons.msg.delete', loading.value);
|
||||
search();
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('app.delete'),
|
||||
click: (row: any) => {
|
||||
deleteBackup([row.id]);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -10,8 +10,15 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column :label="$t('app.description')" prop="description"></el-table-column> -->
|
||||
<el-table-column :label="$t('app.appName')" prop="appName"></el-table-column>
|
||||
<el-table-column :label="$t('app.appName')" prop="app.name"></el-table-column>
|
||||
<el-table-column :label="$t('app.version')" prop="version"></el-table-column>
|
||||
<el-table-column :label="$t('app.backup')">
|
||||
<template #default="{ row }">
|
||||
<el-link :underline="false" @click="openBackups(row.id)" type="primary">
|
||||
{{ $t('app.backup') }} ({{ row.backups.length }})
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('app.status')">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
@ -35,7 +42,7 @@
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="250px"
|
||||
width="300px"
|
||||
:ellipsis="10"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
@ -52,6 +59,7 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<Backups ref="backupRef"></Backups>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -61,6 +69,7 @@ import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import Backups from './backups.vue';
|
||||
|
||||
let data = ref<any>();
|
||||
let loading = ref(false);
|
||||
@ -74,6 +83,7 @@ let operateReq = reactive({
|
||||
installId: 0,
|
||||
operate: '',
|
||||
});
|
||||
const backupRef = ref();
|
||||
|
||||
const sync = () => {
|
||||
loading.value = true;
|
||||
@ -178,6 +188,13 @@ const buttons = [
|
||||
},
|
||||
];
|
||||
|
||||
const openBackups = (installId: number) => {
|
||||
let params = {
|
||||
appInstallId: installId,
|
||||
};
|
||||
backupRef.value.acceptParams(params);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user