feat(drift): surface fingerprint state handoff
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 1m14s
CD Pipeline / build-and-deploy (push) Successful in 3m46s
CD Pipeline / post-deploy-checks (push) Successful in 1m35s

This commit is contained in:
Your Name
2026-05-19 00:39:49 +08:00
parent 55ab8732c5
commit 0b5268a666
7 changed files with 1316 additions and 7 deletions

View File

@@ -14,7 +14,7 @@ import { useTranslations } from 'next-intl'
import { cn } from '@/lib/utils'
import {
Diff, RefreshCw, AlertTriangle, CheckCircle2,
Clock, Terminal, GitMerge, Info,
Clock, Terminal, GitMerge, Info, Fingerprint, GitPullRequest,
} from 'lucide-react'
// =============================================================================
@@ -30,7 +30,7 @@ interface DriftReport {
medium_count: number
info_count: number
interpretation: string | null
status: 'pending' | 'resolved' | 'ignored'
status: 'pending' | 'acknowledged' | 'rolled_back' | 'adopted' | 'ignored'
created_at: string
resolved_at: string | null
}
@@ -45,6 +45,39 @@ interface ScanResult {
interpretation: string | null
}
interface DriftFingerprintState {
namespace?: string
fingerprint?: string
latest_report_id?: string
latest_status?: string
summary?: string
occurrences_12h?: number
fsm_state?: string
next_step?: string
high_count?: number
medium_count?: number
info_count?: number
open_pr?: {
number?: number | string | null
state?: string | null
file_count?: number | null
commit_count?: number | null
is_zero_diff?: boolean | null
lookup_error?: string | null
} | null
latest_handoff?: {
handoff_status?: string | null
} | null
p0_escalation?: {
suppresses_repeated_p0?: boolean | null
dedup_window_hours?: number | null
} | null
writes_drift_status?: boolean | null
writes_incident_state?: boolean | null
writes_auto_repair_result?: boolean | null
writes_ticket?: boolean | null
}
// =============================================================================
// Helpers
// =============================================================================
@@ -107,7 +140,9 @@ function DriftLevelBadge({ high, medium, info, t }: {
function StatusBadge({ status, t }: { status: DriftReport['status']; t: (k: string) => string }) {
const styles: Record<string, string> = {
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',
}
return (
@@ -120,6 +155,63 @@ function StatusBadge({ status, t }: { status: DriftReport['status']; t: (k: stri
)
}
function DriftFingerprintStateCard({
state,
t,
}: {
state: DriftFingerprintState | null
t: (k: string, values?: Record<string, string | number>) => string
}) {
if (!state) return null
return (
<div className="mx-6 mt-4 border border-neutral-200 bg-neutral-50 px-4 py-3">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="flex min-w-0 items-start gap-2">
<Fingerprint size={15} className="mt-0.5 shrink-0 text-neutral-500" />
<div className="min-w-0">
<p className="text-[12px] font-semibold text-neutral-800">
{t('fingerprintState.title')}
</p>
<p className="mt-1 truncate font-mono text-[11px] text-neutral-500">
{state.fingerprint ?? '--'}
</p>
</div>
</div>
<span className="border border-status-warning/20 bg-status-warning/10 px-2 py-0.5 text-[11px] font-medium text-status-warning">
{t('fingerprintState.occurrences', { count: state.occurrences_12h ?? 0 })}
</span>
</div>
<div className="mt-3 grid gap-2 text-[11px] text-neutral-500 md:grid-cols-2">
<p>{t('fingerprintState.report', { report: state.latest_report_id ?? '--' })}</p>
<p>{t('fingerprintState.state', { state: state.fsm_state ?? '--' })}</p>
<p>{t('fingerprintState.next', { step: state.next_step ?? '--' })}</p>
<p>
{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),
})}
</p>
</div>
<div className="mt-3 flex flex-wrap items-center gap-2 text-[11px] text-neutral-500">
<span className="inline-flex items-center gap-1 border border-neutral-200 bg-white px-2 py-0.5">
<GitPullRequest size={11} />
{t('fingerprintState.pr', {
pr: state.open_pr?.number ?? '--',
zeroDiff: String(state.open_pr?.is_zero_diff ?? false),
})}
</span>
<span className="border border-neutral-200 bg-white px-2 py-0.5">
{t('fingerprintState.p0Dedup', {
hours: state.p0_escalation?.dedup_window_hours ?? 24,
})}
</span>
</div>
</div>
)
}
// =============================================================================
// DriftPanel
// =============================================================================
@@ -130,6 +222,7 @@ export function DriftPanel() {
const [loading, setLoading] = useState(true)
const [scanning, setScanning] = useState(false)
const [scanResult, setScanResult] = useState<ScanResult | null>(null)
const [fingerprintState, setFingerprintState] = useState<DriftFingerprintState | null>(null)
const [error, setError] = useState<string | null>(null)
const fetchReports = useCallback(async () => {
@@ -139,7 +232,17 @@ export function DriftPanel() {
const res = await fetch(`${getApiBase()}/api/v1/drift/reports?limit=20`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = await res.json()
setReports(data.items ?? [])
const nextReports = data.items ?? []
setReports(nextReports)
const latestReportId = nextReports[0]?.report_id
if (latestReportId) {
const stateRes = await fetch(
`${getApiBase()}/api/v1/drift/fingerprints/state?report_id=${encodeURIComponent(latestReportId)}`
)
setFingerprintState(stateRes.ok ? await stateRes.json() : null)
} else {
setFingerprintState(null)
}
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to fetch')
} finally {
@@ -252,6 +355,8 @@ export function DriftPanel() {
</div>
)}
<DriftFingerprintStateCard state={fingerprintState} t={t} />
{/* Content */}
<div className="flex-1 px-6 py-4">
{loading && reports.length === 0 ? (