mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-29 09:14:59 +03:00
fix(settings): accept five-field crontab for report and LDAP schedules
The panel scheduler runs WithSeconds(), whose parser rejects a standard five-field crontab such as "30 8 * * *". An admin who typed one into the Telegram/Discord notify time or the LDAP sync schedule saved it without error, AddJob then failed at startup, and the job silently never ran. Admin-typed schedules now go through internal/util/cronspec, which takes an optional seconds column plus the @descriptors and hands the scheduler a parsed cron.Schedule. CheckValid rejects an unparseable schedule on save, and a stored legacy value falls back to the job's default instead of dropping the job.
This commit is contained in:
+29
-34
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/mtproto"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/tuic"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/cronspec"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/sys"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/controller"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/job"
|
||||
@@ -385,13 +386,9 @@ func (s *Server) startTask(restartXray bool, loc *time.Location) {
|
||||
|
||||
// LDAP sync scheduling
|
||||
if ldapEnabled, _ := s.settingService.GetLdapEnable(); ldapEnabled {
|
||||
runtime, err := s.settingService.GetLdapSyncCron()
|
||||
if err != nil || runtime == "" {
|
||||
runtime = "@every 1m"
|
||||
}
|
||||
j := job.NewLdapSyncJob()
|
||||
runtime, _ := s.settingService.GetLdapSyncCron()
|
||||
// job has zero-value services with method receivers that read settings on demand
|
||||
_, _ = s.cron.AddJob(runtime, j)
|
||||
scheduleSettingJob(s.cron, "LDAP sync", runtime, "@every 1m", job.NewLdapSyncJob())
|
||||
}
|
||||
|
||||
// Telegram-bot–dependent jobs: periodic stats report + callback-hash cleanup.
|
||||
@@ -399,16 +396,9 @@ func (s *Server) startTask(restartXray bool, loc *time.Location) {
|
||||
if (err == nil) && isTgbotenabled {
|
||||
runtime, err := s.settingService.GetTgbotRuntime()
|
||||
if err != nil {
|
||||
logger.Warningf("Add NewStatsNotifyJob: failed to load runtime: %v; using default @daily", err)
|
||||
runtime = "@daily"
|
||||
} else if strings.TrimSpace(runtime) == "" {
|
||||
logger.Warning("Add NewStatsNotifyJob runtime is empty, using default @daily")
|
||||
runtime = "@daily"
|
||||
}
|
||||
logger.Infof("Tg notify enabled,run at %s", runtime)
|
||||
if _, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob()); err != nil {
|
||||
logger.Warningf("Add NewStatsNotifyJob: failed to schedule runtime %q: %v", runtime, err)
|
||||
logger.Warningf("Telegram notify: failed to load runtime: %v", err)
|
||||
}
|
||||
scheduleSettingJob(s.cron, "Telegram notify", runtime, "@daily", job.NewStatsNotifyJob())
|
||||
|
||||
// check for Telegram bot callback query hash storage reset
|
||||
_, _ = s.cron.AddJob(cadenceCheckHash, job.NewCheckHashStorageJob())
|
||||
@@ -419,18 +409,9 @@ func (s *Server) startTask(restartXray bool, loc *time.Location) {
|
||||
if (err == nil) && isDiscordEnabled {
|
||||
runtime, err := s.settingService.GetDiscordRunTime()
|
||||
if err != nil {
|
||||
logger.Warningf("Add NewDiscordNotifyJob: failed to load runtime: %v; using default @daily", err)
|
||||
runtime = "@daily"
|
||||
} else if strings.TrimSpace(runtime) == "" {
|
||||
logger.Warning("Add NewDiscordNotifyJob runtime is empty, using default @daily")
|
||||
runtime = "@daily"
|
||||
}
|
||||
logger.Infof("Discord notify enabled, run at %s", runtime)
|
||||
if entryID, err := s.cron.AddJob(runtime, job.NewDiscordNotifyJob(s.discordService)); err != nil {
|
||||
logger.Warningf("Add NewDiscordNotifyJob: failed to schedule runtime %q: %v", runtime, err)
|
||||
} else {
|
||||
s.discordNotifyEntryID = entryID
|
||||
logger.Warningf("Discord notify: failed to load runtime: %v", err)
|
||||
}
|
||||
s.discordNotifyEntryID = scheduleSettingJob(s.cron, "Discord notify", runtime, "@daily", job.NewDiscordNotifyJob(s.discordService))
|
||||
}
|
||||
|
||||
// CPU monitor publishes cpu.high events; register it whenever any notifier
|
||||
@@ -710,16 +691,10 @@ func (s *Server) start(restartXray bool, startTgBot bool) (err error) {
|
||||
return
|
||||
}
|
||||
runtime, err := s.settingService.GetDiscordRunTime()
|
||||
if err != nil || strings.TrimSpace(runtime) == "" {
|
||||
runtime = "@daily"
|
||||
}
|
||||
entryID, err := s.cron.AddJob(runtime, job.NewDiscordNotifyJob(s.discordService))
|
||||
if err != nil {
|
||||
logger.Warningf("Reload Discord notify: failed to schedule runtime %q: %v", runtime, err)
|
||||
} else {
|
||||
s.discordNotifyEntryID = entryID
|
||||
logger.Infof("Discord notify rescheduled, run at %s", runtime)
|
||||
logger.Warningf("Discord notify: failed to load runtime: %v", err)
|
||||
}
|
||||
s.discordNotifyEntryID = scheduleSettingJob(s.cron, "Discord notify", runtime, "@daily", job.NewDiscordNotifyJob(s.discordService))
|
||||
|
||||
if s.discordGateway != nil && !s.discordGateway.IsRunning() {
|
||||
_ = s.discordGateway.Start(s.ctx)
|
||||
@@ -859,3 +834,23 @@ func (s *Server) GetWSHub() any {
|
||||
func (s *Server) RestartXray() error {
|
||||
return s.xrayService.RestartXray(true)
|
||||
}
|
||||
|
||||
// scheduleSettingJob schedules j on an admin-typed spec, running it on fallback
|
||||
// when the spec is empty or a legacy value that no longer parses.
|
||||
func scheduleSettingJob(c *cron.Cron, name, spec, fallback string, j cron.Job) cron.EntryID {
|
||||
if strings.TrimSpace(spec) == "" {
|
||||
logger.Warningf("%s: schedule is empty, using default %s", name, fallback)
|
||||
spec = fallback
|
||||
}
|
||||
schedule, err := cronspec.Parse(spec)
|
||||
if err != nil {
|
||||
logger.Warningf("%s: %v; using default %s", name, err, fallback)
|
||||
spec = fallback
|
||||
if schedule, err = cronspec.Parse(fallback); err != nil {
|
||||
logger.Errorf("%s: default schedule %q: %v", name, fallback, err)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
logger.Infof("%s scheduled, run at %s", name, spec)
|
||||
return c.Schedule(schedule, j)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user