From 2c5cb07cdbb9a5227d0079acf607edb4972343bb Mon Sep 17 00:00:00 2001 From: kodi Date: Fri, 6 Mar 2026 19:55:31 +0100 Subject: [PATCH] feat (ui): exec fase 5 --- webui/html/assets/js/tabs/containers.js | 79 ++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/webui/html/assets/js/tabs/containers.js b/webui/html/assets/js/tabs/containers.js index 9a44007..4538226 100644 --- a/webui/html/assets/js/tabs/containers.js +++ b/webui/html/assets/js/tabs/containers.js @@ -420,6 +420,9 @@ let execTerminalState = { outputText: '', outputLineCount: 0, outputTruncated: false, + sessionInfo: null, + infoPollTimer: null, + infoTickTimer: null, }; const EXEC_OUTPUT_MAX_CHARS = 200000; @@ -430,6 +433,8 @@ const EXEC_RECONNECT_BASE_MS = 350; const EXEC_RECONNECT_MAX_MS = 5000; const EXEC_RECONNECT_MAX_ATTEMPTS = 8; const EXEC_PASTE_CHUNK_SIZE = 2048; +const EXEC_SESSION_INFO_POLL_MS = 7000; +const EXEC_IDLE_TTL_SECONDS = 60 * 60; function _execEls() { return { @@ -526,7 +531,73 @@ function _execInitTerminal() { function _execConnectedStatus() { const sid = execTerminalState.sessionId || ''; - return sid ? `Connected (${sid})` : 'Connected'; + if (!sid) return 'Connected'; + const info = execTerminalState.sessionInfo; + if (!info) return `Connected (${sid})`; + if (info.closed) return `Session closed: ${info.close_reason || 'closed'}`; + + const last = Number(info.last_activity || 0); + if (!Number.isFinite(last) || last <= 0) return `Connected (${sid})`; + const now = Math.floor(Date.now() / 1000); + const idleAge = Math.max(0, now - last); + const idleLeft = Math.max(0, EXEC_IDLE_TTL_SECONDS - idleAge); + const mm = String(Math.floor(idleLeft / 60)).padStart(2, '0'); + const ss = String(idleLeft % 60).padStart(2, '0'); + return `Connected (${sid}) - idle in ${mm}:${ss}`; +} + +function _execStopSessionInfoTimers() { + if (execTerminalState.infoPollTimer) { + clearInterval(execTerminalState.infoPollTimer); + execTerminalState.infoPollTimer = null; + } + if (execTerminalState.infoTickTimer) { + clearInterval(execTerminalState.infoTickTimer); + execTerminalState.infoTickTimer = null; + } +} + +function _execCanUpdateConnectedStatus() { + if (!execTerminalState.sessionId) return false; + if ( + execTerminalState.reconnectEnabled && + execTerminalState.reconnectAttempts > 0 && + !execTerminalState.source + ) { + return false; + } + return true; +} + +async function _execRefreshSessionInfo() { + const sid = execTerminalState.sessionId; + if (!sid) return; + try { + const info = await api(`/containers/exec/${encodeURIComponent(sid)}`, 'GET'); + if (sid !== execTerminalState.sessionId) return; + execTerminalState.sessionInfo = info || null; + if (execTerminalState.sessionInfo?.closed) { + execTerminalState.reconnectEnabled = false; + _execCancelReconnect(); + _execCloseEventSource(); + _execSetStatus(`Session closed: ${execTerminalState.sessionInfo.close_reason || 'closed'}`); + _execStopSessionInfoTimers(); + return; + } + if (_execCanUpdateConnectedStatus()) _execSetStatus(_execConnectedStatus()); + } catch (_) { + // stream reconnect status has priority + } +} + +function _execStartSessionInfoTimers() { + _execStopSessionInfoTimers(); + _execRefreshSessionInfo(); + execTerminalState.infoPollTimer = setInterval(_execRefreshSessionInfo, EXEC_SESSION_INFO_POLL_MS); + execTerminalState.infoTickTimer = setInterval(() => { + if (!_execCanUpdateConnectedStatus()) return; + _execSetStatus(_execConnectedStatus()); + }, 1000); } function _execAppendOutput(txt) { @@ -888,7 +959,9 @@ async function containerExecOpen(name) { execTerminalState.sessionId = ''; execTerminalState.lastSeq = 0; execTerminalState.reconnectEnabled = false; + execTerminalState.sessionInfo = null; _execCancelReconnect(); + _execStopSessionInfoTimers(); execTerminalState.rawMode = true; _execInitTerminal(); _execUpdateInputModeUi(); @@ -907,8 +980,10 @@ async function containerExecOpen(name) { execTerminalState.sessionId = sid; execTerminalState.lastSeq = 0; execTerminalState.reconnectEnabled = true; + execTerminalState.sessionInfo = null; _execSetStatus(`Connected (${sid})`); _execAttachStream(0, false); + _execStartSessionInfoTimers(); _execScheduleResize(); @@ -998,6 +1073,7 @@ async function containerExecClose() { execTerminalState.reconnectEnabled = false; _execCancelReconnect(); + _execStopSessionInfoTimers(); _execCloseEventSource(); _execDestroyTerminal(); _execSetTerminalUiMode(false); @@ -1013,6 +1089,7 @@ async function containerExecClose() { execTerminalState.sessionId = ''; execTerminalState.lastSeq = 0; + execTerminalState.sessionInfo = null; if (els.back) els.back.style.display = 'none'; if (sid) {