From 3b6bb9bd22c930dc81e57cfda46ab9f1c8d11def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=98=AD?= <81747598+lan-yonghui@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:11:06 +0800 Subject: [PATCH] feat: File management optimization (#8452) Refs: #3545 #3387 --- agent/app/api/v2/file.go | 28 + agent/app/api/v2/terminal.go | 3 +- agent/app/dto/response/file.go | 10 + agent/app/service/file.go | 105 ++++ agent/router/ro_file.go | 2 + frontend/src/api/interface/file.ts | 9 + frontend/src/api/modules/files.ts | 9 + .../src/components/complex-table/index.vue | 2 +- .../src/components/layout-content/index.vue | 6 + .../src/components/system-upgrade/index.vue | 41 +- .../host/file-management/batch-role/index.vue | 38 +- .../src/views/host/file-management/index.vue | 551 +++++++++++++----- .../host/file-management/terminal/index.vue | 47 ++ 13 files changed, 678 insertions(+), 173 deletions(-) create mode 100644 frontend/src/views/host/file-management/terminal/index.vue diff --git a/agent/app/api/v2/file.go b/agent/app/api/v2/file.go index 79271372a..d5938f6ac 100644 --- a/agent/app/api/v2/file.go +++ b/agent/app/api/v2/file.go @@ -842,3 +842,31 @@ func (b *BaseApi) GetPathByType(c *gin.Context) { resPath := fileService.GetPathByType(pathType) helper.SuccessWithData(c, resPath) } + +// @Tags File +// @Summary system mount +// @Accept json +// @Success 200 {object} dto.DiskInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /mount [post] +func (b *BaseApi) GetHostMount(c *gin.Context) { + disks := fileService.GetHostMount() + helper.SuccessWithData(c, disks) +} + +// @Tags File +// @Summary system user and group +// @Accept json +// @Success 200 {object} response.UserGroupResponse +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /user/group [post] +func (b *BaseApi) GetUsersAndGroups(c *gin.Context) { + res, err := fileService.GetUsersAndGroups() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/agent/app/api/v2/terminal.go b/agent/app/api/v2/terminal.go index f91e87530..e238c0f81 100644 --- a/agent/app/api/v2/terminal.go +++ b/agent/app/api/v2/terminal.go @@ -46,7 +46,8 @@ func (b *BaseApi) WsSSH(c *gin.Context) { return } defer client.Close() - sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, "") + command := c.DefaultQuery("command", "") + sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command) if wshandleError(wsConn, err) { return } diff --git a/agent/app/dto/response/file.go b/agent/app/dto/response/file.go index 8bf85b7f1..887037245 100644 --- a/agent/app/dto/response/file.go +++ b/agent/app/dto/response/file.go @@ -54,3 +54,13 @@ type ExistFileInfo struct { Size int64 `json:"size"` ModTime time.Time `json:"modTime"` } + +type UserInfo struct { + Username string `json:"username"` + Group string `json:"group"` +} + +type UserGroupResponse struct { + Users []UserInfo `json:"users"` + Groups []string `json:"groups"` +} diff --git a/agent/app/service/file.go b/agent/app/service/file.go index 4da9daca7..faf98baff 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -1,13 +1,18 @@ package service import ( + "bufio" "context" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" "io" "io/fs" "os" + "os/user" "path" "path/filepath" + "sort" + "strconv" "strings" "time" "unicode/utf8" @@ -55,6 +60,8 @@ type IFileService interface { GetPathByType(pathType string) string BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo + GetHostMount() []dto.DiskInfo + GetUsersAndGroups() (*response.UserGroupResponse, error) } var filteredPaths = []string{ @@ -544,3 +551,101 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi } return fileList } + +func (f *FileService) GetHostMount() []dto.DiskInfo { + return loadDiskInfo() +} + +func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) { + groupMap, err := getValidGroups() + if err != nil { + return nil, err + } + + users, groupSet, err := getValidUsers(groupMap) + if err != nil { + return nil, err + } + + var groups []string + for group := range groupSet { + groups = append(groups, group) + } + sort.Strings(groups) + + return &response.UserGroupResponse{ + Users: users, + Groups: groups, + }, nil +} + +func getValidGroups() (map[string]bool, error) { + groupFile, err := os.Open("/etc/group") + if err != nil { + return nil, fmt.Errorf("failed to open /etc/group: %w", err) + } + defer groupFile.Close() + + groupMap := make(map[string]bool) + scanner := bufio.NewScanner(groupFile) + for scanner.Scan() { + parts := strings.Split(scanner.Text(), ":") + if len(parts) < 3 { + continue + } + groupName := parts[0] + gid, _ := strconv.Atoi(parts[2]) + if groupName == "root" || gid >= 1000 { + groupMap[groupName] = true + } + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan /etc/group: %w", err) + } + return groupMap, nil +} + +func getValidUsers(validGroups map[string]bool) ([]response.UserInfo, map[string]struct{}, error) { + passwdFile, err := os.Open("/etc/passwd") + if err != nil { + return nil, nil, fmt.Errorf("failed to open /etc/passwd: %w", err) + } + defer passwdFile.Close() + + var users []response.UserInfo + groupSet := make(map[string]struct{}) + scanner := bufio.NewScanner(passwdFile) + for scanner.Scan() { + parts := strings.Split(scanner.Text(), ":") + if len(parts) < 4 { + continue + } + + username := parts[0] + uid, _ := strconv.Atoi(parts[2]) + gid := parts[3] + + if username != "root" && uid < 1000 { + continue + } + + groupName := gid + if g, err := user.LookupGroupId(gid); err == nil { + groupName = g.Name + } + + if !validGroups[groupName] { + continue + } + + users = append(users, response.UserInfo{ + Username: username, + Group: groupName, + }) + groupSet[groupName] = struct{}{} + } + if err := scanner.Err(); err != nil { + return nil, nil, fmt.Errorf("failed to scan /etc/passwd: %w", err) + } + return users, groupSet, nil +} diff --git a/agent/router/ro_file.go b/agent/router/ro_file.go index 8e24f0b95..1c5a8e1a2 100644 --- a/agent/router/ro_file.go +++ b/agent/router/ro_file.go @@ -49,5 +49,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) { fileRouter.POST("/favorite/del", baseApi.DeleteFavorite) fileRouter.GET("/path/:type", baseApi.GetPathByType) + fileRouter.POST("/mount", baseApi.GetHostMount) + fileRouter.POST("/user/group", baseApi.GetUsersAndGroups) } } diff --git a/frontend/src/api/interface/file.ts b/frontend/src/api/interface/file.ts index a2ff17eb0..f7c0cc5f8 100644 --- a/frontend/src/api/interface/file.ts +++ b/frontend/src/api/interface/file.ts @@ -206,4 +206,13 @@ export namespace File { group: string; sub: boolean; } + + export interface UserGroupResponse { + users: UserInfo[]; + groups: string[]; + } + export interface UserInfo { + username: string; + group: string; + } } diff --git a/frontend/src/api/modules/files.ts b/frontend/src/api/modules/files.ts index d4eff1027..6adaabfc2 100644 --- a/frontend/src/api/modules/files.ts +++ b/frontend/src/api/modules/files.ts @@ -4,6 +4,7 @@ import { AxiosRequestConfig } from 'axios'; import { ResPage } from '../interface'; import { TimeoutEnum } from '@/enums/http-enum'; import { ReqPage } from '@/api/interface'; +import { Dashboard } from '@/api/interface/dashboard'; export const getFilesList = (params: File.ReqFile) => { return http.post('files/search', params, TimeoutEnum.T_5M); @@ -144,3 +145,11 @@ export const getRecycleStatusByNode = (node: string) => { export const getPathByType = (pathType: string) => { return http.get(`files/path/${pathType}`); }; + +export const searchHostMount = () => { + return http.post(`/files/mount`); +}; + +export const searchUserGroup = () => { + return http.post(`/files/user/group`); +}; diff --git a/frontend/src/components/complex-table/index.vue b/frontend/src/components/complex-table/index.vue index 9f10ae52f..108abb081 100644 --- a/frontend/src/components/complex-table/index.vue +++ b/frontend/src/components/complex-table/index.vue @@ -30,7 +30,7 @@ :page-sizes="[5, 10, 20, 50, 100]" @size-change="sizeChange" @current-change="currentChange" - :small="mobile || paginationConfig.small" + :size="mobile || paginationConfig.small ? 'small' : 'default'" :layout="mobile ? 'total, prev, pager, next' : 'total, sizes, prev, pager, next, jumper'" /> diff --git a/frontend/src/components/layout-content/index.vue b/frontend/src/components/layout-content/index.vue index 9ca8323b5..ba38b78ba 100644 --- a/frontend/src/components/layout-content/index.vue +++ b/frontend/src/components/layout-content/index.vue @@ -109,6 +109,12 @@ const showBack = computed(() => { .el-button + .el-button { margin: 0 !important; } + .el-button-group > .el-button + .el-button { + margin-left: 0 !important; + } + .el-button-group > .el-button:not(:last-child) { + margin-right: -1px !important; + } } .content-container_form { diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue index 4635b1ee8..cc330b914 100644 --- a/frontend/src/components/system-upgrade/index.vue +++ b/frontend/src/components/system-upgrade/index.vue @@ -1,7 +1,7 @@