From f5056130e5f1737f7fca36a36763575eb2fb776b Mon Sep 17 00:00:00 2001 From: Arnaud_Cayrol Date: Sat, 31 Jan 2026 10:31:02 +0100 Subject: [PATCH] Fix UI bug with SkipReason count --- Cargo.toml | 2 +- frontend/src/App.svelte | 5 +++-- .../src/lib/components/ProgressDisplay.svelte | 15 ++++++++++----- src/web/handlers/mod.rs | 15 +++++++++++++-- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 130f332..5f401d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ tokio = { version = "1", features = ["full"] } # Web framework axum = { version = "0.8", features = ["ws"] } tower = "0.5" -tower-http = { version = "0.6", features = ["fs", "cors"] } +tower-http = { version = "0.6", features = ["fs", "cors", "set-header"] } # HTTP client reqwest = { version = "0.12", features = ["json", "stream"] } diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 1d414ba..3fac963 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -9,7 +9,8 @@ import ResultsView from './lib/components/ResultsView.svelte'; import SettingsPanel from './lib/components/SettingsPanel.svelte'; - // LocalStorage keys + // Configuration constants + const POLL_INTERVAL_MS = 500; // Progress polling interval during job execution const STORAGE_KEY_PERSON = 'immich-timelapse-selected-person'; // Load persisted state from localStorage @@ -182,7 +183,7 @@ function startPolling() { if (pollInterval) return; // Already polling - pollInterval = setInterval(pollProgress, 500); + pollInterval = setInterval(pollProgress, POLL_INTERVAL_MS); } function stopPolling() { diff --git a/frontend/src/lib/components/ProgressDisplay.svelte b/frontend/src/lib/components/ProgressDisplay.svelte index 1db10c4..105f5b3 100644 --- a/frontend/src/lib/components/ProgressDisplay.svelte +++ b/frontend/src/lib/components/ProgressDisplay.svelte @@ -41,19 +41,24 @@ // Skip statistics from the backend - dynamically handle any keys let skipStats = $derived(progress.skip_stats || {}); - // Calculate total from all numeric values in skip_stats + // Use the backend-provided total if available, otherwise calculate from individual counts + // Note: We exclude 'total' from manual calculation to avoid double-counting let skipTotal = $derived( - Object.values(skipStats).reduce((sum, val) => sum + (typeof val === 'number' ? val : 0), 0) + typeof skipStats.total === 'number' + ? skipStats.total + : Object.entries(skipStats) + .filter(([key, _]) => key !== 'total') + .reduce((sum, [_, val]) => sum + (typeof val === 'number' ? val : 0), 0) ); // Calculate kept (successful) count: completed - skipped let keptCount = $derived(Math.max(0, progress.completed - skipTotal)); // Dynamically build skip reasons from whatever the backend sends - // Filter to only show non-zero counts + // Filter to only show non-zero counts, exclude the 'total' field let skipReasons = $derived( Object.entries(skipStats) - .filter(([_, count]) => typeof count === 'number' && count > 0) + .filter(([key, count]) => key !== 'total' && typeof count === 'number' && count > 0) .map(([key, count]) => ({ label: formatLabel(key), count })) .sort((a, b) => b.count - a.count) // Sort by count descending ); @@ -98,7 +103,7 @@ {#if (jobStatus === 'running' || jobStatus === 'completed' || jobStatus === 'cancelled') && progress.completed > 0}
- Discarded:Kept + Discarded : Kept {skipTotal} : diff --git a/src/web/handlers/mod.rs b/src/web/handlers/mod.rs index f09dc72..47730b2 100644 --- a/src/web/handlers/mod.rs +++ b/src/web/handlers/mod.rs @@ -15,12 +15,15 @@ mod processing; use crate::web::state::AppState; use axum::{ + http::header, response::{Html, IntoResponse}, routing::{delete, get, post, put}, Router, }; use serde::Serialize; +use tower::ServiceBuilder; use tower_http::services::ServeDir; +use tower_http::set_header::SetResponseHeaderLayer; // Re-export handler functions for use in router use config::{get_config, update_config}; @@ -89,8 +92,16 @@ pub fn create_router(state: AppState) -> Router { ) .route("/api/config", get(get_config)) .route("/api/config", put(update_config)) - // Serve output files (video, images) - .nest_service("/output", ServeDir::new(output_dir)) + // Serve output files (video, images) with cache headers + .nest_service( + "/output", + ServiceBuilder::new() + .layer(SetResponseHeaderLayer::if_not_present( + header::CACHE_CONTROL, + header::HeaderValue::from_static("public, max-age=86400"), + )) + .service(ServeDir::new(output_dir)), + ) // Serve frontend static files (fallback to index.html for SPA routing) .fallback_service(ServeDir::new("frontend/dist").fallback(get(serve_index))) .with_state(state)