diff --git a/neode-ui/src/views/apps/AppCard.vue b/neode-ui/src/views/apps/AppCard.vue index 1c2469fe..c7543ac6 100644 --- a/neode-ui/src/views/apps/AppCard.vue +++ b/neode-ui/src/views/apps/AppCard.vue @@ -102,17 +102,23 @@ - +
-
- - - - - {{ uninstallStageLabel }} +
+ + + + + + {{ uninstallStageLabel }} + + {{ uninstallProgress }}%
-
-
+
+
@@ -282,6 +288,29 @@ const uninstallStageLabel = computed(() => { 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(() => { + 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 s = props.pkg.state const h = props.pkg.health