fix(ops): 优化错误日志过滤和查询逻辑
后端改动: - 添加 resolved 参数默认值处理(向后兼容,默认显示未解决错误) - 新增 status_codes_other 查询参数支持 - 移除 service 层的高级设置过滤逻辑,简化错误日志查询流程 前端改动: - 完善错误日志相关组件的国际化支持 - 优化 Ops 监控面板和设置对话框的用户体验
This commit is contained in:
@@ -15,9 +15,9 @@
|
||||
<!-- Header actions -->
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="font-semibold text-gray-600 dark:text-gray-300">Resolved:</span>
|
||||
<span class="font-semibold text-gray-600 dark:text-gray-300">{{ t('admin.ops.errorDetail.resolution') }}</span>
|
||||
<span :class="(detail as any).resolved ? 'text-green-700 dark:text-green-400' : 'text-amber-700 dark:text-amber-300'">
|
||||
{{ (detail as any).resolved ? 'true' : 'false' }}
|
||||
{{ (detail as any).resolved ? t('admin.ops.errorDetails.resolved') : t('admin.ops.errorDetails.unresolved') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -28,7 +28,7 @@
|
||||
:disabled="loading"
|
||||
@click="markResolved(true)"
|
||||
>
|
||||
{{ t('admin.ops.errorDetail.markResolved') || 'Mark resolved' }}
|
||||
{{ t('admin.ops.errorDetail.markResolved') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
@@ -37,17 +37,17 @@
|
||||
:disabled="loading"
|
||||
@click="markResolved(false)"
|
||||
>
|
||||
{{ t('admin.ops.errorDetail.markUnresolved') || 'Mark unresolved' }}
|
||||
{{ t('admin.ops.errorDetail.markUnresolved') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="flex flex-wrap gap-2 border-b border-gray-200 pb-3 dark:border-dark-700">
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='overview' ? 'opacity-100' : 'opacity-70'" @click="activeTab='overview'">{{ t('admin.ops.errorDetail.tabOverview') || 'Overview' }}</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='retries' ? 'opacity-100' : 'opacity-70'" @click="activeTab='retries'">{{ t('admin.ops.errorDetail.tabRetries') || 'Retries' }}</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='request' ? 'opacity-100' : 'opacity-70'" @click="activeTab='request'">{{ t('admin.ops.errorDetail.tabRequest') || 'Request' }}</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='response' ? 'opacity-100' : 'opacity-70'" @click="activeTab='response'">{{ t('admin.ops.errorDetail.tabResponse') || 'Response' }}</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='overview' ? 'opacity-100' : 'opacity-70'" @click="activeTab='overview'">{{ t('admin.ops.errorDetail.tabOverview') }}</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='retries' ? 'opacity-100' : 'opacity-70'" @click="activeTab='retries'">{{ t('admin.ops.errorDetail.tabRetries') }}</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='request' ? 'opacity-100' : 'opacity-70'" @click="activeTab='request'">{{ t('admin.ops.errorDetail.tabRequest') }}</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" :class="activeTab==='response' ? 'opacity-100' : 'opacity-70'" @click="activeTab='response'">{{ t('admin.ops.errorDetail.tabResponse') }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab==='overview'">
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
<!-- Suggestion -->
|
||||
<div v-if="handlingSuggestion" class="rounded-xl bg-gray-50 p-6 dark:bg-dark-900">
|
||||
<h3 class="mb-4 text-sm font-black uppercase tracking-wider text-gray-900 dark:text-white">{{ t('admin.ops.errorDetail.suggestion') || 'Suggestion' }}</h3>
|
||||
<h3 class="mb-4 text-sm font-black uppercase tracking-wider text-gray-900 dark:text-white">{{ t('admin.ops.errorDetail.suggestion') }}</h3>
|
||||
<div class="text-sm font-medium text-gray-800 dark:text-gray-200 break-words">
|
||||
{{ handlingSuggestion }}
|
||||
</div>
|
||||
@@ -110,41 +110,41 @@
|
||||
|
||||
<!-- Classification -->
|
||||
<div class="rounded-xl bg-gray-50 p-6 dark:bg-dark-900">
|
||||
<h3 class="mb-4 text-sm font-black uppercase tracking-wider text-gray-900 dark:text-white">{{ t('admin.ops.errorDetail.classification') || 'Classification' }}</h3>
|
||||
<h3 class="mb-4 text-sm font-black uppercase tracking-wider text-gray-900 dark:text-white">{{ t('admin.ops.errorDetail.classification') }}</h3>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">phase</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.phase') }}</div>
|
||||
<div class="mt-1 text-sm font-bold uppercase text-gray-900 dark:text-white">{{ detail.phase || '—' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">owner</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.owner') }}</div>
|
||||
<div class="mt-1 text-sm font-bold uppercase text-gray-900 dark:text-white">{{ (detail as any).error_owner || '—' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">source</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.source') }}</div>
|
||||
<div class="mt-1 text-sm font-bold uppercase text-gray-900 dark:text-white">{{ (detail as any).error_source || '—' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">retryable</div>
|
||||
<div class="mt-1 text-sm font-bold text-gray-900 dark:text-white">{{ (detail as any).is_retryable ? '✓' : '✗' }}</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.retryable') }}</div>
|
||||
<div class="mt-1 text-sm font-bold text-gray-900 dark:text-white">{{ (detail as any).is_retryable ? t('common.yes') : t('common.no') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">resolved_at</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.resolvedAt') }}</div>
|
||||
<div class="mt-1 font-mono text-xs text-gray-700 dark:text-gray-200">{{ (detail as any).resolved_at || '—' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">resolved_by</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.resolvedBy') }}</div>
|
||||
<div class="mt-1 font-mono text-xs text-gray-700 dark:text-gray-200">{{ (detail as any).resolved_by_user_id ?? '—' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">resolved_retry_id</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.resolvedRetryId') }}</div>
|
||||
<div class="mt-1 font-mono text-xs text-gray-700 dark:text-gray-200">{{ (detail as any).resolved_retry_id ?? '—' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">retry_count</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.classificationKeys.retryCount') }}</div>
|
||||
<div class="mt-1 font-mono text-xs text-gray-700 dark:text-gray-200">{{ (detail as any).retry_count ?? '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +165,7 @@
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.group') }}</div>
|
||||
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
|
||||
<el-tooltip v-if="detail.group_id" :content="'ID: ' + detail.group_id" placement="top">
|
||||
<el-tooltip v-if="detail.group_id" :content="t('admin.ops.errorLog.id') + ' ' + detail.group_id" placement="top">
|
||||
<span>{{ detail.group_name || detail.group_id }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else>—</span>
|
||||
@@ -174,7 +174,7 @@
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.account') }}</div>
|
||||
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
|
||||
<el-tooltip v-if="detail.account_id" :content="'ID: ' + detail.account_id" placement="top">
|
||||
<el-tooltip v-if="detail.account_id" :content="t('admin.ops.errorLog.id') + ' ' + detail.account_id" placement="top">
|
||||
<span>{{ detail.account_name || detail.account_id }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else>—</span>
|
||||
@@ -257,7 +257,7 @@
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-xs font-semibold text-amber-700 dark:text-amber-300">{{ t('admin.ops.errorDetail.notRetryable') || 'Not retryable' }}</span>
|
||||
<span class="text-xs font-semibold text-amber-700 dark:text-amber-300">{{ t('admin.ops.errorDetail.notRetryable') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -268,7 +268,7 @@
|
||||
<label class="mb-1 block text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.pinnedAccountId') }}</label>
|
||||
<input v-model="pinnedAccountIdInput" type="text" class="input font-mono text-sm" disabled />
|
||||
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
pinned to original account_id
|
||||
{{ t('admin.ops.errorDetail.pinnedToOriginalAccountId') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
@@ -294,13 +294,13 @@
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">status</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.upstreamKeys.status') }}</div>
|
||||
<div class="mt-1 font-mono text-sm font-bold text-gray-900 dark:text-white">
|
||||
{{ detail.upstream_status_code != null ? detail.upstream_status_code : '—' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="text-xs font-bold uppercase text-gray-400">message</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.upstreamKeys.message') }}</div>
|
||||
<div class="mt-1 break-words text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ detail.upstream_error_message || '—' }}
|
||||
</div>
|
||||
@@ -308,14 +308,14 @@
|
||||
</div>
|
||||
|
||||
<div v-if="detail.upstream_error_detail" class="mt-4">
|
||||
<div class="text-xs font-bold uppercase text-gray-400">detail</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.upstreamKeys.detail') }}</div>
|
||||
<pre
|
||||
class="mt-2 max-h-[240px] overflow-auto rounded-xl border border-gray-200 bg-white p-4 text-xs text-gray-800 dark:border-dark-700 dark:bg-dark-800 dark:text-gray-100"
|
||||
><code>{{ prettyJSON(detail.upstream_error_detail) }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div v-if="detail.upstream_errors" class="mt-5">
|
||||
<div class="mb-2 text-xs font-bold uppercase text-gray-400">upstream_errors</div>
|
||||
<div class="mb-2 text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.upstreamKeys.upstreamErrors') }}</div>
|
||||
|
||||
<div v-if="upstreamErrors.length" class="space-y-3">
|
||||
<div
|
||||
@@ -333,7 +333,7 @@
|
||||
type="button"
|
||||
class="rounded-md bg-gray-100 px-2 py-1 text-[10px] font-bold text-gray-700 hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-200 dark:hover:bg-dark-600"
|
||||
:disabled="retrying || !ev.upstream_request_body"
|
||||
:title="ev.upstream_request_body ? '' : 'missing upstream request body'"
|
||||
:title="ev.upstream_request_body ? '' : t('admin.ops.errorDetail.missingUpstreamRequestBody')"
|
||||
@click.stop="retryUpstreamEvent(idx)"
|
||||
>
|
||||
{{ t('admin.ops.errorDetail.retryUpstream') }} #{{ idx + 1 }}
|
||||
@@ -346,15 +346,15 @@
|
||||
|
||||
<div class="mt-2 grid grid-cols-1 gap-2 text-xs text-gray-600 dark:text-gray-300 sm:grid-cols-2">
|
||||
<div>
|
||||
<span class="text-gray-400">account:</span>
|
||||
<el-tooltip v-if="ev.account_id" :content="'ID: ' + ev.account_id" placement="top">
|
||||
<span class="text-gray-400">{{ t('admin.ops.errorDetail.upstreamEvent.account') }}:</span>
|
||||
<el-tooltip v-if="ev.account_id" :content="t('admin.ops.errorLog.id') + ' ' + ev.account_id" placement="top">
|
||||
<span class="font-medium text-gray-900 dark:text-white ml-1">{{ ev.account_name || ev.account_id }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="ml-1">—</span>
|
||||
</div>
|
||||
<div><span class="text-gray-400">status:</span> <span class="font-mono ml-1">{{ ev.upstream_status_code ?? '—' }}</span></div>
|
||||
<div><span class="text-gray-400">{{ t('admin.ops.errorDetail.upstreamEvent.status') }}:</span> <span class="font-mono ml-1">{{ ev.upstream_status_code ?? '—' }}</span></div>
|
||||
<div class="sm:col-span-2 break-all">
|
||||
<span class="text-gray-400">request_id:</span> <span class="font-mono ml-1">{{ ev.upstream_request_id || '—' }}</span>
|
||||
<span class="text-gray-400">{{ t('admin.ops.errorDetail.upstreamEvent.requestId') }}:</span> <span class="font-mono ml-1">{{ ev.upstream_request_id || '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -403,7 +403,7 @@
|
||||
|
||||
<div v-else-if="activeTab==='retries'">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.ops.errorDetail.retryHistory') || 'Retry History' }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.ops.errorDetail.retryHistory') }}</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-secondary btn-sm" @click="loadRetryHistory">{{ t('common.refresh') }}</button>
|
||||
</div>
|
||||
@@ -415,14 +415,14 @@
|
||||
<div v-else>
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.compareA') || 'Compare A' }}</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.compareA') }}</div>
|
||||
<select v-model.number="compareA" class="input mt-2 w-full font-mono text-xs">
|
||||
<option :value="null">—</option>
|
||||
<option v-for="a in retryHistory" :key="a.id" :value="a.id">#{{ a.id }} · {{ a.mode }} · {{ a.status }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.compareB') || 'Compare B' }}</div>
|
||||
<div class="text-xs font-bold uppercase text-gray-400">{{ t('admin.ops.errorDetail.compareB') }}</div>
|
||||
<select v-model.number="compareB" class="input mt-2 w-full font-mono text-xs">
|
||||
<option :value="null">—</option>
|
||||
<option v-for="b in retryHistory" :key="b.id" :value="b.id">#{{ b.id }} · {{ b.mode }} · {{ b.status }}</option>
|
||||
@@ -434,8 +434,8 @@
|
||||
<div class="rounded-xl border border-gray-200 bg-white p-4 dark:border-dark-700 dark:bg-dark-800">
|
||||
<div class="text-xs font-black text-gray-900 dark:text-white">{{ selectedA ? `#${selectedA.id} · ${selectedA.mode} · ${selectedA.status}` : '—' }}</div>
|
||||
<div class="mt-2 text-xs text-gray-600 dark:text-gray-300">
|
||||
http: <span class="font-mono">{{ selectedA?.http_status_code ?? '—' }}</span> ·
|
||||
used: <span class="font-mono">
|
||||
{{ t('admin.ops.errorDetail.retryMeta.http') }}: <span class="font-mono">{{ selectedA?.http_status_code ?? '—' }}</span> ·
|
||||
{{ t('admin.ops.errorDetail.retryMeta.used') }}: <span class="font-mono">
|
||||
<el-tooltip v-if="selectedA?.used_account_id" :content="'ID: ' + selectedA.used_account_id" placement="top">
|
||||
<span class="font-medium">{{ selectedA.used_account_name || selectedA.used_account_id }}</span>
|
||||
</el-tooltip>
|
||||
@@ -448,8 +448,8 @@
|
||||
<div class="rounded-xl border border-gray-200 bg-white p-4 dark:border-dark-700 dark:bg-dark-800">
|
||||
<div class="text-xs font-black text-gray-900 dark:text-white">{{ selectedB ? `#${selectedB.id} · ${selectedB.mode} · ${selectedB.status}` : '—' }}</div>
|
||||
<div class="mt-2 text-xs text-gray-600 dark:text-gray-300">
|
||||
http: <span class="font-mono">{{ selectedB?.http_status_code ?? '—' }}</span> ·
|
||||
used: <span class="font-mono">
|
||||
{{ t('admin.ops.errorDetail.retryMeta.http') }}: <span class="font-mono">{{ selectedB?.http_status_code ?? '—' }}</span> ·
|
||||
{{ t('admin.ops.errorDetail.retryMeta.used') }}: <span class="font-mono">
|
||||
<el-tooltip v-if="selectedB?.used_account_id" :content="'ID: ' + selectedB.used_account_id" placement="top">
|
||||
<span class="font-medium">{{ selectedB.used_account_name || selectedB.used_account_id }}</span>
|
||||
</el-tooltip>
|
||||
@@ -468,17 +468,17 @@
|
||||
<div class="font-mono text-xs text-gray-500 dark:text-gray-400">{{ a.created_at }}</div>
|
||||
</div>
|
||||
<div class="mt-2 grid grid-cols-1 gap-2 text-xs text-gray-600 dark:text-gray-300 sm:grid-cols-4">
|
||||
<div><span class="text-gray-400">success:</span> <span class="font-mono">{{ a.success ?? '—' }}</span></div>
|
||||
<div><span class="text-gray-400">http:</span> <span class="font-mono">{{ a.http_status_code ?? '—' }}</span></div>
|
||||
<div><span class="text-gray-400">{{ t('admin.ops.errorDetail.retryMeta.success') }}:</span> <span class="font-mono">{{ a.success ?? '—' }}</span></div>
|
||||
<div><span class="text-gray-400">{{ t('admin.ops.errorDetail.retryMeta.http') }}:</span> <span class="font-mono">{{ a.http_status_code ?? '—' }}</span></div>
|
||||
<div>
|
||||
<span class="text-gray-400">pinned:</span>
|
||||
<span class="text-gray-400">{{ t('admin.ops.errorDetail.retryMeta.pinned') }}:</span>
|
||||
<el-tooltip v-if="a.pinned_account_id" :content="'ID: ' + a.pinned_account_id" placement="top">
|
||||
<span class="font-mono ml-1">{{ a.pinned_account_name || a.pinned_account_id }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="font-mono ml-1">—</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400">used:</span>
|
||||
<span class="text-gray-400">{{ t('admin.ops.errorDetail.retryMeta.used') }}:</span>
|
||||
<el-tooltip v-if="a.used_account_id" :content="'ID: ' + a.used_account_id" placement="top">
|
||||
<span class="font-mono ml-1">{{ a.used_account_name || a.used_account_id }}</span>
|
||||
</el-tooltip>
|
||||
@@ -502,7 +502,7 @@
|
||||
|
||||
<div v-else-if="activeTab==='response'">
|
||||
<div class="rounded-xl bg-gray-50 p-6 dark:bg-dark-900">
|
||||
<h3 class="text-sm font-black uppercase tracking-wider text-gray-900 dark:text-white">{{ t('admin.ops.errorDetail.responseBody') || 'Response' }}</h3>
|
||||
<h3 class="text-sm font-black uppercase tracking-wider text-gray-900 dark:text-white">{{ t('admin.ops.errorDetail.responseBody') }}</h3>
|
||||
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ responseTabHint }}
|
||||
</div>
|
||||
@@ -525,7 +525,7 @@
|
||||
<div class="pointer-events-auto w-full max-w-xl rounded-2xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-900/40 dark:bg-amber-900/20 dark:text-amber-200">
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="forceRetryAck" type="checkbox" class="h-4 w-4" />
|
||||
<span>{{ t('admin.ops.errorDetail.forceRetry') || 'I understand and want to force retry' }}</span>
|
||||
<span>{{ t('admin.ops.errorDetail.forceRetry') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -578,11 +578,11 @@ const compareB = ref<number | null>(null)
|
||||
const pinnedAccountIdInput = ref('')
|
||||
|
||||
const title = computed(() => {
|
||||
if (!props.errorId) return 'Error Detail'
|
||||
return `Error #${props.errorId}`
|
||||
if (!props.errorId) return t('admin.ops.errorDetail.title')
|
||||
return t('admin.ops.errorDetail.titleWithId', { id: String(props.errorId) })
|
||||
})
|
||||
|
||||
const emptyText = computed(() => 'No error selected.')
|
||||
const emptyText = computed(() => t('admin.ops.errorDetail.noErrorSelected'))
|
||||
|
||||
type UpstreamErrorEvent = {
|
||||
at_unix_ms?: number
|
||||
@@ -630,20 +630,20 @@ const handlingSuggestion = computed(() => {
|
||||
|
||||
if (owner === 'provider' && phase === 'upstream') {
|
||||
if (retryHistory.value.some((r) => r.success === true) && d.resolved) {
|
||||
return t('admin.ops.errorDetail.suggestUpstreamResolved') || '✓ Upstream error resolved by retry; no action needed.'
|
||||
return t('admin.ops.errorDetail.suggestUpstreamResolved')
|
||||
}
|
||||
return t('admin.ops.errorDetail.suggestUpstream') || 'Upstream instability: consider checking upstream account status, switching accounts, or retrying.'
|
||||
return t('admin.ops.errorDetail.suggestUpstream')
|
||||
}
|
||||
if (owner === 'client' && phase === 'request') {
|
||||
return t('admin.ops.errorDetail.suggestRequest') || 'Client request validation error: contact customer to fix request parameters.'
|
||||
return t('admin.ops.errorDetail.suggestRequest')
|
||||
}
|
||||
if (owner === 'client' && phase === 'auth') {
|
||||
return t('admin.ops.errorDetail.suggestAuth') || 'Auth failed: verify API key/credentials.'
|
||||
return t('admin.ops.errorDetail.suggestAuth')
|
||||
}
|
||||
if (owner === 'platform') {
|
||||
return t('admin.ops.errorDetail.suggestPlatform') || 'Platform error: prioritize investigation and fix.'
|
||||
return t('admin.ops.errorDetail.suggestPlatform')
|
||||
}
|
||||
return t('admin.ops.errorDetail.suggestGeneric') || 'See details for more context.'
|
||||
return t('admin.ops.errorDetail.suggestGeneric')
|
||||
})
|
||||
|
||||
async function fetchDetail(id: number) {
|
||||
@@ -709,7 +709,7 @@ async function loadRetryHistory() {
|
||||
retryHistory.value = []
|
||||
compareA.value = null
|
||||
compareB.value = null
|
||||
appStore.showError(err?.message || 'Failed to load retry history')
|
||||
appStore.showError(err?.message || t('admin.ops.errorDetail.failedToLoadRetryHistory'))
|
||||
} finally {
|
||||
retryHistoryLoading.value = false
|
||||
}
|
||||
@@ -732,7 +732,7 @@ const responseTabHint = computed(() => {
|
||||
if (succeeded?.response_preview) {
|
||||
return t('admin.ops.errorDetail.responseHintSucceeded', { id: String(succeeded.id) }) || `Showing succeeded retry response_preview (#${succeeded.id})`
|
||||
}
|
||||
return t('admin.ops.errorDetail.responseHintFallback') || 'No succeeded retry found; showing stored error_body'
|
||||
return t('admin.ops.errorDetail.responseHintFallback')
|
||||
})
|
||||
|
||||
async function markResolved(resolved: boolean) {
|
||||
@@ -745,9 +745,9 @@ async function markResolved(resolved: boolean) {
|
||||
await opsAPI.updateRequestErrorResolved(props.errorId, resolved)
|
||||
}
|
||||
await fetchDetail(props.errorId)
|
||||
appStore.showSuccess(resolved ? (t('admin.ops.errorDetails.resolved') || 'Resolved') : (t('admin.ops.errorDetails.unresolved') || 'Unresolved'))
|
||||
appStore.showSuccess(resolved ? t('admin.ops.errorDetails.resolved') : t('admin.ops.errorDetails.unresolved'))
|
||||
} catch (err: any) {
|
||||
appStore.showError(err?.message || 'Failed to update resolved status')
|
||||
appStore.showError(err?.message || t('admin.ops.errorDetail.failedToUpdateResolvedStatus'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,7 +755,7 @@ const retryConfirmMessage = computed(() => {
|
||||
const mode = pendingRetryMode.value
|
||||
const retryable = !!(detail.value as any)?.is_retryable
|
||||
if (!retryable) {
|
||||
return t('admin.ops.errorDetail.forceRetryHint') || 'This error is not recommended to retry. Check the box to force retry.'
|
||||
return t('admin.ops.errorDetail.forceRetryHint')
|
||||
}
|
||||
if (mode === 'upstream') {
|
||||
return t('admin.ops.errorDetail.confirmRetryMessage')
|
||||
@@ -781,7 +781,7 @@ async function runConfirmedRetry() {
|
||||
const mode = pendingRetryMode.value
|
||||
const retryable = !!(detail.value as any)?.is_retryable
|
||||
if (!retryable && !forceRetryAck.value) {
|
||||
appStore.showError(t('admin.ops.errorDetail.forceRetryNeedAck') || 'Please confirm you want to force retry')
|
||||
appStore.showError(t('admin.ops.errorDetail.forceRetryNeedAck'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -799,7 +799,7 @@ async function runConfirmedRetry() {
|
||||
if (mode === 'client') {
|
||||
res = await opsAPI.retryRequestErrorClient(props.errorId)
|
||||
} else {
|
||||
throw new Error('Unsupported retry mode')
|
||||
throw new Error(t('admin.ops.errorDetail.unsupportedRetryMode'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user