feat(ui): stream per-phase progress when installing a preset#257
Merged
Conversation
The Add button on a preset opened with a single opaque spinner and a
60-second wait, with the biggest contributor (image pull) happening
silently inside podman on the first systemctl start.
This rewrites the POST /api/services/presets/{name} response into an
NDJSON stream of phase events:
installing_config -> starting_deps{dep} -> pulling_image{message}
-> starting_unit -> waiting_ready -> done
The image is pulled explicitly before StartUnit so the pull shows up
as visible, line-by-line progress instead of an invisible wait. Each
line of podman output is forwarded to the UI and surfaced under the
Add button, so a slow registry is distinguishable from a stuck
install.
Frontend consumes the stream via a ReadableStream reader, tracks the
current phase/message/dep, and swaps the button label per phase
("Pulling image...", "Starting elasticsearch...", "Waiting for
ready..."). Works for both the preset modal and the suggestion banner
on service detail pages.
geodro
added a commit
that referenced
this pull request
Apr 24, 2026
Bundles four landed changes since beta.6: - #252 Add memcached, rabbitmq, elasticsearch service presets. - #256 Fix check-upstream-php workflow dispatch (was silently broken). - #257 Stream per-phase progress during preset install in the Web UI. - #258 Offline landing page for the installed PWA with lerd start + copy button; nginx vhost allowlists the new SW routes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Clicking Add on a preset (elasticvue, phpMyAdmin, mongo, etc.) used to show a single opaque spinner for up to a minute. The biggest contributor to that latency is the first-time image pull, which happened silently inside podman when
systemctl --user start lerd-<preset>ran, with no output reaching the UI.This rewrites the install flow to stream per-phase progress.
Changes
Backend —
POST /api/services/presets/{name}now returnsapplication/x-ndjson. A newserviceops.InstallPresetStreamingorchestrator emits aPhaseEventat every step:The image pull is now explicit and happens before
StartUnit, using a newpodman.PullImageWithProgress(image, onLine)helper. A smalllineWritersplits podman output on both\nand\rso TTY progress-bar rewrites still surface as discrete events.Frontend —
installPreset()consumes the NDJSON stream viaReadableStream, tracksinstallingPhase / installingMessage / installingDep, and a newpresetPhaseLabel(p)helper maps phase → button text ("Pulling image...", "Starting elasticsearch...", "Waiting for ready..."). The modal card shows the liveCopying blob ...line underneath the button so a slow registry is distinguishable from a stuck install. Both install entry points (preset modal, service-detail suggestion banner) use the same labels.Compatibility — CLI (
lerd service preset <name>) and MCP (service_preset_install) still callserviceops.InstallPresetByNameunchanged; only the HTTP UI response shape is new. macOS is fine:podman.StartUnitalready abstracts launchd vs systemctl viaUnitLifecycle, and the pull helper just shells out topodman pull.Notes
publishAfterstill wraps the handler, so the eventbus services/status broadcast fires after the stream closes and the WS snapshot refreshes as before.