Fix UI bug with SkipReason count
This commit is contained in:
parent
a6d491d3c2
commit
f5056130e5
4 changed files with 27 additions and 10 deletions
|
|
@ -13,7 +13,7 @@ tokio = { version = "1", features = ["full"] }
|
||||||
# Web framework
|
# Web framework
|
||||||
axum = { version = "0.8", features = ["ws"] }
|
axum = { version = "0.8", features = ["ws"] }
|
||||||
tower = "0.5"
|
tower = "0.5"
|
||||||
tower-http = { version = "0.6", features = ["fs", "cors"] }
|
tower-http = { version = "0.6", features = ["fs", "cors", "set-header"] }
|
||||||
|
|
||||||
# HTTP client
|
# HTTP client
|
||||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@
|
||||||
import ResultsView from './lib/components/ResultsView.svelte';
|
import ResultsView from './lib/components/ResultsView.svelte';
|
||||||
import SettingsPanel from './lib/components/SettingsPanel.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';
|
const STORAGE_KEY_PERSON = 'immich-timelapse-selected-person';
|
||||||
|
|
||||||
// Load persisted state from localStorage
|
// Load persisted state from localStorage
|
||||||
|
|
@ -182,7 +183,7 @@
|
||||||
|
|
||||||
function startPolling() {
|
function startPolling() {
|
||||||
if (pollInterval) return; // Already polling
|
if (pollInterval) return; // Already polling
|
||||||
pollInterval = setInterval(pollProgress, 500);
|
pollInterval = setInterval(pollProgress, POLL_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPolling() {
|
function stopPolling() {
|
||||||
|
|
|
||||||
|
|
@ -41,19 +41,24 @@
|
||||||
// Skip statistics from the backend - dynamically handle any keys
|
// Skip statistics from the backend - dynamically handle any keys
|
||||||
let skipStats = $derived(progress.skip_stats || {});
|
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(
|
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
|
// Calculate kept (successful) count: completed - skipped
|
||||||
let keptCount = $derived(Math.max(0, progress.completed - skipTotal));
|
let keptCount = $derived(Math.max(0, progress.completed - skipTotal));
|
||||||
|
|
||||||
// Dynamically build skip reasons from whatever the backend sends
|
// 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(
|
let skipReasons = $derived(
|
||||||
Object.entries(skipStats)
|
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 }))
|
.map(([key, count]) => ({ label: formatLabel(key), count }))
|
||||||
.sort((a, b) => b.count - a.count) // Sort by count descending
|
.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}
|
{#if (jobStatus === 'running' || jobStatus === 'completed' || jobStatus === 'cancelled') && progress.completed > 0}
|
||||||
<div class="skip-stats">
|
<div class="skip-stats">
|
||||||
<div class="skip-header">
|
<div class="skip-header">
|
||||||
<span class="skip-label">Discarded:Kept</span>
|
<span class="skip-label">Discarded : Kept</span>
|
||||||
<span class="skip-totals">
|
<span class="skip-totals">
|
||||||
<span class="skipped-count">{skipTotal}</span>
|
<span class="skipped-count">{skipTotal}</span>
|
||||||
<span class="separator">:</span>
|
<span class="separator">:</span>
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,15 @@ mod processing;
|
||||||
|
|
||||||
use crate::web::state::AppState;
|
use crate::web::state::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
|
http::header,
|
||||||
response::{Html, IntoResponse},
|
response::{Html, IntoResponse},
|
||||||
routing::{delete, get, post, put},
|
routing::{delete, get, post, put},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use tower::ServiceBuilder;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
use tower_http::set_header::SetResponseHeaderLayer;
|
||||||
|
|
||||||
// Re-export handler functions for use in router
|
// Re-export handler functions for use in router
|
||||||
use config::{get_config, update_config};
|
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", get(get_config))
|
||||||
.route("/api/config", put(update_config))
|
.route("/api/config", put(update_config))
|
||||||
// Serve output files (video, images)
|
// Serve output files (video, images) with cache headers
|
||||||
.nest_service("/output", ServeDir::new(output_dir))
|
.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)
|
// Serve frontend static files (fallback to index.html for SPA routing)
|
||||||
.fallback_service(ServeDir::new("frontend/dist").fallback(get(serve_index)))
|
.fallback_service(ServeDir::new("frontend/dist").fallback(get(serve_index)))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue