mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-23 18:49:21 +08:00
feat: FTP 增加状态及日志 (#5065)
This commit is contained in:
parent
201600ea06
commit
65f92bf0c3
@ -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 账户列表分页
|
||||
|
@ -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"`
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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": [
|
||||
|
@ -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": [
|
||||
|
@ -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:
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ReqPage } from '.';
|
||||
|
||||
export namespace Toolbox {
|
||||
export interface DeviceBaseInfo {
|
||||
dns: Array<string>;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -71,10 +71,18 @@ export const updateFail2banByFile = (param: UpdateByFile) => {
|
||||
};
|
||||
|
||||
// ftp
|
||||
export const getFtpBase = () => {
|
||||
return http.get<Toolbox.FtpBaseInfo>(`/toolbox/ftp/base`);
|
||||
};
|
||||
export const searchFtpLog = (param: Toolbox.FtpSearchLog) => {
|
||||
return http.post<ResPage<Toolbox.FtpLog>>(`/toolbox/ftp/log/search`, param);
|
||||
};
|
||||
export const searchFtp = (param: ReqPage) => {
|
||||
return http.post<ResPage<Toolbox.FtpInfo>>(`/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`);
|
||||
};
|
||||
|
@ -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:
|
||||
|
@ -978,6 +978,8 @@ const message = {
|
||||
},
|
||||
ftp: {
|
||||
ftp: 'FTP 帳戶',
|
||||
noFtp: '未檢測到 FTP (pure-ftpd) 服務,請參考官方文檔進行安裝!',
|
||||
operation: '對 FTP 服務進行 [{0}] 操作,是否繼續?',
|
||||
enableHelper: '啟用選取的 FTP 帳號後,該 FTP 帳號將恢復訪問權限,是否繼續操作?',
|
||||
disableHelper: '停用選取的 FTP 帳號後,該 FTP 帳號將失去訪問權限,是否繼續操作?',
|
||||
syncHelper: '同步伺服器與資料庫中的 FTP 帳戶資料,是否繼續操作?',
|
||||
|
@ -979,6 +979,8 @@ const message = {
|
||||
},
|
||||
ftp: {
|
||||
ftp: 'FTP 账户',
|
||||
noFtp: '未检测到 FTP (pure-ftpd) 服务,请参考官方文档进行安装!',
|
||||
operation: '对 FTP 服务进行 [{0}] 操作,是否继续?',
|
||||
enableHelper: '启用选中的 FTP 账号后,该 FTP 账号恢复访问权限,是否继续操作?',
|
||||
disableHelper: '停用选中的 FTP 账号后,该 FTP 账号将失去访问权限,是否继续操作?',
|
||||
syncHelper: '同步服务器与数据库中的 FTP 账户数据,是否继续操作?',
|
||||
|
@ -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;
|
||||
|
@ -1,120 +1,168 @@
|
||||
<template>
|
||||
<div>
|
||||
<LayoutContent v-loading="loading" title="FTP">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
||||
<el-button type="primary" @click="onOpenDialog('add')">
|
||||
{{ $t('commons.button.add') }} FTP
|
||||
<div class="app-status" style="margin-top: 20px">
|
||||
<el-card v-if="form.isExist">
|
||||
<div>
|
||||
<el-tag effect="dark" type="success">FTP</el-tag>
|
||||
<el-tag round class="status-content" v-if="form.isActive" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag round class="status-content" v-if="!form.isActive" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
<span class="buttons">
|
||||
<el-button v-if="form.isActive" type="primary" @click="onOperate('stop')" link>
|
||||
{{ $t('commons.button.stop') }}
|
||||
</el-button>
|
||||
<el-button @click="onSync()">
|
||||
{{ $t('commons.button.sync') }}
|
||||
<el-button v-if="!form.isActive" type="primary" @click="onOperate('start')" link>
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="selects.length === 0" @click="onDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onOperate('restart')" link>
|
||||
{{ $t('container.restart') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
v-model:selects="selects"
|
||||
@sort-change="search"
|
||||
@search="search"
|
||||
:data="data"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.login.username')"
|
||||
:min-width="60"
|
||||
prop="user"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column :label="$t('commons.login.password')" prop="password">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.password.length === 0">-</div>
|
||||
<div v-else class="flex items-center">
|
||||
<div class="star-center" v-if="!row.showPassword">
|
||||
<span>**********</span>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="row.showPassword">
|
||||
{{ row.password }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-if="form.isExist">
|
||||
<LayoutContent v-loading="loading" title="FTP">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
||||
<el-button type="primary" :disabled="!form.isActive" @click="onOpenDialog('add')">
|
||||
{{ $t('commons.button.add') }} FTP
|
||||
</el-button>
|
||||
<el-button @click="onSync()" :disabled="!form.isActive">
|
||||
{{ $t('commons.button.sync') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="selects.length === 0 || !form.isActive" @click="onDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
v-model:selects="selects"
|
||||
@sort-change="search"
|
||||
@search="search"
|
||||
:data="data"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.login.username')"
|
||||
:min-width="60"
|
||||
prop="user"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column :label="$t('commons.login.password')" prop="password">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.password.length === 0">-</div>
|
||||
<div v-else class="flex items-center">
|
||||
<div class="star-center" v-if="!row.showPassword">
|
||||
<span>**********</span>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="row.showPassword">
|
||||
{{ row.password }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="!row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = true"
|
||||
icon="View"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<el-button
|
||||
v-if="row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = false"
|
||||
icon="Hide"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<div>
|
||||
<CopyButton :content="row.password" type="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" :min-width="60" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status === 'deleted'" type="info">
|
||||
{{ $t('database.isDelete') }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
v-if="!row.showPassword"
|
||||
v-if="row.status === 'Enable'"
|
||||
@click="onChangeStatus(row, 'disable')"
|
||||
link
|
||||
@click="row.showPassword = true"
|
||||
icon="View"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
icon="VideoPlay"
|
||||
type="success"
|
||||
>
|
||||
{{ $t('commons.status.enabled') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.showPassword"
|
||||
v-if="row.status === 'Disable'"
|
||||
icon="VideoPause"
|
||||
@click="onChangeStatus(row, 'enable')"
|
||||
link
|
||||
@click="row.showPassword = false"
|
||||
icon="Hide"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<div>
|
||||
<CopyButton :content="row.password" type="icon" />
|
||||
</div>
|
||||
type="danger"
|
||||
>
|
||||
{{ $t('commons.status.disabled') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('file.root')" :min-width="120" prop="path" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.description')"
|
||||
:min-width="80"
|
||||
prop="description"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.createdAt')"
|
||||
:formatter="dateFormat"
|
||||
:min-width="80"
|
||||
prop="createdAt"
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="240px"
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
<div v-else>
|
||||
<LayoutContent title="FTP" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div>
|
||||
<span>{{ $t('toolbox.ftp.noFtp') }}</span>
|
||||
<span @click="toDoc">
|
||||
<el-icon class="ml-2"><Position /></el-icon>
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</span>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" :min-width="60" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status === 'deleted'" type="info">{{ $t('database.isDelete') }}</el-tag>
|
||||
<el-button
|
||||
v-if="row.status === 'Enable'"
|
||||
@click="onChangeStatus(row, 'disable')"
|
||||
link
|
||||
icon="VideoPlay"
|
||||
type="success"
|
||||
>
|
||||
{{ $t('commons.status.enabled') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'Disable'"
|
||||
icon="VideoPause"
|
||||
@click="onChangeStatus(row, 'enable')"
|
||||
link
|
||||
type="danger"
|
||||
>
|
||||
{{ $t('commons.status.disabled') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('file.root')" :min-width="120" prop="path" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.description')"
|
||||
:min-width="80"
|
||||
prop="description"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.createdAt')"
|
||||
:formatter="dateFormat"
|
||||
:min-width="80"
|
||||
prop="createdAt"
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="240px"
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
|
||||
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()" />
|
||||
<OperateDialog @search="search" ref="dialogRef" />
|
||||
<LogDialog ref="dialogLogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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) => {
|
||||
|
119
frontend/src/views/toolbox/ftp/log/index.vue
Normal file
119
frontend/src/views/toolbox/ftp/log/index.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader header="FTP" :resource="paginationConfig.user" :back="handleClose" />
|
||||
</template>
|
||||
<el-select @change="search" class="p-w-200" clearable v-model="paginationConfig.operation">
|
||||
<template #prefix>{{ $t('container.lines') }}</template>
|
||||
<el-option value="PUT" :label="$t('file.upload')" />
|
||||
<el-option value="GET" :label="$t('file.download')" />
|
||||
</el-select>
|
||||
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
<el-table-column label="ip" prop="ip" />
|
||||
<el-table-column :label="$t('commons.login.username')" prop="user" />
|
||||
<el-table-column :label="$t('commons.table.status')" show-overflow-tooltip prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status === '200'">{{ $t('commons.status.success') }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ $t('commons.status.failed') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.operate')" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ loadFileName(row.operation) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('file.file')" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ loadOperation(row.operation) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('file.size')" show-overflow-tooltip prop="size">
|
||||
<template #default="{ row }">
|
||||
{{ computeSizeFromByte(Number(row.size)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.date')" prop="time" show-overflow-tooltip />
|
||||
</ComplexTable>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { searchFtpLog } from '@/api/modules/toolbox';
|
||||
import { computeSizeFromByte } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'ftp-log-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
user: '',
|
||||
operation: '',
|
||||
});
|
||||
const data = ref();
|
||||
|
||||
interface DialogProps {
|
||||
user: string;
|
||||
}
|
||||
const loading = ref();
|
||||
const drawerVisible = ref(false);
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
paginationConfig.user = params.user;
|
||||
search();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
user: paginationConfig.user,
|
||||
operation: paginationConfig.operation,
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
loading.value = true;
|
||||
await searchFtpLog(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadFileName = (operation: string) => {
|
||||
if (operation.startsWith('"PUT')) {
|
||||
return i18n.global.t('file.upload');
|
||||
}
|
||||
if (operation.startsWith('"GET')) {
|
||||
return i18n.global.t('file.download');
|
||||
}
|
||||
};
|
||||
const loadOperation = (operation: string) => {
|
||||
return operation.replaceAll('"', '').replaceAll('PUT', '').replaceAll('GET', '');
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user