Add event bus for live UI updates
Introduces a system for broadcasting real-time events to the web UI. This enables features like automatic page reloads when case data changes in the background, improving the user experience by keeping the UI synchronized with the backend. Key components: - `events` module: Contains the `CaseEventKind` enum, `CaseEvent` struct, and `EventSender` type for managing the broadcast channel. - SSE endpoint (`/web/events`): Streams events to connected browsers. - Client-side JavaScript: Listens for events and triggers debounced page reloads. - Integration points: Workers and route handlers now emit events when relevant state changes occur.
This commit is contained in:
@@ -12,6 +12,7 @@ use crate::analyze::{ANALYSIS_INPUT_FILE, AnalyzeJob, AnalyzeSender, DOCUMENT_FI
|
||||
use crate::auth::AuthenticatedWebUser;
|
||||
use crate::config::Config;
|
||||
use crate::error::AppError;
|
||||
use crate::events::{self, CaseEventKind, EventSender};
|
||||
use crate::paths::{DeleteMarker, write_delete_marker};
|
||||
use crate::routes::case_actions::{
|
||||
build_analysis_input, reset_case_artefacts, write_input_create_new,
|
||||
@@ -34,21 +35,30 @@ pub async fn handle_bulk_action(
|
||||
user: AuthenticatedWebUser,
|
||||
State(config): State<Arc<Config>>,
|
||||
State(analyze_tx): State<AnalyzeSender>,
|
||||
State(events_tx): State<EventSender>,
|
||||
Form(form): Form<BulkForm>,
|
||||
) -> Result<Redirect, AppError> {
|
||||
let user_root = config.data_path.join(&user.slug);
|
||||
|
||||
match form.action.as_str() {
|
||||
"analyze" => {
|
||||
bulk_analyze(&config, &analyze_tx, &user.slug, &user_root, &form.case_ids).await
|
||||
bulk_analyze(
|
||||
&config,
|
||||
&analyze_tx,
|
||||
&events_tx,
|
||||
&user.slug,
|
||||
&user_root,
|
||||
&form.case_ids,
|
||||
)
|
||||
.await
|
||||
}
|
||||
"delete" => bulk_delete(&user.slug, &user_root, &form.case_ids).await,
|
||||
"delete" => bulk_delete(&events_tx, &user.slug, &user_root, &form.case_ids).await,
|
||||
"reset" => {
|
||||
if !user.is_admin() {
|
||||
warn!(slug = %user.slug, "bulk-reset: not admin");
|
||||
return Err(AppError::Forbidden("Nur für Admins".into()));
|
||||
}
|
||||
bulk_reset(&user.slug, &user_root, &form.case_ids).await
|
||||
bulk_reset(&events_tx, &user.slug, &user_root, &form.case_ids).await
|
||||
}
|
||||
other => {
|
||||
warn!(slug = %user.slug, action = %other, "bulk: unknown action");
|
||||
@@ -62,6 +72,7 @@ pub async fn handle_bulk_action(
|
||||
async fn bulk_analyze(
|
||||
config: &Config,
|
||||
analyze_tx: &AnalyzeSender,
|
||||
events_tx: &EventSender,
|
||||
slug: &str,
|
||||
user_root: &std::path::Path,
|
||||
case_ids: &[String],
|
||||
@@ -117,12 +128,18 @@ async fn bulk_analyze(
|
||||
{
|
||||
warn!(slug = %slug, case_id = %case_id, "bulk-analyze: send failed (worker gone)");
|
||||
}
|
||||
events::emit(events_tx, slug, case_id, CaseEventKind::AnalysisQueued);
|
||||
ok += 1;
|
||||
}
|
||||
info!(slug = %slug, ok, skipped, "bulk-analyze completed");
|
||||
}
|
||||
|
||||
async fn bulk_delete(slug: &str, user_root: &std::path::Path, case_ids: &[String]) {
|
||||
async fn bulk_delete(
|
||||
events_tx: &EventSender,
|
||||
slug: &str,
|
||||
user_root: &std::path::Path,
|
||||
case_ids: &[String],
|
||||
) {
|
||||
// One batch UUID + one timestamp shared across the entire bulk action,
|
||||
// so undo can restore exactly this group.
|
||||
let batch = uuid::Uuid::new_v4();
|
||||
@@ -155,12 +172,18 @@ async fn bulk_delete(slug: &str, user_root: &std::path::Path, case_ids: &[String
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
events::emit(events_tx, slug, case_id, CaseEventKind::CaseDeleted);
|
||||
ok += 1;
|
||||
}
|
||||
info!(slug = %slug, batch = %batch, ok, skipped, "bulk-delete completed");
|
||||
}
|
||||
|
||||
async fn bulk_reset(slug: &str, user_root: &std::path::Path, case_ids: &[String]) {
|
||||
async fn bulk_reset(
|
||||
events_tx: &EventSender,
|
||||
slug: &str,
|
||||
user_root: &std::path::Path,
|
||||
case_ids: &[String],
|
||||
) {
|
||||
let mut ok = 0usize;
|
||||
let mut skipped = 0usize;
|
||||
for case_id in case_ids {
|
||||
@@ -179,6 +202,7 @@ async fn bulk_reset(slug: &str, user_root: &std::path::Path, case_ids: &[String]
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
events::emit(events_tx, slug, case_id, CaseEventKind::CaseReset);
|
||||
ok += 1;
|
||||
}
|
||||
info!(slug = %slug, ok, skipped, "bulk-reset completed");
|
||||
|
||||
Reference in New Issue
Block a user