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