-
{t('riskLevel')}
-
-
+
+
{t('riskLevel')}
+
+
{eval_.risk_level}
{eval_.success_rate != null && (
-
-
{t('successRate')}
-
{(eval_.success_rate * 100).toFixed(1)}%
+
+
{t('successRate')}
+
{(eval_.success_rate * 100).toFixed(1)}%
)}
{eval_.total_executions != null && (
-
-
{t('execCount')}
-
{eval_.total_executions}
+
+
{t('execCount')}
+
{eval_.total_executions}
)}
-
-
{t('decisionReason')}
-
{eval_.reason}
+
+
{t('decisionReason')}
+
{eval_.reason}
{result && (
-
+
{result.success
- ?
- : }
-
+ ?
+ : }
+
{result.success ? t('execSuccess', { ms: result.execution_time_ms }) : t('execFailed', { error: result.error ?? '' })}
{result.executed_steps.length > 0 && (
-
+
{result.executed_steps.map((step, i) => (
- -
-
+
-
+
{step}
))}
@@ -237,20 +217,22 @@ function IncidentEvalRow({
)}
{eval_.can_auto_repair && !result && (
-
+
+
+
)}
)}
@@ -264,7 +246,6 @@ function IncidentEvalRow({
export function AutoRepairPanel() {
const t = useTranslations('autoRepair')
- const tNav = useTranslations('nav')
const tCommon = useTranslations('common')
const [stats, setStats] = useState
(null)
@@ -314,70 +295,117 @@ export function AutoRepairPanel() {
return () => { abortRef.current?.abort() }
}, [fetchStats])
+ const statCards = stats ? [
+ {
+ label: t('approvedPlaybooks'),
+ value: stats.approved_playbooks.toLocaleString(),
+ icon: Activity,
+ accent: 'text-[#4A90D9]',
+ bg: 'bg-[#f0f6ff]',
+ border: 'border-[#c7dcf7]',
+ },
+ {
+ label: t('highQualityPlaybooks'),
+ value: stats.high_quality_playbooks.toLocaleString(),
+ icon: CheckCircle2,
+ accent: 'text-[#17602a]',
+ bg: 'bg-[#f0faf2]',
+ border: 'border-[#8fc29a]',
+ },
+ {
+ label: t('totalExecutions'),
+ value: stats.total_executions.toLocaleString(),
+ icon: Zap,
+ accent: 'text-[#d97757]',
+ bg: 'bg-[#fff7f3]',
+ border: 'border-[#f5cdb8]',
+ },
+ {
+ label: t('overallSuccessRate'),
+ value: `${(stats.overall_success_rate * 100).toFixed(1)}%`,
+ icon: TrendingUp,
+ accent: 'text-[#8a5a08]',
+ bg: 'bg-[#fff7e8]',
+ border: 'border-[#d9b36f]',
+ },
+ ] : []
+
return (
- <>
+
{/* Header */}
-
-
-
-
- {tNav('autoRepair')}
-
-
+
+
+
+ Automation
+
+
+ {t('title')}
+
+
{t('subtitle')}
{/* Error */}
{statsError && (
-
-
-
{statsError}
+
+
+
+
{t('error')}
+
{statsError}
+
)}
- {/* Stats */}
+ {/* Stats Cards */}
{stats && (
-
-
-
-
-
+
+ {statCards.map(({ label, value, icon: Icon, accent, bg, border }) => (
+
+ ))}
)}
{/* Disposition summary */}
{disposition && disposition.total > 0 && (
-
-
-
{t('dispositionAuto')}
-
{disposition.auto_repair}
+
+
+
{t('dispositionAuto')}
+
{disposition.auto_repair}
-
-
{t('dispositionHuman')}
-
{disposition.human_approved}
+
+
{t('dispositionHuman')}
+
{disposition.human_approved}
-
-
{t('dispositionManual')}
-
{disposition.manual_resolved}
+
+
{t('dispositionManual')}
+
{disposition.manual_resolved}
-
-
{t('dispositionCold')}
-
{disposition.cold_start_trust}
+
+
{t('dispositionCold')}
+
{disposition.cold_start_trust}
)}
@@ -385,19 +413,24 @@ export function AutoRepairPanel() {
{/* Eligible indicator */}
{stats && (
- {stats.auto_repair_eligible
- ?
- :
}
+
+ {stats.auto_repair_eligible
+ ?
+ : }
+
-
+
{stats.auto_repair_eligible ? t('ready') : t('notReady')}
-
+
{stats.auto_repair_eligible
? t('readyDesc', { count: stats.high_quality_playbooks })
: t('notReadyDesc')}
@@ -407,34 +440,37 @@ export function AutoRepairPanel() {
)}
{/* Incident evaluation list */}
-
-
- {t('incidentEval')}
-
-
- {incidentsLoading && (
-
-
-
- )}
-
- {!incidentsLoading && eligibleIncidents.length === 0 && (
-
- )}
-
-
- {eligibleIncidents.map(incident => (
-
- ))}
+
+
+
+
+ {t('incidentEval')}
+
+
+
+ {incidentsLoading ? (
+
+
+ {tCommon('loading')}
+
+ ) : eligibleIncidents.length === 0 ? (
+
+
+ {t('noEligible')}
+
+ ) : (
+
+ {eligibleIncidents.map(incident => (
+
+ ))}
+
+ )}
- >
+
)
}
diff --git a/apps/web/src/components/panels/DriftPanel.tsx b/apps/web/src/components/panels/DriftPanel.tsx
index bf025d2ea..e7a7d37c4 100644
--- a/apps/web/src/components/panels/DriftPanel.tsx
+++ b/apps/web/src/components/panels/DriftPanel.tsx
@@ -7,6 +7,7 @@
* 供原始頁面和整合頁面 (/automation) 共用
*
* 建立時間: 2026-04-09 (台北時區)
+ * 更新時間: 2026-07-04 — UI/UX 全面升級 (Option A 明亮模式)
*/
import { useState, useEffect, useCallback } from 'react'
@@ -142,8 +143,8 @@ function DriftLevelBadge({ high, medium, info, t }: {
}) {
if (high === 0 && medium === 0 && info === 0) {
return (
-
-
+
+
{t('noDrift')}
)
@@ -151,19 +152,19 @@ function DriftLevelBadge({ high, medium, info, t }: {
return (
{high > 0 && (
-
-
+
+
{t('highCount')} {high}
)}
{medium > 0 && (
-
-
+
+
{t('mediumCount')} {medium}
)}
{info > 0 && (
-
+
{t('infoCount')} {info}
)}
@@ -173,16 +174,16 @@ function DriftLevelBadge({ high, medium, info, t }: {
function StatusBadge({ status, t }: { status: DriftReport['status']; t: (k: string) => string }) {
const styles: Record = {
- pending: 'bg-status-warning/10 text-status-warning border-status-warning/20',
- resolved: 'bg-status-healthy/10 text-status-healthy border-status-healthy/20',
- acknowledged: 'bg-neutral-100 text-neutral-500 border-neutral-200',
- rolled_back: 'bg-status-healthy/10 text-status-healthy border-status-healthy/20',
- adopted: 'bg-status-healthy/10 text-status-healthy border-status-healthy/20',
- ignored: 'bg-neutral-100 text-neutral-400 border-neutral-200',
+ pending: 'bg-[#fff7e8] text-[#8a5a08] border-[#d9b36f]',
+ resolved: 'bg-[#f0faf2] text-[#17602a] border-[#8fc29a]',
+ acknowledged: 'bg-[#f5f4ed] text-[#5f5b52] border-[#e0ddd4]',
+ rolled_back: 'bg-[#f0faf2] text-[#17602a] border-[#8fc29a]',
+ adopted: 'bg-[#f0faf2] text-[#17602a] border-[#8fc29a]',
+ ignored: 'bg-[#faf9f3] text-[#77736a] border-[#e0ddd4]',
}
return (
{t(status)}
@@ -199,75 +200,77 @@ function DriftFingerprintStateCard({
}) {
if (!state) return null
return (
-
-
+
+
-
+
-
+
{t('fingerprintState.title')}
-
+
{state.fingerprint ?? '--'}
-
+
{t('fingerprintState.occurrences', { count: state.occurrences_12h ?? 0 })}
-
-
{t('fingerprintState.report', { report: state.latest_report_id ?? '--' })}
-
{t('fingerprintState.state', { state: state.fsm_state ?? '--' })}
-
{t('fingerprintState.next', { step: state.next_step ?? '--' })}
-
- {t('fingerprintState.writes', {
- drift: String(state.writes_drift_status ?? false),
- incident: String(state.writes_incident_state ?? false),
- repair: String(state.writes_auto_repair_result ?? false),
- ticket: String(state.writes_ticket ?? false),
- })}
-
-
-
-
-
- {t('fingerprintState.pr', {
- pr: state.open_pr?.number ?? '--',
- zeroDiff: String(state.open_pr?.is_zero_diff ?? false),
- })}
-
-
- {t('fingerprintState.p0Dedup', {
- hours: state.p0_escalation?.dedup_window_hours ?? 24,
- })}
-
-
- {t('fingerprintState.remediation', {
- status: state.latest_remediation?.remediation_status ?? '--',
- report: state.latest_remediation?.verification_report_id ?? '--',
- })}
-
-
- {state.latest_remediation ? (
-
-
- {t('fingerprintState.remediationKind', {
- kind: state.latest_remediation.remediation_kind ?? '--',
- })}
-
-
- {t('fingerprintState.remediationVerification', {
- summary: state.latest_remediation.verification_summary?.summary ?? '--',
- })}
-
-
- {t('fingerprintState.remediationNote', {
- note: state.latest_remediation.note ?? '--',
+
+
+
Report: {state.latest_report_id ?? '--'}
+
State: {state.fsm_state ?? '--'}
+
Next: {state.next_step ?? '--'}
+
+ {t('fingerprintState.writes', {
+ drift: String(state.writes_drift_status ?? false),
+ incident: String(state.writes_incident_state ?? false),
+ repair: String(state.writes_auto_repair_result ?? false),
+ ticket: String(state.writes_ticket ?? false),
})}
- ) : null}
+
+
+
+ {t('fingerprintState.pr', {
+ pr: state.open_pr?.number ?? '--',
+ zeroDiff: String(state.open_pr?.is_zero_diff ?? false),
+ })}
+
+
+ {t('fingerprintState.p0Dedup', {
+ hours: state.p0_escalation?.dedup_window_hours ?? 24,
+ })}
+
+
+ {t('fingerprintState.remediation', {
+ status: state.latest_remediation?.remediation_status ?? '--',
+ report: state.latest_remediation?.verification_report_id ?? '--',
+ })}
+
+
+ {state.latest_remediation ? (
+
+
+ {t('fingerprintState.remediationKind', {
+ kind: '',
+ })} {state.latest_remediation.remediation_kind ?? '--'}
+
+
+ {t('fingerprintState.remediationVerification', {
+ summary: '',
+ })} {state.latest_remediation.verification_summary?.summary ?? '--'}
+
+
+ {t('fingerprintState.remediationNote', {
+ note: '',
+ })} {state.latest_remediation.note ?? '--'}
+
+
+ ) : null}
+
)
}
@@ -333,46 +336,46 @@ export function DriftPanel() {
}
return (
-
+
{/* Header */}
-
-
-
-
-
- {t('title')}
-
-
- {t('subtitle')}
-
-
+
+
+
+ Infrastructure
+
+
+ {t('title')}
+
+
+ {t('subtitle')}
+