Skip to main content

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}