mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a35141dd | ||
|
|
f6c334bdad | ||
|
|
cbb119117b | ||
|
|
cae0a6118f | ||
|
|
3c0dc7459c | ||
|
|
ca0c96cb12 | ||
|
|
ed599265e4 | ||
|
|
864bff574a | ||
|
|
3b1df05ae0 | ||
|
|
464392f7e1 | ||
|
|
b562d9f8e8 | ||
|
|
74bd997f07 | ||
|
|
12c6fe6331 | ||
|
|
eb4d550305 | ||
|
|
9a635e99b2 | ||
|
|
03b04eea6b | ||
|
|
d2740308a1 | ||
|
|
6f86dda1aa | ||
|
|
0194362a35 |
@@ -1 +0,0 @@
|
||||
custom: ["https://afdian.net/a/1Panel"]
|
||||
@@ -51,16 +51,38 @@ func (b *BaseApi) UpdateClam(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Clam
|
||||
// @Summary Update clam status
|
||||
// @Description 修改扫描规则状态
|
||||
// @Accept json
|
||||
// @Param request body dto.ClamUpdateStatus true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/clam/status/update [post]
|
||||
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"修改扫描规则 [name] 状态为 [status]","formatEN":"change the status of clam [name] to [status]."}
|
||||
func (b *BaseApi) UpdateClamStatus(c *gin.Context) {
|
||||
var req dto.ClamUpdateStatus
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := clamService.UpdateStatus(req.ID, req.Status); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Clam
|
||||
// @Summary Page clam
|
||||
// @Description 获取扫描规则列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Param request body dto.SearchClamWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/clam/search [post]
|
||||
func (b *BaseApi) SearchClam(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
var req dto.SearchClamWithPage
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type SearchClamWithPage struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=name status created_at"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
}
|
||||
|
||||
type ClamBaseInfo struct {
|
||||
Version string `json:"version"`
|
||||
IsActive bool `json:"isActive"`
|
||||
@@ -19,10 +26,12 @@ type ClamInfo struct {
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
LastHandleDate string `json:"lastHandleDate"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -56,9 +65,11 @@ type ClamLog struct {
|
||||
|
||||
type ClamCreate struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -69,9 +80,15 @@ type ClamUpdate struct {
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ClamUpdateStatus struct {
|
||||
ID uint `json:"id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ClamDelete struct {
|
||||
RemoveRecord bool `json:"removeRecord"`
|
||||
RemoveInfected bool `json:"removeInfected"`
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
type WebsiteSearch struct {
|
||||
dto.PageInfo
|
||||
Name string `json:"name"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=primary_domain type status created_at"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=primary_domain type status created_at expire_date"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
WebsiteGroupID uint `json:"websiteGroupId"`
|
||||
}
|
||||
@@ -55,7 +55,7 @@ type WebsiteUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
PrimaryDomain string `json:"primaryDomain" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
WebsiteGroupID uint `json:"webSiteGroupID" validate:"required"`
|
||||
WebsiteGroupID uint `json:"webSiteGroupID"`
|
||||
ExpireDate string `json:"expireDate"`
|
||||
IPV6 bool `json:"IPV6"`
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ type Clam struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||
Status string `gorm:"type:varchar(64)" json:"status"`
|
||||
Path string `gorm:"type:varchar(64);not null" json:"path"`
|
||||
InfectedStrategy string `gorm:"type:varchar(64)" json:"infectedStrategy"`
|
||||
InfectedDir string `gorm:"type:varchar(64)" json:"infectedDir"`
|
||||
Spec string `gorm:"type:varchar(64)" json:"spec"`
|
||||
EntryID int `gorm:"type:varchar(64)" json:"entryID"`
|
||||
Description string `gorm:"type:varchar(64)" json:"description"`
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ type IClamRepo interface {
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
Get(opts ...DBOption) (model.Clam, error)
|
||||
List(opts ...DBOption) ([]model.Clam, error)
|
||||
}
|
||||
|
||||
func NewIClamRepo() IClamRepo {
|
||||
@@ -29,6 +30,16 @@ func (u *ClamRepo) Get(opts ...DBOption) (model.Clam, error) {
|
||||
return clam, err
|
||||
}
|
||||
|
||||
func (u *ClamRepo) List(opts ...DBOption) ([]model.Clam, error) {
|
||||
var clam []model.Clam
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&clam).Error
|
||||
return clam, err
|
||||
}
|
||||
|
||||
func (u *ClamRepo) Page(page, size int, opts ...DBOption) (int64, []model.Clam, error) {
|
||||
var users []model.Clam
|
||||
db := global.DB.Model(&model.Clam{})
|
||||
|
||||
@@ -96,7 +96,7 @@ func (w *WebsiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.Web
|
||||
db := getDb(opts...).Model(&model.Website{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Preload("WebsiteSSL").Find(&websites).Error
|
||||
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("WebsiteSSL").Find(&websites).Error
|
||||
return count, websites, err
|
||||
}
|
||||
|
||||
|
||||
+108
-45
@@ -12,13 +12,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -37,9 +40,10 @@ type ClamService struct {
|
||||
type IClamService interface {
|
||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||
Operate(operate string) error
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ClamCreate) error
|
||||
Update(req dto.ClamUpdate) error
|
||||
UpdateStatus(id uint, status string) error
|
||||
Delete(req dto.ClamDelete) error
|
||||
HandleOnce(req dto.OperateByID) error
|
||||
LoadFile(req dto.ClamFileReq) (string, error)
|
||||
@@ -54,19 +58,19 @@ func NewIClamService() IClamService {
|
||||
return &ClamService{}
|
||||
}
|
||||
|
||||
func (f *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
var baseInfo dto.ClamBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.FreshVersion = "-"
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
f.serviceName = clamServiceNameCentOs
|
||||
c.serviceName = clamServiceNameCentOs
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
f.serviceName = clamServiceNameUbuntu
|
||||
c.serviceName = clamServiceNameUbuntu
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
}
|
||||
@@ -75,44 +79,45 @@ func (f *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
baseInfo.FreshIsExist = true
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
||||
}
|
||||
if !cmd.Which("clamdscan") {
|
||||
baseInfo.IsActive = false
|
||||
}
|
||||
|
||||
if baseInfo.IsActive {
|
||||
version, err := cmd.Exec("clamdscan --version")
|
||||
if err != nil {
|
||||
return baseInfo, nil
|
||||
}
|
||||
if strings.Contains(version, "/") {
|
||||
baseInfo.Version = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
|
||||
} else {
|
||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||
if err == nil {
|
||||
if strings.Contains(version, "/") {
|
||||
baseInfo.Version = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
|
||||
} else {
|
||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||
}
|
||||
}
|
||||
}
|
||||
if baseInfo.FreshIsActive {
|
||||
version, err := cmd.Exec("freshclam --version")
|
||||
if err != nil {
|
||||
return baseInfo, nil
|
||||
}
|
||||
if strings.Contains(version, "/") {
|
||||
baseInfo.FreshVersion = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
|
||||
} else {
|
||||
baseInfo.FreshVersion = strings.TrimPrefix(version, "ClamAV ")
|
||||
if err == nil {
|
||||
if strings.Contains(version, "/") {
|
||||
baseInfo.FreshVersion = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
|
||||
} else {
|
||||
baseInfo.FreshVersion = strings.TrimPrefix(version, "ClamAV ")
|
||||
}
|
||||
}
|
||||
}
|
||||
return baseInfo, nil
|
||||
}
|
||||
|
||||
func (f *ClamService) Operate(operate string) error {
|
||||
func (c *ClamService) Operate(operate string) error {
|
||||
switch operate {
|
||||
case "start", "restart", "stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", operate, f.serviceName)
|
||||
stdout, err := cmd.Execf("systemctl %s %s", operate, c.serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, f.serviceName, stdout)
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
case "fresh-start", "fresh-restart", "fresh-stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, f.serviceName, stdout)
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
@@ -120,8 +125,8 @@ func (f *ClamService) Operate(operate string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ClamService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, commands, err := clamRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
|
||||
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
|
||||
total, commands, err := clamRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
@@ -151,7 +156,7 @@ func (f *ClamService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (f *ClamService) Create(req dto.ClamCreate) error {
|
||||
func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByName(req.Name))
|
||||
if clam.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
@@ -162,13 +167,21 @@ func (f *ClamService) Create(req dto.ClamCreate) error {
|
||||
if clam.InfectedStrategy == "none" || clam.InfectedStrategy == "remove" {
|
||||
clam.InfectedDir = ""
|
||||
}
|
||||
if len(req.Spec) != 0 {
|
||||
entryID, err := xpack.StartClam(clam, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clam.EntryID = entryID
|
||||
clam.Status = constant.StatusEnable
|
||||
}
|
||||
if err := clamRepo.Create(&clam); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ClamService) Update(req dto.ClamUpdate) error {
|
||||
func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByName(req.Name))
|
||||
if clam.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
@@ -176,11 +189,36 @@ func (f *ClamService) Update(req dto.ClamUpdate) error {
|
||||
if req.InfectedStrategy == "none" || req.InfectedStrategy == "remove" {
|
||||
req.InfectedDir = ""
|
||||
}
|
||||
var clamItem model.Clam
|
||||
if err := copier.Copy(&clamItem, &req); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
clamItem.EntryID = clam.EntryID
|
||||
upMap := map[string]interface{}{}
|
||||
if len(clam.Spec) != 0 && clam.EntryID != 0 {
|
||||
global.Cron.Remove(cron.EntryID(clamItem.EntryID))
|
||||
upMap["entry_id"] = 0
|
||||
}
|
||||
if len(req.Spec) == 0 {
|
||||
upMap["status"] = ""
|
||||
upMap["entry_id"] = 0
|
||||
}
|
||||
if len(req.Spec) != 0 && clam.Status != constant.StatusDisable {
|
||||
newEntryID, err := xpack.StartClam(clamItem, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upMap["entry_id"] = newEntryID
|
||||
}
|
||||
if len(clam.Spec) == 0 && len(req.Spec) != 0 {
|
||||
upMap["status"] = constant.StatusEnable
|
||||
}
|
||||
|
||||
upMap["name"] = req.Name
|
||||
upMap["path"] = req.Path
|
||||
upMap["infected_dir"] = req.InfectedDir
|
||||
upMap["infected_strategy"] = req.InfectedStrategy
|
||||
upMap["spec"] = req.Spec
|
||||
upMap["description"] = req.Description
|
||||
if err := clamRepo.Update(req.ID, upMap); err != nil {
|
||||
return err
|
||||
@@ -188,7 +226,29 @@ func (f *ClamService) Update(req dto.ClamUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ClamService) Delete(req dto.ClamDelete) error {
|
||||
func (c *ClamService) UpdateStatus(id uint, status string) error {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
|
||||
if clam.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
var (
|
||||
entryID int
|
||||
err error
|
||||
)
|
||||
if status == constant.StatusEnable {
|
||||
entryID, err = xpack.StartClam(clam, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
global.Cron.Remove(cron.EntryID(clam.EntryID))
|
||||
global.LOG.Infof("stop cronjob entryID: %v", clam.EntryID)
|
||||
}
|
||||
|
||||
return clamRepo.Update(clam.ID, map[string]interface{}{"status": status, "entry_id": entryID})
|
||||
}
|
||||
|
||||
func (c *ClamService) Delete(req dto.ClamDelete) error {
|
||||
for _, id := range req.Ids {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
|
||||
if clam.ID == 0 {
|
||||
@@ -207,7 +267,10 @@ func (u *ClamService) Delete(req dto.ClamDelete) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
if cmd.Which("clamdscan") == false {
|
||||
return buserr.New("ErrClamdscanNotFound")
|
||||
}
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if clam.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
@@ -241,13 +304,13 @@ func (u *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
|
||||
stdout, err := cmd.Execf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", string(stdout), err)
|
||||
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error) {
|
||||
func (c *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error) {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ClamID))
|
||||
if clam.ID == 0 {
|
||||
return 0, nil, constant.ErrRecordNotFound
|
||||
@@ -294,7 +357,7 @@ func (u *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, er
|
||||
}
|
||||
return int64(total), datas, nil
|
||||
}
|
||||
func (u *ClamService) LoadRecordLog(req dto.ClamLogReq) (string, error) {
|
||||
func (c *ClamService) LoadRecordLog(req dto.ClamLogReq) (string, error) {
|
||||
logPath := path.Join(global.CONF.System.DataDir, resultDir, req.ClamName, req.RecordName)
|
||||
var tail string
|
||||
if req.Tail != "0" {
|
||||
@@ -310,7 +373,7 @@ func (u *ClamService) LoadRecordLog(req dto.ClamLogReq) (string, error) {
|
||||
return string(stdout), nil
|
||||
}
|
||||
|
||||
func (u *ClamService) CleanRecord(req dto.OperateByID) error {
|
||||
func (c *ClamService) CleanRecord(req dto.OperateByID) error {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if clam.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
@@ -320,37 +383,37 @@ func (u *ClamService) CleanRecord(req dto.OperateByID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
filePath := ""
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
case "clamd-log":
|
||||
filePath = u.loadLogPath("clamd-log")
|
||||
filePath = c.loadLogPath("clamd-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/clamav.log"
|
||||
} else {
|
||||
filePath = "/var/log/clamd.scan"
|
||||
}
|
||||
case "freshclam":
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
}
|
||||
case "freshclam-log":
|
||||
filePath = u.loadLogPath("freshclam-log")
|
||||
filePath = c.loadLogPath("freshclam-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/freshclam.log"
|
||||
} else {
|
||||
filePath = "/var/log/freshclam.log"
|
||||
@@ -375,12 +438,12 @@ func (u *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
return string(stdout), nil
|
||||
}
|
||||
|
||||
func (u *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
filePath := ""
|
||||
service := ""
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
service = clamServiceNameUbuntu
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
@@ -388,7 +451,7 @@ func (u *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
case "freshclam":
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
@@ -458,16 +521,16 @@ func loadResultFromLog(pathItem string) dto.ClamLog {
|
||||
}
|
||||
return data
|
||||
}
|
||||
func (u *ClamService) loadLogPath(name string) string {
|
||||
func (c *ClamService) loadLogPath(name string) string {
|
||||
confPath := ""
|
||||
if name == "clamd-log" {
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
confPath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
} else {
|
||||
if u.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
confPath = "/etc/freshclam.conf"
|
||||
|
||||
@@ -17,15 +17,20 @@ import (
|
||||
|
||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
|
||||
var apps []model.AppInstall
|
||||
if cronjob.AppID == "all" {
|
||||
if strings.Contains(cronjob.AppID, "all") {
|
||||
apps, _ = appInstallRepo.ListBy()
|
||||
} else {
|
||||
itemID, _ := strconv.Atoi(cronjob.AppID)
|
||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
appIds := strings.Split(cronjob.AppID, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(appIds); i++ {
|
||||
itemID, _ := strconv.Atoi(appIds[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
appItems, err := appInstallRepo.ListBy(commonRepo.WithIdsIn(idItems))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apps = append(apps, app)
|
||||
apps = appItems
|
||||
}
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
@@ -232,7 +237,7 @@ type databaseHelper struct {
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||
var dbs []databaseHelper
|
||||
if cronjob.DBName == "all" {
|
||||
if strings.Contains(cronjob.DBName, "all") {
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItems, _ := mysqlRepo.List()
|
||||
for _, mysql := range mysqlItems {
|
||||
@@ -254,36 +259,42 @@ func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
itemID, _ := strconv.Atoi(cronjob.DBName)
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
|
||||
dbNames := strings.Split(cronjob.DBName, ",")
|
||||
for _, name := range dbNames {
|
||||
itemID, _ := strconv.Atoi(name)
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
|
||||
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||
var weblist []model.Website
|
||||
if cronjob.Website == "all" {
|
||||
if strings.Contains(cronjob.Website, "all") {
|
||||
weblist, _ = websiteRepo.List()
|
||||
return weblist
|
||||
}
|
||||
itemID, _ := strconv.Atoi(cronjob.Website)
|
||||
webItem, _ := websiteRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
if webItem.ID != 0 {
|
||||
weblist = append(weblist, webItem)
|
||||
websites := strings.Split(cronjob.Website, ",")
|
||||
var idItems []uint
|
||||
for i := 0; i < len(websites); i++ {
|
||||
itemID, _ := strconv.Atoi(websites[i])
|
||||
idItems = append(idItems, uint(itemID))
|
||||
}
|
||||
weblist, _ = websiteRepo.List(commonRepo.WithIdsIn(idItems))
|
||||
return weblist
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ func (f *FileService) Create(op request.FileCreate) error {
|
||||
}
|
||||
fo := files.NewFileOp()
|
||||
if fo.Stat(op.Path) {
|
||||
return buserr.New(constant.ErrFileIsExit)
|
||||
return buserr.New(constant.ErrFileIsExist)
|
||||
}
|
||||
mode := op.Mode
|
||||
if mode == 0 {
|
||||
@@ -273,7 +273,7 @@ func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error {
|
||||
func (f *FileService) Compress(c request.FileCompress) error {
|
||||
fo := files.NewFileOp()
|
||||
if !c.Replace && fo.Stat(filepath.Join(c.Dst, c.Name)) {
|
||||
return buserr.New(constant.ErrFileIsExit)
|
||||
return buserr.New(constant.ErrFileIsExist)
|
||||
}
|
||||
return fo.Compress(c.Files, c.Dst, c.Name, files.CompressType(c.Type), c.Secret)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ var (
|
||||
ErrPathNotFound = "ErrPathNotFound"
|
||||
ErrMovePathFailed = "ErrMovePathFailed"
|
||||
ErrLinkPathNotFound = "ErrLinkPathNotFound"
|
||||
ErrFileIsExit = "ErrFileIsExit"
|
||||
ErrFileIsExist = "ErrFileIsExist"
|
||||
ErrFileUpload = "ErrFileUpload"
|
||||
ErrFileDownloadDir = "ErrFileDownloadDir"
|
||||
ErrCmdNotFound = "ErrCmdNotFound"
|
||||
|
||||
@@ -74,7 +74,7 @@ ErrFileToLarge: "file is too large"
|
||||
ErrPathNotFound: "Path is not found"
|
||||
ErrMovePathFailed: "The target path cannot contain the original path!"
|
||||
ErrLinkPathNotFound: "Target path does not exist!"
|
||||
ErrFileIsExit: "File or directory already exists!"
|
||||
ErrFileIsExist: "File or directory already exists!"
|
||||
ErrFileUpload: "Failed to upload file {{.name}} {{.detail}}"
|
||||
ErrFileDownloadDir: "Download folder not supported"
|
||||
ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first"
|
||||
@@ -174,6 +174,7 @@ CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{
|
||||
#toolbox
|
||||
ErrNotExistUser: "The current user does not exist. Please modify and retry!"
|
||||
ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, please check and try again!"
|
||||
ErrClamdscanNotFound: "The clamdscan command was not detected, please refer to the documentation to install it!"
|
||||
|
||||
#waf
|
||||
ErrScope: "Modification of this configuration is not supported"
|
||||
|
||||
@@ -75,7 +75,7 @@ ErrFileToLarge: "文件超過10M,無法打開"
|
||||
ErrPathNotFound: "目錄不存在"
|
||||
ErrMovePathFailed: "目標路徑不能包含原路徑!"
|
||||
ErrLinkPathNotFound: "目標路徑不存在!"
|
||||
ErrFileIsExit: "文件或文件夾已存在!"
|
||||
ErrFileIsExist: "文件或文件夾已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
|
||||
ErrFileDownloadDir: "不支持下載文件夾"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
||||
@@ -175,6 +175,7 @@ CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .p
|
||||
#toolbox
|
||||
ErrNotExistUser: "當前使用者不存在,請修改後重試!"
|
||||
ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!"
|
||||
ErrClamdscanNotFound: "未偵測到 clamdscan 指令,請參考文件安裝!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支援修改此配置"
|
||||
|
||||
@@ -74,7 +74,7 @@ ErrFileToLarge: "文件超过10M,无法打开"
|
||||
ErrPathNotFound: "目录不存在"
|
||||
ErrMovePathFailed: "目标路径不能包含原路径!"
|
||||
ErrLinkPathNotFound: "目标路径不存在!"
|
||||
ErrFileIsExit: "文件或文件夹已存在!"
|
||||
ErrFileIsExist: "文件或文件夹已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}"
|
||||
ErrFileDownloadDir: "不支持下载文件夹"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||
@@ -177,6 +177,7 @@ CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .p
|
||||
#toolbox
|
||||
ErrNotExistUser: "当前用户不存在,请修改后重试!"
|
||||
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
|
||||
ErrClamdscanNotFound: "未检测到 clamdscan 命令,请参考文档安装!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支持修改此配置"
|
||||
|
||||
@@ -92,6 +92,7 @@ func Init() {
|
||||
migrations.AddForward,
|
||||
migrations.AddShellColumn,
|
||||
migrations.AddClam,
|
||||
migrations.AddClamStatus,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -278,3 +278,13 @@ var AddClam = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddClamStatus = &gormigrate.Migration{
|
||||
ID: "20240716-add-clam-status",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Clam{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
toolboxRouter.POST("/clam/base", baseApi.LoadClamBaseInfo)
|
||||
toolboxRouter.POST("/clam/operate", baseApi.OperateClam)
|
||||
toolboxRouter.POST("/clam/update", baseApi.UpdateClam)
|
||||
toolboxRouter.POST("/clam/status/update", baseApi.UpdateClamStatus)
|
||||
toolboxRouter.POST("/clam/del", baseApi.DeleteClam)
|
||||
toolboxRouter.POST("/clam/handle", baseApi.HandleClamScan)
|
||||
}
|
||||
|
||||
@@ -203,8 +203,11 @@ func SudoHandleCmd() string {
|
||||
}
|
||||
|
||||
func Which(name string) bool {
|
||||
_, err := exec.LookPath(name)
|
||||
return err == nil
|
||||
stdout, err := Execf("which %s", name)
|
||||
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0 && strings.HasPrefix(stdout, "/")) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ExecShellWithTimeOut(cmdStr, workdir string, logger *log.Logger, timeout time.Duration) error {
|
||||
|
||||
@@ -230,13 +230,17 @@ func RemoveRepeatElement(a interface{}) (ret []interface{}) {
|
||||
}
|
||||
|
||||
func LoadSizeUnit(value float64) string {
|
||||
if value > 1048576 {
|
||||
return fmt.Sprintf("%vM", value/1048576)
|
||||
val := int64(value)
|
||||
if val%1024 != 0 {
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
if value > 1024 {
|
||||
return fmt.Sprintf("%vK", value/1024)
|
||||
if val > 1048576 {
|
||||
return fmt.Sprintf("%vM", val/1048576)
|
||||
}
|
||||
return fmt.Sprintf("%v", value)
|
||||
if val > 1024 {
|
||||
return fmt.Sprintf("%vK", val/1024)
|
||||
}
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
|
||||
func LoadSizeUnit2F(value float64) string {
|
||||
|
||||
+122
-71
@@ -104,20 +104,25 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
file.LinkPath = GetSymlink(op.Path)
|
||||
}
|
||||
if op.Expand {
|
||||
if file.IsDir {
|
||||
if err := file.listChildren(op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
} else {
|
||||
if err := file.getContent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := handleExpansion(file, op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func handleExpansion(file *FileInfo, op FileOption) error {
|
||||
if file.IsDir {
|
||||
return file.listChildren(op)
|
||||
}
|
||||
|
||||
if !file.IsDetail {
|
||||
return file.getContent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, total int, err error) {
|
||||
cmd := exec.Command("find", f.Path, "-name", fmt.Sprintf("*%s*", search))
|
||||
output, err := cmd.StdoutPipe()
|
||||
@@ -203,63 +208,82 @@ func (f *FileInfo) listChildren(option FileOption) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dirFiles, err := afs.ReadDir(f.Path)
|
||||
files, err = f.getFiles(afs, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
dirs []FileSearchInfo
|
||||
fileList []FileSearchInfo
|
||||
)
|
||||
for _, file := range dirFiles {
|
||||
info := FileSearchInfo{
|
||||
Path: f.Path,
|
||||
FileInfo: file,
|
||||
}
|
||||
if file.IsDir() {
|
||||
dirs = append(dirs, info)
|
||||
} else {
|
||||
fileList = append(fileList, info)
|
||||
}
|
||||
}
|
||||
sortFileList(dirs, option.SortBy, option.SortOrder)
|
||||
sortFileList(fileList, option.SortBy, option.SortOrder)
|
||||
files = append(dirs, fileList...)
|
||||
}
|
||||
|
||||
items, err := f.processFiles(files, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if option.ContainSub {
|
||||
f.ItemTotal = total
|
||||
}
|
||||
start := (option.Page - 1) * option.PageSize
|
||||
end := option.PageSize + start
|
||||
var result []*FileInfo
|
||||
if start < 0 || start > f.ItemTotal || end < 0 || start > end {
|
||||
result = items
|
||||
} else {
|
||||
if end > f.ItemTotal {
|
||||
result = items[start:]
|
||||
} else {
|
||||
result = items[start:end]
|
||||
}
|
||||
}
|
||||
|
||||
f.Items = result
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileInfo) getFiles(afs *afero.Afero, option FileOption) ([]FileSearchInfo, error) {
|
||||
dirFiles, err := afs.ReadDir(f.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
dirs []FileSearchInfo
|
||||
fileList []FileSearchInfo
|
||||
)
|
||||
|
||||
for _, file := range dirFiles {
|
||||
info := FileSearchInfo{
|
||||
Path: f.Path,
|
||||
FileInfo: file,
|
||||
}
|
||||
if file.IsDir() {
|
||||
dirs = append(dirs, info)
|
||||
} else {
|
||||
fileList = append(fileList, info)
|
||||
}
|
||||
}
|
||||
|
||||
sortFileList(dirs, option.SortBy, option.SortOrder)
|
||||
sortFileList(fileList, option.SortBy, option.SortOrder)
|
||||
|
||||
return append(dirs, fileList...), nil
|
||||
}
|
||||
|
||||
func (f *FileInfo) processFiles(files []FileSearchInfo, option FileOption) ([]*FileInfo, error) {
|
||||
var items []*FileInfo
|
||||
|
||||
for _, df := range files {
|
||||
if option.Dir && !df.IsDir() {
|
||||
if shouldSkipFile(df, option) {
|
||||
continue
|
||||
}
|
||||
name := df.Name()
|
||||
fPath := path.Join(df.Path, df.Name())
|
||||
if option.Search != "" {
|
||||
if option.ContainSub {
|
||||
fPath = df.Path
|
||||
name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/")
|
||||
} else {
|
||||
lowerName := strings.ToLower(name)
|
||||
lowerSearch := strings.ToLower(option.Search)
|
||||
if !strings.Contains(lowerName, lowerSearch) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name, fPath := f.getFilePathAndName(option, df)
|
||||
|
||||
if !option.ShowHidden && IsHidden(name) {
|
||||
continue
|
||||
}
|
||||
f.ItemTotal++
|
||||
isSymlink, isInvalidLink := false, false
|
||||
if IsSymlink(df.Mode()) {
|
||||
isSymlink = true
|
||||
info, err := f.Fs.Stat(fPath)
|
||||
if err == nil {
|
||||
df.FileInfo = info
|
||||
} else {
|
||||
isInvalidLink = true
|
||||
}
|
||||
}
|
||||
|
||||
isSymlink, isInvalidLink := f.checkSymlink(df)
|
||||
|
||||
file := &FileInfo{
|
||||
Fs: f.Fs,
|
||||
@@ -294,24 +318,53 @@ func (f *FileInfo) listChildren(option FileOption) error {
|
||||
}
|
||||
items = append(items, file)
|
||||
}
|
||||
if option.ContainSub {
|
||||
f.ItemTotal = total
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func shouldSkipFile(df FileSearchInfo, option FileOption) bool {
|
||||
if option.Dir && !df.IsDir() {
|
||||
return true
|
||||
}
|
||||
start := (option.Page - 1) * option.PageSize
|
||||
end := option.PageSize + start
|
||||
var result []*FileInfo
|
||||
if start < 0 || start > f.ItemTotal || end < 0 || start > end {
|
||||
result = items
|
||||
} else {
|
||||
if end > f.ItemTotal {
|
||||
result = items[start:]
|
||||
} else {
|
||||
result = items[start:end]
|
||||
|
||||
if option.Search != "" && !option.ContainSub {
|
||||
lowerName := strings.ToLower(df.Name())
|
||||
lowerSearch := strings.ToLower(option.Search)
|
||||
if !strings.Contains(lowerName, lowerSearch) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
f.Items = result
|
||||
return nil
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FileInfo) getFilePathAndName(option FileOption, df FileSearchInfo) (string, string) {
|
||||
name := df.Name()
|
||||
fPath := path.Join(df.Path, df.Name())
|
||||
|
||||
if option.Search != "" && option.ContainSub {
|
||||
fPath = df.Path
|
||||
name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/")
|
||||
}
|
||||
|
||||
return name, fPath
|
||||
}
|
||||
|
||||
func (f *FileInfo) checkSymlink(df FileSearchInfo) (bool, bool) {
|
||||
isSymlink := false
|
||||
isInvalidLink := false
|
||||
|
||||
if IsSymlink(df.Mode()) {
|
||||
isSymlink = true
|
||||
info, err := f.Fs.Stat(path.Join(df.Path, df.Name()))
|
||||
if err == nil {
|
||||
df.FileInfo = info
|
||||
} else {
|
||||
isInvalidLink = true
|
||||
}
|
||||
}
|
||||
|
||||
return isSymlink, isInvalidLink
|
||||
}
|
||||
|
||||
func (f *FileInfo) getContent() error {
|
||||
@@ -326,10 +379,8 @@ func (f *FileInfo) getContent() error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !f.IsDetail {
|
||||
if len(cByte) > 0 && DetectBinary(cByte) {
|
||||
return buserr.New(constant.ErrFileCanNotRead)
|
||||
}
|
||||
if len(cByte) > 0 && DetectBinary(cByte) {
|
||||
return buserr.New(constant.ErrFileCanNotRead)
|
||||
}
|
||||
f.Content = string(cByte)
|
||||
return nil
|
||||
|
||||
@@ -7,6 +7,10 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
)
|
||||
|
||||
func RemoveTamper(website string) {}
|
||||
@@ -27,3 +31,7 @@ func LoadRequestTransport() *http.Transport {
|
||||
func LoadGpuInfo() []interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
|
||||
return 0, buserr.New(constant.ErrXpackNotFound)
|
||||
}
|
||||
|
||||
+76
-4
@@ -11500,6 +11500,58 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/clam/status/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改扫描规则状态",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Clam"
|
||||
],
|
||||
"summary": "Update clam status",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ClamUpdateStatus"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "clams",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id",
|
||||
"status"
|
||||
],
|
||||
"formatEN": "change the status of clam [name] to [status].",
|
||||
"formatZH": "修改扫描规则 [name] 状态为 [status]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/clam/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -15570,6 +15622,12 @@ const docTemplate = `{
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -15665,6 +15723,20 @@ const docTemplate = `{
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamUpdateStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18468,7 +18540,7 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"status",
|
||||
"state",
|
||||
"created_at"
|
||||
]
|
||||
},
|
||||
@@ -22601,7 +22673,8 @@ const docTemplate = `{
|
||||
"primary_domain",
|
||||
"type",
|
||||
"status",
|
||||
"created_at"
|
||||
"created_at",
|
||||
"expire_date"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
@@ -22619,8 +22692,7 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"primaryDomain",
|
||||
"webSiteGroupID"
|
||||
"primaryDomain"
|
||||
],
|
||||
"properties": {
|
||||
"IPV6": {
|
||||
|
||||
@@ -11493,6 +11493,58 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/clam/status/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改扫描规则状态",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Clam"
|
||||
],
|
||||
"summary": "Update clam status",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ClamUpdateStatus"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "clams",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id",
|
||||
"status"
|
||||
],
|
||||
"formatEN": "change the status of clam [name] to [status].",
|
||||
"formatZH": "修改扫描规则 [name] 状态为 [status]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/clam/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -15563,6 +15615,12 @@
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -15658,6 +15716,20 @@
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamUpdateStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18461,7 +18533,7 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"status",
|
||||
"state",
|
||||
"created_at"
|
||||
]
|
||||
},
|
||||
@@ -22594,7 +22666,8 @@
|
||||
"primary_domain",
|
||||
"type",
|
||||
"status",
|
||||
"created_at"
|
||||
"created_at",
|
||||
"expire_date"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
@@ -22612,8 +22685,7 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"primaryDomain",
|
||||
"webSiteGroupID"
|
||||
"primaryDomain"
|
||||
],
|
||||
"properties": {
|
||||
"IPV6": {
|
||||
|
||||
@@ -243,6 +243,10 @@ definitions:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
spec:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
dto.ClamDelete:
|
||||
properties:
|
||||
@@ -305,6 +309,15 @@ definitions:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
spec:
|
||||
type: string
|
||||
type: object
|
||||
dto.ClamUpdateStatus:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
dto.Clean:
|
||||
properties:
|
||||
@@ -2198,7 +2211,7 @@ definitions:
|
||||
orderBy:
|
||||
enum:
|
||||
- name
|
||||
- status
|
||||
- state
|
||||
- created_at
|
||||
type: string
|
||||
page:
|
||||
@@ -4974,6 +4987,7 @@ definitions:
|
||||
- type
|
||||
- status
|
||||
- created_at
|
||||
- expire_date
|
||||
type: string
|
||||
page:
|
||||
type: integer
|
||||
@@ -5004,7 +5018,6 @@ definitions:
|
||||
required:
|
||||
- id
|
||||
- primaryDomain
|
||||
- webSiteGroupID
|
||||
type: object
|
||||
request.WebsiteUpdateDir:
|
||||
properties:
|
||||
@@ -12767,6 +12780,40 @@ paths:
|
||||
summary: Page clam
|
||||
tags:
|
||||
- Clam
|
||||
/toolbox/clam/status/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 修改扫描规则状态
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.ClamUpdateStatus'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Update clam status
|
||||
tags:
|
||||
- Clam
|
||||
x-panel-log:
|
||||
BeforeFunctions:
|
||||
- db: clams
|
||||
input_column: id
|
||||
input_value: id
|
||||
isList: false
|
||||
output_column: name
|
||||
output_value: name
|
||||
bodyKeys:
|
||||
- id
|
||||
- status
|
||||
formatEN: change the status of clam [name] to [status].
|
||||
formatZH: 修改扫描规则 [name] 状态为 [status]
|
||||
paramKeys: []
|
||||
/toolbox/clam/update:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"@highlightjs/vue-plugin": "^2.1.0",
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.8",
|
||||
"@vue-office/pdf": "^2.0.2",
|
||||
"@vueuse/core": "^8.9.4",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
|
||||
@@ -24,6 +24,9 @@ export namespace Cronjob {
|
||||
backupAccounts: string;
|
||||
defaultDownload: string;
|
||||
backupAccountList: Array<string>;
|
||||
appIdList: Array<string>;
|
||||
websiteList: Array<string>;
|
||||
dbNameList: Array<string>;
|
||||
retainCopies: number;
|
||||
status: string;
|
||||
secret: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReqPage } from '.';
|
||||
import { Cronjob } from './cronjob';
|
||||
|
||||
export namespace Toolbox {
|
||||
export interface DeviceBaseInfo {
|
||||
@@ -129,10 +130,14 @@ export namespace Toolbox {
|
||||
export interface ClamInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
lastHandleDate: string;
|
||||
hasSpec: boolean;
|
||||
spec: string;
|
||||
specObj: Cronjob.SpecObj;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamCreate {
|
||||
@@ -140,6 +145,8 @@ export namespace Toolbox {
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
spec: string;
|
||||
specObj: Cronjob.SpecObj;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamUpdate {
|
||||
@@ -148,6 +155,8 @@ export namespace Toolbox {
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
spec: string;
|
||||
specObj: Cronjob.SpecObj;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamSearchLog extends ReqPage {
|
||||
|
||||
@@ -82,7 +82,7 @@ export const DownloadFile = (params: File.FileDownload) => {
|
||||
};
|
||||
|
||||
export const ComputeDirSize = (params: File.DirSizeReq) => {
|
||||
return http.post<File.DirSizeRes>('files/size', params);
|
||||
return http.post<File.DirSizeRes>('files/size', params, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const FileKeys = () => {
|
||||
|
||||
@@ -138,6 +138,9 @@ export const createClam = (params: Toolbox.ClamCreate) => {
|
||||
export const updateClam = (params: Toolbox.ClamUpdate) => {
|
||||
return http.post(`/toolbox/clam/update`, params);
|
||||
};
|
||||
export const updateClamStatus = (id: number, status: string) => {
|
||||
return http.post(`/toolbox/clam/status/update`, { id: id, status: status });
|
||||
};
|
||||
export const deleteClam = (params: { ids: number[]; removeRecord: boolean; removeInfected: boolean }) => {
|
||||
return http.post(`/toolbox/clam/del`, params);
|
||||
};
|
||||
|
||||
@@ -74,7 +74,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
localStorage.setItem('VscodeConnectInfo', JSON.stringify(addForm));
|
||||
dialogVisible.value = false;
|
||||
const vscodeUrl = `vscode://vscode-remote/ssh-remote+${addForm.username}@${addForm.host}${addForm.path}?windowId=_blank`;
|
||||
const vscodeUrl = `vscode://vscode-remote/ssh-remote+${addForm.username}@${addForm.host}:${addForm.port}${addForm.path}?windowId=_blank`;
|
||||
window.open(vscodeUrl);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,6 +69,7 @@ const message = {
|
||||
createNewFolder: 'Create new folder',
|
||||
createNewFile: 'Create new file',
|
||||
helpDoc: 'Help Document',
|
||||
unbind: 'UnBind',
|
||||
},
|
||||
search: {
|
||||
timeStart: 'Time start',
|
||||
@@ -437,6 +438,11 @@ const message = {
|
||||
remote: 'Remote',
|
||||
remoteDB: 'Remote DB',
|
||||
createRemoteDB: 'Create Remote Server',
|
||||
unBindRemoteDB: 'Unbind remote server',
|
||||
unBindForce: 'Force unbind',
|
||||
unBindForceHelper: 'Ignore all errors during the unbinding process to ensure the final operation is successful',
|
||||
unBindRemoteHelper:
|
||||
'Unbinding the remote database will only remove the binding relationship and will not directly delete the remote database',
|
||||
editRemoteDB: 'Edit Remote Server',
|
||||
localDB: 'Local DB',
|
||||
address: 'DB address',
|
||||
@@ -1077,6 +1083,13 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: 'Virus Scan',
|
||||
cron: 'Scheduled scan',
|
||||
cronHelper: 'Professional version supports scheduled scan feature',
|
||||
specErr: 'Execution schedule format error, please check and retry!',
|
||||
disableMsg:
|
||||
'Stopping scheduled execution will prevent this scan task from running automatically. Do you want to continue?',
|
||||
enableMsg:
|
||||
'Enabling scheduled execution will allow this scan task to run automatically at regular intervals. Do you want to continue?',
|
||||
showFresh: 'Show Virus Database Service',
|
||||
hideFresh: 'Hide Virus Database Service',
|
||||
clamHelper:
|
||||
@@ -1262,6 +1275,7 @@ const message = {
|
||||
noEdit: 'The file has not been modified, no need to do this!',
|
||||
noNameFolder: 'Untitled Folder',
|
||||
noNameFile: 'Untitled File',
|
||||
minimap: 'Code Mini Map',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: 'Auto Start',
|
||||
@@ -1571,6 +1585,7 @@ const message = {
|
||||
recoverDetail: 'Recover detail',
|
||||
createSnapshot: 'Create Snapshot',
|
||||
importSnapshot: 'Sync Snapshot',
|
||||
importHelper: 'Snapshot directory:',
|
||||
recover: 'Recover',
|
||||
lastRecoverAt: 'Last recovery time',
|
||||
lastRollbackAt: 'Last rollback time',
|
||||
|
||||
@@ -68,6 +68,7 @@ const message = {
|
||||
createNewFolder: '新建資料夾',
|
||||
createNewFile: '新建檔案',
|
||||
helpDoc: '幫助文档',
|
||||
unbind: '解綁',
|
||||
},
|
||||
search: {
|
||||
timeStart: '開始時間',
|
||||
@@ -424,6 +425,10 @@ const message = {
|
||||
remote: '遠程',
|
||||
remoteDB: '遠程服務器',
|
||||
createRemoteDB: '添加遠程服務器',
|
||||
unBindRemoteDB: '解綁遠程服務器',
|
||||
unBindForce: '強制解綁',
|
||||
unBindForceHelper: '忽略解綁過程中的所有錯誤,確保最終操作成功',
|
||||
unBindRemoteHelper: '解綁遠程數據庫只會刪除綁定關係,不會直接刪除遠程數據庫',
|
||||
editRemoteDB: '編輯遠程服務器',
|
||||
localDB: '本地數據庫',
|
||||
address: '數據庫地址',
|
||||
@@ -1019,6 +1024,11 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: '病毒掃描',
|
||||
cron: '定時掃描',
|
||||
cronHelper: '專業版支持定時掃描功能',
|
||||
specErr: '執行周期格式錯誤,請檢查後重試!',
|
||||
disableMsg: '停止定時執行會導致該掃描任務不再自動執行。是否繼續?',
|
||||
enableMsg: '啟用定時執行會讓該掃描任務定期自動執行。是否繼續?',
|
||||
showFresh: '顯示病毒庫服務',
|
||||
hideFresh: '隱藏病毒庫服務',
|
||||
clamHelper:
|
||||
@@ -1199,6 +1209,7 @@ const message = {
|
||||
noEdit: '檔案未修改,無需此操作!',
|
||||
noNameFolder: '未命名資料夾',
|
||||
noNameFile: '未命名檔案',
|
||||
minimap: '縮略圖',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '開機自啟',
|
||||
@@ -1390,6 +1401,7 @@ const message = {
|
||||
recoverDetail: '恢復詳情',
|
||||
createSnapshot: '創建快照',
|
||||
importSnapshot: '同步快照',
|
||||
importHelper: '快照文件目錄:',
|
||||
recover: '恢復',
|
||||
lastRecoverAt: '上次恢復時間',
|
||||
lastRollbackAt: '上次回滾時間',
|
||||
|
||||
@@ -68,6 +68,7 @@ const message = {
|
||||
createNewFolder: '新建文件夹',
|
||||
createNewFile: '新建文件',
|
||||
helpDoc: '帮助文档',
|
||||
unbind: '解绑',
|
||||
},
|
||||
search: {
|
||||
timeStart: '开始时间',
|
||||
@@ -424,6 +425,10 @@ const message = {
|
||||
remote: '远程',
|
||||
remoteDB: '远程服务器',
|
||||
createRemoteDB: '添加远程服务器',
|
||||
unBindRemoteDB: '解绑远程服务器',
|
||||
unBindForce: '强制解绑',
|
||||
unBindForceHelper: '忽略解绑过程中的所有错误,确保最终操作成功',
|
||||
unBindRemoteHelper: '解绑远程数据库只会删除绑定关系,不会直接删除远程数据库',
|
||||
editRemoteDB: '编辑远程服务器',
|
||||
localDB: '本地数据库',
|
||||
address: '数据库地址',
|
||||
@@ -1020,6 +1025,11 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: '病毒扫描',
|
||||
cron: '定时扫描',
|
||||
cronHelper: '专业版支持定时扫描功能 ',
|
||||
specErr: '执行周期格式错误,请检查后重试!',
|
||||
disableMsg: '停止定时执行会导致该扫描任务不再自动执行。是否继续?',
|
||||
enableMsg: '启用定时执行会让该扫描任务定期自动执行。是否继续?',
|
||||
showFresh: '显示病毒库服务',
|
||||
hideFresh: '隐藏病毒库服务',
|
||||
clamHelper:
|
||||
@@ -1201,6 +1211,7 @@ const message = {
|
||||
noEdit: '文件未修改,无需此操作!',
|
||||
noNameFolder: '未命名文件夹',
|
||||
noNameFile: '未命名文件',
|
||||
minimap: '缩略图',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '开机自启',
|
||||
@@ -1392,6 +1403,7 @@ const message = {
|
||||
recoverDetail: '恢复详情',
|
||||
createSnapshot: '创建快照',
|
||||
importSnapshot: '同步快照',
|
||||
importHelper: '快照文件目录:',
|
||||
recover: '恢复',
|
||||
lastRecoverAt: '上次恢复时间',
|
||||
lastRollbackAt: '上次回滚时间',
|
||||
|
||||
@@ -447,6 +447,10 @@ html {
|
||||
background-color: #e5eefd;
|
||||
}
|
||||
|
||||
.monaco-editor-tree-light .el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: #e5eefd;
|
||||
}
|
||||
|
||||
.monaco-editor-tree-dark .el-tree-node__content:hover {
|
||||
background-color: #111417;
|
||||
}
|
||||
|
||||
@@ -241,6 +241,9 @@ const openInstall = (app: App.App) => {
|
||||
case 'java':
|
||||
router.push({ path: '/websites/runtimes/java' });
|
||||
break;
|
||||
case 'go':
|
||||
router.push({ path: '/websites/runtimes/go' });
|
||||
break;
|
||||
default:
|
||||
const params = {
|
||||
app: app,
|
||||
|
||||
@@ -186,9 +186,9 @@
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'cutWebsiteLog'"
|
||||
:label="dialogData.rowData!.type === 'website' ? $t('cronjob.website'):$t('website.website')"
|
||||
prop="website"
|
||||
prop="websiteList"
|
||||
>
|
||||
<el-select class="selectClass" v-model="dialogData.rowData!.website">
|
||||
<el-select class="selectClass" multiple v-model="dialogData.rowData!.websiteList">
|
||||
<el-option
|
||||
:disabled="websiteOptions.length === 0"
|
||||
:label="$t('commons.table.all')"
|
||||
@@ -212,8 +212,8 @@
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="dialogData.rowData!.type === 'app'">
|
||||
<el-form-item :label="$t('cronjob.app')" prop="appID">
|
||||
<el-select class="selectClass" clearable v-model="dialogData.rowData!.appID">
|
||||
<el-form-item :label="$t('cronjob.app')" prop="appIdList">
|
||||
<el-select class="selectClass" multiple clearable v-model="dialogData.rowData!.appIdList">
|
||||
<el-option
|
||||
:disabled="appOptions.length === 0"
|
||||
:label="$t('commons.table.all')"
|
||||
@@ -239,8 +239,8 @@
|
||||
<el-radio value="postgresql">PostgreSQL</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('cronjob.database')" prop="dbName">
|
||||
<el-select class="selectClass" clearable v-model="dialogData.rowData!.dbName">
|
||||
<el-form-item :label="$t('cronjob.database')" prop="dbNameList">
|
||||
<el-select class="selectClass" multiple clearable v-model="dialogData.rowData!.dbNameList">
|
||||
<el-option
|
||||
:disabled="dbInfo.dbs.length === 0"
|
||||
:label="$t('commons.table.all')"
|
||||
@@ -378,6 +378,7 @@ import { listContainer } from '@/api/modules/container';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import { ListAppInstalled } from '@/api/modules/app';
|
||||
import { loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
interface DialogProps {
|
||||
@@ -385,6 +386,7 @@ interface DialogProps {
|
||||
rowData?: Cronjob.CronjobInfo;
|
||||
getTableList?: () => Promise<any>;
|
||||
}
|
||||
|
||||
const title = ref<string>('');
|
||||
const drawerVisible = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
@@ -408,6 +410,15 @@ const acceptParams = (params: DialogProps): void => {
|
||||
if (dialogData.value.rowData.backupAccounts) {
|
||||
dialogData.value.rowData.backupAccountList = dialogData.value.rowData.backupAccounts.split(',');
|
||||
}
|
||||
if (dialogData.value.rowData.appID) {
|
||||
dialogData.value.rowData.appIdList = dialogData.value.rowData.appID.split(',');
|
||||
}
|
||||
if (dialogData.value.rowData.website) {
|
||||
dialogData.value.rowData.websiteList = dialogData.value.rowData.website.split(',');
|
||||
}
|
||||
if (dialogData.value.rowData.dbName) {
|
||||
dialogData.value.rowData.dbNameList = dialogData.value.rowData.dbName.split(',');
|
||||
}
|
||||
dialogData.value.rowData!.command = dialogData.value.rowData!.command || 'sh';
|
||||
dialogData.value.rowData!.isCustom =
|
||||
dialogData.value.rowData!.command !== 'sh' &&
|
||||
@@ -718,6 +729,17 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
specs.push(itemSpec);
|
||||
}
|
||||
dialogData.value.rowData.backupAccounts = dialogData.value.rowData.backupAccountList.join(',');
|
||||
|
||||
if (dialogData.value.rowData.appIdList) {
|
||||
dialogData.value.rowData.appID = dialogData.value.rowData.appIdList.join(',');
|
||||
}
|
||||
if (dialogData.value.rowData.websiteList) {
|
||||
dialogData.value.rowData.website = dialogData.value.rowData.websiteList.join(',');
|
||||
}
|
||||
if (dialogData.value.rowData.dbNameList) {
|
||||
dialogData.value.rowData.dbName = dialogData.value.rowData.dbNameList.join(',');
|
||||
}
|
||||
|
||||
dialogData.value.rowData.spec = specs.join(',');
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
@@ -750,40 +772,49 @@ defineExpose({
|
||||
.specClass {
|
||||
width: 20% !important;
|
||||
margin-left: 20px;
|
||||
|
||||
.append {
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.specClass {
|
||||
width: 100% !important;
|
||||
margin-top: 20px;
|
||||
margin-left: 0;
|
||||
|
||||
.append {
|
||||
width: 43px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.specTypeClass {
|
||||
width: 22% !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.specTypeClass {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.selectClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tagClass {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.logText {
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
|
||||
.link {
|
||||
font-size: 12px;
|
||||
margin-top: -3px;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('commons.button.delete') + ' - ' + deleteMysqlReq.database"
|
||||
:title="$t('database.unBindRemoteDB') + ' - ' + deleteMysqlReq.database"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('app.forceDelete')" />
|
||||
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('database.unBindForce')" />
|
||||
<span class="input-help">
|
||||
{{ $t('app.forceDeleteHelper') }}
|
||||
{{ $t('database.unBindForceHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -18,25 +18,15 @@
|
||||
{{ $t('database.deleteBackupHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<span style="font-size: 12px">{{ $t('database.delete') }}</span>
|
||||
<span style="font-size: 12px; color: red; font-weight: 500">{{ deleteMysqlReq.database }}</span>
|
||||
<span style="font-size: 12px">{{ $t('database.deleteHelper') }}</span>
|
||||
</div>
|
||||
<el-input v-model="delMysqlInfo" :placeholder="deleteMysqlReq.database"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<span style="font-size: 12px">{{ $t('database.unBindRemoteHelper') }}</span>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submit"
|
||||
:disabled="delMysqlInfo != deleteMysqlReq.database || loading"
|
||||
>
|
||||
<el-button type="primary" @click="submit" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -58,7 +48,6 @@ let deleteMysqlReq = ref({
|
||||
});
|
||||
let dialogVisible = ref(false);
|
||||
let loading = ref(false);
|
||||
let delMysqlInfo = ref('');
|
||||
|
||||
const deleteForm = ref<FormInstance>();
|
||||
|
||||
@@ -70,7 +59,6 @@ interface DialogProps {
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const acceptParams = async (prop: DialogProps) => {
|
||||
delMysqlInfo.value = '';
|
||||
deleteMysqlReq.value = {
|
||||
id: prop.id,
|
||||
database: prop.database,
|
||||
@@ -86,7 +74,7 @@ const submit = async () => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -163,7 +163,7 @@ const buttons = [
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
label: i18n.global.t('commons.button.unbind'),
|
||||
click: (row: Database.DatabaseInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('commons.button.delete') + ' - ' + deleteReq.database"
|
||||
:title="$t('database.unBindRemoteDB') + ' - ' + deleteReq.database"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('app.forceDelete')" />
|
||||
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('database.unBindForce')" />
|
||||
<span class="input-help">
|
||||
{{ $t('app.forceDeleteHelper') }}
|
||||
{{ $t('database.unBindForceHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -18,21 +18,15 @@
|
||||
{{ $t('database.deleteBackupHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<span style="font-size: 12px">{{ $t('database.delete') }}</span>
|
||||
<span style="font-size: 12px; color: red; font-weight: 500">{{ deleteReq.database }}</span>
|
||||
<span style="font-size: 12px">{{ $t('database.deleteHelper') }}</span>
|
||||
</div>
|
||||
<el-input v-model="deleteInfo" :placeholder="deleteReq.database"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<span style="font-size: 12px">{{ $t('database.unBindRemoteHelper') }}</span>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submit" :disabled="deleteInfo != deleteReq.database || loading">
|
||||
<el-button type="primary" @click="submit" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -54,7 +48,6 @@ let deleteReq = ref({
|
||||
});
|
||||
let dialogVisible = ref(false);
|
||||
let loading = ref(false);
|
||||
let deleteInfo = ref('');
|
||||
|
||||
const deleteForm = ref<FormInstance>();
|
||||
|
||||
@@ -66,7 +59,6 @@ interface DialogProps {
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const acceptParams = async (prop: DialogProps) => {
|
||||
deleteInfo.value = '';
|
||||
deleteReq.value = {
|
||||
id: prop.id,
|
||||
database: prop.database,
|
||||
@@ -82,7 +74,7 @@ const submit = async () => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -163,7 +163,7 @@ const buttons = [
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
label: i18n.global.t('commons.button.unbind'),
|
||||
click: (row: Database.DatabaseInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('commons.button.delete') + ' - ' + deleteMysqlReq.database"
|
||||
:title="$t('database.unBindRemoteDB') + ' - ' + deleteMysqlReq.database"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('app.forceDelete')" />
|
||||
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('database.unBindForce')" />
|
||||
<span class="input-help">
|
||||
{{ $t('app.forceDeleteHelper') }}
|
||||
{{ $t('database.unBindForceHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -18,25 +18,15 @@
|
||||
{{ $t('database.deleteBackupHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<span style="font-size: 12px">{{ $t('database.delete') }}</span>
|
||||
<span style="font-size: 12px; color: red; font-weight: 500">{{ deleteMysqlReq.database }}</span>
|
||||
<span style="font-size: 12px">{{ $t('database.deleteHelper') }}</span>
|
||||
</div>
|
||||
<el-input v-model="delMysqlInfo" :placeholder="deleteMysqlReq.database"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<span style="font-size: 12px">{{ $t('database.unBindRemoteHelper') }}</span>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submit"
|
||||
:disabled="delMysqlInfo != deleteMysqlReq.database || loading"
|
||||
>
|
||||
<el-button type="primary" @click="submit" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -58,7 +48,6 @@ let deleteMysqlReq = ref({
|
||||
});
|
||||
let dialogVisible = ref(false);
|
||||
let loading = ref(false);
|
||||
let delMysqlInfo = ref('');
|
||||
|
||||
const deleteForm = ref<FormInstance>();
|
||||
|
||||
@@ -70,7 +59,6 @@ interface DialogProps {
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const acceptParams = async (prop: DialogProps) => {
|
||||
delMysqlInfo.value = '';
|
||||
deleteMysqlReq.value = {
|
||||
id: prop.id,
|
||||
database: prop.database,
|
||||
@@ -86,7 +74,7 @@ const submit = async () => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -163,7 +163,7 @@ const buttons = [
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
label: i18n.global.t('commons.button.unbind'),
|
||||
click: (row: Database.DatabaseInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
<el-option :label="$t('commons.button.disable')" value="off"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('file.minimap')">
|
||||
<el-select v-model="config.minimap" @change="changeMinimap()" class="p-w-100">
|
||||
<el-option :label="$t('commons.button.enable')" :value="true"></el-option>
|
||||
<el-option :label="$t('commons.button.disable')" :value="false"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-loading="loading">
|
||||
@@ -209,6 +215,7 @@ interface EditorConfig {
|
||||
language: string;
|
||||
eol: number;
|
||||
wordWrap: WordWrapOptions;
|
||||
minimap: boolean;
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
@@ -227,6 +234,7 @@ const loading = ref(false);
|
||||
const fileName = ref('');
|
||||
const codeThemeKey = 'code-theme';
|
||||
const warpKey = 'code-warp';
|
||||
const minimapKey = 'code-minimap';
|
||||
const directoryPath = ref('');
|
||||
const fileExtension = ref('');
|
||||
const baseDir = ref();
|
||||
@@ -261,6 +269,7 @@ const config = reactive<EditorConfig>({
|
||||
language: 'plaintext',
|
||||
eol: monaco.editor.EndOfLineSequence.LF,
|
||||
wordWrap: 'on',
|
||||
minimap: false,
|
||||
});
|
||||
|
||||
const eols = [
|
||||
@@ -403,6 +412,15 @@ const changeWarp = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const changeMinimap = () => {
|
||||
localStorage.setItem(minimapKey, JSON.stringify(config.minimap));
|
||||
editor.updateOptions({
|
||||
minimap: {
|
||||
enabled: config.minimap,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const initEditor = () => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
@@ -418,6 +436,9 @@ const initEditor = () => {
|
||||
roundedSelection: false,
|
||||
overviewRulerBorder: false,
|
||||
wordWrap: config.wordWrap,
|
||||
minimap: {
|
||||
enabled: config.minimap,
|
||||
},
|
||||
});
|
||||
if (editor.getModel().getValue() === '') {
|
||||
let defaultContent = '\n\n\n\n';
|
||||
@@ -470,6 +491,7 @@ const acceptParams = (props: EditProps) => {
|
||||
config.eol = monaco.editor.EndOfLineSequence.LF;
|
||||
config.theme = localStorage.getItem(codeThemeKey) || 'vs-dark';
|
||||
config.wordWrap = (localStorage.getItem(warpKey) as WordWrapOptions) || 'on';
|
||||
config.minimap = localStorage.getItem(minimapKey) !== null ? localStorage.getItem(minimapKey) === 'true' : true;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<el-descriptions-item :label="$t('file.path')">{{ data.path }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('file.size')">
|
||||
<span v-if="data.isDir">
|
||||
<el-button type="primary" link small @click="getDirSize(data)">
|
||||
<el-button type="primary" link small @click="getDirSize(data)" :loading="loading">
|
||||
<span v-if="data.dirSize == undefined">
|
||||
{{ $t('file.calculate') }}
|
||||
</span>
|
||||
|
||||
@@ -260,7 +260,13 @@
|
||||
<el-table-column :label="$t('file.size')" prop="size" max-width="50" sortable>
|
||||
<template #default="{ row, $index }">
|
||||
<span v-if="row.isDir">
|
||||
<el-button type="primary" link small @click="getDirSize(row, $index)">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
small
|
||||
@click="getDirSize(row, $index)"
|
||||
:loading="btnLoading.includes($index)"
|
||||
>
|
||||
<span v-if="row.dirSize == undefined">
|
||||
{{ $t('file.calculate') }}
|
||||
</span>
|
||||
@@ -381,6 +387,7 @@ const initData = () => ({
|
||||
});
|
||||
let req = reactive(initData());
|
||||
let loading = ref(false);
|
||||
let btnLoading = ref([]);
|
||||
const paths = ref<FilePaths[]>([]);
|
||||
let pathWidth = ref(0);
|
||||
const history: string[] = [];
|
||||
@@ -631,7 +638,7 @@ const getDirSize = async (row: any, index: number) => {
|
||||
const req = {
|
||||
path: row.path,
|
||||
};
|
||||
loading.value = true;
|
||||
btnLoading.value.push(index);
|
||||
await ComputeDirSize(req)
|
||||
.then(async (res) => {
|
||||
let newData = [...data.value];
|
||||
@@ -639,7 +646,7 @@ const getDirSize = async (row: any, index: number) => {
|
||||
data.value = newData;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
btnLoading.value = btnLoading.value.filter((item) => item !== index);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -688,7 +695,7 @@ const openDeCompress = (item: File.File) => {
|
||||
const openView = (item: File.File) => {
|
||||
const fileType = getFileType(item.extension);
|
||||
|
||||
const previewTypes = ['image', 'video', 'audio', 'pdf', 'word', 'excel'];
|
||||
const previewTypes = ['image', 'video', 'audio', 'word', 'excel'];
|
||||
if (previewTypes.includes(fileType)) {
|
||||
return openPreview(item, fileType);
|
||||
}
|
||||
|
||||
@@ -35,15 +35,6 @@
|
||||
|
||||
<audio v-else-if="fileType === 'audio'" :src="fileUrl" controls></audio>
|
||||
|
||||
<vue-office-pdf
|
||||
v-else-if="fileType === 'pdf'"
|
||||
:src="fileUrl"
|
||||
:style="isFullscreen ? 'height: 90vh' : 'height: 80vh'"
|
||||
class="w-full"
|
||||
@rendered="renderedHandler"
|
||||
@error="errorHandler"
|
||||
/>
|
||||
|
||||
<vue-office-docx
|
||||
v-else-if="fileType === 'word'"
|
||||
:src="fileUrl"
|
||||
@@ -73,7 +64,6 @@ import { ref } from 'vue';
|
||||
import { Close, FullScreen } from '@element-plus/icons-vue';
|
||||
import VueOfficeDocx from '@vue-office/docx';
|
||||
import VueOfficeExcel from '@vue-office/excel';
|
||||
import VueOfficePdf from '@vue-office/pdf';
|
||||
import '@vue-office/docx/lib/index.css';
|
||||
import '@vue-office/excel/lib/index.css';
|
||||
import { MsgError } from '@/utils/message';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="open"
|
||||
:destroy-on-close="true"
|
||||
:before-close="handleClose"
|
||||
size="50%"
|
||||
:close-on-click-modal="false"
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
<div v-if="form.from === 'LOCAL'">
|
||||
<span class="import-help">{{ $t('setting.importHelper') }}</span>
|
||||
<span @click="toFolder()" class="import-link-help">{{ backupPath }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="names">
|
||||
<el-select style="width: 100%" v-model="form.names" multiple clearable>
|
||||
@@ -57,6 +61,7 @@ import { snapshotImport } from '@/api/modules/setting';
|
||||
import { getBackupList, getFilesFromBackup } from '@/api/modules/setting';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import router from '@/routers';
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const loading = ref();
|
||||
@@ -65,6 +70,7 @@ const formRef = ref();
|
||||
const backupOptions = ref();
|
||||
const fileNames = ref();
|
||||
const existNames = ref();
|
||||
const backupPath = ref('');
|
||||
|
||||
const form = reactive({
|
||||
from: '',
|
||||
@@ -102,6 +108,9 @@ const checkDisable = (val: string) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const toFolder = async () => {
|
||||
router.push({ path: '/hosts/files', query: { path: backupPath.value } });
|
||||
};
|
||||
|
||||
const submitImport = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
@@ -131,6 +140,10 @@ const loadBackups = async () => {
|
||||
if (item.id !== 0) {
|
||||
backupOptions.value.push({ label: i18n.global.t('setting.' + item.type), value: item.type });
|
||||
}
|
||||
if (item.type === 'LOCAL') {
|
||||
item.varsJson = JSON.parse(item.vars);
|
||||
backupPath.value = item.varsJson['dir'] + '/system_snapshot';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -148,3 +161,18 @@ defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.import-help {
|
||||
font-size: 12px;
|
||||
color: #8f959e;
|
||||
}
|
||||
.import-link-help {
|
||||
color: $primary-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.import-link-help:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -405,10 +405,17 @@ const search = async () => {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
const res = await searchSnapshotPage(params);
|
||||
cleanData.value = false;
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
loading.value = true;
|
||||
await searchSnapshotPage(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
cleanData.value = false;
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
:label="$t('commons.table.name')"
|
||||
:min-width="60"
|
||||
prop="name"
|
||||
sortable
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
@@ -74,6 +75,47 @@
|
||||
<el-button link type="primary" @click="toFolder(row.path)">{{ row.path }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isProductPro"
|
||||
:label="$t('commons.table.status')"
|
||||
:min-width="70"
|
||||
prop="status"
|
||||
sortable
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="row.status === 'Enable'"
|
||||
@click="onChangeStatus(row.id, 'disable')"
|
||||
link
|
||||
icon="VideoPlay"
|
||||
type="success"
|
||||
>
|
||||
{{ $t('commons.status.enabled') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'Disable'"
|
||||
icon="VideoPause"
|
||||
link
|
||||
type="danger"
|
||||
@click="onChangeStatus(row.id, 'enable')"
|
||||
>
|
||||
{{ $t('commons.status.disabled') }}
|
||||
</el-button>
|
||||
<span v-if="row.status === ''">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isProductPro"
|
||||
:label="$t('cronjob.cronSpec')"
|
||||
show-overflow-tooltip
|
||||
:min-width="120"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.spec !== '' ? transSpecToStr(row.spec) : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('toolbox.clam.infectedDir')"
|
||||
:min-width="120"
|
||||
@@ -138,17 +180,22 @@
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { deleteClam, handleClamScan, searchClam, updateClam } from '@/api/modules/toolbox';
|
||||
import { deleteClam, handleClamScan, searchClam, updateClam, updateClamStatus } from '@/api/modules/toolbox';
|
||||
import OperateDialog from '@/views/toolbox/clam/operate/index.vue';
|
||||
import LogDialog from '@/views/toolbox/clam/record/index.vue';
|
||||
import ClamStatus from '@/views/toolbox/clam/status/index.vue';
|
||||
import SettingDialog from '@/views/toolbox/clam/setting/index.vue';
|
||||
import { Toolbox } from '@/api/interface/toolbox';
|
||||
import router from '@/routers';
|
||||
import { transSpecToStr } from '../../cronjob/helper';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const loading = ref();
|
||||
const selects = ref<any>([]);
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { isProductPro } = storeToRefs(globalStore);
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'clam-page-size',
|
||||
@@ -176,12 +223,16 @@ const clamStatus = ref({
|
||||
isRunning: true,
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||
loading.value = true;
|
||||
let params = {
|
||||
info: searchName.value,
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
orderBy: paginationConfig.orderBy,
|
||||
order: paginationConfig.order,
|
||||
};
|
||||
await searchClam(params)
|
||||
.then((res) => {
|
||||
@@ -218,6 +269,14 @@ const onOpenDialog = async (
|
||||
title: string,
|
||||
rowData: Partial<Toolbox.ClamInfo> = {
|
||||
infectedStrategy: 'none',
|
||||
specObj: {
|
||||
specType: 'perDay',
|
||||
week: 1,
|
||||
day: 3,
|
||||
hour: 1,
|
||||
minute: 30,
|
||||
second: 30,
|
||||
},
|
||||
},
|
||||
) => {
|
||||
let params = {
|
||||
@@ -272,6 +331,18 @@ const onSubmitDelete = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeStatus = async (id: number, status: string) => {
|
||||
ElMessageBox.confirm(i18n.global.t('toolbox.clam.' + status + 'Msg'), i18n.global.t('cronjob.changeStatus'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
let itemStatus = status === 'enable' ? 'Enable' : 'Disable';
|
||||
await updateClamStatus(id, itemStatus);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.handle'),
|
||||
|
||||
@@ -50,6 +50,77 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="hasSpec">
|
||||
<el-checkbox v-model="dialogData.rowData!.hasSpec" :label="$t('toolbox.clam.cron')" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dialogData.rowData!.hasSpec && !isProductPro">
|
||||
<span>{{ $t('toolbox.clam.cronHelper') }}</span>
|
||||
<el-button link type="primary" @click="toUpload">
|
||||
{{ $t('license.levelUpPro') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item prop="spec" v-if="dialogData.rowData!.hasSpec && isProductPro">
|
||||
<el-select
|
||||
class="specTypeClass"
|
||||
v-model="dialogData.rowData!.specObj.specType"
|
||||
@change="changeSpecType()"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in specOptions"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-if="dialogData.rowData!.specObj.specType === 'perWeek'"
|
||||
class="specClass"
|
||||
v-model="dialogData.rowData!.specObj.week"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in weekOptions"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-if="hasDay(dialogData.rowData!.specObj)"
|
||||
class="specClass"
|
||||
v-model.number="dialogData.rowData!.specObj.day"
|
||||
>
|
||||
<template #append>
|
||||
<div class="append">{{ $t('cronjob.day') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-if="hasHour(dialogData.rowData!.specObj)"
|
||||
class="specClass"
|
||||
v-model.number="dialogData.rowData!.specObj.hour"
|
||||
>
|
||||
<template #append>
|
||||
<div class="append">{{ $t('commons.units.hour') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-if="dialogData.rowData!.specObj.specType !== 'perNSecond'"
|
||||
class="specClass"
|
||||
v-model.number="dialogData.rowData!.specObj.minute"
|
||||
>
|
||||
<template #append>
|
||||
<div class="append">{{ $t('commons.units.minute') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-if="dialogData.rowData!.specObj.specType === 'perNSecond'"
|
||||
class="specClass"
|
||||
v-model.number="dialogData.rowData!.specObj.second"
|
||||
>
|
||||
<template #append>
|
||||
<div class="append">{{ $t('commons.units.second') }}</div>
|
||||
</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>
|
||||
@@ -64,6 +135,7 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<LicenseImport ref="licenseRef" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
@@ -73,11 +145,18 @@ import { Rules } from '@/global/form-rules';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import LicenseImport from '@/components/license-import/index.vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Toolbox } from '@/api/interface/toolbox';
|
||||
import { createClam, updateClam } from '@/api/modules/toolbox';
|
||||
import { specOptions, transObjToSpec, transSpecToObj, weekOptions } from '../../../cronjob/helper';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const licenseRef = ref();
|
||||
const { isProductPro } = storeToRefs(globalStore);
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
rowData?: Toolbox.ClamInfo;
|
||||
@@ -92,6 +171,19 @@ const dialogData = ref<DialogProps>({
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
if (dialogData.value.rowData?.spec) {
|
||||
dialogData.value.rowData.hasSpec = true;
|
||||
dialogData.value.rowData.specObj = transSpecToObj(dialogData.value.rowData.spec);
|
||||
} else {
|
||||
dialogData.value.rowData.specObj = {
|
||||
specType: 'perDay',
|
||||
week: 1,
|
||||
day: 3,
|
||||
hour: 1,
|
||||
minute: 30,
|
||||
second: 30,
|
||||
};
|
||||
}
|
||||
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
@@ -101,9 +193,97 @@ const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const verifySpec = (rule: any, value: any, callback: any) => {
|
||||
let item = dialogData.value.rowData!.specObj;
|
||||
if (
|
||||
!Number.isInteger(item.day) ||
|
||||
!Number.isInteger(item.hour) ||
|
||||
!Number.isInteger(item.minute) ||
|
||||
!Number.isInteger(item.second) ||
|
||||
!Number.isInteger(item.week)
|
||||
) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
switch (item.specType) {
|
||||
case 'perMonth':
|
||||
if (
|
||||
item.day < 0 ||
|
||||
item.day > 31 ||
|
||||
item.hour < 0 ||
|
||||
item.hour > 23 ||
|
||||
item.minute < 0 ||
|
||||
item.minute > 59
|
||||
) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'perNDay':
|
||||
if (
|
||||
item.day < 0 ||
|
||||
item.day > 366 ||
|
||||
item.hour < 0 ||
|
||||
item.hour > 23 ||
|
||||
item.minute < 0 ||
|
||||
item.minute > 59
|
||||
) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'perWeek':
|
||||
if (
|
||||
item.week < 0 ||
|
||||
item.week > 6 ||
|
||||
item.hour < 0 ||
|
||||
item.hour > 23 ||
|
||||
item.minute < 0 ||
|
||||
item.minute > 59
|
||||
) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'perDay':
|
||||
if (item.hour < 0 || item.hour > 23 || item.minute < 0 || item.minute > 59) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'perNHour':
|
||||
if (item.hour < 0 || item.hour > 8784 || item.minute < 0 || item.minute > 59) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'perHour':
|
||||
if (item.minute < 0 || item.minute > 59) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
case 'perNMinute':
|
||||
if (item.minute < 0 || item.minute > 527040) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'perNSecond':
|
||||
if (item.second < 0 || item.second > 31622400) {
|
||||
callback(new Error(i18n.global.t('toolbox.clam.specErr')));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
const rules = reactive({
|
||||
name: [Rules.simpleName],
|
||||
path: [Rules.requiredInput, Rules.noSpace],
|
||||
spec: [
|
||||
{ validator: verifySpec, trigger: 'blur', required: true },
|
||||
{ validator: verifySpec, trigger: 'change', required: true },
|
||||
],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
@@ -120,12 +300,62 @@ const loadDir = async (path: string) => {
|
||||
const loadInfectedDir = async (path: string) => {
|
||||
dialogData.value.rowData!.infectedDir = path;
|
||||
};
|
||||
const hasDay = (item: any) => {
|
||||
return item.specType === 'perMonth' || item.specType === 'perNDay';
|
||||
};
|
||||
const hasHour = (item: any) => {
|
||||
return item.specType !== 'perHour' && item.specType !== 'perNMinute' && item.specType !== 'perNSecond';
|
||||
};
|
||||
|
||||
const toUpload = () => {
|
||||
licenseRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const changeSpecType = () => {
|
||||
let item = dialogData.value.rowData!.specObj;
|
||||
switch (item.specType) {
|
||||
case 'perMonth':
|
||||
case 'perNDay':
|
||||
item.day = 3;
|
||||
item.hour = 1;
|
||||
item.minute = 30;
|
||||
break;
|
||||
case 'perWeek':
|
||||
item.week = 1;
|
||||
item.hour = 1;
|
||||
item.minute = 30;
|
||||
break;
|
||||
case 'perDay':
|
||||
case 'perNHour':
|
||||
item.hour = 2;
|
||||
item.minute = 30;
|
||||
break;
|
||||
case 'perHour':
|
||||
case 'perNMinute':
|
||||
item.minute = 30;
|
||||
break;
|
||||
case 'perNSecond':
|
||||
item.second = 30;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
let spec = '';
|
||||
let item = dialogData.value.rowData.specObj;
|
||||
if (dialogData.value.rowData!.hasSpec) {
|
||||
spec = transObjToSpec(item.specType, item.week, item.day, item.hour, item.minute, item.second);
|
||||
if (spec === '') {
|
||||
MsgError(i18n.global.t('cronjob.cronSpecHelper'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
dialogData.value.rowData.spec = spec;
|
||||
|
||||
if (dialogData.value.title === 'edit') {
|
||||
await updateClam(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
@@ -158,3 +388,31 @@ defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.specClass {
|
||||
width: 20% !important;
|
||||
margin-left: 20px;
|
||||
.append {
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.specClass {
|
||||
width: 100% !important;
|
||||
margin-top: 20px;
|
||||
margin-left: 0;
|
||||
.append {
|
||||
width: 43px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.specTypeClass {
|
||||
width: 22% !important;
|
||||
}
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.specTypeClass {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="30%">
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="30%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:header="process.operate == 'create' ? $t('commons.button.create') : $t('commons.button.edit')"
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="50%">
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:header="$t('runtime.' + mode)"
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="50%">
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:header="$t('runtime.' + mode)"
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="50%">
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:header="$t('runtime.' + mode)"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:close-on-click-modal="false"
|
||||
width="40%"
|
||||
:before-close="handleClose"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="50%">
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('ssl.' + operate)" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="50%">
|
||||
<el-drawer
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
v-model="open"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('ssl.upload')" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
@@ -128,25 +128,31 @@
|
||||
prop="protocol"
|
||||
width="90px"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('website.expireDate')">
|
||||
<template #default="{ row, $index }">
|
||||
<div v-show="row.showdate">
|
||||
<el-table-column
|
||||
:label="$t('website.expireDate')"
|
||||
prop="expireDate"
|
||||
:sortable="'custom'"
|
||||
min-width="120px"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.showdate">
|
||||
<el-date-picker
|
||||
style="width: 120px"
|
||||
:key="row.id"
|
||||
class="p-w-100"
|
||||
v-model="row.expireDate"
|
||||
type="date"
|
||||
:disabled-date="checkDate"
|
||||
:shortcuts="shortcuts"
|
||||
:clearable="false"
|
||||
:default-value="setDate(row.expireDate)"
|
||||
:ref="(el) => setdateRefs(el, $index)"
|
||||
@change="submitDate(row)"
|
||||
:ref="(el) => setdateRefs(el)"
|
||||
@visible-change="(visibility:boolean) => pickerVisibility(visibility, row)"
|
||||
size="small"
|
||||
:mounted="initDatePicker(row)"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
<div v-show="!row.showdate">
|
||||
<el-link type="primary" :underline="false" @click="openDatePicker(row, $index)">
|
||||
<div v-else>
|
||||
<el-link type="primary" :underline="false" @click.stop="openDatePicker(row)">
|
||||
<span v-if="isEver(row.expireDate)">
|
||||
{{ $t('website.neverExpire') }}
|
||||
</span>
|
||||
@@ -166,7 +172,7 @@
|
||||
</el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="10"
|
||||
width="400px"
|
||||
width="300px"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
:fixed="mobile ? false : 'right'"
|
||||
@@ -201,7 +207,7 @@ import GroupDialog from '@/components/group/index.vue';
|
||||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
import { onMounted, reactive, ref, computed } from '@vue/runtime-core';
|
||||
import { onMounted, reactive, ref, computed } from 'vue';
|
||||
import { OpWebsite, SearchWebsites, UpdateWebsite } from '@/api/modules/website';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { App } from '@/api/interface/app';
|
||||
@@ -247,8 +253,8 @@ const uploadRef = ref();
|
||||
const dialogBackupRef = ref();
|
||||
const defaultRef = ref();
|
||||
const data = ref();
|
||||
let dateRefs: Map<number, any> = new Map();
|
||||
let groups = ref<Group.GroupInfo[]>([]);
|
||||
const dataRef = ref();
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'website-page-size',
|
||||
@@ -270,7 +276,17 @@ const mobile = computed(() => {
|
||||
|
||||
const changeSort = ({ prop, order }) => {
|
||||
if (order) {
|
||||
req.orderBy = prop == 'primaryDomain' ? 'primary_domain' : prop;
|
||||
switch (prop) {
|
||||
case 'primaryDomain':
|
||||
prop = 'primary_domain';
|
||||
break;
|
||||
case 'expireDate':
|
||||
prop = 'expire_date';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
req.orderBy = prop;
|
||||
req.order = order;
|
||||
} else {
|
||||
req.orderBy = 'created_at';
|
||||
@@ -284,6 +300,7 @@ const search = async () => {
|
||||
req.pageSize = paginationConfig.pageSize;
|
||||
|
||||
loading.value = true;
|
||||
data.value = [];
|
||||
await SearchWebsites(req)
|
||||
.then((res) => {
|
||||
data.value = res.data.items;
|
||||
@@ -316,40 +333,50 @@ const isBeforeNow = (time: string) => {
|
||||
return new Date() > new Date(time);
|
||||
};
|
||||
|
||||
const setDate = (time: string) => {
|
||||
if (isEver(time)) {
|
||||
return new Date();
|
||||
} else {
|
||||
return new Date(time);
|
||||
}
|
||||
};
|
||||
|
||||
const openDatePicker = (row: any, index: number) => {
|
||||
row.showdate = true;
|
||||
const ref = dateRefs.get(index);
|
||||
if (ref != undefined) {
|
||||
if (isBeforeNow(row.expireDate)) {
|
||||
row.oldExpireDate = row.expireDate;
|
||||
const date = new Date().toLocaleDateString();
|
||||
row.expireDate = date;
|
||||
const refreshData = () => {
|
||||
for (let i = 0; i < data.value.length; i++) {
|
||||
if (data.value[i].showdate) {
|
||||
data.value[i].showdate = false;
|
||||
if (data.value[i].oldExpireDate) {
|
||||
data.value[i].expireDate = data.value[i].oldExpireDate;
|
||||
data.value[i].oldExpireDate = undefined;
|
||||
}
|
||||
}
|
||||
ref.handleOpen();
|
||||
}
|
||||
if (dataRef.value) {
|
||||
dataRef.value.handleClose();
|
||||
dataRef.value = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const setdateRefs = (ref: any, index: number) => {
|
||||
dateRefs.set(index, ref);
|
||||
const openDatePicker = (row: any) => {
|
||||
refreshData();
|
||||
row.showdate = true;
|
||||
};
|
||||
|
||||
const setdateRefs = (ref: any) => {
|
||||
dataRef.value = ref;
|
||||
if (dataRef.value != undefined) {
|
||||
dataRef.value.handleOpen();
|
||||
}
|
||||
};
|
||||
|
||||
const initDatePicker = (row: any) => {
|
||||
if (dataRef.value == undefined && row.oldExpireDate == undefined && isBeforeNow(row.expireDate)) {
|
||||
row.oldExpireDate = row.expireDate;
|
||||
const date = new Date().toLocaleDateString();
|
||||
row.expireDate = date;
|
||||
}
|
||||
};
|
||||
|
||||
const pickerVisibility = (visibility: boolean, row: any) => {
|
||||
if (!visibility) {
|
||||
row.showdate = false;
|
||||
if (!row.change) {
|
||||
if (row.oldExpireDate) {
|
||||
row.expireDate = row.oldExpireDate;
|
||||
}
|
||||
row.change = false;
|
||||
dataRef.value = undefined;
|
||||
if (row.oldExpireDate) {
|
||||
row.expireDate = row.oldExpireDate;
|
||||
row.oldExpireDate = undefined;
|
||||
}
|
||||
row.showdate = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -365,7 +392,6 @@ const submitDate = (row: any) => {
|
||||
};
|
||||
|
||||
UpdateWebsite(req).then(() => {
|
||||
row.change = true;
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
search();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user