mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-12-04 01:39:20 +08:00
feat: FTP 增加状态及日志 (#5065)
This commit is contained in:
parent
201600ea06
commit
65f92bf0c3
@ -9,6 +9,70 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"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
|
// @Tags FTP
|
||||||
// @Summary Page FTP user
|
// @Summary Page FTP user
|
||||||
// @Description 获取 FTP 账户列表分页
|
// @Description 获取 FTP 账户列表分页
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type FtpInfo struct {
|
type FtpInfo struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
@ -13,6 +15,17 @@ type FtpInfo struct {
|
|||||||
Description string `json:"description"`
|
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 {
|
type FtpCreate struct {
|
||||||
User string `json:"user" validate:"required"`
|
User string `json:"user" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
|
@ -13,17 +13,60 @@ import (
|
|||||||
type FtpService struct{}
|
type FtpService struct{}
|
||||||
|
|
||||||
type IFtpService interface {
|
type IFtpService interface {
|
||||||
|
LoadBaseInfo() (dto.FtpBaseInfo, error)
|
||||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||||
|
Operate(operation string) error
|
||||||
Create(req dto.FtpCreate) error
|
Create(req dto.FtpCreate) error
|
||||||
Delete(req dto.BatchDeleteReq) error
|
Delete(req dto.BatchDeleteReq) error
|
||||||
Update(req dto.FtpUpdate) error
|
Update(req dto.FtpUpdate) error
|
||||||
Sync() error
|
Sync() error
|
||||||
|
LoadLog(req dto.FtpLogSearch) (int64, interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIFtpService() IFtpService {
|
func NewIFtpService() IFtpService {
|
||||||
return &FtpService{}
|
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) {
|
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"))
|
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithByUser(req.Info), commonRepo.WithOrderBy("created_at desc"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,6 +37,9 @@ func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
toolboxRouter.POST("/fail2ban/update", baseApi.UpdateFail2BanConf)
|
toolboxRouter.POST("/fail2ban/update", baseApi.UpdateFail2BanConf)
|
||||||
toolboxRouter.POST("/fail2ban/update/byconf", baseApi.UpdateFail2BanConfByFile)
|
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/search", baseApi.SearchFtp)
|
||||||
toolboxRouter.POST("/ftp", baseApi.CreateFtp)
|
toolboxRouter.POST("/ftp", baseApi.CreateFtp)
|
||||||
toolboxRouter.POST("/ftp/update", baseApi.UpdateFtp)
|
toolboxRouter.POST("/ftp/update", baseApi.UpdateFtp)
|
||||||
|
@ -15,7 +15,7 @@ type Fail2ban struct{}
|
|||||||
const defaultPath = "/etc/fail2ban/jail.local"
|
const defaultPath = "/etc/fail2ban/jail.local"
|
||||||
|
|
||||||
type FirewallClient interface {
|
type FirewallClient interface {
|
||||||
Status() (bool, bool, bool, error)
|
Status() (bool, bool, bool)
|
||||||
Version() (string, error)
|
Version() (string, error)
|
||||||
Operate(operate string) error
|
Operate(operate string) error
|
||||||
OperateSSHD(operate, ip string) error
|
OperateSSHD(operate, ip string) error
|
||||||
|
@ -2,8 +2,13 @@ package toolbox
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
@ -15,12 +20,14 @@ type Ftp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FtpClient interface {
|
type FtpClient interface {
|
||||||
Status() (bool, error)
|
Status() (bool, bool)
|
||||||
|
Operate(operate string) error
|
||||||
LoadList() ([]FtpList, error)
|
LoadList() ([]FtpList, error)
|
||||||
UserAdd(username, path, passwd string) error
|
UserAdd(username, path, passwd string) error
|
||||||
UserDel(username string) error
|
UserDel(username string) error
|
||||||
SetPasswd(username, passwd string) error
|
SetPasswd(username, passwd string) error
|
||||||
Reload() error
|
Reload() error
|
||||||
|
LoadLogs() ([]FtpLog, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFtpClient() (*Ftp, error) {
|
func NewFtpClient() (*Ftp, error) {
|
||||||
@ -54,8 +61,24 @@ func NewFtpClient() (*Ftp, error) {
|
|||||||
return &Ftp{DefaultUser: "1panel"}, nil
|
return &Ftp{DefaultUser: "1panel"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Ftp) Status() (bool, error) {
|
func (f *Ftp) Status() (bool, bool) {
|
||||||
return systemctl.IsActive("pure-ftpd.service")
|
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 {
|
func (f *Ftp) UserAdd(username, passwd, path string) error {
|
||||||
@ -141,3 +164,92 @@ func (f *Ftp) Reload() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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": {
|
"/toolbox/ftp/del": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"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": {
|
"/toolbox/ftp/search": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -16182,6 +16278,17 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.FtpBaseInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isActive": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isExist": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.FtpCreate": {
|
"dto.FtpCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"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": {
|
"dto.FtpUpdate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"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": {
|
"/toolbox/ftp/del": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"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": {
|
"/toolbox/ftp/search": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -16175,6 +16271,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.FtpBaseInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isActive": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isExist": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.FtpCreate": {
|
"dto.FtpCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"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": {
|
"dto.FtpUpdate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -1202,6 +1202,13 @@ definitions:
|
|||||||
- type
|
- type
|
||||||
- vars
|
- vars
|
||||||
type: object
|
type: object
|
||||||
|
dto.FtpBaseInfo:
|
||||||
|
properties:
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
isExist:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
dto.FtpCreate:
|
dto.FtpCreate:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
@ -1217,6 +1224,20 @@ definitions:
|
|||||||
- path
|
- path
|
||||||
- user
|
- user
|
||||||
type: object
|
type: object
|
||||||
|
dto.FtpLogSearch:
|
||||||
|
properties:
|
||||||
|
operation:
|
||||||
|
type: string
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
pageSize:
|
||||||
|
type: integer
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- page
|
||||||
|
- pageSize
|
||||||
|
type: object
|
||||||
dto.FtpUpdate:
|
dto.FtpUpdate:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
@ -12456,6 +12477,19 @@ paths:
|
|||||||
formatEN: create FTP [user][path]
|
formatEN: create FTP [user][path]
|
||||||
formatZH: 创建 FTP 账户 [user][path]
|
formatZH: 创建 FTP 账户 [user][path]
|
||||||
paramKeys: []
|
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:
|
/toolbox/ftp/del:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
@ -12489,6 +12523,53 @@ paths:
|
|||||||
formatEN: delete FTP users [users]
|
formatEN: delete FTP users [users]
|
||||||
formatZH: 删除 FTP 账户 [users]
|
formatZH: 删除 FTP 账户 [users]
|
||||||
paramKeys: []
|
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:
|
/toolbox/ftp/search:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ReqPage } from '.';
|
||||||
|
|
||||||
export namespace Toolbox {
|
export namespace Toolbox {
|
||||||
export interface DeviceBaseInfo {
|
export interface DeviceBaseInfo {
|
||||||
dns: Array<string>;
|
dns: Array<string>;
|
||||||
@ -77,6 +79,10 @@ export namespace Toolbox {
|
|||||||
operate: string;
|
operate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FtpBaseInfo {
|
||||||
|
isActive: boolean;
|
||||||
|
isExist: boolean;
|
||||||
|
}
|
||||||
export interface FtpInfo {
|
export interface FtpInfo {
|
||||||
id: number;
|
id: number;
|
||||||
user: string;
|
user: string;
|
||||||
@ -98,4 +104,16 @@ export namespace Toolbox {
|
|||||||
path: string;
|
path: string;
|
||||||
description: 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
|
// 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) => {
|
export const searchFtp = (param: ReqPage) => {
|
||||||
return http.post<ResPage<Toolbox.FtpInfo>>(`/toolbox/ftp/search`, param);
|
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 = () => {
|
export const syncFtp = () => {
|
||||||
return http.post(`/toolbox/ftp/sync`);
|
return http.post(`/toolbox/ftp/sync`);
|
||||||
};
|
};
|
||||||
|
@ -1031,6 +1031,8 @@ const message = {
|
|||||||
},
|
},
|
||||||
ftp: {
|
ftp: {
|
||||||
ftp: 'FTP Account',
|
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:
|
enableHelper:
|
||||||
'Enabling the selected FTP account will restore its access permissions. Do you want to continue?',
|
'Enabling the selected FTP account will restore its access permissions. Do you want to continue?',
|
||||||
disableHelper:
|
disableHelper:
|
||||||
|
@ -978,6 +978,8 @@ const message = {
|
|||||||
},
|
},
|
||||||
ftp: {
|
ftp: {
|
||||||
ftp: 'FTP 帳戶',
|
ftp: 'FTP 帳戶',
|
||||||
|
noFtp: '未檢測到 FTP (pure-ftpd) 服務,請參考官方文檔進行安裝!',
|
||||||
|
operation: '對 FTP 服務進行 [{0}] 操作,是否繼續?',
|
||||||
enableHelper: '啟用選取的 FTP 帳號後,該 FTP 帳號將恢復訪問權限,是否繼續操作?',
|
enableHelper: '啟用選取的 FTP 帳號後,該 FTP 帳號將恢復訪問權限,是否繼續操作?',
|
||||||
disableHelper: '停用選取的 FTP 帳號後,該 FTP 帳號將失去訪問權限,是否繼續操作?',
|
disableHelper: '停用選取的 FTP 帳號後,該 FTP 帳號將失去訪問權限,是否繼續操作?',
|
||||||
syncHelper: '同步伺服器與資料庫中的 FTP 帳戶資料,是否繼續操作?',
|
syncHelper: '同步伺服器與資料庫中的 FTP 帳戶資料,是否繼續操作?',
|
||||||
|
@ -979,6 +979,8 @@ const message = {
|
|||||||
},
|
},
|
||||||
ftp: {
|
ftp: {
|
||||||
ftp: 'FTP 账户',
|
ftp: 'FTP 账户',
|
||||||
|
noFtp: '未检测到 FTP (pure-ftpd) 服务,请参考官方文档进行安装!',
|
||||||
|
operation: '对 FTP 服务进行 [{0}] 操作,是否继续?',
|
||||||
enableHelper: '启用选中的 FTP 账号后,该 FTP 账号恢复访问权限,是否继续操作?',
|
enableHelper: '启用选中的 FTP 账号后,该 FTP 账号恢复访问权限,是否继续操作?',
|
||||||
disableHelper: '停用选中的 FTP 账号后,该 FTP 账号将失去访问权限,是否继续操作?',
|
disableHelper: '停用选中的 FTP 账号后,该 FTP 账号将失去访问权限,是否继续操作?',
|
||||||
syncHelper: '同步服务器与数据库中的 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';
|
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB';
|
||||||
return (size / Math.pow(num, 3)).toFixed(2) + ' TB';
|
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 {
|
export function computeSizeFromKBs(size: number): string {
|
||||||
const num = 1024.0;
|
const num = 1024.0;
|
||||||
|
@ -1,120 +1,168 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<LayoutContent v-loading="loading" title="FTP">
|
<div class="app-status" style="margin-top: 20px">
|
||||||
<template #toolbar>
|
<el-card v-if="form.isExist">
|
||||||
<el-row>
|
<div>
|
||||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
<el-tag effect="dark" type="success">FTP</el-tag>
|
||||||
<el-button type="primary" @click="onOpenDialog('add')">
|
<el-tag round class="status-content" v-if="form.isActive" type="success">
|
||||||
{{ $t('commons.button.add') }} FTP
|
{{ $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>
|
||||||
<el-button @click="onSync()">
|
<el-button v-if="!form.isActive" type="primary" @click="onOperate('start')" link>
|
||||||
{{ $t('commons.button.sync') }}
|
{{ $t('commons.button.start') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button plain :disabled="selects.length === 0" @click="onDelete(null)">
|
<el-divider direction="vertical" />
|
||||||
{{ $t('commons.button.delete') }}
|
<el-button type="primary" @click="onOperate('restart')" link>
|
||||||
|
{{ $t('container.restart') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</span>
|
||||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
</div>
|
||||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
</el-card>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
<div v-if="form.isExist">
|
||||||
</template>
|
<LayoutContent v-loading="loading" title="FTP">
|
||||||
<template #main>
|
<template #toolbar>
|
||||||
<ComplexTable
|
<el-row>
|
||||||
:pagination-config="paginationConfig"
|
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
||||||
v-model:selects="selects"
|
<el-button type="primary" :disabled="!form.isActive" @click="onOpenDialog('add')">
|
||||||
@sort-change="search"
|
{{ $t('commons.button.add') }} FTP
|
||||||
@search="search"
|
</el-button>
|
||||||
:data="data"
|
<el-button @click="onSync()" :disabled="!form.isActive">
|
||||||
>
|
{{ $t('commons.button.sync') }}
|
||||||
<el-table-column type="selection" fix />
|
</el-button>
|
||||||
<el-table-column
|
<el-button plain :disabled="selects.length === 0 || !form.isActive" @click="onDelete(null)">
|
||||||
:label="$t('commons.login.username')"
|
{{ $t('commons.button.delete') }}
|
||||||
:min-width="60"
|
</el-button>
|
||||||
prop="user"
|
</el-col>
|
||||||
show-overflow-tooltip
|
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||||
/>
|
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||||
<el-table-column :label="$t('commons.login.password')" prop="password">
|
</el-col>
|
||||||
<template #default="{ row }">
|
</el-row>
|
||||||
<div v-if="row.password.length === 0">-</div>
|
</template>
|
||||||
<div v-else class="flex items-center">
|
<template #main>
|
||||||
<div class="star-center" v-if="!row.showPassword">
|
<ComplexTable
|
||||||
<span>**********</span>
|
:pagination-config="paginationConfig"
|
||||||
</div>
|
v-model:selects="selects"
|
||||||
<div>
|
@sort-change="search"
|
||||||
<span v-if="row.showPassword">
|
@search="search"
|
||||||
{{ row.password }}
|
:data="data"
|
||||||
</span>
|
>
|
||||||
|
<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>
|
</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
|
<el-button
|
||||||
v-if="!row.showPassword"
|
v-if="row.status === 'Enable'"
|
||||||
|
@click="onChangeStatus(row, 'disable')"
|
||||||
link
|
link
|
||||||
@click="row.showPassword = true"
|
icon="VideoPlay"
|
||||||
icon="View"
|
type="success"
|
||||||
class="ml-1.5"
|
>
|
||||||
></el-button>
|
{{ $t('commons.status.enabled') }}
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.showPassword"
|
v-if="row.status === 'Disable'"
|
||||||
|
icon="VideoPause"
|
||||||
|
@click="onChangeStatus(row, 'enable')"
|
||||||
link
|
link
|
||||||
@click="row.showPassword = false"
|
type="danger"
|
||||||
icon="Hide"
|
>
|
||||||
class="ml-1.5"
|
{{ $t('commons.status.disabled') }}
|
||||||
></el-button>
|
</el-button>
|
||||||
<div>
|
</template>
|
||||||
<CopyButton :content="row.password" type="icon" />
|
</el-table-column>
|
||||||
</div>
|
<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>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column :label="$t('commons.table.status')" :min-width="60" prop="status">
|
</template>
|
||||||
<template #default="{ row }">
|
</LayoutContent>
|
||||||
<el-tag v-if="row.status === 'deleted'" type="info">{{ $t('database.isDelete') }}</el-tag>
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()" />
|
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()" />
|
||||||
<OperateDialog @search="search" ref="dialogRef" />
|
<OperateDialog @search="search" ref="dialogRef" />
|
||||||
|
<LogDialog ref="dialogLogRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -123,8 +171,9 @@ import { onMounted, reactive, ref } from 'vue';
|
|||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { dateFormat } from '@/utils/util';
|
import { dateFormat } from '@/utils/util';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
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 OperateDialog from '@/views/toolbox/ftp/operate/index.vue';
|
||||||
|
import LogDialog from '@/views/toolbox/ftp/log/index.vue';
|
||||||
import { Toolbox } from '@/api/interface/toolbox';
|
import { Toolbox } from '@/api/interface/toolbox';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
@ -141,30 +190,72 @@ const paginationConfig = reactive({
|
|||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
isActive: false,
|
||||||
|
isExist: false,
|
||||||
|
});
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const dialogRef = ref();
|
const dialogRef = ref();
|
||||||
const operateIDs = ref();
|
const operateIDs = ref();
|
||||||
|
const dialogLogRef = ref();
|
||||||
|
|
||||||
const search = async (column?: any) => {
|
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;
|
loading.value = true;
|
||||||
await searchFtp(params)
|
await getFtpBase()
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
loading.value = false;
|
form.isActive = res.data.isActive;
|
||||||
data.value = res.data.items || [];
|
form.isExist = res.data.isExist;
|
||||||
paginationConfig.total = res.data.total;
|
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(() => {
|
.catch(() => {
|
||||||
loading.value = false;
|
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) => {
|
const onChangeStatus = async (row: Toolbox.FtpInfo, status: string) => {
|
||||||
ElMessageBox.confirm(i18n.global.t('toolbox.ftp.' + status + 'Helper'), i18n.global.t('cronjob.changeStatus'), {
|
ElMessageBox.confirm(i18n.global.t('toolbox.ftp.' + status + 'Helper'), i18n.global.t('cronjob.changeStatus'), {
|
||||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
@ -251,6 +342,15 @@ const buttons = [
|
|||||||
onOpenDialog('edit', row);
|
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'),
|
label: i18n.global.t('commons.button.delete'),
|
||||||
disabled: (row: Toolbox.FtpInfo) => {
|
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