mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
refactor(health): add dashboard overview and related drill-down
- Replace health monitor tabs with a dashboard layout - Add related health drill-down for endpoint, model, and provider cards - Render provider health as cards and hide empty monitors
This commit is contained in:
@@ -79,6 +79,16 @@
|
||||
entity-label="模型"
|
||||
:entity-name="monitor.model"
|
||||
/>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="openDetails(monitor)"
|
||||
>
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,6 +100,7 @@ import { ref, onMounted, watch } from "vue";
|
||||
import { Bot, Loader2 } from "lucide-vue-next";
|
||||
import Card from "@/components/ui/card.vue";
|
||||
import Badge from "@/components/ui/badge.vue";
|
||||
import Button from "@/components/ui/button.vue";
|
||||
import HealthMetricGrid from "./HealthMetricGrid.vue";
|
||||
import HealthMonitorHeader from "./HealthMonitorHeader.vue";
|
||||
import HealthStatusTimeline from "./HealthStatusTimeline.vue";
|
||||
@@ -100,10 +111,15 @@ import {
|
||||
import type { ModelStatusMonitor } from "@/api/endpoints/types";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { parseApiError } from "@/utils/errorParser";
|
||||
import type {
|
||||
HealthMonitorDetailTarget,
|
||||
HealthMonitorSectionSummary,
|
||||
} from "./health-monitor-utils";
|
||||
import {
|
||||
formatCompactNumber,
|
||||
getHealthBadgeVariant,
|
||||
getHealthLabel,
|
||||
summarizeHealthMonitorItems,
|
||||
} from "./health-monitor-utils";
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -119,6 +135,11 @@ const props = withDefaults(
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
viewDetails: [target: HealthMonitorDetailTarget];
|
||||
summaryUpdated: [summary: HealthMonitorSectionSummary];
|
||||
}>();
|
||||
|
||||
const { error: showError } = useToast();
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -141,6 +162,7 @@ async function loadMonitors() {
|
||||
: await getPublicModelStatusMonitor(params);
|
||||
monitors.value = data.models || [];
|
||||
generatedAt.value = data.generated_at || null;
|
||||
emitSummary();
|
||||
} catch (err: unknown) {
|
||||
showError(parseApiError(err, "加载模型健康监控数据失败"), "错误");
|
||||
} finally {
|
||||
@@ -165,6 +187,39 @@ function getModelMetaText(monitor: ModelStatusMonitor) {
|
||||
return attempts;
|
||||
}
|
||||
|
||||
function openDetails(monitor: ModelStatusMonitor) {
|
||||
emit("viewDetails", {
|
||||
lookbackHours: parseInt(lookbackHours.value),
|
||||
source: {
|
||||
kind: "model",
|
||||
value: monitor.model,
|
||||
title: monitor.display_name || monitor.model,
|
||||
metaText: getModelDetailMetaText(monitor),
|
||||
totalAttempts: monitor.total_attempts,
|
||||
successCount: monitor.success_count,
|
||||
failedCount: monitor.failed_count,
|
||||
successRate: monitor.success_rate,
|
||||
avgLatencyMs: monitor.avg_latency_ms,
|
||||
avgFirstByteMs: monitor.avg_first_byte_ms,
|
||||
avgTps: monitor.avg_tps,
|
||||
timeline: monitor.timeline || null,
|
||||
timeRangeStart: monitor.time_range_start || null,
|
||||
timeRangeEnd: monitor.time_range_end || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getModelDetailMetaText(monitor: ModelStatusMonitor) {
|
||||
if (props.showProviderInfo && typeof monitor.provider_count === "number") {
|
||||
return `${monitor.provider_count} 个提供商`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function emitSummary() {
|
||||
emit("summaryUpdated", summarizeHealthMonitorItems(monitors.value));
|
||||
}
|
||||
|
||||
watch(lookbackHours, () => {
|
||||
loadMonitors();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user