1use std::{
7 collections::HashMap,
8 sync::Arc,
9 time::{Duration, SystemTime},
10};
11
12use tokio::sync::{RwLock, Mutex};
13use tracing::{debug, info, warn};
14use serde::{Serialize, Deserialize};
15
16use crate::monitoring::MonitoringError;
17
18pub struct DashboardManager {
20 config: DashboardConfig,
22 widget_registry: Arc<WidgetRegistry>,
24 data_providers: Arc<RwLock<HashMap<String, DataProviderImpl>>>,
26 template_engine: Arc<TemplateEngine>,
28 realtime_updater: Arc<RealtimeUpdater>,
30 state: Arc<RwLock<DashboardState>>,
32 tasks: Arc<Mutex<Vec<tokio::task::JoinHandle<()>>>>,
34}
35
36impl DashboardManager {
37 pub async fn new(config: DashboardConfig) -> Result<Self, MonitoringError> {
39 let widget_registry = Arc::new(WidgetRegistry::new());
40 let data_providers = Arc::new(RwLock::new(HashMap::new()));
41 let template_engine = Arc::new(TemplateEngine::new());
42 let realtime_updater = Arc::new(RealtimeUpdater::new());
43 let state = Arc::new(RwLock::new(DashboardState::new()));
44
45 let manager = Self {
46 config,
47 widget_registry,
48 data_providers,
49 template_engine,
50 realtime_updater,
51 state,
52 tasks: Arc::new(Mutex::new(Vec::new())),
53 };
54
55 manager.register_default_components().await?;
57
58 Ok(manager)
59 }
60
61 pub async fn start(&self) -> Result<(), MonitoringError> {
63 info!("Starting dashboard manager");
64
65 self.start_dashboard_update_task().await?;
67 self.start_data_refresh_task().await?;
68 self.start_health_monitoring_task().await?;
69
70 self.generate_all_dashboards().await?;
72
73 {
75 let mut state = self.state.write().await;
76 state.status = DashboardStatus::Running;
77 state.start_time = Some(SystemTime::now());
78 }
79
80 info!("Dashboard manager started");
81 Ok(())
82 }
83
84 pub async fn stop(&self) -> Result<(), MonitoringError> {
86 info!("Stopping dashboard manager");
87
88 {
90 let mut state = self.state.write().await;
91 state.status = DashboardStatus::Stopping;
92 }
93
94 let mut tasks = self.tasks.lock().await;
96 for task in tasks.drain(..) {
97 task.abort();
98 }
99
100 {
102 let mut state = self.state.write().await;
103 state.status = DashboardStatus::Stopped;
104 state.stop_time = Some(SystemTime::now());
105 }
106
107 info!("Dashboard manager stopped");
108 Ok(())
109 }
110
111 pub async fn get_status(&self) -> String {
113 let state = self.state.read().await;
114 format!("{:?}", state.status)
115 }
116
117 pub async fn generate_dashboard(&self, dashboard_id: &str) -> Result<Dashboard, MonitoringError> {
119 let config = self.config.dashboards.iter()
120 .find(|d| d.id == dashboard_id)
121 .ok_or_else(|| MonitoringError::ConfigError(format!("Dashboard {} not found", dashboard_id)))?;
122
123 self.build_dashboard(config).await
124 }
125
126 pub async fn get_dashboards(&self) -> Vec<DashboardInfo> {
128 self.config.dashboards.iter()
129 .map(|d| DashboardInfo {
130 id: d.id.clone(),
131 title: d.title.clone(),
132 description: d.description.clone(),
133 category: d.category.clone(),
134 last_updated: SystemTime::now(), })
136 .collect()
137 }
138
139 pub async fn update_dashboard_data(&self, data: DashboardData) -> Result<(), MonitoringError> {
141 self.realtime_updater.push_update(data).await;
142 Ok(())
143 }
144
145 async fn register_default_components(&self) -> Result<(), MonitoringError> {
147 self.widget_registry.register_widget("time_series", WidgetBuilderImpl::TimeSeries(TimeSeriesWidget::new())).await;
149 self.widget_registry.register_widget("gauge", WidgetBuilderImpl::Gauge(GaugeWidget::new())).await;
150 self.widget_registry.register_widget("heatmap", WidgetBuilderImpl::Heatmap(HeatmapWidget::new())).await;
151 self.widget_registry.register_widget("table", WidgetBuilderImpl::Table(TableWidget::new())).await;
152
153 let mut providers = self.data_providers.write().await;
155 providers.insert("nat_attempts".to_string(), DataProviderImpl::NatAttempts(NatAttemptsDataProvider::new()));
156 providers.insert("nat_results".to_string(), DataProviderImpl::NatResults(NatResultsDataProvider::new()));
157 providers.insert("health".to_string(), DataProviderImpl::Health(HealthDataProvider::new()));
158
159 info!("Registered default dashboard components");
160 Ok(())
161 }
162
163 async fn generate_all_dashboards(&self) -> Result<(), MonitoringError> {
165 for dashboard_config in &self.config.dashboards {
166 if let Err(e) = self.build_dashboard(dashboard_config).await {
167 warn!("Failed to generate dashboard {}: {}", dashboard_config.id, e);
168 }
169 }
170 Ok(())
171 }
172
173 async fn build_dashboard(&self, config: &DashboardDefinition) -> Result<Dashboard, MonitoringError> {
175 let mut widgets = Vec::new();
176
177 for widget_config in &config.widgets {
178 let widget = self.build_widget(widget_config).await?;
179 widgets.push(widget);
180 }
181
182 Ok(Dashboard {
183 id: config.id.clone(),
184 title: config.title.clone(),
185 description: config.description.clone(),
186 category: config.category.clone(),
187 layout: config.layout.clone(),
188 widgets,
189 generated_at: SystemTime::now(),
190 auto_refresh: config.auto_refresh,
191 })
192 }
193
194 async fn build_widget(&self, config: &WidgetDefinition) -> Result<Widget, MonitoringError> {
196 let widget_builder = self.widget_registry.get_widget(&config.widget_type).await
198 .ok_or_else(|| MonitoringError::ConfigError(format!("Unknown widget type: {}", config.widget_type)))?;
199
200 let data_providers = self.data_providers.read().await;
202 let data_provider = data_providers.get(&config.data_source)
203 .ok_or_else(|| MonitoringError::ConfigError(format!("Unknown data source: {}", config.data_source)))?;
204
205 let data = data_provider.fetch_data(&config.query).await?;
207
208 let widget_data = widget_builder.build(&data, &config.options).await?;
210
211 Ok(Widget {
212 id: config.id.clone(),
213 title: config.title.clone(),
214 widget_type: config.widget_type.clone(),
215 position: config.position.clone(),
216 size: config.size.clone(),
217 data: widget_data,
218 config: config.options.clone(),
219 })
220 }
221
222 async fn start_dashboard_update_task(&self) -> Result<(), MonitoringError> {
224 let config = self.config.clone();
225 let _widget_registry = self.widget_registry.clone();
226 let _data_providers = self.data_providers.clone();
227
228 let task = tokio::spawn(async move {
229 let mut interval = tokio::time::interval(config.update_interval);
230
231 loop {
232 interval.tick().await;
233
234 debug!("Updating dashboard data");
236 }
237 });
238
239 self.tasks.lock().await.push(task);
240 Ok(())
241 }
242
243 async fn start_data_refresh_task(&self) -> Result<(), MonitoringError> {
245 let data_providers = self.data_providers.clone();
246 let refresh_interval = self.config.data_refresh_interval;
247
248 let task = tokio::spawn(async move {
249 let mut interval = tokio::time::interval(refresh_interval);
250
251 loop {
252 interval.tick().await;
253
254 let providers = data_providers.read().await;
256 for provider in providers.values() {
257 if let Err(e) = provider.refresh().await {
258 warn!("Failed to refresh data provider: {}", e);
259 }
260 }
261 }
262 });
263
264 self.tasks.lock().await.push(task);
265 Ok(())
266 }
267
268 async fn start_health_monitoring_task(&self) -> Result<(), MonitoringError> {
270 let state = self.state.clone();
271
272 let task = tokio::spawn(async move {
273 let mut interval = tokio::time::interval(Duration::from_secs(30));
274
275 loop {
276 interval.tick().await;
277
278 let mut dashboard_state = state.write().await;
279 dashboard_state.last_health_check = Some(SystemTime::now());
280 dashboard_state.dashboards_generated += 1; }
282 });
283
284 self.tasks.lock().await.push(task);
285 Ok(())
286 }
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct DashboardConfig {
292 pub dashboards: Vec<DashboardDefinition>,
294 pub update_interval: Duration,
296 pub data_refresh_interval: Duration,
298 pub realtime_enabled: bool,
300 pub auth: DashboardAuthConfig,
302 pub export: DashboardExportConfig,
304}
305
306impl Default for DashboardConfig {
307 fn default() -> Self {
308 Self {
309 dashboards: vec![
310 DashboardDefinition {
311 id: "nat-overview".to_string(),
312 title: "NAT Traversal Overview".to_string(),
313 description: "High-level overview of NAT traversal performance".to_string(),
314 category: "Overview".to_string(),
315 layout: LayoutType::Grid { columns: 2, rows: 3 },
316 auto_refresh: true,
317 widgets: vec![
318 WidgetDefinition {
319 id: "success-rate".to_string(),
320 title: "Success Rate".to_string(),
321 widget_type: "gauge".to_string(),
322 data_source: "metrics".to_string(),
323 query: "success_rate_last_hour".to_string(),
324 position: Position { x: 0, y: 0 },
325 size: Size { width: 1, height: 1 },
326 options: HashMap::new(),
327 },
328 WidgetDefinition {
329 id: "attempts-timeline".to_string(),
330 title: "Attempts Over Time".to_string(),
331 widget_type: "time_series".to_string(),
332 data_source: "metrics".to_string(),
333 query: "attempts_timeline".to_string(),
334 position: Position { x: 1, y: 0 },
335 size: Size { width: 1, height: 2 },
336 options: HashMap::new(),
337 },
338 ],
339 }
340 ],
341 update_interval: Duration::from_secs(30),
342 data_refresh_interval: Duration::from_secs(60),
343 realtime_enabled: true,
344 auth: DashboardAuthConfig::default(),
345 export: DashboardExportConfig::default(),
346 }
347 }
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct DashboardDefinition {
353 pub id: String,
355 pub title: String,
357 pub description: String,
359 pub category: String,
361 pub layout: LayoutType,
363 pub auto_refresh: bool,
365 pub widgets: Vec<WidgetDefinition>,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct WidgetDefinition {
372 pub id: String,
374 pub title: String,
376 pub widget_type: String,
378 pub data_source: String,
380 pub query: String,
382 pub position: Position,
384 pub size: Size,
386 pub options: HashMap<String, serde_json::Value>,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
392pub enum LayoutType {
393 Grid { columns: u32, rows: u32 },
394 Flexible,
395 Fixed,
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct Position {
401 pub x: u32,
402 pub y: u32,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct Size {
408 pub width: u32,
409 pub height: u32,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct DashboardAuthConfig {
415 pub enabled: bool,
417 pub provider: String,
419 pub access_rules: Vec<AccessRule>,
421}
422
423impl Default for DashboardAuthConfig {
424 fn default() -> Self {
425 Self {
426 enabled: true,
427 provider: "oauth2".to_string(),
428 access_rules: vec![
429 AccessRule {
430 role: "admin".to_string(),
431 permissions: vec!["read".to_string(), "write".to_string()],
432 dashboards: vec!["*".to_string()],
433 }
434 ],
435 }
436 }
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize)]
441pub struct AccessRule {
442 pub role: String,
444 pub permissions: Vec<String>,
446 pub dashboards: Vec<String>,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize)]
452pub struct DashboardExportConfig {
453 pub enabled: bool,
455 pub formats: Vec<ExportFormat>,
457 pub storage_path: String,
459}
460
461impl Default for DashboardExportConfig {
462 fn default() -> Self {
463 Self {
464 enabled: true,
465 formats: vec![ExportFormat::PNG, ExportFormat::PDF, ExportFormat::JSON],
466 storage_path: "/tmp/dashboard-exports".to_string(),
467 }
468 }
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize)]
473pub enum ExportFormat {
474 PNG,
475 PDF,
476 SVG,
477 JSON,
478 CSV,
479}
480
481#[derive(Debug, Serialize, Deserialize)]
483pub struct Dashboard {
484 pub id: String,
486 pub title: String,
488 pub description: String,
490 pub category: String,
492 pub layout: LayoutType,
494 pub widgets: Vec<Widget>,
496 pub generated_at: SystemTime,
498 pub auto_refresh: bool,
500}
501
502#[derive(Debug, Serialize, Deserialize)]
504pub struct Widget {
505 pub id: String,
507 pub title: String,
509 pub widget_type: String,
511 pub position: Position,
513 pub size: Size,
515 pub data: WidgetData,
517 pub config: HashMap<String, serde_json::Value>,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct WidgetData {
524 pub data_points: Vec<DataPoint>,
526 pub metadata: HashMap<String, serde_json::Value>,
528 pub last_updated: SystemTime,
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
534pub struct DataPoint {
535 pub timestamp: SystemTime,
537 pub value: serde_json::Value,
539 pub labels: HashMap<String, String>,
541}
542
543#[derive(Debug, Serialize, Deserialize)]
545pub struct DashboardInfo {
546 pub id: String,
548 pub title: String,
550 pub description: String,
552 pub category: String,
554 pub last_updated: SystemTime,
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize)]
560pub struct DashboardData {
561 pub source: String,
563 pub data: serde_json::Value,
565 pub timestamp: SystemTime,
567}
568
569struct WidgetRegistry {
571 widgets: RwLock<HashMap<String, WidgetBuilderImpl>>,
572}
573
574impl WidgetRegistry {
575 fn new() -> Self {
577 Self {
578 widgets: RwLock::new(HashMap::new()),
579 }
580 }
581
582 async fn register_widget(&self, widget_type: &str, builder: WidgetBuilderImpl) {
584 let mut widgets = self.widgets.write().await;
585 widgets.insert(widget_type.to_string(), builder);
586 }
587
588 async fn get_widget(&self, widget_type: &str) -> Option<WidgetBuilderImpl> {
590 let widgets = self.widgets.read().await;
591 widgets.get(widget_type).cloned()
592 }
593}
594
595#[derive(Clone)]
597pub enum DataProviderImpl {
598 NatAttempts(NatAttemptsDataProvider),
599 NatResults(NatResultsDataProvider),
600 Health(HealthDataProvider),
601}
602
603impl DataProviderImpl {
604 pub async fn fetch_data(&self, query: &str) -> Result<WidgetData, MonitoringError> {
606 match self {
607 DataProviderImpl::NatAttempts(provider) => provider.fetch_data(query).await,
608 DataProviderImpl::NatResults(provider) => provider.fetch_data(query).await,
609 DataProviderImpl::Health(provider) => provider.fetch_data(query).await,
610 }
611 }
612
613 pub async fn refresh(&self) -> Result<(), MonitoringError> {
615 match self {
616 DataProviderImpl::NatAttempts(provider) => provider.refresh().await,
617 DataProviderImpl::NatResults(provider) => provider.refresh().await,
618 DataProviderImpl::Health(provider) => provider.refresh().await,
619 }
620 }
621}
622
623#[derive(Clone)]
625pub enum WidgetBuilderImpl {
626 TimeSeries(TimeSeriesWidget),
627 Gauge(GaugeWidget),
628 Heatmap(HeatmapWidget),
629 Table(TableWidget),
630}
631
632impl WidgetBuilderImpl {
633 pub async fn build(&self, data: &WidgetData, options: &HashMap<String, serde_json::Value>) -> Result<WidgetData, MonitoringError> {
635 match self {
636 WidgetBuilderImpl::TimeSeries(widget) => widget.build(data, options).await,
637 WidgetBuilderImpl::Gauge(widget) => widget.build(data, options).await,
638 WidgetBuilderImpl::Heatmap(widget) => widget.build(data, options).await,
639 WidgetBuilderImpl::Table(widget) => widget.build(data, options).await,
640 }
641 }
642}
643
644trait WidgetBuilder {
646 async fn build(&self, data: &WidgetData, options: &HashMap<String, serde_json::Value>) -> Result<WidgetData, MonitoringError>;
647}
648
649trait DataProvider {
651 async fn fetch_data(&self, query: &str) -> Result<WidgetData, MonitoringError>;
652 async fn refresh(&self) -> Result<(), MonitoringError>;
653}
654
655struct TemplateEngine;
657
658impl TemplateEngine {
659 fn new() -> Self {
661 Self
662 }
663
664 async fn render_dashboard(&self, dashboard: &Dashboard) -> Result<String, MonitoringError> {
666 Ok(format!("Dashboard: {}", dashboard.title))
668 }
669}
670
671struct RealtimeUpdater {
673 updates: Mutex<Vec<DashboardData>>,
674}
675
676impl RealtimeUpdater {
677 fn new() -> Self {
679 Self {
680 updates: Mutex::new(Vec::new()),
681 }
682 }
683
684 async fn push_update(&self, data: DashboardData) {
686 let mut updates = self.updates.lock().await;
687 updates.push(data);
688 }
689}
690
691#[derive(Debug)]
693struct DashboardState {
694 status: DashboardStatus,
695 start_time: Option<SystemTime>,
696 stop_time: Option<SystemTime>,
697 dashboards_generated: u64,
698 last_health_check: Option<SystemTime>,
699}
700
701impl DashboardState {
702 fn new() -> Self {
704 Self {
705 status: DashboardStatus::Stopped,
706 start_time: None,
707 stop_time: None,
708 dashboards_generated: 0,
709 last_health_check: None,
710 }
711 }
712}
713
714#[derive(Debug, Clone)]
716enum DashboardStatus {
717 Stopped,
718 Starting,
719 Running,
720 Stopping,
721 Error,
722}
723
724#[derive(Clone)]
728pub struct TimeSeriesWidget;
729
730impl TimeSeriesWidget {
731 fn new() -> Self {
733 Self
734 }
735}
736
737impl WidgetBuilder for TimeSeriesWidget {
738 async fn build(&self, data: &WidgetData, _options: &HashMap<String, serde_json::Value>) -> Result<WidgetData, MonitoringError> {
739 Ok(data.clone())
741 }
742}
743
744#[derive(Clone)]
746struct CounterWidget;
747
748impl CounterWidget {
749 fn new() -> Self {
751 Self
752 }
753}
754
755impl WidgetBuilder for CounterWidget {
756 async fn build(&self, data: &WidgetData, _options: &HashMap<String, serde_json::Value>) -> Result<WidgetData, MonitoringError> {
757 Ok(data.clone())
759 }
760}
761
762#[derive(Clone)]
764pub struct GaugeWidget;
765
766impl GaugeWidget {
767 fn new() -> Self {
769 Self
770 }
771}
772
773impl WidgetBuilder for GaugeWidget {
774 async fn build(&self, data: &WidgetData, _options: &HashMap<String, serde_json::Value>) -> Result<WidgetData, MonitoringError> {
775 Ok(data.clone())
777 }
778}
779
780#[derive(Clone)]
782pub struct HeatmapWidget;
783
784impl HeatmapWidget {
785 fn new() -> Self {
787 Self
788 }
789}
790
791impl WidgetBuilder for HeatmapWidget {
792 async fn build(&self, data: &WidgetData, _options: &HashMap<String, serde_json::Value>) -> Result<WidgetData, MonitoringError> {
793 Ok(data.clone())
795 }
796}
797
798#[derive(Clone)]
800pub struct TableWidget;
801
802impl TableWidget {
803 fn new() -> Self {
805 Self
806 }
807}
808
809impl WidgetBuilder for TableWidget {
810 async fn build(&self, data: &WidgetData, _options: &HashMap<String, serde_json::Value>) -> Result<WidgetData, MonitoringError> {
811 Ok(data.clone())
813 }
814}
815
816#[derive(Clone)]
820pub struct NatAttemptsDataProvider;
821
822impl NatAttemptsDataProvider {
823 fn new() -> Self {
825 Self
826 }
827}
828
829impl DataProvider for NatAttemptsDataProvider {
830 async fn fetch_data(&self, _query: &str) -> Result<WidgetData, MonitoringError> {
831 Ok(WidgetData {
833 data_points: Vec::new(),
834 metadata: HashMap::new(),
835 last_updated: SystemTime::now(),
836 })
837 }
838
839 async fn refresh(&self) -> Result<(), MonitoringError> {
840 debug!("Refreshing NAT attempts data provider");
841 Ok(())
842 }
843}
844
845#[derive(Clone)]
847pub struct NatResultsDataProvider;
848
849impl NatResultsDataProvider {
850 fn new() -> Self {
852 Self
853 }
854}
855
856impl DataProvider for NatResultsDataProvider {
857 async fn fetch_data(&self, _query: &str) -> Result<WidgetData, MonitoringError> {
858 Ok(WidgetData {
860 data_points: Vec::new(),
861 metadata: HashMap::new(),
862 last_updated: SystemTime::now(),
863 })
864 }
865
866 async fn refresh(&self) -> Result<(), MonitoringError> {
867 debug!("Refreshing NAT results data provider");
868 Ok(())
869 }
870}
871
872#[derive(Clone)]
874pub struct HealthDataProvider;
875
876impl HealthDataProvider {
877 fn new() -> Self {
879 Self
880 }
881}
882
883impl DataProvider for HealthDataProvider {
884 async fn fetch_data(&self, _query: &str) -> Result<WidgetData, MonitoringError> {
885 Ok(WidgetData {
887 data_points: Vec::new(),
888 metadata: HashMap::new(),
889 last_updated: SystemTime::now(),
890 })
891 }
892
893 async fn refresh(&self) -> Result<(), MonitoringError> {
894 debug!("Refreshing health data provider");
895 Ok(())
896 }
897}
898
899#[cfg(test)]
900mod tests {
901 use super::*;
902
903 #[tokio::test]
904 async fn test_dashboard_manager_creation() {
905 let config = DashboardConfig::default();
906 let manager = DashboardManager::new(config).await.unwrap();
907
908 let status = manager.get_status().await;
909 assert!(status.contains("Stopped"));
910 }
911
912 #[tokio::test]
913 async fn test_dashboard_generation() {
914 let config = DashboardConfig::default();
915 let manager = DashboardManager::new(config).await.unwrap();
916
917 let dashboards = manager.get_dashboards().await;
918 assert!(!dashboards.is_empty());
919 assert_eq!(dashboards[0].id, "nat-overview");
920 }
921
922 #[test]
923 fn test_widget_data_serialization() {
924 let widget_data = WidgetData {
925 data_points: vec![
926 DataPoint {
927 timestamp: SystemTime::now(),
928 value: serde_json::json!(42),
929 labels: HashMap::new(),
930 }
931 ],
932 metadata: HashMap::new(),
933 last_updated: SystemTime::now(),
934 };
935
936 let json = serde_json::to_string(&widget_data).unwrap();
937 let _deserialized: WidgetData = serde_json::from_str(&json).unwrap();
938 }
939}