mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 08:00:34 +00:00
refactor: search bind
This commit is contained in:
@@ -21,6 +21,7 @@ type AlertBase struct {
|
||||
}
|
||||
|
||||
type PushAlert struct {
|
||||
Result string `json:"result,omitempty"`
|
||||
TaskName string `json:"taskName"`
|
||||
AlertType string `json:"alertType"`
|
||||
EntryID uint `json:"entryID"`
|
||||
@@ -53,6 +54,7 @@ type AlertDTO struct {
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
TaskName string `json:"taskName,omitempty"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
|
||||
@@ -51,9 +51,10 @@ type CronjobOperate struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
|
||||
CleanLogConfig
|
||||
}
|
||||
@@ -126,7 +127,8 @@ type CronjobInfo struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode"`
|
||||
}
|
||||
|
||||
type CronjobImport struct {
|
||||
@@ -169,9 +171,10 @@ type CronjobTrans struct {
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
}
|
||||
type TransHelper struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
|
||||
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
@@ -109,7 +110,38 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
cronjobProjects := make(map[string]uint)
|
||||
var cronjobIDs []uint
|
||||
for _, item := range alerts {
|
||||
if alertUtil.GetCronJobType(item.Type) != "cronJob" {
|
||||
continue
|
||||
}
|
||||
if _, exists := cronjobProjects[item.Project]; exists {
|
||||
continue
|
||||
}
|
||||
id, parseErr := strconv.ParseUint(item.Project, 10, strconv.IntSize)
|
||||
if parseErr != nil || id == 0 {
|
||||
continue
|
||||
}
|
||||
cronjobProjects[item.Project] = uint(id)
|
||||
cronjobIDs = append(cronjobIDs, uint(id))
|
||||
}
|
||||
cronjobsByID := make(map[uint]model.Cronjob)
|
||||
if len(cronjobIDs) > 0 {
|
||||
cronjobs, err := cronjobRepo.List(repo.WithByIDs(cronjobIDs))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, cronjob := range cronjobs {
|
||||
cronjobsByID[cronjob.ID] = cronjob
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range alerts {
|
||||
var taskName string
|
||||
if cronjob, exists := cronjobsByID[cronjobProjects[item.Project]]; exists && cronjob.Type == item.Type {
|
||||
taskName = cronjob.Name
|
||||
}
|
||||
|
||||
result = append(result, dto.AlertDTO{
|
||||
ID: item.ID,
|
||||
@@ -119,6 +151,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
TaskName: taskName,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
@@ -190,6 +223,16 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
advanced, err := prepareCronJobAlertParams(create.Type, "", create.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
create.AdvancedParams = advanced
|
||||
if create.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(create.Type, advanced, create.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
alertInfo.Status = constant.AlertEnable
|
||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
@@ -207,11 +250,24 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||
if alertUtil.GetCronJobType(req.Type) == "cronJob" {
|
||||
previous, err := alertRepo.Get(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.AdvancedParams, err = prepareCronJobAlertParams(req.Type, previous.AdvancedParams, req.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
methodTypes, err := a.validateAlertMethodReferences(req.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(req.Type, req.AdvancedParams, req.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -278,6 +334,9 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
||||
return err
|
||||
}
|
||||
if status == constant.AlertEnable {
|
||||
if err := a.validateCronJobAlertChannels(alertInfo.Type, alertInfo.AdvancedParams, alertInfo.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1021,6 +1080,23 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
alertRepo.WithByType(updateAlert.Type),
|
||||
alertRepo.WithByProject(updateAlert.Project),
|
||||
)
|
||||
advanced, err := prepareCronJobAlertParams(updateAlert.Type, alertInfo.AdvancedParams, updateAlert.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAlert.AdvancedParams = advanced
|
||||
if alertUtil.GetCronJobType(updateAlert.Type) == "cronJob" {
|
||||
upMap["advanced_params"] = advanced
|
||||
}
|
||||
if newStatus == constant.AlertEnable {
|
||||
method := updateAlert.Method
|
||||
if method == "" {
|
||||
method = alertInfo.Method
|
||||
}
|
||||
if err := a.validateCronJobAlertChannels(updateAlert.Type, advanced, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if alertInfo.ID > 0 {
|
||||
shouldUpdate := false
|
||||
@@ -1034,6 +1110,9 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
||||
shouldUpdate = true
|
||||
}
|
||||
if val, ok := upMap["advanced_params"]; ok && val != alertInfo.AdvancedParams {
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
if shouldUpdate {
|
||||
if err := alertRepo.Update(
|
||||
@@ -1055,3 +1134,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareCronJobAlertParams(alertType, previous, incoming string) (string, error) {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return incoming, nil
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams(previous, incoming)
|
||||
}
|
||||
|
||||
func (a AlertService) validateCronJobAlertChannels(alertType, advanced, method string) error {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return nil
|
||||
}
|
||||
mode, err := alertUtil.CronJobAlertTriggerMode(advanced)
|
||||
if err != nil || mode != alertUtil.CronJobAlertSuccess {
|
||||
return err
|
||||
}
|
||||
_, err = a.validateAlertMethodReferences(method)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -530,11 +530,15 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
||||
}
|
||||
interfaceAddrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Failed to load local IP addresses for ssh login alert: %v", err)
|
||||
}
|
||||
count, records := summarizeSSHLoginHistories(
|
||||
histories,
|
||||
now,
|
||||
failedWindow,
|
||||
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"),
|
||||
sshSuccessLoginWhitelist(alert.AdvancedParams, interfaceAddrs),
|
||||
)
|
||||
isAlert := count >= int(alert.Count)
|
||||
if isAlert {
|
||||
@@ -570,6 +574,19 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func sshSuccessLoginWhitelist(configured string, interfaceAddrs []net.Addr) []string {
|
||||
whitelist := strings.Split(strings.TrimSpace(configured), "\n")
|
||||
whitelist = append(whitelist, "127.0.0.0/8", "::1")
|
||||
for _, addr := range interfaceAddrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok || ipNet.IP == nil || ipNet.IP.IsUnspecified() {
|
||||
continue
|
||||
}
|
||||
whitelist = append(whitelist, ipNet.IP.String())
|
||||
}
|
||||
return whitelist
|
||||
}
|
||||
|
||||
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
||||
filtered := make([]model.LoginLog, 0, len(records))
|
||||
for _, record := range records {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -75,6 +76,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
@@ -98,9 +100,11 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))))
|
||||
item.AlertMethod = alertInfo.Method
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
@@ -195,11 +199,12 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
}
|
||||
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
|
||||
if alertInfo.SendCount != 0 {
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))))
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
}
|
||||
@@ -213,6 +218,17 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
for _, item := range req {
|
||||
advanced, err := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(item.Type, advanced, item.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range req {
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||
if cronjobItem.ID != 0 {
|
||||
@@ -395,17 +411,27 @@ func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
} else {
|
||||
cronjob.Status = constant.StatusDisable
|
||||
}
|
||||
_ = cronjobRepo.Create(&cronjob)
|
||||
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
advanced, _ := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
status := constant.AlertEnable
|
||||
if item.AlertCount == 0 {
|
||||
status = constant.AlertDisable
|
||||
}
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: status,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
if err := NewIAlertService().CreateAlert(createAlert, operator); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -562,6 +588,15 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(req.Type, advanced, req.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||
if cronjob.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -603,12 +638,13 @@ func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
}
|
||||
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
@@ -682,6 +718,10 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cronjob model.Cronjob
|
||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
@@ -697,6 +737,20 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
if err != nil {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
previous, _ := alertRepo.Get(alertRepo.WithByType(cronModel.Type), alertRepo.WithByProject(strconv.Itoa(int(id))))
|
||||
merged, err := prepareCronJobAlertParams(cronModel.Type, previous.AdvancedParams, advanced)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
method := req.AlertMethod
|
||||
if method == "" {
|
||||
method = previous.Method
|
||||
}
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(cronModel.Type, merged, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
cronjob.EntryIDs = cronModel.EntryIDs
|
||||
cronjob.Type = cronModel.Type
|
||||
@@ -753,11 +807,12 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return err
|
||||
}
|
||||
updateAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
@@ -766,6 +821,17 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return nil
|
||||
}
|
||||
|
||||
func cronJobAlertAdvancedParams(mode string) (string, error) {
|
||||
if mode == "" {
|
||||
return "", nil
|
||||
}
|
||||
data, err := json.Marshal(map[string]string{"alertTriggerMode": mode})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams("", string(data))
|
||||
}
|
||||
|
||||
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
||||
if cronjob.ID == 0 {
|
||||
|
||||
@@ -412,6 +412,7 @@ func addSkipTask(source string, taskItem *task.Task) {
|
||||
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
||||
return nil
|
||||
}, nil)
|
||||
taskItem.SubTasks[len(taskItem.SubTasks)-1].StepAlias = cronJobSkippedStep
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
||||
@@ -56,10 +58,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
_ = taskRepo.Save(context.Background(), taskItem.Task)
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}()
|
||||
return
|
||||
}
|
||||
@@ -70,19 +73,20 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(taskItem.ID) == 0 {
|
||||
storedTask, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(storedTask.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
} else {
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -482,8 +486,33 @@ func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
const cronJobSkippedStep = "cronjob-skipped"
|
||||
|
||||
func cronJobAlertResult(taskItem *task.Task, err error) string {
|
||||
if errors.Is(err, context.Canceled) || taskItem.Task.Status == constant.StatusCanceled ||
|
||||
(taskItem.TaskCtx != nil && taskItem.TaskCtx.Err() != nil) {
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
return alertUtil.CronJobAlertFailed
|
||||
}
|
||||
if taskItem.Task.Status != constant.StatusSuccess {
|
||||
return ""
|
||||
}
|
||||
for _, subTask := range taskItem.SubTasks {
|
||||
if subTask.StepAlias != cronJobSkippedStep {
|
||||
return alertUtil.CronJobAlertSuccess
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob, result string) {
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
pushAlert := dto.PushAlert{
|
||||
Result: result,
|
||||
TaskName: cronjob.Name,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: "Panel {{ .node }}{{ .ip }}: {{ .msg }}. Log in to view details."
|
||||
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal. Log in to view details."
|
||||
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details."
|
||||
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details."
|
||||
CronJobSuccessAlert: "Panel {{ .node }}{{ .ip }}: scheduled task {{ .name }} completed successfully. Log in to view details."
|
||||
|
||||
# disk
|
||||
DeviceNotFound: "Device {{ .name }} not found"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el pa
|
||||
NodeExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles.'
|
||||
LicenseExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles.'
|
||||
SSHAndPanelLoginAlert: 'Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: la tarea programada {{ .name }} se completó correctamente. Inicie sesión para ver los detalles.'
|
||||
|
||||
# disco
|
||||
DeviceNotFound: 'Dispositivo {{ .name }} no encontrado'
|
||||
|
||||
@@ -649,6 +649,8 @@ CommonAlert: "پنل {{ .node }}{{ .ip }}: {{ .msg }}. برای مشاهده ج
|
||||
NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید."
|
||||
LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید."
|
||||
SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید."
|
||||
CronJobSuccessAlert: "پنل {{ .node }}{{ .ip }}: وظیفه زمانبندیشده {{ .name }} با موفقیت تکمیل شد. برای مشاهده جزئیات وارد شوید."
|
||||
|
||||
|
||||
# دیسک
|
||||
DeviceNotFound: "دستگاه {{ .name }} یافت نشد"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細
|
||||
NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして'
|
||||
LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして'
|
||||
SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして'
|
||||
CronJobSuccessAlert: 'パネル {{ .node }}{{ .ip }}: スケジュールタスク {{ .name }} が正常に完了しました。詳細はパネルにログインして確認してください。'
|
||||
|
||||
# ディスク
|
||||
DeviceNotFound: 'デバイス {{ .name }} が見つかりません'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내
|
||||
NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
CronJobSuccessAlert: '패널 {{ .node }}{{ .ip }}: 예약 작업 {{ .name }}이(가) 성공적으로 완료되었습니다. 로그인하여 자세한 내용을 확인하십시오.'
|
||||
|
||||
# 디스크
|
||||
DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다'
|
||||
|
||||
@@ -639,6 +639,7 @@ CommonAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: {{ .msg }}. ເ
|
||||
NodeExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໂນດ {{ .num }} ແຫ່ງຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
LicenseExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດ {{ .num }} ສະບັບຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
SSHAndPanelLoginAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ມີການເຂົ້າສູ່ລະບົບ {{ .name }} ທີ່ຜິດປົກກະຕິຈາກ {{ .loginIp }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
CronJobSuccessAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ວຽກທີ່ຕັ້ງເວລາ {{ .name }} ສຳເລັດແລ້ວ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke pan
|
||||
NodeExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||
LicenseExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||
SSHAndPanelLoginAlert: 'Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: tugas berjadual {{ .name }} berjaya diselesaikan. Log masuk untuk melihat butiran.'
|
||||
|
||||
# cakera
|
||||
DeviceNotFound: 'Peranti {{ .name }} tidak ditemui'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel
|
||||
NodeExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes.'
|
||||
LicenseExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes.'
|
||||
SSHAndPanelLoginAlert: 'Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes.'
|
||||
CronJobSuccessAlert: 'Painel {{ .node }}{{ .ip }}: a tarefa agendada {{ .name }} foi concluída com sucesso. Faça login para ver os detalhes.'
|
||||
|
||||
# disco
|
||||
DeviceNotFound: 'Dispositivo {{ .name }} não encontrado'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдит
|
||||
NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.'
|
||||
LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.'
|
||||
SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.'
|
||||
CronJobSuccessAlert: 'Панель {{ .node }}{{ .ip }}: запланированная задача {{ .name }} успешно завершена. Войдите для просмотра подробностей.'
|
||||
|
||||
# диск
|
||||
DeviceNotFound: 'Устройство {{ .name }} не найдено'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için panelin
|
||||
NodeExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın.'
|
||||
LicenseExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın.'
|
||||
SSHAndPanelLoginAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: zamanlanmış görev {{ .name }} başarıyla tamamlandı. Ayrıntıları görmek için giriş yapın.'
|
||||
|
||||
# disk
|
||||
DeviceNotFound: 'Cihaz {{ .name }} bulunamadı'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入
|
||||
NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。'
|
||||
LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。'
|
||||
SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。'
|
||||
CronJobSuccessAlert: '您的 {{ .node }}{{ .ip }} 面板,排程任務 {{ .name }} 執行成功,詳情請登入面板檢視。'
|
||||
|
||||
# 磁碟
|
||||
DeviceNotFound: '裝置 {{ .name }} 未找到'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},请登录面板
|
||||
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,请登录面板查看详情。"
|
||||
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。"
|
||||
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。"
|
||||
CronJobSuccessAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行成功,请登录面板查看详情。"
|
||||
|
||||
# 磁盘
|
||||
DeviceNotFound: "设备 {{ .name }} 未找到"
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"}
|
||||
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp", "syncIpGroup", "cleanLog"}
|
||||
|
||||
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
params := CreateTaskAlertParams(pushAlert)
|
||||
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
|
||||
alertRule := ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
@@ -76,14 +76,14 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
|
||||
Encryption: emailInfo.Encryption,
|
||||
Recipient: emailInfo.Recipient,
|
||||
}
|
||||
content := GetSendContent(alert.Type, params, agentInfo)
|
||||
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||
}
|
||||
msg := email.EmailMessage{
|
||||
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
|
||||
Body: content,
|
||||
IsHTML: true,
|
||||
IsHTML: GetCronJobType(alert.Type) != "cronJob",
|
||||
}
|
||||
|
||||
if err = email.SendMail(smtpConfig, msg, transport); err != nil {
|
||||
@@ -110,7 +110,7 @@ func CreateBarkAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []
|
||||
return SaveAlertLog(create, &alertLog)
|
||||
}
|
||||
|
||||
content := GetSendContent(alert.Type, params, agentInfo)
|
||||
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||
}
|
||||
@@ -268,7 +268,7 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
|
||||
alertDetail := dto.AlertDetail{
|
||||
Type: GetCronJobType(alert.Type),
|
||||
SubType: alert.Type,
|
||||
Title: alert.Title,
|
||||
Title: TaskAlertTitle(alert.Type, alert.Title, project, params),
|
||||
Method: method,
|
||||
Project: project,
|
||||
Params: params,
|
||||
@@ -281,6 +281,14 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
|
||||
return string(marshal)
|
||||
}
|
||||
|
||||
func TaskAlertTitle(alertType, title, project string, params []dto.Param) string {
|
||||
if GetCronJobType(alertType) != "cronJob" || CronJobAlertResultFromParams(params) != CronJobAlertSuccess {
|
||||
return title
|
||||
}
|
||||
name := cronJobTaskName(project, params)
|
||||
return i18n.GetMsgWithMap("TaskSuccess", map[string]interface{}{"name": name})
|
||||
}
|
||||
|
||||
func ProcessAlertRule(alert dto.AlertDTO) string {
|
||||
marshal, err := json.Marshal(alert)
|
||||
if err != nil {
|
||||
@@ -324,6 +332,10 @@ func GetCronJobTypeName(cronJobType string) string {
|
||||
module = "系统快照"
|
||||
case "ntp":
|
||||
module = "同步服务器时间"
|
||||
case "syncIpGroup":
|
||||
module = "同步 IP 组"
|
||||
case "cleanLog":
|
||||
module = "清理日志"
|
||||
default:
|
||||
}
|
||||
return module
|
||||
@@ -444,6 +456,30 @@ func isWithinTimeRange(savedTimeString string) bool {
|
||||
}
|
||||
|
||||
func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
|
||||
return GetAlertDetailContent(dto.AlertDetail{Type: alertType, Params: params}, agentInfo)
|
||||
}
|
||||
|
||||
func GetAlertLogContent(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, agentInfo *dto.AgentInfo) string {
|
||||
detail := dto.AlertDetail{Type: alert.Type, Params: params}
|
||||
var stored dto.AlertDetail
|
||||
if json.Unmarshal([]byte(create.AlertDetail), &stored) == nil {
|
||||
detail = stored
|
||||
if detail.Type == "" {
|
||||
detail.Type = alert.Type
|
||||
}
|
||||
if detail.Params == nil {
|
||||
detail.Params = params
|
||||
}
|
||||
}
|
||||
return GetAlertDetailContent(detail, agentInfo)
|
||||
}
|
||||
|
||||
func GetAlertDetailContent(detail dto.AlertDetail, agentInfo *dto.AgentInfo) string {
|
||||
alertType := detail.SubType
|
||||
if alertType == "" {
|
||||
alertType = detail.Type
|
||||
}
|
||||
params := detail.Params
|
||||
switch GetCronJobType(alertType) {
|
||||
case "ssl":
|
||||
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
@@ -464,7 +500,12 @@ func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentIn
|
||||
case "disk":
|
||||
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
case "cronJob":
|
||||
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
messageKey := "CronJobFailedAlert"
|
||||
if CronJobAlertResultFromParams(params) == CronJobAlertSuccess {
|
||||
messageKey = "CronJobSuccessAlert"
|
||||
}
|
||||
name := cronJobTaskName(detail.Project, params)
|
||||
return i18n.GetMsgWithMap(messageKey, map[string]interface{}{"name": name, "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
case "clams":
|
||||
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
case "panelLogin":
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package alert
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
)
|
||||
|
||||
const (
|
||||
CronJobAlertFailed = "failed"
|
||||
CronJobAlertSuccess = "success"
|
||||
CronJobAlertBoth = "both"
|
||||
)
|
||||
|
||||
func cronJobAlertParams(advanced string) (map[string]json.RawMessage, error) {
|
||||
params := make(map[string]json.RawMessage)
|
||||
if strings.TrimSpace(advanced) != "" {
|
||||
if err := json.Unmarshal([]byte(advanced), ¶ms); err != nil {
|
||||
return nil, fmt.Errorf("invalid cronjob alert advanced parameters: %w", err)
|
||||
}
|
||||
}
|
||||
if params == nil {
|
||||
params = make(map[string]json.RawMessage)
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func CronJobAlertTriggerMode(advanced string) (string, error) {
|
||||
params, err := cronJobAlertParams(advanced)
|
||||
if err != nil {
|
||||
return CronJobAlertFailed, err
|
||||
}
|
||||
mode := CronJobAlertFailed
|
||||
if raw, ok := params["alertTriggerMode"]; ok {
|
||||
if err := json.Unmarshal(raw, &mode); err != nil {
|
||||
return CronJobAlertFailed, fmt.Errorf("invalid cronjob alert trigger mode: %w", err)
|
||||
}
|
||||
}
|
||||
switch mode {
|
||||
case CronJobAlertFailed, CronJobAlertSuccess, CronJobAlertBoth:
|
||||
return mode, nil
|
||||
default:
|
||||
return CronJobAlertFailed, fmt.Errorf("invalid cronjob alert trigger mode %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func MergeCronJobAlertParams(previous, incoming string) (string, error) {
|
||||
params, err := cronJobAlertParams(previous)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
updates, err := cronJobAlertParams(incoming)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for key, value := range updates {
|
||||
params[key] = value
|
||||
}
|
||||
if _, ok := params["alertTriggerMode"]; !ok {
|
||||
params["alertTriggerMode"] = json.RawMessage(`"failed"`)
|
||||
}
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := CronJobAlertTriggerMode(string(data)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func MatchCronJobAlertResult(advanced, result string) bool {
|
||||
mode, err := CronJobAlertTriggerMode(advanced)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if result == "" {
|
||||
result = CronJobAlertFailed
|
||||
}
|
||||
if result != CronJobAlertFailed && result != CronJobAlertSuccess {
|
||||
return false
|
||||
}
|
||||
return mode == CronJobAlertBoth || mode == result
|
||||
}
|
||||
|
||||
func CreateTaskAlertParams(pushAlert dto.PushAlert) []dto.Param {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
if GetCronJobType(pushAlert.AlertType) != "cronJob" {
|
||||
return params
|
||||
}
|
||||
params = append(params, CreateCronJobResultParam(pushAlert.Result))
|
||||
return params
|
||||
}
|
||||
|
||||
func cronJobTaskName(project string, params []dto.Param) string {
|
||||
if project != "" {
|
||||
return project
|
||||
}
|
||||
if name := getValueByIndex(params, "taskName"); name != "" {
|
||||
return name
|
||||
}
|
||||
return getValueByIndex(params, "1")
|
||||
}
|
||||
|
||||
func CreateCronJobResultParam(result string) dto.Param {
|
||||
value := "失败"
|
||||
if result == CronJobAlertSuccess {
|
||||
value = "成功"
|
||||
}
|
||||
return dto.Param{Index: "2", Key: "result", Value: value}
|
||||
}
|
||||
|
||||
func CronJobAlertResultFromParams(params []dto.Param) string {
|
||||
for _, param := range params {
|
||||
if param.Index == "result" {
|
||||
if param.Value == CronJobAlertSuccess {
|
||||
return CronJobAlertSuccess
|
||||
}
|
||||
return CronJobAlertFailed
|
||||
}
|
||||
}
|
||||
for _, param := range params {
|
||||
if param.Index == "2" && param.Key == "result" && param.Value == "成功" {
|
||||
return CronJobAlertSuccess
|
||||
}
|
||||
}
|
||||
return CronJobAlertFailed
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func CreateTaskScanCustomWebhookAlertLog(
|
||||
transport *http.Transport,
|
||||
agentInfo *dto.AgentInfo,
|
||||
) error {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
params := CreateTaskAlertParams(pushAlert)
|
||||
alertInfo := info
|
||||
alertInfo.Type = alertType
|
||||
create.Type = GetCronJobType(alertType)
|
||||
@@ -99,7 +99,7 @@ func customWebhookTemplateData(rawDetail string, agentInfo *dto.AgentInfo, occur
|
||||
if businessType == "" {
|
||||
return webhook_sender.TemplateData{}, errors.New("resolve custom webhook alert detail failed")
|
||||
}
|
||||
content := GetSendContent(businessType, detail.Params, agentInfo)
|
||||
content := GetAlertDetailContent(detail, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": detail.Title})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ func PushAlert(pushAlert dto.PushAlert) error {
|
||||
}
|
||||
var alert dto.AlertDTO
|
||||
_ = copier.Copy(&alert, &alertInfo)
|
||||
if alertUtil.GetCronJobType(pushAlert.AlertType) == "cronJob" && !alertUtil.MatchCronJobAlertResult(alert.AdvancedParams, pushAlert.Result) {
|
||||
return nil
|
||||
}
|
||||
|
||||
methods := strings.Split(alert.Method, ",")
|
||||
for _, m := range methods {
|
||||
@@ -126,7 +129,7 @@ func sendAlert(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert dto.Push
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param))
|
||||
params := alertUtil.CreateTaskAlertParams(pushAlert)
|
||||
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
|
||||
alertRule := alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CommonModel, ReqPage } from '@/api/interface';
|
||||
import type { CronjobAlertTriggerMode } from '@/utils/cronjob-alert';
|
||||
|
||||
export namespace Alert {
|
||||
export interface AlertInfo extends CommonModel {
|
||||
@@ -9,22 +10,25 @@ export namespace Alert {
|
||||
interval: number;
|
||||
method: string;
|
||||
title: string;
|
||||
taskName?: string;
|
||||
project: string;
|
||||
status: string;
|
||||
sendCount: number;
|
||||
sendMethod: string[];
|
||||
advancedParams: string;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
createUser?: string;
|
||||
updateUser?: string;
|
||||
}
|
||||
|
||||
export interface AlertDetail {
|
||||
type: string;
|
||||
subType?: string;
|
||||
licenseId: string;
|
||||
title: string;
|
||||
project: string;
|
||||
method: string;
|
||||
params: string;
|
||||
params: { index: string; key: string; value: string }[] | string;
|
||||
}
|
||||
|
||||
export interface AlertUpdateStatusReq {
|
||||
@@ -74,6 +78,7 @@ export namespace Alert {
|
||||
project: string;
|
||||
status: string;
|
||||
sendCount: number;
|
||||
advancedParams?: string;
|
||||
}
|
||||
|
||||
export interface AlertUpdateReq extends CommonModel {
|
||||
@@ -86,6 +91,7 @@ export namespace Alert {
|
||||
project: string;
|
||||
status: string;
|
||||
sendCount: number;
|
||||
advancedParams?: string;
|
||||
}
|
||||
|
||||
export interface DelReq {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReqPage } from '.';
|
||||
import type { CronjobAlertTriggerMode } from '@/utils/cronjob-alert';
|
||||
|
||||
export namespace Cronjob {
|
||||
export interface Search extends ReqPage {
|
||||
@@ -62,6 +63,7 @@ export namespace Cronjob {
|
||||
secret: string;
|
||||
hasAlert: boolean;
|
||||
alertCount: number;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
alertTitle: string;
|
||||
alertMethod: string;
|
||||
alertMethodItems: Array<string>;
|
||||
@@ -111,6 +113,7 @@ export namespace Cronjob {
|
||||
secret: string;
|
||||
|
||||
alertCount: number;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
alertTitle: string;
|
||||
alertMethod: string;
|
||||
|
||||
@@ -154,6 +157,7 @@ export namespace Cronjob {
|
||||
downloadAccount: string;
|
||||
|
||||
alertCount: number;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
}
|
||||
export interface TransHelper {
|
||||
name: string;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import GlobalStore from '@/store/modules/global';
|
||||
import { bindSearchPersistence } from '@/utils/search-persistence';
|
||||
|
||||
/** Restore the parent's search fields synchronously, before its initial request. */
|
||||
export const useSearchPersistence = (
|
||||
page: string,
|
||||
fields: Parameters<typeof bindSearchPersistence>[1],
|
||||
scope?: () => string | number | undefined,
|
||||
) => {
|
||||
const { currentNode } = storeToRefs(GlobalStore());
|
||||
return bindSearchPersistence(() => {
|
||||
const context = scope?.();
|
||||
if (scope && context === undefined) return undefined;
|
||||
return JSON.stringify([currentNode.value, page, context ?? '']);
|
||||
}, fields);
|
||||
};
|
||||
@@ -6723,6 +6723,15 @@ const message = {
|
||||
snapshot: 'System Snapshot',
|
||||
clamsRulesHelper: 'Virus scanning tasks that require alerts',
|
||||
cronJobRulesHelper: 'This type of scheduled task needs to be configured',
|
||||
alertTriggerMode: 'Notify on',
|
||||
alertTriggerFailed: 'Failure only',
|
||||
alertTriggerSuccess: 'Success only',
|
||||
alertTriggerBoth: 'Success or failure',
|
||||
alertNotificationHelper:
|
||||
'Notify after the final execution result, not during retries. Success and failure notifications share the daily limit and sending hours.',
|
||||
notificationTitle: 'Scheduled task - {0} 「{1}」 execution notification',
|
||||
notificationSuccessTitle: 'Scheduled task - {0} 「{1}」 completed successfully',
|
||||
notificationRule: '{0}: {1}, up to {2} notifications per day',
|
||||
clamsTitle: 'Virus scan task 「 {0} 」 detected infected file alert',
|
||||
cronJobAppTitle: 'Cronjob - Backup App 「 {0} 」 Task Failure Alert',
|
||||
cronJobWebsiteTitle: 'Cronjob - Backup Website「 {0} 」Task Failure Alert',
|
||||
|
||||
@@ -6835,6 +6835,15 @@ const message = {
|
||||
snapshot: 'Snapshot del sistema',
|
||||
clamsRulesHelper: 'Tareas de escaneo antivirus que requieren alerta',
|
||||
cronJobRulesHelper: 'Este tipo de tareas programadas necesita configuración',
|
||||
alertTriggerMode: 'Notificar cuando',
|
||||
alertTriggerFailed: 'Solo falle',
|
||||
alertTriggerSuccess: 'Solo tenga éxito',
|
||||
alertTriggerBoth: 'Tenga éxito o falle',
|
||||
alertNotificationHelper:
|
||||
'Notificar al finalizar la ejecución, no durante los reintentos. Las notificaciones de éxito y fallo comparten el límite diario y el horario de envío.',
|
||||
notificationTitle: 'Tarea programada - {0} 「{1}」 notificación de ejecución',
|
||||
notificationSuccessTitle: 'Tarea programada - {0} 「{1}」 completada correctamente',
|
||||
notificationRule: '{0}: {1}, hasta {2} notificaciones al día',
|
||||
clamsTitle: 'Tarea antivirus 「 {0} 」 detectó archivo infectado',
|
||||
cronJobAppTitle: 'Cronjob - Backup de app 「 {0} 」 falló',
|
||||
cronJobWebsiteTitle: 'Cronjob - Backup de sitio 「 {0} 」 falló',
|
||||
|
||||
@@ -6677,6 +6677,15 @@ const message = {
|
||||
snapshot: 'تصویر لحظهای سیستم',
|
||||
clamsRulesHelper: 'وظایف اسکن ویروس که نیاز به هشدار دارند',
|
||||
cronJobRulesHelper: 'این نوع وظیفه زمانبندی باید پیکربندی شود',
|
||||
alertTriggerMode: 'شرط اعلان',
|
||||
alertTriggerFailed: 'فقط شکست',
|
||||
alertTriggerSuccess: 'فقط موفقیت',
|
||||
alertTriggerBoth: 'موفقیت یا شکست',
|
||||
alertNotificationHelper:
|
||||
'اعلان پس از نتیجه نهایی اجرا ارسال میشود، نه هنگام تلاش مجدد. اعلانهای موفقیت و شکست سقف روزانه و ساعات ارسال مشترک دارند.',
|
||||
notificationTitle: 'وظیفه زمانبندی - {0} «{1}» اعلان اجرا',
|
||||
notificationSuccessTitle: 'وظیفه زمانبندی - {0} «{1}» با موفقیت انجام شد',
|
||||
notificationRule: '{0}: {1}، حداکثر {2} اعلان در روز',
|
||||
clamsTitle: 'وظیفه اسکن ویروس « {0} » هشدار فایل آلوده شناسایی شد',
|
||||
cronJobAppTitle: 'وظیفه زمانبندی - پشتیبانگیری از برنامه « {0} » هشدار شکست وظیفه',
|
||||
cronJobWebsiteTitle: 'وظیفه زمانبندی - پشتیبانگیری از وبسایت « {0} » هشدار شکست وظیفه',
|
||||
|
||||
@@ -6717,6 +6717,15 @@ const message = {
|
||||
snapshot: 'システムスナップショット',
|
||||
clamsRulesHelper: 'アラートが必要なウイルススキャンタスク',
|
||||
cronJobRulesHelper: 'このタイプのスケジュールタスクには設定が必要です',
|
||||
alertTriggerMode: '通知条件',
|
||||
alertTriggerFailed: '失敗時のみ',
|
||||
alertTriggerSuccess: '成功時のみ',
|
||||
alertTriggerBoth: '成功時または失敗時',
|
||||
alertNotificationHelper:
|
||||
'再試行中ではなく、最終的な実行結果に基づいて通知します。成功と失敗の通知は、1日の上限と送信時間帯を共有します。',
|
||||
notificationTitle: '定期タスク - {0}「{1}」実行通知',
|
||||
notificationSuccessTitle: '定期タスク - {0}「{1}」が正常に完了しました',
|
||||
notificationRule: '{0}:{1}、1日最大 {2} 回通知',
|
||||
clamsTitle: 'ウイルススキャンタスク「{0}」が感染ファイルを検出したアラート',
|
||||
cronJobAppTitle: 'Cronジョブ - アプリバックアップ「{0}」タスク失敗アラート',
|
||||
cronJobWebsiteTitle: 'Cronジョブ - ウェブサイトバックアップ「{0}」タスク失敗アラート',
|
||||
|
||||
@@ -6582,6 +6582,15 @@ const message = {
|
||||
snapshot: '시스템 스냅샷',
|
||||
clamsRulesHelper: '알림이 필요한 바이러스 검사 작업',
|
||||
cronJobRulesHelper: '이 유형의 예약된 작업은 구성해야 합니다',
|
||||
alertTriggerMode: '알림 조건',
|
||||
alertTriggerFailed: '실패 시에만',
|
||||
alertTriggerSuccess: '성공 시에만',
|
||||
alertTriggerBoth: '성공 또는 실패 시',
|
||||
alertNotificationHelper:
|
||||
'재시도 중이 아닌 최종 실행 결과에 따라 알립니다. 성공 및 실패 알림은 일일 한도와 전송 시간대를 공유합니다.',
|
||||
notificationTitle: '예약 작업 - {0} 「{1}」 실행 알림',
|
||||
notificationSuccessTitle: '예약 작업 - {0} 「{1}」 실행 성공',
|
||||
notificationRule: '{0}: {1}, 하루 최대 {2}회 알림',
|
||||
clamsTitle: '바이러스 검사 작업 「 {0} 」 감염된 파일 알림',
|
||||
cronJobAppTitle: '크론 작업 - 백업 애플리케이션 「 {0} 」 작업 실패 알림',
|
||||
cronJobWebsiteTitle: '크론 작업 - 백업 웹사이트「 {0} 」작업 실패 알림',
|
||||
|
||||
@@ -6493,6 +6493,15 @@ const message = {
|
||||
snapshot: 'System Snapshot',
|
||||
clamsRulesHelper: 'ງານສະແກນໄວຣັສທີ່ຕ້ອງການແຈ້ງເຕືອນ',
|
||||
cronJobRulesHelper: 'ຕ້ອງຕັ້ງຄ່າງານ Cronjob ປະເພດນີ້',
|
||||
alertTriggerMode: 'ເງື່ອນໄຂແຈ້ງເຕືອນ',
|
||||
alertTriggerFailed: 'ເມື່ອລົ້ມເຫຼວເທົ່ານັ້ນ',
|
||||
alertTriggerSuccess: 'ເມື່ອສຳເລັດເທົ່ານັ້ນ',
|
||||
alertTriggerBoth: 'ເມື່ອສຳເລັດ ຫຼື ລົ້ມເຫຼວ',
|
||||
alertNotificationHelper:
|
||||
'ແຈ້ງເຕືອນຕາມຜົນສຸດທ້າຍ ບໍ່ແມ່ນລະຫວ່າງລອງໃໝ່. ການແຈ້ງເຕືອນສຳເລັດ ແລະ ລົ້ມເຫຼວໃຊ້ຂີດຈຳກັດລາຍວັນ ແລະ ເວລາສົ່ງຮ່ວມກັນ.',
|
||||
notificationTitle: 'ວຽກຕາມກຳນົດເວລາ - {0} 「{1}」 ແຈ້ງເຕືອນການເຮັດວຽກ',
|
||||
notificationSuccessTitle: 'ວຽກຕາມກຳນົດເວລາ - {0} 「{1}」 ສຳເລັດແລ້ວ',
|
||||
notificationRule: '{0}: {1}, ສູງສຸດ {2} ຄັ້ງຕໍ່ມື້',
|
||||
clamsTitle: 'ງານສະແກນໄວຣັສ 「 {0} 」 ກວດພົບໄຟລ໌ອັນຕະລາຍ',
|
||||
cronJobAppTitle: 'ແຈ້ງເຕືອນງານສຳຮອງແອັບ 「 {0} 」 ລົ້ມເຫຼວ',
|
||||
cronJobWebsiteTitle: 'ແຈ້ງເຕືອນງານສຳຮອງເວັບໄຊ 「 {0} 」 ລົ້ມເຫຼວ',
|
||||
|
||||
@@ -6833,6 +6833,15 @@ const message = {
|
||||
snapshot: 'Snapshot Sistem',
|
||||
clamsRulesHelper: 'Tugas imbasan virus yang memerlukan amaran',
|
||||
cronJobRulesHelper: 'Jenis tugas berjadual ini perlu dikonfigurasikan',
|
||||
alertTriggerMode: 'Syarat pemberitahuan',
|
||||
alertTriggerFailed: 'Gagal sahaja',
|
||||
alertTriggerSuccess: 'Berjaya sahaja',
|
||||
alertTriggerBoth: 'Berjaya atau gagal',
|
||||
alertNotificationHelper:
|
||||
'Maklumkan selepas keputusan akhir pelaksanaan, bukan semasa percubaan semula. Pemberitahuan berjaya dan gagal berkongsi had harian dan waktu penghantaran.',
|
||||
notificationTitle: 'Tugas berjadual - {0} 「{1}」 pemberitahuan pelaksanaan',
|
||||
notificationSuccessTitle: 'Tugas berjadual - {0} 「{1}」 berjaya diselesaikan',
|
||||
notificationRule: '{0}: {1}, sehingga {2} pemberitahuan sehari',
|
||||
clamsTitle: 'Tugas imbasan virus 「 {0} 」 mengesan amaran fail dijangkiti',
|
||||
cronJobAppTitle: 'Cronjob - Sandaran Aplikasi 「 {0} 」 Amaran Kegagalan Tugas',
|
||||
cronJobWebsiteTitle: 'Cronjob - Sandaran Laman Web 「 {0} 」 Amaran Kegagalan Tugas',
|
||||
|
||||
@@ -6867,6 +6867,15 @@ const message = {
|
||||
snapshot: 'Instantâneo do Sistema',
|
||||
clamsRulesHelper: 'Tarefas de varredura de vírus que exigem alertas por',
|
||||
cronJobRulesHelper: 'Este tipo de tarefa cron precisa ser configurado',
|
||||
alertTriggerMode: 'Notificar em caso de',
|
||||
alertTriggerFailed: 'Apenas falha',
|
||||
alertTriggerSuccess: 'Apenas sucesso',
|
||||
alertTriggerBoth: 'Sucesso ou falha',
|
||||
alertNotificationHelper:
|
||||
'Notificar após o resultado final, não durante as novas tentativas. As notificações de sucesso e falha compartilham o limite diário e o horário de envio.',
|
||||
notificationTitle: 'Tarefa agendada - {0} 「{1}」 notificação de execução',
|
||||
notificationSuccessTitle: 'Tarefa agendada - {0} 「{1}」 concluída com sucesso',
|
||||
notificationRule: '{0}: {1}, até {2} notificações por dia',
|
||||
clamsTitle: 'Tarefa de Varredura de Vírus 「 {0} 」 detectou arquivo infectado',
|
||||
cronJobAppTitle: 'Tarefa Cron - Falha no Backup do Aplicativo 「 {0} 」',
|
||||
cronJobWebsiteTitle: 'Tarefa Cron - Falha no Backup do Site 「 {0} 」',
|
||||
|
||||
@@ -6835,6 +6835,15 @@ const message = {
|
||||
snapshot: 'Системный снимок',
|
||||
clamsRulesHelper: 'Задачи сканирования на вирусы, требующие уведомлений',
|
||||
cronJobRulesHelper: 'Этот тип планируемой задачи требует конфигурации',
|
||||
alertTriggerMode: 'Условие уведомления',
|
||||
alertTriggerFailed: 'Только при ошибке',
|
||||
alertTriggerSuccess: 'Только при успехе',
|
||||
alertTriggerBoth: 'При успехе или ошибке',
|
||||
alertNotificationHelper:
|
||||
'Уведомление отправляется по окончательному результату, а не во время повторных попыток. Уведомления об успехе и ошибке используют общий дневной лимит и часы отправки.',
|
||||
notificationTitle: 'Плановая задача - {0} «{1}»: уведомление о выполнении',
|
||||
notificationSuccessTitle: 'Плановая задача - {0} «{1}» успешно завершена',
|
||||
notificationRule: '{0}: {1}, до {2} уведомлений в день',
|
||||
clamsTitle: 'Задача сканирования на вирусы 「 {0} 」 обнаружила заражённый файл',
|
||||
cronJobAppTitle: 'Задача Cron - ошибка задачи резервного копирования приложения 「 {0} 」',
|
||||
cronJobWebsiteTitle: 'Задача Cron - ошибка задачи резервного копирования сайта 「 {0} 」',
|
||||
|
||||
@@ -6841,6 +6841,15 @@ const message = {
|
||||
snapshot: 'Sistem Anlık Görüntüsü',
|
||||
clamsRulesHelper: 'Uyarısı gerektiren virüs tarama görevleri',
|
||||
cronJobRulesHelper: 'Bu tür zamanlanmış görevlerin yapılandırılması gerekir',
|
||||
alertTriggerMode: 'Bildirim koşulu',
|
||||
alertTriggerFailed: 'Yalnızca başarısızlık',
|
||||
alertTriggerSuccess: 'Yalnızca başarı',
|
||||
alertTriggerBoth: 'Başarı veya başarısızlık',
|
||||
alertNotificationHelper:
|
||||
'Yeniden denemeler sırasında değil, son yürütme sonucuna göre bildirim gönderilir. Başarı ve başarısızlık bildirimleri günlük sınırı ve gönderim saatlerini paylaşır.',
|
||||
notificationTitle: 'Zamanlanmış görev - {0} 「{1}」 yürütme bildirimi',
|
||||
notificationSuccessTitle: 'Zamanlanmış görev - {0} 「{1}」 başarıyla tamamlandı',
|
||||
notificationRule: '{0}: {1}, günde en fazla {2} bildirim',
|
||||
clamsTitle: 'Virüs tarama görevi 「 {0} 」 enfekte dosya algıladı uyarısı',
|
||||
cronJobAppTitle: 'Zamanlanmış Görev - Yedekleme Uygulaması 「 {0} 」 Görev Başarısızlık Uyarısı',
|
||||
cronJobWebsiteTitle: 'Zamanlanmış Görev - Yedekleme Web Sitesi 「 {0} 」 Görev Başarısızlık Uyarısı',
|
||||
|
||||
@@ -6238,6 +6238,15 @@ const message = {
|
||||
snapshot: '系統快照',
|
||||
clamsRulesHelper: '需要開啟告警的病毒掃描任務',
|
||||
cronJobRulesHelper: '需要設定此類型的計劃任務',
|
||||
alertTriggerMode: '通知條件',
|
||||
alertTriggerFailed: '僅失敗',
|
||||
alertTriggerSuccess: '僅成功',
|
||||
alertTriggerBoth: '成功或失敗',
|
||||
alertNotificationHelper:
|
||||
'任務最終執行完成後按結果通知,重試過程中不發送;成功與失敗共用每日次數及發送時段。',
|
||||
notificationTitle: '計劃任務-{0}「{1}」執行告警',
|
||||
notificationSuccessTitle: '計劃任務-{0}「{1}」執行成功通知',
|
||||
notificationRule: '{0}:{1},每天最多發送 {2} 次',
|
||||
clamsTitle: '病毒掃描「{0}」任務偵測到感染檔案告警',
|
||||
cronJobAppTitle: '計劃任務-備份應用「{0}」任務失敗告警',
|
||||
cronJobWebsiteTitle: '計劃任務-備份網站「{0}」任務失敗告警',
|
||||
|
||||
@@ -6294,6 +6294,15 @@ const message = {
|
||||
snapshot: '系统快照',
|
||||
clamsRulesHelper: '需要开启告警通知的病毒扫描任务',
|
||||
cronJobRulesHelper: '需要配置此类型的计划任务',
|
||||
alertTriggerMode: '通知条件',
|
||||
alertTriggerFailed: '仅失败',
|
||||
alertTriggerSuccess: '仅成功',
|
||||
alertTriggerBoth: '成功或失败',
|
||||
alertNotificationHelper:
|
||||
'任务最终执行完成后按结果通知,重试过程中不发送;成功与失败共用每日次数及发送时段。',
|
||||
notificationTitle: '计划任务-{0}「{1}」执行告警',
|
||||
notificationSuccessTitle: '计划任务-{0}「{1}」执行成功通知',
|
||||
notificationRule: '{0}:{1},每天最多发送 {2} 次',
|
||||
clamsTitle: '病毒扫描「 {0} 」任务检测到感染文件告警',
|
||||
cronJobAppTitle: '计划任务-备份应用「 {0} 」任务失败告警',
|
||||
cronJobWebsiteTitle: '计划任务-备份网站「 {0} 」任务失败告警',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ref, reactive, toRef } from 'vue';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { checkStreamAuth } from '@/utils/stream-auth';
|
||||
|
||||
@@ -46,6 +47,17 @@ export const ProcessStore = defineStore('ProcessStore', () => {
|
||||
port: undefined,
|
||||
});
|
||||
|
||||
useSearchPersistence('host/process/process', {
|
||||
pid: toRef(psSearch, 'pid'),
|
||||
name: toRef(psSearch, 'name'),
|
||||
username: toRef(psSearch, 'username'),
|
||||
});
|
||||
useSearchPersistence('host/process/network', {
|
||||
processID: toRef(netSearch, 'processID'),
|
||||
processName: toRef(netSearch, 'processName'),
|
||||
port: toRef(netSearch, 'port'),
|
||||
});
|
||||
|
||||
let pendingRequestType: 'ps' | 'net' | null = null;
|
||||
|
||||
let queuedRequestType: 'ps' | 'net' | null = null;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
export type CronjobAlertTriggerMode = 'failed' | 'success' | 'both';
|
||||
|
||||
export const cronjobAlertTypes = [
|
||||
'shell',
|
||||
'app',
|
||||
'website',
|
||||
'database',
|
||||
'directory',
|
||||
'log',
|
||||
'snapshot',
|
||||
'curl',
|
||||
'cutWebsiteLog',
|
||||
'clean',
|
||||
'ntp',
|
||||
'syncIpGroup',
|
||||
'cleanLog',
|
||||
];
|
||||
|
||||
export const cronjobAlertModes = [
|
||||
{ value: 'failed', label: 'xpack.alert.alertTriggerFailed' },
|
||||
{ value: 'success', label: 'xpack.alert.alertTriggerSuccess' },
|
||||
{ value: 'both', label: 'xpack.alert.alertTriggerBoth' },
|
||||
] as const;
|
||||
|
||||
export const normalizeCronjobAlertMode = (value: unknown): CronjobAlertTriggerMode => {
|
||||
return value === 'success' || value === 'both' ? value : 'failed';
|
||||
};
|
||||
|
||||
const parseAdvancedParams = (value?: string): Record<string, unknown> => {
|
||||
try {
|
||||
const params = JSON.parse(value || '{}');
|
||||
return params && typeof params === 'object' && !Array.isArray(params) ? params : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const getCronjobAlertMode = (advancedParams?: string): CronjobAlertTriggerMode => {
|
||||
return normalizeCronjobAlertMode(parseAdvancedParams(advancedParams).alertTriggerMode);
|
||||
};
|
||||
|
||||
export const setCronjobAlertMode = (advancedParams: string | undefined, mode: CronjobAlertTriggerMode): string => {
|
||||
return JSON.stringify({ ...parseAdvancedParams(advancedParams), alertTriggerMode: mode });
|
||||
};
|
||||
|
||||
export const getCronjobAlertModeLabel = (mode: unknown): string => {
|
||||
return cronjobAlertModes.find((item) => item.value === normalizeCronjobAlertMode(mode))!.label;
|
||||
};
|
||||
|
||||
export const getCronjobAlertTitle = (
|
||||
row: { type: string; subType?: string; taskName?: string; title: string },
|
||||
renderTitle: (type: string, taskName: string) => string,
|
||||
): string => {
|
||||
const type = row.type === 'cronJob' ? row.subType : row.type;
|
||||
if (type && cronjobAlertTypes.includes(type) && typeof row.taskName === 'string' && row.taskName.length > 0) {
|
||||
return renderTitle(type, row.taskName);
|
||||
}
|
||||
return row.title;
|
||||
};
|
||||
|
||||
export const getCronjobAlertResult = (params: unknown): 'success' | 'failed' => {
|
||||
if (typeof params === 'string') {
|
||||
try {
|
||||
params = JSON.parse(params);
|
||||
} catch {
|
||||
return 'failed';
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(params)) return 'failed';
|
||||
const result = params.find((item) => item?.index === 'result');
|
||||
if (result) return result.value === 'success' ? 'success' : 'failed';
|
||||
const numberedResult = params.find((item) => item?.index === '2' && item?.key === 'result');
|
||||
return numberedResult?.value === '成功' ? 'success' : 'failed';
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { watch, type Ref } from 'vue';
|
||||
|
||||
type SearchValue = string | number | undefined;
|
||||
type SearchFields = Record<string, Ref<SearchValue>>;
|
||||
|
||||
export const bindSearchPersistence = (key: () => string | undefined, fields: SearchFields) => {
|
||||
const defaults = Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value]));
|
||||
let initialized = false;
|
||||
let restoring = false;
|
||||
let activeKey: string | undefined;
|
||||
|
||||
const stopRestore = watch(
|
||||
key,
|
||||
(value) => {
|
||||
activeKey = value ? `1panel:search:v1:${value}` : undefined;
|
||||
let saved: Record<string, unknown> = {};
|
||||
try {
|
||||
const parsed = activeKey ? JSON.parse(localStorage.getItem(activeKey) || '{}') : {};
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) saved = parsed;
|
||||
} catch {
|
||||
// Invalid or unavailable storage must not prevent a page from loading.
|
||||
}
|
||||
restoring = true;
|
||||
for (const [name, field] of Object.entries(fields)) {
|
||||
const value = saved[name];
|
||||
const initial = defaults[name];
|
||||
const fallback = initialized ? (typeof initial === 'string' ? '' : undefined) : initial;
|
||||
const valid =
|
||||
(typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))) &&
|
||||
(fallback === undefined || typeof value === typeof fallback);
|
||||
field.value =
|
||||
value === null
|
||||
? typeof initial === 'string'
|
||||
? ''
|
||||
: undefined
|
||||
: valid
|
||||
? (value as SearchValue)
|
||||
: fallback;
|
||||
}
|
||||
restoring = false;
|
||||
initialized = true;
|
||||
},
|
||||
{ immediate: true, flush: 'sync' },
|
||||
);
|
||||
|
||||
const stopSave = watch(
|
||||
() => Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value ?? null])),
|
||||
(value) => {
|
||||
if (restoring || !activeKey) return;
|
||||
try {
|
||||
localStorage.setItem(activeKey, JSON.stringify(value));
|
||||
} catch {
|
||||
// Search remains usable when browser storage is disabled or full.
|
||||
}
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
|
||||
return () => {
|
||||
stopRestore();
|
||||
stopSave();
|
||||
};
|
||||
};
|
||||
@@ -172,6 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
@@ -192,6 +193,11 @@ const searching = ref(false);
|
||||
const operating = ref('');
|
||||
const keyword = ref('');
|
||||
const marketKeyword = ref('');
|
||||
useSearchPersistence(
|
||||
'ai/agents/agent/config/tabs/plugins',
|
||||
{ keyword, marketKeyword },
|
||||
() => agentId.value || undefined,
|
||||
);
|
||||
const origin = ref('');
|
||||
const status = ref('');
|
||||
const page = ref(1);
|
||||
@@ -299,8 +305,6 @@ async function operate(plugin: AI.AgentPluginItem, action: PluginOperate) {
|
||||
async function load(id: number) {
|
||||
agentId.value = id;
|
||||
mode.value = 'installed';
|
||||
keyword.value = '';
|
||||
marketKeyword.value = '';
|
||||
origin.value = '';
|
||||
status.value = '';
|
||||
page.value = 1;
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -168,6 +169,11 @@ const getDefaultMarketSource = (): HermesSkillMarketSource => {
|
||||
const marketSource = ref<HermesSkillMarketSource>(getDefaultMarketSource());
|
||||
const marketSearched = ref(false);
|
||||
const agentId = ref(0);
|
||||
useSearchPersistence(
|
||||
'ai/agents/agent/config/tabs/skills/hermes',
|
||||
{ installedKeyword, marketKeyword },
|
||||
() => agentId.value || undefined,
|
||||
);
|
||||
const installedSkills = ref<AI.AgentSkillItem[]>([]);
|
||||
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
||||
const installingSkill = ref('');
|
||||
@@ -315,8 +321,6 @@ const handleModeChange = async (value: SkillViewMode) => {
|
||||
const load = async (id: number) => {
|
||||
agentId.value = id;
|
||||
mode.value = 'market';
|
||||
installedKeyword.value = '';
|
||||
marketKeyword.value = '';
|
||||
marketSource.value = getDefaultMarketSource();
|
||||
marketSearched.value = false;
|
||||
installedSkills.value = [];
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -180,6 +181,11 @@ const getDefaultMarketSource = (): SkillMarketSource => {
|
||||
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
|
||||
const marketSearched = ref(false);
|
||||
const agentId = ref(0);
|
||||
useSearchPersistence(
|
||||
'ai/agents/agent/config/tabs/skills/openclaw',
|
||||
{ installedKeyword, marketKeyword },
|
||||
() => agentId.value || undefined,
|
||||
);
|
||||
const skills = ref<AI.AgentSkillItem[]>([]);
|
||||
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
||||
const updatingSkill = ref('');
|
||||
|
||||
@@ -272,6 +272,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { deleteAgentCheck, pageAgents, resetAgentToken, unbindAgentWebsite, updateAgentRemark } from '@/api/modules/ai';
|
||||
@@ -340,6 +341,7 @@ const isActive = ref(false);
|
||||
const isExist = ref(false);
|
||||
const noApp = ref(false);
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('ai/agents/agent/index', { search: searchName });
|
||||
const defaultHttpsPort = ref(443);
|
||||
const openrestyPortLoaded = ref(false);
|
||||
const websiteDomainsMap = ref<Record<number, Website.Domain[]>>({});
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
@@ -85,6 +86,7 @@ const items = ref<AI.AgentAccountItem[]>([]);
|
||||
const addRef = ref();
|
||||
const modelPoolRef = ref();
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('ai/agents/model/index', { search: searchName });
|
||||
const apiType = ref('');
|
||||
const apiTypeOptions = ref<string[]>([]);
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import AddDialog from '@/views/ai/model/ollama/add/index.vue';
|
||||
import Conn from '@/views/ai/model/ollama/conn/index.vue';
|
||||
@@ -234,6 +235,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('ai/model/ollama/index', { search: searchName });
|
||||
const appInstallID = ref(0);
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
@@ -70,8 +70,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||
import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCustomAppConfig } from '@/api/modules/app';
|
||||
import Install from '../detail/install/index.vue';
|
||||
import router from '@/routers';
|
||||
@@ -104,6 +105,7 @@ const req = reactive({
|
||||
resource: 'all',
|
||||
showCurrentArch: false,
|
||||
});
|
||||
useSearchPersistence('app-store/apps/index', { search: toRef(req, 'name') });
|
||||
|
||||
const apps = ref<App.AppItem[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import AppCard from '@/views/app-store/installed/app/card.vue';
|
||||
import Backups from '@/components/backup/index.vue';
|
||||
import Uploads from '@/components/upload/index.vue';
|
||||
@@ -156,7 +157,7 @@ import IgnoreApp from '@/views/app-store/installed/ignore/create/index.vue';
|
||||
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||
|
||||
import { searchAppInstalled, installedOp, appInstalledDeleteCheck, updateAppInstallSort } from '@/api/modules/app';
|
||||
import { onMounted, onUnmounted, reactive, ref, nextTick } from 'vue';
|
||||
import { toRef, onMounted, onUnmounted, reactive, ref, nextTick } from 'vue';
|
||||
import Sortable from 'sortablejs';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
@@ -210,6 +211,7 @@ const searchReq = reactive({
|
||||
update: false,
|
||||
sync: false,
|
||||
});
|
||||
useSearchPersistence('app-store/installed/index', { search: toRef(searchReq, 'name') });
|
||||
const router = useRouter();
|
||||
const activeName = ref(i18n.global.t('app.installed'));
|
||||
const mode = ref('installed');
|
||||
|
||||
@@ -521,6 +521,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, h, ref } from 'vue';
|
||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
import ContainerLog from '@/components/log/container/index.vue';
|
||||
@@ -575,6 +576,7 @@ const dialogBackupRef = ref();
|
||||
const uploadRef = ref();
|
||||
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('container/compose/index', { search: searchName });
|
||||
const includeAppStore = ref(localStorage.getItem('includeAppStore') !== 'false');
|
||||
const showType = ref('compose');
|
||||
const containerStats = ref<any[]>([]);
|
||||
|
||||
@@ -473,6 +473,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import PruneDialog from '@/views/container/container/prune/index.vue';
|
||||
import RenameDialog from '@/views/container/container/rename/index.vue';
|
||||
import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
||||
@@ -534,6 +535,7 @@ const paginationConfig = reactive({
|
||||
order: 'ascending',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('container/container/index', { search: searchName });
|
||||
const dialogUpgradeRef = ref();
|
||||
const dialogCommitRef = ref();
|
||||
const dialogPortJumpRef = ref();
|
||||
|
||||
@@ -157,7 +157,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { toRef, reactive, ref, computed } from 'vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { computeSize2 } from '@/utils/size';
|
||||
@@ -199,6 +200,7 @@ const paginationConfig = reactive({
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
useSearchPersistence('container/image/index', { search: toRef(paginationConfig, 'name') });
|
||||
const columns = ref([]);
|
||||
|
||||
const isActive = ref(false);
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import CreateDialog from '@/views/container/network/create/index.vue';
|
||||
import DetailDrawer from '@/views/container/network/detail/index.vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
@@ -117,6 +118,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('container/network/index', { search: searchName });
|
||||
|
||||
const opRef = ref();
|
||||
const isActive = ref(false);
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import OperatorDialog from '@/views/container/repo/operator/index.vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
@@ -77,6 +78,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('container/repo/index', { search: searchName });
|
||||
|
||||
const opRef = ref();
|
||||
const confirmDialog = ref();
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
||||
import { downloadWithContent } from '@/utils/file';
|
||||
@@ -108,6 +109,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('container/template/index', { search: searchName });
|
||||
|
||||
const search = async () => {
|
||||
if (!isActive.value || !isExist.value) {
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import CreateDialog from '@/views/container/volume/create/index.vue';
|
||||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
@@ -155,6 +156,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('container/volume/index', { search: searchName });
|
||||
const isActive = ref(false);
|
||||
const isExist = ref(false);
|
||||
|
||||
|
||||
@@ -270,6 +270,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import Records from '@/views/cronjob/cronjob/record/index.vue';
|
||||
import Backups from '@/views/cronjob/cronjob/backup/index.vue';
|
||||
import Import from '@/views/cronjob/cronjob/import/index.vue';
|
||||
@@ -329,6 +330,7 @@ const pageState = usePageState(() => ({
|
||||
}));
|
||||
const paginationConfig = pageState.paginationConfig;
|
||||
const { defaultGroupID, searchName, searchGroupID } = toRefs(pageState);
|
||||
useSearchPersistence('cronjob/cronjob/index', { search: searchName });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
if (column) {
|
||||
|
||||
@@ -689,10 +689,10 @@
|
||||
|
||||
<el-card class="mt-5">
|
||||
<el-row :gutter="20">
|
||||
<LayoutCol :span="8">
|
||||
<LayoutCol :span="20">
|
||||
<el-form-item prop="hasAlert">
|
||||
<el-checkbox v-model="form.hasAlert" :label="$t('xpack.alert.isAlert')" />
|
||||
<span class="input-help">{{ $t('xpack.alert.cronJobHelper') }}</span>
|
||||
<span class="input-help">{{ $t('xpack.alert.alertNotificationHelper') }}</span>
|
||||
|
||||
<span class="input-help logText" v-if="form.hasAlert && !isProductPro">
|
||||
{{ $t('xpack.alert.licenseHelper') }}
|
||||
@@ -703,6 +703,20 @@
|
||||
</el-form-item>
|
||||
</LayoutCol>
|
||||
</el-row>
|
||||
<el-row :gutter="20" v-if="form.hasAlert">
|
||||
<LayoutCol>
|
||||
<el-form-item :label="$t('xpack.alert.alertTriggerMode')" prop="alertTriggerMode">
|
||||
<el-select class="selectClass" v-model="form.alertTriggerMode">
|
||||
<el-option
|
||||
v-for="item in cronjobAlertModes"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="$t(item.label)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</LayoutCol>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<LayoutCol>
|
||||
<el-form-item
|
||||
@@ -871,6 +885,7 @@ import { getGroupList } from '@/api/modules/group';
|
||||
import { routerToName, routerToPath } from '@/utils/router';
|
||||
import { loadBaseDir } from '@/api/modules/setting';
|
||||
import { getAlertConfigDisplayName } from '@/views/setting/alert/setting/drawer/secret-field';
|
||||
import { cronjobAlertModes, normalizeCronjobAlertMode } from '@/utils/cronjob-alert';
|
||||
const router = useRouter();
|
||||
|
||||
const { docsUrl, isFxplay, isProductPro } = useGlobalStore();
|
||||
@@ -1034,6 +1049,7 @@ const form = reactive<Cronjob.CronjobInfo>({
|
||||
secret: '',
|
||||
hasAlert: false,
|
||||
alertCount: 3,
|
||||
alertTriggerMode: 'failed',
|
||||
alertTitle: '',
|
||||
alertMethod: '',
|
||||
alertMethodItems: [],
|
||||
@@ -1133,6 +1149,7 @@ const search = async () => {
|
||||
form.secret = res.data.secret;
|
||||
form.hasAlert = res.data.alertCount > 0;
|
||||
form.alertCount = res.data.alertCount || 3;
|
||||
form.alertTriggerMode = normalizeCronjobAlertMode(res.data.alertTriggerMode);
|
||||
form.alertTitle = res.data.alertTitle;
|
||||
if (res.data.alertMethod) {
|
||||
form.alertMethodItems = normalizeAlertMethodItems(res.data.alertMethod.split(',') || []);
|
||||
@@ -1671,7 +1688,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
form.alertCount = form.hasAlert ? form.alertCount : 0;
|
||||
form.alertMethod = form.alertMethodItems.join(',');
|
||||
form.alertTitle = form.hasAlert
|
||||
? i18n.global.t('cronjob.alertTitle', [i18n.global.t('cronjob.' + form.type), form.name])
|
||||
? i18n.global.t('xpack.alert.notificationTitle', [i18n.global.t('cronjob.' + form.type), form.name])
|
||||
: '';
|
||||
if (!form) return;
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { newUUID } from '@/utils/id';
|
||||
@@ -167,6 +168,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchInfo = ref<string>('');
|
||||
useSearchPersistence('cronjob/library/index', { search: searchInfo });
|
||||
const group = ref<string>('');
|
||||
const groupOptions = ref();
|
||||
|
||||
|
||||
@@ -323,6 +323,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { getRandomStr } from '@/utils/id';
|
||||
@@ -365,6 +366,7 @@ const loading = ref(false);
|
||||
const maskShow = ref(true);
|
||||
const submitLoading = ref(false);
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('database/mongodb/index', { search: searchName });
|
||||
const createVisible = ref(false);
|
||||
const appStatusRef = ref();
|
||||
const bindRef = ref();
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -100,6 +101,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('database/mongodb/remote/index', { search: searchName });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import BindDialog from '@/views/database/mysql/bind/index.vue';
|
||||
import UserDialog from '@/views/database/mysql/user/index.vue';
|
||||
import OperateDialog from '@/views/database/mysql/create/index.vue';
|
||||
@@ -300,6 +301,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('database/mysql/index', { search: searchName });
|
||||
|
||||
const mysqlContainer = ref();
|
||||
const mysqlStatus = ref();
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -99,6 +100,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('database/mysql/remote/index', { search: searchName });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -241,6 +241,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import BindDialog from '@/views/database/postgresql/bind/index.vue';
|
||||
import OperateDialog from '@/views/database/postgresql/create/index.vue';
|
||||
import DeleteDialog from '@/views/database/postgresql/delete/index.vue';
|
||||
@@ -312,6 +313,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('database/postgresql/index', { search: searchName });
|
||||
|
||||
const postgresqlContainer = ref();
|
||||
const postgresqlStatus = ref();
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -99,6 +100,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('database/postgresql/remote/index', { search: searchName });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -98,6 +99,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('database/redis/remote/index', { search: searchName });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -739,7 +739,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { toRef, computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import {
|
||||
addFavorite,
|
||||
batchGetFileRemarks,
|
||||
@@ -852,6 +853,7 @@ const initData = () => ({
|
||||
sortOrder: 'ascending',
|
||||
});
|
||||
let req = reactive(initData());
|
||||
useSearchPersistence('host/file-management/index', { search: toRef(req, 'search') });
|
||||
let loading = ref(false);
|
||||
const paths = ref<FilePaths[]>([]);
|
||||
const breadcrumbVisiblePaths = ref<FilePaths[]>([]);
|
||||
|
||||
@@ -232,6 +232,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import FireRouter from '@/views/host/firewall/index.vue';
|
||||
import DockerGuardStatus from '@/views/host/firewall/docker/status/index.vue';
|
||||
@@ -274,6 +275,7 @@ const taskLogRef = ref<InstanceType<typeof TaskLog>>();
|
||||
const openRuleTask = (taskID: string) => taskLogRef.value?.openWithTaskID(taskID, true);
|
||||
const orphanDrawerVisible = ref(false);
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('host/firewall/docker/index', { search: searchName });
|
||||
const selects = ref<Firewall.DockerGuardContainer[]>([]);
|
||||
const orphanSelects = ref<Firewall.DockerGuardEndpoint[]>([]);
|
||||
const data = reactive<Firewall.DockerGuardList>({
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import OperateDialog from './operate/index.vue';
|
||||
import ImportDialog from './import/index.vue';
|
||||
import RuleSync from '@/views/host/firewall/sync/index.vue';
|
||||
@@ -153,6 +154,7 @@ import { ElMessageBox } from 'element-plus';
|
||||
const loading = ref();
|
||||
const selects = ref<any>([]);
|
||||
const searchName = ref();
|
||||
useSearchPersistence('host/firewall/forward/index', { search: searchName });
|
||||
|
||||
const isInit = ref(false);
|
||||
const isBind = ref(false);
|
||||
|
||||
@@ -397,6 +397,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { Firewall } from '@/api/interface/firewall';
|
||||
import { Process } from '@/api/interface/process';
|
||||
import {
|
||||
@@ -516,6 +517,7 @@ const visibleIptablesChains = ref<string[]>(
|
||||
loadCachedFilterValues(chainFilterStorageKey, iptablesChains, ['1PANEL_BASIC']),
|
||||
);
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('host/firewall/rule/index', { search: searchName });
|
||||
const inventoryItems = ref<Firewall.InventoryItem[]>([]);
|
||||
const positionRanges = ref<Partial<Record<Firewall.Family, Firewall.PositionRange>>>({});
|
||||
const inventoryTotal = ref(0);
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { dateFormatServerTimezone } from '@/utils/date';
|
||||
import { downloadFile } from '@/utils/file';
|
||||
@@ -119,6 +120,7 @@ const exportConfig = reactive({
|
||||
status: 'All',
|
||||
});
|
||||
const searchInfo = ref();
|
||||
useSearchPersistence('host/ssh/log/log', { search: searchInfo });
|
||||
const searchStatus = ref('All');
|
||||
|
||||
const search = async () => {
|
||||
|
||||
@@ -24,8 +24,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import FireRouter from '@/views/host/ssh/index.vue';
|
||||
import { ref, onMounted, onUnmounted, reactive } from 'vue';
|
||||
import { toRef, ref, onMounted, onUnmounted, reactive } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { stopProcess } from '@/api/modules/process';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
@@ -37,6 +38,7 @@ const sshSearch = reactive({
|
||||
type: 'ssh',
|
||||
loginUser: '',
|
||||
});
|
||||
useSearchPersistence('host/ssh/session/index', { search: toRef(sshSearch, 'loginUser') });
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { Log } from '@/api/interface/log';
|
||||
import { getSystemLogStatus, listRunningServices, readSystemLogs } from '@/api/modules/log';
|
||||
@@ -113,6 +114,7 @@ const logs = ref<Log.SystemLogItem[]>([]);
|
||||
const loading = ref(false);
|
||||
const watching = ref(false);
|
||||
const keyword = ref('');
|
||||
useSearchPersistence('log/host-system/index', { keyword });
|
||||
const priority = ref('');
|
||||
const service = ref('');
|
||||
const services = ref<string[]>([]);
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import LogRouter from '@/views/log/router/index.vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
@@ -66,6 +67,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchInfo = ref<string>('');
|
||||
useSearchPersistence('log/login/index', { search: searchInfo });
|
||||
const searchStatus = ref<string>('');
|
||||
|
||||
const search = async () => {
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import LogRouter from '@/views/log/router/index.vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
@@ -145,6 +146,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref<string>('');
|
||||
useSearchPersistence('log/operation/index', { search: searchName });
|
||||
const searchGroup = ref<string>('');
|
||||
const searchStatus = ref<string>('');
|
||||
const searchNode = ref<string>('');
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
<el-table-column
|
||||
:label="$t('commons.table.title')"
|
||||
prop="title"
|
||||
:formatter="formatTitle"
|
||||
min-width="300px"
|
||||
show-overflow-tooltip
|
||||
></el-table-column>
|
||||
@@ -167,6 +168,12 @@ import { ElMessageBox } from 'element-plus';
|
||||
import AddTask from '@/views/setting/alert/dash/task/index.vue';
|
||||
import { Alert } from '@/api/interface/alert';
|
||||
import { UpdateAlertStatus, SearchAlerts, DeleteAlert, PageAlertConfigs } from '@/api/modules/alert';
|
||||
import {
|
||||
cronjobAlertTypes,
|
||||
getCronjobAlertMode,
|
||||
getCronjobAlertModeLabel,
|
||||
getCronjobAlertTitle,
|
||||
} from '@/utils/cronjob-alert';
|
||||
|
||||
const { isMobile, isMaster, isProductPro, isEE } = useGlobalStore();
|
||||
|
||||
@@ -238,7 +245,21 @@ const changeSort = ({ prop, order }) => {
|
||||
search();
|
||||
};
|
||||
|
||||
const formatTitle = (row: Alert.AlertInfo) => {
|
||||
return getCronjobAlertTitle(row, (type, taskName) =>
|
||||
t('xpack.alert.notificationTitle', [t('cronjob.' + type), taskName]),
|
||||
);
|
||||
};
|
||||
|
||||
const formatRule = (row: Alert.AlertInfo) => {
|
||||
const type = row.type === 'cronJob' ? row.subType : row.type;
|
||||
if (cronjobAlertTypes.includes(type)) {
|
||||
return t('xpack.alert.notificationRule', [
|
||||
t('cronjob.' + type),
|
||||
t(getCronjobAlertModeLabel(getCronjobAlertMode(row.advancedParams))),
|
||||
row.sendCount,
|
||||
]);
|
||||
}
|
||||
const ruleTemplates = {
|
||||
ssl: () => t('xpack.alert.timeRule', [row.cycle, row.sendCount]),
|
||||
siteEndTime: () => t('xpack.alert.timeRule', [row.cycle, row.sendCount]),
|
||||
@@ -253,17 +274,6 @@ const formatRule = (row: Alert.AlertInfo) => {
|
||||
: t('xpack.alert.diskRule', [row.project, row.count, row.cycle === 1 ? 'G' : '%', row.sendCount]);
|
||||
},
|
||||
clams: () => t('xpack.alert.clamsRule', [row.sendCount]),
|
||||
app: () => t('xpack.alert.cronJobAppRule', [row.sendCount]),
|
||||
website: () => t('xpack.alert.cronJobWebsiteRule', [row.sendCount]),
|
||||
database: () => t('xpack.alert.cronJobDatabaseRule', [row.sendCount]),
|
||||
directory: () => t('xpack.alert.cronJobDirectoryRule', [row.sendCount]),
|
||||
log: () => t('xpack.alert.cronJobLogRule', [row.sendCount]),
|
||||
snapshot: () => t('xpack.alert.cronJobSnapshotRule', [row.sendCount]),
|
||||
shell: () => t('xpack.alert.cronJobShellRule', [row.sendCount]),
|
||||
curl: () => t('xpack.alert.cronJobCurlRule', [row.sendCount]),
|
||||
cutWebsiteLog: () => t('xpack.alert.cronJobCutWebsiteLogRule', [row.sendCount]),
|
||||
clean: () => t('xpack.alert.cronJobCleanRule', [row.sendCount]),
|
||||
ntp: () => t('xpack.alert.cronJobNtpRule', [row.sendCount]),
|
||||
nodeException: () => t('xpack.alert.nodeExceptionRule', [row.sendCount]),
|
||||
licenseException: () => t('xpack.alert.licenseExceptionRule', [row.sendCount]),
|
||||
panelLogin: () => t('xpack.alert.panelLoginRule', [row.sendCount]),
|
||||
|
||||
@@ -60,6 +60,8 @@
|
||||
<el-option value="clean" :label="$t('setting.diskClean')" />
|
||||
<el-option value="snapshot" :label="$t('cronjob.snapshot')" />
|
||||
<el-option value="ntp" :label="$t('cronjob.ntp')" />
|
||||
<el-option value="syncIpGroup" :label="$t('cronjob.syncIpGroup')" />
|
||||
<el-option value="cleanLog" :label="$t('cronjob.cleanLog')" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -305,6 +307,22 @@
|
||||
<span class="input-help">{{ $t('xpack.alert.ipWhiteListHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'cronJob'"
|
||||
:label="$t('xpack.alert.alertTriggerMode')"
|
||||
prop="alertTriggerMode"
|
||||
>
|
||||
<el-select class="selectClass" v-model="dialogData.rowData!.alertTriggerMode">
|
||||
<el-option
|
||||
v-for="item in cronjobAlertModes"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="$t(item.label)"
|
||||
/>
|
||||
</el-select>
|
||||
<span class="input-help">{{ $t('xpack.alert.alertNotificationHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('xpack.alert.sendCount')" prop="sendCount">
|
||||
<el-input v-model.number="dialogData.rowData!.sendCount" />
|
||||
<span class="input-help">
|
||||
@@ -398,6 +416,7 @@ import { routerToName } from '@/utils/router';
|
||||
import { checkCidr, checkCidrV6, checkIpV4V6 } from '@/utils/validate';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { getAlertConfigDisplayName } from '@/views/setting/alert/setting/drawer/secret-field';
|
||||
import { cronjobAlertTypes, cronjobAlertModes, getCronjobAlertMode, setCronjobAlertMode } from '@/utils/cronjob-alert';
|
||||
|
||||
const { isMaster, isProductPro, isEE, isIntl } = useGlobalStore();
|
||||
|
||||
@@ -524,19 +543,7 @@ const noParamTypes = ['panelUpdate'];
|
||||
const intervalTypes = ['cpu', 'memory', 'load', 'disk', 'sshLogin', 'panelLogin', 'nodeException', 'licenseException'];
|
||||
|
||||
const diskTypes = ['disk'];
|
||||
const cronjobTypes = [
|
||||
'shell',
|
||||
'app',
|
||||
'website',
|
||||
'database',
|
||||
'directory',
|
||||
'log',
|
||||
'snapshot',
|
||||
'curl',
|
||||
'cutWebsiteLog',
|
||||
'clean',
|
||||
'ntp',
|
||||
];
|
||||
const cronjobTypes = cronjobAlertTypes;
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
@@ -556,6 +563,7 @@ const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value.rowData.subType = dialogData.value.rowData.type;
|
||||
dialogData.value.rowData.type = 'cronJob';
|
||||
}
|
||||
dialogData.value.rowData.alertTriggerMode = getCronjobAlertMode(dialogData.value.rowData.advancedParams);
|
||||
initOptions(dialogData.value.rowData.type, dialogData.value.rowData.subType);
|
||||
visible.value = true;
|
||||
};
|
||||
@@ -655,6 +663,7 @@ function checkSendCount(rule: any, value: any, callback: any) {
|
||||
}
|
||||
|
||||
function checkIPs(rule: any, value: any, callback: any) {
|
||||
if (!ipTypes.includes(dialogData.value.rowData.type)) return callback();
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
let addr = value.split('\n');
|
||||
for (const item of addr) {
|
||||
@@ -793,6 +802,12 @@ const changeType = () => {
|
||||
type = subType;
|
||||
rowData.subType = subType;
|
||||
}
|
||||
if (cronjobTypes.includes(type)) {
|
||||
rowData.project = '';
|
||||
rowData.cycle = 0;
|
||||
rowData.count = 0;
|
||||
rowData.alertTriggerMode = rowData.alertTriggerMode || 'failed';
|
||||
}
|
||||
rowData.project = typeof typeToProjectMap[type] !== 'undefined' ? typeToProjectMap[type] : rowData.project;
|
||||
rowData.cycle = typeof typeToCycleMap[type] !== 'undefined' ? typeToCycleMap[type] : rowData.cycle;
|
||||
rowData.count = typeof typeToCountMap[type] !== 'undefined' ? typeToCountMap[type] : rowData.count;
|
||||
@@ -838,12 +853,13 @@ const loadCronJob = async (jobType: string) => {
|
||||
status: '',
|
||||
});
|
||||
cronJobOptions.value = res.data || [];
|
||||
dialogData.value.rowData.project = dialogData.value.rowData.project || String(cronJobOptions.value[0].id);
|
||||
dialogData.value.rowData.project = dialogData.value.rowData.project || String(cronJobOptions.value[0]?.id || '');
|
||||
};
|
||||
|
||||
const formatTitle = (row: Alert.AlertInfo) => {
|
||||
if (row.type === 'cronJob') {
|
||||
row.type = row.subType;
|
||||
const type = row.type === 'cronJob' ? row.subType : row.type;
|
||||
if (cronjobTypes.includes(type)) {
|
||||
return t('xpack.alert.notificationTitle', [t('cronjob.' + type), formatCronJobName(Number(row.project))]);
|
||||
}
|
||||
const titleTemplates = {
|
||||
ssl: () => {
|
||||
@@ -865,17 +881,6 @@ const formatTitle = (row: Alert.AlertInfo) => {
|
||||
return row.project === 'all' ? t('xpack.alert.allDiskTitle') : t('xpack.alert.diskTitle', [row.project]);
|
||||
},
|
||||
clams: () => t('xpack.alert.clamsTitle', [formatClamName(Number(row.project))]),
|
||||
app: () => t('xpack.alert.cronJobAppTitle', [formatCronJobName(Number(row.project))]),
|
||||
website: () => t('xpack.alert.cronJobWebsiteTitle', [formatCronJobName(Number(row.project))]),
|
||||
database: () => t('xpack.alert.cronJobDatabaseTitle', [formatCronJobName(Number(row.project))]),
|
||||
directory: () => t('xpack.alert.cronJobDirectoryTitle', [formatCronJobName(Number(row.project))]),
|
||||
log: () => t('xpack.alert.cronJobLogTitle', [formatCronJobName(Number(row.project))]),
|
||||
snapshot: () => t('xpack.alert.cronJobSnapshotTitle', [formatCronJobName(Number(row.project))]),
|
||||
shell: () => t('xpack.alert.cronJobShellTitle', [formatCronJobName(Number(row.project))]),
|
||||
curl: () => t('xpack.alert.cronJobCurlTitle', [formatCronJobName(Number(row.project))]),
|
||||
cutWebsiteLog: () => t('xpack.alert.cronJobCutWebsiteLogTitle', [formatCronJobName(Number(row.project))]),
|
||||
clean: () => t('xpack.alert.cronJobCleanTitle', [formatCronJobName(Number(row.project))]),
|
||||
ntp: () => t('xpack.alert.cronJobNtpTitle', [formatCronJobName(Number(row.project))]),
|
||||
nodeException: () => t('xpack.alert.nodeException'),
|
||||
licenseException: () => t('xpack.alert.licenseException'),
|
||||
panelLogin: () => t('xpack.alert.panelLogin'),
|
||||
@@ -909,25 +914,31 @@ const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
if (!formEl) return;
|
||||
loading.value = true;
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (!dialogData.value.rowData) return;
|
||||
if (!valid || !dialogData.value.rowData) {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
const sendMethods = dialogData.value.rowData.sendMethod.includes(ALL_SEND_METHOD)
|
||||
? allConfigValues.value
|
||||
: dialogData.value.rowData.sendMethod;
|
||||
dialogData.value.rowData.method = sendMethods.join(',');
|
||||
dialogData.value.rowData.title = formatTitle(dialogData.value.rowData);
|
||||
if (dialogData.value.rowData.type === 'cronJob') {
|
||||
dialogData.value.rowData.type = dialogData.value.rowData.subType;
|
||||
const payload = {
|
||||
...dialogData.value.rowData,
|
||||
method: sendMethods.join(','),
|
||||
title: formatTitle(dialogData.value.rowData),
|
||||
};
|
||||
if (payload.type === 'cronJob') {
|
||||
payload.type = payload.subType;
|
||||
payload.advancedParams = setCronjobAlertMode(payload.advancedParams, payload.alertTriggerMode || 'failed');
|
||||
}
|
||||
try {
|
||||
if (dialogData.value.title === 'create') {
|
||||
await CreateAlert(dialogData.value.rowData);
|
||||
await CreateAlert(payload);
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
} else if (dialogData.value.title === 'edit') {
|
||||
await UpdateAlert(dialogData.value.rowData);
|
||||
await UpdateAlert(payload);
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
}
|
||||
emit('search');
|
||||
|
||||
@@ -99,6 +99,7 @@ import {
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { getAlertConfigDisplayName } from '@/views/setting/alert/setting/drawer/secret-field';
|
||||
import { cronjobAlertTypes, getCronjobAlertResult } from '@/utils/cronjob-alert';
|
||||
|
||||
const { isMobile, isProductPro, isIntl, isMaster } = useGlobalStore();
|
||||
const { t } = i18n.global;
|
||||
@@ -193,7 +194,15 @@ const syncAlert = (row: Alert.AlertLog) => {
|
||||
});
|
||||
};
|
||||
|
||||
const formatMessage = (row: Alert.AlertInfo) => {
|
||||
const formatMessage = (row: Alert.AlertDetail) => {
|
||||
const type = row.type === 'cronJob' ? row.subType : row.type;
|
||||
if (cronjobAlertTypes.includes(type)) {
|
||||
const title =
|
||||
getCronjobAlertResult(row.params) === 'success'
|
||||
? 'xpack.alert.notificationSuccessTitle'
|
||||
: 'cronjob.alertTitle';
|
||||
return t(title, [t('cronjob.' + type), row.project]);
|
||||
}
|
||||
const messageTemplates = {
|
||||
ssl: () => {
|
||||
return row.project === 'all' ? t('xpack.alert.allSslTitle') : t('xpack.alert.sslTitle', [row.project]);
|
||||
@@ -212,17 +221,6 @@ const formatMessage = (row: Alert.AlertInfo) => {
|
||||
return row.project === 'all' ? t('xpack.alert.allDiskTitle') : t('xpack.alert.diskTitle', [row.project]);
|
||||
},
|
||||
clams: () => t('xpack.alert.clamsTitle', [row.project]),
|
||||
app: () => t('xpack.alert.cronJobAppTitle', [row.project]),
|
||||
website: () => t('xpack.alert.cronJobWebsiteTitle', [row.project]),
|
||||
database: () => t('xpack.alert.cronJobDatabaseTitle', [row.project]),
|
||||
directory: () => t('xpack.alert.cronJobDirectoryTitle', [row.project]),
|
||||
log: () => t('xpack.alert.cronJobLogTitle', [row.project]),
|
||||
snapshot: () => t('xpack.alert.cronJobSnapshotTitle', [row.project]),
|
||||
shell: () => t('xpack.alert.cronJobShellTitle', [row.project]),
|
||||
curl: () => t('xpack.alert.cronJobCurlTitle', [row.project]),
|
||||
cutWebsiteLog: () => t('xpack.alert.cronJobCutWebsiteLogTitle', [row.project]),
|
||||
clean: () => t('xpack.alert.cronJobCleanTitle', [row.project]),
|
||||
ntp: () => t('xpack.alert.cronJobNtpTitle', [row.project]),
|
||||
nodeException: () => t('xpack.alert.nodeException'),
|
||||
licenseException: () => t('xpack.alert.licenseException'),
|
||||
panelLogin: () => t('xpack.alert.panelLogin'),
|
||||
@@ -230,7 +228,6 @@ const formatMessage = (row: Alert.AlertInfo) => {
|
||||
panelIpLogin: () => t('xpack.alert.panelIpLogin'),
|
||||
sshIpLogin: () => t('xpack.alert.sshIpLogin'),
|
||||
};
|
||||
let type = row.type === 'cronJob' ? row.subType : row.type;
|
||||
return messageTemplates[type] ? messageTemplates[type]() : '';
|
||||
};
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
<template v-else-if="form.type === 'sms'">
|
||||
<el-form-item :label="$t('xpack.alert.displayName')" prop="smsDisplayName">
|
||||
<el-input v-model.trim="form.smsDisplayName" />
|
||||
<span class="input-help">{{ $t('xpack.alert.displayNameHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.phone')" prop="smsPhone">
|
||||
<el-input clearable v-model.trim="form.smsPhone" />
|
||||
|
||||
@@ -120,8 +120,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { toRef, onMounted, ref } from 'vue';
|
||||
import { searchBackup, deleteBackup, refreshToken } from '@/api/modules/backup';
|
||||
import Operate from '@/views/setting/backup-account/operate/index.vue';
|
||||
import DetailShow from '@/components/detail-show/index.vue';
|
||||
@@ -142,6 +143,7 @@ const paginationConfig = reactive({
|
||||
type: '',
|
||||
name: '',
|
||||
});
|
||||
useSearchPersistence('setting/backup-account/index', { search: toRef(paginationConfig, 'name') });
|
||||
const opRef = ref();
|
||||
const dialogRef = ref();
|
||||
const detailRef = ref();
|
||||
|
||||
@@ -223,6 +223,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import {
|
||||
searchSnapshotPage,
|
||||
snapshotDelete,
|
||||
@@ -258,6 +259,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('setting/snapshot/index', { search: searchName });
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { Command } from '@/api/interface/command';
|
||||
import GroupDialog from '@/components/group/index.vue';
|
||||
import OperateDialog from '@/views/terminal/command/operate/index.vue';
|
||||
@@ -123,6 +124,7 @@ const paginationConfig = reactive({
|
||||
order: 'ascending',
|
||||
});
|
||||
const info = ref();
|
||||
useSearchPersistence('terminal/command/index', { search: info });
|
||||
const group = ref<string>('');
|
||||
const dialogRef = ref();
|
||||
const opRef = ref();
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import GroupDialog from '@/components/agent-group/index.vue';
|
||||
import OperateDialog from '@/views/terminal/host/operate/index.vue';
|
||||
import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/terminal';
|
||||
@@ -99,6 +100,7 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const info = ref();
|
||||
useSearchPersistence('terminal/host/index', { search: info });
|
||||
const group = ref<string>('');
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
@@ -215,6 +216,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('toolbox/clam/index', { search: searchName });
|
||||
|
||||
const opRef = ref();
|
||||
const dialogRef = ref();
|
||||
|
||||
@@ -174,6 +174,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
@@ -200,6 +201,7 @@ const paginationConfig = reactive({
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchPersistence('toolbox/ftp/index', { search: searchName });
|
||||
|
||||
const form = reactive({
|
||||
isActive: true,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { GetPHPExtensions, InstallPHPExtension, UnInstallPHPExtension } from '@/api/modules/runtime';
|
||||
import i18n from '@/lang';
|
||||
@@ -48,6 +49,7 @@ const supportExtensions = ref([]);
|
||||
const loading = ref(false);
|
||||
const taskLogRef = ref();
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('website/runtime/php/extension-management/index', { search: searchName });
|
||||
const data = ref([]);
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -137,7 +139,6 @@ const searchByName = () => {
|
||||
};
|
||||
|
||||
const acceptParams = (req: Runtime.Runtime): void => {
|
||||
searchName.value = '';
|
||||
open.value = true;
|
||||
runtime.value = req;
|
||||
search();
|
||||
|
||||
@@ -221,7 +221,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { toRef, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteSSL, downloadFile, pushSSLToNode, searchSSL, updateSSL } from '@/api/modules/website';
|
||||
import DnsAccount from './dns-account/index.vue';
|
||||
import AcmeAccount from './acme-account/index.vue';
|
||||
@@ -286,6 +287,7 @@ const req = reactive({
|
||||
orderBy: 'updated_at',
|
||||
order: 'descending',
|
||||
});
|
||||
useSearchPersistence('website/ssl/index', { search: toRef(req, 'domain') });
|
||||
const pushForm = ref({
|
||||
id: 0,
|
||||
pushNode: true,
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { deleteTemplate, searchTemplates } from '@/api/modules/website';
|
||||
import TemplateOperate from '@/views/website/template/operate/index.vue';
|
||||
@@ -71,6 +72,7 @@ import { reactive, ref, onMounted } from 'vue';
|
||||
const loading = ref(false);
|
||||
const data = ref<Website.Template[]>([]);
|
||||
const searchName = ref('');
|
||||
useSearchPersistence('website/template/index', { search: searchName });
|
||||
const opRef = ref();
|
||||
const operateRef = ref();
|
||||
const outputCreateRef = ref();
|
||||
|
||||
@@ -344,6 +344,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import Backups from '@/components/backup/index.vue';
|
||||
import UploadDialog from '@/components/upload/index.vue';
|
||||
import DefaultServer from '@/views/website/website/default/index.vue';
|
||||
@@ -358,7 +359,7 @@ import BatchSetGroup from '@/views/website/website/batch-op/group.vue';
|
||||
import BatchSetHttps from '@/views/website/website/batch-op/https.vue';
|
||||
|
||||
import i18n from '@/lang';
|
||||
import { onMounted, reactive, ref, computed } from 'vue';
|
||||
import { toRef, onMounted, reactive, ref, computed } from 'vue';
|
||||
import { batchOperate, opWebsite, searchWebsites, updateWebsite } from '@/api/modules/website';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { App } from '@/api/interface/app';
|
||||
@@ -448,6 +449,7 @@ const pageState = usePageState(() => ({
|
||||
}));
|
||||
const paginationConfig = pageState.paginationConfig;
|
||||
const req = pageState.req;
|
||||
useSearchPersistence('website/website/index', { search: toRef(req, 'name') });
|
||||
const tableSort = pageState.tableSort;
|
||||
|
||||
const goRouter = async (key: string) => {
|
||||
|
||||
Reference in New Issue
Block a user