From 705bc17097324c581ba644e79d4e2531d35a5a3e Mon Sep 17 00:00:00 2001 From: Sergey Antropoff Date: Thu, 4 Sep 2025 17:29:06 +0300 Subject: [PATCH] =?UTF-8?q?UI:=20=D1=84=D0=B8=D0=BA=D1=81=D1=8B=20=D1=81?= =?UTF-8?q?=D0=B0=D0=B9=D0=B4=D0=B1=D0=B0=D1=80=D0=B0=20(=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D0=BA=D0=B0=20container-name),=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=B1=D0=B8=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20Tail?= =?UTF-8?q?=20Lines=20=D0=B2=20SingleView=20=D0=B1=D0=B5=D0=B7=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F;=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=20UX=20=D0=B8=20=D1=81=D1=87=D0=B5=D1=82=D1=87?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/index.css | 26 ++++++++++++++ app/static/js/index.js | 75 ++++++++++++++++++++++++++++++---------- app/templates/index.html | 2 +- 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/app/static/css/index.css b/app/static/css/index.css index 40b02b0..63b5187 100644 --- a/app/static/css/index.css +++ b/app/static/css/index.css @@ -1947,6 +1947,7 @@ a{color:var(--link)} .container-list { flex: 1; overflow-y: auto; + overflow-x: hidden; /* запрещаем горизонтальный скролл и обрезаем переполнение */ padding: 16px; } @@ -1959,6 +1960,10 @@ a{color:var(--link)} cursor: pointer; transition: all 0.2s ease; position: relative; + overflow: hidden; + min-width: 0; + width: 100%; + box-sizing: border-box; } .container-item:hover { @@ -1990,12 +1995,33 @@ a{color:var(--link)} display: flex; align-items: center; gap: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + min-width: 0; + width: 100%; +} + +.container-name i { + flex: 0 0 auto; +} + +.container-name-text { + flex: 1 1 auto; + min-width: 0; + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .container-service { font-size: 11px; color: var(--muted); margin-bottom: 4px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .container-status { diff --git a/app/static/js/index.js b/app/static/js/index.js index 29dcb54..12be81a 100644 --- a/app/static/js/index.js +++ b/app/static/js/index.js @@ -143,6 +143,9 @@ function determineWsState() { } else if (obj.ws.readyState === WebSocket.CLOSED || obj.ws.readyState === WebSocket.CLOSING) { closedConnections.push(id); } + } else if (obj && obj.ajaxOnly) { + // Не удаляем ajax-only объекты из state.open, они используются для AJAX обновлений + continue; } else { closedConnections.push(id); } @@ -150,6 +153,8 @@ function determineWsState() { // Удаляем закрытые соединения closedConnections.forEach(id => { + const obj = state.open[id]; + if (obj && obj.ajaxOnly) return; // сохраняем ajax-only объекты delete state.open[id]; }); @@ -851,9 +856,9 @@ function buildTabs(){ svc.status === 'stopped' ? 'stopped' : 'paused'; item.innerHTML = ` -
+
- ${escapeHtml(svc.name)} + ${escapeHtml(svc.name)}
${escapeHtml(svc.service || svc.name)} @@ -2395,8 +2400,9 @@ function openWs(svc, panel){ els.logContent.innerHTML = ''; } // Также очищаем legacy элемент лога - if (obj.logEl) { - obj.logEl.innerHTML = ''; + const current = state.open[id]; + if (current && current.logEl) { + current.logEl.innerHTML = ''; } // Принудительно проверяем состояние через AJAX через 500мс и 1 секунду @@ -4531,10 +4537,24 @@ if (els.snapshotBtn) { }; } if (els.tail) { - els.tail.onchange = ()=> { - Object.keys(state.open).forEach(id=>{ + els.tail.onchange = async ()=> { + // Single View: не переподключаем WS, просто пересчитываем отображение и счетчики + if (!state.multiViewMode) { + if (els.logContent) { + updateLogVisibility(els.logContent); + } else { + refreshAllLogs(); + } + // Обновляем счетчики и прокрутку + setTimeout(() => { + recalculateCounters(); + scrollToBottom(); + }, 50); + return; + } + for (const id of Object.keys(state.open)){ const svc = state.services.find(s=> s.id===id); - if (!svc) return; + if (!svc) continue; // В multi view режиме используем openMultiViewWs if (state.multiViewMode && state.selectedContainers.includes(id)) { @@ -4543,17 +4563,23 @@ if (els.tail) { } else { // В обычном режиме используем openWs const panel = els.grid.querySelector(`.panel[data-cid="${id}"]`); - if (!panel) return; - state.open[id].logEl.textContent=''; - closeWs(id); + if (!panel) { + // В современном интерфейсе панели может не быть — переподключаем через switchToSingle только для текущего контейнера + if (state.current && state.current.id === id) { + closeWs(id); + // Переключаемся на тот же контейнер, чтобы пересоздать WS с новым tail + await switchToSingle(svc); + } + continue; + } + if (state.open[id] && state.open[id].logEl) { + state.open[id].logEl.textContent=''; + } + closeWs(id); openWs(svc, panel); } - }); - - // Обновляем современный интерфейс - if (state.current && els.logContent) { - els.logContent.textContent = 'Reconnecting...'; } + // MultiView: не трогаем здесь UI — trimming делается ниже в делегированном обработчике // Пересчитываем счетчики после изменения Tail Lines setTimeout(() => { @@ -5437,11 +5463,24 @@ function reinitializeElements() { * @param {Array} newLogs - Массив новых логов */ function appendNewLogsForContainer(containerId, newLogs) { - const obj = state.open[containerId]; + let obj = state.open[containerId]; if (!obj) { - console.warn(`AJAX Update: Object not found for container ${containerId}`); - return; + // Лениво инициализируем объект для AJAX-обновлений, когда WS ещё не открыт + const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`); + const logEl = els.logContent || multiViewLog || null; + state.open[containerId] = { + ws: null, + logEl: logEl, + wrapEl: logEl ? logEl.parentElement : null, + counters: {dbg:0, info:0, warn:0, err:0, other:0}, + pausedBuffer: [], + serviceName: containerId, + allLogs: [], + ajaxOnly: true + }; + obj = state.open[containerId]; + console.warn(`AJAX Update: Created ajaxOnly state for container ${containerId}`); } // Обрабатываем каждую новую строку лога через handleLine diff --git a/app/templates/index.html b/app/templates/index.html index 139eaca..abb86f5 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -309,7 +309,7 @@ Автор: Сергей Антропов
- Версия 2.0 + Версия 1.0