33import { useState , useEffect , useCallback } from 'react'
44import Link from 'next/link'
55import { apiFetch } from '@/lib/api'
6- import { formatRelativeTime , formatDuration , formatCmd } from '@/lib/format'
6+ import { formatRelativeTime , formatDuration , formatCmd , formatBytes } from '@/lib/format'
7+ import StatusBadge from '@/components/ui/StatusBadge'
8+ import EmptyState from '@/components/ui/EmptyState'
79import type {
810 ReplayBundle ,
911 ReplayExec ,
1012 ReplayArtifact ,
1113 ExecOutputEntry ,
1214} from '@sandchest/contract'
1315
14- const STATUS_COLORS : Record < string , string > = {
15- queued : 'var(--color-text-weak)' ,
16- provisioning : 'hsl(40, 80%, 60%)' ,
17- running : 'hsl(140, 60%, 50%)' ,
18- stopping : 'hsl(40, 80%, 60%)' ,
19- stopped : 'var(--color-text-weak)' ,
20- failed : 'hsl(0, 70%, 60%)' ,
21- deleted : 'var(--color-text-weak)' ,
22- in_progress : 'hsl(140, 60%, 50%)' ,
23- complete : 'var(--color-text-weak)' ,
24- }
25-
2616function ExecCard ( { exec } : { exec : ReplayExec } ) {
2717 const [ expanded , setExpanded ] = useState ( false )
2818 const [ output , setOutput ] = useState < ExecOutputEntry [ ] | null > ( null )
@@ -106,8 +96,7 @@ function ExecCard({ exec }: { exec: ReplayExec }) {
10696 { exec . resource_usage && (
10797 < div className = "replay-exec-resources" >
10898 CPU: { exec . resource_usage . cpu_ms } ms | Memory:{ ' ' }
109- { ( exec . resource_usage . peak_memory_bytes / 1024 / 1024 ) . toFixed ( 1 ) }
110- MB
99+ { formatBytes ( exec . resource_usage . peak_memory_bytes ) }
111100 </ div >
112101 ) }
113102 </ div >
@@ -117,13 +106,6 @@ function ExecCard({ exec }: { exec: ReplayExec }) {
117106}
118107
119108function ArtifactRow ( { artifact } : { artifact : ReplayArtifact } ) {
120- const sizeStr =
121- artifact . bytes < 1024
122- ? `${ artifact . bytes } B`
123- : artifact . bytes < 1048576
124- ? `${ ( artifact . bytes / 1024 ) . toFixed ( 1 ) } KB`
125- : `${ ( artifact . bytes / 1048576 ) . toFixed ( 1 ) } MB`
126-
127109 return (
128110 < tr >
129111 < td >
@@ -136,7 +118,7 @@ function ArtifactRow({ artifact }: { artifact: ReplayArtifact }) {
136118 </ a >
137119 </ td >
138120 < td className = "replay-text-weak" > { artifact . mime } </ td >
139- < td className = "replay-text-weak" > { sizeStr } </ td >
121+ < td className = "replay-text-weak" > { formatBytes ( artifact . bytes ) } </ td >
140122 </ tr >
141123 )
142124}
@@ -177,7 +159,7 @@ export default function ReplayViewer({ sandboxId }: ReplayViewerProps) {
177159 if ( loading ) {
178160 return (
179161 < div className = "replay-container" >
180- < div className = "replay-loading" > Loading replay...</ div >
162+ < EmptyState message = " Loading replay..." className = "replay-loading" / >
181163 </ div >
182164 )
183165 }
@@ -210,12 +192,11 @@ export default function ReplayViewer({ sandboxId }: ReplayViewerProps) {
210192 < span className = "replay-sandbox-id" > { bundle . sandbox_id } </ span >
211193 </ div >
212194 < div className = "replay-header-right" >
213- < span
195+ < StatusBadge
196+ status = { bundle . status }
197+ label = { bundle . status === 'in_progress' ? 'live' : 'complete' }
214198 className = "replay-status"
215- style = { { color : STATUS_COLORS [ bundle . status ] ?? 'var(--color-text)' } }
216- >
217- { bundle . status === 'in_progress' ? 'live' : 'complete' }
218- </ span >
199+ />
219200 </ div >
220201 </ header >
221202
@@ -283,7 +264,7 @@ export default function ReplayViewer({ sandboxId }: ReplayViewerProps) {
283264 { activeTab === 'execs' && (
284265 < div className = "replay-exec-list" >
285266 { bundle . execs . length === 0 ? (
286- < div className = "replay-empty" > No executions recorded.</ div >
267+ < EmptyState message = " No executions recorded." className = "replay-empty" / >
287268 ) : (
288269 bundle . execs . map ( ( exec ) => (
289270 < ExecCard key = { exec . exec_id } exec = { exec } />
@@ -295,7 +276,7 @@ export default function ReplayViewer({ sandboxId }: ReplayViewerProps) {
295276 { activeTab === 'sessions' && (
296277 < div className = "replay-session-list" >
297278 { bundle . sessions . length === 0 ? (
298- < div className = "replay-empty" > No sessions recorded.</ div >
279+ < EmptyState message = " No sessions recorded." className = "replay-empty" / >
299280 ) : (
300281 < table className = "replay-table" >
301282 < thead >
@@ -319,15 +300,9 @@ export default function ReplayViewer({ sandboxId }: ReplayViewerProps) {
319300 { formatRelativeTime ( session . created_at ) }
320301 </ td >
321302 < td >
322- < span
323- style = { {
324- color : session . destroyed_at
325- ? 'var(--color-text-weak)'
326- : 'hsl(140, 60%, 50%)' ,
327- } }
328- >
329- { session . destroyed_at ? 'destroyed' : 'active' }
330- </ span >
303+ < StatusBadge
304+ status = { session . destroyed_at ? 'destroyed' : 'active' }
305+ />
331306 </ td >
332307 </ tr >
333308 ) ) }
@@ -340,7 +315,7 @@ export default function ReplayViewer({ sandboxId }: ReplayViewerProps) {
340315 { activeTab === 'artifacts' && (
341316 < div className = "replay-artifact-list" >
342317 { bundle . artifacts . length === 0 ? (
343- < div className = "replay-empty" > No artifacts collected.</ div >
318+ < EmptyState message = " No artifacts collected." className = "replay-empty" / >
344319 ) : (
345320 < table className = "replay-table" >
346321 < thead >
0 commit comments