feat(monitoring): generate daily tasks and optimize heartbeat writes
Adds a MonitoringDailyTaskWorker that materializes and dispatches daily collect tasks from active subscription/desktop-client state, with per-run atomic metrics and a Prometheus collector. Dashboard and question-detail APIs now surface a platform_authorization_status so the UI can distinguish no-desktop-client, no-authorized-platforms, and authorized states; quota/access-state lookups resolve by request workspace instead of tenant. Hardens heartbeat: a desktop_client_primary_leases table gives per-(tenant, workspace) sticky primary selection, read-mostly lease resolution avoids a per-heartbeat transaction, and unchanged platform access reports skip snapshot upserts outside a 10-minute refresh window. Adds heartbeat primary/snapshot metrics exposed via token-protected tenant-api /api/internal/metrics endpoints plus Prometheus. Monitoring quota seed is removed from dev-seed since daily plans now derive from desktop bindings, and the projection uses the canonical six-platform catalog so platforms with zero samples still render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,9 @@ type RabbitMQConfig struct {
|
||||
}
|
||||
|
||||
type SchedulerConfig struct {
|
||||
HTTPHost string `mapstructure:"http_host"`
|
||||
HTTPPort int `mapstructure:"http_port"`
|
||||
InternalMetricsToken string `mapstructure:"internal_metrics_token"`
|
||||
DispatchInterval time.Duration `mapstructure:"dispatch_interval"`
|
||||
DispatchTimeout time.Duration `mapstructure:"dispatch_timeout"`
|
||||
DispatchBatchSize int `mapstructure:"dispatch_batch_size"`
|
||||
@@ -427,6 +430,12 @@ func applyEnvOverrides(cfg *Config) {
|
||||
if model, ok := lookupFirstNonEmptyEnv("SILICONFLOW_RERANKER_MODEL", "RETRIEVAL_RERANKER_MODEL"); ok {
|
||||
cfg.Retrieval.RerankerModel = model
|
||||
}
|
||||
if host, ok := lookupNonEmptyEnv("SCHEDULER_HTTP_HOST"); ok {
|
||||
cfg.Scheduler.HTTPHost = host
|
||||
}
|
||||
if token, ok := lookupFirstNonEmptyEnv("SCHEDULER_INTERNAL_METRICS_TOKEN", "SCHEDULER_METRICS_TOKEN"); ok {
|
||||
cfg.Scheduler.InternalMetricsToken = token
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeRabbitMQConfig(cfg *RabbitMQConfig) {
|
||||
@@ -569,6 +578,14 @@ func normalizeSchedulerConfig(cfg *SchedulerConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.HTTPPort == 0 {
|
||||
cfg.HTTPPort = 8081
|
||||
}
|
||||
cfg.HTTPHost = strings.TrimSpace(cfg.HTTPHost)
|
||||
if cfg.HTTPHost == "" {
|
||||
cfg.HTTPHost = "127.0.0.1"
|
||||
}
|
||||
cfg.InternalMetricsToken = strings.TrimSpace(cfg.InternalMetricsToken)
|
||||
if cfg.DispatchInterval <= 0 {
|
||||
cfg.DispatchInterval = 15 * time.Second
|
||||
}
|
||||
|
||||
@@ -114,6 +114,58 @@ brand_library: {}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesSchedulerHTTPDefaultsAndDisableSentinel(t *testing.T) {
|
||||
defaultConfigPath := writeTestConfig(t, `
|
||||
scheduler: {}
|
||||
`)
|
||||
|
||||
cfg, err := Load(defaultConfigPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() default error = %v", err)
|
||||
}
|
||||
if cfg.Scheduler.HTTPHost != "127.0.0.1" {
|
||||
t.Fatalf("expected default scheduler http host 127.0.0.1, got %q", cfg.Scheduler.HTTPHost)
|
||||
}
|
||||
if cfg.Scheduler.HTTPPort != 8081 {
|
||||
t.Fatalf("expected default scheduler http port 8081, got %d", cfg.Scheduler.HTTPPort)
|
||||
}
|
||||
|
||||
disabledConfigPath := writeTestConfig(t, `
|
||||
scheduler:
|
||||
http_port: -1
|
||||
`)
|
||||
|
||||
cfg, err = Load(disabledConfigPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() disabled error = %v", err)
|
||||
}
|
||||
if cfg.Scheduler.HTTPPort != -1 {
|
||||
t.Fatalf("expected scheduler http port -1 to disable server, got %d", cfg.Scheduler.HTTPPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPrefersEnvSchedulerMetricsSettings(t *testing.T) {
|
||||
t.Setenv("SCHEDULER_HTTP_HOST", "0.0.0.0")
|
||||
t.Setenv("SCHEDULER_INTERNAL_METRICS_TOKEN", "env-metrics-token")
|
||||
|
||||
configPath := writeTestConfig(t, `
|
||||
scheduler:
|
||||
http_host: 127.0.0.1
|
||||
internal_metrics_token: config-token
|
||||
`)
|
||||
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
if cfg.Scheduler.HTTPHost != "0.0.0.0" {
|
||||
t.Fatalf("expected env scheduler http host, got %q", cfg.Scheduler.HTTPHost)
|
||||
}
|
||||
if cfg.Scheduler.InternalMetricsToken != "env-metrics-token" {
|
||||
t.Fatalf("expected env scheduler metrics token, got %q", cfg.Scheduler.InternalMetricsToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesMembershipDefaults(t *testing.T) {
|
||||
configPath := writeTestConfig(t, `
|
||||
membership: {}
|
||||
|
||||
Reference in New Issue
Block a user