release(v1.7.30-alpha): live install/uninstall progress + cleaner pull waterfall

- Backend: unified pull-progress streaming across primary AND fallback
  registries. Earlier code only streamed for the primary attempt; if it
  failed fast (VPS 404, etc.) the UI froze at 0% until the fallback
  finished. The waterfall now uses a single shared helper that streams
  podman stderr through update_install_progress for every URL tried.
- Backend: PackageDataEntry gains uninstall_stage, set at each phase of
  handle_package_uninstall ("Stopping containers (i/total)",
  "Cleaning up volumes", "Removing app data"). State flips to Removing
  during the pipeline.
- Frontend: MarketplaceAppCard renders the live progress bar with byte
  counts during installs, matching the System Update download bar style.
- Frontend: AppCard renders the live uninstall stage label per app.
  Modal closes immediately on confirm so concurrent uninstalls each
  show their own progress on their own card.
- Cleanup: removed dead helpers (image_candidates, rewrite_for_primary,
  primary_image_url, pull_from_registries_with_skip) made unused by
  the install.rs refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-21 19:11:36 -04:00
co-authored by Claude Opus 4.7
parent 1709149ebd
commit 18f0929614
13 changed files with 221 additions and 209 deletions
+9 -2
View File
@@ -99,14 +99,14 @@
</div>
</div>
<!-- Uninstalling progress replaces action buttons -->
<!-- Uninstalling progress live stage label from backend -->
<div v-else-if="isUninstalling" class="mt-4">
<div class="flex items-center gap-1.5">
<svg class="animate-spin h-3 w-3 text-red-400" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-xs text-red-300">{{ t('common.uninstalling') }}...</span>
<span class="text-xs text-red-300 truncate">{{ uninstallStageLabel }}</span>
</div>
<div class="mt-1.5 w-full h-1.5 bg-white/10 rounded-full overflow-hidden">
<div class="h-full bg-red-400/60 rounded-full animate-pulse w-full"></div>
@@ -251,6 +251,13 @@ const tier = computed(() => {
return 'optional'
})
// Live uninstall stage from backend, with a sensible fallback so the
// label is never blank between WS pushes.
const uninstallStageLabel = computed(() => {
const raw = props.pkg['uninstall-stage']
return raw ? raw : `${t('common.uninstalling')}`
})
const isTransitioning = computed(() => {
const s = props.pkg.state
const h = props.pkg.health