feat(frontend): display error observability fields in ops admin panel
Show endpoint, model mapping, and request type in the ops error log table and detail modal: - Endpoint column with inbound/upstream tooltip - Model column showing requested→upstream mapping with arrow - Request type badge (sync/stream/ws) in status column - New detail cards for inbound endpoint, upstream endpoint, request type
This commit is contained in:
@@ -969,6 +969,10 @@ export interface OpsErrorLog {
|
|||||||
client_ip?: string | null
|
client_ip?: string | null
|
||||||
request_path?: string
|
request_path?: string
|
||||||
stream?: boolean
|
stream?: boolean
|
||||||
|
|
||||||
|
// Model mapping context for ops error observability
|
||||||
|
requested_model?: string
|
||||||
|
upstream_model?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpsErrorDetail extends OpsErrorLog {
|
export interface OpsErrorDetail extends OpsErrorLog {
|
||||||
|
|||||||
@@ -59,7 +59,14 @@
|
|||||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||||
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.model') }}</div>
|
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.model') }}</div>
|
||||||
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
|
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
|
||||||
{{ detail.model || '—' }}
|
<template v-if="hasModelMapping(detail)">
|
||||||
|
<span class="font-mono">{{ detail.requested_model }}</span>
|
||||||
|
<span class="mx-1 text-gray-400">→</span>
|
||||||
|
<span class="font-mono text-primary-600 dark:text-primary-400">{{ detail.upstream_model }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ displayModel(detail) || '—' }}
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -213,6 +220,22 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean {
|
|||||||
return phase === 'upstream' && owner === 'provider'
|
return phase === 'upstream' && owner === 'provider'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasModelMapping(d: OpsErrorDetail | null): boolean {
|
||||||
|
if (!d) return false
|
||||||
|
const requested = String(d.requested_model || '').trim()
|
||||||
|
const upstream = String(d.upstream_model || '').trim()
|
||||||
|
return !!requested && !!upstream && requested !== upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayModel(d: OpsErrorDetail | null): string {
|
||||||
|
if (!d) return ''
|
||||||
|
const upstream = String(d.upstream_model || '').trim()
|
||||||
|
if (upstream) return upstream
|
||||||
|
const requested = String(d.requested_model || '').trim()
|
||||||
|
if (requested) return requested
|
||||||
|
return String(d.model || '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
const correlatedUpstream = ref<OpsErrorDetail[]>([])
|
const correlatedUpstream = ref<OpsErrorDetail[]>([])
|
||||||
const correlatedUpstreamLoading = ref(false)
|
const correlatedUpstreamLoading = ref(false)
|
||||||
|
|
||||||
|
|||||||
@@ -83,11 +83,22 @@
|
|||||||
|
|
||||||
<!-- Model -->
|
<!-- Model -->
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
<div class="max-w-[120px] truncate" :title="log.model">
|
<div class="max-w-[180px]">
|
||||||
<span v-if="log.model" class="font-mono text-[11px] text-gray-700 dark:text-gray-300">
|
<template v-if="hasModelMapping(log)">
|
||||||
{{ log.model }}
|
<el-tooltip :content="modelMappingTooltip(log)" placement="top" :show-after="500">
|
||||||
|
<span class="flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
|
||||||
|
<span class="truncate">{{ log.requested_model }}</span>
|
||||||
|
<span class="flex-shrink-0 text-gray-400">→</span>
|
||||||
|
<span class="truncate text-primary-600 dark:text-primary-400">{{ log.upstream_model }}</span>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span v-if="displayModel(log)" class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300" :title="displayModel(log)">
|
||||||
|
{{ displayModel(log) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="text-xs text-gray-400">-</span>
|
<span v-else class="text-xs text-gray-400">-</span>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -193,6 +204,28 @@ function isUpstreamRow(log: OpsErrorLog): boolean {
|
|||||||
return phase === 'upstream' && owner === 'provider'
|
return phase === 'upstream' && owner === 'provider'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasModelMapping(log: OpsErrorLog): boolean {
|
||||||
|
const requested = String(log.requested_model || '').trim()
|
||||||
|
const upstream = String(log.upstream_model || '').trim()
|
||||||
|
return !!requested && !!upstream && requested !== upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
function modelMappingTooltip(log: OpsErrorLog): string {
|
||||||
|
const requested = String(log.requested_model || '').trim()
|
||||||
|
const upstream = String(log.upstream_model || '').trim()
|
||||||
|
if (!requested && !upstream) return ''
|
||||||
|
if (requested && upstream) return `${requested} → ${upstream}`
|
||||||
|
return upstream || requested
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayModel(log: OpsErrorLog): string {
|
||||||
|
const upstream = String(log.upstream_model || '').trim()
|
||||||
|
if (upstream) return upstream
|
||||||
|
const requested = String(log.requested_model || '').trim()
|
||||||
|
if (requested) return requested
|
||||||
|
return String(log.model || '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
function getTypeBadge(log: OpsErrorLog): { label: string; className: string } {
|
function getTypeBadge(log: OpsErrorLog): { label: string; className: string } {
|
||||||
const phase = String(log.phase || '').toLowerCase()
|
const phase = String(log.phase || '').toLowerCase()
|
||||||
const owner = String(log.error_owner || '').toLowerCase()
|
const owner = String(log.error_owner || '').toLowerCase()
|
||||||
|
|||||||
Reference in New Issue
Block a user