feat(dashboard): replace recent-failures list with platform health bars + strict failing-source table
This commit is contained in:
@@ -238,27 +238,59 @@
|
|||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
Sources Needing Attention
|
Sources Needing Attention
|
||||||
<v-chip class="ml-2" color="error" size="small" v-if="sourcesWithErrors.length">
|
<v-chip class="ml-2" color="error" size="small" v-if="totalFailingCount">
|
||||||
{{ sourcesWithErrors.length }}
|
{{ totalFailingCount }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-table v-if="sourcesWithErrors.length">
|
<!-- Platform Health bars -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="text-caption text-medium-emphasis mb-2">Platform Health</div>
|
||||||
|
<div
|
||||||
|
v-for="row in platformHealth"
|
||||||
|
:key="row.platform"
|
||||||
|
class="d-flex align-center mb-2"
|
||||||
|
>
|
||||||
|
<v-icon :color="getPlatformColor(row.platform)" size="small" class="mr-2">
|
||||||
|
{{ getPlatformIcon(row.platform) }}
|
||||||
|
</v-icon>
|
||||||
|
<div class="text-body-2 platform-label">{{ row.platform }}</div>
|
||||||
|
<v-progress-linear
|
||||||
|
:model-value="row.failing > 0 ? (row.failing / row.total) * 100 : 100"
|
||||||
|
:color="row.failing > 0 ? 'error' : 'success'"
|
||||||
|
height="12"
|
||||||
|
rounded
|
||||||
|
class="mx-3 flex-grow-1"
|
||||||
|
/>
|
||||||
|
<div class="text-caption text-medium-emphasis count-label">
|
||||||
|
{{ row.failing }}/{{ row.total }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!platformHealth.length"
|
||||||
|
class="text-caption text-medium-emphasis text-center py-2"
|
||||||
|
>
|
||||||
|
No enabled sources
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-divider class="my-3" />
|
||||||
|
|
||||||
|
<!-- Failing Sources list -->
|
||||||
|
<v-table v-if="failingSources.length" density="compact">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Source</th>
|
<th>Source</th>
|
||||||
<th>Last Error</th>
|
<th>Consecutive failures</th>
|
||||||
<th>Last Check</th>
|
<th>Last check</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="source in sourcesWithErrors" :key="source.id">
|
<tr v-for="source in failingSources" :key="source.id">
|
||||||
<td>{{ getSourceLabel(source) }}</td>
|
<td>{{ getSourceLabel(source) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<v-chip color="error" size="small">
|
<v-chip color="error" size="small">{{ source.error_count }}</v-chip>
|
||||||
{{ source.last_error_type || 'failed' }}
|
|
||||||
</v-chip>
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ formatDate(source.last_check) }}</td>
|
<td>{{ formatDate(source.last_check) }}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -270,11 +302,7 @@
|
|||||||
>
|
>
|
||||||
Retry
|
Retry
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn size="small" variant="text" to="/subscriptions">
|
||||||
size="small"
|
|
||||||
variant="text"
|
|
||||||
to="/subscriptions"
|
|
||||||
>
|
|
||||||
View
|
View
|
||||||
<v-tooltip activator="parent">View in Subscriptions</v-tooltip>
|
<v-tooltip activator="parent">View in Subscriptions</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -282,10 +310,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
<div v-else class="text-center py-8">
|
<div v-else class="text-caption text-medium-emphasis text-center py-3">
|
||||||
<v-icon size="48" color="secondary" :style="{ opacity: 0.5 }">mdi-check-all</v-icon>
|
No sources in failing state
|
||||||
<div class="text-body-1 mt-2">All sources healthy</div>
|
|
||||||
<div class="text-caption text-medium-emphasis mt-1">No failures in the last 7 days</div>
|
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -357,25 +383,6 @@ const failingSources = computed(() => {
|
|||||||
|
|
||||||
const totalFailingCount = computed(() => failingSources.value.length)
|
const totalFailingCount = computed(() => failingSources.value.length)
|
||||||
|
|
||||||
// Sources with recent failures - derived from recent activity
|
|
||||||
// Only shows sources where a recent download failed (not cumulative error_count)
|
|
||||||
const sourcesWithErrors = computed(() => {
|
|
||||||
const failedDownloads = recentActivity.value.filter(d => d.status === 'failed')
|
|
||||||
// Group by source_id and keep unique sources with their most recent failure
|
|
||||||
const sourceMap = new Map()
|
|
||||||
for (const dl of failedDownloads) {
|
|
||||||
if (dl.source_id && !sourceMap.has(dl.source_id)) {
|
|
||||||
sourceMap.set(dl.source_id, {
|
|
||||||
id: dl.source_id,
|
|
||||||
subscription_name: dl.subscription_name,
|
|
||||||
platform: dl.platform,
|
|
||||||
last_error_type: dl.error_type,
|
|
||||||
last_check: dl.created_at,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(sourceMap.values()).slice(0, 5)
|
|
||||||
})
|
|
||||||
|
|
||||||
const failedCount = computed(() => stats.value?.active_failed || 0)
|
const failedCount = computed(() => stats.value?.active_failed || 0)
|
||||||
const activeDownloadsCount = computed(() =>
|
const activeDownloadsCount = computed(() =>
|
||||||
@@ -640,4 +647,14 @@ async function retrySource(source) {
|
|||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0.4; }
|
50% { opacity: 0.4; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.platform-label {
|
||||||
|
min-width: 110px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-label {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user