Files
Aether/frontend/src/features/providers/components/HealthRelatedMonitorCard.vue
T
AAEE86 86f72da3d9 feat(health): 增加历史状态条指标 Tooltip
- 为健康监控时间轴返回 timeline_details 分段指标
- Hover 历史状态柱时展示总请求/成功/失败/可用率/状态
- 展示平均耗时/TTFB/速度和完整时间范围
- 修复历史状态柱 Tooltip 触发区域不可用的问题
- 补齐前端类型、详情抽屉透传和 mock 数据
2026-06-02 10:36:54 +08:00

134 lines
3.7 KiB
Vue

<template>
<div class="relative overflow-hidden rounded-xl border border-border/60 bg-card/60 p-4 transition-colors hover:border-primary/50">
<div class="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-primary/40 to-transparent" />
<div class="flex items-start justify-between gap-3">
<div class="flex min-w-0 items-center gap-3">
<div class="flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-xl border border-border/60 bg-muted/50">
<component
:is="iconComponent"
class="h-5 w-5 text-muted-foreground"
/>
</div>
<div class="min-w-0">
<h4 class="truncate text-sm font-semibold">
{{ monitor.display_name || monitor.key }}
</h4>
<p
v-if="monitor.meta_text"
class="mt-1 truncate text-xs text-muted-foreground"
>
{{ monitor.meta_text }}
</p>
</div>
</div>
<Badge
:variant="getHealthBadgeVariant(monitor)"
class="shrink-0"
>
{{ getHealthLabel(monitor) }}
</Badge>
</div>
<HealthMetricGrid
class="mt-4"
:avg-latency-ms="monitor.avg_latency_ms"
:avg-first-byte-ms="monitor.avg_first_byte_ms"
:avg-tps="monitor.avg_tps"
:total-attempts="monitor.total_attempts"
:success-rate="monitor.success_rate"
/>
<div class="mt-4 flex items-center justify-between gap-3 text-[11px] uppercase tracking-wide text-muted-foreground">
<span>History (60pts)</span>
<span class="truncate normal-case tracking-normal">
{{ metaText }}
</span>
</div>
<HealthStatusTimeline
class="mt-2"
:timeline="monitor.timeline"
:timeline-details="monitor.timeline_details"
:time-range-start="monitor.time_range_start"
:time-range-end="monitor.time_range_end"
:generated-at="generatedAt"
:lookback-hours="lookbackHours"
:entity-label="entityLabel"
:entity-name="monitor.key"
/>
<div
v-if="showDetailButton"
class="mt-4 flex justify-end"
>
<Button
variant="outline"
size="sm"
@click="$emit('viewDetails', monitor)"
>
查看详情
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Activity, Bot, Server } from 'lucide-vue-next'
import Badge from '@/components/ui/badge.vue'
import Button from '@/components/ui/button.vue'
import type { HealthRelatedMonitor } from '@/api/endpoints/types'
import HealthMetricGrid from './HealthMetricGrid.vue'
import HealthStatusTimeline from './HealthStatusTimeline.vue'
import {
formatCompactNumber,
getHealthBadgeVariant,
getHealthLabel
} from './health-monitor-utils'
const props = withDefaults(defineProps<{
monitor: HealthRelatedMonitor
lookbackHours: number
generatedAt?: string | null
showDetailButton?: boolean
}>(), {
generatedAt: null,
showDetailButton: true
})
defineEmits<{
viewDetails: [monitor: HealthRelatedMonitor]
}>()
const iconComponent = computed(() => {
switch (props.monitor.kind) {
case 'model':
return Bot
case 'provider':
return Server
default:
return Activity
}
})
const entityLabel = computed(() => {
switch (props.monitor.kind) {
case 'model':
return '模型'
case 'provider':
return '提供商'
default:
return '端点'
}
})
const metaText = computed(() => {
const attempts = `${formatCompactNumber(props.monitor.total_attempts)} 次请求`
if (props.monitor.meta_text?.includes('次请求')) return props.monitor.meta_text
return props.monitor.meta_text
? `${props.monitor.meta_text} / ${attempts}`
: attempts
})
</script>