diff --git a/backend/app/api/v1/file.go b/backend/app/api/v1/file.go index b33defdea..bae992ca1 100644 --- a/backend/app/api/v1/file.go +++ b/backend/app/api/v1/file.go @@ -707,3 +707,23 @@ func (b *BaseApi) ReadFileByLine(c *gin.Context) { res.Content = strings.Join(lines, "\n") helper.SuccessWithData(c, res) } + +// @Tags File +// @Summary Batch change file mode and owner +// @Description 批量修改文件权限和用户/组 +// @Accept json +// @Param request body request.FileRoleReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /files/batch/role [post] +// @x-panel-log {"bodyKeys":["paths","mode","user","group"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"批量修改文件权限和用户/组 [paths] => [mode]/[user]/[group]","formatEN":"Batch change file mode and owner [paths] => [mode]/[user]/[group]"} +func (b *BaseApi) BatchChangeModeAndOwner(c *gin.Context) { + var req request.FileRoleReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := fileService.BatchChangeModeAndOwner(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + } + helper.SuccessWithOutData(c) +} diff --git a/backend/app/dto/request/file.go b/backend/app/dto/request/file.go index 02e7b4056..6f6df07ce 100644 --- a/backend/app/dto/request/file.go +++ b/backend/app/dto/request/file.go @@ -29,6 +29,14 @@ type FileCreate struct { Sub bool `json:"sub"` } +type FileRoleReq struct { + Paths []string `json:"paths" validate:"required"` + Mode int64 `json:"mode" validate:"required"` + User string `json:"user" validate:"required"` + Group string `json:"group" validate:"required"` + Sub bool `json:"sub"` +} + type FileDelete struct { Path string `json:"path" validate:"required"` IsDir bool `json:"isDir"` @@ -114,7 +122,7 @@ type FileReadByLineReq struct { Page int `json:"page" validate:"required"` PageSize int `json:"pageSize" validate:"required"` } - + type FileExistReq struct { Name string `json:"name" validate:"required"` Dir string `json:"dir" validate:"required"` diff --git a/backend/app/service/file.go b/backend/app/service/file.go index f7f9f9caf..37b69093a 100644 --- a/backend/app/service/file.go +++ b/backend/app/service/file.go @@ -29,7 +29,6 @@ type IFileService interface { Create(op request.FileCreate) error Delete(op request.FileDelete) error BatchDelete(op request.FileBatchDelete) error - ChangeMode(op request.FileCreate) error Compress(c request.FileCompress) error DeCompress(c request.FileDeCompress) error GetContent(op request.FileContentReq) (response.FileInfo, error) @@ -40,6 +39,8 @@ type IFileService interface { Wget(w request.FileWget) (string, error) MvFile(m request.FileMove) error ChangeOwner(req request.FileRoleUpdate) error + ChangeMode(op request.FileCreate) error + BatchChangeModeAndOwner(op request.FileRoleReq) error } func NewIFileService() IFileService { @@ -166,11 +167,24 @@ func (f *FileService) BatchDelete(op request.FileBatchDelete) error { func (f *FileService) ChangeMode(op request.FileCreate) error { fo := files.NewFileOp() - if op.Sub { - return fo.ChmodR(op.Path, op.Mode) - } else { - return fo.Chmod(op.Path, fs.FileMode(op.Mode)) + return fo.ChmodR(op.Path, op.Mode, op.Sub) +} + +func (f *FileService) BatchChangeModeAndOwner(op request.FileRoleReq) error { + fo := files.NewFileOp() + for _, path := range op.Paths { + if !fo.Stat(path) { + return buserr.New(constant.ErrPathNotFound) + } + if err := fo.ChownR(path, op.User, op.Group, op.Sub); err != nil { + return err + } + if err := fo.ChmodR(path, op.Mode, op.Sub); err != nil { + return err + } } + return nil + } func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error { diff --git a/backend/router/ro_file.go b/backend/router/ro_file.go index 0daa98dab..6fe469f18 100644 --- a/backend/router/ro_file.go +++ b/backend/router/ro_file.go @@ -38,6 +38,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) { fileRouter.GET("/ws", baseApi.Ws) fileRouter.GET("/keys", baseApi.Keys) fileRouter.POST("/read", baseApi.ReadFileByLine) + fileRouter.POST("/batch/role", baseApi.BatchChangeModeAndOwner) fileRouter.POST("/recycle/search", baseApi.SearchRecycleBinFile) fileRouter.POST("/recycle/reduce", baseApi.ReduceRecycleBinFile) diff --git a/backend/utils/files/file_op.go b/backend/utils/files/file_op.go index 707cf2c19..ed43b8b99 100644 --- a/backend/utils/files/file_op.go +++ b/backend/utils/files/file_op.go @@ -149,8 +149,11 @@ func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error { return nil } -func (f FileOp) ChmodR(dst string, mode int64) error { - cmdStr := fmt.Sprintf(`chmod -R %v "%s"`, fmt.Sprintf("%04o", mode), dst) +func (f FileOp) ChmodR(dst string, mode int64, sub bool) error { + cmdStr := fmt.Sprintf(`chmod %v "%s"`, fmt.Sprintf("%04o", mode), dst) + if sub { + cmdStr = fmt.Sprintf(`chmod -R %v "%s"`, fmt.Sprintf("%04o", mode), dst) + } if cmd.HasNoPasswordSudo() { cmdStr = fmt.Sprintf("sudo %s", cmdStr) } diff --git a/frontend/src/api/interface/file.ts b/frontend/src/api/interface/file.ts index de3760ba9..88ce43672 100644 --- a/frontend/src/api/interface/file.ts +++ b/frontend/src/api/interface/file.ts @@ -176,4 +176,12 @@ export namespace File { isTxt: boolean; name: string; } + + export interface FileRole { + paths: string[]; + mode: number; + user: string; + group: string; + sub: boolean; + } } diff --git a/frontend/src/api/modules/files.ts b/frontend/src/api/modules/files.ts index aa0658d56..101d2a928 100644 --- a/frontend/src/api/modules/files.ts +++ b/frontend/src/api/modules/files.ts @@ -116,3 +116,7 @@ export const ReadByLine = (req: File.FileReadByLine) => { export const RemoveFavorite = (id: number) => { return http.post('files/favorite/del', { id: id }); }; + +export const BatchChangeRole = (params: File.FileRole) => { + return http.post('files/batch/role', params); +}; diff --git a/frontend/src/components/file-role/index.vue b/frontend/src/components/file-role/index.vue index b153973b7..e89995b1e 100644 --- a/frontend/src/components/file-role/index.vue +++ b/frontend/src/components/file-role/index.vue @@ -16,7 +16,7 @@ - + diff --git a/frontend/src/views/host/file-management/batch-role/index.vue b/frontend/src/views/host/file-management/batch-role/index.vue new file mode 100644 index 000000000..061c267d1 --- /dev/null +++ b/frontend/src/views/host/file-management/batch-role/index.vue @@ -0,0 +1,109 @@ + + + diff --git a/frontend/src/views/host/file-management/index.vue b/frontend/src/views/host/file-management/index.vue index a701faf15..07dced10b 100644 --- a/frontend/src/views/host/file-management/index.vue +++ b/frontend/src/views/host/file-management/index.vue @@ -80,6 +80,9 @@ {{ $t('file.compress') }} + + {{ $t('file.role') }} + {{ $t('commons.button.delete') }} @@ -291,6 +294,7 @@ + @@ -333,6 +337,7 @@ import Process from './process/index.vue'; import Detail from './detail/index.vue'; import RecycleBin from './recycle-bin/index.vue'; import Favorite from './favorite/index.vue'; +import BatchRole from './batch-role/index.vue'; const globalStore = GlobalStore(); @@ -394,6 +399,7 @@ const recycleBinRef = ref(); const favoriteRef = ref(); const hoveredRowIndex = ref(-1); const favorites = ref([]); +const batchRoleRef = ref(); // editablePath const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchable(paths); @@ -655,6 +661,10 @@ const openWget = () => { wgetRef.value.acceptParams(fileWget); }; +const openBatchRole = (items: File.File[]) => { + batchRoleRef.value.acceptParams({ files: items }); +}; + const closeWget = (submit: Boolean) => { search(); if (submit) {