Handle package data fetch errors and improve UI feedback.

Added error handling for package data fetches with visual cues to indicate failures (e.g., red pulsating circle). Updated UI text to prompt retries on failures and refactored timers for better synchronization. Improved code readability by removing redundant URL formatting.
This commit is contained in:
2025-04-10 22:48:13 +02:00
parent 8fe8f1e122
commit 265bfac74a

View File

@@ -11,11 +11,21 @@
<v-row :class="mobile ? 'mt-1' : ''" class="flex-nowrap ms-1 d-flex align-items-center"> <v-row :class="mobile ? 'mt-1' : ''" class="flex-nowrap ms-1 d-flex align-items-center">
<div <div
:class=" :class="
packageCount.building > 0 ? 'pulsating-circle-amber' : 'pulsating-circle-green' updateFailed
? 'pulsating-circle-error'
: packageCount.building > 0
? 'pulsating-circle-amber'
: 'pulsating-circle-green'
" "
class="circle-offset flex-circle" /> class="circle-offset flex-circle" />
<span class="ms-2"> <span class="ms-2">
{{ packageCount.building > 0 ? 'Building' : 'Idle' }} {{
updateFailed
? 'Could not fetch data.'
: packageCount.building > 0
? 'Building'
: 'Idle'
}}
</span> </span>
</v-row> </v-row>
</v-col> </v-col>
@@ -33,19 +43,23 @@
class="text-grey v-col-12 v-col-lg-2 mb-3" class="text-grey v-col-12 v-col-lg-2 mb-3"
cols="auto" cols="auto"
style="font-size: 13px"> style="font-size: 13px">
Last updated <template v-if="!updateFailed">
{{ Last updated
lastUpdatedSeconds > 59 {{
? rtf.format(-Math.floor(lastUpdatedSeconds / 60), 'minutes') lastUpdatedSeconds > 59
: rtf.format(-lastUpdatedSeconds, 'seconds') ? rtf.format(-Math.floor(lastUpdatedSeconds / 60), 'minutes')
}} : rtf.format(-lastUpdatedSeconds, 'seconds')
}}
</template>
<template v-else>Please try again later.</template>
</v-col> </v-col>
</v-row> </v-row>
</v-card-title> </v-card-title>
<v-card-text <v-card-text
v-if="packageCount.building > 0 || packageCount.queued > 0" v-if="packageCount.building > 0 || packageCount.queued > 0"
class="d-flex flex-column"> class="d-flex flex-column">
<v-list width="100%"> <v-list v-if="packageCount.building > 0" class="mb-4" width="100%">
<v-list-subheader>Building</v-list-subheader> <v-list-subheader>Building</v-list-subheader>
<v-list-item v-for="(pkg, index) in packages.building" :key="index"> <v-list-item v-for="(pkg, index) in packages.building" :key="index">
<template v-slot:prepend> <template v-slot:prepend>
@@ -58,7 +72,7 @@
<v-list-item-subtitle>{{ pkg.arch_version }}</v-list-item-subtitle> <v-list-item-subtitle>{{ pkg.arch_version }}</v-list-item-subtitle>
</v-list-item> </v-list-item>
</v-list> </v-list>
<v-sheet class="ps-4 mt-4" color="transparent" rounded width="100%"> <v-sheet class="ps-4" color="transparent" rounded width="100%">
<h4 class="mb-2 font-weight-light text-grey">Queued</h4> <h4 class="mb-2 font-weight-light text-grey">Queued</h4>
<QueuedPackagesList :packages="packages.queued" /> <QueuedPackagesList :packages="packages.queued" />
</v-sheet> </v-sheet>
@@ -79,6 +93,7 @@ const UPDATE_INTERVAL_MINUTES = 5
const { mobile } = useDisplay() const { mobile } = useDisplay()
const lastUpdatedTime = ref(0) const lastUpdatedTime = ref(0)
const lastUpdatedSeconds = ref(0) const lastUpdatedSeconds = ref(0)
const updateFailed = ref(false)
const rtf = new Intl.RelativeTimeFormat('en', { const rtf = new Intl.RelativeTimeFormat('en', {
localeMatcher: 'best fit', localeMatcher: 'best fit',
numeric: 'always', numeric: 'always',
@@ -102,8 +117,10 @@ const packages = ref<{
queued: [] queued: []
}) })
const fetchPackages = async (endpoint: string, status?: string) => { let timerInterval: NodeJS.Timeout // Declare timerInterval outside the function
let url = `${BASE_URL}?limit=0&offset=0`
const fetchPackages = async (offset: string, status?: string) => {
let url = `${BASE_URL}?limit=0&offset=${offset}`
if (status) { if (status) {
url += `&status=${status}` url += `&status=${status}`
} }
@@ -114,22 +131,28 @@ const fetchPackages = async (endpoint: string, status?: string) => {
throw new Error(`HTTP error! status: ${response.status}`) throw new Error(`HTTP error! status: ${response.status}`)
} }
const json: Packages = await response.json() const json: Packages = await response.json()
lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = 0 // Reset seconds counter
updateFailed.value = false
return json return json
} catch (error) { } catch (error) {
console.error('Error fetching packages:', error) console.error('Error fetching packages:', error)
updateFailed.value = true
return null return null
} }
} }
const getTotalPackages = async () => { const getTotalPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=1&offset=0`) const json = await fetchPackages('1')
if (json) { if (json) {
packageCount.value.total = json.total packageCount.value.total = json.total
} }
} }
const getBuiltPackages = async () => { const getBuiltPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'built') const json = await fetchPackages('0', 'built')
if (json) { if (json) {
packageCount.value.built = json.total packageCount.value.built = json.total
packages.value.built = json.packages packages.value.built = json.packages
@@ -137,7 +160,7 @@ const getBuiltPackages = async () => {
} }
const getBuildingPackages = async () => { const getBuildingPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'building') const json = await fetchPackages('0', 'building')
if (json) { if (json) {
packageCount.value.building = json.total packageCount.value.building = json.total
packages.value.building = json.packages packages.value.building = json.packages
@@ -145,38 +168,44 @@ const getBuildingPackages = async () => {
} }
const getQueuedPackages = async () => { const getQueuedPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'queued') const json = await fetchPackages('0', 'queued')
if (json) { if (json) {
packageCount.value.queued = json.total packageCount.value.queued = json.total
packages.value.queued = json.packages packages.value.queued = json.packages
} }
} }
onMounted(() => { // Function to start the timer
const startTimer = () => {
stopTimer() // Clear any existing timer
getTotalPackages() getTotalPackages()
getBuiltPackages() getBuiltPackages()
getBuildingPackages() getBuildingPackages()
getQueuedPackages() getQueuedPackages()
timerInterval = setInterval(
lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
setInterval(() => {
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
}, 1000)
setInterval(
() => { () => {
getTotalPackages() getTotalPackages()
getBuiltPackages() getBuiltPackages()
getBuildingPackages() getBuildingPackages()
getQueuedPackages() getQueuedPackages()
lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
}, },
UPDATE_INTERVAL_MINUTES * 60 * 1000 UPDATE_INTERVAL_MINUTES * 60 * 1000
) )
}
// Function to stop the timer
const stopTimer = () => {
clearInterval(timerInterval)
}
onMounted(() => {
startTimer() // Start the timer when the component is mounted
}) })
// Update seconds counter every second
setInterval(() => {
lastUpdatedSeconds.value++
}, 1000)
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -222,6 +251,27 @@ onMounted(() => {
animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite; animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
} }
.pulsating-circle-error {
background-color: rgba(225, 64, 6, 0.94);
border-radius: 50%;
width: 12px;
height: 12px;
}
.pulsating-circle-error:before {
content: '';
display: block;
width: 200%;
height: 200%;
box-sizing: border-box;
margin-left: -50%;
margin-top: -50%;
border-radius: 50%;
background-color: rgba(225, 64, 6, 0.94);
-webkit-animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
}
.circle-offset { .circle-offset {
transform: translateY(10px); /* Preserves vertical shift as needed */ transform: translateY(10px); /* Preserves vertical shift as needed */
} }