use leptos::prelude::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use chrono::{DateTime, Utc}; use uuid::Uuid; use gloo_storage::{LocalStorage, Storage}; use crate::components::grid::{GridLayout, GridPosition, GridSize}; use crate::components::charts::ChartConfig; use crate::components::widgets::{ActivityFeedConfig, SystemHealthConfig, MetricConfig}; use crate::types::UserRole; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DashboardConfig { pub id: String, pub name: String, pub description: Option, pub user_id: Option, pub user_role: UserRole, pub layout: GridLayout, pub widgets: HashMap, pub theme_config: ThemeSettings, pub filter_presets: Vec, pub export_settings: ExportSettings, pub auto_refresh_settings: AutoRefreshSettings, pub notification_settings: NotificationSettings, pub created_at: DateTime, pub updated_at: DateTime, pub is_default: bool, pub is_template: bool, pub template_category: Option, pub version: u32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WidgetConfig { pub id: String, pub widget_type: WidgetType, pub position: GridPosition, pub size: GridSize, pub title: String, pub data_source: String, pub refresh_interval: u32, pub visible: bool, pub config_data: WidgetConfigData, pub permissions: WidgetPermissions, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type")] pub enum WidgetType { Chart, Metric, SystemHealth, ActivityFeed, Table, Map, Custom { component_name: String }, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "widget_type")] pub enum WidgetConfigData { Chart(ChartConfig), Metric(MetricConfig), SystemHealth(SystemHealthConfig), ActivityFeed(ActivityFeedConfig), Table(TableConfig), Map(MapConfig), Custom(serde_json::Value), } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WidgetPermissions { pub can_edit: bool, pub can_move: bool, pub can_resize: bool, pub can_remove: bool, pub required_roles: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ThemeSettings { pub theme_name: String, pub custom_colors: HashMap, pub font_size: FontSize, pub density: UiDensity, pub animation_enabled: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FilterPreset { pub id: String, pub name: String, pub filters: HashMap, pub date_range: Option, pub is_default: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DateRangePreset { pub name: String, pub range_type: DateRangeType, pub custom_start: Option>, pub custom_end: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DateRangeType { Last15Minutes, LastHour, Last6Hours, Last24Hours, Last7Days, Last30Days, ThisMonth, LastMonth, Custom, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExportSettings { pub default_format: ExportFormat, pub include_metadata: bool, pub compress_data: bool, pub auto_timestamp: bool, pub custom_filename_template: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AutoRefreshSettings { pub enabled: bool, pub global_interval: u32, // seconds pub widget_overrides: HashMap, pub pause_when_hidden: bool, pub pause_on_error: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NotificationSettings { pub enabled: bool, pub position: NotificationPosition, pub auto_dismiss: bool, pub dismiss_timeout: u32, pub max_visible: u32, pub sound_enabled: bool, pub level_filters: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum FontSize { Small, Medium, Large, ExtraLarge, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum UiDensity { Compact, Normal, Comfortable, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ExportFormat { Png, Pdf, Csv, Excel, Json, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum NotificationPosition { TopRight, TopLeft, BottomRight, BottomLeft, TopCenter, BottomCenter, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum NotificationLevel { Info, Success, Warning, Error, Critical, } // Additional widget config types #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableConfig { pub columns: Vec, pub row_height: u32, pub pagination: PaginationConfig, pub sorting: SortingConfig, pub filtering: TableFilterConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableColumn { pub id: String, pub label: String, pub field: String, pub width: Option, pub sortable: bool, pub filterable: bool, pub formatter: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PaginationConfig { pub enabled: bool, pub page_size: u32, pub show_size_selector: bool, pub available_sizes: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SortingConfig { pub enabled: bool, pub multi_column: bool, pub default_sort: Option<(String, SortDirection)>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableFilterConfig { pub enabled: bool, pub show_search: bool, pub column_filters: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SortDirection { Asc, Desc, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MapConfig { pub center: (f64, f64), // lat, lng pub zoom: u32, pub map_style: String, pub show_controls: bool, pub markers: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MapMarker { pub id: String, pub position: (f64, f64), pub label: String, pub color: String, pub icon: Option, } // Dashboard configuration service #[derive(Debug, Clone)] pub struct DashboardConfigService { current_config: RwSignal, saved_configs: RwSignal>, templates: RwSignal>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DashboardTemplate { pub id: String, pub name: String, pub description: String, pub category: String, pub icon: String, pub config: DashboardConfig, pub preview_image: Option, pub tags: Vec, pub required_roles: Vec, pub created_by: String, pub is_official: bool, } impl DashboardConfigService { pub fn new() -> Self { // Load saved configurations from localStorage let saved_configs = LocalStorage::get::>("dashboard_configs") .unwrap_or_default(); // Load current configuration let current_config = if let Some(config) = saved_configs.first() { config.clone() } else { DashboardConfig::default_for_role(UserRole::Admin) }; // Load templates let templates = Self::load_default_templates(); Self { current_config: RwSignal::new(current_config), saved_configs: RwSignal::new(saved_configs), templates: RwSignal::new(templates), } } pub fn get_current_config(&self) -> ReadSignal { self.current_config.into() } pub fn update_config(&self, f: F) where F: FnOnce(&mut DashboardConfig) + 'static, { self.current_config.update(|config| { f(config); config.updated_at = Utc::now(); config.version += 1; }); // Auto-save to localStorage self.save_current_config(); } pub fn save_current_config(&self) { let config = self.current_config.get(); self.save_config(config); } pub fn save_config(&self, mut config: DashboardConfig) { config.updated_at = Utc::now(); self.saved_configs.update(|configs| { // Update existing or add new if let Some(existing) = configs.iter_mut().find(|c| c.id == config.id) { *existing = config.clone(); } else { configs.push(config.clone()); } // Limit number of saved configs configs.sort_by(|a, b| b.updated_at.cmp(&a.updated_at)); configs.truncate(10); // Save to localStorage let _ = LocalStorage::set("dashboard_configs", configs); }); } pub fn load_config(&self, config_id: &str) -> Option { self.saved_configs.get().into_iter().find(|c| c.id == config_id) } pub fn set_current_config(&self, config: DashboardConfig) { self.current_config.set(config); self.save_current_config(); } pub fn delete_config(&self, config_id: &str) { self.saved_configs.update(|configs| { configs.retain(|c| c.id != config_id); let _ = LocalStorage::set("dashboard_configs", configs); }); } pub fn get_saved_configs(&self) -> ReadSignal> { self.saved_configs.into() } pub fn create_config_from_template(&self, template: &DashboardTemplate) -> DashboardConfig { let mut config = template.config.clone(); config.id = Uuid::new_v4().to_string(); config.name = format!("{} - Copy", template.name); config.created_at = Utc::now(); config.updated_at = Utc::now(); config.version = 1; config.is_template = false; config.is_default = false; config } pub fn get_templates(&self) -> ReadSignal> { self.templates.into() } pub fn get_templates_for_role(&self, role: UserRole) -> Vec { self.templates.get() .into_iter() .filter(|template| { template.required_roles.is_empty() || template.required_roles.contains(&role) }) .collect() } // Widget management pub fn add_widget(&self, widget: WidgetConfig) { self.update_config(|config| { config.widgets.insert(widget.id.clone(), widget); }); } pub fn remove_widget(&self, widget_id: &str) { self.update_config(|config| { config.widgets.remove(widget_id); }); } pub fn update_widget(&self, widget_id: &str, f: F) where F: FnOnce(&mut WidgetConfig) + 'static, { self.update_config(|config| { if let Some(widget) = config.widgets.get_mut(widget_id) { f(widget); } }); } pub fn move_widget(&self, widget_id: &str, position: GridPosition) { self.update_widget(widget_id, |widget| { widget.position = position; }); } pub fn resize_widget(&self, widget_id: &str, size: GridSize) { self.update_widget(widget_id, |widget| { widget.size = size; }); } // Layout management pub fn update_layout(&self, layout: GridLayout) { self.update_config(|config| { config.layout = layout; }); } // Theme management pub fn update_theme(&self, theme_settings: ThemeSettings) { self.update_config(|config| { config.theme_config = theme_settings; }); } // Filter presets pub fn add_filter_preset(&self, preset: FilterPreset) { self.update_config(|config| { config.filter_presets.push(preset); }); } pub fn remove_filter_preset(&self, preset_id: &str) { self.update_config(|config| { config.filter_presets.retain(|p| p.id != preset_id); }); } pub fn apply_filter_preset(&self, preset_id: &str) -> Option { let config = self.current_config.get(); config.filter_presets.into_iter().find(|p| p.id == preset_id) } // Export functionality pub fn export_config(&self) -> String { let config = self.current_config.get(); serde_json::to_string_pretty(&config).unwrap_or_default() } pub fn import_config(&self, config_json: &str) -> Result<(), String> { let config: DashboardConfig = serde_json::from_str(config_json) .map_err(|e| format!("Invalid configuration format: {}", e))?; self.set_current_config(config); Ok(()) } // Reset functionality pub fn reset_to_default(&self, role: UserRole) { let default_config = DashboardConfig::default_for_role(role); self.set_current_config(default_config); } fn load_default_templates() -> Vec { vec![ DashboardTemplate { id: "admin_dashboard".to_string(), name: "Administrator Dashboard".to_string(), description: "Complete overview with system metrics, security monitoring, and activity feeds".to_string(), category: "Admin".to_string(), icon: "bi-shield-check".to_string(), config: DashboardConfig::admin_template(), preview_image: Some("admin_dashboard_preview.png".to_string()), tags: vec!["admin".to_string(), "complete".to_string(), "monitoring".to_string()], required_roles: vec![UserRole::Admin], created_by: "System".to_string(), is_official: true, }, DashboardTemplate { id: "security_dashboard".to_string(), name: "Security Dashboard".to_string(), description: "Focus on security metrics, threat detection, and compliance monitoring".to_string(), category: "Security".to_string(), icon: "bi-shield-exclamation".to_string(), config: DashboardConfig::security_template(), preview_image: Some("security_dashboard_preview.png".to_string()), tags: vec!["security".to_string(), "compliance".to_string(), "threats".to_string()], required_roles: vec![UserRole::Admin, UserRole::User], created_by: "System".to_string(), is_official: true, }, DashboardTemplate { id: "operational_dashboard".to_string(), name: "Operations Dashboard".to_string(), description: "System health, performance metrics, and operational insights".to_string(), category: "Operations".to_string(), icon: "bi-gear".to_string(), config: DashboardConfig::operations_template(), preview_image: Some("operations_dashboard_preview.png".to_string()), tags: vec!["operations".to_string(), "performance".to_string(), "health".to_string()], required_roles: vec![UserRole::Admin, UserRole::User], created_by: "System".to_string(), is_official: true, }, DashboardTemplate { id: "minimal_dashboard".to_string(), name: "Minimal Dashboard".to_string(), description: "Simple layout with essential metrics only".to_string(), category: "Basic".to_string(), icon: "bi-layout-text-sidebar".to_string(), config: DashboardConfig::minimal_template(), preview_image: Some("minimal_dashboard_preview.png".to_string()), tags: vec!["minimal".to_string(), "simple".to_string(), "basic".to_string()], required_roles: vec![], // Available to all roles created_by: "System".to_string(), is_official: true, }, ] } } // Implementation of default configurations for different contexts impl DashboardConfig { pub fn default_for_role(role: UserRole) -> Self { match role { UserRole::Admin => Self::admin_template(), UserRole::User => Self::user_template(), UserRole::ReadOnly => Self::readonly_template(), } } pub fn admin_template() -> Self { let mut config = Self::base_config("Admin Dashboard"); // Add comprehensive widget set for admin config.widgets.insert("system_health".to_string(), WidgetConfig { id: "system_health".to_string(), widget_type: WidgetType::SystemHealth, position: GridPosition { x: 0, y: 0 }, size: GridSize { width: 6, height: 3 }, title: "System Health".to_string(), data_source: "system_metrics".to_string(), refresh_interval: 15, visible: true, config_data: WidgetConfigData::SystemHealth(SystemHealthConfig::default()), permissions: WidgetPermissions::admin(), }); config.widgets.insert("workflow_metrics".to_string(), WidgetConfig { id: "workflow_metrics".to_string(), widget_type: WidgetType::Chart, position: GridPosition { x: 6, y: 0 }, size: GridSize { width: 6, height: 3 }, title: "Workflow Metrics".to_string(), data_source: "workflow_data".to_string(), refresh_interval: 30, visible: true, config_data: WidgetConfigData::Chart(ChartConfig::default()), permissions: WidgetPermissions::admin(), }); config.widgets.insert("activity_feed".to_string(), WidgetConfig { id: "activity_feed".to_string(), widget_type: WidgetType::ActivityFeed, position: GridPosition { x: 0, y: 3 }, size: GridSize { width: 8, height: 4 }, title: "Activity Feed".to_string(), data_source: "activity_events".to_string(), refresh_interval: 30, visible: true, config_data: WidgetConfigData::ActivityFeed(ActivityFeedConfig::default()), permissions: WidgetPermissions::admin(), }); config.widgets.insert("security_metrics".to_string(), WidgetConfig { id: "security_metrics".to_string(), widget_type: WidgetType::Chart, position: GridPosition { x: 8, y: 3 }, size: GridSize { width: 4, height: 4 }, title: "Security Events".to_string(), data_source: "security_data".to_string(), refresh_interval: 60, visible: true, config_data: WidgetConfigData::Chart(ChartConfig::default()), permissions: WidgetPermissions::admin(), }); config } pub fn security_template() -> Self { let mut config = Self::base_config("Security Dashboard"); // Security-focused widgets config.widgets.insert("security_overview".to_string(), WidgetConfig { id: "security_overview".to_string(), widget_type: WidgetType::Chart, position: GridPosition { x: 0, y: 0 }, size: GridSize { width: 6, height: 3 }, title: "Security Overview".to_string(), data_source: "security_metrics".to_string(), refresh_interval: 30, visible: true, config_data: WidgetConfigData::Chart(ChartConfig::default()), permissions: WidgetPermissions::user(), }); config.widgets.insert("threat_feed".to_string(), WidgetConfig { id: "threat_feed".to_string(), widget_type: WidgetType::ActivityFeed, position: GridPosition { x: 6, y: 0 }, size: GridSize { width: 6, height: 5 }, title: "Security Events".to_string(), data_source: "security_events".to_string(), refresh_interval: 15, visible: true, config_data: WidgetConfigData::ActivityFeed(ActivityFeedConfig::default()), permissions: WidgetPermissions::user(), }); config } pub fn operations_template() -> Self { let mut config = Self::base_config("Operations Dashboard"); // Operations-focused widgets config.widgets.insert("system_resources".to_string(), WidgetConfig { id: "system_resources".to_string(), widget_type: WidgetType::Chart, position: GridPosition { x: 0, y: 0 }, size: GridSize { width: 8, height: 3 }, title: "System Resources".to_string(), data_source: "system_metrics".to_string(), refresh_interval: 15, visible: true, config_data: WidgetConfigData::Chart(ChartConfig::default()), permissions: WidgetPermissions::user(), }); config.widgets.insert("system_health".to_string(), WidgetConfig { id: "system_health".to_string(), widget_type: WidgetType::SystemHealth, position: GridPosition { x: 8, y: 0 }, size: GridSize { width: 4, height: 3 }, title: "Health Status".to_string(), data_source: "health_data".to_string(), refresh_interval: 15, visible: true, config_data: WidgetConfigData::SystemHealth(SystemHealthConfig::default()), permissions: WidgetPermissions::user(), }); config } pub fn user_template() -> Self { let mut config = Self::base_config("User Dashboard"); // User-focused widgets config.widgets.insert("my_activities".to_string(), WidgetConfig { id: "my_activities".to_string(), widget_type: WidgetType::ActivityFeed, position: GridPosition { x: 0, y: 0 }, size: GridSize { width: 8, height: 4 }, title: "My Activities".to_string(), data_source: "user_activities".to_string(), refresh_interval: 60, visible: true, config_data: WidgetConfigData::ActivityFeed(ActivityFeedConfig::default()), permissions: WidgetPermissions::user(), }); config.widgets.insert("quick_metrics".to_string(), WidgetConfig { id: "quick_metrics".to_string(), widget_type: WidgetType::Metric, position: GridPosition { x: 8, y: 0 }, size: GridSize { width: 4, height: 2 }, title: "Quick Stats".to_string(), data_source: "user_metrics".to_string(), refresh_interval: 120, visible: true, config_data: WidgetConfigData::Metric(MetricConfig::default()), permissions: WidgetPermissions::user(), }); config } pub fn readonly_template() -> Self { let mut config = Self::base_config("Readonly Dashboard"); // Readonly widgets with view-only permissions config.widgets.insert("status_overview".to_string(), WidgetConfig { id: "status_overview".to_string(), widget_type: WidgetType::SystemHealth, position: GridPosition { x: 0, y: 0 }, size: GridSize { width: 6, height: 3 }, title: "System Status".to_string(), data_source: "system_status".to_string(), refresh_interval: 60, visible: true, config_data: WidgetConfigData::SystemHealth(SystemHealthConfig::default()), permissions: WidgetPermissions::readonly(), }); config.widgets.insert("basic_metrics".to_string(), WidgetConfig { id: "basic_metrics".to_string(), widget_type: WidgetType::Metric, position: GridPosition { x: 6, y: 0 }, size: GridSize { width: 6, height: 3 }, title: "Key Metrics".to_string(), data_source: "basic_metrics".to_string(), refresh_interval: 120, visible: true, config_data: WidgetConfigData::Metric(MetricConfig::default()), permissions: WidgetPermissions::readonly(), }); config } pub fn minimal_template() -> Self { let mut config = Self::base_config("Minimal Dashboard"); // Single widget for minimal interface config.widgets.insert("essential_metrics".to_string(), WidgetConfig { id: "essential_metrics".to_string(), widget_type: WidgetType::Metric, position: GridPosition { x: 0, y: 0 }, size: GridSize { width: 12, height: 2 }, title: "Essential Metrics".to_string(), data_source: "essential_data".to_string(), refresh_interval: 60, visible: true, config_data: WidgetConfigData::Metric(MetricConfig::default()), permissions: WidgetPermissions::user(), }); config } fn base_config(name: &str) -> Self { Self { id: Uuid::new_v4().to_string(), name: name.to_string(), description: None, user_id: None, user_role: UserRole::User, layout: GridLayout::default(), widgets: HashMap::new(), theme_config: ThemeSettings::default(), filter_presets: vec![], export_settings: ExportSettings::default(), auto_refresh_settings: AutoRefreshSettings::default(), notification_settings: NotificationSettings::default(), created_at: Utc::now(), updated_at: Utc::now(), is_default: true, is_template: false, template_category: None, version: 1, } } } // Default implementations impl Default for ThemeSettings { fn default() -> Self { Self { theme_name: "auto".to_string(), custom_colors: HashMap::new(), font_size: FontSize::Medium, density: UiDensity::Normal, animation_enabled: true, } } } impl Default for ExportSettings { fn default() -> Self { Self { default_format: ExportFormat::Png, include_metadata: true, compress_data: false, auto_timestamp: true, custom_filename_template: None, } } } impl Default for AutoRefreshSettings { fn default() -> Self { Self { enabled: true, global_interval: 30, widget_overrides: HashMap::new(), pause_when_hidden: true, pause_on_error: true, } } } impl Default for NotificationSettings { fn default() -> Self { Self { enabled: true, position: NotificationPosition::TopRight, auto_dismiss: true, dismiss_timeout: 5, max_visible: 5, sound_enabled: false, level_filters: vec![], // Empty = show all levels } } } impl WidgetPermissions { pub fn admin() -> Self { Self { can_edit: true, can_move: true, can_resize: true, can_remove: true, required_roles: vec![UserRole::Admin], } } pub fn user() -> Self { Self { can_edit: true, can_move: true, can_resize: true, can_remove: true, required_roles: vec![UserRole::Admin, UserRole::User], } } pub fn readonly() -> Self { Self { can_edit: false, can_move: false, can_resize: false, can_remove: false, required_roles: vec![], // All roles can view } } }