diff --git a/backend/app/dto/response/file.go b/backend/app/dto/response/file.go index b67447bd5b3f..2882608009db 100644 --- a/backend/app/dto/response/file.go +++ b/backend/app/dto/response/file.go @@ -15,10 +15,12 @@ type UploadInfo struct { } type FileTree struct { - ID string `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - Children []FileTree `json:"children"` + ID string `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + IsDir bool `json:"isDir"` + Extension string `json:"extension"` + Children []FileTree `json:"children"` } type DirSizeRes struct { diff --git a/backend/app/service/file.go b/backend/app/service/file.go index a73877b491c6..bdcb77e8fa4f 100644 --- a/backend/app/service/file.go +++ b/backend/app/service/file.go @@ -17,6 +17,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/constant" "golang.org/x/net/html/charset" + "golang.org/x/sys/unix" "golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/transform" @@ -51,6 +52,10 @@ type IFileService interface { ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) } +var filteredPaths = []string{ + "/.1panel_clash", +} + func NewIFileService() IFileService { return &FileService{} } @@ -100,27 +105,78 @@ func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (in func (f *FileService) GetFileTree(op request.FileOption) ([]response.FileTree, error) { var treeArray []response.FileTree + if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) { + return treeArray, nil + } info, err := files.NewFileInfo(op.FileOption) if err != nil { return nil, err } node := response.FileTree{ - ID: common.GetUuid(), - Name: info.Name, - Path: info.Path, - } - for _, v := range info.Items { - if v.IsDir { - node.Children = append(node.Children, response.FileTree{ - ID: common.GetUuid(), - Name: v.Name, - Path: v.Path, - }) - } + ID: common.GetUuid(), + Name: info.Name, + Path: info.Path, + IsDir: info.IsDir, + Extension: info.Extension, + } + err = f.buildFileTree(&node, info.Items, op, 2) + if err != nil { + return nil, err } return append(treeArray, node), nil } +func shouldFilterPath(path string) bool { + cleanedPath := filepath.Clean(path) + for _, filteredPath := range filteredPaths { + cleanedFilteredPath := filepath.Clean(filteredPath) + if cleanedFilteredPath == cleanedPath || strings.HasPrefix(cleanedPath, cleanedFilteredPath+"/") { + return true + } + } + return false +} + +// 递归构建文件树(只取当前目录以及当前目录下的第一层子节点) +func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error { + for _, v := range items { + if shouldFilterPath(v.Path) { + global.LOG.Info("File Tree: Skipping %s due to filter\n", v.Path) + continue + } + childNode := response.FileTree{ + ID: common.GetUuid(), + Name: v.Name, + Path: v.Path, + IsDir: v.IsDir, + Extension: v.Extension, + } + if level > 1 && v.IsDir { + if err := f.buildChildNode(&childNode, v, op, level); err != nil { + return err + } + } + + node.Children = append(node.Children, childNode) + } + return nil +} + +func (f *FileService) buildChildNode(childNode *response.FileTree, fileInfo *files.FileInfo, op request.FileOption, level int) error { + op.Path = fileInfo.Path + subInfo, err := files.NewFileInfo(op.FileOption) + if err != nil { + if os.IsPermission(err) || errors.Is(err, unix.EACCES) { + global.LOG.Info("File Tree: Skipping %s due to permission denied\n", fileInfo.Path) + return nil + } + global.LOG.Errorf("File Tree: Skipping %s due to error: %s\n", fileInfo.Path, err.Error()) + return nil + } + + return f.buildFileTree(childNode, subInfo.Items, op, level-1) +} + func (f *FileService) Create(op request.FileCreate) error { if files.IsInvalidChar(op.Path) { return buserr.New("ErrInvalidChar") diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index d6f35cdafcdd..dd9333bcdde6 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -93,6 +93,7 @@ const message = { user: 'User', title: 'Title', port: 'Port', + forward: 'Forward', protocol: 'Protocol', tableSetting: 'Table setting', refreshRate: 'Rate', @@ -139,6 +140,8 @@ const message = { remove: 'Remove', backupHelper: 'The current operation will back up {0}. Do you want to proceed?', recoverHelper: 'Restoring from {0} file. This operation is irreversible. Do you want to continue?', + refreshSuccess: 'Refresh successful', + rootInfoErr: "It's already the root directory", }, login: { username: 'UserName', @@ -1219,6 +1222,7 @@ const message = { refresh: 'Refresh', openWithVscode: 'Open with VS Code', vscodeHelper: 'Please make sure that VS Code is installed locally and the SSH Remote plugin is configured', + up: 'Go back', }, ssh: { autoStart: 'Auto Start', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index d4a04cdbb7bc..aba32842d210 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -92,6 +92,7 @@ const message = { user: '用戶', title: '標題', port: '端口', + forward: '轉發', protocol: '協議', tableSetting: '列表設置', refreshRate: '刷新頻率', @@ -139,6 +140,8 @@ const message = { remove: '移出', backupHelper: '當前操作將對 {0} 進行備份,是否繼續?', recoverHelper: '將從 {0} 文件進行恢復,該操作不可回滾,是否繼續?', + refreshSuccess: '重繪成功', + rootInfoErr: '已經是根目錄了', }, login: { username: '用戶名', @@ -1149,12 +1152,13 @@ const message = { '下載時忽略不可信證書可能導致數據洩露或篡改。請謹慎使用此選項,僅在信任下載源的情況下啟用', uploadOverLimit: '文件數量超過 1000! 請壓縮後上傳', clashDitNotSupport: '檔名禁止包含 .1panel_clash', - clashDleteAlert: '回收站資料夾不能刪除', + clashDeleteAlert: '回收站資料夾不能刪除', clashOpenAlert: '回收站目錄請點選【回收站】按鈕開啟', right: '前進', back: '後退', top: '返回上一層', refresh: '重新整理', + up: '上一層', openWithVscode: 'VS Code 打開', vscodeHelper: '請確保本地已安裝 VS Code 並配置了 SSH Remote 插件', }, diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f16581ec92ca..f51a7489d640 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -140,6 +140,8 @@ const message = { remove: '移出', backupHelper: '当前操作将对 {0} 进行备份,是否继续?', recoverHelper: '将从 {0} 文件进行恢复,该操作不可回滚,是否继续?', + refreshSuccess: '刷新成功', + rootInfoErr: '已经是根目录了', }, login: { username: '用户名', @@ -1151,12 +1153,13 @@ const message = { '下载时忽略不可信证书可能导致数据泄露或篡改。请谨慎使用此选项,仅在信任下载源的情况下启用', uploadOverLimit: '文件数量超过 1000!请压缩后上传', clashDitNotSupport: '文件名禁止包含 .1panel_clash', - clashDleteAlert: '回收站文件夹不能删除', + clashDeleteAlert: '回收站文件夹不能删除', clashOpenAlert: '回收站目录请点击【回收站】按钮打开', right: '前进', back: '后退', top: '返回上一级', refresh: '刷新', + up: '上一级', openWithVscode: 'VS Code 打开', vscodeHelper: '请确保本地已安装 VS Code 并配置了 SSH Remote 插件', }, diff --git a/frontend/src/views/host/file-management/code-editor/index.vue b/frontend/src/views/host/file-management/code-editor/index.vue index e855868c3cd0..8301962ca480 100644 --- a/frontend/src/views/host/file-management/code-editor/index.vue +++ b/frontend/src/views/host/file-management/code-editor/index.vue @@ -1,7 +1,7 @@