Fix UI bug with SkipReason count

This commit is contained in:
Arnaud_Cayrol 2026-01-31 10:31:02 +01:00
parent a6d491d3c2
commit f5056130e5
4 changed files with 27 additions and 10 deletions

View file

@ -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"] }

View file

@ -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() {

View file

@ -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}
<div class="skip-stats">
<div class="skip-header">
<span class="skip-label">Discarded:Kept</span>
<span class="skip-label">Discarded : Kept</span>
<span class="skip-totals">
<span class="skipped-count">{skipTotal}</span>
<span class="separator">:</span>

View file

@ -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)