TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use client";2import type { FetchTiming } from "@fetcha/core/client";3import { formatMs } from "@/lib/format";4import { cn } from "@/lib/utils";56const PHASES: Array<{ key: keyof FetchTiming; label: string; hint: string; color: string }> = [7 { key: "dns_ms", label: "DNS", hint: "Hostname resolution and SSRF validation", color: "bg-fg-subtle" },8 { key: "proxy_connect_ms", label: "Network connect", hint: "Connecting through the selected network class", color: "bg-info" },9 { key: "tls_ms", label: "TLS", hint: "TLS handshake with the target", color: "bg-accent" },10 { key: "origin_ms", label: "Origin", hint: "Time to receive the full response from the target", color: "bg-success" },11 { key: "processing_ms", label: "Processing", hint: "Decoding, format conversion and metadata", color: "bg-warning" },12];1314/** Horizontal breakdown of the request timing phases. */15export function TimingBars({ timing, totalFallbackMs }: { timing: FetchTiming | undefined; totalFallbackMs: number }) {16 if (!timing) {17 return <p className="text-sm text-fg-muted">No timing breakdown was returned for this request. Total duration: <span className="font-mono tabular">{formatMs(totalFallbackMs)}</span>.</p>;18 }19 const sum = PHASES.reduce((a, p) => a + Math.max(0, timing[p.key] ?? 0), 0);20 const total = Math.max(timing.total_ms || 0, sum, 1);21 return (22 <div className="grid gap-4">23 <div className="flex h-3 w-full overflow-hidden rounded-sm bg-bg-muted" role="img" aria-label={`Timing breakdown, total ${formatMs(total)}`}>24 {PHASES.map((p) => {25 const v = Math.max(0, timing[p.key] ?? 0);26 if (v <= 0) return null;27 return <div key={p.key} className={cn("h-full", p.color)} style={{ width: `${(v / total) * 100}%` }} title={`${p.label}: ${formatMs(v)}`} />;28 })}29 </div>30 <dl className="grid gap-2">31 {PHASES.map((p) => {32 const v = Math.max(0, timing[p.key] ?? 0);33 const pct = total ? (v / total) * 100 : 0;34 return (35 <div key={p.key} className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 sm:grid-cols-[9rem_minmax(0,1fr)_5.5rem]">36 <dt className="flex items-center gap-2 text-[13px]">37 <span className={cn("size-2 rounded-[2px]", p.color)} aria-hidden />38 <span className="font-medium">{p.label}</span>39 <span className="hidden text-fg-subtle sm:inline">· {p.hint}</span>40 </dt>41 <div className="hidden h-1.5 rounded-full bg-bg-muted sm:block" aria-hidden>42 <div className={cn("h-full rounded-full", p.color)} style={{ width: `${Math.min(100, pct)}%` }} />43 </div>44 <dd className="text-right font-mono text-[12.5px] tabular text-fg-muted">45 {formatMs(v)} <span className="text-fg-subtle">({pct.toFixed(0)}%)</span>46 </dd>47 </div>48 );49 })}50 <div className="mt-1 flex items-center justify-between border-t border-border pt-2 text-[13px]">51 <span className="font-medium">Total</span>52 <span className="font-mono tabular">{formatMs(timing.total_ms || total)}</span>53 </div>54 </dl>55 </div>56 );57}58