fix(ui): truthful uninstall progress bar (was a solid full-red block)
AppCard's uninstall bar was hardcoded `w-full bg-red-400/60 animate-pulse` — a solid, full-width, red, fake-pulsing block that never moved and read as an error, no matter the actual teardown progress (the install bar, by contrast, renders a real percentage). Derive a truthful percentage from the backend's existing `uninstall-stage` label — "Stopping containers (X/N)" → 10–50%, "Cleaning up volumes" → 70%, "Removing app data" → 90% — and render it exactly like install: neutral fill, real width + percent, shimmer (not a fake pulse) carrying motion when a stage has no number. Frontend-only; the backend already broadcasts these stages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
67426c0d41
commit
9f17ba6867
@ -102,17 +102,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Uninstalling progress — live stage label from backend -->
|
<!-- Uninstalling progress — truthful stage-driven bar (mirrors install) -->
|
||||||
<div v-else-if="isUninstalling" class="mt-4">
|
<div v-else-if="isUninstalling" class="mt-4">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center justify-between mb-1.5">
|
||||||
<svg class="animate-spin h-3 w-3 text-red-400" fill="none" viewBox="0 0 24 24">
|
<span class="text-xs text-white/70 flex items-center gap-1.5">
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
|
||||||
<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>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
</svg>
|
<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>
|
||||||
<span class="text-xs text-red-300 truncate">{{ uninstallStageLabel }}</span>
|
</svg>
|
||||||
|
{{ uninstallStageLabel }}
|
||||||
|
</span>
|
||||||
|
<span v-if="uninstallProgress !== null" class="text-xs text-white/50">{{ uninstallProgress }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1.5 w-full h-1.5 bg-white/10 rounded-full overflow-hidden">
|
<div class="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>
|
<div
|
||||||
|
class="install-progress-fill h-full bg-white/60 rounded-full transition-all duration-500"
|
||||||
|
:style="{ width: `${Math.max(uninstallProgress ?? 8, 4)}%` }"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -282,6 +288,29 @@ const uninstallStageLabel = computed(() => {
|
|||||||
return raw ? raw : `${t('common.uninstalling')}…`
|
return raw ? raw : `${t('common.uninstalling')}…`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Map the backend's uninstall-stage label to a truthful percentage so the bar
|
||||||
|
// progresses through the teardown instead of sitting at a solid full(-red)
|
||||||
|
// block. Backend stages (set_uninstall_stage):
|
||||||
|
// "Stopping containers (X/N)" → 10–50% (linear over the stack)
|
||||||
|
// "Cleaning up volumes" → 70%
|
||||||
|
// "Removing app data" → 90%
|
||||||
|
// Unknown/between pushes → null → the bar parks low and the shimmer overlay
|
||||||
|
// (install-progress-fill) carries the motion, exactly like a fixed install phase.
|
||||||
|
const uninstallProgress = computed<number | null>(() => {
|
||||||
|
const raw = props.pkg['uninstall-stage'] || ''
|
||||||
|
const m = raw.match(/\((\d+)\s*\/\s*(\d+)\)/)
|
||||||
|
if (m) {
|
||||||
|
const done = Number(m[1])
|
||||||
|
const total = Number(m[2])
|
||||||
|
if (total > 0) {
|
||||||
|
return Math.round(10 + Math.min(done / total, 1) * 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (/volume/i.test(raw)) return 70
|
||||||
|
if (/data/i.test(raw)) return 90
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
const isTransitioning = computed(() => {
|
const isTransitioning = computed(() => {
|
||||||
const s = props.pkg.state
|
const s = props.pkg.state
|
||||||
const h = props.pkg.health
|
const h = props.pkg.health
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user