use std::time::Duration;
const DASHBOARD_PORT: u16 = 11391;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum NotificationMessage {
TaskChanged {
task_id: Option<i64>,
operation: String,
project_path: Option<String>,
},
EventAdded {
task_id: i64,
event_id: i64,
project_path: Option<String>,
},
WorkspaceChanged {
current_task_id: Option<i64>,
project_path: Option<String>,
},
}
pub struct CliNotifier {
base_url: String,
client: reqwest::Client,
}
impl CliNotifier {
pub fn new() -> Self {
let base_url = std::env::var("IE_DASHBOARD_BASE_URL")
.unwrap_or_else(|_| format!("http://127.0.0.1:{}", DASHBOARD_PORT));
Self::with_base_url(base_url)
}
pub fn with_base_url(base_url: String) -> Self {
let client = reqwest::Client::builder()
.timeout(Duration::from_millis(100)) .build()
.unwrap_or_else(|_| reqwest::Client::new());
Self { base_url, client }
}
pub fn with_port(port: u16) -> Self {
let base_url = format!("http://127.0.0.1:{}", port);
Self::with_base_url(base_url)
}
pub async fn notify(&self, message: NotificationMessage) {
if std::env::var("IE_DISABLE_DASHBOARD_NOTIFICATIONS")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false)
{
tracing::debug!(
"Dashboard notifications disabled via IE_DISABLE_DASHBOARD_NOTIFICATIONS"
);
return; }
let url = format!("{}/api/internal/cli-notify", self.base_url);
if let Err(e) = self.client.post(&url).json(&message).send().await {
tracing::debug!("Failed to notify Dashboard: {}", e);
}
}
pub async fn notify_task_changed(
&self,
task_id: Option<i64>,
operation: &str,
project_path: Option<String>,
) {
self.notify(NotificationMessage::TaskChanged {
task_id,
operation: operation.to_string(),
project_path,
})
.await;
}
pub async fn notify_event_added(
&self,
task_id: i64,
event_id: i64,
project_path: Option<String>,
) {
self.notify(NotificationMessage::EventAdded {
task_id,
event_id,
project_path,
})
.await;
}
pub async fn notify_workspace_changed(
&self,
current_task_id: Option<i64>,
project_path: Option<String>,
) {
self.notify(NotificationMessage::WorkspaceChanged {
current_task_id,
project_path,
})
.await;
}
}
impl Default for CliNotifier {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_notifier_creation() {
let notifier = CliNotifier::new();
assert_eq!(notifier.base_url, "http://127.0.0.1:11391");
}
#[test]
fn test_notifier_with_custom_port() {
let notifier = CliNotifier::with_port(8080);
assert_eq!(notifier.base_url, "http://127.0.0.1:8080");
}
#[tokio::test]
async fn test_notify_non_blocking() {
let notifier = CliNotifier::with_port(65000); notifier
.notify_task_changed(Some(42), "created", Some("/test/path".to_string()))
.await;
}
}