mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-08 01:20:07 +08:00
feat: 文件增加回收站功能 (#2586)
Refs https://github.com/1Panel-dev/1Panel/issues/1137 Refs https://github.com/1Panel-dev/1Panel/issues/624
This commit is contained in:
parent
eeb80749a2
commit
743e7d0b59
@ -53,4 +53,6 @@ var (
|
|||||||
processService = service.NewIProcessService()
|
processService = service.NewIProcessService()
|
||||||
|
|
||||||
hostToolService = service.NewIHostToolService()
|
hostToolService = service.NewIHostToolService()
|
||||||
|
|
||||||
|
recycleBinService = service.NewIRecycleBinService()
|
||||||
)
|
)
|
||||||
|
70
backend/app/api/v1/recycle_bin.go
Normal file
70
backend/app/api/v1/recycle_bin.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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 RecycleBin
|
||||||
|
// @Summary List RecycleBin files
|
||||||
|
// @Description 获取回收站文件列表
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.PageInfo true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /recycle/search [post]
|
||||||
|
func (b *BaseApi) SearchRecycleBinFile(c *gin.Context) {
|
||||||
|
var req dto.PageInfo
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
total, list, err := recycleBinService.Page(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags RecycleBin
|
||||||
|
// @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]"}
|
||||||
|
func (b *BaseApi) ReduceRecycleBinFile(c *gin.Context) {
|
||||||
|
var req request.RecycleBinReduce
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := recycleBinService.Reduce(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithOutData(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags RecycleBin
|
||||||
|
// @Summary Clear RecycleBin files
|
||||||
|
// @Description 清空回收站文件
|
||||||
|
// @Accept json
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /recycle/clear [post]
|
||||||
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空回收站","formatEN":"清空回收站"}
|
||||||
|
func (b *BaseApi) ClearRecycleBinFile(c *gin.Context) {
|
||||||
|
if err := recycleBinService.Clear(); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithOutData(c)
|
||||||
|
}
|
@ -28,6 +28,7 @@ type FileCreate struct {
|
|||||||
type FileDelete struct {
|
type FileDelete struct {
|
||||||
Path string `json:"path" validate:"required"`
|
Path string `json:"path" validate:"required"`
|
||||||
IsDir bool `json:"isDir"`
|
IsDir bool `json:"isDir"`
|
||||||
|
ForceDelete bool `json:"forceDelete"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileBatchDelete struct {
|
type FileBatchDelete struct {
|
||||||
|
11
backend/app/dto/request/recycle_bin.go
Normal file
11
backend/app/dto/request/recycle_bin.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
type RecycleBinCreate struct {
|
||||||
|
SourcePath string `json:"sourcePath" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecycleBinReduce struct {
|
||||||
|
From string `json:"from" validate:"required"`
|
||||||
|
RName string `json:"rName" validate:"required"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
14
backend/app/dto/response/recycle_bin.go
Normal file
14
backend/app/dto/response/recycle_bin.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type RecycleBinDTO struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
DeleteTime time.Time `json:"deleteTime"`
|
||||||
|
RName string `json:"rName"`
|
||||||
|
SourcePath string `json:"sourcePath"`
|
||||||
|
IsDir bool `json:"isDir"`
|
||||||
|
From string `json:"from"`
|
||||||
|
}
|
@ -133,12 +133,15 @@ func (f *FileService) Create(op request.FileCreate) error {
|
|||||||
|
|
||||||
func (f *FileService) Delete(op request.FileDelete) error {
|
func (f *FileService) Delete(op request.FileDelete) error {
|
||||||
fo := files.NewFileOp()
|
fo := files.NewFileOp()
|
||||||
|
if op.ForceDelete {
|
||||||
if op.IsDir {
|
if op.IsDir {
|
||||||
return fo.DeleteDir(op.Path)
|
return fo.DeleteDir(op.Path)
|
||||||
} else {
|
} else {
|
||||||
return fo.DeleteFile(op.Path)
|
return fo.DeleteFile(op.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return NewIRecycleBinService().Create(request.RecycleBinCreate{SourcePath: op.Path})
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FileService) BatchDelete(op request.FileBatchDelete) error {
|
func (f *FileService) BatchDelete(op request.FileBatchDelete) error {
|
||||||
fo := files.NewFileOp()
|
fo := files.NewFileOp()
|
||||||
|
208
backend/app/service/recycle_bin.go
Normal file
208
backend/app/service/recycle_bin.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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/buserr"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecycleBinService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type IRecycleBinService interface {
|
||||||
|
Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error)
|
||||||
|
Create(create request.RecycleBinCreate) error
|
||||||
|
Reduce(reduce request.RecycleBinReduce) error
|
||||||
|
Clear() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIRecycleBinService() IRecycleBinService {
|
||||||
|
return &RecycleBinService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RecycleBinService) Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error) {
|
||||||
|
var (
|
||||||
|
result []response.RecycleBinDTO
|
||||||
|
)
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
op := files.NewFileOp()
|
||||||
|
for _, p := range partitions {
|
||||||
|
dir := path.Join(p.Mountpoint, ".1panel_clash")
|
||||||
|
if !op.Stat(dir) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clashFiles, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
for _, file := range clashFiles {
|
||||||
|
if strings.HasPrefix(file.Name(), "_1p_") {
|
||||||
|
recycleDTO, err := getRecycleBinDTOFromName(file.Name())
|
||||||
|
recycleDTO.IsDir = file.IsDir()
|
||||||
|
recycleDTO.From = dir
|
||||||
|
if err == nil {
|
||||||
|
result = append(result, *recycleDTO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startIndex := (search.Page - 1) * search.PageSize
|
||||||
|
endIndex := startIndex + search.PageSize
|
||||||
|
|
||||||
|
if startIndex > len(result) {
|
||||||
|
return int64(len(result)), result, nil
|
||||||
|
}
|
||||||
|
if endIndex > len(result) {
|
||||||
|
endIndex = len(result)
|
||||||
|
}
|
||||||
|
return int64(len(result)), result[startIndex:endIndex], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RecycleBinService) Create(create request.RecycleBinCreate) error {
|
||||||
|
op := files.NewFileOp()
|
||||||
|
if !op.Stat(create.SourcePath) {
|
||||||
|
return buserr.New(constant.ErrLinkPathNotFound)
|
||||||
|
}
|
||||||
|
clashDir, err := getClashDir(create.SourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
paths := strings.Split(create.SourcePath, "/")
|
||||||
|
rNamePre := strings.Join(paths, "_1p_")
|
||||||
|
deleteTime := time.Now()
|
||||||
|
openFile, err := op.OpenFile(create.SourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileInfo, err := openFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
size := 0
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
sizeF, err := op.GetDirSize(create.SourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
size = int(sizeF)
|
||||||
|
} else {
|
||||||
|
size = int(fileInfo.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
rName := fmt.Sprintf("_1p_%s%s_p_%d_%d", "file", rNamePre, size, deleteTime.Unix())
|
||||||
|
return op.Rename(create.SourcePath, path.Join(clashDir, rName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RecycleBinService) Reduce(reduce request.RecycleBinReduce) error {
|
||||||
|
filePath := path.Join(reduce.From, reduce.RName)
|
||||||
|
op := files.NewFileOp()
|
||||||
|
if !op.Stat(filePath) {
|
||||||
|
return buserr.New(constant.ErrLinkPathNotFound)
|
||||||
|
}
|
||||||
|
recycleBinDTO, err := getRecycleBinDTOFromName(reduce.RName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !op.Stat(path.Dir(recycleBinDTO.SourcePath)) {
|
||||||
|
return buserr.New("ErrSourcePathNotFound")
|
||||||
|
}
|
||||||
|
if op.Stat(recycleBinDTO.SourcePath) {
|
||||||
|
if err = op.RmRf(recycleBinDTO.SourcePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return op.Rename(filePath, recycleBinDTO.SourcePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RecycleBinService) Clear() error {
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
op := files.NewFileOp()
|
||||||
|
for _, p := range partitions {
|
||||||
|
dir := path.Join(p.Mountpoint, ".1panel_clash")
|
||||||
|
if !op.Stat(dir) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newDir := path.Join(p.Mountpoint, "1panel_clash")
|
||||||
|
if err := op.Rename(dir, newDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_ = op.DeleteDir(newDir)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClashDir(realPath string) (string, error) {
|
||||||
|
trimmedPath := strings.Trim(realPath, "/")
|
||||||
|
parts := strings.Split(trimmedPath, "/")
|
||||||
|
dir := ""
|
||||||
|
if len(parts) > 0 {
|
||||||
|
dir = parts[0]
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, p := range partitions {
|
||||||
|
if p.Mountpoint == dir {
|
||||||
|
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constant.RecycleBinDir, createClashDir(constant.RecycleBinDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClashDir(clashDir string) error {
|
||||||
|
op := files.NewFileOp()
|
||||||
|
if !op.Stat(clashDir) {
|
||||||
|
if err := op.CreateDir(clashDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRecycleBinDTOFromName(filename string) (*response.RecycleBinDTO, error) {
|
||||||
|
r := regexp.MustCompile(`_1p_file_1p_(.+)_p_(\d+)_(\d+)`)
|
||||||
|
matches := r.FindStringSubmatch(filename)
|
||||||
|
if len(matches) != 4 {
|
||||||
|
return nil, fmt.Errorf("invalid filename format")
|
||||||
|
}
|
||||||
|
sourcePath := "/" + strings.ReplaceAll(matches[1], "_1p_", "/")
|
||||||
|
size, err := strconv.ParseInt(matches[2], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
deleteTime, err := strconv.ParseInt(matches[3], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.RecycleBinDTO{
|
||||||
|
Name: path.Base(sourcePath),
|
||||||
|
Size: int(size),
|
||||||
|
Type: "file",
|
||||||
|
DeleteTime: time.Unix(deleteTime, 0),
|
||||||
|
SourcePath: sourcePath,
|
||||||
|
RName: filename,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -15,4 +15,5 @@ var (
|
|||||||
LocalAppInstallDir = path.Join(AppInstallDir, "local")
|
LocalAppInstallDir = path.Join(AppInstallDir, "local")
|
||||||
RemoteAppResourceDir = path.Join(AppResourceDir, "remote")
|
RemoteAppResourceDir = path.Join(AppResourceDir, "remote")
|
||||||
RuntimeDir = path.Join(DataDir, "runtime")
|
RuntimeDir = path.Join(DataDir, "runtime")
|
||||||
|
RecycleBinDir = "/.1panel_clash"
|
||||||
)
|
)
|
||||||
|
@ -63,6 +63,7 @@ ErrFileIsExit: "File already exists!"
|
|||||||
ErrFileUpload: "Failed to upload file {{.name}} {{.detail}}"
|
ErrFileUpload: "Failed to upload file {{.name}} {{.detail}}"
|
||||||
ErrFileDownloadDir: "Download folder not supported"
|
ErrFileDownloadDir: "Download folder not supported"
|
||||||
ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first"
|
ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first"
|
||||||
|
ErrSourcePathNotFound: "Source directory does not exist"
|
||||||
|
|
||||||
#website
|
#website
|
||||||
ErrDomainIsExist: "Domain is already exist"
|
ErrDomainIsExist: "Domain is already exist"
|
||||||
|
@ -63,6 +63,7 @@ ErrFileIsExit: "文件已存在!"
|
|||||||
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
|
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
|
||||||
ErrFileDownloadDir: "不支持下載文件夾"
|
ErrFileDownloadDir: "不支持下載文件夾"
|
||||||
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
||||||
|
ErrSourcePathNotFound: "源目錄不存在"
|
||||||
|
|
||||||
#website
|
#website
|
||||||
ErrDomainIsExist: "域名已存在"
|
ErrDomainIsExist: "域名已存在"
|
||||||
|
@ -63,6 +63,7 @@ ErrFileIsExit: "文件已存在!"
|
|||||||
ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}"
|
ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}"
|
||||||
ErrFileDownloadDir: "不支持下载文件夹"
|
ErrFileDownloadDir: "不支持下载文件夹"
|
||||||
ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||||
|
ErrSourcePathNotFound: "源目录不存在"
|
||||||
|
|
||||||
#website
|
#website
|
||||||
ErrDomainIsExist: "域名已存在"
|
ErrDomainIsExist: "域名已存在"
|
||||||
|
@ -37,5 +37,9 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
|||||||
fileRouter.POST("/size", baseApi.Size)
|
fileRouter.POST("/size", baseApi.Size)
|
||||||
fileRouter.GET("/ws", baseApi.Ws)
|
fileRouter.GET("/ws", baseApi.Ws)
|
||||||
fileRouter.GET("/keys", baseApi.Keys)
|
fileRouter.GET("/keys", baseApi.Keys)
|
||||||
|
|
||||||
|
fileRouter.POST("/recycle/search", baseApi.SearchRecycleBinFile)
|
||||||
|
fileRouter.POST("/recycle/reduce", baseApi.ReduceRecycleBinFile)
|
||||||
|
fileRouter.POST("/recycle/clear", baseApi.ClearRecycleBinFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,14 @@ func (f FileOp) DeleteFile(dst string) error {
|
|||||||
return f.Fs.Remove(dst)
|
return f.Fs.Remove(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f FileOp) Delete(dst string) error {
|
||||||
|
return os.RemoveAll(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileOp) RmRf(dst string) error {
|
||||||
|
return cmd.ExecCmd(fmt.Sprintf("rm -rf %s", dst))
|
||||||
|
}
|
||||||
|
|
||||||
func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error {
|
func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error {
|
||||||
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -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
|
package docs
|
||||||
|
|
||||||
import "github.com/swaggo/swag"
|
import "github.com/swaggo/swag"
|
||||||
@ -7826,6 +7826,94 @@ 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": {
|
"/runtimes": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -16228,6 +16316,9 @@ const docTemplate = `{
|
|||||||
"path"
|
"path"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"forceDelete": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"isDir": {
|
"isDir": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@ -16837,6 +16928,21 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"request.RecycleBinReduce": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"rName"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"rName": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"request.RuntimeCreate": {
|
"request.RuntimeCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -7819,6 +7819,94 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/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": {
|
"/runtimes": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -16221,6 +16309,9 @@
|
|||||||
"path"
|
"path"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"forceDelete": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"isDir": {
|
"isDir": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@ -16830,6 +16921,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"request.RecycleBinReduce": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"rName"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"rName": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"request.RuntimeCreate": {
|
"request.RuntimeCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2778,6 +2778,8 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
request.FileDelete:
|
request.FileDelete:
|
||||||
properties:
|
properties:
|
||||||
|
forceDelete:
|
||||||
|
type: boolean
|
||||||
isDir:
|
isDir:
|
||||||
type: boolean
|
type: boolean
|
||||||
path:
|
path:
|
||||||
@ -3190,6 +3192,16 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- PID
|
- PID
|
||||||
type: object
|
type: object
|
||||||
|
request.RecycleBinReduce:
|
||||||
|
properties:
|
||||||
|
from:
|
||||||
|
type: string
|
||||||
|
rName:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- from
|
||||||
|
- rName
|
||||||
|
type: object
|
||||||
request.RuntimeCreate:
|
request.RuntimeCreate:
|
||||||
properties:
|
properties:
|
||||||
appDetailId:
|
appDetailId:
|
||||||
@ -9147,6 +9159,59 @@ paths:
|
|||||||
formatEN: 结束进程 [PID]
|
formatEN: 结束进程 [PID]
|
||||||
formatZH: 结束进程 [PID]
|
formatZH: 结束进程 [PID]
|
||||||
paramKeys: []
|
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:
|
/runtimes:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -65,6 +65,7 @@ export namespace File {
|
|||||||
export interface FileDelete {
|
export interface FileDelete {
|
||||||
path: string;
|
path: string;
|
||||||
isDir: boolean;
|
isDir: boolean;
|
||||||
|
forceDelete: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileBatchDelete {
|
export interface FileBatchDelete {
|
||||||
@ -145,4 +146,20 @@ export namespace File {
|
|||||||
export interface FilePath {
|
export interface FilePath {
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RecycleBin {
|
||||||
|
sourcePath: string;
|
||||||
|
name: string;
|
||||||
|
isDir: boolean;
|
||||||
|
size: number;
|
||||||
|
deleteTime: string;
|
||||||
|
rName: string;
|
||||||
|
from: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecycleBinReduce {
|
||||||
|
rName: string;
|
||||||
|
from: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import http from '@/api';
|
|||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { ResPage } from '../interface';
|
import { ResPage } from '../interface';
|
||||||
import { TimeoutEnum } from '@/enums/http-enum';
|
import { TimeoutEnum } from '@/enums/http-enum';
|
||||||
|
import { ReqPage } from '@/api/interface';
|
||||||
|
|
||||||
export const GetFilesList = (params: File.ReqFile) => {
|
export const GetFilesList = (params: File.ReqFile) => {
|
||||||
return http.post<File.File>('files/search', params, TimeoutEnum.T_5M);
|
return http.post<File.File>('files/search', params, TimeoutEnum.T_5M);
|
||||||
@ -87,3 +88,15 @@ export const ComputeDirSize = (params: File.DirSizeReq) => {
|
|||||||
export const FileKeys = () => {
|
export const FileKeys = () => {
|
||||||
return http.get<File.FileKeys>('files/keys');
|
return http.get<File.FileKeys>('files/keys');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getRecycleList = (params: ReqPage) => {
|
||||||
|
return http.post<ResPage<File.RecycleBin>>('files/recycle/search', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reduceFile = (params: File.RecycleBinReduce) => {
|
||||||
|
return http.post<any>('files/recycle/reduce', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearRecycle = () => {
|
||||||
|
return http.post<any>('files/recycle/clear');
|
||||||
|
};
|
||||||
|
@ -957,8 +957,18 @@ const message = {
|
|||||||
fileUploadStart: 'Uploading [{0}]....',
|
fileUploadStart: 'Uploading [{0}]....',
|
||||||
currentSelect: 'Current Select: ',
|
currentSelect: 'Current Select: ',
|
||||||
unsupportType: 'Unsupported file type',
|
unsupportType: 'Unsupported file type',
|
||||||
deleteHelper: 'The following resources will be deleted, this operation cannot be rolled back, continue? ',
|
deleteHelper:
|
||||||
|
'Are you sure you want to delete the following files? By default, it will enter the recycle bin after deletion',
|
||||||
fileHeper: 'Note: 1. Sorting is not supported after searching 2. Folders are not supported by size sorting',
|
fileHeper: 'Note: 1. Sorting is not supported after searching 2. Folders are not supported by size sorting',
|
||||||
|
forceDeleteHelper: 'Permanently delete the file (without entering the recycle bin, delete it directly)',
|
||||||
|
recycleBin: 'Recycle bin',
|
||||||
|
sourcePath: 'Original path',
|
||||||
|
deleteTime: 'Delete time',
|
||||||
|
reduce: 'Reduction',
|
||||||
|
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? ',
|
||||||
},
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
autoStart: 'Auto Start',
|
autoStart: 'Auto Start',
|
||||||
|
@ -920,8 +920,16 @@ const message = {
|
|||||||
fileUploadStart: '正在上傳【{0}】....',
|
fileUploadStart: '正在上傳【{0}】....',
|
||||||
currentSelect: '當前選中: ',
|
currentSelect: '當前選中: ',
|
||||||
unsupportType: '不支持的文件類型',
|
unsupportType: '不支持的文件類型',
|
||||||
deleteHelper: '以下資源將被刪除,此操作不可回滾,是否繼續?',
|
deleteHelper: '確定刪除所選檔案? 預設刪除之後將進入回收站?',
|
||||||
fileHeper: '注意:1.搜尋之後不支援排序 2.依大小排序不支援資料夾',
|
fileHeper: '注意:1.搜尋之後不支援排序 2.依大小排序不支援資料夾',
|
||||||
|
forceDeleteHelper: '永久刪除檔案(不進入回收站,直接刪除)',
|
||||||
|
recycleBin: '回收站',
|
||||||
|
sourcePath: '原路徑',
|
||||||
|
deleteTime: '刪除時間',
|
||||||
|
reduce: '還原',
|
||||||
|
reduceHelper: '恢復檔案到原路徑,如果檔案原始位址,存在同名檔案或目錄,將會覆蓋,是否繼續? ',
|
||||||
|
clearRecycleBin: '清空回收站',
|
||||||
|
clearRecycleBinHelper: '是否清空回收站? ',
|
||||||
},
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
autoStart: '開機自啟',
|
autoStart: '開機自啟',
|
||||||
|
@ -921,8 +921,16 @@ const message = {
|
|||||||
fileUploadStart: '正在上传【{0}】....',
|
fileUploadStart: '正在上传【{0}】....',
|
||||||
currentSelect: '当前选中: ',
|
currentSelect: '当前选中: ',
|
||||||
unsupportType: '不支持的文件类型',
|
unsupportType: '不支持的文件类型',
|
||||||
deleteHelper: '以下资源将被删除,此操作不可回滚,是否继续?',
|
deleteHelper: '确定删除所选文件? 默认删除之后将进入回收站',
|
||||||
fileHeper: '注意:1.搜索之后不支持排序 2.按大小排序不支持文件夹',
|
fileHeper: '注意:1.搜索之后不支持排序 2.按大小排序不支持文件夹',
|
||||||
|
forceDeleteHelper: '永久删除文件(不进入回收站,直接删除)',
|
||||||
|
recycleBin: '回收站',
|
||||||
|
sourcePath: '原路径',
|
||||||
|
deleteTime: '删除时间',
|
||||||
|
reduce: '还原',
|
||||||
|
reduceHelper: '恢复文件到原路径,如果文件原地址,存在同名文件或目录,将会覆盖,是否继续?',
|
||||||
|
clearRecycleBin: '清空回收站',
|
||||||
|
clearRecycleBinHelper: '是否清空回收站?',
|
||||||
},
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
autoStart: '开机自启',
|
autoStart: '开机自启',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer v-model="open" :before-close="handleClose" :close-on-click-modal="false" width="50%">
|
<el-drawer v-model="open" :before-close="handleClose" :close-on-click-modal="false" size="50%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<DrawerHeader :header="$t('file.setRole')" :resource="name" :back="handleClose" />
|
<DrawerHeader :header="$t('file.setRole')" :resource="name" :back="handleClose" />
|
||||||
</template>
|
</template>
|
||||||
@ -32,7 +32,7 @@ import { MsgSuccess } from '@/utils/message';
|
|||||||
|
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
const form = ref<File.FileCreate>({ path: '', isDir: false, mode: 0o755 });
|
const form = ref<File.FileCreate>({ path: '', isDir: false, mode: 0o755 });
|
||||||
const loading = ref<Boolean>(false);
|
const loading = ref(false);
|
||||||
const mode = ref('0755');
|
const mode = ref('0755');
|
||||||
const name = ref('');
|
const name = ref('');
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<el-checkbox v-model="forceDelete">{{ $t('file.forceDeleteHelper') }}</el-checkbox>
|
||||||
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
@ -45,6 +48,7 @@ const open = ref(false);
|
|||||||
const files = ref();
|
const files = ref();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const em = defineEmits(['close']);
|
const em = defineEmits(['close']);
|
||||||
|
const forceDelete = ref(false);
|
||||||
|
|
||||||
const acceptParams = (props: File.File[]) => {
|
const acceptParams = (props: File.File[]) => {
|
||||||
files.value = props;
|
files.value = props;
|
||||||
@ -54,7 +58,7 @@ const acceptParams = (props: File.File[]) => {
|
|||||||
const onConfirm = () => {
|
const onConfirm = () => {
|
||||||
const pros = [];
|
const pros = [];
|
||||||
for (const s of files.value) {
|
for (const s of files.value) {
|
||||||
pros.push(DeleteFile({ path: s['path'], isDir: s['isDir'] }));
|
pros.push(DeleteFile({ path: s['path'], isDir: s['isDir'], forceDelete: forceDelete.value }));
|
||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
Promise.all(pros)
|
Promise.all(pros)
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
</el-alert>
|
</el-alert>
|
||||||
</template>
|
</template>
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
|
<div class="btn-container">
|
||||||
|
<div class="left-section">
|
||||||
<el-dropdown @command="handleCreate">
|
<el-dropdown @command="handleCreate">
|
||||||
<el-button type="primary">
|
<el-button type="primary">
|
||||||
{{ $t('commons.button.create') }}
|
{{ $t('commons.button.create') }}
|
||||||
@ -66,7 +68,7 @@
|
|||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
<el-button-group style="margin-left: 10px">
|
<el-button-group>
|
||||||
<el-button plain @click="openUpload">{{ $t('file.upload') }}</el-button>
|
<el-button plain @click="openUpload">{{ $t('file.upload') }}</el-button>
|
||||||
<el-button plain @click="openWget">{{ $t('file.remoteFile') }}</el-button>
|
<el-button plain @click="openWget">{{ $t('file.remoteFile') }}</el-button>
|
||||||
<el-button plain @click="openMove('copy')" :disabled="selects.length === 0">
|
<el-button plain @click="openMove('copy')" :disabled="selects.length === 0">
|
||||||
@ -82,6 +84,7 @@
|
|||||||
{{ $t('commons.button.delete') }}
|
{{ $t('commons.button.delete') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
|
|
||||||
<el-button-group class="copy-button" v-if="moveOpen">
|
<el-button-group class="copy-button" v-if="moveOpen">
|
||||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.paste')" placement="bottom">
|
<el-tooltip class="box-item" effect="dark" :content="$t('file.paste')" placement="bottom">
|
||||||
<el-button plain @click="openPaste">{{ $t('file.paste') }}</el-button>
|
<el-button plain @click="openPaste">{{ $t('file.paste') }}</el-button>
|
||||||
@ -92,7 +95,13 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
<div class="search search-button">
|
</div>
|
||||||
|
|
||||||
|
<div class="right-section">
|
||||||
|
<el-button class="btn" @click="openRecycleBin" :icon="Delete">
|
||||||
|
{{ $t('file.recycleBin') }}
|
||||||
|
</el-button>
|
||||||
|
<div class="search-button">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="req.search"
|
v-model="req.search"
|
||||||
clearable
|
clearable
|
||||||
@ -110,6 +119,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
@ -200,7 +211,8 @@
|
|||||||
<Process :open="processPage.open" @close="closeProcess" />
|
<Process :open="processPage.open" @close="closeProcess" />
|
||||||
<Owner ref="chownRef" @close="search"></Owner>
|
<Owner ref="chownRef" @close="search"></Owner>
|
||||||
<Detail ref="detailRef" />
|
<Detail ref="detailRef" />
|
||||||
<Delete ref="deleteRef" @close="search" />
|
<DeleteFile ref="deleteRef" @close="search" />
|
||||||
|
<RecycleBin ref="recycleBinRef" @close="search" />
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -209,6 +221,7 @@
|
|||||||
import { nextTick, onMounted, reactive, ref, computed } from '@vue/runtime-core';
|
import { nextTick, onMounted, reactive, ref, computed } from '@vue/runtime-core';
|
||||||
import { GetFilesList, GetFileContent, ComputeDirSize } from '@/api/modules/files';
|
import { GetFilesList, GetFileContent, ComputeDirSize } from '@/api/modules/files';
|
||||||
import { computeSize, dateFormat, downloadFile, getIcon, getRandomStr } from '@/utils/util';
|
import { computeSize, dateFormat, downloadFile, getIcon, getRandomStr } from '@/utils/util';
|
||||||
|
import { Delete } from '@element-plus/icons-vue';
|
||||||
import { File } from '@/api/interface/file';
|
import { File } from '@/api/interface/file';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import CreateFile from './create/index.vue';
|
import CreateFile from './create/index.vue';
|
||||||
@ -222,10 +235,11 @@ import Wget from './wget/index.vue';
|
|||||||
import Move from './move/index.vue';
|
import Move from './move/index.vue';
|
||||||
import Download from './download/index.vue';
|
import Download from './download/index.vue';
|
||||||
import Owner from './chown/index.vue';
|
import Owner from './chown/index.vue';
|
||||||
import Delete from './delete/index.vue';
|
import DeleteFile from './delete/index.vue';
|
||||||
import { Mimetypes, Languages } from '@/global/mimetype';
|
import { Mimetypes, Languages } from '@/global/mimetype';
|
||||||
import Process from './process/index.vue';
|
import Process from './process/index.vue';
|
||||||
import Detail from './detail/index.vue';
|
import Detail from './detail/index.vue';
|
||||||
|
import RecycleBin from './recycle-bin/index.vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { Back, Refresh } from '@element-plus/icons-vue';
|
import { Back, Refresh } from '@element-plus/icons-vue';
|
||||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||||
@ -288,6 +302,7 @@ const breadCrumbRef = ref();
|
|||||||
const chownRef = ref();
|
const chownRef = ref();
|
||||||
const moveOpen = ref(false);
|
const moveOpen = ref(false);
|
||||||
const deleteRef = ref();
|
const deleteRef = ref();
|
||||||
|
const recycleBinRef = ref();
|
||||||
|
|
||||||
// editablePath
|
// editablePath
|
||||||
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchable(paths);
|
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchable(paths);
|
||||||
@ -608,6 +623,10 @@ const openDetail = (row: File.File) => {
|
|||||||
detailRef.value.acceptParams({ path: row.path });
|
detailRef.value.acceptParams({ path: row.path });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openRecycleBin = () => {
|
||||||
|
recycleBinRef.value.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
const changeSort = ({ prop, order }) => {
|
const changeSort = ({ prop, order }) => {
|
||||||
req.sortBy = prop;
|
req.sortBy = prop;
|
||||||
req.sortOrder = order;
|
req.sortOrder = order;
|
||||||
@ -713,12 +732,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
|
||||||
display: inline;
|
|
||||||
width: 400px;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-button {
|
.copy-button {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
.close {
|
.close {
|
||||||
@ -728,4 +741,25 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section,
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section > *:not(:first-child) {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section > *:not(:last-child) {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
155
frontend/src/views/host/file-management/recycle-bin/index.vue
Normal file
155
frontend/src/views/host/file-management/recycle-bin/index.vue
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="open" :before-close="handleClose" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('file.recycleBin')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-button @click="clear" type="primary" :disabled="data == null || data.length == 0">
|
||||||
|
{{ $t('file.clearRecycleBin') }}
|
||||||
|
</el-button>
|
||||||
|
<ComplexTable
|
||||||
|
:pagination-config="paginationConfig"
|
||||||
|
v-model:selects="selects"
|
||||||
|
:data="data"
|
||||||
|
@search="search"
|
||||||
|
class="mt-5"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" fix />
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('commons.table.name')"
|
||||||
|
min-width="100"
|
||||||
|
fix
|
||||||
|
show-overflow-tooltip
|
||||||
|
prop="name"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column :label="$t('file.sourcePath')" show-overflow-tooltip prop="sourcePath"></el-table-column>
|
||||||
|
<el-table-column :label="$t('file.size')" prop="size" max-width="50">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getFileSize(row.size) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('file.deleteTime')"
|
||||||
|
prop="deleteTime"
|
||||||
|
:formatter="dateFormat"
|
||||||
|
show-overflow-tooltip
|
||||||
|
></el-table-column>
|
||||||
|
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||||
|
</ComplexTable>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { DeleteFile, clearRecycle, getRecycleList, reduceFile } from '@/api/modules/files';
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { dateFormat, computeSize } from '@/utils/util';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const req = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
});
|
||||||
|
const data = ref([]);
|
||||||
|
const em = defineEmits(['close']);
|
||||||
|
const selects = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const files = ref([]);
|
||||||
|
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
cacheSizeKey: 'recycle-page-size',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
open.value = false;
|
||||||
|
em('close', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileSize = (size: number) => {
|
||||||
|
return computeSize(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const acceptParams = () => {
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getRecycleList(req);
|
||||||
|
data.value = res.data.items;
|
||||||
|
paginationConfig.total = res.data.total;
|
||||||
|
open.value = true;
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const singleDel = (row: any) => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('commons.button.delete'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
}).then(async () => {
|
||||||
|
files.value = [];
|
||||||
|
files.value.push(row);
|
||||||
|
deleteFile();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFile = async () => {
|
||||||
|
const pros = [];
|
||||||
|
for (const s of files.value) {
|
||||||
|
pros.push(DeleteFile({ path: s.from + '/' + s.rName, isDir: s.isDir, forceDelete: true }));
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
Promise.all(pros)
|
||||||
|
.then(() => {
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const rdFile = async (row: any) => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('file.reduceHelper'), i18n.global.t('file.reduce'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await reduceFile({ from: row.from, rName: row.rName, name: row.name });
|
||||||
|
loading.value = false;
|
||||||
|
search();
|
||||||
|
} catch (error) {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = async () => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('file.clearRecycleBinHelper'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await clearRecycle();
|
||||||
|
loading.value = false;
|
||||||
|
search();
|
||||||
|
} catch (error) {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('file.reduce'),
|
||||||
|
click: rdFile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.delete'),
|
||||||
|
click: singleDel,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
defineExpose({ acceptParams });
|
||||||
|
</script>
|
Loading…
x
Reference in New Issue
Block a user