@ -14,6 +14,8 @@ document.addEventListener('DOMContentLoaded', () => {
const simStatusDot = document . getElementById ( 'sim-status-dot' ) ;
const simStatusDot = document . getElementById ( 'sim-status-dot' ) ;
const simStatusText = document . getElementById ( 'sim-status-text' ) ;
const simStatusText = document . getElementById ( 'sim-status-text' ) ;
const launchSimBtn = document . getElementById ( 'launch-sim-btn' ) ;
const launchSimBtn = document . getElementById ( 'launch-sim-btn' ) ;
const killSimBtn = document . getElementById ( 'kill-sim-btn' ) ;
const exitAllBtn = document . getElementById ( 'exit-all-btn' ) ;
// Idle Toggle Elements
// Idle Toggle Elements
const idleToggleContainer = document . getElementById ( 'idle-toggle-container' ) ;
const idleToggleContainer = document . getElementById ( 'idle-toggle-container' ) ;
@ -91,12 +93,14 @@ document.addEventListener('DOMContentLoaded', () => {
simStatusText . textContent = 'Ready' ;
simStatusText . textContent = 'Ready' ;
}
}
launchSimBtn . style . display = 'none' ;
launchSimBtn . style . display = 'none' ;
if ( killSimBtn ) killSimBtn . style . display = 'block' ;
} else if ( status === 'loading' ) {
} else if ( status === 'loading' ) {
isSimulatorRunning = false ;
isSimulatorRunning = false ;
if ( idleToggleContainer ) idleToggleContainer . style . display = 'none' ;
if ( idleToggleContainer ) idleToggleContainer . style . display = 'none' ;
simStatusDot . classList . add ( 'yellow' ) ;
simStatusDot . classList . add ( 'yellow' ) ;
simStatusText . textContent = 'Loading...' ;
simStatusText . textContent = 'Loading...' ;
launchSimBtn . style . display = 'none' ;
launchSimBtn . style . display = 'none' ;
if ( killSimBtn ) killSimBtn . style . display = 'block' ;
} else {
} else {
isSimulatorRunning = false ;
isSimulatorRunning = false ;
if ( idleToggleContainer ) idleToggleContainer . style . display = 'none' ;
if ( idleToggleContainer ) idleToggleContainer . style . display = 'none' ;
@ -105,6 +109,7 @@ document.addEventListener('DOMContentLoaded', () => {
simStatusDot . classList . add ( 'red' ) ;
simStatusDot . classList . add ( 'red' ) ;
simStatusText . textContent = 'Offline' ;
simStatusText . textContent = 'Offline' ;
launchSimBtn . style . display = 'block' ;
launchSimBtn . style . display = 'block' ;
if ( killSimBtn ) killSimBtn . style . display = 'none' ;
}
}
} )
} )
. catch ( ( ) => {
. catch ( ( ) => {
@ -123,6 +128,60 @@ document.addEventListener('DOMContentLoaded', () => {
fetch ( '/api/simulator/launch' , { method : 'POST' } ) ;
fetch ( '/api/simulator/launch' , { method : 'POST' } ) ;
} ) ;
} ) ;
// Kill CarlaUE4 action
if ( killSimBtn ) {
killSimBtn . addEventListener ( 'click' , ( ) => {
if ( confirm ( "Are you sure you want to FORCE KILL CarlaUE4?\n\nThis will terminate the engine immediately. Any unsaved simulation data or unsynced frames will be lost." ) ) {
fetch ( '/api/simulator/kill' , { method : 'POST' } )
. then ( res => res . json ( ) )
. then ( data => {
if ( data . success ) {
appendLog ( '> CarlaUE4 process terminated by user.' , 'warn' ) ;
updateSimulatorStatus ( ) ;
}
} )
. catch ( err => appendLog ( ` > Failed to kill simulator: ${ err . message } ` , 'error' ) ) ;
}
} ) ;
}
// EXIT ALL action
if ( exitAllBtn ) {
exitAllBtn . addEventListener ( 'click' , ( ) => {
if ( confirm ( "FULL SYSTEM EXIT\n\nThis will:\n1. Terminate CarlaUE4\n2. Shutdown the Dashboard Server\n3. Close this browser window\n\nProceed ? (use with caution)" ) ) {
appendLog ( '\n> [CRITICAL] Initiating full system shutdown...' , 'error' ) ;
// 1. Kill CARLA
fetch ( '/api/simulator/kill' , { method : 'POST' } ) ;
// 2. Shutdown Server
fetch ( '/api/shutdown' , { method : 'POST' } ) ;
// 3. UI Feedback and self-destruct window
setTimeout ( ( ) => {
document . body . innerHTML = `
< div style = "height:100vh; width: 100vw; position: fixed; top: 0; left: 0; z-index: 9999; display:flex; flex-direction:column; align-items:center; justify-content:center; background:radial-gradient(circle at center, #1a202c, #0d1117); color:white; font-family:'Inter', sans-serif; text-align:center;" >
< div style = "width: 80px; height: 80px; background: #f85149; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 2rem; box-shadow: 0 0 40px rgba(248, 81, 73, 0.6); animation: pulse 2s infinite;" >
< svg viewBox = "0 0 24 24" width = "40" height = "40" stroke = "white" stroke - width = "2" fill = "none" > < path d = "M18.36 6.64a9 9 0 1 1-12.73 0" > < / p a t h > < l i n e x 1 = " 1 2 " y 1 = " 2 " x 2 = " 1 2 " y 2 = " 1 2 " > < / l i n e > < / s v g >
< / d i v >
< h1 style = "font-size: 2.5rem; margin-bottom: 0.5rem; letter-spacing: -1.5px; font-weight: 800; background: linear-gradient(to bottom, #fff, #8b949e); -webkit-background-clip: text; -webkit-text-fill-color: transparent;" > SYSTEM OFFLINE < / h 1 >
< p style = "color: #8b949e; font-size: 1.2rem; max-width: 400px;" > All simulation processes and the dashboard server have been terminated . < / p >
< p style = "margin-top: 3rem; font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; color: #484f58; border: 1px solid #30363d; padding: 0.5rem 1rem; border-radius: 6px;" > You can now safely close this tab . < / p >
< style >
@ keyframes pulse {
0 % { transform : scale ( 1 ) ; box - shadow : 0 0 0 0 rgba ( 248 , 81 , 73 , 0.7 ) ; }
70 % { transform : scale ( 1.05 ) ; box - shadow : 0 0 0 20 px rgba ( 248 , 81 , 73 , 0 ) ; }
100 % { transform : scale ( 1 ) ; box - shadow : 0 0 0 0 rgba ( 248 , 81 , 73 , 0 ) ; }
}
< / s t y l e >
< / d i v >
` ;
setTimeout ( ( ) => window . close ( ) , 2000 ) ;
} , 500 ) ;
}
} ) ;
}
// Fetch Initial Config
// Fetch Initial Config
fetch ( '/api/config' )
fetch ( '/api/config' )
. then ( response => response . json ( ) )
. then ( response => response . json ( ) )