oxirs_federate/
advanced_visualization.rs

1//! # Advanced Visualization & Dashboarding Module
2//!
3//! Comprehensive visualization and dashboarding capabilities:
4//! - Real-time metrics visualization
5//! - Query performance dashboards
6//! - Federation topology visualization
7//! - Security monitoring dashboards
8//! - Compliance dashboards
9//! - Customizable widget system
10//! - Alert visualization
11//! - Export capabilities (PNG, SVG, JSON)
12
13use 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/// Main visualization and dashboarding system
25#[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/// Visualization configuration
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct VisualizationConfig {
39    /// Enable real-time updates
40    pub enable_realtime: bool,
41    /// Update interval for real-time dashboards
42    pub update_interval: Duration,
43    /// Default chart theme
44    pub default_theme: ChartTheme,
45    /// Maximum data points to display
46    pub max_data_points: usize,
47    /// Enable export features
48    pub enable_export: bool,
49    /// Default export format
50    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/// Chart theme
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub enum ChartTheme {
69    Light,
70    Dark,
71    HighContrast,
72    Custom(CustomTheme),
73}
74
75/// Custom theme configuration
76#[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/// Export format
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
87pub enum ExportFormat {
88    PNG,
89    SVG,
90    JSON,
91    CSV,
92    PDF,
93}
94
95/// Dashboard
96#[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/// Dashboard layout
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub enum DashboardLayout {
112    Grid { rows: usize, cols: usize },
113    Flexible,
114    SingleColumn,
115    TwoColumn,
116}
117
118/// Widget
119#[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/// Widget position
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct WidgetPosition {
134    pub row: usize,
135    pub col: usize,
136}
137
138/// Widget size
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct WidgetSize {
141    pub width: usize,  // Grid units
142    pub height: usize, // Grid units
143}
144
145/// Widget type
146#[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/// Data source for widgets
158#[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/// Visualization type
170#[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/// Widget configuration
185#[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
214/// Metrics collector
215pub 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    /// Record metric value
236    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        // Keep only recent data points (last 1000)
252        if series.data_points.len() > 1000 {
253            series.data_points.pop_front();
254        }
255
256        Ok(())
257    }
258
259    /// Get time series data
260    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    /// Calculate aggregation
266    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/// Time series data
313#[derive(Debug, Clone)]
314pub struct TimeSeries {
315    pub name: String,
316    pub data_points: VecDeque<DataPoint>,
317    pub unit: String,
318}
319
320/// Data point
321#[derive(Debug, Clone)]
322pub struct DataPoint {
323    pub timestamp: DateTime<Utc>,
324    pub value: f64,
325}
326
327/// Aggregation type
328#[derive(Debug, Clone, Serialize, Deserialize)]
329pub enum AggregationType {
330    Average,
331    Sum,
332    Min,
333    Max,
334    Count,
335    Percentile(f64),
336}
337
338/// Metric aggregation
339#[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
347/// Chart generator
348pub 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    /// Generate line chart
359    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    /// Generate bar chart
386    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    /// Generate pie chart
413    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    /// Generate heatmap
442    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/// Chart data
472#[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/// Chart series
483#[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/// Pie chart data
491#[derive(Debug, Clone, Serialize)]
492pub struct PieChartData {
493    pub slices: Vec<PieSlice>,
494    pub show_legend: bool,
495}
496
497/// Pie slice
498#[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/// Heatmap data
507#[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/// Heatmap cell
516#[derive(Debug, Clone, Serialize)]
517pub struct HeatmapCell {
518    pub row: usize,
519    pub col: usize,
520    pub value: f64,
521}
522
523/// Color scale for heatmaps
524#[derive(Debug, Clone, Serialize)]
525pub enum ColorScale {
526    Viridis,
527    Plasma,
528    Inferno,
529    Magma,
530    Grayscale,
531}
532
533/// Topology visualizer
534pub 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    /// Add node to topology
554    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    /// Add edge to topology
561    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    /// Generate topology visualization
568    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        // Calculate node positions using force-directed layout (simplified)
573        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    /// Calculate node positions using force-directed layout
583    async fn calculate_layout(
584        &self,
585        nodes: &[TopologyNode],
586        _edges: &[TopologyEdge],
587    ) -> Result<Vec<PositionedNode>> {
588        // Simplified circular layout
589        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/// Topology node
615#[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/// Node type
625#[derive(Debug, Clone, Serialize)]
626pub enum NodeType {
627    Service,
628    DataSource,
629    Gateway,
630    Cache,
631    LoadBalancer,
632}
633
634/// Node status
635#[derive(Debug, Clone, Serialize)]
636pub enum NodeStatus {
637    Healthy,
638    Degraded,
639    Unhealthy,
640    Unknown,
641}
642
643/// Topology edge
644#[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/// Edge type
654#[derive(Debug, Clone, Serialize)]
655pub enum EdgeType {
656    Query,
657    DataFlow,
658    Federation,
659    Replication,
660}
661
662/// Positioned node
663#[derive(Debug, Clone, Serialize)]
664pub struct PositionedNode {
665    pub node: TopologyNode,
666    pub x: f64,
667    pub y: f64,
668}
669
670/// Topology visualization
671#[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/// Layout algorithm
679#[derive(Debug, Clone, Serialize)]
680pub enum LayoutAlgorithm {
681    ForceDirected,
682    Hierarchical,
683    Circular,
684    Grid,
685}
686
687/// Alert visualizer
688pub 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    /// Add alert
706    pub async fn add_alert(&self, alert: Alert) -> Result<()> {
707        let mut alerts = self.alerts.write().await;
708        alerts.push_back(alert);
709
710        // Keep only recent alerts (last 100)
711        if alerts.len() > 100 {
712            alerts.pop_front();
713        }
714
715        Ok(())
716    }
717
718    /// Get alerts by severity
719    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    /// Generate alert timeline
729    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/// Alert
740#[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/// Alert severity
752#[derive(Debug, Clone, Serialize, PartialEq)]
753pub enum AlertSeverity {
754    Critical,
755    High,
756    Medium,
757    Low,
758    Info,
759}
760
761/// Alert timeline
762#[derive(Debug, Clone, Serialize)]
763pub struct AlertTimeline {
764    pub alerts: Vec<Alert>,
765    pub group_by: AlertGrouping,
766}
767
768/// Alert grouping
769#[derive(Debug, Clone, Serialize)]
770pub enum AlertGrouping {
771    Severity,
772    Source,
773    Time,
774}
775
776impl AdvancedVisualization {
777    /// Create new visualization system
778    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    /// Create new dashboard
790    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    /// Get dashboard
802    pub async fn get_dashboard(&self, dashboard_id: &str) -> Option<Dashboard> {
803        self.dashboards.get(dashboard_id).map(|d| d.clone())
804    }
805
806    /// Update dashboard
807    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    /// Delete dashboard
818    pub async fn delete_dashboard(&self, dashboard_id: &str) -> Result<()> {
819        self.dashboards.remove(dashboard_id);
820        Ok(())
821    }
822
823    /// Record metric
824    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    /// Generate chart for widget
831    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    /// Generate topology visualization
860    pub async fn generate_topology_visualization(&self) -> Result<TopologyVisualization> {
861        self.topology_visualizer.generate_topology().await
862    }
863
864    /// Add topology node
865    pub async fn add_topology_node(&self, node: TopologyNode) -> Result<()> {
866        self.topology_visualizer.add_node(node).await
867    }
868
869    /// Add topology edge
870    pub async fn add_topology_edge(&self, edge: TopologyEdge) -> Result<()> {
871        self.topology_visualizer.add_edge(edge).await
872    }
873
874    /// Add alert
875    pub async fn add_alert(&self, alert: Alert) -> Result<()> {
876        self.alert_visualizer.add_alert(alert).await
877    }
878
879    /// Get alert timeline
880    pub async fn get_alert_timeline(&self) -> Result<AlertTimeline> {
881        self.alert_visualizer.generate_alert_timeline().await
882    }
883
884    /// Export dashboard
885    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                // Simplified SVG export
902                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    /// Create default performance dashboard
915    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    /// Create security monitoring dashboard
972    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}