diff --git a/backend/app/api/v1/setting.go b/backend/app/api/v1/setting.go index 4f5a62b3d..41830abc8 100644 --- a/backend/app/api/v1/setting.go +++ b/backend/app/api/v1/setting.go @@ -277,6 +277,37 @@ func (b *BaseApi) CleanMonitor(c *gin.Context) { helper.SuccessWithData(c, nil) } +// @Tags System Setting +// @Summary Scan system +// @Description 扫描系统垃圾文件 +// @Success 200 +// @Security ApiKeyAuth +// @Router /settings/scan [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"扫描系统垃圾文件","formatEN":"scan System Junk Files"} +func (b *BaseApi) ScanSystem(c *gin.Context) { + helper.SuccessWithData(c, settingService.SystemScan()) +} + +// @Tags System Setting +// @Summary System clean +// @Description 清理系统垃圾文件 +// @Accept json +// @Param request body []dto.Clean true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /settings/clean [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"清理系统垃圾文件","formatEN":"Clean system junk files"} +func (b *BaseApi) SystemClean(c *gin.Context) { + var req []dto.Clean + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + settingService.SystemClean(req) + + helper.SuccessWithData(c, nil) +} + // @Tags System Setting // @Summary Load mfa info // @Description 获取 mfa 信息 diff --git a/backend/app/dto/setting.go b/backend/app/dto/setting.go index c18831cd6..271ec1020 100644 --- a/backend/app/dto/setting.go +++ b/backend/app/dto/setting.go @@ -18,6 +18,9 @@ type SettingInfo struct { Theme string `json:"theme"` Language string `json:"language"` DefaultNetwork string `json:"defaultNetwork"` + LastCleanTime string `json:"lastCleanTime"` + LastCleanSize string `json:"lastCleanSize"` + LastCleanData string `json:"lastCleanData"` ServerPort string `json:"serverPort"` SSL string `json:"ssl"` @@ -136,3 +139,29 @@ type SyncTime struct { type Upgrade struct { Version string `json:"version"` } + +type CleanData struct { + SystemClean []CleanTree `json:"systemClean"` + UploadClean []CleanTree `json:"uploadClean"` + DownloadClean []CleanTree `json:"downloadClean"` + SystemLogClean []CleanTree `json:"systemLogClean"` +} + +type CleanTree struct { + ID string `json:"id"` + Label string `json:"label"` + Children []CleanTree `json:"children"` + + Type string `json:"type"` + Name string `json:"name"` + + Size uint64 `json:"size"` + IsCheck bool `json:"isCheck"` + IsRecommend bool `json:"isRecommend"` +} + +type Clean struct { + TreeType string `json:"treeType"` + Name string `json:"name"` + Size uint64 `json:"size"` +} diff --git a/backend/app/repo/cronjob.go b/backend/app/repo/cronjob.go index 0d630e895..115b573b8 100644 --- a/backend/app/repo/cronjob.go +++ b/backend/app/repo/cronjob.go @@ -22,6 +22,7 @@ type ICronjobRepo interface { WithByJobID(id int) DBOption WithByBackupID(id uint) DBOption WithByRecordDropID(id int) DBOption + WithByRecordFile(file string) DBOption Save(id uint, cronjob model.Cronjob) error Update(id uint, vars map[string]interface{}) error Delete(opts ...DBOption) error @@ -121,6 +122,12 @@ func (c *CronjobRepo) WithByBackupID(id uint) DBOption { } } +func (c *CronjobRepo) WithByRecordFile(file string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("records = ?", file) + } +} + func (c *CronjobRepo) WithByRecordDropID(id int) DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("id < ?", id) diff --git a/backend/app/service/setting.go b/backend/app/service/setting.go index e34e3064b..e087afd5a 100644 --- a/backend/app/service/setting.go +++ b/backend/app/service/setting.go @@ -38,6 +38,9 @@ type ISettingService interface { LoadFromCert() (*dto.SSLInfo, error) HandlePasswordExpired(c *gin.Context, old, new string) error SyncTime(req dto.SyncTime) error + + SystemScan() dto.CleanData + SystemClean(req []dto.Clean) } func NewISettingService() ISettingService { diff --git a/backend/app/service/setting_clean.go b/backend/app/service/setting_clean.go new file mode 100644 index 000000000..6abddb06c --- /dev/null +++ b/backend/app/service/setting_clean.go @@ -0,0 +1,358 @@ +package service + +import ( + "fmt" + "os" + "path" + "sort" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/cmd" + fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files" + "github.com/google/uuid" +) + +func (u *SettingService) SystemScan() dto.CleanData { + var ( + SystemClean dto.CleanData + treeData []dto.CleanTree + ) + fileOp := fileUtils.NewFileOp() + + originalPath := path.Join(global.CONF.System.BaseDir, "1panel_original") + originalSize, _ := fileOp.GetDirSize(originalPath) + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "1panel_original", + Size: uint64(originalSize), + IsCheck: true, + IsRecommend: true, + Type: "1panel_original", + Children: loadTreeWithDir(true, "1panel_original", originalPath, fileOp), + }) + + upgradePath := path.Join(global.CONF.System.BaseDir, "1panel/tmp/upgrade") + upgradeSize, _ := fileOp.GetDirSize(upgradePath) + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "upgrade", + Size: uint64(upgradeSize), + IsCheck: false, + IsRecommend: true, + Type: "upgrade", + Children: loadTreeWithDir(true, "upgrade", upgradePath, fileOp), + }) + + snapTree := loadSnapshotTree(fileOp) + snapSize := uint64(0) + for _, snap := range snapTree { + snapSize += uint64(snap.Size) + } + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "snapshot", + Size: snapSize, + IsCheck: true, + IsRecommend: true, + Type: "snapshot", + Children: snapTree, + }) + + cachePath := path.Join(global.CONF.System.BaseDir, "1panel/cache") + cacheSize, _ := fileOp.GetDirSize(cachePath) + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "cache", + Size: uint64(cacheSize), + IsCheck: false, + IsRecommend: false, + Type: "cache", + }) + + unusedTree := loadUnusedFile(fileOp) + unusedSize := uint64(0) + for _, unused := range unusedTree { + unusedSize += uint64(unused.Size) + } + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "unused", + Size: unusedSize, + IsCheck: true, + IsRecommend: true, + Type: "unused", + Children: unusedTree, + }) + SystemClean.SystemClean = treeData + + uploadPath := path.Join(global.CONF.System.BaseDir, "1panel/uploads") + uploadTreeData := loadTreeWithAllFile(true, uploadPath, "upload", uploadPath, fileOp) + SystemClean.UploadClean = append(SystemClean.UploadClean, uploadTreeData...) + + downloadPath := path.Join(global.CONF.System.BaseDir, "1panel/download") + downloadTreeData := loadTreeWithAllFile(true, downloadPath, "download", downloadPath, fileOp) + SystemClean.DownloadClean = append(SystemClean.DownloadClean, downloadTreeData...) + + logTree := loadLogTree(fileOp) + SystemClean.SystemLogClean = append(SystemClean.SystemLogClean, logTree...) + + return SystemClean +} + +func (u *SettingService) SystemClean(req []dto.Clean) { + size := uint64(0) + for _, item := range req { + size += item.Size + switch item.TreeType { + case "1panel_original": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel_original", item.Name)) + + case "upgrade": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp/upgrade", item.Name)) + + case "snapshot": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp/system", item.Name)) + dropFileOrDir(path.Join(global.CONF.System.Backup, "system", item.Name)) + case "snapshot_tmp": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp/system", item.Name)) + case "snapshot_local": + dropFileOrDir(path.Join(global.CONF.System.Backup, "system", item.Name)) + + case "cache": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/cache", item.Name)) + defer func() { + _, _ = cmd.Exec("systemctl restart 1panel.service") + }() + + case "unused": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "original", item.Name)) + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/resource/apps_bak", item.Name)) + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp/download", item.Name)) + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp", item.Name)) + case "old_original": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "original", item.Name)) + case "old_apps_bak": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/resource/apps_bak", item.Name)) + case "old_download": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp/download", item.Name)) + case "old_upgrade": + if len(item.Name) == 0 { + files, _ := os.ReadDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp")) + if len(files) == 0 { + continue + } + for _, file := range files { + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp", file.Name())) + } + } else { + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp", item.Name)) + } + + case "upload": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/uploads", item.Name)) + case "download": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/download", item.Name)) + + case "system_log": + if len(item.Name) == 0 { + files, _ := os.ReadDir(path.Join(global.CONF.System.BaseDir, "1panel/log")) + if len(files) == 0 { + continue + } + for _, file := range files { + if file.Name() == "1Panel.log" { + continue + } + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/log", file.Name())) + } + } else { + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/log", item.Name)) + } + case "docker_log": + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/tmp/docker_logs", item.Name)) + case "task_log": + pathItem := path.Join(global.CONF.System.BaseDir, "1panel/task", item.Name) + dropFileOrDir(path.Join(global.CONF.System.BaseDir, "1panel/task", item.Name)) + if len(item.Name) == 0 { + files, _ := os.ReadDir(pathItem) + if len(files) == 0 { + continue + } + for _, file := range files { + _ = cronjobRepo.DeleteRecord(cronjobRepo.WithByRecordFile(path.Join(pathItem, file.Name()))) + } + } else { + _ = cronjobRepo.DeleteRecord(cronjobRepo.WithByRecordFile(pathItem)) + } + } + } + + _ = settingRepo.Update("LastCleanTime", time.Now().Format("2006-01-02 15:04:05")) + _ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size)) + _ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", len(req))) +} + +func loadSnapshotTree(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + path1 := path.Join(global.CONF.System.BaseDir, "1panel/tmp/system") + list1 := loadTreeWithAllFile(true, path1, "snapshot_tmp", path1, fileOp) + if len(list1) != 0 { + size, _ := fileOp.GetDirSize(path1) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "snapshot_tmp", Size: uint64(size), Children: list1, Type: "snapshot_tmp", IsRecommend: true}) + } + + path2 := path.Join(global.CONF.System.Backup, "system") + list2 := loadTreeWithAllFile(true, path2, "snapshot_local", path2, fileOp) + if len(list2) != 0 { + size, _ := fileOp.GetDirSize(path2) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "snapshot_local", Size: uint64(size), Children: list2, Type: "snapshot_local", IsRecommend: true}) + } + return treeData +} + +func loadUnusedFile(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + path1 := path.Join(global.CONF.System.BaseDir, "original") + list1 := loadTreeWithAllFile(true, path1, "old_original", path1, fileOp) + if len(list1) != 0 { + size, _ := fileOp.GetDirSize(path1) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_original", Size: uint64(size), Children: list1, Type: "old_original"}) + } + + path2 := path.Join(global.CONF.System.BaseDir, "1panel/resource/apps_bak") + list2 := loadTreeWithAllFile(true, path2, "old_apps_bak", path2, fileOp) + if len(list2) != 0 { + size, _ := fileOp.GetDirSize(path2) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_apps_bak", Size: uint64(size), Children: list2, Type: "old_apps_bak"}) + } + + path3 := path.Join(global.CONF.System.BaseDir, "1panel/tmp/download") + list3 := loadTreeWithAllFile(true, path3, "old_download", path3, fileOp) + if len(list3) != 0 { + size, _ := fileOp.GetDirSize(path3) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_download", Size: uint64(size), Children: list3, Type: "old_download"}) + } + + path4 := path.Join(global.CONF.System.BaseDir, "1panel/tmp") + list4 := loadTreeWithDir(true, "old_upgrade", path4, fileOp) + itemSize := uint64(0) + for _, item := range list4 { + itemSize += item.Size + } + if len(list4) != 0 { + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_upgrade", Size: itemSize, Children: list4, Type: "old_upgrade"}) + } + return treeData +} + +func loadLogTree(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + path1 := path.Join(global.CONF.System.BaseDir, "1panel/log") + list1 := loadTreeWithAllFile(true, path1, "system_log", path1, fileOp) + size := uint64(0) + for _, file := range list1 { + size += file.Size + } + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: uint64(size), Children: list1, Type: "system_log", IsRecommend: true}) + + path2 := path.Join(global.CONF.System.BaseDir, "1panel/tmp/docker_logs") + list2 := loadTreeWithAllFile(true, path2, "docker_log", path2, fileOp) + size2, _ := fileOp.GetDirSize(path2) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "docker_log", Size: uint64(size2), Children: list2, Type: "docker_log", IsRecommend: true}) + + path3 := path.Join(global.CONF.System.BaseDir, "1panel/task") + list3 := loadTreeWithAllFile(false, path3, "task_log", path3, fileOp) + size3, _ := fileOp.GetDirSize(path3) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "task_log", Size: uint64(size3), Children: list3, Type: "task_log"}) + return treeData +} + +func loadTreeWithDir(isCheck bool, treeType, pathItem string, fileOp fileUtils.FileOp) []dto.CleanTree { + var lists []dto.CleanTree + files, err := os.ReadDir(pathItem) + if err != nil { + return lists + } + sort.Slice(files, func(i, j int) bool { + return files[i].Name() > files[j].Name() + }) + for _, file := range files { + if (treeType == "old_upgrade" || treeType == "upgrade") && !strings.HasPrefix(file.Name(), "upgrade_2023") { + continue + } + if file.IsDir() { + size, err := fileOp.GetDirSize(path.Join(pathItem, file.Name())) + if err != nil { + continue + } + item := dto.CleanTree{ + ID: uuid.NewString(), + Label: file.Name(), + Type: treeType, + Size: uint64(size), + Name: strings.TrimPrefix(file.Name(), "/"), + IsCheck: isCheck, + IsRecommend: isCheck, + } + if treeType == "upgrade" && len(lists) == 0 { + item.IsCheck = false + item.IsRecommend = false + } + lists = append(lists, item) + } + } + return lists +} + +func loadTreeWithAllFile(isCheck bool, originalPath, treeType, pathItem string, fileOp fileUtils.FileOp) []dto.CleanTree { + var lists []dto.CleanTree + + files, err := os.ReadDir(pathItem) + if err != nil { + return lists + } + for _, file := range files { + if treeType == "system_log" && file.Name() == "1Panel.log" { + continue + } + size := uint64(0) + name := strings.TrimPrefix(path.Join(pathItem, file.Name()), originalPath+"/") + if file.IsDir() { + sizeItem, err := fileOp.GetDirSize(path.Join(pathItem, file.Name())) + if err != nil { + continue + } + size = uint64(sizeItem) + } else { + fileInfo, err := file.Info() + if err != nil { + continue + } + size = uint64(fileInfo.Size()) + } + item := dto.CleanTree{ + ID: uuid.NewString(), + Label: file.Name(), + Type: treeType, + Size: uint64(size), + Name: name, + IsCheck: isCheck, + IsRecommend: isCheck, + } + if file.IsDir() { + item.Children = loadTreeWithAllFile(isCheck, originalPath, treeType, path.Join(pathItem, file.Name()), fileOp) + } + lists = append(lists, item) + } + return lists +} + +func dropFileOrDir(itemPath string) { + global.LOG.Debugf("drop file %s", itemPath) + if err := os.RemoveAll(itemPath); err != nil { + global.LOG.Errorf("drop file %s failed, err %v", itemPath, err) + } +} diff --git a/backend/init/migration/migrations/v_1_7.go b/backend/init/migration/migrations/v_1_7.go index 3d824435d..223638397 100644 --- a/backend/init/migration/migrations/v_1_7.go +++ b/backend/init/migration/migrations/v_1_7.go @@ -7,11 +7,20 @@ import ( ) var AddDefaultNetwork = &gormigrate.Migration{ - ID: "20230918-add-default-network", + ID: "20230928-add-default-network", Migrate: func(tx *gorm.DB) error { if err := tx.Create(&model.Setting{Key: "DefaultNetwork", Value: ""}).Error; err != nil { return err } + if err := tx.Create(&model.Setting{Key: "LastCleanTime", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "LastCleanSize", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "LastCleanData", Value: ""}).Error; err != nil { + return err + } return nil }, } diff --git a/backend/router/ro_setting.go b/backend/router/ro_setting.go index 31a045cc7..5c73afbe2 100644 --- a/backend/router/ro_setting.go +++ b/backend/router/ro_setting.go @@ -32,6 +32,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) { settingRouter.POST("/monitor/clean", baseApi.CleanMonitor) settingRouter.GET("/mfa/:interval", baseApi.GetMFA) settingRouter.POST("/mfa/bind", baseApi.MFABind) + settingRouter.POST("/scan", baseApi.ScanSystem) + settingRouter.POST("/clean", baseApi.SystemClean) settingRouter.POST("/snapshot", baseApi.CreateSnapshot) settingRouter.POST("/snapshot/status", baseApi.LoadSnapShotStatus) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index fb3d7142b..392c19803 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -1,5 +1,5 @@ -// Package docs GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag +// Code generated by swaggo/swag. DO NOT EDIT. + package docs import "github.com/swaggo/swag" @@ -8565,6 +8565,49 @@ const docTemplate = `{ } } }, + "/settings/clean": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "清理系统垃圾文件", + "consumes": [ + "application/json" + ], + "tags": [ + "System Setting" + ], + "summary": "System clean", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.Clean" + } + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFuntions": [], + "bodyKeys": [], + "formatEN": "Clean system junk files", + "formatZH": "清理系统垃圾文件", + "paramKeys": [] + } + } + }, "/settings/expired/handle": { "post": { "security": [ @@ -8784,6 +8827,32 @@ const docTemplate = `{ } } }, + "/settings/scan": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "扫描系统垃圾文件", + "tags": [ + "System Setting" + ], + "summary": "Scan system", + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFuntions": [], + "bodyKeys": [], + "formatEN": "scan System Junk Files", + "formatZH": "扫描系统垃圾文件", + "paramKeys": [] + } + } + }, "/settings/search": { "post": { "security": [ @@ -12132,6 +12201,20 @@ const docTemplate = `{ } } }, + "dto.Clean": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "treeType": { + "type": "string" + } + } + }, "dto.CleanLog": { "type": "object", "required": [ @@ -14904,6 +14987,15 @@ const docTemplate = `{ "language": { "type": "string" }, + "lastCleanData": { + "type": "string" + }, + "lastCleanSize": { + "type": "string" + }, + "lastCleanTime": { + "type": "string" + }, "localTime": { "type": "string" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index d237e6faa..3b9bba6fa 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -8558,6 +8558,49 @@ } } }, + "/settings/clean": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "清理系统垃圾文件", + "consumes": [ + "application/json" + ], + "tags": [ + "System Setting" + ], + "summary": "System clean", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.Clean" + } + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFuntions": [], + "bodyKeys": [], + "formatEN": "Clean system junk files", + "formatZH": "清理系统垃圾文件", + "paramKeys": [] + } + } + }, "/settings/expired/handle": { "post": { "security": [ @@ -8777,6 +8820,32 @@ } } }, + "/settings/scan": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "扫描系统垃圾文件", + "tags": [ + "System Setting" + ], + "summary": "Scan system", + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFuntions": [], + "bodyKeys": [], + "formatEN": "scan System Junk Files", + "formatZH": "扫描系统垃圾文件", + "paramKeys": [] + } + } + }, "/settings/search": { "post": { "security": [ @@ -12125,6 +12194,20 @@ } } }, + "dto.Clean": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "treeType": { + "type": "string" + } + } + }, "dto.CleanLog": { "type": "object", "required": [ @@ -14897,6 +14980,15 @@ "language": { "type": "string" }, + "lastCleanData": { + "type": "string" + }, + "lastCleanSize": { + "type": "string" + }, + "lastCleanTime": { + "type": "string" + }, "localTime": { "type": "string" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index fe0534fb0..00bce06f4 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -170,6 +170,15 @@ definitions: required: - value type: object + dto.Clean: + properties: + name: + type: string + size: + type: integer + treeType: + type: string + type: object dto.CleanLog: properties: logType: @@ -2038,6 +2047,12 @@ definitions: type: string language: type: string + lastCleanData: + type: string + lastCleanSize: + type: string + lastCleanTime: + type: string localTime: type: string messageType: @@ -9542,6 +9557,34 @@ paths: summary: Load local backup dir tags: - System Setting + /settings/clean: + post: + consumes: + - application/json + description: 清理系统垃圾文件 + parameters: + - description: request + in: body + name: request + required: true + schema: + items: + $ref: '#/definitions/dto.Clean' + type: array + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: System clean + tags: + - System Setting + x-panel-log: + BeforeFuntions: [] + bodyKeys: [] + formatEN: Clean system junk files + formatZH: 清理系统垃圾文件 + paramKeys: [] /settings/expired/handle: post: consumes: @@ -9683,6 +9726,23 @@ paths: formatEN: update system port => [serverPort] formatZH: 修改系统端口 => [serverPort] paramKeys: [] + /settings/scan: + post: + description: 扫描系统垃圾文件 + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Scan system + tags: + - System Setting + x-panel-log: + BeforeFuntions: [] + bodyKeys: [] + formatEN: scan System Junk Files + formatZH: 扫描系统垃圾文件 + paramKeys: [] /settings/search: post: description: 加载系统配置信息 diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index 873a73935..7e7634a92 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -17,6 +17,9 @@ export namespace Setting { theme: string; language: string; defaultNetwork: string; + lastCleanTime: string; + lastCleanSize: string; + lastCleanData: string; serverPort: number; ssl: string; @@ -76,6 +79,24 @@ export namespace Setting { code: string; interval: string; } + + export interface CleanData { + systemClean: Array; + uploadClean: Array; + downloadClean: Array; + systemLogClean: Array; + } + export interface CleanTree { + id: string; + label: string; + children: Array; + type: string; + name: string; + size: number; + isCheck: boolean; + isRecommend: boolean; + } + export interface SnapshotCreate { id: number; from: string; diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index 5e115216c..6d1a4bae5 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -39,6 +39,13 @@ export const handleExpired = (param: Setting.PasswordUpdate) => { return http.post(`/settings/expired/handle`, param); }; +export const scanSystem = () => { + return http.post(`/settings/scan`, {}); +}; +export const cleanSystem = (param: any) => { + return http.post(`/settings/clean`, param); +}; + export const loadTimeZone = () => { return http.get>(`/settings/time/option`); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index d6c647b81..2d46ab682 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -89,7 +89,6 @@ const message = { loadingText: { Upgrading: 'System upgrade, please wait...', Restarting: 'System restart, please wait...', - Snapshoting: 'Making snapshots, please wait...', Recovering: 'Recovering from snapshot, please wait...', Rollbacking: 'Rollbacking from snapshot, please wait...', }, @@ -1203,6 +1202,58 @@ const message = { doc2: 'Document', currentVersion: 'Version', }, + clean: { + scan: 'Start Scanning', + clean: 'Clean Now', + reScan: 'Rescan', + cleanHelper: + 'After checking the files and directories for cleaning, it cannot be undone (system cache cleaning requires a service restart). Do you want to continue?', + statusSuggest: '( Recommended Cleaning )', + statusClean: '( Very Clean )', + statusEmpty: 'Very clean, no cleaning needed!', + statusWarning: '( Proceed with Caution )', + lastCleanTime: 'Last Cleaned: {0}', + lastCleanHelper: 'Files and directories cleaned: {0}, Total cleaned: {1}', + cleanSuccessful: 'Cleaning Successful!', + currentCleanHelper: 'Files and directories cleaned in this session: {0}, Total cleaned: {1}', + suggest: '(Recommended)', + totalScan: 'Total junk files to be cleaned: ', + selectScan: 'Total selected junk files: ', + + system: 'System Junk', + systemHelper: + 'Scan system junk (specifically includes temporary files generated during snapshots, upgrades, and obsolete file contents during version iterations)', + panelOriginal: 'System snapshot recovery backup files', + upgrade: 'System upgrade backup files', + cache: 'System cache files', + snapshot: 'System snapshot temporary files', + snapshotTmp: 'System snapshot upload temporary files', + snapshotLocal: 'System snapshot creation temporary files', + unused: 'Unused system directories', + oldUpgrade: 'Unused pre-upgrade backup directories', + oldOriginal: 'Unused pre-snapshot recovery backup directories', + oldAppsBak: 'Unused application backup directories', + + upload: 'Temporary upload files', + uploadHelper: + 'Scan temporary upload directory (specifically includes temporary files uploaded from the system backup list)', + download: 'Temporary Download Files', + downloadHelper: + 'Scan temporary download directory (specifically includes temporary files downloaded from third-party backup accounts by the system)', + website: 'Website', + app: 'Application', + database: 'Database', + directory: 'Directory', + + systemLog: 'System Log Files', + systemLogHelper: + 'Scan system junk (specifically includes system log information, container build or image pull log information, and log files generated in scheduled tasks)', + dockerLog: 'Container operation log files', + taskLog: 'Scheduled task execution log files', + shell: 'Shell script scheduled tasks', + containerShell: 'Container internal Shell script scheduled tasks', + curl: 'CURL scheduled tasks', + }, app: { app: 'Application', installName: 'Name', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index e946761ae..2f53179b4 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -89,7 +89,6 @@ const message = { loadingText: { Upgrading: '系統升級中,請稍候...', Restarting: '系統重啟中,請稍候...', - Snapshoting: '製作快照中,請稍候...', Recovering: '從快照恢復中,請稍候...', Rollbacking: '快照回滾中,請稍候...', }, @@ -1144,6 +1143,54 @@ const message = { doc2: '文檔', currentVersion: '當前運行版本:', }, + clean: { + scan: '開始掃描', + clean: '立即清理', + reScan: '重新掃描', + cleanHelper: '已勾選文件及目錄清理後無法回滾(系統緩存文件清理需要重啟服務),是否繼續?', + statusSuggest: '( 建議清理 )', + statusClean: '( 很幹凈 )', + statusEmpty: '非常幹凈,無需清理!', + statusWarning: '( 謹慎操作 )', + lastCleanTime: '上次清理時間: {0}', + lastCleanHelper: '清理文件及目錄:{0} 個, 總計清理:{1}', + cleanSuccessful: '清理成功!', + currentCleanHelper: '本次清理文件及目錄:{0} 個, 總計清理:{1}', + suggest: '( 建議清理 )', + totalScan: '待清理垃圾文件共計: ', + selectScan: '已選中垃圾文件共計: ', + + system: '系統垃圾', + systemHelper: '掃描系統垃圾(具體包括:快照、升級等過程中產生的臨時文件以及版本叠代過程中廢棄的文件內容)', + panelOriginal: '系統快照恢復前備份文件', + upgrade: '系統升級備份文件', + cache: '系統緩存文件', + snapshot: '系統快照臨時文件', + snapshotTmp: '系統快照上傳臨時文件', + snapshotLocal: '系統快照製作臨時文件', + unused: '系統廢棄目錄', + oldUpgrade: '廢棄升級前備份目錄', + oldOriginal: '廢棄快照恢復前備份目錄', + oldAppsBak: '廢棄應用備份目錄', + + upload: '臨時上傳文件', + uploadHelper: '掃描臨時上傳目錄(具體包括:系統上傳備份列表中上傳的臨時文件)', + download: '臨時下載文件', + downloadHelper: '掃描臨時下載目錄(具體包括:系統從第三方備份賬號下載的臨時文件)', + website: '網站', + app: '應用', + database: '數據庫', + directory: '應用', + + systemLog: '系統日誌文件', + systemLogHelper: + '掃描系統垃圾(具體包括:系統日誌信息、容器構建或鏡像拉取等日誌信息以及計劃任務中產生的日誌文件)', + dockerLog: '容器操作日誌文件', + taskLog: '計劃任務執行日誌文件', + shell: 'Shell 腳本計劃任務', + containerShell: '容器內執行 Shell 腳本計劃任務', + curl: 'CURL 計劃任務', + }, app: { app: '應用', installName: '安裝名稱', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 7d7a7c8fb..53199a085 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -89,7 +89,6 @@ const message = { loadingText: { Upgrading: '系统升级中,请稍候...', Restarting: '系统重启中,请稍候...', - Snapshoting: '制作快照中,请稍候...', Recovering: '从快照恢复中,请稍候...', Rollbacking: '快照回滚中,请稍候...', }, @@ -996,6 +995,7 @@ const message = { newPassword: '新密码', retryPassword: '确认密码', duplicatePassword: '新密码不能与原始密码一致,请重新输入!', + diskClean: '磁盘清理', thirdParty: '第三方账号', createBackupAccount: '添加 {0}', @@ -1144,6 +1144,53 @@ const message = { doc2: '文档', currentVersion: '当前运行版本:', }, + clean: { + scan: '开始扫描', + clean: '立即清理', + reScan: '重新扫描', + cleanHelper: '已勾选文件及目录清理后无法回滚(系统缓存文件清理需要重启服务),是否继续?', + statusSuggest: '( 建议清理 )', + statusClean: '( 很干净 )', + statusEmpty: '非常干净,无需清理!', + statusWarning: '( 谨慎操作 )', + lastCleanTime: '上次清理时间: {0}', + lastCleanHelper: '清理文件及目录:{0} 个, 总计清理:{1}', + cleanSuccessful: '清理成功!', + currentCleanHelper: '本次清理文件及目录:{0} 个, 总计清理:{1}', + totalScan: '待清理垃圾文件共计: ', + selectScan: '已选中垃圾文件共计: ', + + system: '系统垃圾', + systemHelper: '扫描系统垃圾(具体包括:快照、升级等过程中产生的临时文件以及版本迭代过程中废弃的文件内容)', + panelOriginal: '系统快照恢复前备份文件', + upgrade: '系统升级备份文件', + cache: '系统缓存文件', + snapshot: '系统快照临时文件', + snapshotTmp: '系统快照上传临时文件', + snapshotLocal: '系统快照制作临时文件', + unused: '系统废弃目录', + oldUpgrade: '废弃升级前备份目录', + oldOriginal: '废弃快照恢复前备份目录', + oldAppsBak: '废弃应用备份目录', + + upload: '临时上传文件', + uploadHelper: '扫描临时上传目录(具体包括:系统上传备份列表中上传的临时文件)', + download: '临时下载文件', + downloadHelper: '扫描临时下载目录(具体包括:系统从第三方备份账号下载的临时文件)', + website: '网站', + app: '应用', + database: '数据库', + directory: '应用', + + systemLog: '系统日志文件', + systemLogHelper: + '扫描系统垃圾(具体包括:系统日志信息、容器构建或镜像拉取等日志信息以及计划任务中产生的日志文件)', + dockerLog: '容器操作日志文件', + taskLog: '计划任务执行日志文件', + shell: 'Shell 脚本计划任务', + containerShell: '容器内执行 Shell 脚本计划任务', + curl: 'CURL 计划任务', + }, app: { app: '应用', installName: '安装名称', diff --git a/frontend/src/views/setting/panel/clean/index.vue b/frontend/src/views/setting/panel/clean/index.vue new file mode 100644 index 000000000..b2023675a --- /dev/null +++ b/frontend/src/views/setting/panel/clean/index.vue @@ -0,0 +1,534 @@ + + + + diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue index 2e5ce32b3..9aabcc5e1 100644 --- a/frontend/src/views/setting/panel/index.vue +++ b/frontend/src/views/setting/panel/index.vue @@ -117,6 +117,15 @@ + + + + + @@ -130,7 +139,8 @@ - + + @@ -148,7 +158,8 @@ import Timeout from '@/views/setting/panel/timeout/index.vue'; import PanelName from '@/views/setting/panel/name/index.vue'; import SystemIP from '@/views/setting/panel/systemip/index.vue'; import TimeZone from '@/views/setting/panel/timezone/index.vue'; -import Netwrok from '@/views/setting/panel/default-network/index.vue'; +import Network from '@/views/setting/panel/default-network/index.vue'; +import Clean from '@/views/setting/panel/clean/index.vue'; import Ntp from '@/views/setting/panel/ntp/index.vue'; const loading = ref(false); @@ -172,6 +183,10 @@ const form = reactive({ complexityVerification: '', defaultNetwork: '', defaultNetworkVal: '', + + lastCleanTime: '', + lastCleanSize: '', + lastCleanData: '', }); const show = ref(); @@ -183,7 +198,8 @@ const systemIPRef = ref(); const timeoutRef = ref(); const ntpRef = ref(); const timezoneRef = ref(); -const netwrokRef = ref(); +const networkRef = ref(); +const cleanRef = ref(); const unset = ref(i18n.t('setting.unSetting')); const search = async () => { @@ -201,6 +217,10 @@ const search = async () => { form.complexityVerification = res.data.complexityVerification; form.defaultNetwork = res.data.defaultNetwork; form.defaultNetworkVal = res.data.defaultNetwork === 'all' ? i18n.t('commons.table.all') : res.data.defaultNetwork; + + form.lastCleanTime = res.data.lastCleanTime; + form.lastCleanSize = res.data.lastCleanSize; + form.lastCleanData = res.data.lastCleanData; }; const onChangePassword = () => { @@ -225,7 +245,14 @@ const onChangeNtp = () => { ntpRef.value.acceptParams({ localTime: form.localTime, ntpSite: form.ntpSite }); }; const onChangeNetwork = () => { - netwrokRef.value.acceptParams({ defaultNetwork: form.defaultNetwork }); + networkRef.value.acceptParams({ defaultNetwork: form.defaultNetwork }); +}; +const onClean = () => { + cleanRef.value.acceptParams({ + lastCleanTime: form.lastCleanTime, + lastCleanSize: form.lastCleanSize, + lastCleanData: form.lastCleanData, + }); }; const onSave = async (key: string, val: any) => {