mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-13 17:24:44 +08:00
feat: 工具箱支持 FTP (#5039)
This commit is contained in:
parent
35c4f4edfd
commit
64ed07e4fa
@ -118,11 +118,11 @@ func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
|
||||
// @Summary Delete backup account
|
||||
// @Description 删除备份账号
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchDeleteReq true "request"
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":true,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
|
||||
func (b *BaseApi) DeleteBackup(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
@ -37,6 +37,7 @@ var (
|
||||
|
||||
deviceService = service.NewIDeviceService()
|
||||
fail2banService = service.NewIFail2BanService()
|
||||
ftpService = service.NewIFtpService()
|
||||
|
||||
settingService = service.NewISettingService()
|
||||
backupService = service.NewIBackupService()
|
||||
|
135
backend/app/api/v1/ftp.go
Normal file
135
backend/app/api/v1/ftp.go
Normal file
@ -0,0 +1,135 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags FTP
|
||||
// @Summary Page FTP user
|
||||
// @Description 获取 FTP 账户列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/ftp/search [post]
|
||||
func (b *BaseApi) SearchFtp(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := ftpService.SearchWithPage(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 Create FTP user
|
||||
// @Description 创建 FTP 账户
|
||||
// @Accept json
|
||||
// @Param request body dto.FtpCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/ftp [post]
|
||||
// @x-panel-log {"bodyKeys":["user", "path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 FTP 账户 [user][path]","formatEN":"create FTP [user][path]"}
|
||||
func (b *BaseApi) CreateFtp(c *gin.Context) {
|
||||
var req dto.FtpCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Password) != 0 {
|
||||
pass, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(pass)
|
||||
}
|
||||
if err := ftpService.Create(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags FTP
|
||||
// @Summary Delete FTP user
|
||||
// @Description 删除 FTP 账户
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchDeleteReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/ftp/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ftps","output_column":"user","output_value":"users"}],"formatZH":"删除 FTP 账户 [users]","formatEN":"delete FTP users [users]"}
|
||||
func (b *BaseApi) DeleteFtp(c *gin.Context) {
|
||||
var req dto.BatchDeleteReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := ftpService.Delete(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags FTP
|
||||
// @Summary Sync FTP user
|
||||
// @Description 同步 FTP 账户
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchDeleteReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/ftp/sync [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 FTP 账户","formatEN":"sync FTP users"}
|
||||
func (b *BaseApi) SyncFtp(c *gin.Context) {
|
||||
if err := ftpService.Sync(); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags FTP
|
||||
// @Summary Update FTP user
|
||||
// @Description 修改 FTP 账户
|
||||
// @Accept json
|
||||
// @Param request body dto.FtpUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/ftp/update [post]
|
||||
// @x-panel-log {"bodyKeys":["user", "path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 FTP 账户 [user][path]","formatEN":"update FTP [user][path]"}
|
||||
func (b *BaseApi) UpdateFtp(c *gin.Context) {
|
||||
var req dto.FtpUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Password) != 0 {
|
||||
pass, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(pass)
|
||||
}
|
||||
if err := ftpService.Update(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
30
backend/app/dto/ftp.go
Normal file
30
backend/app/dto/ftp.go
Normal file
@ -0,0 +1,30 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type FtpInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Path string `json:"path"`
|
||||
Status string `json:"status"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type FtpCreate struct {
|
||||
User string `json:"user" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type FtpUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
|
||||
Password string `json:"password" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Status string `json:"status"`
|
||||
Description string `json:"description"`
|
||||
}
|
11
backend/app/model/ftp.go
Normal file
11
backend/app/model/ftp.go
Normal file
@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
type Ftp struct {
|
||||
BaseModel
|
||||
|
||||
User string `gorm:"type:varchar(64);not null" json:"user"`
|
||||
Password string `gorm:"type:varchar(64);not null" json:"password"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Path string `gorm:"type:varchar(64);not null" json:"path"`
|
||||
Description string `gorm:"type:varchar(64);not null" json:"description"`
|
||||
}
|
80
backend/app/repo/ftp.go
Normal file
80
backend/app/repo/ftp.go
Normal file
@ -0,0 +1,80 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type FtpRepo struct{}
|
||||
|
||||
type IFtpRepo interface {
|
||||
Get(opts ...DBOption) (model.Ftp, error)
|
||||
GetList(opts ...DBOption) ([]model.Ftp, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.Ftp, error)
|
||||
Create(ftp *model.Ftp) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
WithByUser(user string) DBOption
|
||||
}
|
||||
|
||||
func NewIFtpRepo() IFtpRepo {
|
||||
return &FtpRepo{}
|
||||
}
|
||||
|
||||
func (u *FtpRepo) Get(opts ...DBOption) (model.Ftp, error) {
|
||||
var ftp model.Ftp
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&ftp).Error
|
||||
return ftp, err
|
||||
}
|
||||
|
||||
func (h *FtpRepo) WithByUser(user string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(user) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("user like ?", "%"+user+"%")
|
||||
}
|
||||
}
|
||||
|
||||
func (u *FtpRepo) GetList(opts ...DBOption) ([]model.Ftp, error) {
|
||||
var ftps []model.Ftp
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&ftps).Error
|
||||
return ftps, err
|
||||
}
|
||||
|
||||
func (h *FtpRepo) Page(page, size int, opts ...DBOption) (int64, []model.Ftp, error) {
|
||||
var users []model.Ftp
|
||||
db := global.DB.Model(&model.Ftp{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
|
||||
return count, users, err
|
||||
}
|
||||
|
||||
func (h *FtpRepo) Create(ftp *model.Ftp) error {
|
||||
return global.DB.Create(ftp).Error
|
||||
}
|
||||
|
||||
func (h *FtpRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.Ftp{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (h *FtpRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Ftp{}).Error
|
||||
}
|
@ -24,6 +24,7 @@ var (
|
||||
hostRepo = repo.NewIHostRepo()
|
||||
groupRepo = repo.NewIGroupRepo()
|
||||
commandRepo = repo.NewICommandRepo()
|
||||
ftpRepo = repo.NewIFtpRepo()
|
||||
|
||||
settingRepo = repo.NewISettingRepo()
|
||||
backupRepo = repo.NewIBackupRepo()
|
||||
|
177
backend/app/service/ftp.go
Normal file
177
backend/app/service/ftp.go
Normal file
@ -0,0 +1,177 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/toolbox"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type FtpService struct{}
|
||||
|
||||
type IFtpService interface {
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
Create(req dto.FtpCreate) error
|
||||
Delete(req dto.BatchDeleteReq) error
|
||||
Update(req dto.FtpUpdate) error
|
||||
Sync() error
|
||||
}
|
||||
|
||||
func NewIFtpService() IFtpService {
|
||||
return &FtpService{}
|
||||
}
|
||||
|
||||
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 {
|
||||
return 0, nil, err
|
||||
}
|
||||
var users []dto.FtpInfo
|
||||
for _, user := range lists {
|
||||
var item dto.FtpInfo
|
||||
if err := copier.Copy(&item, &user); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
item.Password, _ = encrypt.StringDecrypt(item.Password)
|
||||
users = append(users, item)
|
||||
}
|
||||
return total, users, err
|
||||
}
|
||||
|
||||
func (f *FtpService) Sync() error {
|
||||
client, err := toolbox.NewFtpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lists, err := client.LoadList()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
listsInDB, err := ftpRepo.GetList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sameData := make(map[string]struct{})
|
||||
for _, item := range lists {
|
||||
for _, itemInDB := range listsInDB {
|
||||
if item.User == itemInDB.User {
|
||||
sameData[item.User] = struct{}{}
|
||||
if item.Path != itemInDB.Path {
|
||||
_ = ftpRepo.Update(itemInDB.ID, map[string]interface{}{"path": item.Path, "status": constant.StatusDisable})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range lists {
|
||||
if _, ok := sameData[item.User]; !ok {
|
||||
_ = ftpRepo.Create(&model.Ftp{User: item.User, Path: item.Path, Status: constant.StatusDisable})
|
||||
}
|
||||
}
|
||||
for _, item := range listsInDB {
|
||||
if _, ok := sameData[item.User]; !ok {
|
||||
_ = ftpRepo.Update(item.ID, map[string]interface{}{"status": "deleted"})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FtpService) Create(req dto.FtpCreate) error {
|
||||
pass, err := encrypt.StringEncrypt(req.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userInDB, _ := ftpRepo.Get(hostRepo.WithByUser(req.User))
|
||||
if userInDB.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
client, err := toolbox.NewFtpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.UserAdd(req.User, req.Password, req.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
var ftp model.Ftp
|
||||
if err := copier.Copy(&ftp, &req); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
ftp.Status = constant.StatusEnable
|
||||
ftp.Password = pass
|
||||
if err := ftpRepo.Create(&ftp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FtpService) Delete(req dto.BatchDeleteReq) error {
|
||||
client, err := toolbox.NewFtpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range req.Ids {
|
||||
ftpItem, err := ftpRepo.Get(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = client.UserDel(ftpItem.User)
|
||||
_ = ftpRepo.Delete(commonRepo.WithByID(id))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FtpService) Update(req dto.FtpUpdate) error {
|
||||
pass, err := encrypt.StringEncrypt(req.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ftpItem, _ := ftpRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if ftpItem.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
passItem, err := encrypt.StringDecrypt(ftpItem.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := toolbox.NewFtpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needReload := false
|
||||
updates := make(map[string]interface{})
|
||||
if req.Password != passItem {
|
||||
if err := client.SetPasswd(ftpItem.User, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
updates["password"] = pass
|
||||
needReload = true
|
||||
}
|
||||
if req.Status != ftpItem.Status {
|
||||
if err := client.SetStatus(ftpItem.User, req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
updates["status"] = req.Status
|
||||
needReload = true
|
||||
}
|
||||
if req.Path != ftpItem.Path {
|
||||
if err := client.SetPath(ftpItem.User, req.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
updates["path"] = req.Path
|
||||
needReload = true
|
||||
}
|
||||
if req.Description != ftpItem.Description {
|
||||
updates["description"] = req.Description
|
||||
}
|
||||
if needReload {
|
||||
_ = client.Reload()
|
||||
}
|
||||
if len(updates) != 0 {
|
||||
return ftpRepo.Update(ftpItem.ID, updates)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -86,6 +86,7 @@ func Init() {
|
||||
migrations.AddWebsiteSSLColumn,
|
||||
migrations.AddRedisCommand,
|
||||
migrations.AddMonitorMenu,
|
||||
migrations.AddFtp,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -2,6 +2,7 @@ package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
@ -202,3 +203,13 @@ var AddMonitorMenu = &gormigrate.Migration{
|
||||
return tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
|
||||
},
|
||||
}
|
||||
|
||||
var AddFtp = &gormigrate.Migration{
|
||||
ID: "20240517-add-ftp",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Ftp{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -36,5 +36,11 @@ func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
toolboxRouter.POST("/fail2ban/operate/sshd", baseApi.OperateSSHD)
|
||||
toolboxRouter.POST("/fail2ban/update", baseApi.UpdateFail2BanConf)
|
||||
toolboxRouter.POST("/fail2ban/update/byconf", baseApi.UpdateFail2BanConfByFile)
|
||||
|
||||
toolboxRouter.POST("/ftp/search", baseApi.SearchFtp)
|
||||
toolboxRouter.POST("/ftp", baseApi.CreateFtp)
|
||||
toolboxRouter.POST("/ftp/update", baseApi.UpdateFtp)
|
||||
toolboxRouter.POST("/ftp/del", baseApi.DeleteFtp)
|
||||
toolboxRouter.POST("/ftp/sync", baseApi.SyncFtp)
|
||||
}
|
||||
}
|
||||
|
143
backend/utils/toolbox/pure-ftpd.go
Normal file
143
backend/utils/toolbox/pure-ftpd.go
Normal file
@ -0,0 +1,143 @@
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
type Ftp struct {
|
||||
DefaultUser string
|
||||
}
|
||||
|
||||
type FtpClient interface {
|
||||
Status() (bool, error)
|
||||
LoadList() ([]FtpList, error)
|
||||
UserAdd(username, path, passwd string) error
|
||||
UserDel(username string) error
|
||||
SetPasswd(username, passwd string) error
|
||||
Reload() error
|
||||
}
|
||||
|
||||
func NewFtpClient() (*Ftp, error) {
|
||||
userItem, err := user.LookupId("1000")
|
||||
if err == nil {
|
||||
return &Ftp{DefaultUser: userItem.Username}, err
|
||||
}
|
||||
if err.Error() != user.UnknownUserIdError(1000).Error() {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupItem, err := user.LookupGroupId("1000")
|
||||
if err == nil {
|
||||
stdout2, err := cmd.Execf("useradd -u 1000 -g %s %s", groupItem.Name, "1panel")
|
||||
if err != nil {
|
||||
return nil, errors.New(stdout2)
|
||||
}
|
||||
return &Ftp{DefaultUser: "1panel"}, nil
|
||||
}
|
||||
if err.Error() != user.UnknownGroupIdError("1000").Error() {
|
||||
return nil, err
|
||||
}
|
||||
stdout, err := cmd.Exec("groupadd -g 1000 1panel")
|
||||
if err != nil {
|
||||
return nil, errors.New(string(stdout))
|
||||
}
|
||||
stdout2, err := cmd.Execf("useradd -u 1000 -g %s %s", groupItem.Name, userItem.Username)
|
||||
if err != nil {
|
||||
return nil, errors.New(stdout2)
|
||||
}
|
||||
return &Ftp{DefaultUser: "1panel"}, nil
|
||||
}
|
||||
|
||||
func (f *Ftp) Status() (bool, error) {
|
||||
return systemctl.IsActive("pure-ftpd.service")
|
||||
}
|
||||
|
||||
func (f *Ftp) UserAdd(username, passwd, path string) error {
|
||||
std, err := cmd.Execf("pure-pw useradd %s -u %s -d %s <<EOF \n%s\n%s\nEOF", username, f.DefaultUser, path, passwd, passwd)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
_ = f.Reload()
|
||||
std2, err := cmd.Execf("chown %s %s", f.DefaultUser, path)
|
||||
if err != nil {
|
||||
return errors.New(std2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Ftp) UserDel(username string) error {
|
||||
std, err := cmd.Execf("pure-pw userdel %s", username)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
_ = f.Reload()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Ftp) SetPasswd(username, passwd string) error {
|
||||
std, err := cmd.Execf("pure-pw passwd %s <<EOF \n%s\n%s\nEOF", username, passwd, passwd)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Ftp) SetPath(username, path string) error {
|
||||
std, err := cmd.Execf("pure-pw usermod %s -d %s", username, path)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
std2, err := cmd.Execf("chown %s %s", f.DefaultUser, path)
|
||||
if err != nil {
|
||||
return errors.New(std2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Ftp) SetStatus(username, status string) error {
|
||||
statusItem := "''"
|
||||
if status == constant.StatusDisable {
|
||||
statusItem = "1"
|
||||
}
|
||||
std, err := cmd.Execf("pure-pw usermod %s -r %s", username, statusItem)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Ftp) LoadList() ([]FtpList, error) {
|
||||
std, err := cmd.Exec("pure-pw list")
|
||||
if err != nil {
|
||||
return nil, errors.New(std)
|
||||
}
|
||||
var lists []FtpList
|
||||
lines := strings.Split(std, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
lists = append(lists, FtpList{User: parts[0], Path: strings.ReplaceAll(parts[1], "/./", "")})
|
||||
}
|
||||
return lists, nil
|
||||
}
|
||||
|
||||
type FtpList struct {
|
||||
User string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (f *Ftp) Reload() error {
|
||||
std, err := cmd.Exec("pure-pw mkdb")
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -9561,7 +9561,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
"$ref": "#/definitions/dto.OperateByID"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -9576,7 +9576,7 @@ const docTemplate = `{
|
||||
"db": "backup_accounts",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": true,
|
||||
"isList": false,
|
||||
"output_column": "type",
|
||||
"output_value": "types"
|
||||
}
|
||||
@ -11512,6 +11512,219 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Create FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.FtpCreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"user",
|
||||
"path"
|
||||
],
|
||||
"formatEN": "create FTP [user][path]",
|
||||
"formatZH": "创建 FTP 账户 [user][path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/del": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "删除 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Delete FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "ftps",
|
||||
"input_column": "id",
|
||||
"input_value": "ids",
|
||||
"isList": true,
|
||||
"output_column": "user",
|
||||
"output_value": "users"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"ids"
|
||||
],
|
||||
"formatEN": "delete FTP users [users]",
|
||||
"formatZH": "删除 FTP 账户 [users]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 FTP 账户列表分页",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Page FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SearchWithPage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/sync": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "同步 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Sync FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [],
|
||||
"formatEN": "sync FTP users",
|
||||
"formatZH": "同步 FTP 账户",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Update FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.FtpUpdate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"user",
|
||||
"path"
|
||||
],
|
||||
"formatEN": "update FTP [user][path]",
|
||||
"formatZH": "修改 FTP 账户 [user][path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/scan": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -15969,6 +16182,52 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.FtpCreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"path",
|
||||
"user"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.FtpUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.GPUInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -9554,7 +9554,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
"$ref": "#/definitions/dto.OperateByID"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -9569,7 +9569,7 @@
|
||||
"db": "backup_accounts",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": true,
|
||||
"isList": false,
|
||||
"output_column": "type",
|
||||
"output_value": "types"
|
||||
}
|
||||
@ -11505,6 +11505,219 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Create FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.FtpCreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"user",
|
||||
"path"
|
||||
],
|
||||
"formatEN": "create FTP [user][path]",
|
||||
"formatZH": "创建 FTP 账户 [user][path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/del": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "删除 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Delete FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "ftps",
|
||||
"input_column": "id",
|
||||
"input_value": "ids",
|
||||
"isList": true,
|
||||
"output_column": "user",
|
||||
"output_value": "users"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"ids"
|
||||
],
|
||||
"formatEN": "delete FTP users [users]",
|
||||
"formatZH": "删除 FTP 账户 [users]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 FTP 账户列表分页",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Page FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SearchWithPage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/sync": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "同步 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Sync FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [],
|
||||
"formatEN": "sync FTP users",
|
||||
"formatZH": "同步 FTP 账户",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/ftp/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改 FTP 账户",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"FTP"
|
||||
],
|
||||
"summary": "Update FTP user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.FtpUpdate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"user",
|
||||
"path"
|
||||
],
|
||||
"formatEN": "update FTP [user][path]",
|
||||
"formatZH": "修改 FTP 账户 [user][path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/scan": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -15962,6 +16175,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.FtpCreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"path",
|
||||
"user"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.FtpUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.GPUInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -1202,6 +1202,37 @@ definitions:
|
||||
- type
|
||||
- vars
|
||||
type: object
|
||||
dto.FtpCreate:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
- path
|
||||
- user
|
||||
type: object
|
||||
dto.FtpUpdate:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
password:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
- path
|
||||
type: object
|
||||
dto.GPUInfo:
|
||||
properties:
|
||||
fanSpeed:
|
||||
@ -11162,7 +11193,7 @@ paths:
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.BatchDeleteReq'
|
||||
$ref: '#/definitions/dto.OperateByID'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@ -11176,7 +11207,7 @@ paths:
|
||||
- db: backup_accounts
|
||||
input_column: id
|
||||
input_value: id
|
||||
isList: true
|
||||
isList: false
|
||||
output_column: type
|
||||
output_value: types
|
||||
bodyKeys:
|
||||
@ -12397,6 +12428,143 @@ paths:
|
||||
summary: Update fail2ban conf by file
|
||||
tags:
|
||||
- Fail2ban
|
||||
/toolbox/ftp:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 创建 FTP 账户
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.FtpCreate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Create FTP user
|
||||
tags:
|
||||
- FTP
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- user
|
||||
- path
|
||||
formatEN: create FTP [user][path]
|
||||
formatZH: 创建 FTP 账户 [user][path]
|
||||
paramKeys: []
|
||||
/toolbox/ftp/del:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 删除 FTP 账户
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.BatchDeleteReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Delete FTP user
|
||||
tags:
|
||||
- FTP
|
||||
x-panel-log:
|
||||
BeforeFunctions:
|
||||
- db: ftps
|
||||
input_column: id
|
||||
input_value: ids
|
||||
isList: true
|
||||
output_column: user
|
||||
output_value: users
|
||||
bodyKeys:
|
||||
- ids
|
||||
formatEN: delete FTP users [users]
|
||||
formatZH: 删除 FTP 账户 [users]
|
||||
paramKeys: []
|
||||
/toolbox/ftp/search:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取 FTP 账户列表分页
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SearchWithPage'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.PageResult'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Page FTP user
|
||||
tags:
|
||||
- FTP
|
||||
/toolbox/ftp/sync:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 同步 FTP 账户
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.BatchDeleteReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Sync FTP user
|
||||
tags:
|
||||
- FTP
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: sync FTP users
|
||||
formatZH: 同步 FTP 账户
|
||||
paramKeys: []
|
||||
/toolbox/ftp/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 修改 FTP 账户
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.FtpUpdate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Update FTP user
|
||||
tags:
|
||||
- FTP
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- user
|
||||
- path
|
||||
formatEN: update FTP [user][path]
|
||||
formatZH: 修改 FTP 账户 [user][path]
|
||||
paramKeys: []
|
||||
/toolbox/scan:
|
||||
post:
|
||||
description: 扫描系统垃圾文件
|
||||
|
@ -76,4 +76,26 @@ export namespace Toolbox {
|
||||
ips: Array<string>;
|
||||
operate: string;
|
||||
}
|
||||
|
||||
export interface FtpInfo {
|
||||
id: number;
|
||||
user: string;
|
||||
password: string;
|
||||
status: string;
|
||||
path: string;
|
||||
description: string;
|
||||
}
|
||||
export interface FtpCreate {
|
||||
user: string;
|
||||
password: string;
|
||||
path: string;
|
||||
description: string;
|
||||
}
|
||||
export interface FtpUpdate {
|
||||
id: number;
|
||||
password: string;
|
||||
status: string;
|
||||
path: string;
|
||||
description: string;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import http from '@/api';
|
||||
import { UpdateByFile } from '../interface';
|
||||
import { ReqPage, ResPage, UpdateByFile } from '../interface';
|
||||
import { Toolbox } from '../interface/toolbox';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
|
||||
// device
|
||||
export const getDeviceBase = () => {
|
||||
@ -68,3 +69,32 @@ export const updateFail2ban = (param: Toolbox.Fail2banUpdate) => {
|
||||
export const updateFail2banByFile = (param: UpdateByFile) => {
|
||||
return http.post(`/toolbox/fail2ban/update/byconf`, param, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
// ftp
|
||||
export const searchFtp = (param: ReqPage) => {
|
||||
return http.post<ResPage<Toolbox.FtpInfo>>(`/toolbox/ftp/search`, param);
|
||||
};
|
||||
|
||||
export const syncFtp = () => {
|
||||
return http.post(`/toolbox/ftp/sync`);
|
||||
};
|
||||
|
||||
export const createFtp = (params: Toolbox.FtpCreate) => {
|
||||
let request = deepCopy(params) as Toolbox.FtpCreate;
|
||||
if (request.password) {
|
||||
request.password = Base64.encode(request.password);
|
||||
}
|
||||
return http.post(`/toolbox/ftp`, request);
|
||||
};
|
||||
|
||||
export const updateFtp = (params: Toolbox.FtpUpdate) => {
|
||||
let request = deepCopy(params) as Toolbox.FtpUpdate;
|
||||
if (request.password) {
|
||||
request.password = Base64.encode(request.password);
|
||||
}
|
||||
return http.post(`/toolbox/ftp/update`, request);
|
||||
};
|
||||
|
||||
export const deleteFtp = (params: { ids: number[] }) => {
|
||||
return http.post(`/toolbox/ftp/del`, params);
|
||||
};
|
||||
|
@ -1029,6 +1029,14 @@ const message = {
|
||||
logPath: 'Log Path',
|
||||
logPathHelper: 'Default is /var/log/secure or /var/log/auth.log',
|
||||
},
|
||||
ftp: {
|
||||
ftp: 'FTP Account',
|
||||
enableHelper:
|
||||
'Enabling the selected FTP account will restore its access permissions. Do you want to continue?',
|
||||
disableHelper:
|
||||
'Disabling the selected FTP account will revoke its access permissions. Do you want to continue?',
|
||||
syncHelper: 'Sync FTP account data between server and database. Do you want to continue?',
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
panelLog: 'Panel logs',
|
||||
|
@ -976,6 +976,12 @@ const message = {
|
||||
logPath: '日誌路徑',
|
||||
logPathHelper: '預設為 /var/log/secure 或者 /var/log/auth.log',
|
||||
},
|
||||
ftp: {
|
||||
ftp: 'FTP 帳戶',
|
||||
enableHelper: '啟用選取的 FTP 帳號後,該 FTP 帳號將恢復訪問權限,是否繼續操作?',
|
||||
disableHelper: '停用選取的 FTP 帳號後,該 FTP 帳號將失去訪問權限,是否繼續操作?',
|
||||
syncHelper: '同步伺服器與資料庫中的 FTP 帳戶資料,是否繼續操作?',
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
panelLog: '面板日誌',
|
||||
|
@ -977,6 +977,12 @@ const message = {
|
||||
logPath: '日志路径',
|
||||
logPathHelper: '默认 /var/log/secure 或者 /var/log/auth.log',
|
||||
},
|
||||
ftp: {
|
||||
ftp: 'FTP 账户',
|
||||
enableHelper: '启用选中的 FTP 账号后,该 FTP 账号恢复访问权限,是否继续操作?',
|
||||
disableHelper: '停用选中的 FTP 账号后,该 FTP 账号将失去访问权限,是否继续操作?',
|
||||
syncHelper: '同步服务器与数据库中的 FTP 账户数据,是否继续操作?',
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
panelLog: '面板日志',
|
||||
|
@ -37,6 +37,16 @@ const toolboxRouter = {
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'ftp',
|
||||
name: 'FTP',
|
||||
component: () => import('@/views/toolbox/ftp/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/toolbox',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'fail2Ban',
|
||||
name: 'Fail2ban',
|
||||
|
268
frontend/src/views/toolbox/ftp/index.vue
Normal file
268
frontend/src/views/toolbox/ftp/index.vue
Normal file
@ -0,0 +1,268 @@
|
||||
<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
|
||||
</el-button>
|
||||
<el-button @click="onSync()">
|
||||
{{ $t('commons.button.sync') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="selects.length === 0" @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.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()" />
|
||||
<OperateDialog @search="search" ref="dialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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 OperateDialog from '@/views/toolbox/ftp/operate/index.vue';
|
||||
import { Toolbox } from '@/api/interface/toolbox';
|
||||
|
||||
const loading = ref();
|
||||
const selects = ref<any>([]);
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'ftp-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
orderBy: 'created_at',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
|
||||
const opRef = ref();
|
||||
const dialogRef = ref();
|
||||
const operateIDs = 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;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
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'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
row.status = status === 'enable' ? 'Enable' : 'Disable';
|
||||
await updateFtp(row);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
});
|
||||
};
|
||||
|
||||
const onOpenDialog = async (title: string, rowData: Partial<Toolbox.FtpInfo> = {}) => {
|
||||
let params = {
|
||||
title,
|
||||
rowData: { ...rowData },
|
||||
};
|
||||
dialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const onSync = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('toolbox.ftp.syncHelper'), i18n.global.t('commons.button.sync'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await syncFtp()
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('toolbox.ftp.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = async (row: Toolbox.FtpInfo | null) => {
|
||||
let names = [];
|
||||
let ids = [];
|
||||
if (row) {
|
||||
ids = [row.id];
|
||||
names = [row.user];
|
||||
} else {
|
||||
for (const item of selects.value) {
|
||||
names.push(item.user);
|
||||
ids.push(item.id);
|
||||
}
|
||||
}
|
||||
operateIDs.value = ids;
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
names: names,
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('cronjob.cronTask'),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
]),
|
||||
api: null,
|
||||
params: null,
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmitDelete = async () => {
|
||||
loading.value = true;
|
||||
await deleteFtp({ ids: operateIDs.value })
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
disabled: (row: Toolbox.FtpInfo) => {
|
||||
return row.status === 'deleted';
|
||||
},
|
||||
click: (row: Toolbox.FtpInfo) => {
|
||||
onOpenDialog('edit', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
disabled: (row: Toolbox.FtpInfo) => {
|
||||
return row.status === 'deleted';
|
||||
},
|
||||
click: (row: Toolbox.FtpInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
137
frontend/src/views/toolbox/ftp/operate/index.vue
Normal file
137
frontend/src/views/toolbox/ftp/operate/index.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<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="title"
|
||||
:hideResource="dialogData.title === 'add'"
|
||||
:resource="dialogData.rowData?.user"
|
||||
:back="handleClose"
|
||||
/>
|
||||
</template>
|
||||
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('commons.login.username')" prop="user">
|
||||
<el-input
|
||||
:disabled="dialogData.title === 'edit'"
|
||||
clearable
|
||||
v-model.trim="dialogData.rowData!.user"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input clearable v-model="dialogData.rowData!.password" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('file.root')" prop="path">
|
||||
<el-input v-model="dialogData.rowData!.path">
|
||||
<template #prepend>
|
||||
<FileList @choose="loadDir" :dir="true"></FileList>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||
<el-input type="textarea" :rows="3" clearable v-model="dialogData.rowData!.description" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Toolbox } from '@/api/interface/toolbox';
|
||||
import { createFtp, updateFtp } from '@/api/modules/toolbox';
|
||||
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
rowData?: Toolbox.FtpInfo;
|
||||
getTableList?: () => Promise<any>;
|
||||
}
|
||||
const loading = ref();
|
||||
const title = ref<string>('');
|
||||
const drawerVisible = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
user: [Rules.requiredInput, Rules.noSpace],
|
||||
password: [Rules.requiredInput],
|
||||
path: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const loadDir = async (path: string) => {
|
||||
dialogData.value.rowData!.path = path;
|
||||
};
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (dialogData.value.title === 'edit') {
|
||||
loading.value = true;
|
||||
await updateFtp(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
drawerVisible.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await createFtp(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -61,6 +61,10 @@ const buttons = [
|
||||
label: i18n.global.t('menu.supervisor'),
|
||||
path: '/toolbox/supervisor',
|
||||
},
|
||||
{
|
||||
label: 'FTP',
|
||||
path: '/toolbox/ftp',
|
||||
},
|
||||
{
|
||||
label: 'Fail2ban',
|
||||
path: '/toolbox/fail2ban',
|
||||
|
Loading…
x
Reference in New Issue
Block a user