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:
@@ -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">
|
||||
<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 */
|
||||
}
|
||||
|
Reference in New Issue
Block a user