1use anyhow::{anyhow, Result};
14use chrono::{DateTime, Utc};
15use dashmap::DashMap;
16use scirs2_core::ndarray_ext::Array2;
17use serde::{Deserialize, Serialize};
18use std::collections::{HashMap, VecDeque};
19use std::sync::Arc;
20use std::time::Duration;
21use tokio::sync::RwLock;
22use tracing::info;
23
24#[derive(Clone)]
26pub struct AdvancedVisualization {
27 #[allow(dead_code)]
28 config: VisualizationConfig,
29 dashboards: Arc<DashMap<String, Dashboard>>,
30 metrics_collector: Arc<MetricsCollector>,
31 chart_generator: Arc<ChartGenerator>,
32 topology_visualizer: Arc<TopologyVisualizer>,
33 alert_visualizer: Arc<AlertVisualizer>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct VisualizationConfig {
39 pub enable_realtime: bool,
41 pub update_interval: Duration,
43 pub default_theme: ChartTheme,
45 pub max_data_points: usize,
47 pub enable_export: bool,
49 pub default_export_format: ExportFormat,
51}
52
53impl Default for VisualizationConfig {
54 fn default() -> Self {
55 Self {
56 enable_realtime: true,
57 update_interval: Duration::from_secs(5),
58 default_theme: ChartTheme::Dark,
59 max_data_points: 1000,
60 enable_export: true,
61 default_export_format: ExportFormat::SVG,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub enum ChartTheme {
69 Light,
70 Dark,
71 HighContrast,
72 Custom(CustomTheme),
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct CustomTheme {
78 pub background_color: String,
79 pub text_color: String,
80 pub primary_color: String,
81 pub secondary_color: String,
82 pub grid_color: String,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
87pub enum ExportFormat {
88 PNG,
89 SVG,
90 JSON,
91 CSV,
92 PDF,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct Dashboard {
98 pub id: String,
99 pub name: String,
100 pub description: String,
101 pub layout: DashboardLayout,
102 pub widgets: Vec<Widget>,
103 pub created_at: DateTime<Utc>,
104 pub last_updated: DateTime<Utc>,
105 pub auto_refresh: bool,
106 pub refresh_interval: Duration,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub enum DashboardLayout {
112 Grid { rows: usize, cols: usize },
113 Flexible,
114 SingleColumn,
115 TwoColumn,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct Widget {
121 pub id: String,
122 pub widget_type: WidgetType,
123 pub title: String,
124 pub position: WidgetPosition,
125 pub size: WidgetSize,
126 pub data_source: DataSource,
127 pub visualization_type: VisualizationType,
128 pub config: WidgetConfig,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct WidgetPosition {
134 pub row: usize,
135 pub col: usize,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct WidgetSize {
141 pub width: usize, pub height: usize, }
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub enum WidgetType {
148 Chart,
149 Table,
150 Map,
151 Topology,
152 Alert,
153 Metric,
154 Text,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub enum DataSource {
160 QueryMetrics,
161 FederationTopology,
162 SecurityAlerts,
163 ComplianceStatus,
164 PerformanceMetrics,
165 ServiceHealth,
166 Custom(String),
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub enum VisualizationType {
172 LineChart,
173 BarChart,
174 PieChart,
175 ScatterPlot,
176 Heatmap,
177 NetworkGraph,
178 Gauge,
179 Table,
180 TreeMap,
181 Sankey,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct WidgetConfig {
187 pub show_legend: bool,
188 pub show_grid: bool,
189 pub x_axis_label: Option<String>,
190 pub y_axis_label: Option<String>,
191 pub color_scheme: Vec<String>,
192 pub custom_options: HashMap<String, serde_json::Value>,
193}
194
195impl Default for WidgetConfig {
196 fn default() -> Self {
197 Self {
198 show_legend: true,
199 show_grid: true,
200 x_axis_label: None,
201 y_axis_label: None,
202 color_scheme: vec![
203 "#1f77b4".to_string(),
204 "#ff7f0e".to_string(),
205 "#2ca02c".to_string(),
206 "#d62728".to_string(),
207 "#9467bd".to_string(),
208 ],
209 custom_options: HashMap::new(),
210 }
211 }
212}
213
214pub struct MetricsCollector {
216 time_series: Arc<RwLock<HashMap<String, TimeSeries>>>,
217 #[allow(dead_code)]
218 aggregations: Arc<RwLock<HashMap<String, MetricAggregation>>>,
219}
220
221impl Default for MetricsCollector {
222 fn default() -> Self {
223 Self {
224 time_series: Arc::new(RwLock::new(HashMap::new())),
225 aggregations: Arc::new(RwLock::new(HashMap::new())),
226 }
227 }
228}
229
230impl MetricsCollector {
231 pub fn new() -> Self {
232 Self::default()
233 }
234
235 pub async fn record_metric(&self, metric_name: &str, value: f64) -> Result<()> {
237 let mut time_series = self.time_series.write().await;
238 let series = time_series
239 .entry(metric_name.to_string())
240 .or_insert_with(|| TimeSeries {
241 name: metric_name.to_string(),
242 data_points: VecDeque::new(),
243 unit: "".to_string(),
244 });
245
246 series.data_points.push_back(DataPoint {
247 timestamp: Utc::now(),
248 value,
249 });
250
251 if series.data_points.len() > 1000 {
253 series.data_points.pop_front();
254 }
255
256 Ok(())
257 }
258
259 pub async fn get_time_series(&self, metric_name: &str) -> Option<TimeSeries> {
261 let time_series = self.time_series.read().await;
262 time_series.get(metric_name).cloned()
263 }
264
265 pub async fn calculate_aggregation(
267 &self,
268 metric_name: &str,
269 aggregation_type: AggregationType,
270 window: Duration,
271 ) -> Result<f64> {
272 let time_series = self.time_series.read().await;
273 let series = time_series
274 .get(metric_name)
275 .ok_or_else(|| anyhow!("Metric not found: {}", metric_name))?;
276
277 let cutoff = Utc::now() - chrono::Duration::from_std(window)?;
278 let recent_values: Vec<f64> = series
279 .data_points
280 .iter()
281 .filter(|dp| dp.timestamp >= cutoff)
282 .map(|dp| dp.value)
283 .collect();
284
285 if recent_values.is_empty() {
286 return Ok(0.0);
287 }
288
289 let result = match aggregation_type {
290 AggregationType::Average => {
291 recent_values.iter().sum::<f64>() / recent_values.len() as f64
292 }
293 AggregationType::Sum => recent_values.iter().sum(),
294 AggregationType::Min => recent_values.iter().cloned().fold(f64::INFINITY, f64::min),
295 AggregationType::Max => recent_values
296 .iter()
297 .cloned()
298 .fold(f64::NEG_INFINITY, f64::max),
299 AggregationType::Count => recent_values.len() as f64,
300 AggregationType::Percentile(p) => {
301 let mut sorted = recent_values.clone();
302 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
303 let index = ((p / 100.0) * sorted.len() as f64) as usize;
304 sorted[index.min(sorted.len() - 1)]
305 }
306 };
307
308 Ok(result)
309 }
310}
311
312#[derive(Debug, Clone)]
314pub struct TimeSeries {
315 pub name: String,
316 pub data_points: VecDeque<DataPoint>,
317 pub unit: String,
318}
319
320#[derive(Debug, Clone)]
322pub struct DataPoint {
323 pub timestamp: DateTime<Utc>,
324 pub value: f64,
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
329pub enum AggregationType {
330 Average,
331 Sum,
332 Min,
333 Max,
334 Count,
335 Percentile(f64),
336}
337
338#[derive(Debug, Clone)]
340pub struct MetricAggregation {
341 pub metric_name: String,
342 pub aggregation_type: AggregationType,
343 pub value: f64,
344 pub calculated_at: DateTime<Utc>,
345}
346
347pub struct ChartGenerator {
349 #[allow(dead_code)]
350 theme: ChartTheme,
351}
352
353impl ChartGenerator {
354 pub fn new(theme: ChartTheme) -> Self {
355 Self { theme }
356 }
357
358 pub async fn generate_line_chart(
360 &self,
361 data: &TimeSeries,
362 config: &WidgetConfig,
363 ) -> Result<ChartData> {
364 let points: Vec<(f64, f64)> = data
365 .data_points
366 .iter()
367 .enumerate()
368 .map(|(i, dp)| (i as f64, dp.value))
369 .collect();
370
371 Ok(ChartData {
372 chart_type: VisualizationType::LineChart,
373 series: vec![ChartSeries {
374 name: data.name.clone(),
375 data: points,
376 color: config.color_scheme.first().cloned(),
377 }],
378 x_axis_label: config.x_axis_label.clone(),
379 y_axis_label: config.y_axis_label.clone(),
380 show_legend: config.show_legend,
381 show_grid: config.show_grid,
382 })
383 }
384
385 pub async fn generate_bar_chart(
387 &self,
388 _categories: Vec<String>,
389 values: Vec<f64>,
390 config: &WidgetConfig,
391 ) -> Result<ChartData> {
392 let points: Vec<(f64, f64)> = values
393 .iter()
394 .enumerate()
395 .map(|(i, &v)| (i as f64, v))
396 .collect();
397
398 Ok(ChartData {
399 chart_type: VisualizationType::BarChart,
400 series: vec![ChartSeries {
401 name: "Values".to_string(),
402 data: points,
403 color: config.color_scheme.first().cloned(),
404 }],
405 x_axis_label: config.x_axis_label.clone(),
406 y_axis_label: config.y_axis_label.clone(),
407 show_legend: config.show_legend,
408 show_grid: config.show_grid,
409 })
410 }
411
412 pub async fn generate_pie_chart(
414 &self,
415 labels: Vec<String>,
416 values: Vec<f64>,
417 config: &WidgetConfig,
418 ) -> Result<PieChartData> {
419 let total: f64 = values.iter().sum();
420 let slices: Vec<PieSlice> = labels
421 .into_iter()
422 .zip(values.iter())
423 .enumerate()
424 .map(|(i, (label, &value))| PieSlice {
425 label,
426 value,
427 percentage: (value / total) * 100.0,
428 color: config
429 .color_scheme
430 .get(i % config.color_scheme.len())
431 .cloned(),
432 })
433 .collect();
434
435 Ok(PieChartData {
436 slices,
437 show_legend: config.show_legend,
438 })
439 }
440
441 pub async fn generate_heatmap(
443 &self,
444 data: Array2<f64>,
445 row_labels: Vec<String>,
446 col_labels: Vec<String>,
447 _config: &WidgetConfig,
448 ) -> Result<HeatmapData> {
449 let (rows, cols) = data.dim();
450 let mut cells = Vec::new();
451
452 for i in 0..rows {
453 for j in 0..cols {
454 cells.push(HeatmapCell {
455 row: i,
456 col: j,
457 value: data[[i, j]],
458 });
459 }
460 }
461
462 Ok(HeatmapData {
463 cells,
464 row_labels,
465 col_labels,
466 color_scale: ColorScale::Viridis,
467 })
468 }
469}
470
471#[derive(Debug, Clone, Serialize)]
473pub struct ChartData {
474 pub chart_type: VisualizationType,
475 pub series: Vec<ChartSeries>,
476 pub x_axis_label: Option<String>,
477 pub y_axis_label: Option<String>,
478 pub show_legend: bool,
479 pub show_grid: bool,
480}
481
482#[derive(Debug, Clone, Serialize)]
484pub struct ChartSeries {
485 pub name: String,
486 pub data: Vec<(f64, f64)>,
487 pub color: Option<String>,
488}
489
490#[derive(Debug, Clone, Serialize)]
492pub struct PieChartData {
493 pub slices: Vec<PieSlice>,
494 pub show_legend: bool,
495}
496
497#[derive(Debug, Clone, Serialize)]
499pub struct PieSlice {
500 pub label: String,
501 pub value: f64,
502 pub percentage: f64,
503 pub color: Option<String>,
504}
505
506#[derive(Debug, Clone, Serialize)]
508pub struct HeatmapData {
509 pub cells: Vec<HeatmapCell>,
510 pub row_labels: Vec<String>,
511 pub col_labels: Vec<String>,
512 pub color_scale: ColorScale,
513}
514
515#[derive(Debug, Clone, Serialize)]
517pub struct HeatmapCell {
518 pub row: usize,
519 pub col: usize,
520 pub value: f64,
521}
522
523#[derive(Debug, Clone, Serialize)]
525pub enum ColorScale {
526 Viridis,
527 Plasma,
528 Inferno,
529 Magma,
530 Grayscale,
531}
532
533pub struct TopologyVisualizer {
535 nodes: Arc<RwLock<Vec<TopologyNode>>>,
536 edges: Arc<RwLock<Vec<TopologyEdge>>>,
537}
538
539impl Default for TopologyVisualizer {
540 fn default() -> Self {
541 Self {
542 nodes: Arc::new(RwLock::new(Vec::new())),
543 edges: Arc::new(RwLock::new(Vec::new())),
544 }
545 }
546}
547
548impl TopologyVisualizer {
549 pub fn new() -> Self {
550 Self::default()
551 }
552
553 pub async fn add_node(&self, node: TopologyNode) -> Result<()> {
555 let mut nodes = self.nodes.write().await;
556 nodes.push(node);
557 Ok(())
558 }
559
560 pub async fn add_edge(&self, edge: TopologyEdge) -> Result<()> {
562 let mut edges = self.edges.write().await;
563 edges.push(edge);
564 Ok(())
565 }
566
567 pub async fn generate_topology(&self) -> Result<TopologyVisualization> {
569 let nodes = self.nodes.read().await.clone();
570 let edges = self.edges.read().await.clone();
571
572 let positioned_nodes = self.calculate_layout(&nodes, &edges).await?;
574
575 Ok(TopologyVisualization {
576 nodes: positioned_nodes,
577 edges,
578 layout_algorithm: LayoutAlgorithm::ForceDirected,
579 })
580 }
581
582 async fn calculate_layout(
584 &self,
585 nodes: &[TopologyNode],
586 _edges: &[TopologyEdge],
587 ) -> Result<Vec<PositionedNode>> {
588 let n = nodes.len();
590 let radius = 200.0;
591 let center_x = 300.0;
592 let center_y = 300.0;
593
594 let positioned: Vec<PositionedNode> = nodes
595 .iter()
596 .enumerate()
597 .map(|(i, node)| {
598 let angle = (i as f64 / n as f64) * 2.0 * std::f64::consts::PI;
599 let x = center_x + radius * angle.cos();
600 let y = center_y + radius * angle.sin();
601
602 PositionedNode {
603 node: node.clone(),
604 x,
605 y,
606 }
607 })
608 .collect();
609
610 Ok(positioned)
611 }
612}
613
614#[derive(Debug, Clone, Serialize)]
616pub struct TopologyNode {
617 pub id: String,
618 pub label: String,
619 pub node_type: NodeType,
620 pub status: NodeStatus,
621 pub metadata: HashMap<String, String>,
622}
623
624#[derive(Debug, Clone, Serialize)]
626pub enum NodeType {
627 Service,
628 DataSource,
629 Gateway,
630 Cache,
631 LoadBalancer,
632}
633
634#[derive(Debug, Clone, Serialize)]
636pub enum NodeStatus {
637 Healthy,
638 Degraded,
639 Unhealthy,
640 Unknown,
641}
642
643#[derive(Debug, Clone, Serialize)]
645pub struct TopologyEdge {
646 pub source_id: String,
647 pub target_id: String,
648 pub edge_type: EdgeType,
649 pub weight: f64,
650 pub label: Option<String>,
651}
652
653#[derive(Debug, Clone, Serialize)]
655pub enum EdgeType {
656 Query,
657 DataFlow,
658 Federation,
659 Replication,
660}
661
662#[derive(Debug, Clone, Serialize)]
664pub struct PositionedNode {
665 pub node: TopologyNode,
666 pub x: f64,
667 pub y: f64,
668}
669
670#[derive(Debug, Clone, Serialize)]
672pub struct TopologyVisualization {
673 pub nodes: Vec<PositionedNode>,
674 pub edges: Vec<TopologyEdge>,
675 pub layout_algorithm: LayoutAlgorithm,
676}
677
678#[derive(Debug, Clone, Serialize)]
680pub enum LayoutAlgorithm {
681 ForceDirected,
682 Hierarchical,
683 Circular,
684 Grid,
685}
686
687pub struct AlertVisualizer {
689 alerts: Arc<RwLock<VecDeque<Alert>>>,
690}
691
692impl Default for AlertVisualizer {
693 fn default() -> Self {
694 Self {
695 alerts: Arc::new(RwLock::new(VecDeque::new())),
696 }
697 }
698}
699
700impl AlertVisualizer {
701 pub fn new() -> Self {
702 Self::default()
703 }
704
705 pub async fn add_alert(&self, alert: Alert) -> Result<()> {
707 let mut alerts = self.alerts.write().await;
708 alerts.push_back(alert);
709
710 if alerts.len() > 100 {
712 alerts.pop_front();
713 }
714
715 Ok(())
716 }
717
718 pub async fn get_alerts_by_severity(&self, severity: AlertSeverity) -> Vec<Alert> {
720 let alerts = self.alerts.read().await;
721 alerts
722 .iter()
723 .filter(|a| a.severity == severity)
724 .cloned()
725 .collect()
726 }
727
728 pub async fn generate_alert_timeline(&self) -> Result<AlertTimeline> {
730 let alerts = self.alerts.read().await.clone().into_iter().collect();
731
732 Ok(AlertTimeline {
733 alerts,
734 group_by: AlertGrouping::Severity,
735 })
736 }
737}
738
739#[derive(Debug, Clone, Serialize)]
741pub struct Alert {
742 pub id: String,
743 pub timestamp: DateTime<Utc>,
744 pub severity: AlertSeverity,
745 pub title: String,
746 pub description: String,
747 pub source: String,
748 pub acknowledged: bool,
749}
750
751#[derive(Debug, Clone, Serialize, PartialEq)]
753pub enum AlertSeverity {
754 Critical,
755 High,
756 Medium,
757 Low,
758 Info,
759}
760
761#[derive(Debug, Clone, Serialize)]
763pub struct AlertTimeline {
764 pub alerts: Vec<Alert>,
765 pub group_by: AlertGrouping,
766}
767
768#[derive(Debug, Clone, Serialize)]
770pub enum AlertGrouping {
771 Severity,
772 Source,
773 Time,
774}
775
776impl AdvancedVisualization {
777 pub fn new(config: VisualizationConfig) -> Self {
779 Self {
780 config: config.clone(),
781 dashboards: Arc::new(DashMap::new()),
782 metrics_collector: Arc::new(MetricsCollector::new()),
783 chart_generator: Arc::new(ChartGenerator::new(config.default_theme.clone())),
784 topology_visualizer: Arc::new(TopologyVisualizer::new()),
785 alert_visualizer: Arc::new(AlertVisualizer::new()),
786 }
787 }
788
789 pub async fn create_dashboard(&self, mut dashboard: Dashboard) -> Result<String> {
791 dashboard.created_at = Utc::now();
792 dashboard.last_updated = Utc::now();
793 let id = dashboard.id.clone();
794
795 self.dashboards.insert(id.clone(), dashboard);
796 info!("Created dashboard: {}", id);
797
798 Ok(id)
799 }
800
801 pub async fn get_dashboard(&self, dashboard_id: &str) -> Option<Dashboard> {
803 self.dashboards.get(dashboard_id).map(|d| d.clone())
804 }
805
806 pub async fn update_dashboard(
808 &self,
809 dashboard_id: &str,
810 mut dashboard: Dashboard,
811 ) -> Result<()> {
812 dashboard.last_updated = Utc::now();
813 self.dashboards.insert(dashboard_id.to_string(), dashboard);
814 Ok(())
815 }
816
817 pub async fn delete_dashboard(&self, dashboard_id: &str) -> Result<()> {
819 self.dashboards.remove(dashboard_id);
820 Ok(())
821 }
822
823 pub async fn record_metric(&self, metric_name: &str, value: f64) -> Result<()> {
825 self.metrics_collector
826 .record_metric(metric_name, value)
827 .await
828 }
829
830 pub async fn generate_widget_chart(&self, widget: &Widget) -> Result<ChartData> {
832 match widget.data_source {
833 DataSource::QueryMetrics => {
834 if let Some(series) = self
835 .metrics_collector
836 .get_time_series("query_latency")
837 .await
838 {
839 self.chart_generator
840 .generate_line_chart(&series, &widget.config)
841 .await
842 } else {
843 Err(anyhow!("No data available for query metrics"))
844 }
845 }
846 DataSource::PerformanceMetrics => {
847 if let Some(series) = self.metrics_collector.get_time_series("performance").await {
848 self.chart_generator
849 .generate_line_chart(&series, &widget.config)
850 .await
851 } else {
852 Err(anyhow!("No data available for performance metrics"))
853 }
854 }
855 _ => Err(anyhow!("Data source not yet implemented")),
856 }
857 }
858
859 pub async fn generate_topology_visualization(&self) -> Result<TopologyVisualization> {
861 self.topology_visualizer.generate_topology().await
862 }
863
864 pub async fn add_topology_node(&self, node: TopologyNode) -> Result<()> {
866 self.topology_visualizer.add_node(node).await
867 }
868
869 pub async fn add_topology_edge(&self, edge: TopologyEdge) -> Result<()> {
871 self.topology_visualizer.add_edge(edge).await
872 }
873
874 pub async fn add_alert(&self, alert: Alert) -> Result<()> {
876 self.alert_visualizer.add_alert(alert).await
877 }
878
879 pub async fn get_alert_timeline(&self) -> Result<AlertTimeline> {
881 self.alert_visualizer.generate_alert_timeline().await
882 }
883
884 pub async fn export_dashboard(
886 &self,
887 dashboard_id: &str,
888 format: ExportFormat,
889 ) -> Result<Vec<u8>> {
890 let dashboard = self
891 .get_dashboard(dashboard_id)
892 .await
893 .ok_or_else(|| anyhow!("Dashboard not found"))?;
894
895 match format {
896 ExportFormat::JSON => {
897 let json = serde_json::to_string_pretty(&dashboard)?;
898 Ok(json.into_bytes())
899 }
900 ExportFormat::SVG => {
901 let svg = format!(
903 r#"<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600">
904 <text x="10" y="20">{}</text>
905 </svg>"#,
906 dashboard.name
907 );
908 Ok(svg.into_bytes())
909 }
910 _ => Err(anyhow!("Export format not yet implemented")),
911 }
912 }
913
914 pub async fn create_default_performance_dashboard(&self) -> Result<String> {
916 let dashboard = Dashboard {
917 id: uuid::Uuid::new_v4().to_string(),
918 name: "Performance Dashboard".to_string(),
919 description: "Real-time performance metrics".to_string(),
920 layout: DashboardLayout::Grid { rows: 2, cols: 2 },
921 widgets: vec![
922 Widget {
923 id: uuid::Uuid::new_v4().to_string(),
924 widget_type: WidgetType::Chart,
925 title: "Query Latency".to_string(),
926 position: WidgetPosition { row: 0, col: 0 },
927 size: WidgetSize {
928 width: 1,
929 height: 1,
930 },
931 data_source: DataSource::QueryMetrics,
932 visualization_type: VisualizationType::LineChart,
933 config: WidgetConfig::default(),
934 },
935 Widget {
936 id: uuid::Uuid::new_v4().to_string(),
937 widget_type: WidgetType::Chart,
938 title: "Throughput".to_string(),
939 position: WidgetPosition { row: 0, col: 1 },
940 size: WidgetSize {
941 width: 1,
942 height: 1,
943 },
944 data_source: DataSource::PerformanceMetrics,
945 visualization_type: VisualizationType::BarChart,
946 config: WidgetConfig::default(),
947 },
948 Widget {
949 id: uuid::Uuid::new_v4().to_string(),
950 widget_type: WidgetType::Topology,
951 title: "Federation Topology".to_string(),
952 position: WidgetPosition { row: 1, col: 0 },
953 size: WidgetSize {
954 width: 2,
955 height: 1,
956 },
957 data_source: DataSource::FederationTopology,
958 visualization_type: VisualizationType::NetworkGraph,
959 config: WidgetConfig::default(),
960 },
961 ],
962 created_at: Utc::now(),
963 last_updated: Utc::now(),
964 auto_refresh: true,
965 refresh_interval: Duration::from_secs(5),
966 };
967
968 self.create_dashboard(dashboard).await
969 }
970
971 pub async fn create_security_dashboard(&self) -> Result<String> {
973 let dashboard = Dashboard {
974 id: uuid::Uuid::new_v4().to_string(),
975 name: "Security Monitoring".to_string(),
976 description: "Real-time security alerts and metrics".to_string(),
977 layout: DashboardLayout::TwoColumn,
978 widgets: vec![
979 Widget {
980 id: uuid::Uuid::new_v4().to_string(),
981 widget_type: WidgetType::Alert,
982 title: "Security Alerts".to_string(),
983 position: WidgetPosition { row: 0, col: 0 },
984 size: WidgetSize {
985 width: 1,
986 height: 1,
987 },
988 data_source: DataSource::SecurityAlerts,
989 visualization_type: VisualizationType::Table,
990 config: WidgetConfig::default(),
991 },
992 Widget {
993 id: uuid::Uuid::new_v4().to_string(),
994 widget_type: WidgetType::Chart,
995 title: "Threat Level".to_string(),
996 position: WidgetPosition { row: 0, col: 1 },
997 size: WidgetSize {
998 width: 1,
999 height: 1,
1000 },
1001 data_source: DataSource::SecurityAlerts,
1002 visualization_type: VisualizationType::Gauge,
1003 config: WidgetConfig::default(),
1004 },
1005 ],
1006 created_at: Utc::now(),
1007 last_updated: Utc::now(),
1008 auto_refresh: true,
1009 refresh_interval: Duration::from_secs(10),
1010 };
1011
1012 self.create_dashboard(dashboard).await
1013 }
1014}
1015
1016#[cfg(test)]
1017mod tests {
1018 use super::*;
1019
1020 #[tokio::test]
1021 async fn test_visualization_creation() {
1022 let config = VisualizationConfig::default();
1023 let viz = AdvancedVisualization::new(config);
1024
1025 let dashboard_id = viz.create_default_performance_dashboard().await.unwrap();
1026 assert!(!dashboard_id.is_empty());
1027
1028 let dashboard = viz.get_dashboard(&dashboard_id).await;
1029 assert!(dashboard.is_some());
1030 }
1031
1032 #[tokio::test]
1033 async fn test_metrics_collection() {
1034 let collector = MetricsCollector::new();
1035
1036 collector.record_metric("test_metric", 100.0).await.unwrap();
1037 collector.record_metric("test_metric", 200.0).await.unwrap();
1038
1039 let series = collector.get_time_series("test_metric").await;
1040 assert!(series.is_some());
1041 assert_eq!(series.unwrap().data_points.len(), 2);
1042 }
1043
1044 #[tokio::test]
1045 async fn test_aggregation() {
1046 let collector = MetricsCollector::new();
1047
1048 for i in 1..=10 {
1049 collector.record_metric("test", i as f64).await.unwrap();
1050 }
1051
1052 let avg = collector
1053 .calculate_aggregation("test", AggregationType::Average, Duration::from_secs(3600))
1054 .await
1055 .unwrap();
1056
1057 assert!((avg - 5.5).abs() < 0.1);
1058 }
1059
1060 #[tokio::test]
1061 async fn test_chart_generation() {
1062 let config = WidgetConfig::default();
1063 let generator = ChartGenerator::new(ChartTheme::Dark);
1064
1065 let time_series = TimeSeries {
1066 name: "test".to_string(),
1067 data_points: (1..=5)
1068 .map(|i| DataPoint {
1069 timestamp: Utc::now(),
1070 value: i as f64,
1071 })
1072 .collect(),
1073 unit: "ms".to_string(),
1074 };
1075
1076 let chart = generator
1077 .generate_line_chart(&time_series, &config)
1078 .await
1079 .unwrap();
1080
1081 assert_eq!(chart.series.len(), 1);
1082 assert_eq!(chart.series[0].data.len(), 5);
1083 }
1084
1085 #[tokio::test]
1086 async fn test_topology_visualization() {
1087 let visualizer = TopologyVisualizer::new();
1088
1089 visualizer
1090 .add_node(TopologyNode {
1091 id: "node1".to_string(),
1092 label: "Service 1".to_string(),
1093 node_type: NodeType::Service,
1094 status: NodeStatus::Healthy,
1095 metadata: HashMap::new(),
1096 })
1097 .await
1098 .unwrap();
1099
1100 visualizer
1101 .add_node(TopologyNode {
1102 id: "node2".to_string(),
1103 label: "Service 2".to_string(),
1104 node_type: NodeType::Service,
1105 status: NodeStatus::Healthy,
1106 metadata: HashMap::new(),
1107 })
1108 .await
1109 .unwrap();
1110
1111 visualizer
1112 .add_edge(TopologyEdge {
1113 source_id: "node1".to_string(),
1114 target_id: "node2".to_string(),
1115 edge_type: EdgeType::Query,
1116 weight: 1.0,
1117 label: None,
1118 })
1119 .await
1120 .unwrap();
1121
1122 let topology = visualizer.generate_topology().await.unwrap();
1123 assert_eq!(topology.nodes.len(), 2);
1124 assert_eq!(topology.edges.len(), 1);
1125 }
1126
1127 #[tokio::test]
1128 async fn test_alert_visualization() {
1129 let visualizer = AlertVisualizer::new();
1130
1131 visualizer
1132 .add_alert(Alert {
1133 id: uuid::Uuid::new_v4().to_string(),
1134 timestamp: Utc::now(),
1135 severity: AlertSeverity::Critical,
1136 title: "Test Alert".to_string(),
1137 description: "Test description".to_string(),
1138 source: "test".to_string(),
1139 acknowledged: false,
1140 })
1141 .await
1142 .unwrap();
1143
1144 let timeline = visualizer.generate_alert_timeline().await.unwrap();
1145 assert_eq!(timeline.alerts.len(), 1);
1146 }
1147
1148 #[tokio::test]
1149 async fn test_dashboard_export() {
1150 let config = VisualizationConfig::default();
1151 let viz = AdvancedVisualization::new(config);
1152
1153 let dashboard_id = viz.create_default_performance_dashboard().await.unwrap();
1154
1155 let json_export = viz
1156 .export_dashboard(&dashboard_id, ExportFormat::JSON)
1157 .await
1158 .unwrap();
1159 assert!(!json_export.is_empty());
1160
1161 let svg_export = viz
1162 .export_dashboard(&dashboard_id, ExportFormat::SVG)
1163 .await
1164 .unwrap();
1165 assert!(!svg_export.is_empty());
1166 }
1167}