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//! .with_allowed_origin("https://ops.example.com");
59//!
60//! assert_eq!(config.bind_addr(), "0.0.0.0:9090");
61//! assert_eq!(config.database_url, "postgresql://localhost/hammerwork");
62//! assert!(config.auth.enabled);
63//! assert!(config.enable_cors);
64//! ```
65//!
66//! #### Configuration from File
67//!
68//! ```rust,no_run
69//! use hammerwork_web::DashboardConfig;
70//!
71//! // Load from TOML file
72//! let config = DashboardConfig::from_file("dashboard.toml")?;
73//!
74//! // Save configuration
75//! config.save_to_file("dashboard.toml")?;
76//! # Ok::<(), Box<dyn std::error::Error>>(())
77//! ```
78//!
79//! #### Authentication Configuration
80//!
81//! ```rust
82//! use hammerwork_web::AuthConfig;
83//! use std::time::Duration;
84//!
85//! let auth_config = AuthConfig {
86//! enabled: true,
87//! username: "admin".to_string(),
88//! password_hash: "$2b$12$hash...".to_string(),
89//! session_timeout: Duration::from_secs(8 * 60 * 60), // 8 hours
90//! max_failed_attempts: 5,
91//! lockout_duration: Duration::from_secs(15 * 60), // 15 minutes
92//! };
93//!
94//! assert!(auth_config.enabled);
95//! assert_eq!(auth_config.max_failed_attempts, 5);
96//! ```
97
98// The dashboard needs a database backend to serve anything.
99#[cfg(not(any(feature = "postgres", feature = "mysql")))]
100compile_error!(
101 "hammerwork-web needs a database backend: enable the `postgres` (default) and/or `mysql` feature"
102);
103
104pub mod api;
105pub mod auth;
106pub mod config;
107pub mod live;
108pub mod security;
109pub mod server;
110pub mod websocket;
111
112pub use config::{AuthConfig, DashboardConfig};
113pub use server::WebDashboard;
114
115/// Result type alias for consistent error handling
116pub type Result<T> = std::result::Result<T, anyhow::Error>;
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use tempfile::tempdir;
122
123 #[test]
124 fn test_dashboard_config_creation() {
125 let config = DashboardConfig::new()
126 .with_bind_address("0.0.0.0", 3000)
127 .with_database_url("postgresql://localhost/test")
128 .with_cors(true);
129
130 assert_eq!(config.bind_addr(), "0.0.0.0:3000");
131 assert_eq!(config.database_url, "postgresql://localhost/test");
132 assert!(config.enable_cors);
133 }
134
135 #[test]
136 fn test_auth_config_security_defaults() {
137 let auth_config = AuthConfig::default();
138
139 // Ensure secure defaults
140 assert!(
141 auth_config.enabled,
142 "Authentication should be enabled by default"
143 );
144 assert_eq!(auth_config.username, "admin");
145 assert_eq!(auth_config.max_failed_attempts, 5);
146 assert!(auth_config.lockout_duration.as_secs() > 0);
147 }
148
149 #[tokio::test]
150 async fn test_dashboard_creation_with_invalid_config() {
151 let temp_dir = tempdir().unwrap();
152
153 let config = DashboardConfig {
154 database_url: "invalid://url".to_string(),
155 static_dir: temp_dir.path().to_path_buf(),
156 auth: crate::AuthConfig {
157 enabled: cfg!(feature = "auth"),
158 ..Default::default()
159 },
160 ..Default::default()
161 };
162
163 // Dashboard creation should succeed, but starting would fail with invalid URL
164 let result = WebDashboard::new(config).await;
165 assert!(
166 result.is_ok(),
167 "Dashboard creation should succeed, connection validation happens later"
168 );
169 }
170
171 #[test]
172 fn test_config_builder_pattern() {
173 let temp_dir = tempdir().unwrap();
174
175 let config = DashboardConfig::new()
176 .with_bind_address("192.168.1.1", 8888)
177 .with_database_url("mysql://root:pass@localhost/db")
178 .with_static_dir(temp_dir.path().to_path_buf())
179 .with_auth("user", "hash")
180 .with_cors(false);
181
182 assert_eq!(config.bind_address, "192.168.1.1");
183 assert_eq!(config.port, 8888);
184 assert_eq!(config.database_url, "mysql://root:pass@localhost/db");
185 assert!(config.auth.enabled);
186 assert_eq!(config.auth.username, "user");
187 assert!(!config.enable_cors);
188 }
189}