Browse Source

feat: 文件管理增加用户/用户组设置 (#803)

pull/804/head
zhengkunwang223 2 years ago committed by GitHub
parent
commit
7887bf96de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      backend/app/api/v1/file.go
  2. 2
      backend/app/api/v1/setting.go
  3. 7
      backend/app/dto/request/file.go
  4. 6
      backend/app/service/file.go
  5. 1
      backend/router/ro_file.go
  6. 31
      backend/utils/files/file_op.go
  7. 5
      backend/utils/files/fileinfo.go
  8. 18
      backend/utils/files/utils.go
  9. 177
      cmd/server/docs/docs.go
  10. 177
      cmd/server/docs/swagger.json
  11. 117
      cmd/server/docs/swagger.yaml
  12. 2
      frontend/package-lock.json
  13. 2
      frontend/package.json
  14. 7
      frontend/src/api/interface/file.ts
  15. 4
      frontend/src/api/modules/files.ts
  16. 4
      frontend/src/lang/modules/en.ts
  17. 3
      frontend/src/lang/modules/zh.ts
  18. 108
      frontend/src/views/host/file-management/chown/index.vue
  19. 20
      frontend/src/views/host/file-management/index.vue

24
backend/app/api/v1/file.go

@ -186,7 +186,29 @@ func (b *BaseApi) ChangeFileMode(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
helper.SuccessWithData(c, nil) helper.SuccessWithOutData(c)
}
// @Tags File
// @Summary Change file owner
// @Description 修改文件用户/组
// @Accept json
// @Param request body request.FileRoleUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/owner [post]
// @x-panel-log {"bodyKeys":["path","user","group"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改用户/组 [paths] => [user]/[group]","formatEN":"Change owner [paths] => [user]/[group]"}
func (b *BaseApi) ChangeFileOwner(c *gin.Context) {
var req request.FileRoleUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := fileService.ChangeOwner(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
} }
// @Tags File // @Tags File

2
backend/app/api/v1/setting.go

@ -127,7 +127,7 @@ func (b *BaseApi) UpdatePassword(c *gin.Context) {
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /settings/ssl/update [post] // @Router /settings/ssl/update [post]
// @x-panel-log {"bodyKeys":[ssl],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改系统 ssl => [ssl]","formatEN":"update system ssl => [ssl]"} // @x-panel-log {"bodyKeys":["ssl"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改系统 ssl => [ssl]","formatEN":"update system ssl => [ssl]"}
func (b *BaseApi) UpdateSSL(c *gin.Context) { func (b *BaseApi) UpdateSSL(c *gin.Context) {
var req dto.SSLUpdate var req dto.SSLUpdate
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {

7
backend/app/dto/request/file.go

@ -88,3 +88,10 @@ type DirSizeReq struct {
type FileProcessReq struct { type FileProcessReq struct {
Key string `json:"key"` Key string `json:"key"`
} }
type FileRoleUpdate struct {
Path string `json:"path" validate:"required"`
User string `json:"user" validate:"required"`
Group string `json:"group" validate:"required"`
Sub bool `json:"sub" validate:"required"`
}

6
backend/app/service/file.go

@ -39,6 +39,7 @@ type IFileService interface {
ChangeName(req request.FileRename) error ChangeName(req request.FileRename) error
Wget(w request.FileWget) (string, error) Wget(w request.FileWget) (string, error)
MvFile(m request.FileMove) error MvFile(m request.FileMove) error
ChangeOwner(req request.FileRoleUpdate) error
} }
func NewIFileService() IFileService { func NewIFileService() IFileService {
@ -162,6 +163,11 @@ func (f *FileService) ChangeMode(op request.FileCreate) error {
return fo.Chmod(op.Path, fs.FileMode(op.Mode)) return fo.Chmod(op.Path, fs.FileMode(op.Mode))
} }
func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error {
fo := files.NewFileOp()
return fo.ChownR(req.Path, req.User, req.Group, req.Sub)
}
func (f *FileService) Compress(c request.FileCompress) error { func (f *FileService) Compress(c request.FileCompress) error {
fo := files.NewFileOp() fo := files.NewFileOp()
if !c.Replace && fo.Stat(filepath.Join(c.Dst, c.Name)) { if !c.Replace && fo.Stat(filepath.Join(c.Dst, c.Name)) {

1
backend/router/ro_file.go

@ -21,6 +21,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
fileRouter.POST("/del", baseApi.DeleteFile) fileRouter.POST("/del", baseApi.DeleteFile)
fileRouter.POST("/batch/del", baseApi.BatchDeleteFile) fileRouter.POST("/batch/del", baseApi.BatchDeleteFile)
fileRouter.POST("/mode", baseApi.ChangeFileMode) fileRouter.POST("/mode", baseApi.ChangeFileMode)
fileRouter.POST("/owner", baseApi.ChangeFileOwner)
fileRouter.POST("/compress", baseApi.CompressFile) fileRouter.POST("/compress", baseApi.CompressFile)
fileRouter.POST("/decompress", baseApi.DeCompressFile) fileRouter.POST("/decompress", baseApi.DeCompressFile)
fileRouter.POST("/content", baseApi.GetContent) fileRouter.POST("/content", baseApi.GetContent)

31
backend/utils/files/file_op.go

@ -6,6 +6,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"io" "io"
"io/fs" "io/fs"
"net/http" "net/http"
@ -106,7 +107,7 @@ func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
} }
defer file.Close() defer file.Close()
write := bufio.NewWriter(file) write := bufio.NewWriter(file)
_, _ = write.WriteString(string(content)) _, _ = write.WriteString(content)
write.Flush() write.Flush()
return nil return nil
} }
@ -119,6 +120,34 @@ func (f FileOp) Chown(dst string, uid int, gid int) error {
return f.Fs.Chown(dst, uid, gid) return f.Fs.Chown(dst, uid, gid)
} }
func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
cmdStr := fmt.Sprintf("sudo chown %s:%s %s", uid, gid, dst)
if sub {
cmdStr = fmt.Sprintf("sudo chown -R %s:%s %s", uid, gid, dst)
}
if msg, err := cmd.ExecWithTimeOut(cmdStr, 2*time.Second); err != nil {
if msg != "" {
return errors.New(msg)
}
return err
}
return nil
}
func (f FileOp) ChmodR(dst string, mode fs.FileMode) error {
cmdStr := fmt.Sprintf("chmod -R %v %s", mode, dst)
if cmd.HasNoPasswordSudo() {
cmdStr = fmt.Sprintf("sudo %s", cmdStr)
}
if msg, err := cmd.ExecWithTimeOut(cmdStr, 2*time.Second); err != nil {
if msg != "" {
return errors.New(msg)
}
return err
}
return nil
}
func (f FileOp) Rename(oldName string, newName string) error { func (f FileOp) Rename(oldName string, newName string) error {
return f.Fs.Rename(oldName, newName) return f.Fs.Rename(oldName, newName)
} }

5
backend/utils/files/fileinfo.go

@ -9,6 +9,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall"
"time" "time"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -73,6 +74,8 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
Extension: filepath.Ext(info.Name()), Extension: filepath.Ext(info.Name()),
IsHidden: IsHidden(op.Path), IsHidden: IsHidden(op.Path),
Mode: fmt.Sprintf("%04o", info.Mode().Perm()), Mode: fmt.Sprintf("%04o", info.Mode().Perm()),
User: GetUsername(info.Sys().(*syscall.Stat_t).Uid),
Group: GetGroup(info.Sys().(*syscall.Stat_t).Gid),
MimeType: GetMimeType(op.Path), MimeType: GetMimeType(op.Path),
} }
if file.IsSymlink { if file.IsSymlink {
@ -202,6 +205,8 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
Path: fPath, Path: fPath,
Mode: fmt.Sprintf("%04o", df.Mode().Perm()), Mode: fmt.Sprintf("%04o", df.Mode().Perm()),
MimeType: GetMimeType(fPath), MimeType: GetMimeType(fPath),
User: GetUsername(df.Sys().(*syscall.Stat_t).Uid),
Group: GetGroup(df.Sys().(*syscall.Stat_t).Gid),
} }
if isSymlink { if isSymlink {

18
backend/utils/files/utils.go

@ -4,7 +4,9 @@ import (
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
"github.com/spf13/afero" "github.com/spf13/afero"
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"strconv"
"sync" "sync"
) )
@ -28,6 +30,22 @@ func GetSymlink(path string) string {
return linkPath return linkPath
} }
func GetUsername(uid uint32) string {
usr, err := user.LookupId(strconv.Itoa(int(uid)))
if err != nil {
return ""
}
return usr.Username
}
func GetGroup(gid uint32) string {
usr, err := user.LookupGroupId(strconv.Itoa(int(gid)))
if err != nil {
return ""
}
return usr.Name
}
func ScanDir(fs afero.Fs, path string, dirMap *sync.Map, wg *sync.WaitGroup) { func ScanDir(fs afero.Fs, path string, dirMap *sync.Map, wg *sync.WaitGroup) {
afs := &afero.Afero{Fs: fs} afs := &afero.Afero{Fs: fs}
files, _ := afs.ReadDir(path) files, _ := afs.ReadDir(path)

177
cmd/server/docs/docs.go

@ -4529,6 +4529,50 @@ const docTemplate = `{
} }
} }
}, },
"/files/owner": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改文件用户/组",
"consumes": [
"application/json"
],
"tags": [
"File"
],
"summary": "Change file owner",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.FileRoleUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"path",
"user",
"group"
],
"formatEN": "Change owner [paths] =\u003e [user]/[group]",
"formatZH": "修改用户/组 [paths] =\u003e [user]/[group]",
"paramKeys": []
}
}
},
"/files/rename": { "/files/rename": {
"post": { "post": {
"security": [ "security": [
@ -7431,6 +7475,70 @@ const docTemplate = `{
} }
} }
}, },
"/settings/ssl/info": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取证书信息",
"tags": [
"System Setting"
],
"summary": "Load system cert info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SettingInfo"
}
}
}
}
},
"/settings/ssl/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统 ssl 登录",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Update system ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SSLUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"ssl"
],
"formatEN": "update system ssl =\u003e [ssl]",
"formatZH": "修改系统 ssl =\u003e [ssl]",
"paramKeys": []
}
}
},
"/settings/time/sync": { "/settings/time/sync": {
"post": { "post": {
"security": [ "security": [
@ -11440,10 +11548,16 @@ const docTemplate = `{
"type": "object", "type": "object",
"properties": { "properties": {
"containerPort": { "containerPort": {
"type": "integer" "type": "string"
},
"hostIP": {
"type": "string"
}, },
"hostPort": { "hostPort": {
"type": "integer" "type": "string"
},
"protocol": {
"type": "string"
} }
} }
}, },
@ -11669,6 +11783,36 @@ const docTemplate = `{
} }
} }
}, },
"dto.SSLUpdate": {
"type": "object",
"required": [
"ssl"
],
"properties": {
"cert": {
"type": "string"
},
"domain": {
"type": "string"
},
"key": {
"type": "string"
},
"ssl": {
"type": "string",
"enum": [
"enable",
"disable"
]
},
"sslID": {
"type": "integer"
},
"sslType": {
"type": "string"
}
}
},
"dto.SearchForTree": { "dto.SearchForTree": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -11851,6 +11995,12 @@ const docTemplate = `{
"sessionTimeout": { "sessionTimeout": {
"type": "string" "type": "string"
}, },
"ssl": {
"type": "string"
},
"sslType": {
"type": "string"
},
"systemVersion": { "systemVersion": {
"type": "string" "type": "string"
}, },
@ -12796,6 +12946,29 @@ const docTemplate = `{
} }
} }
}, },
"request.FileRoleUpdate": {
"type": "object",
"required": [
"group",
"path",
"sub",
"user"
],
"properties": {
"group": {
"type": "string"
},
"path": {
"type": "string"
},
"sub": {
"type": "boolean"
},
"user": {
"type": "string"
}
}
},
"request.FileWget": { "request.FileWget": {
"type": "object", "type": "object",
"required": [ "required": [

177
cmd/server/docs/swagger.json

@ -4522,6 +4522,50 @@
} }
} }
}, },
"/files/owner": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改文件用户/组",
"consumes": [
"application/json"
],
"tags": [
"File"
],
"summary": "Change file owner",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.FileRoleUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"path",
"user",
"group"
],
"formatEN": "Change owner [paths] =\u003e [user]/[group]",
"formatZH": "修改用户/组 [paths] =\u003e [user]/[group]",
"paramKeys": []
}
}
},
"/files/rename": { "/files/rename": {
"post": { "post": {
"security": [ "security": [
@ -7424,6 +7468,70 @@
} }
} }
}, },
"/settings/ssl/info": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取证书信息",
"tags": [
"System Setting"
],
"summary": "Load system cert info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SettingInfo"
}
}
}
}
},
"/settings/ssl/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统 ssl 登录",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Update system ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SSLUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"ssl"
],
"formatEN": "update system ssl =\u003e [ssl]",
"formatZH": "修改系统 ssl =\u003e [ssl]",
"paramKeys": []
}
}
},
"/settings/time/sync": { "/settings/time/sync": {
"post": { "post": {
"security": [ "security": [
@ -11433,10 +11541,16 @@
"type": "object", "type": "object",
"properties": { "properties": {
"containerPort": { "containerPort": {
"type": "integer" "type": "string"
},
"hostIP": {
"type": "string"
}, },
"hostPort": { "hostPort": {
"type": "integer" "type": "string"
},
"protocol": {
"type": "string"
} }
} }
}, },
@ -11662,6 +11776,36 @@
} }
} }
}, },
"dto.SSLUpdate": {
"type": "object",
"required": [
"ssl"
],
"properties": {
"cert": {
"type": "string"
},
"domain": {
"type": "string"
},
"key": {
"type": "string"
},
"ssl": {
"type": "string",
"enum": [
"enable",
"disable"
]
},
"sslID": {
"type": "integer"
},
"sslType": {
"type": "string"
}
}
},
"dto.SearchForTree": { "dto.SearchForTree": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -11844,6 +11988,12 @@
"sessionTimeout": { "sessionTimeout": {
"type": "string" "type": "string"
}, },
"ssl": {
"type": "string"
},
"sslType": {
"type": "string"
},
"systemVersion": { "systemVersion": {
"type": "string" "type": "string"
}, },
@ -12789,6 +12939,29 @@
} }
} }
}, },
"request.FileRoleUpdate": {
"type": "object",
"required": [
"group",
"path",
"sub",
"user"
],
"properties": {
"group": {
"type": "string"
},
"path": {
"type": "string"
},
"sub": {
"type": "boolean"
},
"user": {
"type": "string"
}
}
},
"request.FileWget": { "request.FileWget": {
"type": "object", "type": "object",
"required": [ "required": [

117
cmd/server/docs/swagger.yaml

@ -1201,9 +1201,13 @@ definitions:
dto.PortHelper: dto.PortHelper:
properties: properties:
containerPort: containerPort:
type: integer type: string
hostIP:
type: string
hostPort: hostPort:
type: integer type: string
protocol:
type: string
type: object type: object
dto.PortRuleOperate: dto.PortRuleOperate:
properties: properties:
@ -1354,6 +1358,26 @@ definitions:
used_memory_rss: used_memory_rss:
type: string type: string
type: object type: object
dto.SSLUpdate:
properties:
cert:
type: string
domain:
type: string
key:
type: string
ssl:
enum:
- enable
- disable
type: string
sslID:
type: integer
sslType:
type: string
required:
- ssl
type: object
dto.SearchForTree: dto.SearchForTree:
properties: properties:
info: info:
@ -1475,6 +1499,10 @@ definitions:
type: string type: string
sessionTimeout: sessionTimeout:
type: string type: string
ssl:
type: string
sslType:
type: string
systemVersion: systemVersion:
type: string type: string
theme: theme:
@ -2101,6 +2129,22 @@ definitions:
- newName - newName
- oldName - oldName
type: object type: object
request.FileRoleUpdate:
properties:
group:
type: string
path:
type: string
sub:
type: boolean
user:
type: string
required:
- group
- path
- sub
- user
type: object
request.FileWget: request.FileWget:
properties: properties:
name: name:
@ -5899,6 +5943,35 @@ paths:
formatEN: Move [oldPaths] => [newPath] formatEN: Move [oldPaths] => [newPath]
formatZH: 移动文件 [oldPaths] => [newPath] formatZH: 移动文件 [oldPaths] => [newPath]
paramKeys: [] paramKeys: []
/files/owner:
post:
consumes:
- application/json
description: 修改文件用户/组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.FileRoleUpdate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Change file owner
tags:
- File
x-panel-log:
BeforeFuntions: []
bodyKeys:
- path
- user
- group
formatEN: Change owner [paths] => [user]/[group]
formatZH: 修改用户/组 [paths] => [user]/[group]
paramKeys: []
/files/rename: /files/rename:
post: post:
consumes: consumes:
@ -7742,6 +7815,46 @@ paths:
summary: Page system snapshot summary: Page system snapshot
tags: tags:
- System Setting - System Setting
/settings/ssl/info:
get:
description: 获取证书信息
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SettingInfo'
security:
- ApiKeyAuth: []
summary: Load system cert info
tags:
- System Setting
/settings/ssl/update:
post:
consumes:
- application/json
description: 修改系统 ssl 登录
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SSLUpdate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update system ssl
tags:
- System Setting
x-panel-log:
BeforeFuntions: []
bodyKeys:
- ssl
formatEN: update system ssl => [ssl]
formatZH: 修改系统 ssl => [ssl]
paramKeys: []
/settings/time/sync: /settings/time/sync:
post: post:
description: 系统时间同步 description: 系统时间同步

2
frontend/package-lock.json generated

@ -17,7 +17,7 @@
"axios": "^0.27.2", "axios": "^0.27.2",
"echarts": "^5.3.0", "echarts": "^5.3.0",
"echarts-liquidfill": "^3.1.0", "echarts-liquidfill": "^3.1.0",
"element-plus": "^2.2.32", "element-plus": "^2.3.4",
"fit2cloud-ui-plus": "^1.0.7", "fit2cloud-ui-plus": "^1.0.7",
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"js-md5": "^0.7.3", "js-md5": "^0.7.3",

2
frontend/package.json

@ -29,7 +29,7 @@
"axios": "^0.27.2", "axios": "^0.27.2",
"echarts": "^5.3.0", "echarts": "^5.3.0",
"echarts-liquidfill": "^3.1.0", "echarts-liquidfill": "^3.1.0",
"element-plus": "^2.2.32", "element-plus": "^2.3.4",
"fit2cloud-ui-plus": "^1.0.7", "fit2cloud-ui-plus": "^1.0.7",
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"js-md5": "^0.7.3", "js-md5": "^0.7.3",

7
frontend/src/api/interface/file.ts

@ -90,6 +90,13 @@ export namespace File {
newName: string; newName: string;
} }
export interface FileOwner {
path: string;
user: string;
group: string;
sub: boolean;
}
export interface FileWget { export interface FileWget {
path: string; path: string;
name: string; name: string;

4
frontend/src/api/modules/files.ts

@ -67,6 +67,10 @@ export const RenameRile = (params: File.FileRename) => {
return http.post<File.File>('files/rename', params); return http.post<File.File>('files/rename', params);
}; };
export const ChangeOwner = (params: File.FileOwner) => {
return http.post<File.File>('files/owner', params);
};
export const WgetFile = (params: File.FileWget) => { export const WgetFile = (params: File.FileWget) => {
return http.post<File.FileWgetRes>('files/wget', params); return http.post<File.FileWgetRes>('files/wget', params);
}; };

4
frontend/src/lang/modules/en.ts

@ -800,6 +800,10 @@ const message = {
copyDir: 'Copy Dir', copyDir: 'Copy Dir',
paste: 'Paste', paste: 'Paste',
cancel: 'Cancel', cancel: 'Cancel',
changeOwner: 'Modify user and user group',
containSub: 'Modify sub-file attributes at the same time',
ownerHelper:
'The default user of the PHP operating environment: the user group is 1000:1000, it is normal that the users inside and outside the container show inconsistencies',
}, },
setting: { setting: {
all: 'All', all: 'All',

3
frontend/src/lang/modules/zh.ts

@ -807,6 +807,9 @@ const message = {
copyDir: '复制路径', copyDir: '复制路径',
paste: '粘贴', paste: '粘贴',
cancel: '取消', cancel: '取消',
changeOwner: '修改用户和用户组',
containSub: '同时修改子文件属性',
ownerHelper: 'PHP 运行环境默认用户:用户组为 1000:1000, 容器内外用户显示不一致为正常现象',
}, },
setting: { setting: {
all: '全部', all: '全部',

108
frontend/src/views/host/file-management/chown/index.vue

@ -0,0 +1,108 @@
<template>
<el-drawer v-model="open" size="40%">
<template #header>
<DrawerHeader :header="$t('file.changeOwner')" :back="handleClose" />
</template>
<el-row>
<el-col :span="22" :offset="1">
<el-form
ref="fileForm"
label-position="top"
:model="addForm"
label-width="100px"
:rules="rules"
v-loading="loading"
>
<el-form-item :label="$t('file.user')" prop="user">
<el-input v-model.trim="addForm.user" />
</el-form-item>
<el-form-item :label="$t('file.group')" prop="group">
<el-input v-model.trim="addForm.group" />
</el-form-item>
<el-form-item v-if="isDir">
<el-checkbox v-model="addForm.sub">{{ $t('file.containSub') }}</el-checkbox>
</el-form-item>
<el-form-item>
<el-alert :title="$t('file.ownerHelper')" type="info" :closable="false" />
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit(fileForm)">{{ $t('commons.button.confirm') }}</el-button>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { ChangeOwner } from '@/api/modules/files';
import { Rules } from '@/global/form-rules';
import { FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
interface OwnerProps {
path: string;
user: string;
group: string;
isDir: boolean;
}
const fileForm = ref<FormInstance>();
const loading = ref(false);
const open = ref(false);
const isDir = ref(false);
const addForm = reactive({
path: '',
user: '',
group: '',
sub: false,
});
const rules = reactive<FormRules>({
user: [Rules.requiredInput],
group: [Rules.requiredInput],
});
const em = defineEmits(['close']);
const handleClose = () => {
open.value = false;
if (fileForm.value) {
fileForm.value.resetFields();
}
em('close', false);
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
ChangeOwner(addForm)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
});
};
const acceptParams = (props: OwnerProps) => {
addForm.user = props.user;
addForm.path = props.path;
addForm.group = props.group;
isDir.value = props.isDir;
open.value = true;
};
defineExpose({ acceptParams });
</script>

20
frontend/src/views/host/file-management/index.vue

@ -123,8 +123,16 @@
<el-link :underline="false" @click="openMode(row)" type="primary">{{ row.mode }}</el-link> <el-link :underline="false" @click="openMode(row)" type="primary">{{ row.mode }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column :label="$t('file.user')" prop="user" show-overflow-tooltip></el-table-column> <el-table-column :label="$t('file.user')" prop="user" show-overflow-tooltip>
<el-table-column :label="$t('file.group')" prop="group"></el-table-column> --> <template #default="{ row }">
<el-link :underline="false" @click="openChown(row)" type="primary">{{ row.user }}</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('file.group')" prop="group">
<template #default="{ row }">
<el-link :underline="false" @click="openChown(row)" type="primary">{{ row.group }}</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('file.size')" prop="size" max-width="50"> <el-table-column :label="$t('file.size')" prop="size" max-width="50">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.isDir"> <span v-if="row.isDir">
@ -167,6 +175,7 @@
<Move ref="moveRef" @close="closeMovePage" /> <Move ref="moveRef" @close="closeMovePage" />
<Download ref="downloadRef" @close="search" /> <Download ref="downloadRef" @close="search" />
<Process :open="processPage.open" @close="closeProcess" /> <Process :open="processPage.open" @close="closeProcess" />
<Owner ref="chownRef" @close="search"></Owner>
<!-- <Detail ref="detailRef" /> --> <!-- <Detail ref="detailRef" /> -->
</LayoutContent> </LayoutContent>
</div> </div>
@ -191,6 +200,7 @@ import CodeEditor from './code-editor/index.vue';
import Wget from './wget/index.vue'; 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 { 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';
@ -251,7 +261,7 @@ const moveRef = ref();
const downloadRef = ref(); const downloadRef = ref();
const pathRef = ref(); const pathRef = ref();
const breadCrumbRef = ref(); const breadCrumbRef = ref();
const chownRef = ref();
const moveOpen = ref(false); const moveOpen = ref(false);
// editablePath // editablePath
@ -443,6 +453,10 @@ const openMode = (item: File.File) => {
roleRef.value.acceptParams(item); roleRef.value.acceptParams(item);
}; };
const openChown = (item: File.File) => {
chownRef.value.acceptParams(item);
};
const openCompress = (items: File.File[]) => { const openCompress = (items: File.File[]) => {
const paths = []; const paths = [];
for (const item of items) { for (const item of items) {

Loading…
Cancel
Save