@@ -46,6 +46,7 @@ const state = {
4646 checkProcessIdOnNextMessage : false ,
4747 // Idle state tracking
4848 isIdle : false ,
49+ intentionalDisconnect : false ,
4950 idleTimeoutMinutes : 20 ,
5051 secondsUntilIdle : - 1 ,
5152 // Track processed event keys to avoid duplicate server event display
@@ -217,10 +218,8 @@ function initializeWebSocket() {
217218 wsDisconnectMessageTimeout = null ;
218219 }
219220
220- // Don't overwrite 'Idle' status if app is currently idle
221- if ( ! state . isIdle ) {
222- updateConnectionStatus ( 'connected' , 'Connected' ) ;
223- }
221+ state . intentionalDisconnect = false ;
222+ updateConnectionStatus ( 'connected' , 'Connected' ) ;
224223
225224 // Log reconnection if we were previously connected
226225 if ( wsDisconnectTime ) {
@@ -241,6 +240,11 @@ function initializeWebSocket() {
241240 } ;
242241
243242 state . wsConnection . onclose = ( event ) => {
243+ // If we intentionally disconnected for idle, don't reconnect or update status
244+ if ( state . intentionalDisconnect ) {
245+ return ;
246+ }
247+
244248 updateConnectionStatus ( 'disconnected' , 'Disconnected' ) ;
245249
246250 // Record disconnect time if not already set
@@ -282,6 +286,17 @@ function updateConnectionStatus(status, text) {
282286 }
283287}
284288
289+ /**
290+ * Ensures the WebSocket connection is open. If disconnected (e.g., after idle),
291+ * reconnects immediately. Called by simulation functions to wake the connection.
292+ */
293+ function ensureWebSocket ( ) {
294+ if ( ! state . wsConnection || state . wsConnection . readyState === WebSocket . CLOSED || state . wsConnection . readyState === WebSocket . CLOSING ) {
295+ state . intentionalDisconnect = false ;
296+ initializeWebSocket ( ) ;
297+ }
298+ }
299+
285300// ==========================================================================
286301// Metrics Handling
287302// ==========================================================================
@@ -357,11 +372,16 @@ function handleMetricsUpdate(message) {
357372 state . isIdle = idleData . is_idle ;
358373 state . secondsUntilIdle = idleData . seconds_until_idle || - 1 ;
359374
360- // Transition to idle
375+ // Transition to idle - stop probes and disconnect WebSocket
361376 if ( idleData . is_idle && ! wasIdle ) {
362377 stopLatencyProbe ( ) ;
363378 updateIdleDisplay ( true ) ;
364379 logEvent ( 'warning' , 'Application going idle, no health probes being sent. There will be gaps in diagnostics and logs.' ) ;
380+ // Intentionally disconnect WebSocket to prevent reconnect cycle flicker
381+ state . intentionalDisconnect = true ;
382+ if ( state . wsConnection ) {
383+ state . wsConnection . close ( ) ;
384+ }
365385 }
366386 // Transition from idle
367387 else if ( ! idleData . is_idle && wasIdle ) {
@@ -1107,6 +1127,7 @@ function getServerEventIcon(eventType) {
11071127// ==========================================================================
11081128
11091129async function triggerCpuStress ( ) {
1130+ ensureWebSocket ( ) ;
11101131 const duration = parseInt ( document . getElementById ( 'cpuDuration' ) . value ) || 30 ;
11111132 const level = document . getElementById ( 'cpuLevel' ) . value || 'high' ;
11121133
@@ -1135,6 +1156,7 @@ async function triggerCpuStress() {
11351156}
11361157
11371158async function stopCpuStress ( ) {
1159+ ensureWebSocket ( ) ;
11381160 try {
11391161 // Server broadcasts stop event via WebSocket
11401162 const response = await fetch ( `${ CONFIG . apiBaseUrl } /cpu/stop` , { method : 'POST' } ) ;
@@ -1149,6 +1171,7 @@ async function stopCpuStress() {
11491171}
11501172
11511173async function allocateMemory ( ) {
1174+ ensureWebSocket ( ) ;
11521175 const sizeMb = parseInt ( document . getElementById ( 'memorySize' ) . value ) || 100 ;
11531176
11541177 try {
@@ -1169,6 +1192,7 @@ async function allocateMemory() {
11691192}
11701193
11711194async function releaseMemory ( ) {
1195+ ensureWebSocket ( ) ;
11721196 try {
11731197 // Server broadcasts release event via WebSocket
11741198 const response = await fetch ( `${ CONFIG . apiBaseUrl } /memory/release-all` , { method : 'POST' } ) ;
@@ -1183,6 +1207,7 @@ async function releaseMemory() {
11831207}
11841208
11851209async function triggerThreadBlock ( ) {
1210+ ensureWebSocket ( ) ;
11861211 const delay = parseFloat ( document . getElementById ( 'threadDelay' ) . value ) || 10 ;
11871212 const count = parseInt ( document . getElementById ( 'threadConcurrent' ) . value ) || 10 ;
11881213
@@ -1208,6 +1233,7 @@ async function triggerThreadBlock() {
12081233}
12091234
12101235async function stopThreadBlock ( ) {
1236+ ensureWebSocket ( ) ;
12111237 try {
12121238 // Server logs stop event via WebSocket; no client-side duplicate needed
12131239 const response = await fetch ( `${ CONFIG . apiBaseUrl } /blocking/stop` , { method : 'POST' } ) ;
@@ -1222,6 +1248,7 @@ async function stopThreadBlock() {
12221248}
12231249
12241250async function startSlowRequests ( ) {
1251+ ensureWebSocket ( ) ;
12251252 const duration = parseInt ( document . getElementById ( 'slowRequestDuration' ) . value ) || 25 ;
12261253 const interval = parseFloat ( document . getElementById ( 'slowRequestInterval' ) . value ) || 2 ;
12271254 const maxRequests = parseInt ( document . getElementById ( 'slowRequestMax' ) . value ) || 10 ;
@@ -1248,6 +1275,7 @@ async function startSlowRequests() {
12481275}
12491276
12501277async function stopSlowRequests ( ) {
1278+ ensureWebSocket ( ) ;
12511279 try {
12521280 // Server broadcasts stop event via WebSocket
12531281 const response = await fetch ( `${ CONFIG . apiBaseUrl } /slow/stop` , { method : 'POST' } ) ;
@@ -1262,6 +1290,7 @@ async function stopSlowRequests() {
12621290}
12631291
12641292async function generateFailedRequests ( ) {
1293+ ensureWebSocket ( ) ;
12651294 const count = parseInt ( document . getElementById ( 'failedRequestCount' ) . value ) || 10 ;
12661295
12671296 try {
@@ -1282,6 +1311,7 @@ async function generateFailedRequests() {
12821311}
12831312
12841313async function triggerCrash ( ) {
1314+ ensureWebSocket ( ) ;
12851315 const crashType = document . getElementById ( 'crashType' ) . value ;
12861316 const crashTypeDisplay = crashType . charAt ( 0 ) . toUpperCase ( ) + crashType . slice ( 1 ) ;
12871317
@@ -1650,6 +1680,9 @@ document.addEventListener('DOMContentLoaded', async function() {
16501680 initSimulationIdCopyHandlers ( ) ;
16511681 await fetchConfig ( ) ;
16521682
1683+ // Wake the server if it's idle (page load counts as activity)
1684+ await recordActivity ( 'page_load' ) ;
1685+
16531686 // Connect to WebSocket
16541687 initializeWebSocket ( ) ;
16551688
0 commit comments