chore: release v1.7.86-alpha
This commit is contained in:
@@ -69,12 +69,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-3">
|
||||
<div class="flex flex-col gap-3 sm:flex-row">
|
||||
<!-- Git path: one-shot pull+rebuild+restart -->
|
||||
<button
|
||||
v-if="updateMethod === 'git' && !applying"
|
||||
@click="requestGitApply"
|
||||
class="glass-button rounded-lg px-6 py-2 text-sm font-medium bg-orange-500/20 border-orange-400/30"
|
||||
class="glass-button w-full rounded-lg px-6 py-2 text-sm font-medium bg-orange-500/20 border-orange-400/30 sm:w-auto"
|
||||
>
|
||||
{{ t('systemUpdate.pullAndRebuild') }}
|
||||
</button>
|
||||
@@ -82,14 +82,14 @@
|
||||
<button
|
||||
v-if="updateMethod !== 'git' && !downloading && !applying && !downloaded"
|
||||
@click="downloadUpdate"
|
||||
class="glass-button rounded-lg px-6 py-2 text-sm font-medium"
|
||||
class="glass-button w-full rounded-lg px-6 py-2 text-sm font-medium sm:w-auto"
|
||||
>
|
||||
{{ t('systemUpdate.downloadUpdate') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="updateMethod !== 'git' && downloaded && !applying"
|
||||
@click="requestApply"
|
||||
class="glass-button rounded-lg px-6 py-2 text-sm font-medium bg-orange-500/20 border-orange-400/30"
|
||||
class="glass-button w-full rounded-lg px-6 py-2 text-sm font-medium bg-orange-500/20 border-orange-400/30 sm:w-auto"
|
||||
>
|
||||
{{ t('systemUpdate.applyUpdate') }}
|
||||
</button>
|
||||
@@ -145,8 +145,16 @@
|
||||
<!-- Applying -->
|
||||
<div v-if="applying" class="glass-card p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">{{ t('systemUpdate.applying') }}</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-5 h-5 border-2 border-orange-400 border-t-transparent rounded-full animate-spin"></div>
|
||||
<div class="flex flex-col items-start gap-3 sm:flex-row sm:items-center">
|
||||
<svg
|
||||
class="w-5 h-5 shrink-0 animate-spin text-orange-400"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" stroke-opacity="0.2"></circle>
|
||||
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||
</svg>
|
||||
<p class="text-sm text-white/70">{{ t('systemUpdate.applyWarning') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -262,22 +270,25 @@
|
||||
<!-- Actions row -->
|
||||
<div class="glass-card p-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">{{ t('systemUpdate.actions') }}</h2>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap">
|
||||
<button
|
||||
@click="checkForUpdates"
|
||||
:disabled="loading"
|
||||
class="glass-button rounded-lg px-5 py-2 text-sm font-medium disabled:opacity-40"
|
||||
class="glass-button w-full rounded-lg px-5 py-2 text-sm font-medium disabled:opacity-40 sm:w-auto"
|
||||
>
|
||||
{{ loading ? t('systemUpdate.checking') : t('systemUpdate.checkForUpdates') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="rollbackAvailable"
|
||||
@click="requestRollback"
|
||||
class="glass-button rounded-lg px-5 py-2 text-sm font-medium bg-red-500/10 border-red-400/20"
|
||||
class="glass-button w-full rounded-lg px-5 py-2 text-sm font-medium bg-red-500/10 border-red-400/20 sm:w-auto"
|
||||
>
|
||||
{{ t('systemUpdate.rollback') }}
|
||||
</button>
|
||||
<RouterLink to="/dashboard/settings" class="glass-button rounded-lg px-5 py-2 text-sm font-medium text-center">
|
||||
<RouterLink
|
||||
to="/dashboard/settings"
|
||||
class="glass-button w-full rounded-lg px-5 py-2 text-sm font-medium text-center sm:w-auto"
|
||||
>
|
||||
{{ t('systemUpdate.backToSettings') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
@@ -650,12 +661,28 @@ const installStartedAt = ref<number>(0)
|
||||
const installElapsedSec = ref(0)
|
||||
let installPollTimer: ReturnType<typeof setInterval> | null = null
|
||||
let installElapsedTimer: ReturnType<typeof setInterval> | null = null
|
||||
let installReadyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const installElapsedLabel = computed(() => {
|
||||
const s = installElapsedSec.value
|
||||
if (s < 60) return `Elapsed: ${s}s`
|
||||
return `Elapsed: ${Math.floor(s / 60)}m${s % 60 < 10 ? '0' : ''}${s % 60}s`
|
||||
})
|
||||
function clearInstallTimers() {
|
||||
if (installPollTimer) {
|
||||
clearInterval(installPollTimer)
|
||||
installPollTimer = null
|
||||
}
|
||||
if (installElapsedTimer) {
|
||||
clearInterval(installElapsedTimer)
|
||||
installElapsedTimer = null
|
||||
}
|
||||
if (installReadyTimer) {
|
||||
clearTimeout(installReadyTimer)
|
||||
installReadyTimer = null
|
||||
}
|
||||
}
|
||||
function startInstallOverlay(targetVersion: string) {
|
||||
clearInstallTimers()
|
||||
installing.value = true
|
||||
installStage.value = 'applying'
|
||||
installTargetVersion.value = targetVersion
|
||||
@@ -672,7 +699,7 @@ function startInstallOverlay(targetVersion: string) {
|
||||
// Start polling /health after a short delay — the backend restarts 2s
|
||||
// after replying to update.apply, so an immediate poll would see the
|
||||
// old backend and conclude nothing happened.
|
||||
setTimeout(() => {
|
||||
installReadyTimer = setTimeout(() => {
|
||||
installStage.value = 'restarting'
|
||||
installPollTimer = setInterval(pollHealth, 1500)
|
||||
}, 2500)
|
||||
@@ -687,22 +714,37 @@ async function pollHealth() {
|
||||
installStage.value = 'ready'
|
||||
if (installPollTimer) { clearInterval(installPollTimer); installPollTimer = null }
|
||||
// Brief pause so the user sees the "Ready" state before the reload.
|
||||
setTimeout(() => { window.location.reload() }, 1200)
|
||||
installReadyTimer = setTimeout(() => { window.location.reload() }, 1200)
|
||||
} else {
|
||||
// Backend is up but still reporting the old version — frontend
|
||||
// and backend are mid-swap. Signal to the user.
|
||||
installStage.value = 'reconnecting'
|
||||
void confirmBackendUpdateSettled()
|
||||
}
|
||||
} catch {
|
||||
// Fetch fails while the server is mid-restart. Stay in 'restarting'.
|
||||
}
|
||||
}
|
||||
async function confirmBackendUpdateSettled() {
|
||||
try {
|
||||
const res = await rpcClient.call<{
|
||||
current_version: string
|
||||
update_in_progress: boolean
|
||||
}>({ method: 'update.status' })
|
||||
if (!res.update_in_progress && installStage.value !== 'ready' && installStage.value !== 'stalled') {
|
||||
installStage.value = 'ready'
|
||||
if (installPollTimer) { clearInterval(installPollTimer); installPollTimer = null }
|
||||
installReadyTimer = setTimeout(() => { window.location.reload() }, 800)
|
||||
}
|
||||
} catch {
|
||||
// Keep waiting on /health.
|
||||
}
|
||||
}
|
||||
function reloadNow() { window.location.reload() }
|
||||
// Cleanup if the component is torn down mid-install (unlikely but safe).
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
onBeforeUnmount(() => {
|
||||
if (installPollTimer) clearInterval(installPollTimer)
|
||||
if (installElapsedTimer) clearInterval(installElapsedTimer)
|
||||
clearInstallTimers()
|
||||
})
|
||||
|
||||
const lastCheckDisplay = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user