diff --git a/backend/app/api/v1/recycle_bin.go b/backend/app/api/v1/recycle_bin.go index 7722a1e55c02..0c7794e01088 100644 --- a/backend/app/api/v1/recycle_bin.go +++ b/backend/app/api/v1/recycle_bin.go @@ -68,3 +68,19 @@ func (b *BaseApi) ClearRecycleBinFile(c *gin.Context) { } helper.SuccessWithOutData(c) } + +// @Tags File +// @Summary Get RecycleBin status +// @Description 获取回收站状态 +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Router /files/recycle/status [get] +func (b *BaseApi) GetRecycleStatus(c *gin.Context) { + settingInfo, err := settingService.GetSettingInfo() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, settingInfo.FileRecycleBin) +} diff --git a/backend/app/dto/setting.go b/backend/app/dto/setting.go index 3aef429394f9..866734261ad7 100644 --- a/backend/app/dto/setting.go +++ b/backend/app/dto/setting.go @@ -50,6 +50,8 @@ type SettingInfo struct { AppStoreVersion string `json:"appStoreVersion"` AppStoreLastModified string `json:"appStoreLastModified"` AppStoreSyncStatus string `json:"appStoreSyncStatus"` + + FileRecycleBin string `json:"fileRecycleBin"` } type SettingUpdate struct { diff --git a/backend/app/service/file.go b/backend/app/service/file.go index b9e77d058422..7545588b4558 100644 --- a/backend/app/service/file.go +++ b/backend/app/service/file.go @@ -136,6 +136,10 @@ func (f *FileService) Create(op request.FileCreate) error { func (f *FileService) Delete(op request.FileDelete) error { fo := files.NewFileOp() + recycleBinStatus, _ := settingRepo.Get(settingRepo.WithByKey("FileRecycleBin")) + if recycleBinStatus.Value == "disable" { + op.ForceDelete = true + } if op.ForceDelete { if op.IsDir { return fo.DeleteDir(op.Path) diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 7e7940ed7093..7ba892d5ea8c 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -59,6 +59,7 @@ func Init() { migrations.AddDockerSockPath, migrations.AddDatabaseSSL, migrations.AddDefaultCA, + migrations.AddSettingRecycleBin, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/v_1_9.go b/backend/init/migration/migrations/v_1_9.go index 53c898ad308d..f6393c14eff5 100644 --- a/backend/init/migration/migrations/v_1_9.go +++ b/backend/init/migration/migrations/v_1_9.go @@ -77,3 +77,13 @@ var AddDefaultCA = &gormigrate.Migration{ return nil }, } + +var AddSettingRecycleBin = &gormigrate.Migration{ + ID: "20231129-add-setting-recycle-bin", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "FileRecycleBin", Value: "enable"}).Error; err != nil { + return err + } + return nil + }, +} diff --git a/backend/router/ro_file.go b/backend/router/ro_file.go index 6fe469f1874e..49aaee5becc9 100644 --- a/backend/router/ro_file.go +++ b/backend/router/ro_file.go @@ -43,6 +43,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) { fileRouter.POST("/recycle/search", baseApi.SearchRecycleBinFile) fileRouter.POST("/recycle/reduce", baseApi.ReduceRecycleBinFile) fileRouter.POST("/recycle/clear", baseApi.ClearRecycleBinFile) + fileRouter.GET("/recycle/status", baseApi.GetRecycleStatus) fileRouter.POST("/favorite/search", baseApi.SearchFavorite) fileRouter.POST("/favorite", baseApi.CreateFavorite) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index fb18463a5784..fd799504e503 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -1,5 +1,5 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs GENERATED BY SWAG; DO NOT EDIT +// This file was generated by swaggo/swag package docs import "github.com/swaggo/swag" @@ -5919,6 +5919,28 @@ const docTemplate = `{ } } }, + "/files/recycle/status": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取回收站状态", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Get RecycleBin status", + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/rename": { "post": { "security": [ @@ -13506,6 +13528,9 @@ const docTemplate = `{ "names" ], "properties": { + "force": { + "type": "boolean" + }, "names": { "type": "array", "items": { @@ -16822,6 +16847,9 @@ const docTemplate = `{ "expirationTime": { "type": "string" }, + "fileRecycleBin": { + "type": "string" + }, "ipv6": { "type": "string" }, @@ -19035,6 +19063,9 @@ const docTemplate = `{ "autoRenew": { "type": "boolean" }, + "description": { + "type": "string" + }, "dir": { "type": "string" }, @@ -19652,6 +19683,9 @@ const docTemplate = `{ "certificatePath": { "type": "string" }, + "description": { + "type": "string" + }, "privateKey": { "type": "string" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 07e93ee943e5..7f63c75f73c1 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -5912,6 +5912,28 @@ } } }, + "/files/recycle/status": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取回收站状态", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Get RecycleBin status", + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/rename": { "post": { "security": [ @@ -13499,6 +13521,9 @@ "names" ], "properties": { + "force": { + "type": "boolean" + }, "names": { "type": "array", "items": { @@ -16815,6 +16840,9 @@ "expirationTime": { "type": "string" }, + "fileRecycleBin": { + "type": "string" + }, "ipv6": { "type": "string" }, @@ -19028,6 +19056,9 @@ "autoRenew": { "type": "boolean" }, + "description": { + "type": "string" + }, "dir": { "type": "string" }, @@ -19645,6 +19676,9 @@ "certificatePath": { "type": "string" }, + "description": { + "type": "string" + }, "privateKey": { "type": "string" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 6214cbe4b3d6..31f0f110f097 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -95,6 +95,8 @@ definitions: type: object dto.BatchDelete: properties: + force: + type: boolean names: items: type: string @@ -2334,6 +2336,8 @@ definitions: type: string expirationTime: type: string + fileRecycleBin: + type: string ipv6: type: string language: @@ -3808,6 +3812,8 @@ definitions: properties: autoRenew: type: boolean + description: + type: string dir: type: string domains: @@ -4227,6 +4233,8 @@ definitions: type: string certificatePath: type: string + description: + type: string privateKey: type: string privateKeyPath: @@ -8522,6 +8530,19 @@ paths: summary: List RecycleBin files tags: - File + /files/recycle/status: + get: + consumes: + - application/json + description: 获取回收站状态 + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Get RecycleBin status + tags: + - File /files/rename: post: consumes: diff --git a/frontend/src/api/modules/files.ts b/frontend/src/api/modules/files.ts index d3c5083cd4c3..30610d5cb134 100644 --- a/frontend/src/api/modules/files.ts +++ b/frontend/src/api/modules/files.ts @@ -120,3 +120,7 @@ export const RemoveFavorite = (id: number) => { export const BatchChangeRole = (params: File.FileRole) => { return http.post('files/batch/role', params); }; + +export const GetRecycleStatus = () => { + return http.get('files/recycle/status'); +}; diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts index 9b2cafa08954..675c30b9e7fb 100644 --- a/frontend/src/global/mimetype.ts +++ b/frontend/src/global/mimetype.ts @@ -88,7 +88,7 @@ export const Rewrites = [ 'typecho', 'typecho2', 'thinkphp', - 'yii2', + 'yii2', 'laravel5', 'discuz', 'discuzx', diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 3d424eee5fa1..1e1c875edfd7 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1087,6 +1087,8 @@ const message = { deleteRecycleHelper: 'Are you sure you want to permanently delete the following files?', typeErrOrEmpty: '[{0}] file type is wrong or empty folder', dropHelper: 'Drag the files you want to upload here', + fileRecycleBin: 'File Recycle Bin', + fileRecycleBinMsg: '{0} recycle bin', }, ssh: { autoStart: 'Auto Start', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index f510ec06312a..b8e2f3cfe3e3 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1036,6 +1036,8 @@ const message = { deleteRecycleHelper: '確定永久刪除以下文件?', typeErrOrEmpty: '【{0}】 檔案類型錯誤或為空資料夾', dropHelper: '將需要上傳的文件拖曳到此處', + fileRecycleBin: '檔案回收站', + fileRecycleBinMsg: '已{0}回收站', }, ssh: { autoStart: '開機自啟', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index bd67dad2d44f..ca2fb8bd5d3c 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1037,6 +1037,8 @@ const message = { deleteRecycleHelper: '确定永久删除以下文件?', typeErrOrEmpty: '【{0}】 文件类型错误或为空文件夹', dropHelper: '将需要上传的文件拖曳到此处', + fileRecycleBin: '文件回收站', + fileRecycleBinMsg: '已{0}回收站', }, ssh: { autoStart: '开机自启', diff --git a/frontend/src/views/host/file-management/delete/index.vue b/frontend/src/views/host/file-management/delete/index.vue index fafd2dd59e71..60318e5b13e0 100644 --- a/frontend/src/views/host/file-management/delete/index.vue +++ b/frontend/src/views/host/file-management/delete/index.vue @@ -8,7 +8,7 @@ {{ $t('file.deleteHelper') }} -
+
{{ $t('file.forceDeleteHelper') }}
@@ -49,7 +49,7 @@ import i18n from '@/lang'; import { ref } from 'vue'; import { File } from '@/api/interface/file'; import { getIcon } from '@/utils/util'; -import { DeleteFile } from '@/api/modules/files'; +import { DeleteFile, GetRecycleStatus } from '@/api/modules/files'; import { MsgSuccess } from '@/utils/message'; const open = ref(false); @@ -57,13 +57,25 @@ const files = ref(); const loading = ref(false); const em = defineEmits(['close']); const forceDelete = ref(false); +const recycleStatus = ref('enable'); const acceptParams = (props: File.File[]) => { + getStatus(); files.value = props; open.value = true; forceDelete.value = false; }; +const getStatus = async () => { + try { + const res = await GetRecycleStatus(); + recycleStatus.value = res.data; + if (recycleStatus.value === 'disable') { + forceDelete.value = true; + } + } catch (error) {} +}; + const onConfirm = () => { const pros = []; for (const s of files.value) { @@ -98,8 +110,9 @@ defineExpose({ } .file-list { - height: 400px; + max-height: 400px; overflow-y: auto; + margin-top: 15px; } .delete-warn { diff --git a/frontend/src/views/host/file-management/recycle-bin/index.vue b/frontend/src/views/host/file-management/recycle-bin/index.vue index eda9b6c12597..907c95f85279 100644 --- a/frontend/src/views/host/file-management/recycle-bin/index.vue +++ b/frontend/src/views/host/file-management/recycle-bin/index.vue @@ -3,12 +3,17 @@ - - {{ $t('file.clearRecycleBin') }} - - - {{ $t('commons.button.delete') }} - +
+ + {{ $t('file.clearRecycleBin') }} + + + {{ $t('commons.button.delete') }} + + + + +