feat(triggers): add schedule trigger kind + internal scheduler
Build / build (push) Successful in 10m42s
Build / build (push) Successful in 10m42s
Fourth trigger kind alongside registry/git/manual. Recurring time-interval fires driven by a new internal/scheduler tick loop (default 30s, clamped to 5m). Goes through the same webhook.Handler.FanOutForTrigger seam as inbound HTTP webhooks, so per-binding concurrency, outcome accounting, and config-merge semantics are identical. Schema: triggers.last_fired_at TEXT column (additive ALTER for existing DBs). Scheduler persists last_fired_at BEFORE dispatch so a panicking Match cannot wedge a tight loop; failed deploys wait one full interval before retry — correct trade-off for a periodic refresh trigger. Frontend: TriggerKindForm + /triggers/new + /triggers/[id] gain the schedule kind (4-col card grid, preset chips Hourly/Daily/Weekly, custom interval input matched to Go time.ParseDuration syntax, optional pinned reference). /triggers/[id] surfaces "last fired" on schedule rows. EN+RU i18n in parity. Review fixes from go-reviewer / security-reviewer / typescript-reviewer: - Scheduler Start/Stop wrapped in sync.Once (no goroutine leak / double- cancel panic on shutdown re-entry). - shouldFire rejects sub-MinInterval as defense-in-depth against hand-inserted rows that bypassed Validate. - fire() asserts trigger Kind=="schedule" before dispatching. - Aligned isValidInterval regex across all three frontend sites; reject the unsupported "d" unit (Go time.ParseDuration doesn't accept it). - formatLastFired falls back to lastFiredNever on malformed timestamps rather than leaking raw bytes into the UI. - main.go scheduler closure logs per-fire deployed/errored counts.
This commit is contained in:
@@ -25,8 +25,14 @@ type triggerView struct {
|
||||
WebhookEnabled bool `json:"webhook_enabled"`
|
||||
WebhookRequireSignature bool `json:"webhook_require_signature"`
|
||||
BindingCount int `json:"binding_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
// LastFiredAt is the RFC3339 wall-clock the scheduler last
|
||||
// dispatched this trigger. Always present in the response shape;
|
||||
// empty for triggers that have never fired or are not scheduler-
|
||||
// driven. The detail page renders it as "last fired" on schedule
|
||||
// triggers; other kinds ignore it.
|
||||
LastFiredAt string `json:"last_fired_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (s *Server) toTriggerView(t store.Trigger) triggerView {
|
||||
@@ -42,6 +48,7 @@ func (s *Server) toTriggerView(t store.Trigger) triggerView {
|
||||
WebhookEnabled: t.WebhookSecret != "",
|
||||
WebhookRequireSignature: t.WebhookRequireSignature,
|
||||
BindingCount: count,
|
||||
LastFiredAt: t.LastFiredAt,
|
||||
CreatedAt: t.CreatedAt,
|
||||
UpdatedAt: t.UpdatedAt,
|
||||
}
|
||||
@@ -59,6 +66,7 @@ func toTriggerViewWithCount(row store.TriggerWithBindingCount) triggerView {
|
||||
WebhookEnabled: row.WebhookSecret != "",
|
||||
WebhookRequireSignature: row.WebhookRequireSignature,
|
||||
BindingCount: row.BindingCount,
|
||||
LastFiredAt: row.LastFiredAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user