From 65f92bf0c37ac46c2fda23460c850aa4b17b9932 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Mon, 20 May 2024 18:48:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20FTP=20=E5=A2=9E=E5=8A=A0=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=8F=8A=E6=97=A5=E5=BF=97=20(#5065)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/ftp.go | 64 ++++ backend/app/dto/ftp.go | 15 +- backend/app/service/ftp.go | 43 +++ backend/router/ro_toolbox.go | 3 + backend/utils/toolbox/fail2ban.go | 2 +- backend/utils/toolbox/pure-ftpd.go | 118 ++++++- cmd/server/docs/docs.go | 128 +++++++ cmd/server/docs/swagger.json | 128 +++++++ cmd/server/docs/swagger.yaml | 81 +++++ frontend/src/api/interface/toolbox.ts | 18 + frontend/src/api/modules/toolbox.ts | 10 +- frontend/src/lang/modules/en.ts | 2 + frontend/src/lang/modules/tw.ts | 2 + frontend/src/lang/modules/zh.ts | 2 + frontend/src/utils/util.ts | 8 + frontend/src/views/toolbox/ftp/index.vue | 332 ++++++++++++------- frontend/src/views/toolbox/ftp/log/index.vue | 119 +++++++ 17 files changed, 953 insertions(+), 122 deletions(-) create mode 100644 frontend/src/views/toolbox/ftp/log/index.vue diff --git a/backend/app/api/v1/ftp.go b/backend/app/api/v1/ftp.go index 15630ead1..618395f98 100644 --- a/backend/app/api/v1/ftp.go +++ b/backend/app/api/v1/ftp.go @@ -9,6 +9,70 @@ import ( "github.com/gin-gonic/gin" ) +// @Tags FTP +// @Summary Load FTP base info +// @Description 获取 FTP 基础信息 +// @Success 200 {object} dto.FtpBaseInfo +// @Security ApiKeyAuth +// @Router /toolbox/ftp/base [get] +func (b *BaseApi) LoadFtpBaseInfo(c *gin.Context) { + data, err := ftpService.LoadBaseInfo() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags FTP +// @Summary Load FTP operation log +// @Description 获取 FTP 操作日志 +// @Accept json +// @Param request body dto.FtpLogSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Router /toolbox/ftp/log/search [post] +func (b *BaseApi) LoadFtpLogInfo(c *gin.Context) { + var req dto.FtpLogSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := ftpService.LoadLog(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags FTP +// @Summary Operate FTP +// @Description 修改 FTP 状态 +// @Accept json +// @Param request body dto.Operate true "request" +// @Security ApiKeyAuth +// @Router /toolbox/ftp/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] FTP","formatEN":"[operation] FTP"} +func (b *BaseApi) OperateFtp(c *gin.Context) { + var req dto.Operate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := ftpService.Operate(req.Operation); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + // @Tags FTP // @Summary Page FTP user // @Description 获取 FTP 账户列表分页 diff --git a/backend/app/dto/ftp.go b/backend/app/dto/ftp.go index 862e66aa9..fad9376d0 100644 --- a/backend/app/dto/ftp.go +++ b/backend/app/dto/ftp.go @@ -1,6 +1,8 @@ package dto -import "time" +import ( + "time" +) type FtpInfo struct { ID uint `json:"id"` @@ -13,6 +15,17 @@ type FtpInfo struct { Description string `json:"description"` } +type FtpBaseInfo struct { + IsActive bool `json:"isActive"` + IsExist bool `json:"isExist"` +} + +type FtpLogSearch struct { + PageInfo + User string `json:"user"` + Operation string `json:"operation"` +} + type FtpCreate struct { User string `json:"user" validate:"required"` Password string `json:"password" validate:"required"` diff --git a/backend/app/service/ftp.go b/backend/app/service/ftp.go index 1b8ac2bf3..4e95e923b 100644 --- a/backend/app/service/ftp.go +++ b/backend/app/service/ftp.go @@ -13,17 +13,60 @@ import ( type FtpService struct{} type IFtpService interface { + LoadBaseInfo() (dto.FtpBaseInfo, error) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) + Operate(operation string) error Create(req dto.FtpCreate) error Delete(req dto.BatchDeleteReq) error Update(req dto.FtpUpdate) error Sync() error + LoadLog(req dto.FtpLogSearch) (int64, interface{}, error) } func NewIFtpService() IFtpService { return &FtpService{} } +func (f *FtpService) LoadBaseInfo() (dto.FtpBaseInfo, error) { + var baseInfo dto.FtpBaseInfo + client, err := toolbox.NewFtpClient() + if err != nil { + return baseInfo, err + } + baseInfo.IsActive, baseInfo.IsExist = client.Status() + return baseInfo, nil +} + +func (f *FtpService) LoadLog(req dto.FtpLogSearch) (int64, interface{}, error) { + client, err := toolbox.NewFtpClient() + if err != nil { + return 0, nil, err + } + logItem, err := client.LoadLogs(req.User, req.Operation) + if err != nil { + return 0, nil, err + } + var logs []toolbox.FtpLog + total, start, end := len(logItem), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + logs = make([]toolbox.FtpLog, 0) + } else { + if end >= total { + end = total + } + logs = logItem[start:end] + } + return int64(total), logs, nil +} + +func (u *FtpService) Operate(operation string) error { + client, err := toolbox.NewFtpClient() + if err != nil { + return err + } + return client.Operate(operation) +} + func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) { total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithByUser(req.Info), commonRepo.WithOrderBy("created_at desc")) if err != nil { diff --git a/backend/router/ro_toolbox.go b/backend/router/ro_toolbox.go index 4c70a8619..a35dd1b11 100644 --- a/backend/router/ro_toolbox.go +++ b/backend/router/ro_toolbox.go @@ -37,6 +37,9 @@ func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) { toolboxRouter.POST("/fail2ban/update", baseApi.UpdateFail2BanConf) toolboxRouter.POST("/fail2ban/update/byconf", baseApi.UpdateFail2BanConfByFile) + toolboxRouter.GET("/ftp/base", baseApi.LoadFtpBaseInfo) + toolboxRouter.POST("/ftp/log/search", baseApi.LoadFtpLogInfo) + toolboxRouter.POST("/ftp/operate", baseApi.OperateFtp) toolboxRouter.POST("/ftp/search", baseApi.SearchFtp) toolboxRouter.POST("/ftp", baseApi.CreateFtp) toolboxRouter.POST("/ftp/update", baseApi.UpdateFtp) diff --git a/backend/utils/toolbox/fail2ban.go b/backend/utils/toolbox/fail2ban.go index a745f6327..0a5e978d5 100644 --- a/backend/utils/toolbox/fail2ban.go +++ b/backend/utils/toolbox/fail2ban.go @@ -15,7 +15,7 @@ type Fail2ban struct{} const defaultPath = "/etc/fail2ban/jail.local" type FirewallClient interface { - Status() (bool, bool, bool, error) + Status() (bool, bool, bool) Version() (string, error) Operate(operate string) error OperateSSHD(operate, ip string) error diff --git a/backend/utils/toolbox/pure-ftpd.go b/backend/utils/toolbox/pure-ftpd.go index 44b46d31d..b864a108e 100644 --- a/backend/utils/toolbox/pure-ftpd.go +++ b/backend/utils/toolbox/pure-ftpd.go @@ -2,8 +2,13 @@ package toolbox import ( "errors" + "fmt" + "os" "os/user" + "path" + "path/filepath" "strings" + "time" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/utils/cmd" @@ -15,12 +20,14 @@ type Ftp struct { } type FtpClient interface { - Status() (bool, error) + Status() (bool, bool) + Operate(operate string) error LoadList() ([]FtpList, error) UserAdd(username, path, passwd string) error UserDel(username string) error SetPasswd(username, passwd string) error Reload() error + LoadLogs() ([]FtpLog, error) } func NewFtpClient() (*Ftp, error) { @@ -54,8 +61,24 @@ func NewFtpClient() (*Ftp, error) { return &Ftp{DefaultUser: "1panel"}, nil } -func (f *Ftp) Status() (bool, error) { - return systemctl.IsActive("pure-ftpd.service") +func (f *Ftp) Status() (bool, bool) { + isActive, _ := systemctl.IsActive("pure-ftpd.service") + isExist, _ := systemctl.IsExist("pure-ftpd.service") + + return isActive, isExist +} + +func (f *Ftp) Operate(operate string) error { + switch operate { + case "start", "restart", "stop": + stdout, err := cmd.Execf("systemctl %s pure-ftpd.service", operate) + if err != nil { + return fmt.Errorf("%s the pure-ftpd.service failed, err: %s", operate, stdout) + } + return nil + default: + return fmt.Errorf("not support such operation: %v", operate) + } } func (f *Ftp) UserAdd(username, passwd, path string) error { @@ -141,3 +164,92 @@ func (f *Ftp) Reload() error { } return nil } + +func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) { + var logs []FtpLog + logItem := "" + if _, err := os.Stat("/etc/pure-ftpd/conf"); err != nil && os.IsNotExist(err) { + std, err := cmd.Exec("cat /etc/pure-ftpd/pure-ftpd.conf | grep AltLog | grep clf:") + if err != nil { + return logs, err + } + logItem = std + } else { + if err != nil { + return logs, err + } + std, err := cmd.Exec("cat /etc/pure-ftpd/conf/AltLog") + if err != nil { + return nil, err + } + logItem = std + } + + logItem = strings.ReplaceAll(logItem, "AltLog", "") + logItem = strings.ReplaceAll(logItem, "clf:", "") + logItem = strings.ReplaceAll(logItem, "\n", "") + logPath := strings.Trim(logItem, " ") + + fileName := path.Base(logPath) + var fileList []string + if err := filepath.Walk(path.Dir(logPath), func(pathItem string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && strings.HasPrefix(info.Name(), fileName) { + fileList = append(fileList, pathItem) + } + return nil + }); err != nil { + return nil, err + } + logs = loadLogsByFiles(fileList, user, operation) + return logs, nil +} + +func loadLogsByFiles(fileList []string, user, operation string) []FtpLog { + var logs []FtpLog + layout := "02/Jan/2006:15:04:05-0700" + for _, file := range fileList { + data, err := os.ReadFile(file) + if err != nil { + continue + } + lines := strings.Split(string(data), "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 9 { + continue + } + if (len(user) != 0 && parts[2] != user) || (len(operation) != 0 && parts[5] != fmt.Sprintf("\"%s", operation)) { + continue + } + timeStr := parts[3] + parts[4] + timeStr = strings.ReplaceAll(timeStr, "[", "") + timeStr = strings.ReplaceAll(timeStr, "]", "") + timeItem, err := time.Parse(layout, timeStr) + if err == nil { + timeStr = timeItem.Format("2006-01-02 15:04:05") + } + operateStr := parts[5] + parts[6] + logs = append(logs, FtpLog{ + IP: parts[0], + User: parts[2], + Time: timeStr, + Operation: operateStr, + Status: parts[7], + Size: parts[8], + }) + } + } + return logs +} + +type FtpLog struct { + IP string `json:"ip"` + User string `json:"user"` + Time string `json:"time"` + Operation string `json:"operation"` + Status string `json:"status"` + Size string `json:"size"` +} diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 36f83a0b2..164d718ec 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -11555,6 +11555,28 @@ const docTemplate = `{ } } }, + "/toolbox/ftp/base": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 FTP 基础信息", + "tags": [ + "FTP" + ], + "summary": "Load FTP base info", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FtpBaseInfo" + } + } + } + } + }, "/toolbox/ftp/del": { "post": { "security": [ @@ -11606,6 +11628,80 @@ const docTemplate = `{ } } }, + "/toolbox/ftp/log/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 FTP 操作日志", + "consumes": [ + "application/json" + ], + "tags": [ + "FTP" + ], + "summary": "Load FTP operation log", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpLogSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + } + } + }, + "/toolbox/ftp/operate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "修改 FTP 状态", + "consumes": [ + "application/json" + ], + "tags": [ + "FTP" + ], + "summary": "Operate FTP", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": {}, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] FTP", + "formatZH": "[operation] FTP", + "paramKeys": [] + } + } + }, "/toolbox/ftp/search": { "post": { "security": [ @@ -16182,6 +16278,17 @@ const docTemplate = `{ } } }, + "dto.FtpBaseInfo": { + "type": "object", + "properties": { + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + } + } + }, "dto.FtpCreate": { "type": "object", "required": [ @@ -16204,6 +16311,27 @@ const docTemplate = `{ } } }, + "dto.FtpLogSearch": { + "type": "object", + "required": [ + "page", + "pageSize" + ], + "properties": { + "operation": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "user": { + "type": "string" + } + } + }, "dto.FtpUpdate": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 9e89b1f8d..a766883e5 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -11548,6 +11548,28 @@ } } }, + "/toolbox/ftp/base": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 FTP 基础信息", + "tags": [ + "FTP" + ], + "summary": "Load FTP base info", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FtpBaseInfo" + } + } + } + } + }, "/toolbox/ftp/del": { "post": { "security": [ @@ -11599,6 +11621,80 @@ } } }, + "/toolbox/ftp/log/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 FTP 操作日志", + "consumes": [ + "application/json" + ], + "tags": [ + "FTP" + ], + "summary": "Load FTP operation log", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpLogSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + } + } + }, + "/toolbox/ftp/operate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "修改 FTP 状态", + "consumes": [ + "application/json" + ], + "tags": [ + "FTP" + ], + "summary": "Operate FTP", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": {}, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] FTP", + "formatZH": "[operation] FTP", + "paramKeys": [] + } + } + }, "/toolbox/ftp/search": { "post": { "security": [ @@ -16175,6 +16271,17 @@ } } }, + "dto.FtpBaseInfo": { + "type": "object", + "properties": { + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + } + } + }, "dto.FtpCreate": { "type": "object", "required": [ @@ -16197,6 +16304,27 @@ } } }, + "dto.FtpLogSearch": { + "type": "object", + "required": [ + "page", + "pageSize" + ], + "properties": { + "operation": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "user": { + "type": "string" + } + } + }, "dto.FtpUpdate": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index bc8a242ab..3e8b39eae 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -1202,6 +1202,13 @@ definitions: - type - vars type: object + dto.FtpBaseInfo: + properties: + isActive: + type: boolean + isExist: + type: boolean + type: object dto.FtpCreate: properties: description: @@ -1217,6 +1224,20 @@ definitions: - path - user type: object + dto.FtpLogSearch: + properties: + operation: + type: string + page: + type: integer + pageSize: + type: integer + user: + type: string + required: + - page + - pageSize + type: object dto.FtpUpdate: properties: description: @@ -12456,6 +12477,19 @@ paths: formatEN: create FTP [user][path] formatZH: 创建 FTP 账户 [user][path] paramKeys: [] + /toolbox/ftp/base: + get: + description: 获取 FTP 基础信息 + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.FtpBaseInfo' + security: + - ApiKeyAuth: [] + summary: Load FTP base info + tags: + - FTP /toolbox/ftp/del: post: consumes: @@ -12489,6 +12523,53 @@ paths: formatEN: delete FTP users [users] formatZH: 删除 FTP 账户 [users] paramKeys: [] + /toolbox/ftp/log/search: + post: + consumes: + - application/json + description: 获取 FTP 操作日志 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.FtpLogSearch' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.PageResult' + security: + - ApiKeyAuth: [] + summary: Load FTP operation log + tags: + - FTP + /toolbox/ftp/operate: + post: + consumes: + - application/json + description: 修改 FTP 状态 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.Operate' + responses: {} + security: + - ApiKeyAuth: [] + summary: Operate FTP + tags: + - FTP + x-panel-log: + BeforeFunctions: [] + bodyKeys: + - operation + formatEN: '[operation] FTP' + formatZH: '[operation] FTP' + paramKeys: [] /toolbox/ftp/search: post: consumes: diff --git a/frontend/src/api/interface/toolbox.ts b/frontend/src/api/interface/toolbox.ts index 8eeb51fad..da368efab 100644 --- a/frontend/src/api/interface/toolbox.ts +++ b/frontend/src/api/interface/toolbox.ts @@ -1,3 +1,5 @@ +import { ReqPage } from '.'; + export namespace Toolbox { export interface DeviceBaseInfo { dns: Array; @@ -77,6 +79,10 @@ export namespace Toolbox { operate: string; } + export interface FtpBaseInfo { + isActive: boolean; + isExist: boolean; + } export interface FtpInfo { id: number; user: string; @@ -98,4 +104,16 @@ export namespace Toolbox { path: string; description: string; } + export interface FtpSearchLog extends ReqPage { + user: string; + operation: string; + } + export interface FtpLog { + ip: string; + user: string; + time: string; + operation: string; + status: string; + size: string; + } } diff --git a/frontend/src/api/modules/toolbox.ts b/frontend/src/api/modules/toolbox.ts index 237f926f5..faed93ed4 100644 --- a/frontend/src/api/modules/toolbox.ts +++ b/frontend/src/api/modules/toolbox.ts @@ -71,10 +71,18 @@ export const updateFail2banByFile = (param: UpdateByFile) => { }; // ftp +export const getFtpBase = () => { + return http.get(`/toolbox/ftp/base`); +}; +export const searchFtpLog = (param: Toolbox.FtpSearchLog) => { + return http.post>(`/toolbox/ftp/log/search`, param); +}; export const searchFtp = (param: ReqPage) => { return http.post>(`/toolbox/ftp/search`, param); }; - +export const operateFtp = (operate: string) => { + return http.post(`/toolbox/ftp/operate`, { operation: operate }, TimeoutEnum.T_5M); +}; export const syncFtp = () => { return http.post(`/toolbox/ftp/sync`); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 1223e5250..675a84a95 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1031,6 +1031,8 @@ const message = { }, ftp: { ftp: 'FTP Account', + noFtp: 'FTP (pure-ftpd) service not detected, please refer to the official documentation for installation!', + operation: 'Perform [{0}] operation on FTP service, continue?', enableHelper: 'Enabling the selected FTP account will restore its access permissions. Do you want to continue?', disableHelper: diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 93406b0cb..546719a93 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -978,6 +978,8 @@ const message = { }, ftp: { ftp: 'FTP 帳戶', + noFtp: '未檢測到 FTP (pure-ftpd) 服務,請參考官方文檔進行安裝!', + operation: '對 FTP 服務進行 [{0}] 操作,是否繼續?', enableHelper: '啟用選取的 FTP 帳號後,該 FTP 帳號將恢復訪問權限,是否繼續操作?', disableHelper: '停用選取的 FTP 帳號後,該 FTP 帳號將失去訪問權限,是否繼續操作?', syncHelper: '同步伺服器與資料庫中的 FTP 帳戶資料,是否繼續操作?', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 29377492a..b9671d3a0 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -979,6 +979,8 @@ const message = { }, ftp: { ftp: 'FTP 账户', + noFtp: '未检测到 FTP (pure-ftpd) 服务,请参考官方文档进行安装!', + operation: '对 FTP 服务进行 [{0}] 操作,是否继续?', enableHelper: '启用选中的 FTP 账号后,该 FTP 账号恢复访问权限,是否继续操作?', disableHelper: '停用选中的 FTP 账号后,该 FTP 账号将失去访问权限,是否继续操作?', syncHelper: '同步服务器与数据库中的 FTP 账户数据,是否继续操作?', diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 499543412..50f809f70 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -173,6 +173,14 @@ export function computeSizeFromKB(size: number): string { if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB'; return (size / Math.pow(num, 3)).toFixed(2) + ' TB'; } +export function computeSizeFromByte(size: number): string { + const num = 1024.0; + if (size < num) return size + ' B'; + if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' KB'; + if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' MB'; + if (size < Math.pow(num, 4)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB'; + return (size / Math.pow(num, 5)).toFixed(2) + ' TB'; +} export function computeSizeFromKBs(size: number): string { const num = 1024.0; diff --git a/frontend/src/views/toolbox/ftp/index.vue b/frontend/src/views/toolbox/ftp/index.vue index 06d14e0c8..d61cd4bf3 100644 --- a/frontend/src/views/toolbox/ftp/index.vue +++ b/frontend/src/views/toolbox/ftp/index.vue @@ -1,120 +1,168 @@ @@ -123,8 +171,9 @@ import { onMounted, reactive, ref } from 'vue'; import i18n from '@/lang'; import { dateFormat } from '@/utils/util'; import { MsgSuccess } from '@/utils/message'; -import { deleteFtp, searchFtp, updateFtp, syncFtp } from '@/api/modules/toolbox'; +import { deleteFtp, searchFtp, updateFtp, syncFtp, operateFtp, getFtpBase } from '@/api/modules/toolbox'; import OperateDialog from '@/views/toolbox/ftp/operate/index.vue'; +import LogDialog from '@/views/toolbox/ftp/log/index.vue'; import { Toolbox } from '@/api/interface/toolbox'; const loading = ref(); @@ -141,30 +190,72 @@ const paginationConfig = reactive({ }); const searchName = ref(); +const form = reactive({ + isActive: false, + isExist: false, +}); + const opRef = ref(); const dialogRef = ref(); const operateIDs = ref(); +const dialogLogRef = ref(); const search = async (column?: any) => { - paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy; - paginationConfig.order = column?.order ? column.order : paginationConfig.order; - let params = { - info: searchName.value, - page: paginationConfig.currentPage, - pageSize: paginationConfig.pageSize, - }; loading.value = true; - await searchFtp(params) - .then((res) => { - loading.value = false; - data.value = res.data.items || []; - paginationConfig.total = res.data.total; + await getFtpBase() + .then(async (res) => { + form.isActive = res.data.isActive; + form.isExist = res.data.isExist; + paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy; + paginationConfig.order = column?.order ? column.order : paginationConfig.order; + let params = { + info: searchName.value, + page: paginationConfig.currentPage, + pageSize: paginationConfig.pageSize, + }; + await searchFtp(params) + .then((res) => { + loading.value = false; + data.value = res.data.items || []; + paginationConfig.total = res.data.total; + }) + .catch(() => { + loading.value = false; + }); }) .catch(() => { loading.value = false; }); }; +const toDoc = () => { + window.open('https://1panel.cn/docs/user_manual/toolbox/ftp/', '_blank', 'noopener,noreferrer'); +}; + +const onOperate = async (operation: string) => { + let msg = operation === 'enable' || operation === 'disable' ? 'ssh.' : 'commons.button.'; + ElMessageBox.confirm(i18n.global.t('toolbox.ftp.operation', [i18n.global.t(msg + operation)]), 'FTP', { + confirmButtonText: i18n.global.t('commons.button.confirm'), + cancelButtonText: i18n.global.t('commons.button.cancel'), + type: 'info', + }) + .then(async () => { + loading.value = true; + await operateFtp(operation) + .then(() => { + loading.value = false; + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + search(); + }) + .catch(() => { + loading.value = false; + }); + }) + .catch(() => { + search(); + }); +}; + const onChangeStatus = async (row: Toolbox.FtpInfo, status: string) => { ElMessageBox.confirm(i18n.global.t('toolbox.ftp.' + status + 'Helper'), i18n.global.t('cronjob.changeStatus'), { confirmButtonText: i18n.global.t('commons.button.confirm'), @@ -251,6 +342,15 @@ const buttons = [ onOpenDialog('edit', row); }, }, + { + label: i18n.global.t('commons.button.log'), + disabled: (row: Toolbox.FtpInfo) => { + return row.status === 'deleted'; + }, + click: (row: Toolbox.FtpInfo) => { + dialogLogRef.value!.acceptParams({ user: row.user }); + }, + }, { label: i18n.global.t('commons.button.delete'), disabled: (row: Toolbox.FtpInfo) => { diff --git a/frontend/src/views/toolbox/ftp/log/index.vue b/frontend/src/views/toolbox/ftp/log/index.vue new file mode 100644 index 000000000..b7f46dd57 --- /dev/null +++ b/frontend/src/views/toolbox/ftp/log/index.vue @@ -0,0 +1,119 @@ + + +