diff --git a/templates/index.html b/templates/index.html
index f65512c..fade638 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -627,6 +627,39 @@ const els = {
})();
function setWsState(s){ els.wsstate.textContent = 'ws: ' + s; }
+
+// Функция для обновления всех логов при изменении фильтров
+function refreshAllLogs() {
+ Object.keys(state.open).forEach(id => {
+ const obj = state.open[id];
+ if (!obj || !obj.logEl) return;
+
+ // Получаем все строки логов
+ const lines = obj.logEl.innerHTML.split('\n');
+ const filteredLines = [];
+
+ lines.forEach(line => {
+ if (line.trim() === '') return;
+
+ // Проверяем уровень логирования
+ const cls = classify(line);
+ if (!allowedByLevel(cls)) return;
+
+ // Проверяем фильтр
+ if (!applyFilter(line)) return;
+
+ filteredLines.push(line);
+ });
+
+ // Обновляем отображение
+ obj.logEl.innerHTML = filteredLines.join('\n');
+
+ // Обновляем современный интерфейс
+ if (state.current && state.current.id === id && els.logContent) {
+ els.logContent.innerHTML = obj.logEl.innerHTML;
+ }
+ });
+}
function escapeHtml(s){ return s.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); }
function classify(line){
@@ -889,10 +922,29 @@ function closeWs(id){
async function sendSnapshot(id){
const o = state.open[id];
if (!o){ alert('not open'); return; }
- const text = o.logEl.textContent;
+
+ // Получаем текст логов из современного интерфейса или из legacy
+ let text = '';
+ if (state.current && state.current.id === id && els.logContent) {
+ text = els.logContent.textContent;
+ } else if (o.logEl) {
+ text = o.logEl.textContent;
+ }
+
+ if (!text || text.trim() === '') {
+ alert('No logs to save');
+ return;
+ }
+
+ console.log('Saving snapshot with content length:', text.length);
+
const payload = {container_id: id, service: o.serviceName || id, content: text};
const res = await fetch('/api/snapshot', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)});
- if (!res.ok){ alert('snapshot failed'); return; }
+ if (!res.ok){
+ console.error('Snapshot failed:', res.status, res.statusText);
+ alert('snapshot failed');
+ return;
+ }
const js = await res.json();
const a = document.createElement('a');
a.href = js.url; a.download = js.file; a.click();
@@ -1133,8 +1185,24 @@ if (els.groupBtn && els.groupBtn.onclick !== null) {
}
// Controls
-els.clearBtn.onclick = ()=> Object.values(state.open).forEach(o=> o.logEl.textContent='');
-els.refreshBtn.onclick = fetchServices;
+els.clearBtn.onclick = ()=> {
+ Object.values(state.open).forEach(o => {
+ if (o.logEl) o.logEl.textContent = '';
+ });
+ // Очищаем современный интерфейс
+ if (els.logContent) {
+ els.logContent.textContent = '';
+ }
+ // Сбрасываем счетчики
+ document.querySelectorAll('.cdbg, .cinfo, .cwarn, .cerr').forEach(el => {
+ el.textContent = '0';
+ });
+};
+
+els.refreshBtn.onclick = async () => {
+ console.log('Refreshing services...');
+ await fetchServices();
+};
els.projectSelect.onchange = fetchServices;
// Mobile menu toggle
@@ -1157,16 +1225,83 @@ els.tail.onchange = ()=> {
state.open[id].logEl.textContent='';
closeWs(id); openWs(svc, panel);
});
+
+ // Обновляем современный интерфейс
+ if (state.current && els.logContent) {
+ els.logContent.textContent = 'Reconnecting...';
+ }
};
els.wrapToggle.onchange = ()=> {
document.querySelectorAll('.log').forEach(el=> el.style.whiteSpace = els.wrapToggle.checked ? 'pre-wrap' : 'pre');
- els.logContent.style.whiteSpace = els.wrapToggle.checked ? 'pre-wrap' : 'pre';
+ if (els.logContent) {
+ els.logContent.style.whiteSpace = els.wrapToggle.checked ? 'pre-wrap' : 'pre';
+ }
+};
+
+// Добавляем обработчики для autoscroll и pause
+els.autoscroll.onchange = ()=> {
+ // Обновляем настройку автопрокрутки для всех открытых логов
+ Object.keys(state.open).forEach(id => {
+ const obj = state.open[id];
+ if (obj && obj.wrapEl) {
+ if (els.autoscroll.checked) {
+ obj.wrapEl.scrollTop = obj.wrapEl.scrollHeight;
+ }
+ }
+ });
+
+ // Обновляем современный интерфейс
+ if (state.current && els.logContent) {
+ const logContent = document.querySelector('.log-content');
+ if (logContent && els.autoscroll.checked) {
+ logContent.scrollTop = logContent.scrollHeight;
+ }
+ }
+};
+
+els.pause.onchange = ()=> {
+ // При снятии паузы показываем накопленные логи
+ if (!els.pause.checked) {
+ Object.keys(state.open).forEach(id => {
+ const obj = state.open[id];
+ if (obj && obj.pausedBuffer && obj.pausedBuffer.length > 0) {
+ obj.pausedBuffer.forEach(html => {
+ obj.logEl.insertAdjacentHTML('beforeend', html);
+ });
+ obj.pausedBuffer = [];
+
+ // Обновляем современный интерфейс
+ if (state.current && state.current.id === id && els.logContent) {
+ els.logContent.innerHTML = obj.logEl.innerHTML;
+ const logContent = document.querySelector('.log-content');
+ if (logContent && els.autoscroll.checked) {
+ logContent.scrollTop = logContent.scrollHeight;
+ }
+ }
+ }
+ });
+ }
+};
+els.filter.oninput = ()=> {
+ state.filter = els.filter.value.trim();
+ refreshAllLogs();
+};
+els.lvlDebug.onchange = ()=> {
+ state.levels.debug = els.lvlDebug.checked;
+ refreshAllLogs();
+};
+els.lvlInfo.onchange = ()=> {
+ state.levels.info = els.lvlInfo.checked;
+ refreshAllLogs();
+};
+els.lvlWarn.onchange = ()=> {
+ state.levels.warn = els.lvlWarn.checked;
+ refreshAllLogs();
+};
+els.lvlErr.onchange = ()=> {
+ state.levels.err = els.lvlErr.checked;
+ refreshAllLogs();
};
-els.filter.oninput = ()=> { state.filter = els.filter.value.trim(); };
-els.lvlDebug.onchange = ()=> state.levels.debug = els.lvlDebug.checked;
-els.lvlInfo.onchange = ()=> state.levels.info = els.lvlInfo.checked;
-els.lvlWarn.onchange = ()=> state.levels.warn = els.lvlWarn.checked;
-els.lvlErr.onchange = ()=> state.levels.err = els.lvlErr.checked;
// Hotkeys: [ ] — tabs, M — multi
window.addEventListener('keydown', (e)=>{