apalis_board_web/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use apalis_board_types::LogEntry;
4use chrono::{DateTime, Local, Utc};
5use leptos::{prelude::*, reactive::spawn_local};
6use serde::{Deserialize, Serialize};
7
8pub mod api;
9pub mod components;
10pub mod pages;
11pub mod translate;
12
13#[allow(deprecated)]
14pub mod locales {
15    // TODO: @geofmureithi move to build script
16    leptos_i18n::load_locales!();
17}
18
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
20pub struct User {
21    pub id: String,
22    pub username: String,
23}
24
25type UserSignal = RwSignal<Option<User>>;
26pub fn use_user_signal() -> UserSignal {
27    use_context::<UserSignal>().expect("UserSignal")
28}
29
30pub fn use_sse_signal() -> RwSignal<Vec<String>> {
31    use_context::<RwSignal<Vec<String>>>().expect("SSE Signal")
32}
33
34#[derive(Clone, Copy)]
35pub struct SseProvider {
36    event_source: RwSignal<LogEntry>,
37    is_healthy: RwSignal<bool>,
38}
39
40impl SseProvider {
41    pub fn event_source(&self) -> RwSignal<LogEntry> {
42        self.event_source
43    }
44
45    pub fn is_healthy(&self) -> RwSignal<bool> {
46        self.is_healthy
47    }
48}
49
50pub fn use_sse_provider() -> SseProvider {
51    use_context::<SseProvider>().expect("SSE Provider")
52}
53
54pub fn create_sse_resource(url: &str) -> SseProvider {
55    use futures::StreamExt;
56    let data = RwSignal::new(LogEntry::default());
57    let mut source = gloo_net::eventsource::futures::EventSource::new(url)
58        .expect("couldn't connect to SSE stream");
59    let mut stream = source
60        .subscribe("message")
61        .unwrap()
62        .map(|value| match value {
63            Ok(value) => Ok(serde_json::from_str::<LogEntry>(
64                &value.1.data().as_string().expect("expected string value"),
65            )
66            .expect("couldn't parse message")),
67            Err(e) => Err(e),
68        });
69    let is_healthy = RwSignal::new(true);
70    spawn_local(async move {
71        while let Some(next_value) = stream.next().await {
72            if let Ok(log_entry) = next_value {
73                data.set(log_entry);
74            } else {
75                is_healthy.set(false);
76                source.close();
77                break;
78            }
79        }
80    });
81    SseProvider {
82        event_source: data,
83        is_healthy,
84    }
85}
86
87pub fn relative_timestamp(timestamp: u64) -> String {
88    let now = Utc::now().timestamp() as u64;
89
90    match timestamp.cmp(&now) {
91        std::cmp::Ordering::Greater => {
92            let future_diff = timestamp - now;
93            match future_diff {
94                0..=59 => "in a few seconds".to_string(),
95                60..=3599 => format!("in {} minutes", future_diff / 60),
96                3600..=86399 => format!("in {} hours", future_diff / 3600),
97                86400..=2_592_000 => format!("in {} days", future_diff / 86400),
98                _ => {
99                    let datetime = DateTime::<Utc>::from_timestamp(timestamp as i64, 0)
100                        .expect("Invalid timestamp");
101                    let local: DateTime<Local> = datetime.into();
102                    format!("on {}", local.format("%B %d, %Y"))
103                }
104            }
105        }
106        _ => {
107            let diff = now.saturating_sub(timestamp);
108            match diff {
109                0..=59 => "just now".to_string(),
110                60..=3599 => format!("{} minutes ago", diff / 60),
111                3600..=86399 => format!("{} hours ago", diff / 3600),
112                86400..=2_592_000 => format!("{} days ago", diff / 86400),
113                _ => {
114                    let datetime = DateTime::<Utc>::from_timestamp(timestamp as i64, 0)
115                        .expect("Invalid timestamp");
116                    let local: DateTime<Local> = datetime.into();
117                    local.format("%B %d, %Y").to_string()
118                }
119            }
120        }
121    }
122}
123
124pub type RawTask = apalis_core::task::Task<serde_json::Value, serde_json::Value, String>;