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">
<div
: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" />
<span class="ms-2">
{{ packageCount.building > 0 ? 'Building' : 'Idle' }}
{{
updateFailed
? 'Could not fetch data.'
: packageCount.building > 0
? 'Building'
: 'Idle'
}}
</span>
</v-row>
</v-col>
@@ -33,19 +43,23 @@
class="text-grey v-col-12 v-col-lg-2 mb-3"
cols="auto"
style="font-size: 13px">
Last updated
{{
lastUpdatedSeconds > 59
? rtf.format(-Math.floor(lastUpdatedSeconds / 60), 'minutes')
: rtf.format(-lastUpdatedSeconds, 'seconds')
}}
<template v-if="!updateFailed">
Last updated
{{
lastUpdatedSeconds > 59
? rtf.format(-Math.floor(lastUpdatedSeconds / 60), 'minutes')
: rtf.format(-lastUpdatedSeconds, 'seconds')
}}
</template>
<template v-else>Please try again later.</template>
</v-col>
</v-row>
</v-card-title>
<v-card-text
v-if="packageCount.building > 0 || packageCount.queued > 0"
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-item v-for="(pkg, index) in packages.building" :key="index">
<template v-slot:prepend>
@@ -58,7 +72,7 @@
<v-list-item-subtitle>{{ pkg.arch_version }}</v-list-item-subtitle>
</v-list-item>
</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>
<QueuedPackagesList :packages="packages.queued" />
</v-sheet>
@@ -79,6 +93,7 @@ const UPDATE_INTERVAL_MINUTES = 5
const { mobile } = useDisplay()
const lastUpdatedTime = ref(0)
const lastUpdatedSeconds = ref(0)
const updateFailed = ref(false)
const rtf = new Intl.RelativeTimeFormat('en', {
localeMatcher: 'best fit',
numeric: 'always',
@@ -102,8 +117,10 @@ const packages = ref<{
queued: []
})
const fetchPackages = async (endpoint: string, status?: string) => {
let url = `${BASE_URL}?limit=0&offset=0`
let timerInterval: NodeJS.Timeout // Declare timerInterval outside the function
const fetchPackages = async (offset: string, status?: string) => {
let url = `${BASE_URL}?limit=0&offset=${offset}`
if (status) {
url += `&status=${status}`
}
@@ -114,22 +131,28 @@ const fetchPackages = async (endpoint: string, status?: string) => {
throw new Error(`HTTP error! status: ${response.status}`)
}
const json: Packages = await response.json()
lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = 0 // Reset seconds counter
updateFailed.value = false
return json
} catch (error) {
console.error('Error fetching packages:', error)
updateFailed.value = true
return null
}
}
const getTotalPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=1&offset=0`)
const json = await fetchPackages('1')
if (json) {
packageCount.value.total = json.total
}
}
const getBuiltPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'built')
const json = await fetchPackages('0', 'built')
if (json) {
packageCount.value.built = json.total
packages.value.built = json.packages
@@ -137,7 +160,7 @@ const getBuiltPackages = async () => {
}
const getBuildingPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'building')
const json = await fetchPackages('0', 'building')
if (json) {
packageCount.value.building = json.total
packages.value.building = json.packages
@@ -145,38 +168,44 @@ const getBuildingPackages = async () => {
}
const getQueuedPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'queued')
const json = await fetchPackages('0', 'queued')
if (json) {
packageCount.value.queued = json.total
packages.value.queued = json.packages
}
}
onMounted(() => {
// Function to start the timer
const startTimer = () => {
stopTimer() // Clear any existing timer
getTotalPackages()
getBuiltPackages()
getBuildingPackages()
getQueuedPackages()
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(
timerInterval = setInterval(
() => {
getTotalPackages()
getBuiltPackages()
getBuildingPackages()
getQueuedPackages()
lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 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>
<style lang="scss">
@@ -222,6 +251,27 @@ onMounted(() => {
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 {
transform: translateY(10px); /* Preserves vertical shift as needed */
}