feat: signed releases, release feed, notification (read-only)

This commit is contained in:
fsylvain committed 2026-10-01 15:01:53 +02:00
1 parent a49eec909c
commit 834493a4f5
49 files changed
+4191 -18

No files matched your search

+99 -3
View File
@@ -1,8 +1,15 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useToast } from '@nuxt/ui/composables'
import DashboardShell from '@/components/DashboardShell.vue'
import UpdatesCard from '@/components/UpdatesCard.vue'
import { ApiError } from '@/composables/useApi'
import { useRetryAfter } from '@/composables/useRetryAfter'
import { useSettings } from '@/composables/useSettings'
import type { UpdateStatus } from '@/composables/useUpdate'
import { isCheckDue } from '@/lib/updateFormat'
import { useSessionStore } from '@/stores/session'
import { useUpdateStore } from '@/stores/update'
const SKELETON_DELAY_MS = 150
const UNREACHABLE_COPY = "Couldn't reach RenewCMS. Check your connection and try again."
@@ -10,6 +17,10 @@ const UNREACHABLE_COPY = "Couldn't reach RenewCMS. Check your connection and try
type LoadState = 'loading' | 'ready' | 'error'
const { getSettings } = useSettings()
const toast = useToast()
const session = useSessionStore()
const update = useUpdateStore()
const retry = useRetryAfter()
const loadState = ref<LoadState>('loading')
const version = ref('')
@@ -49,13 +60,88 @@ async function loadSettings() {
}
}
onMounted(loadSettings)
function neverCheckedStatus(): UpdateStatus {
return {
current: version.value,
latest: '',
available: false,
release_url: null,
notes: null,
checked_at: null,
can_apply: false,
state: 'idle',
progress: null,
error: null,
started_at: null,
}
}
const updatesStatus = computed(() => {
if (update.status) {
return update.status
}
return update.loadState === 'error' && update.isOwner ? neverCheckedStatus() : null
})
const updatesPhase = computed(() => (update.status ? update.phase : 'failed'))
async function onCheck() {
const result = await update.check()
if (result !== 'forbidden') {
return
}
toast.add({
color: 'error',
icon: 'i-lucide-circle-x',
title: 'Only the owner can do this',
description: 'Ask the owner of this site to update RenewCMS.',
})
await session.refresh()
await update.load()
}
const updateRequested = ref(false)
let autoCheckHandled = false
watch(
() => [session.status, updateRequested.value, update.loadState] as const,
([sessionStatus, requested, loadStateOfUpdate]) => {
if (
autoCheckHandled ||
!requested ||
sessionStatus !== 'ready' ||
loadStateOfUpdate !== 'ready'
) {
return
}
autoCheckHandled = true
if (update.isOwner && isCheckDue(update.status?.checked_at ?? null)) {
onCheck()
}
},
{ immediate: true },
)
watch(
() => update.phase,
(phase) => {
if (phase === 'limited') {
retry.start(update.retryAfter)
}
},
)
onMounted(async () => {
loadSettings()
await update.load()
updateRequested.value = true
})
onBeforeUnmount(clearSkeletonTimer)
</script>
<template>
<DashboardShell>
<div class="flex max-w-180 flex-col gap-5">
<div class="flex max-w-164 flex-col gap-5">
<div class="flex flex-col gap-0.5">
<h1 class="text-xl font-semibold text-highlighted">Settings</h1>
<p class="text-muted">Workspace information.</p>
@@ -106,6 +192,16 @@ onBeforeUnmount(clearSkeletonTimer)
>
</div>
</div>
<UpdatesCard
v-if="updatesStatus"
:status="updatesStatus"
:role="session.session?.role"
:phase="updatesPhase"
:retry-label="retry.formatted.value"
:retry-active="retry.active.value"
@check="onCheck"
/>
</div>
</DashboardShell>
</template>