feat(install): phase-based progress bar replaces unparseable pull bytes

Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.

Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.

Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
This commit is contained in:
archipelago
2026-04-23 07:58:43 -04:00
parent 576ff1a6de
commit 7e62ea07f7
8 changed files with 224 additions and 29 deletions
+32
View File
@@ -245,6 +245,38 @@ pub enum ServiceStatus {
pub struct InstallProgress {
pub size: u64,
pub downloaded: u64,
/// High-level pipeline phase. Preferred by the UI over the byte
/// counters (podman pull doesn't emit parseable progress on a piped
/// stderr, so `size`/`downloaded` are often 0). Each phase maps to
/// a fixed UI percentage and a descriptive label.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub phase: Option<InstallPhase>,
}
/// Phases of the install / update pipeline, surfaced to the UI so users
/// see where the pipeline is rather than a stuck 0% bar.
///
/// Ordered so each variant's index roughly corresponds to pipeline time.
/// Serialized as kebab-case: "preparing", "pulling-image", …
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum InstallPhase {
/// Validating params, checking deps, writing dynamic configs.
Preparing,
/// `podman pull` in progress (the longest phase — up to several
/// minutes for large images on slow networks).
PullingImage,
/// Creating data directories, writing app-specific configs
/// (bitcoin.conf, lnd.conf, searxng settings.yml, chown).
CreatingContainer,
/// `podman run` has returned; container is transitioning to running.
StartingContainer,
/// Post-start loop waiting up to 60s for `State.Status == running`.
WaitingHealthy,
/// Running post-install hooks (chain init, wallet setup, …).
PostInstall,
/// Pipeline finished successfully. Terminal phase, UI clears entry.
Done,
}
/// WebSocket message sent to clients