fix: LiveStats: show hard error when Ponder is unreachable (not silent skeletons) (#201)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4125efc8f0
commit
3426fbf80b
1 changed files with 24 additions and 2 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!error && stats" class="live-stats">
|
<div v-if="stats" class="live-stats">
|
||||||
<div class="live-header">
|
<div class="live-header">
|
||||||
<span class="live-dot" :class="{ 'live-dot-error': error }"></span>
|
<span class="live-dot" :class="{ 'live-dot-error': error }"></span>
|
||||||
<span class="live-text">Live</span>
|
<span class="live-text">Live</span>
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="freshness">{{ error ? 'Connection lost' : `Updated ${secondsSinceUpdate}s ago` }}</div>
|
<div class="freshness">{{ error ? 'Connection lost' : `Updated ${secondsSinceUpdate}s ago` }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!error && !stats" class="live-stats">
|
<div v-else-if="!error" class="live-stats">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-item skeleton" v-for="i in 6" :key="i">
|
<div class="stat-item skeleton" v-for="i in 6" :key="i">
|
||||||
<div class="stat-label skeleton-text"></div>
|
<div class="stat-label skeleton-text"></div>
|
||||||
|
|
@ -54,6 +54,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="live-stats live-stats-error" data-testid="livestats-error">
|
||||||
|
<p class="error-message">Protocol data unavailable</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -88,6 +91,8 @@ const changedStats = ref<Set<string>>(new Set());
|
||||||
const secondsSinceUpdate = ref(0);
|
const secondsSinceUpdate = ref(0);
|
||||||
let freshnessTicker: number | null = null;
|
let freshnessTicker: number | null = null;
|
||||||
let changeTimeout: number | null = null;
|
let changeTimeout: number | null = null;
|
||||||
|
let loadingTimer: number | null = null;
|
||||||
|
const LOADING_TIMEOUT_MS = 10_000; // escalate to error after 10s with no data
|
||||||
|
|
||||||
// Helper: safely convert a Wei BigInt string to ETH float
|
// Helper: safely convert a Wei BigInt string to ETH float
|
||||||
function weiToEth(wei: string | null | undefined): number {
|
function weiToEth(wei: string | null | undefined): number {
|
||||||
|
|
@ -417,6 +422,7 @@ async function fetchStats() {
|
||||||
stats.value = s;
|
stats.value = s;
|
||||||
secondsSinceUpdate.value = 0;
|
secondsSinceUpdate.value = 0;
|
||||||
error.value = false;
|
error.value = false;
|
||||||
|
if (loadingTimer) { clearTimeout(loadingTimer); loadingTimer = null; }
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No stats data');
|
throw new Error('No stats data');
|
||||||
}
|
}
|
||||||
|
|
@ -428,6 +434,10 @@ async function fetchStats() {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// Escalate to error state if no data arrives within the timeout window
|
||||||
|
loadingTimer = window.setTimeout(() => {
|
||||||
|
if (!stats.value) error.value = true;
|
||||||
|
}, LOADING_TIMEOUT_MS);
|
||||||
await fetchStats();
|
await fetchStats();
|
||||||
fetchEthPrice();
|
fetchEthPrice();
|
||||||
refreshInterval = window.setInterval(fetchStats, 30000); // Refresh every 30 seconds
|
refreshInterval = window.setInterval(fetchStats, 30000); // Refresh every 30 seconds
|
||||||
|
|
@ -440,6 +450,7 @@ onUnmounted(() => {
|
||||||
if (ethPriceInterval) clearInterval(ethPriceInterval);
|
if (ethPriceInterval) clearInterval(ethPriceInterval);
|
||||||
if (freshnessTicker) clearInterval(freshnessTicker);
|
if (freshnessTicker) clearInterval(freshnessTicker);
|
||||||
if (changeTimeout) clearTimeout(changeTimeout);
|
if (changeTimeout) clearTimeout(changeTimeout);
|
||||||
|
if (loadingTimer) clearTimeout(loadingTimer);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -610,4 +621,15 @@ onUnmounted(() => {
|
||||||
background-position: 200% 0
|
background-position: 200% 0
|
||||||
100%
|
100%
|
||||||
background-position: -200% 0
|
background-position: -200% 0
|
||||||
|
|
||||||
|
.live-stats-error
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
|
||||||
|
.error-message
|
||||||
|
font-size: 14px
|
||||||
|
color: #f87171
|
||||||
|
text-align: center
|
||||||
|
letter-spacing: 0.3px
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue