From b38abf852dc7b5b8a696a143eb97097ac0250d44 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Fri, 20 Oct 2023 04:56:46 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=87=E4=BB=B6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=94=B6=E8=97=8F=E5=A4=B9=E5=8A=9F=E8=83=BD=20(#2612)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs https://github.com/1Panel-dev/1Panel/issues/492 --- backend/app/api/v1/entry.go | 1 + backend/app/api/v1/favorite.go | 76 ++++ backend/app/api/v1/helper/helper.go | 10 +- backend/app/api/v1/recycle_bin.go | 14 +- backend/app/dto/request/favorite.go | 9 + backend/app/dto/response/favorite.go | 7 + backend/app/model/favorite.go | 10 + backend/app/repo/favorite.go | 66 ++++ backend/app/service/entry.go | 2 + backend/app/service/favorite.go | 83 +++++ backend/constant/errs.go | 1 + backend/i18n/lang/en.yaml | 1 + backend/i18n/lang/zh-Hant.yaml | 1 + backend/i18n/lang/zh.yaml | 1 + backend/init/migration/migrate.go | 1 + backend/init/migration/migrations/v_1_8.go | 17 + backend/router/ro_file.go | 5 + backend/utils/files/fileinfo.go | 18 +- cmd/server/docs/docs.go | 349 +++++++++++++----- cmd/server/docs/swagger.json | 349 +++++++++++++----- cmd/server/docs/swagger.yaml | 219 ++++++++--- frontend/src/api/interface/file.ts | 7 + frontend/src/api/modules/files.ts | 12 + frontend/src/lang/modules/en.ts | 6 +- frontend/src/lang/modules/tw.ts | 6 +- frontend/src/lang/modules/zh.ts | 4 + .../host/file-management/favorite/index.vue | 72 ++++ .../src/views/host/file-management/index.vue | 105 +++++- 28 files changed, 1194 insertions(+), 258 deletions(-) create mode 100644 backend/app/api/v1/favorite.go create mode 100644 backend/app/dto/request/favorite.go create mode 100644 backend/app/dto/response/favorite.go create mode 100644 backend/app/model/favorite.go create mode 100644 backend/app/repo/favorite.go create mode 100644 backend/app/service/favorite.go create mode 100644 backend/init/migration/migrations/v_1_8.go create mode 100644 frontend/src/views/host/file-management/favorite/index.vue diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index 4060d6645..cadec348b 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -55,4 +55,5 @@ var ( hostToolService = service.NewIHostToolService() recycleBinService = service.NewIRecycleBinService() + favoriteService = service.NewIFavoriteService() ) diff --git a/backend/app/api/v1/favorite.go b/backend/app/api/v1/favorite.go new file mode 100644 index 000000000..53253ee1c --- /dev/null +++ b/backend/app/api/v1/favorite.go @@ -0,0 +1,76 @@ +package v1 + +import ( + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/app/dto/request" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/gin-gonic/gin" +) + +// @Tags File +// @Summary List favorites +// @Description 获取收藏列表 +// @Accept json +// @Param request body dto.PageInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /files/favorite/search [post] +func (b *BaseApi) SearchFavorite(c *gin.Context) { + var req dto.PageInfo + if err := helper.CheckBind(req, c); err != nil { + return + } + total, list, err := favoriteService.Page(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: list, + }) +} + +// @Tags File +// @Summary Create favorite +// @Description 创建收藏 +// @Accept json +// @Param request body request.FavoriteCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /files/favorite [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"收藏文件/文件夹 [path]","formatEN":"收藏文件/文件夹 [path]"} +func (b *BaseApi) CreateFavorite(c *gin.Context) { + var req request.FavoriteCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + favorite, err := favoriteService.Create(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, favorite) +} + +// @Tags File +// @Summary Delete favorite +// @Description 删除收藏 +// @Accept json +// @Param request body request.FavoriteDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /files/favorite/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"favorites","output_column":"path","output_value":"path"}],"formatZH":"删除收藏 [path]","formatEN":"delete avorite [path]"} +func (b *BaseApi) DeleteFavorite(c *gin.Context) { + var req request.FavoriteDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := favoriteService.Delete(req.ID); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} diff --git a/backend/app/api/v1/helper/helper.go b/backend/app/api/v1/helper/helper.go index e76fddae5..69421a820 100644 --- a/backend/app/api/v1/helper/helper.go +++ b/backend/app/api/v1/helper/helper.go @@ -115,7 +115,7 @@ func GetTxAndContext() (tx *gorm.DB, ctx context.Context) { } func CheckBindAndValidate(req interface{}, c *gin.Context) error { - if err := c.ShouldBindJSON(&req); err != nil { + if err := c.ShouldBindJSON(req); err != nil { ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return err } @@ -125,3 +125,11 @@ func CheckBindAndValidate(req interface{}, c *gin.Context) error { } return nil } + +func CheckBind(req interface{}, c *gin.Context) error { + if err := c.ShouldBindJSON(&req); err != nil { + ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return err + } + return nil +} diff --git a/backend/app/api/v1/recycle_bin.go b/backend/app/api/v1/recycle_bin.go index f41eaeac2..7722a1e55 100644 --- a/backend/app/api/v1/recycle_bin.go +++ b/backend/app/api/v1/recycle_bin.go @@ -8,14 +8,14 @@ import ( "github.com/gin-gonic/gin" ) -// @Tags RecycleBin +// @Tags File // @Summary List RecycleBin files // @Description 获取回收站文件列表 // @Accept json // @Param request body dto.PageInfo true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /recycle/search [post] +// @Router /files/recycle/search [post] func (b *BaseApi) SearchRecycleBinFile(c *gin.Context) { var req dto.PageInfo if err := helper.CheckBindAndValidate(&req, c); err != nil { @@ -32,15 +32,15 @@ func (b *BaseApi) SearchRecycleBinFile(c *gin.Context) { }) } -// @Tags RecycleBin +// @Tags File // @Summary Reduce RecycleBin files // @Description 还原回收站文件 // @Accept json // @Param request body request.RecycleBinReduce true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /recycle/reduce [post] -// @x-panel-log {"bodyKeys":["name],"paramKeys":[],"BeforeFunctions":[],"formatZH":"还原回收站文件 [name]","formatEN":"Reduce RecycleBin file [name]"} +// @Router /files/recycle/reduce [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"还原回收站文件 [name]","formatEN":"Reduce RecycleBin file [name]"} func (b *BaseApi) ReduceRecycleBinFile(c *gin.Context) { var req request.RecycleBinReduce if err := helper.CheckBindAndValidate(&req, c); err != nil { @@ -53,13 +53,13 @@ func (b *BaseApi) ReduceRecycleBinFile(c *gin.Context) { helper.SuccessWithOutData(c) } -// @Tags RecycleBin +// @Tags File // @Summary Clear RecycleBin files // @Description 清空回收站文件 // @Accept json // @Success 200 // @Security ApiKeyAuth -// @Router /recycle/clear [post] +// @Router /files/recycle/clear [post] // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空回收站","formatEN":"清空回收站"} func (b *BaseApi) ClearRecycleBinFile(c *gin.Context) { if err := recycleBinService.Clear(); err != nil { diff --git a/backend/app/dto/request/favorite.go b/backend/app/dto/request/favorite.go new file mode 100644 index 000000000..70280929c --- /dev/null +++ b/backend/app/dto/request/favorite.go @@ -0,0 +1,9 @@ +package request + +type FavoriteCreate struct { + Path string `json:"path" validate:"required"` +} + +type FavoriteDelete struct { + ID uint `json:"id" validate:"required"` +} diff --git a/backend/app/dto/response/favorite.go b/backend/app/dto/response/favorite.go new file mode 100644 index 000000000..b00a6c475 --- /dev/null +++ b/backend/app/dto/response/favorite.go @@ -0,0 +1,7 @@ +package response + +import "github.com/1Panel-dev/1Panel/backend/app/model" + +type FavoriteDTO struct { + model.Favorite +} diff --git a/backend/app/model/favorite.go b/backend/app/model/favorite.go new file mode 100644 index 000000000..6e5421dcf --- /dev/null +++ b/backend/app/model/favorite.go @@ -0,0 +1,10 @@ +package model + +type Favorite struct { + BaseModel + Name string `gorm:"type:varchar(256);not null;" json:"name" ` + Path string `gorm:"type:varchar(256);not null;unique" json:"path"` + Type string `gorm:"type:varchar(64);" json:"type"` + IsDir bool `json:"isDir"` + IsTxt bool `json:"isTxt"` +} diff --git a/backend/app/repo/favorite.go b/backend/app/repo/favorite.go new file mode 100644 index 000000000..01d2ba1ac --- /dev/null +++ b/backend/app/repo/favorite.go @@ -0,0 +1,66 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/global" + "gorm.io/gorm" +) + +type FavoriteRepo struct{} + +type IFavoriteRepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.Favorite, error) + Create(group *model.Favorite) error + Delete(opts ...DBOption) error + GetFirst(opts ...DBOption) (model.Favorite, error) + All() ([]model.Favorite, error) + WithByPath(path string) DBOption +} + +func NewIFavoriteRepo() IFavoriteRepo { + return &FavoriteRepo{} +} + +func (f *FavoriteRepo) WithByPath(path string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("path = ?", path) + } +} + +func (f *FavoriteRepo) Page(page, size int, opts ...DBOption) (int64, []model.Favorite, error) { + var ( + favorites []model.Favorite + count int64 + ) + count = int64(0) + db := getDb(opts...).Model(&model.Favorite{}) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&favorites).Error + return count, favorites, err +} + +func (f *FavoriteRepo) Create(favorite *model.Favorite) error { + return global.DB.Create(favorite).Error +} + +func (f *FavoriteRepo) GetFirst(opts ...DBOption) (model.Favorite, error) { + var favorite model.Favorite + db := getDb(opts...).Model(&model.Favorite{}) + if err := db.First(&favorite).Error; err != nil { + return favorite, err + } + return favorite, nil +} + +func (f *FavoriteRepo) Delete(opts ...DBOption) error { + db := getDb(opts...).Model(&model.Favorite{}) + return db.Delete(&model.Favorite{}).Error +} + +func (f *FavoriteRepo) All() ([]model.Favorite, error) { + var favorites []model.Favorite + if err := getDb().Find(&favorites).Error; err != nil { + return nil, err + } + return favorites, nil +} diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index e085513cd..a9a1ebbc9 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -37,4 +37,6 @@ var ( snapshotRepo = repo.NewISnapshotRepo() runtimeRepo = repo.NewIRunTimeRepo() + + favoriteRepo = repo.NewIFavoriteRepo() ) diff --git a/backend/app/service/favorite.go b/backend/app/service/favorite.go new file mode 100644 index 000000000..3d2ae3a90 --- /dev/null +++ b/backend/app/service/favorite.go @@ -0,0 +1,83 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/app/dto/request" + "github.com/1Panel-dev/1Panel/backend/app/dto/response" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/buserr" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/utils/files" + "github.com/spf13/afero" +) + +type FavoriteService struct { +} + +type IFavoriteService interface { + Page(req dto.PageInfo) (int64, []response.FavoriteDTO, error) + Create(req request.FavoriteCreate) (*model.Favorite, error) + Delete(id uint) error +} + +func NewIFavoriteService() IFavoriteService { + return &FavoriteService{} +} + +func (f *FavoriteService) Page(req dto.PageInfo) (int64, []response.FavoriteDTO, error) { + total, favorites, err := favoriteRepo.Page(req.Page, req.PageSize) + if err != nil { + return 0, nil, err + } + var dtoFavorites []response.FavoriteDTO + for _, favorite := range favorites { + dtoFavorites = append(dtoFavorites, response.FavoriteDTO{ + Favorite: favorite, + }) + } + return total, dtoFavorites, nil +} + +func (f *FavoriteService) Create(req request.FavoriteCreate) (*model.Favorite, error) { + exist, _ := favoriteRepo.GetFirst(favoriteRepo.WithByPath(req.Path)) + if exist.ID > 0 { + return nil, buserr.New(constant.ErrFavoriteExist) + } + op := files.NewFileOp() + if !op.Stat(req.Path) { + return nil, buserr.New(constant.ErrLinkPathNotFound) + } + openFile, err := op.OpenFile(req.Path) + if err != nil { + return nil, err + } + fileInfo, err := openFile.Stat() + if err != nil { + return nil, err + } + favorite := &model.Favorite{ + Name: fileInfo.Name(), + IsDir: fileInfo.IsDir(), + Path: req.Path, + } + if fileInfo.Size() <= 10*1024*1024 { + afs := &afero.Afero{Fs: op.Fs} + cByte, err := afs.ReadFile(req.Path) + if err == nil { + if len(cByte) > 0 && !files.DetectBinary(cByte) { + favorite.IsTxt = true + } + } + } + if err := favoriteRepo.Create(favorite); err != nil { + return nil, err + } + return favorite, nil +} + +func (f *FavoriteService) Delete(id uint) error { + if err := favoriteRepo.Delete(commonRepo.WithByID(id)); err != nil { + return err + } + return nil +} diff --git a/backend/constant/errs.go b/backend/constant/errs.go index 0d46ff1ec..2281a3804 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -90,6 +90,7 @@ var ( ErrFileUpload = "ErrFileUpload" ErrFileDownloadDir = "ErrFileDownloadDir" ErrCmdNotFound = "ErrCmdNotFound" + ErrFavoriteExist = "ErrFavoriteExist" ) // mysql diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index a943e2764..3d5483904 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -64,6 +64,7 @@ ErrFileUpload: "Failed to upload file {{.name}} {{.detail}}" ErrFileDownloadDir: "Download folder not supported" ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first" ErrSourcePathNotFound: "Source directory does not exist" +ErrFavoriteExist: "This path has been collected" #website ErrDomainIsExist: "Domain is already exist" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 0b3850b53..631e23a9e 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -64,6 +64,7 @@ ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}" ErrFileDownloadDir: "不支持下載文件夾" ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令" ErrSourcePathNotFound: "源目錄不存在" +ErrFavoriteExist: "已收藏此路徑" #website ErrDomainIsExist: "域名已存在" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index c49d72be4..ba66325c9 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -64,6 +64,7 @@ ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}" ErrFileDownloadDir: "不支持下载文件夹" ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令" ErrSourcePathNotFound: "源目录不存在" +ErrFavoriteExist: "已收藏此路径" #website ErrDomainIsExist: "域名已存在" diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 4efa47320..568b0f09e 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -47,6 +47,7 @@ func Init() { migrations.AddDefaultNetwork, migrations.UpdateRuntime, migrations.UpdateTag, + migrations.AddFavorite, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/v_1_8.go b/backend/init/migration/migrations/v_1_8.go new file mode 100644 index 000000000..84dd19ff7 --- /dev/null +++ b/backend/init/migration/migrations/v_1_8.go @@ -0,0 +1,17 @@ +package migrations + +import ( + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +var AddFavorite = &gormigrate.Migration{ + ID: "20231020-add-favorite", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.Favorite{}); err != nil { + return err + } + return nil + }, +} diff --git a/backend/router/ro_file.go b/backend/router/ro_file.go index 64bf409b3..ab510cf51 100644 --- a/backend/router/ro_file.go +++ b/backend/router/ro_file.go @@ -41,5 +41,10 @@ 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.POST("/favorite/search", baseApi.SearchFavorite) + fileRouter.POST("/favorite", baseApi.CreateFavorite) + fileRouter.POST("/favorite/del", baseApi.DeleteFavorite) + } } diff --git a/backend/utils/files/fileinfo.go b/backend/utils/files/fileinfo.go index 11948ad12..9ab0a93a0 100644 --- a/backend/utils/files/fileinfo.go +++ b/backend/utils/files/fileinfo.go @@ -3,6 +3,7 @@ package files import ( "bufio" "fmt" + "github.com/1Panel-dev/1Panel/backend/app/repo" "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/constant" "io/fs" @@ -42,6 +43,7 @@ type FileInfo struct { FileMode os.FileMode `json:"-"` Items []*FileInfo `json:"items"` ItemTotal int `json:"itemTotal"` + FavoriteID uint `json:"favoriteID"` } type FileOption struct { @@ -88,6 +90,12 @@ func NewFileInfo(op FileOption) (*FileInfo, error) { Group: GetGroup(info.Sys().(*syscall.Stat_t).Gid), MimeType: GetMimeType(op.Path), } + favoriteRepo := repo.NewIFavoriteRepo() + favorite, _ := favoriteRepo.GetFirst(favoriteRepo.WithByPath(op.Path)) + if favorite.ID > 0 { + file.FavoriteID = favorite.ID + } + if file.IsSymlink { file.LinkPath = GetSymlink(op.Path) } @@ -266,7 +274,11 @@ func (f *FileInfo) listChildren(option FileOption) error { Uid: strconv.FormatUint(uint64(df.Sys().(*syscall.Stat_t).Uid), 10), Gid: strconv.FormatUint(uint64(df.Sys().(*syscall.Stat_t).Gid), 10), } - + favoriteRepo := repo.NewIFavoriteRepo() + favorite, _ := favoriteRepo.GetFirst(favoriteRepo.WithByPath(fPath)) + if favorite.ID > 0 { + file.FavoriteID = favorite.ID + } if isSymlink { file.LinkPath = GetSymlink(fPath) } @@ -305,7 +317,7 @@ func (f *FileInfo) getContent() error { if err != nil { return nil } - if len(cByte) > 0 && detectBinary(cByte) { + if len(cByte) > 0 && DetectBinary(cByte) { return buserr.New(constant.ErrFileCanNotRead) } f.Content = string(cByte) @@ -315,7 +327,7 @@ func (f *FileInfo) getContent() error { } } -func detectBinary(buf []byte) bool { +func DetectBinary(buf []byte) bool { whiteByte := 0 n := min(1024, len(buf)) for i := 0; i < n; i++ { diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 484b06a01..aa0229a88 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -5368,6 +5368,132 @@ const docTemplate = `{ } } }, + "/files/favorite": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "创建收藏", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Create favorite", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "收藏文件/文件夹 [path]", + "formatZH": "收藏文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/del": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "删除收藏", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Delete favorite", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "favorites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "path", + "output_value": "path" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete avorite [path]", + "formatZH": "删除收藏 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取收藏列表", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "List favorites", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/mode": { "post": { "security": [ @@ -5498,6 +5624,110 @@ const docTemplate = `{ } } }, + "/files/recycle/clear": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "清空回收站文件", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Clear RecycleBin files", + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "清空回收站", + "formatZH": "清空回收站", + "paramKeys": [] + } + } + }, + "/files/recycle/reduce": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "还原回收站文件", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Reduce RecycleBin files", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.RecycleBinReduce" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Reduce RecycleBin file [name]", + "formatZH": "还原回收站文件 [name]", + "paramKeys": [] + } + } + }, + "/files/recycle/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取回收站文件列表", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "List RecycleBin files", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/rename": { "post": { "security": [ @@ -7826,94 +8056,6 @@ const docTemplate = `{ } } }, - "/recycle/clear": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "清空回收站文件", - "consumes": [ - "application/json" - ], - "tags": [ - "RecycleBin" - ], - "summary": "Clear RecycleBin files", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/recycle/reduce": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "还原回收站文件", - "consumes": [ - "application/json" - ], - "tags": [ - "RecycleBin" - ], - "summary": "Reduce RecycleBin files", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/request.RecycleBinReduce" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/recycle/search": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "获取回收站文件列表", - "consumes": [ - "application/json" - ], - "tags": [ - "RecycleBin" - ], - "summary": "List RecycleBin files", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/dto.PageInfo" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, "/runtimes": { "post": { "security": [ @@ -15563,6 +15705,9 @@ const docTemplate = `{ "extension": { "type": "string" }, + "favoriteID": { + "type": "integer" + }, "gid": { "type": "string" }, @@ -16212,6 +16357,28 @@ const docTemplate = `{ } } }, + "request.FavoriteCreate": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + } + } + }, + "request.FavoriteDelete": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + }, "request.FileBatchDelete": { "type": "object", "required": [ @@ -16938,6 +17105,9 @@ const docTemplate = `{ "from": { "type": "string" }, + "name": { + "type": "string" + }, "rName": { "type": "string" } @@ -18062,6 +18232,9 @@ const docTemplate = `{ "extension": { "type": "string" }, + "favoriteID": { + "type": "integer" + }, "gid": { "type": "string" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 4ad1ac140..b873464d3 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -5361,6 +5361,132 @@ } } }, + "/files/favorite": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "创建收藏", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Create favorite", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "收藏文件/文件夹 [path]", + "formatZH": "收藏文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/del": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "删除收藏", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Delete favorite", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "favorites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "path", + "output_value": "path" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete avorite [path]", + "formatZH": "删除收藏 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取收藏列表", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "List favorites", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/mode": { "post": { "security": [ @@ -5491,6 +5617,110 @@ } } }, + "/files/recycle/clear": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "清空回收站文件", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Clear RecycleBin files", + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "清空回收站", + "formatZH": "清空回收站", + "paramKeys": [] + } + } + }, + "/files/recycle/reduce": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "还原回收站文件", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "Reduce RecycleBin files", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.RecycleBinReduce" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Reduce RecycleBin file [name]", + "formatZH": "还原回收站文件 [name]", + "paramKeys": [] + } + } + }, + "/files/recycle/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取回收站文件列表", + "consumes": [ + "application/json" + ], + "tags": [ + "File" + ], + "summary": "List RecycleBin files", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/rename": { "post": { "security": [ @@ -7819,94 +8049,6 @@ } } }, - "/recycle/clear": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "清空回收站文件", - "consumes": [ - "application/json" - ], - "tags": [ - "RecycleBin" - ], - "summary": "Clear RecycleBin files", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/recycle/reduce": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "还原回收站文件", - "consumes": [ - "application/json" - ], - "tags": [ - "RecycleBin" - ], - "summary": "Reduce RecycleBin files", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/request.RecycleBinReduce" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/recycle/search": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "获取回收站文件列表", - "consumes": [ - "application/json" - ], - "tags": [ - "RecycleBin" - ], - "summary": "List RecycleBin files", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/dto.PageInfo" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, "/runtimes": { "post": { "security": [ @@ -15556,6 +15698,9 @@ "extension": { "type": "string" }, + "favoriteID": { + "type": "integer" + }, "gid": { "type": "string" }, @@ -16205,6 +16350,28 @@ } } }, + "request.FavoriteCreate": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + } + } + }, + "request.FavoriteDelete": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + }, "request.FileBatchDelete": { "type": "object", "required": [ @@ -16931,6 +17098,9 @@ "from": { "type": "string" }, + "name": { + "type": "string" + }, "rName": { "type": "string" } @@ -18055,6 +18225,9 @@ "extension": { "type": "string" }, + "favoriteID": { + "type": "integer" + }, "gid": { "type": "string" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index a1c33dcc1..4198ad127 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -2280,6 +2280,8 @@ definitions: type: string extension: type: string + favoriteID: + type: integer gid: type: string group: @@ -2710,6 +2712,20 @@ definitions: required: - path type: object + request.FavoriteCreate: + properties: + path: + type: string + required: + - path + type: object + request.FavoriteDelete: + properties: + id: + type: integer + required: + - id + type: object request.FileBatchDelete: properties: isDir: @@ -3196,6 +3212,8 @@ definitions: properties: from: type: string + name: + type: string rName: type: string required: @@ -3955,6 +3973,8 @@ definitions: type: string extension: type: string + favoriteID: + type: integer gid: type: string group: @@ -7604,6 +7624,86 @@ paths: formatEN: Download file [name] formatZH: 下载文件 [name] paramKeys: [] + /files/favorite: + post: + consumes: + - application/json + description: 创建收藏 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.FavoriteCreate' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Create favorite + tags: + - File + x-panel-log: + BeforeFunctions: [] + bodyKeys: + - path + formatEN: 收藏文件/文件夹 [path] + formatZH: 收藏文件/文件夹 [path] + paramKeys: [] + /files/favorite/del: + post: + consumes: + - application/json + description: 删除收藏 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.FavoriteDelete' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Delete favorite + tags: + - File + x-panel-log: + BeforeFunctions: + - db: favorites + input_column: id + input_value: id + isList: false + output_column: path + output_value: path + bodyKeys: + - id + formatEN: delete avorite [path] + formatZH: 删除收藏 [path] + paramKeys: [] + /files/favorite/search: + post: + consumes: + - application/json + description: 获取收藏列表 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.PageInfo' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: List favorites + tags: + - File /files/mode: post: consumes: @@ -7689,6 +7789,72 @@ paths: formatEN: Change owner [paths] => [user]/[group] formatZH: 修改用户/组 [paths] => [user]/[group] paramKeys: [] + /files/recycle/clear: + post: + consumes: + - application/json + description: 清空回收站文件 + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Clear RecycleBin files + tags: + - File + x-panel-log: + BeforeFunctions: [] + bodyKeys: [] + formatEN: 清空回收站 + formatZH: 清空回收站 + paramKeys: [] + /files/recycle/reduce: + post: + consumes: + - application/json + description: 还原回收站文件 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.RecycleBinReduce' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Reduce RecycleBin files + tags: + - File + x-panel-log: + BeforeFunctions: [] + bodyKeys: + - name + formatEN: Reduce RecycleBin file [name] + formatZH: 还原回收站文件 [name] + paramKeys: [] + /files/recycle/search: + post: + consumes: + - application/json + description: 获取回收站文件列表 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.PageInfo' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: List RecycleBin files + tags: + - File /files/rename: post: consumes: @@ -9159,59 +9325,6 @@ paths: formatEN: 结束进程 [PID] formatZH: 结束进程 [PID] paramKeys: [] - /recycle/clear: - post: - consumes: - - application/json - description: 清空回收站文件 - responses: - "200": - description: OK - security: - - ApiKeyAuth: [] - summary: Clear RecycleBin files - tags: - - RecycleBin - /recycle/reduce: - post: - consumes: - - application/json - description: 还原回收站文件 - parameters: - - description: request - in: body - name: request - required: true - schema: - $ref: '#/definitions/request.RecycleBinReduce' - responses: - "200": - description: OK - security: - - ApiKeyAuth: [] - summary: Reduce RecycleBin files - tags: - - RecycleBin - /recycle/search: - post: - consumes: - - application/json - description: 获取回收站文件列表 - parameters: - - description: request - in: body - name: request - required: true - schema: - $ref: '#/definitions/dto.PageInfo' - responses: - "200": - description: OK - security: - - ApiKeyAuth: [] - summary: List RecycleBin files - tags: - - RecycleBin /runtimes: post: consumes: diff --git a/frontend/src/api/interface/file.ts b/frontend/src/api/interface/file.ts index 701e74e0e..e22d73ae0 100644 --- a/frontend/src/api/interface/file.ts +++ b/frontend/src/api/interface/file.ts @@ -21,6 +21,7 @@ export namespace File { items: File[]; extension: string; itemTotal: number; + favoriteID: number; } export interface ReqFile extends ReqPage { @@ -162,4 +163,10 @@ export namespace File { from: string; name: string; } + + export interface Favorite extends CommonModel { + path: string; + isDir: boolean; + isTxt: boolean; + } } diff --git a/frontend/src/api/modules/files.ts b/frontend/src/api/modules/files.ts index 1261f087e..b5089a0ed 100644 --- a/frontend/src/api/modules/files.ts +++ b/frontend/src/api/modules/files.ts @@ -100,3 +100,15 @@ export const reduceFile = (params: File.RecycleBinReduce) => { export const clearRecycle = () => { return http.post('files/recycle/clear'); }; + +export const SearchFavorite = (params: ReqPage) => { + return http.post>('files/favorite/search', params); +}; + +export const AddFavorite = (path: string) => { + return http.post('files/favorite', { path: path }); +}; + +export const RemoveFavorite = (id: number) => { + return http.post('files/favorite/del', { id: id }); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 2785e4e7f..fa3d93b60 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -122,6 +122,7 @@ const message = { 'You can upload only files whose name contains 1 to 256 characters, including English, Chinese, digits, or periods (.-_)', confirmNoNull: 'Make sure the value {0} is not empty', errPort: 'Incorrect port information, please confirm!', + remove: 'Remove', }, login: { username: 'UserName', @@ -970,7 +971,10 @@ const message = { reduceHelper: 'Restore the file to its original path. If a file or directory with the same name exists at the original address of the file, it will be overwritten. Do you want to continue?', clearRecycleBin: 'Clear the recycle bin', - clearRecycleBinHelper: 'Do you want to clear the recycle bin? ', + clearRecycleBinHelper: 'Do you want to clear the recycle bin?', + favorite: 'favorites', + removeFavorite: 'Remove from favorites?', + addFavorite: 'Add to favorites', }, ssh: { autoStart: 'Auto Start', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 508dc766b..c94c6911a 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -123,6 +123,7 @@ const message = { fileNameErr: '僅支持上傳名稱包含英文、中文、數字或者 .-_ ,長度 1-256 位的文件', confirmNoNull: '請確認 {0} 值不為空', errPort: '錯誤的端口信息,請確認!', + remove: '移出', }, login: { username: '用戶名', @@ -931,7 +932,10 @@ const message = { reduce: '還原', reduceHelper: '恢復檔案到原路徑,如果檔案原始位址,存在同名檔案或目錄,將會覆蓋,是否繼續? ', clearRecycleBin: '清空回收站', - clearRecycleBinHelper: '是否清空回收站? ', + clearRecycleBinHelper: '是否清空回收站?', + favorite: '收藏夾', + removeFavorite: '是否從收藏夾移出?', + addFavorite: '加入收藏夾', }, ssh: { autoStart: '開機自啟', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 21a7978d6..79351fe14 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -123,6 +123,7 @@ const message = { fileNameErr: '仅支持上传名称包含英文、中文、数字或者 .-_ ,长度 1-256 位的文件', confirmNoNull: '请确认 {0} 值不为空', errPort: '错误的端口信息,请确认!', + remove: '移出', }, login: { username: '用户名', @@ -933,6 +934,9 @@ const message = { reduceHelper: '恢复文件到原路径,如果文件原地址,存在同名文件或目录,将会覆盖,是否继续?', clearRecycleBin: '清空回收站', clearRecycleBinHelper: '是否清空回收站?', + favorite: '收藏夹', + removeFavorite: '是否从收藏夹移出?', + addFavorite: '加入收藏夹子', }, ssh: { autoStart: '开机自启', diff --git a/frontend/src/views/host/file-management/favorite/index.vue b/frontend/src/views/host/file-management/favorite/index.vue new file mode 100644 index 000000000..fb4018bea --- /dev/null +++ b/frontend/src/views/host/file-management/favorite/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/frontend/src/views/host/file-management/index.vue b/frontend/src/views/host/file-management/index.vue index a1e6809b8..b40c4c807 100644 --- a/frontend/src/views/host/file-management/index.vue +++ b/frontend/src/views/host/file-management/index.vue @@ -98,6 +98,9 @@
+ + {{ $t('file.favorite') }} + {{ $t('file.recycleBin') }} @@ -140,11 +143,42 @@ sortable prop="name" > -