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