feat(dashboard): replace recent-failures list with platform health bars + strict failing-source table

This commit is contained in:
2026-04-17 23:44:42 -04:00
parent 7680f03560
commit f809fa552d
+54 -37
View File
@@ -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>