diff --git a/dashboard/app.py b/dashboard/app.py index 7711ea3..5fd32dc 100644 --- a/dashboard/app.py +++ b/dashboard/app.py @@ -112,7 +112,7 @@ def simulator_status(): try: import carla client = carla.Client('localhost', 2000) - client.set_timeout(10.0) + client.set_timeout(5.0) client.get_server_version() # Will raise if not ready status = "ready" world = client.get_world() diff --git a/dashboard/static/app.js b/dashboard/static/app.js index b780af7..dc7ec18 100644 --- a/dashboard/static/app.js +++ b/dashboard/static/app.js @@ -23,6 +23,7 @@ document.addEventListener('DOMContentLoaded', () => { let currentEventSource = null; let idleTimer = null; + let manualIdleOverride = false; let isSimulatorRunning = false; let isSimulationActive = false; // user currently running a scenario @@ -43,8 +44,8 @@ document.addEventListener('DOMContentLoaded', () => { // Auto Idle Tracker function resetIdleTimer() { if (idleTimer) clearTimeout(idleTimer); - // If simulation is not active and simulator is ready but NOT paused - if (!isSimulationActive && isSimulatorRunning && gpuIdleToggle && !gpuIdleToggle.checked) { + // If simulation is not active and simulator is ready but NOT paused AND no manual override + if (!isSimulationActive && isSimulatorRunning && gpuIdleToggle && !gpuIdleToggle.checked && !manualIdleOverride) { idleTimer = setTimeout(() => { console.log("Auto-idling simulator due to 30s inactivity..."); gpuIdleToggle.checked = true; @@ -56,11 +57,23 @@ document.addEventListener('DOMContentLoaded', () => { if (gpuIdleToggle) { gpuIdleToggle.addEventListener('change', (e) => { const isIdle = e.target.checked; + + // If manual, toggle the override flag based on the NEW state + if (e.isTrusted) { + // OFF position (isIdle: false) means Manual Wake. + // ON position (isIdle: true) means Auto-Idle enabled. + manualIdleOverride = !isIdle; + console.log(`Auto-Idle system: ${manualIdleOverride ? 'DISABLED (Manual Wake)' : 'ENABLED (Auto mode)'}`); + } + const endpoint = isIdle ? '/api/simulator/idle' : '/api/simulator/wake'; fetch(endpoint, { method: 'POST' }); + if (!isIdle) { + // If we woke it up, we only start the timer if override is not active resetIdleTimer(); } else { + // If we paused it, clear any existing timers if (idleTimer) clearTimeout(idleTimer); } }); @@ -132,6 +145,10 @@ document.addEventListener('DOMContentLoaded', () => { 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.")) { + // Immediate UI Feedback + simStatusText.textContent = 'Terminating...'; + simStatusDot.className = 'dot yellow'; + fetch('/api/simulator/kill', { method: 'POST' }) .then(res => res.json()) .then(data => { diff --git a/dashboard/static/style.css b/dashboard/static/style.css index e6888a4..1ef2227 100644 --- a/dashboard/static/style.css +++ b/dashboard/static/style.css @@ -711,18 +711,42 @@ input:checked + .slider:before { } .small-switch { - width: 34px; - height: 18px; + width: 46px; + height: 20px; } .small-switch .slider:before { - height: 12px; - width: 12px; + height: 14px; + width: 14px; bottom: 3px; left: 3px; } +.small-switch input:checked + .slider { + background-color: var(--success); +} + .small-switch input:checked + .slider:before { - transform: translateX(16px); + transform: translateX(26px); +} + +.small-switch .slider::after { + content: "AUTO"; + position: absolute; + right: 22px; + top: 50%; + transform: translateY(-50%); + font-size: 7px; + font-weight: 500; + font-family: 'JetBrains Mono', monospace; + color: white; + opacity: 0; + transition: opacity 0.2s; + letter-spacing: 0.5px; + pointer-events: none; +} + +.small-switch input:checked + .slider::after { + opacity: 1; } diff --git a/dashboard/templates/index.html b/dashboard/templates/index.html index ff170de..f585658 100644 --- a/dashboard/templates/index.html +++ b/dashboard/templates/index.html @@ -38,7 +38,7 @@