@@ -2,7 +2,7 @@ import { useState } from 'react';
22import { useQuery , useQueryClient } from '@tanstack/react-query' ;
33import { adminApi , quotaApi } from '../api/familyApi' ;
44import { useTrackedTask } from '../hooks/useTrackedTask' ;
5- import { RefreshCw , AlertTriangle } from 'lucide-react' ;
5+ import { RefreshCw , AlertTriangle , Terminal } from 'lucide-react' ;
66
77function formatBytes ( bytes : number ) : string {
88 if ( bytes >= 1024 * 1024 * 1024 ) return `${ ( bytes / ( 1024 * 1024 * 1024 ) ) . toFixed ( 1 ) } GB` ;
@@ -11,6 +11,90 @@ function formatBytes(bytes: number): string {
1111 return '0 KB' ;
1212}
1313
14+ const STATUS_COLORS : Record < string , string > = {
15+ Uploading : '#60a5fa' ,
16+ Pending : '#a78bfa' ,
17+ Processing : '#fbbf24' ,
18+ Transcoding : '#f97316' ,
19+ Complete : '#4ade80' ,
20+ Failed : '#f87171' ,
21+ } ;
22+
23+ function ActivityFeed ( ) {
24+ const { data : activity } = useQuery ( {
25+ queryKey : [ 'admin-activity' ] ,
26+ queryFn : adminApi . getActivity ,
27+ refetchInterval : 5000 ,
28+ } ) ;
29+ const [ expanded , setExpanded ] = useState ( true ) ;
30+
31+ if ( ! activity ) return null ;
32+
33+ return (
34+ < div className = "mb-8" >
35+ < button
36+ onClick = { ( ) => setExpanded ( ! expanded ) }
37+ className = "flex items-center gap-2 text-sm font-medium mb-2"
38+ style = { { color : 'var(--text-primary)' } }
39+ >
40+ < Terminal size = { 16 } />
41+ Recent Activity (last 30 items)
42+ < span className = "text-xs" style = { { color : 'var(--text-muted)' } } > { expanded ? '▼' : '▶' } </ span >
43+ </ button >
44+ { expanded && (
45+ < div
46+ className = "rounded-xl overflow-hidden font-mono text-xs"
47+ style = { { background : '#0d1117' , border : '1px solid #30363d' } }
48+ >
49+ < div className = "overflow-y-auto" style = { { maxHeight : '320px' } } >
50+ < table className = "w-full" >
51+ < thead >
52+ < tr style = { { borderBottom : '1px solid #30363d' , color : '#8b949e' } } >
53+ < th className = "text-left px-3 py-2 font-normal" > Status</ th >
54+ < th className = "text-left px-3 py-2 font-normal" > File</ th >
55+ < th className = "text-left px-3 py-2 font-normal" > Type</ th >
56+ < th className = "text-left px-3 py-2 font-normal" > Size</ th >
57+ < th className = "text-left px-3 py-2 font-normal" > Thumb</ th >
58+ < th className = "text-left px-3 py-2 font-normal" > Play</ th >
59+ < th className = "text-left px-3 py-2 font-normal" > Uploaded</ th >
60+ </ tr >
61+ </ thead >
62+ < tbody >
63+ { activity . map ( ( item ) => (
64+ < tr key = { item . id } style = { { borderBottom : '1px solid #21262d' } } >
65+ < td className = "px-3 py-1.5" >
66+ < span style = { { color : STATUS_COLORS [ item . status ] || '#8b949e' } } >
67+ { item . status === 'Processing' ? '⚙ ' : item . status === 'Complete' ? '✓ ' : item . status === 'Failed' ? '✗ ' : item . status === 'Pending' ? '◌ ' : item . status === 'Uploading' ? '↑ ' : '' }
68+ { item . status }
69+ </ span >
70+ </ td >
71+ < td className = "px-3 py-1.5 truncate max-w-[200px]" style = { { color : '#e6edf3' } } > { item . fileName } </ td >
72+ < td className = "px-3 py-1.5" style = { { color : '#8b949e' } } > { item . contentType . split ( '/' ) [ 1 ] || item . contentType } </ td >
73+ < td className = "px-3 py-1.5" style = { { color : '#8b949e' } } > { item . sizeMB } MB</ td >
74+ < td className = "px-3 py-1.5" >
75+ < span style = { { color : item . hasThumbnail ? '#4ade80' : '#f87171' } } > { item . hasThumbnail ? '✓' : '✗' } </ span >
76+ </ td >
77+ < td className = "px-3 py-1.5" >
78+ < span style = { { color : item . hasPlayback ? '#4ade80' : '#f87171' } } > { item . hasPlayback ? '✓' : '✗' } </ span >
79+ </ td >
80+ < td className = "px-3 py-1.5" style = { { color : '#8b949e' } } >
81+ { new Date ( item . uploadedAt ) . toLocaleTimeString ( ) }
82+ </ td >
83+ </ tr >
84+ ) ) }
85+ </ tbody >
86+ </ table >
87+ </ div >
88+ < div className = "px-3 py-1.5 text-[10px] flex justify-between" style = { { borderTop : '1px solid #30363d' , color : '#484f58' } } >
89+ < span > Auto-refreshing every 5s</ span >
90+ < span > { activity . length } items</ span >
91+ </ div >
92+ </ div >
93+ ) }
94+ </ div >
95+ ) ;
96+ }
97+
1498export function AdminPage ( ) {
1599 const queryClient = useQueryClient ( ) ;
16100 const { runTask } = useTrackedTask ( ) ;
@@ -195,6 +279,9 @@ export function AdminPage() {
195279 </ button >
196280 </ div >
197281
282+ { /* Recent Activity — terminal style */ }
283+ < ActivityFeed />
284+
198285 { /* Users */ }
199286 < h3 className = "text-lg font-medium mb-3" style = { { color : 'var(--text-primary)' } } > Users & Quotas </ h3 >
200287 < div className = "space-y-3" >
0 commit comments