Files
pleno-vue/src/components/release/ReleaseUpdateWidget.vue
T
Jeppe Bundgaard c437c89cfa Add release management enhancements, runtime-aware frontend updates, and extensive test coverage
- Implement release update detection, asset preloading, and frontend version management.
- Add unit and E2E tests for release update workflows, widget behavior, and failure scenarios.
- Introduce new services for handling release ping, error reporting, and update installation workflows.
- Extend i18n for release-related components and error report localization.
- Add `ReleaseFrontendVersionBadge.vue` and related styles to display frontend update statuses.
2026-05-27 13:23:22 +02:00

233 lines
6.0 KiB
Vue

<script setup>
import { computed, onBeforeUnmount, onMounted, watch } from "vue";
import { releaseRuntimeState } from "@/services/releaseTimeline.js";
import {
checkReleaseUpdateNow,
downloadReleaseCandidate,
inspectReleaseRuntimeForUpdate,
installReadyReleaseUpdate,
isSameReleaseCommit,
releaseUpdateState,
shortReleaseCommit,
startReleaseUpdateAutoCheck,
} from "@/services/releaseUpdate.js";
let stopAutoCheck = null;
const updateAvailable = computed(
() =>
Boolean(releaseUpdateState.latestCommit) &&
!isSameReleaseCommit(releaseUpdateState.currentCommit, releaseUpdateState.latestCommit)
);
const isVisible = computed(
() => updateAvailable.value && ["downloading", "ready", "failed"].includes(releaseUpdateState.status)
);
const progress = computed(() => Math.max(0, Math.min(100, Number(releaseUpdateState.progress || 0))));
const progressStyle = computed(() => ({ width: `${progress.value}%` }));
const latestCommitText = computed(() => shortReleaseCommit(releaseUpdateState.latestCommit));
const currentCommitText = computed(() => shortReleaseCommit(releaseUpdateState.currentCommit));
const statusLabel = computed(() => {
if (releaseUpdateState.status === "ready") {
return "Ready";
}
if (releaseUpdateState.status === "failed") {
return "Failed";
}
return "Downloading";
});
const runtimeSignature = computed(() =>
JSON.stringify({
channel: releaseRuntimeState.channel?.slug || "",
commit: releaseRuntimeState.versions?.frontend?.commit_sha || releaseRuntimeState.versions?.frontend?.commit || "",
frontendBaseUrl: releaseRuntimeState.frontendBaseUrl || "",
configured: releaseRuntimeState.availability?.configured !== false,
})
);
const hasRuntimeCandidate = () =>
Boolean(releaseRuntimeState.channel || releaseRuntimeState.frontendBaseUrl || releaseRuntimeState.versions?.frontend);
const inspectCurrentRuntime = () => {
if (!hasRuntimeCandidate()) {
return;
}
void inspectReleaseRuntimeForUpdate(releaseRuntimeState, { autoDownload: true });
};
const retryDownload = () => {
if (releaseUpdateState.candidate) {
void downloadReleaseCandidate(releaseUpdateState.candidate);
return;
}
void checkReleaseUpdateNow();
};
const installUpdate = () => {
void installReadyReleaseUpdate();
};
onMounted(() => {
stopAutoCheck = startReleaseUpdateAutoCheck();
inspectCurrentRuntime();
});
onBeforeUnmount(() => {
stopAutoCheck?.();
stopAutoCheck = null;
});
watch(runtimeSignature, inspectCurrentRuntime, { immediate: true });
</script>
<template>
<section v-if="isVisible" class="release-update-widget" data-testid="release-update-widget">
<div class="release-update-widget__topline">
<span class="release-update-widget__label">{{ statusLabel }}</span>
<span class="release-update-widget__commits">{{ currentCommitText }} -&gt; {{ latestCommitText }}</span>
</div>
<template v-if="releaseUpdateState.status === 'ready'">
<button
type="button"
class="release-update-widget__install"
data-testid="release-update-install"
@click="installUpdate"
>
Install
</button>
</template>
<template v-else-if="releaseUpdateState.status === 'failed'">
<div class="release-update-widget__failed">
<span>{{ releaseUpdateState.error || "Download failed." }}</span>
<button type="button" data-testid="release-update-retry" @click="retryDownload">Retry</button>
</div>
</template>
<template v-else>
<div
class="release-update-widget__progress"
role="progressbar"
:aria-valuenow="progress"
aria-valuemin="0"
aria-valuemax="100"
data-testid="release-update-progress"
>
<span :style="progressStyle"></span>
</div>
<div class="release-update-widget__progress-meta">
<span>{{ progress }}%</span>
<span v-if="releaseUpdateState.totalAssets">
{{ releaseUpdateState.downloadedAssets }}/{{ releaseUpdateState.totalAssets }}
</span>
</div>
</template>
</section>
</template>
<style scoped>
.release-update-widget {
margin: -2px 16px 14px;
padding: 9px 10px;
border: 1px solid #d7e2ef;
border-radius: 5px;
background: #f8fbff;
color: #1f2937;
}
.release-update-widget__topline,
.release-update-widget__progress-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.release-update-widget__label {
color: #10243f;
font-size: 0.72rem;
font-weight: 800;
text-transform: uppercase;
}
.release-update-widget__commits {
min-width: 0;
overflow: hidden;
color: #475569;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-size: 0.68rem;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.release-update-widget__progress {
height: 7px;
margin-top: 8px;
border-radius: 999px;
background: #dbe5f0;
overflow: hidden;
}
.release-update-widget__progress span {
display: block;
height: 100%;
border-radius: inherit;
background: #153554;
transition: width 0.18s ease;
}
.release-update-widget__progress-meta {
margin-top: 5px;
color: #64748b;
font-size: 0.68rem;
font-weight: 700;
}
.release-update-widget__install {
width: 100%;
min-height: 30px;
margin-top: 8px;
border: 0;
border-radius: 4px;
background: #153554;
color: #ffffff;
cursor: pointer;
font-size: 0.78rem;
font-weight: 800;
}
.release-update-widget__install:hover {
background: #10243f;
}
.release-update-widget__failed {
display: grid;
gap: 7px;
margin-top: 7px;
}
.release-update-widget__failed span {
color: #b42318;
font-size: 0.7rem;
font-weight: 700;
line-height: 1.25;
}
.release-update-widget__failed button {
min-height: 28px;
border: 1px solid #f4a3a3;
border-radius: 4px;
background: #fff5f5;
color: #9f1c1c;
cursor: pointer;
font-size: 0.74rem;
font-weight: 800;
}
@media (prefers-reduced-motion: reduce) {
.release-update-widget__progress span {
transition: none;
}
}
</style>