hammerwork_web/
lib.rs

1//! # Hammerwork Web Dashboard
2//!
3//! A web-based admin dashboard for monitoring and managing Hammerwork job queues.
4//!
5//! This crate provides a comprehensive web interface for:
6//! - Real-time queue monitoring and statistics
7//! - Job management (retry, cancel, inspect)
8//! - Worker status and utilization
9//! - Dead job analysis and bulk operations
10//! - System health monitoring
11//!
12//! ## Usage
13//!
14//! ### As a Binary
15//!
16//! ```bash
17//! # Install the web dashboard
18//! cargo install hammerwork-web --features postgres
19//!
20//! # Start the dashboard
21//! hammerwork-web --database-url postgresql://localhost/hammerwork --bind 0.0.0.0:8080
22//! ```
23//!
24//! ### As a Library
25//!
26//! #### Basic Usage
27//!
28//! ```rust,no_run
29//! use hammerwork_web::{WebDashboard, DashboardConfig};
30//!
31//! #[tokio::main]
32//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
33//!     let config = DashboardConfig {
34//!         bind_address: "127.0.0.1".to_string(),
35//!         port: 8080,
36//!         database_url: "postgresql://localhost/hammerwork".to_string(),
37//!         ..Default::default()
38//!     };
39//!
40//!     let dashboard = WebDashboard::new(config).await?;
41//!     dashboard.start().await?;
42//!
43//!     Ok(())
44//! }
45//! ```
46//!
47//! #### Builder Pattern Configuration
48//!
49//! ```rust
50//! use hammerwork_web::DashboardConfig;
51//! use std::time::Duration;
52//!
53//! let config = DashboardConfig::new()
54//!     .with_bind_address("0.0.0.0", 9090)
55//!     .with_database_url("postgresql://localhost/hammerwork")
56//!     .with_auth("admin", "bcrypt_hash_here")
57//!     .with_cors(true);
58//!
59//! assert_eq!(config.bind_addr(), "0.0.0.0:9090");
60//! assert_eq!(config.database_url, "postgresql://localhost/hammerwork");
61//! assert!(config.auth.enabled);
62//! assert!(config.enable_cors);
63//! ```
64//!
65//! #### Configuration from File
66//!
67//! ```rust,no_run
68//! use hammerwork_web::DashboardConfig;
69//!
70//! // Load from TOML file
71//! let config = DashboardConfig::from_file("dashboard.toml")?;
72//!
73//! // Save configuration
74//! config.save_to_file("dashboard.toml")?;
75//! # Ok::<(), Box<dyn std::error::Error>>(())
76//! ```
77//!
78//! #### Authentication Configuration
79//!
80//! ```rust
81//! use hammerwork_web::AuthConfig;
82//! use std::time::Duration;
83//!
84//! let auth_config = AuthConfig {
85//!     enabled: true,
86//!     username: "admin".to_string(),
87//!     password_hash: "$2b$12$hash...".to_string(),
88//!     session_timeout: Duration::from_secs(8 * 60 * 60), // 8 hours
89//!     max_failed_attempts: 5,
90//!     lockout_duration: Duration::from_secs(15 * 60), // 15 minutes
91//! };
92//!
93//! assert!(auth_config.enabled);
94//! assert_eq!(auth_config.max_failed_attempts, 5);
95//! ```
96
97pub mod api;
98pub mod auth;
99pub mod config;
100pub mod server;
101pub mod websocket;
102
103pub use config::{AuthConfig, DashboardConfig};
104pub use server::WebDashboard;
105
106/// Result type alias for consistent error handling
107pub type Result<T> = std::result::Result<T, anyhow::Error>;
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use tempfile::tempdir;
113
114    #[test]
115    fn test_dashboard_config_creation() {
116        let config = DashboardConfig::new()
117            .with_bind_address("0.0.0.0", 3000)
118            .with_database_url("postgresql://localhost/test")
119            .with_cors(true);
120
121        assert_eq!(config.bind_addr(), "0.0.0.0:3000");
122        assert_eq!(config.database_url, "postgresql://localhost/test");
123        assert!(config.enable_cors);
124    }
125
126    #[test]
127    fn test_auth_config_security_defaults() {
128        let auth_config = AuthConfig::default();
129
130        // Ensure secure defaults
131        assert!(
132            auth_config.enabled,
133            "Authentication should be enabled by default"
134        );
135        assert_eq!(auth_config.username, "admin");
136        assert_eq!(auth_config.max_failed_attempts, 5);
137        assert!(auth_config.lockout_duration.as_secs() > 0);
138    }
139
140    #[tokio::test]
141    async fn test_dashboard_creation_with_invalid_config() {
142        let temp_dir = tempdir().unwrap();
143
144        let config = DashboardConfig {
145            database_url: "invalid://url".to_string(),
146            static_dir: temp_dir.path().to_path_buf(),
147            ..Default::default()
148        };
149
150        // Dashboard creation should succeed, but starting would fail with invalid URL
151        let result = WebDashboard::new(config).await;
152        assert!(
153            result.is_ok(),
154            "Dashboard creation should succeed, connection validation happens later"
155        );
156    }
157
158    #[test]
159    fn test_config_builder_pattern() {
160        let temp_dir = tempdir().unwrap();
161
162        let config = DashboardConfig::new()
163            .with_bind_address("192.168.1.1", 8888)
164            .with_database_url("mysql://root:pass@localhost/db")
165            .with_static_dir(temp_dir.path().to_path_buf())
166            .with_auth("user", "hash")
167            .with_cors(false);
168
169        assert_eq!(config.bind_address, "192.168.1.1");
170        assert_eq!(config.port, 8888);
171        assert_eq!(config.database_url, "mysql://root:pass@localhost/db");
172        assert!(config.auth.enabled);
173        assert_eq!(config.auth.username, "user");
174        assert!(!config.enable_cors);
175    }
176}