mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-08 22:46:51 +08:00
parent
f196d029cb
commit
63ae17372d
20 changed files with 332 additions and 21 deletions
|
@ -14,8 +14,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Tags App
|
// @Tags App
|
||||||
// @Summary List app installed
|
// @Summary Page app installed
|
||||||
// @Description 获取已安装应用列表
|
// @Description 分页获取已安装应用列表
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body request.AppInstalledSearch true "request"
|
// @Param request body request.AppInstalledSearch true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
|
@ -47,6 +47,22 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags App
|
||||||
|
// @Summary List app installed
|
||||||
|
// @Description 获取已安装应用列表
|
||||||
|
// @Accept json
|
||||||
|
// @Success 200 array dto.AppInstallInfo
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /apps/installed/list [get]
|
||||||
|
func (b *BaseApi) ListAppInstalled(c *gin.Context) {
|
||||||
|
list, err := appInstallService.GetInstallList()
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, list)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags App
|
// @Tags App
|
||||||
// @Summary Check app installed
|
// @Summary Check app installed
|
||||||
// @Description 检查应用安装情况
|
// @Description 检查应用安装情况
|
||||||
|
|
|
@ -132,3 +132,9 @@ var AppToolMap = map[string]string{
|
||||||
"mysql": "phpmyadmin",
|
"mysql": "phpmyadmin",
|
||||||
"redis": "redis-commander",
|
"redis": "redis-commander",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppInstallInfo struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ type CronjobCreate struct {
|
||||||
|
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
ContainerName string `json:"containerName"`
|
ContainerName string `json:"containerName"`
|
||||||
|
AppID string `json:"appID"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
ExclusionRules string `json:"exclusionRules"`
|
ExclusionRules string `json:"exclusionRules"`
|
||||||
DBName string `json:"dbName"`
|
DBName string `json:"dbName"`
|
||||||
|
@ -36,6 +37,7 @@ type CronjobUpdate struct {
|
||||||
|
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
ContainerName string `json:"containerName"`
|
ContainerName string `json:"containerName"`
|
||||||
|
AppID string `json:"appID"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
ExclusionRules string `json:"exclusionRules"`
|
ExclusionRules string `json:"exclusionRules"`
|
||||||
DBName string `json:"dbName"`
|
DBName string `json:"dbName"`
|
||||||
|
@ -79,6 +81,7 @@ type CronjobInfo struct {
|
||||||
|
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
ContainerName string `json:"containerName"`
|
ContainerName string `json:"containerName"`
|
||||||
|
AppID string `json:"appID"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
ExclusionRules string `json:"exclusionRules"`
|
ExclusionRules string `json:"exclusionRules"`
|
||||||
DBName string `json:"dbName"`
|
DBName string `json:"dbName"`
|
||||||
|
|
|
@ -18,6 +18,7 @@ type Cronjob struct {
|
||||||
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
|
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
|
||||||
Script string `gorm:"longtext" json:"script"`
|
Script string `gorm:"longtext" json:"script"`
|
||||||
Website string `gorm:"type:varchar(64)" json:"website"`
|
Website string `gorm:"type:varchar(64)" json:"website"`
|
||||||
|
AppID string `gorm:"type:varchar(64)" json:"appID"`
|
||||||
DBName string `gorm:"type:varchar(64)" json:"dbName"`
|
DBName string `gorm:"type:varchar(64)" json:"dbName"`
|
||||||
URL string `gorm:"type:varchar(256)" json:"url"`
|
URL string `gorm:"type:varchar(256)" json:"url"`
|
||||||
SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"`
|
SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"`
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -12,6 +11,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
@ -54,12 +55,26 @@ type IAppInstallService interface {
|
||||||
ChangeAppPort(req request.PortUpdate) error
|
ChangeAppPort(req request.PortUpdate) error
|
||||||
GetDefaultConfigByKey(key string) (string, error)
|
GetDefaultConfigByKey(key string) (string, error)
|
||||||
DeleteCheck(installId uint) ([]dto.AppResource, error)
|
DeleteCheck(installId uint) ([]dto.AppResource, error)
|
||||||
|
|
||||||
|
GetInstallList() ([]dto.AppInstallInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIAppInstalledService() IAppInstallService {
|
func NewIAppInstalledService() IAppInstallService {
|
||||||
return &AppInstallService{}
|
return &AppInstallService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AppInstallService) GetInstallList() ([]dto.AppInstallInfo, error) {
|
||||||
|
var datas []dto.AppInstallInfo
|
||||||
|
appInstalls, err := appInstallRepo.ListBy()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, install := range appInstalls {
|
||||||
|
datas = append(datas, dto.AppInstallInfo{ID: install.ID, Key: install.App.Key, Name: install.Name})
|
||||||
|
}
|
||||||
|
return datas, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
|
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
|
||||||
var (
|
var (
|
||||||
opts []repo.DBOption
|
opts []repo.DBOption
|
||||||
|
|
|
@ -46,7 +46,7 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter
|
||||||
if err := copier.Copy(&item, &cronjob); err != nil {
|
if err := copier.Copy(&item, &cronjob); err != nil {
|
||||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
}
|
}
|
||||||
if item.Type == "website" || item.Type == "database" || item.Type == "directory" {
|
if item.Type == "app" || item.Type == "website" || item.Type == "database" || item.Type == "directory" {
|
||||||
backup, _ := backupRepo.Get(commonRepo.WithByID(uint(item.TargetDirID)))
|
backup, _ := backupRepo.Get(commonRepo.WithByID(uint(item.TargetDirID)))
|
||||||
if len(backup.Type) != 0 {
|
if len(backup.Type) != 0 {
|
||||||
item.TargetDir = backup.Type
|
item.TargetDir = backup.Type
|
||||||
|
@ -103,7 +103,7 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if req.CleanData && (cronjob.Type == "database" || cronjob.Type == "website" || cronjob.Type == "directory") {
|
if req.CleanData && (cronjob.Type == "app" || cronjob.Type == "database" || cronjob.Type == "website" || cronjob.Type == "directory") {
|
||||||
cronjob.RetainCopies = 0
|
cronjob.RetainCopies = 0
|
||||||
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -47,9 +47,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||||
case "ntp":
|
case "ntp":
|
||||||
err = u.handleNtpSync()
|
err = u.handleNtpSync()
|
||||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||||
case "website":
|
case "website", "database", "app":
|
||||||
record.File, err = u.handleBackup(cronjob, record.StartTime)
|
|
||||||
case "database":
|
|
||||||
record.File, err = u.handleBackup(cronjob, record.StartTime)
|
record.File, err = u.handleBackup(cronjob, record.StartTime)
|
||||||
case "directory":
|
case "directory":
|
||||||
if len(cronjob.SourceDir) == 0 {
|
if len(cronjob.SourceDir) == 0 {
|
||||||
|
@ -120,6 +118,9 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||||
case "database":
|
case "database":
|
||||||
paths, err := u.handleDatabase(*cronjob, backup, startTime)
|
paths, err := u.handleDatabase(*cronjob, backup, startTime)
|
||||||
return strings.Join(paths, ","), err
|
return strings.Join(paths, ","), err
|
||||||
|
case "app":
|
||||||
|
paths, err := u.handleApp(*cronjob, backup, startTime)
|
||||||
|
return strings.Join(paths, ","), err
|
||||||
case "website":
|
case "website":
|
||||||
paths, err := u.handleWebsite(*cronjob, backup, startTime)
|
paths, err := u.handleWebsite(*cronjob, backup, startTime)
|
||||||
return strings.Join(paths, ","), err
|
return strings.Join(paths, ","), err
|
||||||
|
@ -221,7 +222,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||||
path = sourceDir
|
path = sourceDir
|
||||||
}
|
}
|
||||||
|
|
||||||
commands := fmt.Sprintf("tar --warning=no-file-changed -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
|
commands := fmt.Sprintf("tar -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
|
||||||
global.LOG.Debug(commands)
|
global.LOG.Debug(commands)
|
||||||
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -396,6 +397,84 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||||
return strings.Join(filePaths, ","), nil
|
return strings.Join(filePaths, ","), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *CronjobService) handleApp(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||||
|
var paths []string
|
||||||
|
localDir, err := loadLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var applist []model.AppInstall
|
||||||
|
if cronjob.AppID == "all" {
|
||||||
|
applist, err = appInstallRepo.ListBy()
|
||||||
|
if err != nil {
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemID, _ := (strconv.Atoi(cronjob.AppID))
|
||||||
|
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||||
|
if err != nil {
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
applist = append(applist, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
var client cloud_storage.CloudStorageClient
|
||||||
|
if backup.Type != "LOCAL" {
|
||||||
|
client, err = NewIBackupService().NewClient(&backup)
|
||||||
|
if err != nil {
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, app := range applist {
|
||||||
|
var record model.BackupRecord
|
||||||
|
record.Type = "app"
|
||||||
|
record.Name = app.App.Key
|
||||||
|
record.DetailName = app.Name
|
||||||
|
record.Source = "LOCAL"
|
||||||
|
record.BackupType = backup.Type
|
||||||
|
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
|
||||||
|
record.FileDir = backupDir
|
||||||
|
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||||
|
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||||
|
record.Source = backup.Type
|
||||||
|
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
|
||||||
|
}
|
||||||
|
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format("20060102150405"))
|
||||||
|
if err := handleAppBackup(&app, backupDir, record.FileName); err != nil {
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
record.Name = app.Name
|
||||||
|
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
if backup.Type != "LOCAL" {
|
||||||
|
if !cronjob.KeepLocal {
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if len(backup.BackupPath) != 0 {
|
||||||
|
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||||
|
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||||
|
itemFileDir = itemPath + itemFileDir
|
||||||
|
}
|
||||||
|
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||||
|
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||||
|
} else {
|
||||||
|
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||||
var paths []string
|
var paths []string
|
||||||
localDir, err := loadLocalDir()
|
localDir, err := loadLocalDir()
|
||||||
|
|
|
@ -572,9 +572,9 @@ var UpdateCronjobWithDb = &gormigrate.Migration{
|
||||||
}
|
}
|
||||||
|
|
||||||
var AddTableFirewall = &gormigrate.Migration{
|
var AddTableFirewall = &gormigrate.Migration{
|
||||||
ID: "20230821-add-table-firewall",
|
ID: "20230823-add-table-firewall",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
if err := tx.AutoMigrate(&model.Firewall{}, model.SnapshotStatus{}); err != nil {
|
if err := tx.AutoMigrate(&model.Firewall{}, model.SnapshotStatus{}, &model.Cronjob{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -29,6 +29,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
|
||||||
appRouter.GET("/installed/conninfo/:key", baseApi.LoadConnInfo)
|
appRouter.GET("/installed/conninfo/:key", baseApi.LoadConnInfo)
|
||||||
appRouter.GET("/installed/delete/check/:appInstallId", baseApi.DeleteCheck)
|
appRouter.GET("/installed/delete/check/:appInstallId", baseApi.DeleteCheck)
|
||||||
appRouter.POST("/installed/search", baseApi.SearchAppInstalled)
|
appRouter.POST("/installed/search", baseApi.SearchAppInstalled)
|
||||||
|
appRouter.GET("/installed/list", baseApi.ListAppInstalled)
|
||||||
appRouter.POST("/installed/op", baseApi.OperateInstalled)
|
appRouter.POST("/installed/op", baseApi.OperateInstalled)
|
||||||
appRouter.POST("/installed/sync", baseApi.SyncInstalled)
|
appRouter.POST("/installed/sync", baseApi.SyncInstalled)
|
||||||
appRouter.POST("/installed/port/change", baseApi.ChangeAppPort)
|
appRouter.POST("/installed/port/change", baseApi.ChangeAppPort)
|
||||||
|
|
|
@ -460,6 +460,34 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/apps/installed/list": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取已安装应用列表",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"App"
|
||||||
|
],
|
||||||
|
"summary": "List app installed",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.AppInstallInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/apps/installed/loadport/:key": {
|
"/apps/installed/loadport/:key": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -689,14 +717,14 @@ const docTemplate = `{
|
||||||
"ApiKeyAuth": []
|
"ApiKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "获取已安装应用列表",
|
"description": "分页获取已安装应用列表",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"App"
|
"App"
|
||||||
],
|
],
|
||||||
"summary": "List app installed",
|
"summary": "Page app installed",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "request",
|
"description": "request",
|
||||||
|
@ -11580,6 +11608,20 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.AppInstallInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.AppResource": {
|
"dto.AppResource": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -12213,6 +12255,9 @@ const docTemplate = `{
|
||||||
"type"
|
"type"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"appID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"containerName": {
|
"containerName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -12295,6 +12340,9 @@ const docTemplate = `{
|
||||||
"specType"
|
"specType"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"appID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"containerName": {
|
"containerName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
@ -453,6 +453,34 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/apps/installed/list": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取已安装应用列表",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"App"
|
||||||
|
],
|
||||||
|
"summary": "List app installed",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.AppInstallInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/apps/installed/loadport/:key": {
|
"/apps/installed/loadport/:key": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -682,14 +710,14 @@
|
||||||
"ApiKeyAuth": []
|
"ApiKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "获取已安装应用列表",
|
"description": "分页获取已安装应用列表",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"App"
|
"App"
|
||||||
],
|
],
|
||||||
"summary": "List app installed",
|
"summary": "Page app installed",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "request",
|
"description": "request",
|
||||||
|
@ -11573,6 +11601,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.AppInstallInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.AppResource": {
|
"dto.AppResource": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -12206,6 +12248,9 @@
|
||||||
"type"
|
"type"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"appID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"containerName": {
|
"containerName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -12288,6 +12333,9 @@
|
||||||
"specType"
|
"specType"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"appID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"containerName": {
|
"containerName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,6 +28,15 @@ definitions:
|
||||||
oldRule:
|
oldRule:
|
||||||
$ref: '#/definitions/dto.AddrRuleOperate'
|
$ref: '#/definitions/dto.AddrRuleOperate'
|
||||||
type: object
|
type: object
|
||||||
|
dto.AppInstallInfo:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.AppResource:
|
dto.AppResource:
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
@ -449,6 +458,8 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
dto.CronjobCreate:
|
dto.CronjobCreate:
|
||||||
properties:
|
properties:
|
||||||
|
appID:
|
||||||
|
type: string
|
||||||
containerName:
|
containerName:
|
||||||
type: string
|
type: string
|
||||||
day:
|
day:
|
||||||
|
@ -505,6 +516,8 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
dto.CronjobUpdate:
|
dto.CronjobUpdate:
|
||||||
properties:
|
properties:
|
||||||
|
appID:
|
||||||
|
type: string
|
||||||
containerName:
|
containerName:
|
||||||
type: string
|
type: string
|
||||||
day:
|
day:
|
||||||
|
@ -4177,6 +4190,23 @@ paths:
|
||||||
formatEN: Application param update [installId]
|
formatEN: Application param update [installId]
|
||||||
formatZH: 忽略应用 [installId] 版本升级
|
formatZH: 忽略应用 [installId] 版本升级
|
||||||
paramKeys: []
|
paramKeys: []
|
||||||
|
/apps/installed/list:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 获取已安装应用列表
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/dto.AppInstallInfo'
|
||||||
|
type: array
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: List app installed
|
||||||
|
tags:
|
||||||
|
- App
|
||||||
/apps/installed/loadport/:key:
|
/apps/installed/loadport/:key:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -4325,7 +4355,7 @@ paths:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: 获取已安装应用列表
|
description: 分页获取已安装应用列表
|
||||||
parameters:
|
parameters:
|
||||||
- description: request
|
- description: request
|
||||||
in: body
|
in: body
|
||||||
|
@ -4338,7 +4368,7 @@ paths:
|
||||||
description: OK
|
description: OK
|
||||||
security:
|
security:
|
||||||
- ApiKeyAuth: []
|
- ApiKeyAuth: []
|
||||||
summary: List app installed
|
summary: Page app installed
|
||||||
tags:
|
tags:
|
||||||
- App
|
- App
|
||||||
/apps/installed/sync:
|
/apps/installed/sync:
|
||||||
|
|
|
@ -116,6 +116,12 @@ export namespace App {
|
||||||
app: App;
|
app: App;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppInstalledInfo {
|
||||||
|
id: number;
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CheckInstalled {
|
export interface CheckInstalled {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
|
|
@ -15,6 +15,7 @@ export namespace Cronjob {
|
||||||
script: string;
|
script: string;
|
||||||
inContainer: boolean;
|
inContainer: boolean;
|
||||||
containerName: string;
|
containerName: string;
|
||||||
|
appID: string;
|
||||||
website: string;
|
website: string;
|
||||||
exclusionRules: string;
|
exclusionRules: string;
|
||||||
dbName: string;
|
dbName: string;
|
||||||
|
|
|
@ -42,6 +42,10 @@ export const SearchAppInstalled = (search: App.AppInstallSearch) => {
|
||||||
return http.post<ResPage<App.AppInstalled>>('apps/installed/search', search);
|
return http.post<ResPage<App.AppInstalled>>('apps/installed/search', search);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ListAppInstalled = () => {
|
||||||
|
return http.get<Array<App.AppInstalledInfo>>('apps/installed/list');
|
||||||
|
};
|
||||||
|
|
||||||
export const GetAppPort = (key: string) => {
|
export const GetAppPort = (key: string) => {
|
||||||
return http.get<number>(`apps/installed/loadport/${key}`);
|
return http.get<number>(`apps/installed/loadport/${key}`);
|
||||||
};
|
};
|
||||||
|
|
|
@ -689,6 +689,7 @@ const message = {
|
||||||
containerCheckBox: 'In container (no need to enter the container command)',
|
containerCheckBox: 'In container (no need to enter the container command)',
|
||||||
containerName: 'Container name',
|
containerName: 'Container name',
|
||||||
ntp: 'Time synchronization',
|
ntp: 'Time synchronization',
|
||||||
|
app: 'Backup app',
|
||||||
website: 'Backup website',
|
website: 'Backup website',
|
||||||
rulesHelper:
|
rulesHelper:
|
||||||
'When there are multiple compression exclusion rules, they need to be displayed with line breaks. For example: \n*.log \n*.sql',
|
'When there are multiple compression exclusion rules, they need to be displayed with line breaks. For example: \n*.log \n*.sql',
|
||||||
|
|
|
@ -665,6 +665,7 @@ const message = {
|
||||||
containerCheckBox: '在容器中執行(無需再輸入進入容器命令)',
|
containerCheckBox: '在容器中執行(無需再輸入進入容器命令)',
|
||||||
containerName: '容器名稱',
|
containerName: '容器名稱',
|
||||||
ntp: '時間同步',
|
ntp: '時間同步',
|
||||||
|
app: '備份應用',
|
||||||
website: '備份網站',
|
website: '備份網站',
|
||||||
rulesHelper: '當存在多個壓縮排除規則時,需要換行顯示,例:\n*.log \n*.sql',
|
rulesHelper: '當存在多個壓縮排除規則時,需要換行顯示,例:\n*.log \n*.sql',
|
||||||
lastRecordTime: '上次執行時間',
|
lastRecordTime: '上次執行時間',
|
||||||
|
|
|
@ -665,6 +665,7 @@ const message = {
|
||||||
containerCheckBox: '在容器中执行(无需再输入进入容器命令)',
|
containerCheckBox: '在容器中执行(无需再输入进入容器命令)',
|
||||||
containerName: '容器名称',
|
containerName: '容器名称',
|
||||||
ntp: '时间同步',
|
ntp: '时间同步',
|
||||||
|
app: '备份应用',
|
||||||
website: '备份网站',
|
website: '备份网站',
|
||||||
rulesHelper: '当存在多个压缩排除规则时,需要换行显示,例:\n*.log \n*.sql',
|
rulesHelper: '当存在多个压缩排除规则时,需要换行显示,例:\n*.log \n*.sql',
|
||||||
lastRecordTime: '上次执行时间',
|
lastRecordTime: '上次执行时间',
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
v-model="dialogData.rowData!.type"
|
v-model="dialogData.rowData!.type"
|
||||||
>
|
>
|
||||||
<el-option value="shell" :label="$t('cronjob.shell')" />
|
<el-option value="shell" :label="$t('cronjob.shell')" />
|
||||||
|
<el-option value="app" :label="$t('cronjob.app')" />
|
||||||
<el-option value="website" :label="$t('cronjob.website')" />
|
<el-option value="website" :label="$t('cronjob.website')" />
|
||||||
<el-option value="database" :label="$t('cronjob.database')" />
|
<el-option value="database" :label="$t('cronjob.database')" />
|
||||||
<el-option value="directory" :label="$t('cronjob.directory')" />
|
<el-option value="directory" :label="$t('cronjob.directory')" />
|
||||||
|
@ -118,6 +119,17 @@
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<div v-if="dialogData.rowData!.type === 'app'">
|
||||||
|
<el-form-item :label="$t('cronjob.app')" prop="appID">
|
||||||
|
<el-select class="selectClass" clearable v-model="dialogData.rowData!.appID">
|
||||||
|
<el-option :label="$t('commons.table.all')" value="all" />
|
||||||
|
<div v-for="item in appOptions" :key="item.id">
|
||||||
|
<el-option :label="item.key + ' [' + item.name + ']'" :value="item.id + ''" />
|
||||||
|
</div>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="dialogData.rowData!.type === 'database'">
|
<div v-if="dialogData.rowData!.type === 'database'">
|
||||||
<el-form-item :label="$t('cronjob.database')" prop="dbName">
|
<el-form-item :label="$t('cronjob.database')" prop="dbName">
|
||||||
<el-select class="selectClass" clearable v-model="dialogData.rowData!.dbName">
|
<el-select class="selectClass" clearable v-model="dialogData.rowData!.dbName">
|
||||||
|
@ -237,6 +249,7 @@ import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { listContainer } from '@/api/modules/container';
|
import { listContainer } from '@/api/modules/container';
|
||||||
import { Database } from '@/api/interface/database';
|
import { Database } from '@/api/interface/database';
|
||||||
|
import { ListAppInstalled } from '@/api/modules/app';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
|
@ -264,6 +277,7 @@ const acceptParams = (params: DialogProps): void => {
|
||||||
drawerVisiable.value = true;
|
drawerVisiable.value = true;
|
||||||
checkMysqlInstalled();
|
checkMysqlInstalled();
|
||||||
loadBackups();
|
loadBackups();
|
||||||
|
loadAppInstalls();
|
||||||
loadWebsites();
|
loadWebsites();
|
||||||
loadContainers();
|
loadContainers();
|
||||||
};
|
};
|
||||||
|
@ -282,6 +296,7 @@ const localDirID = ref();
|
||||||
const containerOptions = ref();
|
const containerOptions = ref();
|
||||||
const websiteOptions = ref();
|
const websiteOptions = ref();
|
||||||
const backupOptions = ref();
|
const backupOptions = ref();
|
||||||
|
const appOptions = ref();
|
||||||
|
|
||||||
const mysqlInfo = reactive({
|
const mysqlInfo = reactive({
|
||||||
isExist: false,
|
isExist: false,
|
||||||
|
@ -417,6 +432,11 @@ const changeType = () => {
|
||||||
dialogData.value.rowData.hour = 1;
|
dialogData.value.rowData.hour = 1;
|
||||||
dialogData.value.rowData.minute = 30;
|
dialogData.value.rowData.minute = 30;
|
||||||
break;
|
break;
|
||||||
|
case 'app':
|
||||||
|
dialogData.value.rowData.specType = 'perDay';
|
||||||
|
dialogData.value.rowData.hour = 2;
|
||||||
|
dialogData.value.rowData.minute = 30;
|
||||||
|
break;
|
||||||
case 'database':
|
case 'database':
|
||||||
dialogData.value.rowData.specType = 'perDay';
|
dialogData.value.rowData.specType = 'perDay';
|
||||||
dialogData.value.rowData.hour = 2;
|
dialogData.value.rowData.hour = 2;
|
||||||
|
@ -459,6 +479,11 @@ const loadBackups = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadAppInstalls = async () => {
|
||||||
|
const res = await ListAppInstalled();
|
||||||
|
appOptions.value = res.data || [];
|
||||||
|
};
|
||||||
|
|
||||||
const loadWebsites = async () => {
|
const loadWebsites = async () => {
|
||||||
const res = await GetWebsiteOptions();
|
const res = await GetWebsiteOptions();
|
||||||
websiteOptions.value = res.data || [];
|
websiteOptions.value = res.data || [];
|
||||||
|
@ -476,6 +501,7 @@ const checkMysqlInstalled = async () => {
|
||||||
|
|
||||||
function isBackup() {
|
function isBackup() {
|
||||||
return (
|
return (
|
||||||
|
dialogData.value.rowData!.type === 'app' ||
|
||||||
dialogData.value.rowData!.type === 'website' ||
|
dialogData.value.rowData!.type === 'website' ||
|
||||||
dialogData.value.rowData!.type === 'database' ||
|
dialogData.value.rowData!.type === 'database' ||
|
||||||
dialogData.value.rowData!.type === 'directory'
|
dialogData.value.rowData!.type === 'directory'
|
||||||
|
|
|
@ -177,6 +177,17 @@
|
||||||
{{ $t('file.download') }}
|
{{ $t('file.download') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item class="description" v-if="dialogData.rowData!.type === 'app'">
|
||||||
|
<template #label>
|
||||||
|
<span class="status-label">{{ $t('cronjob.app') }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-if="dialogData.rowData!.appID !== 'all'" class="status-count">
|
||||||
|
{{ dialogData.rowData!.appID }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="status-count">
|
||||||
|
{{ $t('commons.table.all') }}
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item class="description" v-if="dialogData.rowData!.type === 'website'">
|
<el-form-item class="description" v-if="dialogData.rowData!.type === 'website'">
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="status-label">{{ $t('cronjob.website') }}</span>
|
<span class="status-label">{{ $t('cronjob.website') }}</span>
|
||||||
|
@ -228,10 +239,7 @@
|
||||||
<span class="status-count">{{ dialogData.rowData!.retainCopies }}</span>
|
<span class="status-count">{{ dialogData.rowData!.retainCopies }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-form-item
|
<el-form-item class="description" v-if=" dialogData.rowData!.type === 'directory'">
|
||||||
class="description"
|
|
||||||
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
|
|
||||||
>
|
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="status-label">{{ $t('cronjob.exclusionRules') }}</span>
|
<span class="status-label">{{ $t('cronjob.exclusionRules') }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -375,6 +383,7 @@ import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { oneDark } from '@codemirror/theme-one-dark';
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message';
|
||||||
import { loadDBOptions } from '@/api/modules/database';
|
import { loadDBOptions } from '@/api/modules/database';
|
||||||
|
import { ListAppInstalled } from '@/api/modules/app';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const refresh = ref(false);
|
const refresh = ref(false);
|
||||||
|
@ -415,6 +424,16 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (dialogData.value.rowData.type === 'app') {
|
||||||
|
const res = await ListAppInstalled();
|
||||||
|
let itemApps = res.data || [];
|
||||||
|
for (const item of itemApps) {
|
||||||
|
if (item.id == dialogData.value.rowData.appID) {
|
||||||
|
dialogData.value.rowData.appID = item.key + ' [' + item.name + ']';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
search();
|
search();
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
search();
|
search();
|
||||||
|
@ -572,6 +591,10 @@ const onDownload = async (record: any, backupID: number) => {
|
||||||
MsgInfo(i18n.global.t('cronjob.allOptionHelper', [i18n.global.t('database.database')]));
|
MsgInfo(i18n.global.t('cronjob.allOptionHelper', [i18n.global.t('database.database')]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (dialogData.value.rowData.app === 'all') {
|
||||||
|
MsgInfo(i18n.global.t('cronjob.allOptionHelper', [i18n.global.t('app.app')]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (dialogData.value.rowData.website === 'all') {
|
if (dialogData.value.rowData.website === 'all') {
|
||||||
MsgInfo(i18n.global.t('cronjob.allOptionHelper', [i18n.global.t('website.website')]));
|
MsgInfo(i18n.global.t('cronjob.allOptionHelper', [i18n.global.t('website.website')]));
|
||||||
return;
|
return;
|
||||||
|
@ -652,6 +675,7 @@ const cleanRecord = async () => {
|
||||||
|
|
||||||
function isBackup() {
|
function isBackup() {
|
||||||
return (
|
return (
|
||||||
|
dialogData.value.rowData!.type === 'app' ||
|
||||||
dialogData.value.rowData!.type === 'website' ||
|
dialogData.value.rowData!.type === 'website' ||
|
||||||
dialogData.value.rowData!.type === 'database' ||
|
dialogData.value.rowData!.type === 'database' ||
|
||||||
dialogData.value.rowData!.type === 'directory'
|
dialogData.value.rowData!.type === 'directory'
|
||||||
|
|
Loading…
Add table
Reference in a new issue