feat(report): add drilldown dialog for slow API details
This commit is contained in:
@@ -38,6 +38,7 @@ const dockerStats = ref(null)
|
||||
const browserPoolStats = ref(null)
|
||||
const systemConfig = ref(null)
|
||||
const requestMetrics = ref(null)
|
||||
const requestDetailsOpen = ref(false)
|
||||
const queueTab = ref('running')
|
||||
|
||||
function recordUpdatedAt() {
|
||||
@@ -85,6 +86,16 @@ function formatUnixTime(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatUnixDateTime(value) {
|
||||
const ts = Number(value)
|
||||
if (!Number.isFinite(ts) || ts <= 0) return '-'
|
||||
try {
|
||||
return new Date(ts * 1000).toLocaleString('zh-CN', { hour12: false, timeZone: 'Asia/Shanghai' })
|
||||
} catch {
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
|
||||
function sourceLabel(source) {
|
||||
const raw = String(source ?? '').trim()
|
||||
if (!raw) return '手动'
|
||||
@@ -335,9 +346,47 @@ const requestModuleDesc = computed(() => {
|
||||
const avgMs = formatMs(requestMetrics.value?.avg_duration_ms)
|
||||
const maxMs = formatMs(requestMetrics.value?.max_duration_ms)
|
||||
const lastAt = formatUnixTime(requestMetrics.value?.last_request_ts)
|
||||
return `均值 ${avgMs} · 峰值 ${maxMs} · 最近 ${lastAt}`
|
||||
const slowThreshold = formatMs(requestMetrics.value?.slow_threshold_ms)
|
||||
return `均值 ${avgMs} · 峰值 ${maxMs} · 慢阈 ${slowThreshold} · 最近 ${lastAt}`
|
||||
})
|
||||
|
||||
const requestDetailTopPaths = computed(() => {
|
||||
const rows = Array.isArray(requestMetrics.value?.top_paths) ? requestMetrics.value.top_paths : []
|
||||
return rows.map((row, index) => ({
|
||||
rank: index + 1,
|
||||
path: String(row?.path || '-'),
|
||||
count: normalizeCount(row?.count),
|
||||
avg_ms: formatMs(row?.avg_ms),
|
||||
max_ms: formatMs(row?.max_ms),
|
||||
status_5xx: normalizeCount(row?.status_5xx),
|
||||
}))
|
||||
})
|
||||
|
||||
const requestRecentSlowRows = computed(() => {
|
||||
const rows = Array.isArray(requestMetrics.value?.recent_slow) ? requestMetrics.value.recent_slow : []
|
||||
return [...rows]
|
||||
.sort((a, b) => Number(b?.time || 0) - Number(a?.time || 0))
|
||||
.map((row) => ({
|
||||
time_text: formatUnixDateTime(row?.time),
|
||||
method: String(row?.method || '-').toUpperCase(),
|
||||
path: String(row?.path || '-'),
|
||||
status: normalizeCount(row?.status),
|
||||
duration_ms: formatMs(row?.duration_ms),
|
||||
}))
|
||||
})
|
||||
|
||||
function requestStatusTagType(status) {
|
||||
const code = normalizeCount(status)
|
||||
if (code >= 500) return 'danger'
|
||||
if (code >= 400) return 'warning'
|
||||
if (code >= 300) return 'info'
|
||||
return 'success'
|
||||
}
|
||||
|
||||
function openRequestDetails() {
|
||||
requestDetailsOpen.value = true
|
||||
}
|
||||
|
||||
const configModuleItems = computed(() => [
|
||||
{ label: '定时任务', value: scheduleEnabled.value ? '启用' : '关闭' },
|
||||
{ label: '执行时间', value: scheduleTime.value || '-' },
|
||||
@@ -550,8 +599,51 @@ onUnmounted(() => {
|
||||
<div class="mobile-metric-value">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="module.key === 'request'" class="module-extra-actions">
|
||||
<el-button size="small" type="primary" plain @click="openRequestDetails">查看慢接口详情</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="requestDetailsOpen" title="慢接口详情" width="min(1080px, 96vw)">
|
||||
<div class="request-dialog-summary app-muted">
|
||||
<span>总请求:{{ normalizeCount(requestMetrics?.total_requests) }}</span>
|
||||
<span>API请求:{{ normalizeCount(requestMetrics?.api_requests) }}</span>
|
||||
<span>慢请求:{{ normalizeCount(requestMetrics?.slow_requests) }}</span>
|
||||
<span>错误请求:{{ normalizeCount(requestMetrics?.error_requests) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="request-dialog-block">
|
||||
<div class="request-dialog-title">慢接口排行榜</div>
|
||||
<div class="table-wrap">
|
||||
<el-table :data="requestDetailTopPaths" size="small" max-height="280">
|
||||
<el-table-column prop="rank" label="#" width="60" />
|
||||
<el-table-column prop="path" label="接口路径" min-width="340" show-overflow-tooltip />
|
||||
<el-table-column prop="count" label="请求数" width="100" />
|
||||
<el-table-column prop="avg_ms" label="平均耗时" width="120" />
|
||||
<el-table-column prop="max_ms" label="峰值耗时" width="120" />
|
||||
<el-table-column prop="status_5xx" label="5xx" width="90" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="request-dialog-block">
|
||||
<div class="request-dialog-title">最近慢请求</div>
|
||||
<div class="table-wrap">
|
||||
<el-table :data="requestRecentSlowRows" size="small" max-height="320">
|
||||
<el-table-column prop="time_text" label="时间" width="180" />
|
||||
<el-table-column prop="method" label="方法" width="90" />
|
||||
<el-table-column prop="path" label="接口路径" min-width="320" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="requestStatusTagType(scope.row.status)">{{ scope.row.status || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="duration_ms" label="耗时" width="110" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -710,6 +802,30 @@ onUnmounted(() => {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.module-extra-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.request-dialog-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 16px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.request-dialog-block + .request-dialog-block {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.request-dialog-title {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.hero-overview-grid {
|
||||
display: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user