/// Module Grafana pour dashboards production use std::sync::Arc; use tokio::sync::RwLock; // Note: Use tracing::info! macro directly instead of importing use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use crate::error::AppError; /// Gestionnaire de dashboards Grafana #[derive(Debug)] pub struct GrafanaManager { dashboards: Arc>>, config: GrafanaConfig, } /// Configuration Grafana #[derive(Debug, Clone)] pub struct GrafanaConfig { pub grafana_url: String, pub api_key: Option, pub org_id: u32, pub datasource_uid: String, } /// Dashboard Grafana #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GrafanaDashboard { pub id: String, pub title: String, pub description: String, pub tags: Vec, pub panels: Vec, pub template_variables: Vec, pub time_range: TimeRange, pub refresh_interval: String, } /// Panel Grafana #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GrafanaPanel { pub id: u32, pub title: String, pub panel_type: PanelType, pub targets: Vec, pub position: PanelPosition, pub options: PanelOptions, pub thresholds: Option>, } /// Types de panels #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PanelType { Graph, Stat, Table, Heatmap, Gauge, BarGauge, Logs, NodeGraph, } /// Requête Prometheus #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PrometheusQuery { pub expr: String, pub legend: String, pub interval: Option, pub instant: bool, } /// Position du panel #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PanelPosition { pub x: u32, pub y: u32, pub width: u32, pub height: u32, } /// Options du panel #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PanelOptions { pub unit: Option, pub decimals: Option, pub min: Option, pub max: Option, pub color_mode: Option, } /// Seuil d'alerte #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Threshold { pub value: f64, pub color: String, pub op: String, // "gt", "lt", etc. } /// Variable de template #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TemplateVariable { pub name: String, pub label: String, pub var_type: String, pub query: String, pub multi: bool, } /// Plage de temps #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimeRange { pub from: String, pub to: String, } impl Default for GrafanaConfig { fn default() -> Self { Self { grafana_url: "http://localhost:3000".to_string(), api_key: None, org_id: 1, datasource_uid: "prometheus".to_string(), } } } impl GrafanaManager { /// Crée un nouveau gestionnaire Grafana pub async fn new() -> Result { tracing::info!("📊 Initialisation Grafana Manager"); let mut manager = Self { dashboards: Arc::new(RwLock::new(Vec::new())), config: GrafanaConfig::default(), }; // Créer dashboards par défaut manager.create_default_dashboards().await?; Ok(manager) } /// Crée les dashboards par défaut async fn create_default_dashboards(&mut self) -> Result<(), AppError> { tracing::info!("🎨 Création dashboards Grafana par défaut"); let dashboards = vec![ self.create_system_overview_dashboard(), self.create_application_metrics_dashboard(), self.create_business_metrics_dashboard(), self.create_alerts_dashboard(), self.create_performance_dashboard(), ]; let mut dashboard_store = self.dashboards.write().await; *dashboard_store = dashboards; tracing::info!("✅ {} dashboards créés", dashboard_store.len()); Ok(()) } /// Dashboard Vue d'ensemble système fn create_system_overview_dashboard(&self) -> GrafanaDashboard { GrafanaDashboard { id: "system-overview".to_string(), title: "🖥️ System Overview".to_string(), description: "Vue d'ensemble des métriques système".to_string(), tags: vec!["system".to_string(), "overview".to_string()], panels: vec![ // CPU Usage GrafanaPanel { id: 1, title: "CPU Usage".to_string(), panel_type: PanelType::Gauge, targets: vec![PrometheusQuery { expr: "system_cpu_usage_percent".to_string(), legend: "CPU %".to_string(), interval: None, instant: true, }], position: PanelPosition { x: 0, y: 0, width: 6, height: 4, }, options: PanelOptions { unit: Some("percent".to_string()), decimals: Some(1), min: Some(0.0), max: Some(100.0), color_mode: Some("thresholds".to_string()), }, thresholds: Some(vec![ Threshold { value: 70.0, color: "yellow".to_string(), op: "gt".to_string(), }, Threshold { value: 85.0, color: "red".to_string(), op: "gt".to_string(), }, ]), }, // Memory Usage GrafanaPanel { id: 2, title: "Memory Usage".to_string(), panel_type: PanelType::Gauge, targets: vec![PrometheusQuery { expr: "system_memory_usage_bytes / system_memory_total_bytes * 100" .to_string(), legend: "Memory %".to_string(), interval: None, instant: true, }], position: PanelPosition { x: 6, y: 0, width: 6, height: 4, }, options: PanelOptions { unit: Some("percent".to_string()), decimals: Some(1), min: Some(0.0), max: Some(100.0), color_mode: Some("thresholds".to_string()), }, thresholds: Some(vec![ Threshold { value: 75.0, color: "yellow".to_string(), op: "gt".to_string(), }, Threshold { value: 90.0, color: "red".to_string(), op: "gt".to_string(), }, ]), }, // Network I/O GrafanaPanel { id: 3, title: "Network I/O".to_string(), panel_type: PanelType::Graph, targets: vec![ PrometheusQuery { expr: "rate(system_network_rx_bytes_total[5m])".to_string(), legend: "RX".to_string(), interval: Some("30s".to_string()), instant: false, }, PrometheusQuery { expr: "rate(system_network_tx_bytes_total[5m])".to_string(), legend: "TX".to_string(), interval: Some("30s".to_string()), instant: false, }, ], position: PanelPosition { x: 0, y: 4, width: 12, height: 6, }, options: PanelOptions { unit: Some("bytes".to_string()), decimals: Some(2), min: None, max: None, color_mode: None, }, thresholds: None, }, ], template_variables: vec![], time_range: TimeRange { from: "now-1h".to_string(), to: "now".to_string(), }, refresh_interval: "30s".to_string(), } } /// Dashboard Métriques Application fn create_application_metrics_dashboard(&self) -> GrafanaDashboard { GrafanaDashboard { id: "application-metrics".to_string(), title: "🚀 Application Metrics".to_string(), description: "Métriques de performance de l'application".to_string(), tags: vec!["application".to_string(), "performance".to_string()], panels: vec![ // Active Connections GrafanaPanel { id: 1, title: "Active Connections".to_string(), panel_type: PanelType::Stat, targets: vec![PrometheusQuery { expr: "stream_connections_active".to_string(), legend: "Connections".to_string(), interval: None, instant: true, }], position: PanelPosition { x: 0, y: 0, width: 3, height: 3, }, options: PanelOptions { unit: Some("short".to_string()), decimals: Some(0), min: None, max: None, color_mode: Some("value".to_string()), }, thresholds: Some(vec![ Threshold { value: 50000.0, color: "green".to_string(), op: "gt".to_string(), }, Threshold { value: 80000.0, color: "yellow".to_string(), op: "gt".to_string(), }, Threshold { value: 95000.0, color: "red".to_string(), op: "gt".to_string(), }, ]), }, // Requests per Second GrafanaPanel { id: 2, title: "Requests/sec".to_string(), panel_type: PanelType::Stat, targets: vec![PrometheusQuery { expr: "rate(http_requests_total[1m])".to_string(), legend: "RPS".to_string(), interval: None, instant: true, }], position: PanelPosition { x: 3, y: 0, width: 3, height: 3, }, options: PanelOptions { unit: Some("reqps".to_string()), decimals: Some(1), min: None, max: None, color_mode: Some("value".to_string()), }, thresholds: None, }, // Response Time Percentiles GrafanaPanel { id: 3, title: "Response Time Percentiles".to_string(), panel_type: PanelType::Graph, targets: vec![ PrometheusQuery { expr: "histogram_quantile(0.50, http_request_duration_seconds)" .to_string(), legend: "P50".to_string(), interval: Some("30s".to_string()), instant: false, }, PrometheusQuery { expr: "histogram_quantile(0.95, http_request_duration_seconds)" .to_string(), legend: "P95".to_string(), interval: Some("30s".to_string()), instant: false, }, PrometheusQuery { expr: "histogram_quantile(0.99, http_request_duration_seconds)" .to_string(), legend: "P99".to_string(), interval: Some("30s".to_string()), instant: false, }, ], position: PanelPosition { x: 6, y: 0, width: 6, height: 6, }, options: PanelOptions { unit: Some("ms".to_string()), decimals: Some(2), min: Some(0.0), max: None, color_mode: None, }, thresholds: Some(vec![Threshold { value: 50.0, color: "red".to_string(), op: "gt".to_string(), }]), }, // Error Rate GrafanaPanel { id: 4, title: "Error Rate".to_string(), panel_type: PanelType::Graph, targets: vec![PrometheusQuery { expr: "rate(stream_errors_total[5m]) / rate(http_requests_total[5m]) * 100" .to_string(), legend: "Error %".to_string(), interval: Some("1m".to_string()), instant: false, }], position: PanelPosition { x: 0, y: 6, width: 6, height: 4, }, options: PanelOptions { unit: Some("percent".to_string()), decimals: Some(3), min: Some(0.0), max: None, color_mode: None, }, thresholds: Some(vec![ Threshold { value: 0.1, color: "yellow".to_string(), op: "gt".to_string(), }, Threshold { value: 1.0, color: "red".to_string(), op: "gt".to_string(), }, ]), }, ], template_variables: vec![], time_range: TimeRange { from: "now-6h".to_string(), to: "now".to_string(), }, refresh_interval: "15s".to_string(), } } /// Dashboard Métriques Business fn create_business_metrics_dashboard(&self) -> GrafanaDashboard { GrafanaDashboard { id: "business-metrics".to_string(), title: "💼 Business Metrics".to_string(), description: "Métriques business et engagement utilisateur".to_string(), tags: vec!["business".to_string(), "revenue".to_string()], panels: vec![ // Active Users GrafanaPanel { id: 1, title: "Active Users".to_string(), panel_type: PanelType::Stat, targets: vec![PrometheusQuery { expr: "business_active_users".to_string(), legend: "Users".to_string(), interval: None, instant: true, }], position: PanelPosition { x: 0, y: 0, width: 4, height: 4, }, options: PanelOptions { unit: Some("short".to_string()), decimals: Some(0), min: None, max: None, color_mode: Some("value".to_string()), }, thresholds: None, }, // Revenue GrafanaPanel { id: 2, title: "Total Revenue".to_string(), panel_type: PanelType::Stat, targets: vec![PrometheusQuery { expr: "business_revenue_total".to_string(), legend: "Revenue".to_string(), interval: None, instant: true, }], position: PanelPosition { x: 4, y: 0, width: 4, height: 4, }, options: PanelOptions { unit: Some("currencyUSD".to_string()), decimals: Some(2), min: None, max: None, color_mode: Some("value".to_string()), }, thresholds: None, }, // Premium Subscriptions GrafanaPanel { id: 3, title: "Premium Subscriptions".to_string(), panel_type: PanelType::Stat, targets: vec![PrometheusQuery { expr: "business_premium_subscriptions".to_string(), legend: "Premium".to_string(), interval: None, instant: true, }], position: PanelPosition { x: 8, y: 0, width: 4, height: 4, }, options: PanelOptions { unit: Some("short".to_string()), decimals: Some(0), min: None, max: None, color_mode: Some("value".to_string()), }, thresholds: None, }, ], template_variables: vec![], time_range: TimeRange { from: "now-24h".to_string(), to: "now".to_string(), }, refresh_interval: "1m".to_string(), } } /// Dashboard Alertes fn create_alerts_dashboard(&self) -> GrafanaDashboard { GrafanaDashboard { id: "alerts".to_string(), title: "🚨 Alerts & Incidents".to_string(), description: "Monitoring des alertes et incidents".to_string(), tags: vec!["alerts".to_string(), "incidents".to_string()], panels: vec![], template_variables: vec![], time_range: TimeRange { from: "now-24h".to_string(), to: "now".to_string(), }, refresh_interval: "1m".to_string(), } } /// Dashboard Performance fn create_performance_dashboard(&self) -> GrafanaDashboard { GrafanaDashboard { id: "performance".to_string(), title: "⚡ Performance Deep Dive".to_string(), description: "Analyse détaillée des performances".to_string(), tags: vec!["performance".to_string(), "deep-dive".to_string()], panels: vec![], template_variables: vec![], time_range: TimeRange { from: "now-1h".to_string(), to: "now".to_string(), }, refresh_interval: "10s".to_string(), } } /// Exporte un dashboard au format JSON Grafana pub async fn export_dashboard(&self, dashboard_id: &str) -> Result { let dashboards = self.dashboards.read().await; if let Some(dashboard) = dashboards.iter().find(|d| d.id == dashboard_id) { Ok(json!({ "dashboard": dashboard, "folderId": 0, "overwrite": true })) } else { Err(AppError::InvalidData { message: format!("Dashboard not found: {}", dashboard_id), }) } } /// Liste tous les dashboards disponibles pub async fn list_dashboards(&self) -> Vec { let dashboards = self.dashboards.read().await; dashboards.iter().map(|d| d.id.clone()).collect() } }