mirror of https://github.com/1Panel-dev/1Panel
ssongliu
6 months ago
committed by
GitHub
24 changed files with 1782 additions and 9 deletions
@ -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) |
||||
} |
@ -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"` |
||||
} |
@ -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"` |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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> |
@ -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> |
Loading…
Reference in new issue