From 55e469c7fe35ee4fcfab714e132d17bd1f8d16b6 Mon Sep 17 00:00:00 2001
From: IanShaw027 <131567472+IanShaw027@users.noreply.github.com>
Date: Wed, 14 Jan 2026 16:26:33 +0800
Subject: [PATCH] =?UTF-8?q?fix(ops):=20=E4=BC=98=E5=8C=96=E9=94=99?=
=?UTF-8?q?=E8=AF=AF=E6=97=A5=E5=BF=97=E8=BF=87=E6=BB=A4=E5=92=8C=E6=9F=A5?=
=?UTF-8?q?=E8=AF=A2=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
后端改动:
- 添加 resolved 参数默认值处理(向后兼容,默认显示未解决错误)
- 新增 status_codes_other 查询参数支持
- 移除 service 层的高级设置过滤逻辑,简化错误日志查询流程
前端改动:
- 完善错误日志相关组件的国际化支持
- 优化 Ops 监控面板和设置对话框的用户体验
---
backend/internal/handler/admin/ops_handler.go | 17 +++
backend/internal/repository/ops_repo.go | 12 +-
backend/internal/service/ops_models.go | 13 +-
backend/internal/service/ops_service.go | 55 --------
frontend/src/api/admin/ops.ts | 1 +
frontend/src/i18n/locales/en.ts | 87 ++++++++++++-
frontend/src/i18n/locales/zh.ts | 87 ++++++++++++-
.../ops/components/OpsDashboardHeader.vue | 64 ++++-----
.../ops/components/OpsErrorDetailModal.vue | 122 +++++++++---------
.../ops/components/OpsErrorDetailsModal.vue | 26 ++--
.../admin/ops/components/OpsErrorLogTable.vue | 14 +-
.../ops/components/OpsRuntimeSettingsCard.vue | 28 ++--
.../ops/components/OpsSettingsDialog.vue | 40 +++---
13 files changed, 349 insertions(+), 217 deletions(-)
diff --git a/backend/internal/handler/admin/ops_handler.go b/backend/internal/handler/admin/ops_handler.go
index 9349838a..c76b6a60 100644
--- a/backend/internal/handler/admin/ops_handler.go
+++ b/backend/internal/handler/admin/ops_handler.go
@@ -110,6 +110,12 @@ func (h *OpsHandler) GetErrorLogs(c *gin.Context) {
filter.Source = source
}
filter.View = parseOpsViewParam(c)
+
+ // Legacy endpoint default: unresolved only (backward-compatible).
+ {
+ b := false
+ filter.Resolved = &b
+ }
if v := strings.TrimSpace(c.Query("resolved")); v != "" {
switch strings.ToLower(v) {
case "1", "true", "yes":
@@ -143,6 +149,17 @@ func (h *OpsHandler) GetErrorLogs(c *gin.Context) {
}
filter.StatusCodes = out
}
+ if v := strings.TrimSpace(c.Query("status_codes_other")); v != "" {
+ switch strings.ToLower(v) {
+ case "1", "true", "yes":
+ filter.StatusCodesOther = true
+ case "0", "false", "no":
+ filter.StatusCodesOther = false
+ default:
+ response.BadRequest(c, "Invalid status_codes_other")
+ return
+ }
+ }
result, err := h.opsService.GetErrorLogs(c.Request.Context(), filter)
if err != nil {
diff --git a/backend/internal/repository/ops_repo.go b/backend/internal/repository/ops_repo.go
index c9cca1d5..0535547d 100644
--- a/backend/internal/repository/ops_repo.go
+++ b/backend/internal/repository/ops_repo.go
@@ -132,7 +132,6 @@ func (r *opsRepository) ListErrorLogs(ctx context.Context, filter *service.OpsEr
pageSize = 500
}
- // buildOpsErrorLogsWhere may mutate filter (default resolved filter).
where, args := buildOpsErrorLogsWhere(filter)
countSQL := "SELECT COUNT(*) FROM ops_error_logs e " + where
@@ -933,15 +932,11 @@ func buildOpsErrorLogsWhere(filter *service.OpsErrorLogFilter) (string, []any) {
}
// ops_error_logs stores client-visible error requests (status>=400),
// but we also persist "recovered" upstream errors (status<400) for upstream health visibility.
- // By default, keep list endpoints scoped to unresolved records if the caller didn't specify.
+ // If Resolved is not specified, do not filter by resolved state (backward-compatible).
resolvedFilter := (*bool)(nil)
if filter != nil {
resolvedFilter = filter.Resolved
}
- if resolvedFilter == nil {
- f := false
- resolvedFilter = &f
- }
// Keep list endpoints scoped to client errors unless explicitly filtering upstream phase.
if phaseFilter != "upstream" {
clauses = append(clauses, "COALESCE(status_code, 0) >= 400")
@@ -1007,6 +1002,11 @@ func buildOpsErrorLogsWhere(filter *service.OpsErrorLogFilter) (string, []any) {
if len(filter.StatusCodes) > 0 {
args = append(args, pq.Array(filter.StatusCodes))
clauses = append(clauses, "COALESCE(upstream_status_code, status_code, 0) = ANY($"+itoa(len(args))+")")
+ } else if filter.StatusCodesOther {
+ // "Other" means: status codes not in the common list.
+ known := []int{400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504, 529}
+ args = append(args, pq.Array(known))
+ clauses = append(clauses, "NOT (COALESCE(upstream_status_code, status_code, 0) = ANY($"+itoa(len(args))+"))")
}
if q := strings.TrimSpace(filter.Query); q != "" {
like := "%" + q + "%"
diff --git a/backend/internal/service/ops_models.go b/backend/internal/service/ops_models.go
index c48c9b56..ebdf148f 100644
--- a/backend/internal/service/ops_models.go
+++ b/backend/internal/service/ops_models.go
@@ -86,12 +86,13 @@ type OpsErrorLogFilter struct {
GroupID *int64
AccountID *int64
- StatusCodes []int
- Phase string
- Owner string
- Source string
- Resolved *bool
- Query string
+ StatusCodes []int
+ StatusCodesOther bool
+ Phase string
+ Owner string
+ Source string
+ Resolved *bool
+ Query string
// View controls error categorization for list endpoints.
// - errors: show actionable errors (exclude business-limited / 429 / 529)
diff --git a/backend/internal/service/ops_service.go b/backend/internal/service/ops_service.go
index d606ba09..915be5df 100644
--- a/backend/internal/service/ops_service.go
+++ b/backend/internal/service/ops_service.go
@@ -261,64 +261,9 @@ func (s *OpsService) GetErrorLogs(ctx context.Context, filter *OpsErrorLogFilter
return nil, err
}
- // Apply error filtering based on settings (for historical data)
- result = s.filterErrorLogsBySettings(ctx, result)
return result, nil
}
-// filterErrorLogsBySettings filters error logs based on advanced settings.
-// This ensures that historical errors are also filtered when viewing the dashboard.
-func (s *OpsService) filterErrorLogsBySettings(ctx context.Context, result *OpsErrorLogList) *OpsErrorLogList {
- if result == nil || len(result.Errors) == 0 {
- return result
- }
-
- settings, err := s.GetOpsAdvancedSettings(ctx)
- if err != nil || settings == nil {
- // If we can't get settings, return unfiltered (fail open)
- return result
- }
-
- filtered := make([]*OpsErrorLog, 0, len(result.Errors))
- for _, errLog := range result.Errors {
- if shouldFilterErrorLog(settings, errLog) {
- continue // Skip this error
- }
- filtered = append(filtered, errLog)
- }
-
- // Update total count to reflect filtered results
- result.Errors = filtered
- result.Total = len(filtered)
- return result
-}
-
-// shouldFilterErrorLog determines if an error log should be filtered based on settings.
-func shouldFilterErrorLog(settings *OpsAdvancedSettings, errLog *OpsErrorLog) bool {
- if settings == nil || errLog == nil {
- return false
- }
-
- msgLower := strings.ToLower(errLog.Message)
-
- // Check if count_tokens errors should be ignored
- if settings.IgnoreCountTokensErrors && strings.Contains(errLog.RequestPath, "/count_tokens") {
- return true
- }
-
- // Check if context canceled errors should be ignored
- if settings.IgnoreContextCanceled && strings.Contains(msgLower, "context canceled") {
- return true
- }
-
- // Check if "no available accounts" errors should be ignored
- if settings.IgnoreNoAvailableAccounts && strings.Contains(msgLower, "no available accounts") {
- return true
- }
-
- return false
-}
-
func (s *OpsService) GetErrorLogByID(ctx context.Context, id int64) (*OpsErrorLogDetail, error) {
if err := s.RequireMonitoringEnabled(ctx); err != nil {
return nil, err
diff --git a/frontend/src/api/admin/ops.ts b/frontend/src/api/admin/ops.ts
index 4ec560a4..0ac54db6 100644
--- a/frontend/src/api/admin/ops.ts
+++ b/frontend/src/api/admin/ops.ts
@@ -965,6 +965,7 @@ export type OpsErrorListQueryParams = {
q?: string
status_codes?: string
+ status_codes_other?: string
}
// Legacy unified endpoints
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 993a18c2..936d6bfa 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -2009,6 +2009,11 @@ export default {
// Error Log
errorLog: {
timeId: 'Time / ID',
+ commonErrors: {
+ contextDeadlineExceeded: 'context deadline exceeded',
+ connectionRefused: 'connection refused',
+ rateLimit: 'rate limit'
+ },
time: 'Time',
type: 'Type',
context: 'Context',
@@ -2038,12 +2043,64 @@ export default {
requestErrors: 'Request Errors',
unresolved: 'Unresolved',
resolved: 'Resolved',
+ viewErrors: 'Errors',
+ viewExcluded: 'Excluded',
+ statusCodeOther: 'Other',
+ owner: {
+ provider: 'Provider',
+ client: 'Client',
+ platform: 'Platform'
+ },
+ phase: {
+ request: 'Request',
+ auth: 'Auth',
+ routing: 'Routing',
+ upstream: 'Upstream',
+ network: 'Network',
+ internal: 'Internal'
+ },
total: 'Total:',
searchPlaceholder: 'Search request_id / client_request_id / message',
accountIdPlaceholder: 'account_id'
},
// Error Detail Modal
errorDetail: {
+ title: 'Error Detail',
+ titleWithId: 'Error #{id}',
+ noErrorSelected: 'No error selected.',
+ resolution: 'Resolved:',
+ pinnedToOriginalAccountId: 'Pinned to original account_id',
+ missingUpstreamRequestBody: 'Missing upstream request body',
+ failedToLoadRetryHistory: 'Failed to load retry history',
+ failedToUpdateResolvedStatus: 'Failed to update resolved status',
+ unsupportedRetryMode: 'Unsupported retry mode',
+ classificationKeys: {
+ phase: 'Phase',
+ owner: 'Owner',
+ source: 'Source',
+ retryable: 'Retryable',
+ resolvedAt: 'Resolved At',
+ resolvedBy: 'Resolved By',
+ resolvedRetryId: 'Resolved Retry',
+ retryCount: 'Retry Count'
+ },
+ upstreamKeys: {
+ status: 'Status',
+ message: 'Message',
+ detail: 'Detail',
+ upstreamErrors: 'Upstream Errors'
+ },
+ upstreamEvent: {
+ account: 'Account',
+ status: 'Status',
+ requestId: 'Request ID'
+ },
+ retryMeta: {
+ http: 'HTTP',
+ used: 'Used',
+ success: 'Success',
+ pinned: 'Pinned'
+ },
loading: 'Loading…',
requestId: 'Request ID',
time: 'Time',
@@ -2053,6 +2110,8 @@ export default {
basicInfo: 'Basic Info',
platform: 'Platform',
model: 'Model',
+ group: 'Group',
+ account: 'Account',
latency: 'Request Duration',
ttft: 'TTFT',
businessLimited: 'Business Limited',
@@ -2083,6 +2142,7 @@ export default {
retryNote1: 'Retry will use the same request body and parameters',
retryNote2: 'If the original request failed due to account issues, pinned retry may still fail',
retryNote3: 'Client retry will reselect an account',
+ retryNote4: 'You can force retry for non-retryable errors, but it is not recommended',
confirmRetryMessage: 'Confirm retry this request?',
confirmRetryHint: 'Will resend with the same request parameters',
forceRetry: 'I understand and want to force retry',
@@ -2337,7 +2397,11 @@ export default {
lockKeyRequired: 'Distributed lock key is required when lock is enabled',
lockKeyPrefix: 'Distributed lock key must start with "{prefix}"',
lockKeyHint: 'Recommended: start with "{prefix}" to avoid conflicts',
- lockTtlRange: 'Distributed lock TTL must be between 1 and 86400 seconds'
+ lockTtlRange: 'Distributed lock TTL must be between 1 and 86400 seconds',
+ slaMinPercentRange: 'SLA minimum percentage must be between 0 and 100',
+ ttftP99MaxRange: 'TTFT P99 maximum must be a number ≥ 0',
+ requestErrorRateMaxRange: 'Request error rate maximum must be between 0 and 100',
+ upstreamErrorRateMaxRange: 'Upstream error rate maximum must be between 0 and 100'
}
},
email: {
@@ -2420,9 +2484,28 @@ export default {
aggregation: 'Pre-aggregation Tasks',
enableAggregation: 'Enable Pre-aggregation',
aggregationHint: 'Pre-aggregation improves query performance for long time windows',
+ errorFiltering: 'Error Filtering',
+ ignoreCountTokensErrors: 'Ignore count_tokens errors',
+ ignoreCountTokensErrorsHint: 'When enabled, errors from count_tokens requests will not be written to the error log.',
+ ignoreContextCanceled: 'Ignore client disconnect errors',
+ ignoreContextCanceledHint: 'When enabled, client disconnect (context canceled) errors will not be written to the error log.',
+ ignoreNoAvailableAccounts: 'Ignore no available accounts errors',
+ ignoreNoAvailableAccountsHint: 'When enabled, "No available accounts" errors will not be written to the error log (not recommended; usually a config issue).',
+ autoRefresh: 'Auto Refresh',
+ enableAutoRefresh: 'Enable auto refresh',
+ enableAutoRefreshHint: 'Automatically refresh dashboard data at a fixed interval.',
+ refreshInterval: 'Refresh Interval',
+ refreshInterval15s: '15 seconds',
+ refreshInterval30s: '30 seconds',
+ refreshInterval60s: '60 seconds',
+ autoRefreshCountdown: 'Auto refresh: {seconds}s',
validation: {
title: 'Please fix the following issues',
- retentionDaysRange: 'Retention days must be between 1-365 days'
+ retentionDaysRange: 'Retention days must be between 1-365 days',
+ slaMinPercentRange: 'SLA minimum percentage must be between 0 and 100',
+ ttftP99MaxRange: 'TTFT P99 maximum must be a number ≥ 0',
+ requestErrorRateMaxRange: 'Request error rate maximum must be between 0 and 100',
+ upstreamErrorRateMaxRange: 'Upstream error rate maximum must be between 0 and 100'
}
},
concurrency: {
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index fcc84b19..85270e02 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -2153,6 +2153,11 @@ export default {
// Error Log
errorLog: {
timeId: '时间 / ID',
+ commonErrors: {
+ contextDeadlineExceeded: '请求超时',
+ connectionRefused: '连接被拒绝',
+ rateLimit: '触发限流'
+ },
time: '时间',
type: '类型',
context: '上下文',
@@ -2182,12 +2187,64 @@ export default {
requestErrors: '请求错误',
unresolved: '未解决',
resolved: '已解决',
+ viewErrors: '错误',
+ viewExcluded: '排除项',
+ statusCodeOther: '其他',
+ owner: {
+ provider: '服务商',
+ client: '客户端',
+ platform: '平台'
+ },
+ phase: {
+ request: '请求',
+ auth: '认证',
+ routing: '路由',
+ upstream: '上游',
+ network: '网络',
+ internal: '内部'
+ },
total: '总计:',
searchPlaceholder: '搜索 request_id / client_request_id / message',
accountIdPlaceholder: 'account_id'
},
// Error Detail Modal
errorDetail: {
+ title: '错误详情',
+ titleWithId: '错误 #{id}',
+ noErrorSelected: '未选择错误。',
+ resolution: '已解决:',
+ pinnedToOriginalAccountId: '固定到原 account_id',
+ missingUpstreamRequestBody: '缺少上游请求体',
+ failedToLoadRetryHistory: '加载重试历史失败',
+ failedToUpdateResolvedStatus: '更新解决状态失败',
+ unsupportedRetryMode: '不支持的重试模式',
+ classificationKeys: {
+ phase: '阶段',
+ owner: '归属方',
+ source: '来源',
+ retryable: '可重试',
+ resolvedAt: '解决时间',
+ resolvedBy: '解决人',
+ resolvedRetryId: '解决重试ID',
+ retryCount: '重试次数'
+ },
+ upstreamKeys: {
+ status: '状态码',
+ message: '消息',
+ detail: '详情',
+ upstreamErrors: '上游错误列表'
+ },
+ upstreamEvent: {
+ account: '账号',
+ status: '状态码',
+ requestId: '请求ID'
+ },
+ retryMeta: {
+ http: 'HTTP',
+ used: '使用账号',
+ success: '成功',
+ pinned: '固定账号'
+ },
loading: '加载中…',
requestId: '请求 ID',
time: '时间',
@@ -2197,6 +2254,8 @@ export default {
basicInfo: '基本信息',
platform: '平台',
model: '模型',
+ group: '分组',
+ account: '账号',
latency: '请求时长',
ttft: 'TTFT',
businessLimited: '业务限制',
@@ -2227,6 +2286,7 @@ export default {
retryNote1: '重试会使用相同的请求体和参数',
retryNote2: '如果原请求失败是因为账号问题,固定重试可能仍会失败',
retryNote3: '客户端重试会重新选择账号',
+ retryNote4: '对不可重试的错误可以强制重试,但不推荐',
confirmRetryMessage: '确认要重试该请求吗?',
confirmRetryHint: '将使用相同的请求参数重新发送',
forceRetry: '我已确认并理解强制重试风险',
@@ -2481,7 +2541,11 @@ export default {
lockKeyRequired: '启用分布式锁时必须填写 Lock Key',
lockKeyPrefix: '分布式锁 Key 必须以「{prefix}」开头',
lockKeyHint: '建议以「{prefix}」开头以避免冲突',
- lockTtlRange: '分布式锁 TTL 必须在 1 到 86400 秒之间'
+ lockTtlRange: '分布式锁 TTL 必须在 1 到 86400 秒之间',
+ slaMinPercentRange: 'SLA 最低值必须在 0-100 之间',
+ ttftP99MaxRange: 'TTFT P99 最大值必须大于或等于 0',
+ requestErrorRateMaxRange: '请求错误率最大值必须在 0-100 之间',
+ upstreamErrorRateMaxRange: '上游错误率最大值必须在 0-100 之间'
}
},
email: {
@@ -2564,9 +2628,28 @@ export default {
aggregation: '预聚合任务',
enableAggregation: '启用预聚合任务',
aggregationHint: '预聚合可提升长时间窗口查询性能',
+ errorFiltering: '错误过滤',
+ ignoreCountTokensErrors: '忽略 count_tokens 错误',
+ ignoreCountTokensErrorsHint: '启用后,count_tokens 请求的错误将不会写入错误日志。',
+ ignoreContextCanceled: '忽略客户端断连错误',
+ ignoreContextCanceledHint: '启用后,客户端主动断开连接(context canceled)的错误将不会写入错误日志。',
+ ignoreNoAvailableAccounts: '忽略无可用账号错误',
+ ignoreNoAvailableAccountsHint: '启用后,“No available accounts” 错误将不会写入错误日志(不推荐,这通常是配置问题)。',
+ autoRefresh: '自动刷新',
+ enableAutoRefresh: '启用自动刷新',
+ enableAutoRefreshHint: '自动刷新仪表板数据,启用后会定期拉取最新数据。',
+ refreshInterval: '刷新间隔',
+ refreshInterval15s: '15 秒',
+ refreshInterval30s: '30 秒',
+ refreshInterval60s: '60 秒',
+ autoRefreshCountdown: '自动刷新:{seconds}s',
validation: {
title: '请先修正以下问题',
- retentionDaysRange: '保留天数必须在1-365天之间'
+ retentionDaysRange: '保留天数必须在1-365天之间',
+ slaMinPercentRange: 'SLA最低百分比必须在0-100之间',
+ ttftP99MaxRange: 'TTFT P99最大值必须大于等于0',
+ requestErrorRateMaxRange: '请求错误率最大值必须在0-100之间',
+ upstreamErrorRateMaxRange: '上游错误率最大值必须在0-100之间'
}
},
concurrency: {
diff --git a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue
index f92c6c50..c50524ac 100644
--- a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue
+++ b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue
@@ -826,7 +826,7 @@ function handleToolbarRefresh() {
- 自动刷新: {{ props.autoRefreshCountdown }}s
+ {{ t('admin.ops.settings.autoRefreshCountdown', { seconds: props.autoRefreshCountdown }) }}
@@ -1084,11 +1084,11 @@ function handleToolbarRefresh() {
{{ displayRealTimeQps.toFixed(1) }}
- QPS
+ {{ t('admin.ops.qps') }}
{{ displayRealTimeTps.toFixed(1) }}
- TPS
+ {{ t('admin.ops.tps') }}
@@ -1101,11 +1101,11 @@ function handleToolbarRefresh() {
{{ realtimeQpsPeakLabel }}
- QPS
+ {{ t('admin.ops.qps') }}
{{ realtimeTpsPeakLabel }}
- TPS
+ {{ t('admin.ops.tps') }}
@@ -1116,11 +1116,11 @@ function handleToolbarRefresh() {
{{ realtimeQpsAvgLabel }}
- QPS
+ {{ t('admin.ops.qps') }}
{{ realtimeTpsAvgLabel }}
- TPS
+ {{ t('admin.ops.tps') }}
@@ -1195,7 +1195,7 @@ function handleToolbarRefresh() {
- SLA
+ {{ t('admin.ops.sla') }}
@@ -1242,33 +1242,33 @@ function handleToolbarRefresh() {
{{ durationP99Ms ?? '-' }}
-
ms (P99)
+
{{ t('admin.ops.msP99') }}
- P95:
+ {{ t('admin.ops.p95') }}
{{ durationP95Ms ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
- P90:
+ {{ t('admin.ops.p90') }}
{{ durationP90Ms ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
- P50:
+ {{ t('admin.ops.p50') }}
{{ durationP50Ms ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
Avg:
{{ durationAvgMs ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
Max:
{{ durationMaxMs ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
@@ -1277,14 +1277,14 @@ function handleToolbarRefresh() {
- TTFT
+ {{ t('admin.ops.ttft') }}
@@ -1293,33 +1293,33 @@ function handleToolbarRefresh() {
{{ ttftP99Ms ?? '-' }}
-
ms (P99)
+
{{ t('admin.ops.msP99') }}
- P95:
+ {{ t('admin.ops.p95') }}
{{ ttftP95Ms ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
- P90:
+ {{ t('admin.ops.p90') }}
{{ ttftP90Ms ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
- P50:
+ {{ t('admin.ops.p50') }}
{{ ttftP50Ms ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
Avg:
{{ ttftAvgMs ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
Max:
{{ ttftMaxMs ?? '-' }}
- ms
+ {{ t('admin.ops.ms') }}
@@ -1384,7 +1384,7 @@ function handleToolbarRefresh() {
-
CPU
+
{{ t('admin.ops.cpu') }}
@@ -1398,7 +1398,7 @@ function handleToolbarRefresh() {
-
MEM
+
{{ t('admin.ops.mem') }}
@@ -1416,7 +1416,7 @@ function handleToolbarRefresh() {
-
DB
+
{{ t('admin.ops.db') }}
@@ -1433,7 +1433,7 @@ function handleToolbarRefresh() {
-
Redis
+
{{ t('admin.ops.redis') }}
diff --git a/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue b/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue
index db9cb80c..88af52e5 100644
--- a/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue
+++ b/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue
@@ -15,9 +15,9 @@
- Resolved:
+ {{ t('admin.ops.errorDetail.resolution') }}
- {{ (detail as any).resolved ? 'true' : 'false' }}
+ {{ (detail as any).resolved ? t('admin.ops.errorDetails.resolved') : t('admin.ops.errorDetails.unresolved') }}
@@ -28,7 +28,7 @@
:disabled="loading"
@click="markResolved(true)"
>
- {{ t('admin.ops.errorDetail.markResolved') || 'Mark resolved' }}
+ {{ t('admin.ops.errorDetail.markResolved') }}
-
-
-
-
+
+
+
+
@@ -102,7 +102,7 @@
-
{{ t('admin.ops.errorDetail.suggestion') || 'Suggestion' }}
+
{{ t('admin.ops.errorDetail.suggestion') }}
{{ handlingSuggestion }}
@@ -110,41 +110,41 @@
-
{{ t('admin.ops.errorDetail.classification') || 'Classification' }}
+
{{ t('admin.ops.errorDetail.classification') }}
-
phase
+
{{ t('admin.ops.errorDetail.classificationKeys.phase') }}
{{ detail.phase || '—' }}
-
owner
+
{{ t('admin.ops.errorDetail.classificationKeys.owner') }}
{{ (detail as any).error_owner || '—' }}
-
source
+
{{ t('admin.ops.errorDetail.classificationKeys.source') }}
{{ (detail as any).error_source || '—' }}
-
retryable
-
{{ (detail as any).is_retryable ? '✓' : '✗' }}
+
{{ t('admin.ops.errorDetail.classificationKeys.retryable') }}
+
{{ (detail as any).is_retryable ? t('common.yes') : t('common.no') }}
-
resolved_at
+
{{ t('admin.ops.errorDetail.classificationKeys.resolvedAt') }}
{{ (detail as any).resolved_at || '—' }}
-
resolved_by
+
{{ t('admin.ops.errorDetail.classificationKeys.resolvedBy') }}
{{ (detail as any).resolved_by_user_id ?? '—' }}
-
resolved_retry_id
+
{{ t('admin.ops.errorDetail.classificationKeys.resolvedRetryId') }}
{{ (detail as any).resolved_retry_id ?? '—' }}
-
retry_count
+
{{ t('admin.ops.errorDetail.classificationKeys.retryCount') }}
{{ (detail as any).retry_count ?? '—' }}
@@ -165,7 +165,7 @@
{{ t('admin.ops.errorDetail.group') }}
-
+
{{ detail.group_name || detail.group_id }}
—
@@ -174,7 +174,7 @@
{{ t('admin.ops.errorDetail.account') }}
-
+
{{ detail.account_name || detail.account_id }}
—
@@ -257,7 +257,7 @@
- {{ t('admin.ops.errorDetail.notRetryable') || 'Not retryable' }}
+ {{ t('admin.ops.errorDetail.notRetryable') }}
@@ -268,7 +268,7 @@
- pinned to original account_id
+ {{ t('admin.ops.errorDetail.pinnedToOriginalAccountId') }}
@@ -294,13 +294,13 @@
-
status
+
{{ t('admin.ops.errorDetail.upstreamKeys.status') }}
{{ detail.upstream_status_code != null ? detail.upstream_status_code : '—' }}
-
message
+
{{ t('admin.ops.errorDetail.upstreamKeys.message') }}
{{ detail.upstream_error_message || '—' }}
@@ -308,14 +308,14 @@
-
detail
+
{{ t('admin.ops.errorDetail.upstreamKeys.detail') }}
{{ prettyJSON(detail.upstream_error_detail) }}
-
upstream_errors
+
{{ t('admin.ops.errorDetail.upstreamKeys.upstreamErrors') }}
{{ t('admin.ops.errorDetail.retryUpstream') }} #{{ idx + 1 }}
@@ -346,15 +346,15 @@
- account:
-
+ {{ t('admin.ops.errorDetail.upstreamEvent.account') }}:
+
{{ ev.account_name || ev.account_id }}
—
-
status: {{ ev.upstream_status_code ?? '—' }}
+
{{ t('admin.ops.errorDetail.upstreamEvent.status') }}: {{ ev.upstream_status_code ?? '—' }}
- request_id: {{ ev.upstream_request_id || '—' }}
+ {{ t('admin.ops.errorDetail.upstreamEvent.requestId') }}: {{ ev.upstream_request_id || '—' }}
@@ -403,7 +403,7 @@
-
{{ t('admin.ops.errorDetail.retryHistory') || 'Retry History' }}
+
{{ t('admin.ops.errorDetail.retryHistory') }}
@@ -415,14 +415,14 @@
-
{{ t('admin.ops.errorDetail.compareA') || 'Compare A' }}
+
{{ t('admin.ops.errorDetail.compareA') }}
-
{{ t('admin.ops.errorDetail.compareB') || 'Compare B' }}
+
{{ t('admin.ops.errorDetail.compareB') }}