@@ -11,6 +11,11 @@ class DashboardConfig {
1111 required this .stem,
1212 required this .namespace,
1313 required this .routing,
14+ required this .alertWebhookUrls,
15+ required this .alertBacklogThreshold,
16+ required this .alertFailedTaskThreshold,
17+ required this .alertOfflineWorkerThreshold,
18+ required this .alertCooldown,
1419 });
1520
1621 /// Loads a dashboard config from the provided environment map.
@@ -29,12 +34,37 @@ class DashboardConfig {
2934 final routing = RoutingConfigLoader (
3035 StemRoutingContext .fromConfig (stemConfig),
3136 ).load ();
37+ final webhookUrls = _parseCsv (
38+ env['STEM_DASHBOARD_ALERT_WEBHOOK_URLS' ] ??
39+ env['STEM_DASHBOARD_WEBHOOK_URLS' ],
40+ );
41+ final backlogThreshold = _parsePositiveInt (
42+ env['STEM_DASHBOARD_ALERT_BACKLOG_THRESHOLD' ],
43+ fallback: 500 ,
44+ );
45+ final failedThreshold = _parsePositiveInt (
46+ env['STEM_DASHBOARD_ALERT_FAILED_TASK_THRESHOLD' ],
47+ fallback: 25 ,
48+ );
49+ final offlineThreshold = _parsePositiveInt (
50+ env['STEM_DASHBOARD_ALERT_OFFLINE_WORKER_THRESHOLD' ],
51+ fallback: 1 ,
52+ );
53+ final cooldown = _parseDuration (
54+ env['STEM_DASHBOARD_ALERT_COOLDOWN' ],
55+ fallback: const Duration (minutes: 5 ),
56+ );
3257
3358 return DashboardConfig ._(
3459 environment: Map .unmodifiable (env),
3560 stem: stemConfig,
3661 namespace: namespace,
3762 routing: routing,
63+ alertWebhookUrls: webhookUrls,
64+ alertBacklogThreshold: backlogThreshold,
65+ alertFailedTaskThreshold: failedThreshold,
66+ alertOfflineWorkerThreshold: offlineThreshold,
67+ alertCooldown: cooldown,
3868 );
3969 }
4070
@@ -54,6 +84,21 @@ class DashboardConfig {
5484 /// Routing registry resolved for this dashboard session.
5585 final RoutingRegistry routing;
5686
87+ /// Alert webhook URLs.
88+ final List <String > alertWebhookUrls;
89+
90+ /// Backlog alert threshold.
91+ final int alertBacklogThreshold;
92+
93+ /// Failed task alert threshold.
94+ final int alertFailedTaskThreshold;
95+
96+ /// Offline worker alert threshold.
97+ final int alertOfflineWorkerThreshold;
98+
99+ /// Alert cooldown.
100+ final Duration alertCooldown;
101+
57102 /// Broker URL resolved from the underlying Stem config.
58103 String get brokerUrl => stem.brokerUrl;
59104
@@ -63,3 +108,39 @@ class DashboardConfig {
63108 /// TLS configuration resolved from the underlying Stem config.
64109 TlsConfig get tls => stem.tls;
65110}
111+
112+ List <String > _parseCsv (String ? raw) {
113+ if (raw == null || raw.trim ().isEmpty) return const [];
114+ return raw
115+ .split (',' )
116+ .map ((value) => value.trim ())
117+ .where ((value) => value.isNotEmpty)
118+ .toList (growable: false );
119+ }
120+
121+ int _parsePositiveInt (String ? raw, {required int fallback}) {
122+ if (raw == null || raw.trim ().isEmpty) return fallback;
123+ final parsed = int .tryParse (raw.trim ());
124+ if (parsed == null || parsed <= 0 ) return fallback;
125+ return parsed;
126+ }
127+
128+ Duration _parseDuration (String ? raw, {required Duration fallback}) {
129+ if (raw == null || raw.trim ().isEmpty) return fallback;
130+ final value = raw.trim ();
131+ final match = RegExp (r'^(\d+)(ms|s|m|h)$' ).firstMatch (value);
132+ if (match == null ) return fallback;
133+ final amount = int .tryParse (match.group (1 ) ?? '' );
134+ if (amount == null || amount <= 0 ) return fallback;
135+ switch (match.group (2 )) {
136+ case 'ms' :
137+ return Duration (milliseconds: amount);
138+ case 's' :
139+ return Duration (seconds: amount);
140+ case 'm' :
141+ return Duration (minutes: amount);
142+ case 'h' :
143+ return Duration (hours: amount);
144+ }
145+ return fallback;
146+ }
0 commit comments