fix(i18n): MonitoringTools 硬編碼中文 → i18n keys + MTTR 趨勢改為真實計算
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
- MonitoringTools: 載入中/無法連線/觸發/正常/離線/版本/統計/更新 → useTranslations - MTTR 趨勢: '↓2m' hardcode → 前半/後半 resolved incidents 真實比較 - zh-TW.json + en.json: 新增 connectionError/monitoringStatus.firing/metaVersion/metaStats/metaUpdatedAt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -148,8 +148,14 @@
|
||||
"monitoringStatus": {
|
||||
"up": "OK",
|
||||
"down": "Down",
|
||||
"unknown": "Unknown"
|
||||
}
|
||||
"unknown": "Unknown",
|
||||
"firing": "firing",
|
||||
"alert": "alerts"
|
||||
},
|
||||
"connectionError": "Connection failed",
|
||||
"metaVersion": "Version",
|
||||
"metaStats": "Stats",
|
||||
"metaUpdatedAt": "Updated"
|
||||
},
|
||||
"openclaw": {
|
||||
"name": "OpenClaw",
|
||||
@@ -911,4 +917,4 @@
|
||||
"noData": "--",
|
||||
"comingSoon": "Integration pending"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,8 +149,14 @@
|
||||
"monitoringStatus": {
|
||||
"up": "正常",
|
||||
"down": "離線",
|
||||
"unknown": "未知"
|
||||
}
|
||||
"unknown": "未知",
|
||||
"firing": "觸發",
|
||||
"alert": "告警"
|
||||
},
|
||||
"connectionError": "無法連線",
|
||||
"metaVersion": "版本",
|
||||
"metaStats": "統計",
|
||||
"metaUpdatedAt": "更新"
|
||||
},
|
||||
"openclaw": {
|
||||
"name": "OpenClaw",
|
||||
@@ -912,4 +918,4 @@
|
||||
"noData": "--",
|
||||
"comingSoon": "資料尚未整合"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,8 @@ const TOOL_EMOJI: Record<string, string> = {
|
||||
}
|
||||
|
||||
function MonitoringTools() {
|
||||
const tDash = useTranslations('dashboard')
|
||||
const tCommon = useTranslations('common')
|
||||
const [tools, setTools] = useState<MonitoringTool[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
@@ -125,10 +127,10 @@ function MonitoringTools() {
|
||||
}, [])
|
||||
|
||||
if (loading) return (
|
||||
<div style={{ padding: '12px 14px', fontSize: 12, color: '#87867f' }}>載入中...</div>
|
||||
<div style={{ padding: '12px 14px', fontSize: 12, color: '#87867f' }}>{tCommon('loading')}</div>
|
||||
)
|
||||
if (tools.length === 0) return (
|
||||
<div style={{ padding: '12px 14px', fontSize: 12, color: '#cc2200' }}>無法連線</div>
|
||||
<div style={{ padding: '12px 14px', fontSize: 12, color: '#cc2200' }}>{tDash('connectionError')}</div>
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -137,7 +139,7 @@ function MonitoringTools() {
|
||||
const isUp = tool.status === 'up'
|
||||
const hasFiring = (tool.firing_count ?? 0) > 0
|
||||
const statusColor = isUp ? (hasFiring ? '#F59E0B' : '#22C55E') : '#cc2200'
|
||||
const statusText = isUp ? (hasFiring ? `${tool.firing_count} 觸發` : '正常') : '離線'
|
||||
const statusText = isUp ? (hasFiring ? `${tool.firing_count} ${tDash('monitoringStatus.firing')}` : tDash('monitoringStatus.up')) : tDash('monitoringStatus.down')
|
||||
const accentColor = TOOL_ACCENT_COLOR[tool.name] ?? '#b0ad9f'
|
||||
const emoji = TOOL_EMOJI[tool.name] ?? '🔧'
|
||||
const link = TOOL_LINKS[tool.name] ?? '#'
|
||||
@@ -218,18 +220,18 @@ function MonitoringTools() {
|
||||
}}>
|
||||
{tool.version && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, paddingRight: 14, fontSize: 10 }}>
|
||||
<span style={{ color: '#b0ad9f', whiteSpace: 'nowrap' }}>版本</span>
|
||||
<span style={{ color: '#b0ad9f', whiteSpace: 'nowrap' }}>{tDash('metaVersion')}</span>
|
||||
<span style={{ color: '#141413', fontWeight: 600, whiteSpace: 'nowrap' }}>v{tool.version}</span>
|
||||
</div>
|
||||
)}
|
||||
{tool.stats && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, paddingRight: 14, fontSize: 10 }}>
|
||||
<span style={{ color: '#b0ad9f', whiteSpace: 'nowrap' }}>統計</span>
|
||||
<span style={{ color: '#b0ad9f', whiteSpace: 'nowrap' }}>{tDash('metaStats')}</span>
|
||||
<span style={{ color: '#141413', fontWeight: 600, whiteSpace: 'nowrap' }}>{tool.stats}</span>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 4, fontSize: 10 }}>
|
||||
<span style={{ color: '#b0ad9f', whiteSpace: 'nowrap' }}>更新</span>
|
||||
<span style={{ color: '#b0ad9f', whiteSpace: 'nowrap' }}>{tDash('metaUpdatedAt')}</span>
|
||||
<span style={{ color: '#141413', fontWeight: 600, whiteSpace: 'nowrap' }}>{timeStr}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -386,16 +388,29 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
return Math.round((resolved / incidents.length) * 100)
|
||||
})()
|
||||
|
||||
// MTTR 均值
|
||||
const mttrAvg = (() => {
|
||||
if (!incidents?.length) return '--'
|
||||
// MTTR 均值 + 趨勢(前半 vs 後半比較)
|
||||
const { mttrAvg, mttrTrend } = (() => {
|
||||
if (!incidents?.length) return { mttrAvg: '--', mttrTrend: undefined }
|
||||
const resolved = incidents.filter(i => i.updated_at && (i.status === 'resolved' || i.status === 'closed'))
|
||||
if (!resolved.length) return '--'
|
||||
const avgMs = resolved.reduce((sum, i) =>
|
||||
sum + (new Date(i.updated_at).getTime() - new Date(i.created_at).getTime()), 0
|
||||
) / resolved.length
|
||||
if (!resolved.length) return { mttrAvg: '--', mttrTrend: undefined }
|
||||
const durs = resolved.map(i => new Date(i.updated_at).getTime() - new Date(i.created_at).getTime())
|
||||
const avgMs = durs.reduce((a, b) => a + b, 0) / durs.length
|
||||
const mins = Math.round(avgMs / 60000)
|
||||
return mins < 60 ? `${mins}m` : `${(mins / 60).toFixed(1)}h`
|
||||
const avgStr = mins < 60 ? `${mins}m` : `${(mins / 60).toFixed(1)}h`
|
||||
// 趨勢:比較前半與後半(需至少4筆)
|
||||
let trend: { text: string; color: string } | undefined
|
||||
if (durs.length >= 4) {
|
||||
const half = Math.floor(durs.length / 2)
|
||||
const older = durs.slice(0, half).reduce((a, b) => a + b, 0) / half
|
||||
const newer = durs.slice(half).reduce((a, b) => a + b, 0) / (durs.length - half)
|
||||
const diffMins = Math.round((newer - older) / 60000)
|
||||
if (Math.abs(diffMins) >= 1) {
|
||||
trend = diffMins < 0
|
||||
? { text: `↓${Math.abs(diffMins)}m`, color: '#22C55E' }
|
||||
: { text: `↑${diffMins}m`, color: '#d97757' }
|
||||
}
|
||||
}
|
||||
return { mttrAvg: avgStr, mttrTrend: trend }
|
||||
})()
|
||||
|
||||
// (pulseMetrics reserved for future sparklines)
|
||||
@@ -483,7 +498,7 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
{
|
||||
label: tDashboard('mttrAvg'),
|
||||
value: mttrAvg,
|
||||
trend: mttrAvg !== '--' ? { text: '↓2m', color: '#22C55E' } : undefined,
|
||||
trend: mttrTrend,
|
||||
extra: (
|
||||
<svg width="60" height="12" viewBox="0 0 60 12" fill="none">
|
||||
<polyline points="0,2 10,3 20,2 30,5 40,4 50,7 60,9" stroke="#22C55E" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
|
||||
Reference in New Issue
Block a user