2026-01-12 05:03:09 +00:00

868 lines
27 KiB
Rust

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<String>,
pub user_id: Option<String>,
pub user_role: UserRole,
pub layout: GridLayout,
pub widgets: HashMap<String, WidgetConfig>,
pub theme_config: ThemeSettings,
pub filter_presets: Vec<FilterPreset>,
pub export_settings: ExportSettings,
pub auto_refresh_settings: AutoRefreshSettings,
pub notification_settings: NotificationSettings,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub is_default: bool,
pub is_template: bool,
pub template_category: Option<String>,
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<UserRole>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThemeSettings {
pub theme_name: String,
pub custom_colors: HashMap<String, String>,
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<String, serde_json::Value>,
pub date_range: Option<DateRangePreset>,
pub is_default: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateRangePreset {
pub name: String,
pub range_type: DateRangeType,
pub custom_start: Option<DateTime<Utc>>,
pub custom_end: Option<DateTime<Utc>>,
}
#[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<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoRefreshSettings {
pub enabled: bool,
pub global_interval: u32, // seconds
pub widget_overrides: HashMap<String, u32>,
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<NotificationLevel>,
}
#[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<TableColumn>,
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<u32>,
pub sortable: bool,
pub filterable: bool,
pub formatter: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationConfig {
pub enabled: bool,
pub page_size: u32,
pub show_size_selector: bool,
pub available_sizes: Vec<u32>,
}
#[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<MapMarker>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapMarker {
pub id: String,
pub position: (f64, f64),
pub label: String,
pub color: String,
pub icon: Option<String>,
}
// Dashboard configuration service
#[derive(Debug, Clone)]
pub struct DashboardConfigService {
current_config: RwSignal<DashboardConfig>,
saved_configs: RwSignal<Vec<DashboardConfig>>,
templates: RwSignal<Vec<DashboardTemplate>>,
}
#[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<String>,
pub tags: Vec<String>,
pub required_roles: Vec<UserRole>,
pub created_by: String,
pub is_official: bool,
}
impl DashboardConfigService {
pub fn new() -> Self {
// Load saved configurations from localStorage
let saved_configs = LocalStorage::get::<Vec<DashboardConfig>>("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<DashboardConfig> {
self.current_config.into()
}
pub fn update_config<F>(&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<DashboardConfig> {
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<Vec<DashboardConfig>> {
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<Vec<DashboardTemplate>> {
self.templates.into()
}
pub fn get_templates_for_role(&self, role: UserRole) -> Vec<DashboardTemplate> {
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<F>(&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<FilterPreset> {
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<DashboardTemplate> {
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
}
}
}