2026-01-08 21:32:59 +00:00
|
|
|
use leptos::prelude::*;
|
2025-10-07 10:59:52 +01:00
|
|
|
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 {
|
2026-01-08 21:32:59 +00:00
|
|
|
current_config: RwSignal::new(current_config),
|
|
|
|
|
saved_configs: RwSignal::new(saved_configs),
|
|
|
|
|
templates: RwSignal::new(templates),
|
2025-10-07 10:59:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-12 05:03:09 +00:00
|
|
|
}
|