1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00

feat: non-blocking batch capture dialog, toasts on failure, fix /runs errors

CaptureDialog:
- Replace single textarea with multi-row inputs; + button adds rows
- Submit fires all pending rows in parallel, dialog stays open/usable
- Polling intervals live on a persistent ref (not cleared on close) so
  toasts fire even after the dialog is dismissed
- archiveId stored per item at submit time; page-refresh reconnect uses
  it.archiveId instead of the possibly-null prop
- Completed rows flash green then self-remove; failed rows show inline
  error + retry button
- Cancel becomes Close while jobs are in flight

ToastStack (new component):
- Fixed bottom-right overlay with spring-in animation
- Error toast: truncated locator, View error / Hide toggle expanding
  full error_text in a monospace pre block
- Auto-dismisses after 7 s; timer pauses while detail is expanded

RunsView:
- Failed rows are clickable and expand a full-width detail row showing
  error_summary in a scrollable monospace block

capture.rs (archivr-core):
- Staging dir is now "{millis}-{uuid}" — parallel captures in the same
  millisecond can no longer collide on temp paths
- create_archive_run moved before URL Content-Type probe so every
  attempt appears in /runs regardless of outcome
- Probe failures now call create_archive_run_item with source_metadata
  fallback then fail_run, recording error_text on the item and
  error_summary on the run with correct failed_count

styles.css:
- Capture dialog: header row, multi-row layout, status dots, spinner,
  add-row dashed button, per-row error text
- Toast stack: fixed overlay, error card with coloured left border,
  monospace detail expansion
- Run error rows: clickable hover tint, expand hint chevron, detail pre
This commit is contained in:
TheGeneralist 2026-07-05 13:56:00 +02:00
parent d83faab8e6
commit fb1115a409
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
11 changed files with 698 additions and 223 deletions

View file

@ -1,5 +1,6 @@
use anyhow::{Context, Result};
use chrono::Local;
use uuid::Uuid;
use serde_json::json;
use std::{
collections::HashSet,
@ -677,7 +678,13 @@ pub fn perform_capture(
locator: &str,
archive_id: Option<&str>,
) -> Result<CaptureResult> {
let timestamp = Local::now().format("%Y-%m-%dT%H-%M-%S%.3f").to_string();
// Append a UUID so parallel captures starting in the same millisecond
// never collide on the staging directory or file names.
let timestamp = format!(
"{}-{}",
Local::now().format("%Y-%m-%dT%H-%M-%S%.3f"),
Uuid::new_v4().simple(),
);
let store_path = &archive_paths.store_path;
let conn = database::open_or_initialize(&archive_paths.archive_path)?;
@ -685,24 +692,31 @@ pub fn perform_capture(
let mut source = determine_source(locator);
// For generic http/https URLs, probe Content-Type to distinguish a plain
// file download from an HTML page that needs single-file archiving.
// The probe runs before creating DB records so source_kind is set correctly.
//
// Note: probe failures return early without a DB run record. This is
// intentional — a failed network probe means we haven't started archiving
// and there is nothing meaningful to record.
// Create the run record before probing so every attempt — including
// probe failures — is visible in /runs with a proper status and error.
let run = database::create_archive_run(&conn, user_id, 1)?;
// For generic http/https URLs, probe Content-Type to decide whether to
// treat the URL as a raw file download or an HTML page for SingleFile.
if source == Source::Url {
match downloader::http::probe_url_kind(locator) {
Ok(downloader::http::UrlKind::Html) => source = Source::WebPage,
Ok(downloader::http::UrlKind::File) => {}
Err(e) => return Err(anyhow::anyhow!("Failed to probe URL: {e}")),
Err(e) => {
// Record a failed item using the pre-probe source_kind so
// failed_count increments and the item carries error_text.
let (probe_sk, probe_ek, _) = source_metadata(Source::Url);
let item = database::create_archive_run_item(
&conn, run.id, None, 0, locator, None, probe_sk, probe_ek,
)?;
let msg = format!("Failed to probe URL: {e}");
return Err(fail_run(&conn, &run, &item, &msg));
}
}
}
let (source_kind, entity_kind, _) = source_metadata(source);
let run = database::create_archive_run(&conn, user_id, 1)?;
let item = database::create_archive_run_item(
&conn,
run.id,