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,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,8 +4,8 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Archivr</title>
<script type="module" crossorigin src="/assets/index-j6_-GyNh.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dhs0pFsV.css">
<script type="module" crossorigin src="/assets/index-CTwpNgei.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BZfMx3eM.css">
</head>
<body>
<div id="root"></div>