1+ import { useState , useEffect , useRef } from "react" ;
12import { useTerminalStore } from "~/stores/terminalStore" ;
23
4+ function SystemInfoPopup ( { onClose } : { onClose : ( ) => void } ) {
5+ const [ info , setInfo ] = useState < Record < string , string | null > | null > ( null ) ;
6+ const popupRef = useRef < HTMLDivElement > ( null ) ;
7+
8+ useEffect ( ( ) => {
9+ fetch ( "/api/system-info" )
10+ . then ( ( r ) => r . json ( ) )
11+ . then ( setInfo )
12+ . catch ( ( ) => setInfo ( { } ) ) ;
13+ } , [ ] ) ;
14+
15+ useEffect ( ( ) => {
16+ const handler = ( e : MouseEvent ) => {
17+ if ( popupRef . current && ! popupRef . current . contains ( e . target as Node ) ) onClose ( ) ;
18+ } ;
19+ document . addEventListener ( "mousedown" , handler ) ;
20+ return ( ) => document . removeEventListener ( "mousedown" , handler ) ;
21+ } , [ onClose ] ) ;
22+
23+ return (
24+ < div ref = { popupRef } className = "absolute right-2 top-10 z-50 bg-[#16162a] border border-gray-700 rounded-lg shadow-xl w-72 max-h-80 overflow-y-auto" >
25+ < div className = "flex items-center justify-between px-3 py-2 border-b border-gray-700" >
26+ < span className = "text-xs font-medium text-gray-300" > System Info</ span >
27+ < button onClick = { onClose } className = "text-gray-500 hover:text-white" >
28+ < svg className = "w-3.5 h-3.5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M6 18L18 6M6 6l12 12" /> </ svg >
29+ </ button >
30+ </ div >
31+ { ! info ? (
32+ < div className = "flex items-center justify-center py-4" >
33+ < svg className = "w-5 h-5 animate-spin text-blue-500" fill = "none" viewBox = "0 0 24 24" >
34+ < circle className = "opacity-25" cx = "12" cy = "12" r = "10" stroke = "currentColor" strokeWidth = "4" />
35+ < path className = "opacity-75" fill = "currentColor" d = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
36+ </ svg >
37+ </ div >
38+ ) : (
39+ < div className = "px-3 py-2 space-y-1" >
40+ { Object . entries ( info ) . map ( ( [ key , val ] ) => val ? (
41+ < div key = { key } className = "flex justify-between gap-2 text-[11px]" >
42+ < span className = "text-gray-500 shrink-0" > { key } </ span >
43+ < span className = "text-gray-300 text-right break-all" > { val } </ span >
44+ </ div >
45+ ) : null ) }
46+ </ div >
47+ ) }
48+ </ div >
49+ ) ;
50+ }
51+
352export default function Header ( ) {
453 const socketConnected = useTerminalStore ( ( s ) => s . socketConnected ) ;
554 const sessions = useTerminalStore ( ( s ) => s . sessions ) ;
55+ const [ showInfo , setShowInfo ] = useState ( false ) ;
656
7- // Check if any session is actually connected (PTY alive)
857 const hasActiveSessions = Object . values ( sessions ) . some (
958 ( s ) => s . status === "connected" || s . status === "connecting"
1059 ) ;
1160
12- // online = socket connected AND at least one session alive
13- // reconnecting = socket connected but all sessions dead (just reconnected)
14- // offline = socket disconnected
1561 const status = ! socketConnected
1662 ? "offline"
1763 : hasActiveSessions
@@ -21,12 +67,21 @@ export default function Header() {
2167 : "online" ;
2268
2369 return (
24- < header className = "flex items-center justify-between px-3 py-1.5 bg-[#0d0d1a] border-b border-gray-800 shrink-0" >
70+ < header className = "flex items-center justify-between px-3 py-1.5 bg-[#0d0d1a] border-b border-gray-800 shrink-0 relative " >
2571 < div className = "flex items-center gap-2" >
2672 < img src = "/logo-square.png" alt = "OTG Code" className = "w-6 h-6 rounded" />
2773 < span className = "text-white font-bold text-sm" > OTG Code</ span >
2874 </ div >
2975 < div className = "flex items-center gap-2" >
76+ < button
77+ onClick = { ( ) => setShowInfo ( ! showInfo ) }
78+ className = "p-1 text-gray-500 hover:text-white transition-colors rounded"
79+ title = "System info"
80+ >
81+ < svg className = "w-3.5 h-3.5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" strokeWidth = { 2 } >
82+ < path strokeLinecap = "round" strokeLinejoin = "round" d = "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
83+ </ svg >
84+ </ button >
3085 < span
3186 className = { `w-2 h-2 rounded-full ${
3287 status === "online"
@@ -46,6 +101,7 @@ export default function Header() {
46101 { status }
47102 </ span >
48103 </ div >
104+ { showInfo && < SystemInfoPopup onClose = { ( ) => setShowInfo ( false ) } /> }
49105 </ header >
50106 ) ;
51107}
0 commit comments