feat: добавлены страницы ошибок и кнопка OTHER в LogLevels
- Добавлена кнопка OTHER в LogLevels для неклассифицированных логов - Созданы красивые страницы ошибок с поддержкой темной/светлой темы - Добавлены обработчики для ошибок 401, 403, 404, 500 - Реализована безопасность: убраны детали ошибок из пользовательского интерфейса - Кнопка 'Войти в систему' показывается только на странице ошибки 403 - На странице 403 убран error-message, оставлен только auth-notice - Обновлены счетчики логов для поддержки уровня OTHER - Добавлены тестовые маршруты для проверки страниц ошибок - Улучшен UX: адаптивный дизайн, интерактивность, навигация Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
@@ -562,6 +562,15 @@ a{color:var(--link)}
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.other-btn {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.other-btn:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -1313,6 +1322,10 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
|
||||
<span class="counter-label">ERROR</span>
|
||||
<span class="counter-value cerr">0</span>
|
||||
</button>
|
||||
<button class="counter-btn other-btn" title="OTHER">
|
||||
<span class="counter-label">OTHER</span>
|
||||
<span class="counter-value cother">0</span>
|
||||
</button>
|
||||
<button id="logRefreshBtn" class="btn btn-small" title="Обновить логи и счетчики">
|
||||
<i class="fas fa-sync-alt"></i> Refresh
|
||||
</button>
|
||||
@@ -1341,7 +1354,7 @@ const state = {
|
||||
open: {}, // id -> {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName}
|
||||
layout: 'tabs', // 'tabs' | 'grid2' | 'grid3' | 'grid4'
|
||||
filter: null,
|
||||
levels: {debug:true, info:true, warn:true, err:true},
|
||||
levels: {debug:true, info:true, warn:true, err:true, other:true},
|
||||
selectedContainers: [], // Массив ID выбранных контейнеров для мультипросмотра
|
||||
multiViewMode: false, // Режим мультипросмотра
|
||||
};
|
||||
@@ -1365,6 +1378,7 @@ const els = {
|
||||
lvlInfo: document.getElementById('lvlInfo'),
|
||||
lvlWarn: document.getElementById('lvlWarn'),
|
||||
lvlErr: document.getElementById('lvlErr'),
|
||||
lvlOther: document.getElementById('lvlOther'),
|
||||
layoutBadge: document.getElementById('layoutBadge') || { textContent: '' },
|
||||
aggregate: document.getElementById('aggregate') || { checked: false },
|
||||
themeSwitch: document.getElementById('themeSwitch'),
|
||||
@@ -1528,7 +1542,7 @@ function allowedByLevel(cls){
|
||||
if (cls==='err') return state.levels.err;
|
||||
if (cls==='warn') return state.levels.warn;
|
||||
if (cls==='ok') return state.levels.info;
|
||||
if (cls==='other') return true; // Всегда показываем неклассифицированные строки
|
||||
if (cls==='other') return state.levels.other; // Показываем неклассифицированные строки в зависимости от настройки
|
||||
return true;
|
||||
}
|
||||
function applyFilter(line){
|
||||
@@ -2345,7 +2359,7 @@ function openMultiViewWs(service) {
|
||||
serviceName: service.service,
|
||||
logEl: logEl,
|
||||
wrapEl: logEl,
|
||||
counters: {dbg:0, info:0, warn:0, err:0},
|
||||
counters: {dbg:0, info:0, warn:0, err:0, other:0},
|
||||
pausedBuffer: [],
|
||||
allLogs: [] // Добавляем буфер для логов
|
||||
};
|
||||
@@ -2543,7 +2557,8 @@ function openWs(svc, panel){
|
||||
const cinfo = panel.querySelector('.cinfo') || document.querySelector('.cinfo');
|
||||
const cwarn = panel.querySelector('.cwarn') || document.querySelector('.cwarn');
|
||||
const cerr = panel.querySelector('.cerr') || document.querySelector('.cerr');
|
||||
const counters = {dbg:0,info:0,warn:0,err:0};
|
||||
const cother = panel.querySelector('.cother') || document.querySelector('.cother');
|
||||
const counters = {dbg:0,info:0,warn:0,err:0,other:0};
|
||||
|
||||
const ws = new WebSocket(wsUrl(id, (svc.service||svc.name), svc.project||''));
|
||||
state.open[id] = {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName: (svc.service||svc.name)};
|
||||
@@ -2621,6 +2636,7 @@ function openWs(svc, panel){
|
||||
cinfo.textContent = counters.info;
|
||||
cwarn.textContent = counters.warn;
|
||||
cerr.textContent = counters.err;
|
||||
if (cother) cother.textContent = counters.other;
|
||||
};
|
||||
|
||||
// Убираем автоматический refresh - теперь только по кнопке
|
||||
@@ -3253,7 +3269,7 @@ function handleLine(id, line){
|
||||
// Отладочная информация для первых нескольких строк
|
||||
if (!obj.counters) {
|
||||
console.error(`handleLine: Counters not initialized for container ${id}`);
|
||||
obj.counters = {dbg:0, info:0, warn:0, err:0};
|
||||
obj.counters = {dbg:0, info:0, warn:0, err:0, other:0};
|
||||
}
|
||||
|
||||
// Фильтруем сообщение "Connected to container" для всех режимов
|
||||
@@ -3278,6 +3294,7 @@ function handleLine(id, line){
|
||||
if (cls==='ok') obj.counters.info++;
|
||||
if (cls==='warn') obj.counters.warn++;
|
||||
if (cls==='err') obj.counters.err++;
|
||||
if (cls==='other') obj.counters.other++;
|
||||
}
|
||||
|
||||
// Для Single View НЕ добавляем перенос строки после каждой строки лога
|
||||
@@ -3643,7 +3660,8 @@ function openFanGroup(services){
|
||||
const cinfo = panel.querySelector('.cinfo') || document.querySelector('.cinfo');
|
||||
const cwarn = panel.querySelector('.cwarn') || document.querySelector('.cwarn');
|
||||
const cerr = panel.querySelector('.cerr') || document.querySelector('.cerr');
|
||||
const counters = {dbg:0,info:0,warn:0,err:0};
|
||||
const cother = panel.querySelector('.cother') || document.querySelector('.cother');
|
||||
const counters = {dbg:0,info:0,warn:0,err:0,other:0};
|
||||
|
||||
const ws = new WebSocket(fanGroupUrl(services.join(','), fake.project||''));
|
||||
state.open[id] = {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName: ('group:'+services.join(','))};
|
||||
@@ -3663,6 +3681,7 @@ function openFanGroup(services){
|
||||
cinfo.textContent = counters.info;
|
||||
cwarn.textContent = counters.warn;
|
||||
cerr.textContent = counters.err;
|
||||
if (cother) cother.textContent = counters.other;
|
||||
|
||||
// Обновляем видимость счетчиков
|
||||
updateCounterVisibility();
|
||||
@@ -3707,11 +3726,13 @@ async function updateCounters(containerId) {
|
||||
const cinfo = document.querySelector('.cinfo');
|
||||
const cwarn = document.querySelector('.cwarn');
|
||||
const cerr = document.querySelector('.cerr');
|
||||
const cother = document.querySelector('.cother');
|
||||
|
||||
if (cdbg) cdbg.textContent = stats.debug || 0;
|
||||
if (cinfo) cinfo.textContent = stats.info || 0;
|
||||
if (cwarn) cwarn.textContent = stats.warn || 0;
|
||||
if (cerr) cerr.textContent = stats.error || 0;
|
||||
if (cother) cother.textContent = stats.other || 0;
|
||||
|
||||
// Обновляем видимость счетчиков
|
||||
updateCounterVisibility();
|
||||
@@ -3779,7 +3800,7 @@ function recalculateCounters() {
|
||||
const visibleLogs = obj.allLogs.slice(-tailLines);
|
||||
|
||||
// Сбрасываем счетчики
|
||||
obj.counters = {dbg: 0, info: 0, warn: 0, err: 0};
|
||||
obj.counters = {dbg: 0, info: 0, warn: 0, err: 0, other: 0};
|
||||
|
||||
// Пересчитываем счетчики только для отображаемых логов
|
||||
visibleLogs.forEach(logEntry => {
|
||||
@@ -3789,6 +3810,7 @@ function recalculateCounters() {
|
||||
if (logEntry.cls === 'ok') obj.counters.info++;
|
||||
if (logEntry.cls === 'warn') obj.counters.warn++;
|
||||
if (logEntry.cls === 'err') obj.counters.err++;
|
||||
if (logEntry.cls === 'other') obj.counters.other++;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3797,11 +3819,13 @@ function recalculateCounters() {
|
||||
const cinfo = document.querySelector('.cinfo');
|
||||
const cwarn = document.querySelector('.cwarn');
|
||||
const cerr = document.querySelector('.cerr');
|
||||
const cother = document.querySelector('.cother');
|
||||
|
||||
if (cdbg) cdbg.textContent = obj.counters.dbg;
|
||||
if (cinfo) cinfo.textContent = obj.counters.info;
|
||||
if (cwarn) cwarn.textContent = obj.counters.warn;
|
||||
if (cerr) cerr.textContent = obj.counters.err;
|
||||
if (cother) cother.textContent = obj.counters.other;
|
||||
|
||||
console.log(`Counters recalculated for container ${containerId} (tail: ${tailLines}):`, obj.counters);
|
||||
}
|
||||
@@ -3822,6 +3846,7 @@ function recalculateMultiViewCounters() {
|
||||
let totalInfo = 0;
|
||||
let totalWarn = 0;
|
||||
let totalError = 0;
|
||||
let totalOther = 0;
|
||||
|
||||
// Пересчитываем счетчики для каждого контейнера
|
||||
for (const containerId of state.selectedContainers) {
|
||||
@@ -3832,7 +3857,7 @@ function recalculateMultiViewCounters() {
|
||||
const visibleLogs = obj.allLogs.slice(-tailLines);
|
||||
|
||||
// Сбрасываем счетчики для этого контейнера
|
||||
obj.counters = {dbg: 0, info: 0, warn: 0, err: 0};
|
||||
obj.counters = {dbg: 0, info: 0, warn: 0, err: 0, other: 0};
|
||||
|
||||
// Пересчитываем счетчики только для отображаемых логов
|
||||
visibleLogs.forEach(logEntry => {
|
||||
@@ -3842,6 +3867,7 @@ function recalculateMultiViewCounters() {
|
||||
if (logEntry.cls === 'ok') obj.counters.info++;
|
||||
if (logEntry.cls === 'warn') obj.counters.warn++;
|
||||
if (logEntry.cls === 'err') obj.counters.err++;
|
||||
if (logEntry.cls === 'other') obj.counters.other++;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3850,6 +3876,7 @@ function recalculateMultiViewCounters() {
|
||||
totalInfo += obj.counters.info;
|
||||
totalWarn += obj.counters.warn;
|
||||
totalError += obj.counters.err;
|
||||
totalOther += obj.counters.other;
|
||||
}
|
||||
|
||||
// Обновляем отображение счетчиков
|
||||
@@ -3857,13 +3884,15 @@ function recalculateMultiViewCounters() {
|
||||
const cinfo = document.querySelector('.cinfo');
|
||||
const cwarn = document.querySelector('.cwarn');
|
||||
const cerr = document.querySelector('.cerr');
|
||||
const cother = document.querySelector('.cother');
|
||||
|
||||
if (cdbg) cdbg.textContent = totalDebug;
|
||||
if (cinfo) cinfo.textContent = totalInfo;
|
||||
if (cwarn) cwarn.textContent = totalWarn;
|
||||
if (cerr) cerr.textContent = totalError;
|
||||
if (cother) cother.textContent = totalOther;
|
||||
|
||||
console.log(`Multi-view counters recalculated (tail: ${tailLines}):`, { totalDebug, totalInfo, totalWarn, totalError });
|
||||
console.log(`Multi-view counters recalculated (tail: ${tailLines}):`, { totalDebug, totalInfo, totalWarn, totalError, totalOther });
|
||||
}
|
||||
|
||||
// Функция для обновления видимости счетчиков
|
||||
@@ -3872,6 +3901,7 @@ function updateCounterVisibility() {
|
||||
const infoBtn = document.querySelector('.info-btn');
|
||||
const warnBtn = document.querySelector('.warn-btn');
|
||||
const errorBtn = document.querySelector('.error-btn');
|
||||
const otherBtn = document.querySelector('.other-btn');
|
||||
|
||||
if (debugBtn) {
|
||||
debugBtn.classList.toggle('disabled', !state.levels.debug);
|
||||
@@ -3885,6 +3915,9 @@ function updateCounterVisibility() {
|
||||
if (errorBtn) {
|
||||
errorBtn.classList.toggle('disabled', !state.levels.err);
|
||||
}
|
||||
if (otherBtn) {
|
||||
otherBtn.classList.toggle('disabled', !state.levels.other);
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для обновления логов и счетчиков
|
||||
@@ -4020,6 +4053,7 @@ function addCounterClickHandlers() {
|
||||
const infoBtn = document.querySelector('.info-btn');
|
||||
const warnBtn = document.querySelector('.warn-btn');
|
||||
const errorBtn = document.querySelector('.error-btn');
|
||||
const otherBtn = document.querySelector('.other-btn');
|
||||
|
||||
if (debugBtn) {
|
||||
debugBtn.onclick = () => {
|
||||
@@ -4084,6 +4118,22 @@ function addCounterClickHandlers() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (otherBtn) {
|
||||
otherBtn.onclick = () => {
|
||||
state.levels.other = !state.levels.other;
|
||||
updateCounterVisibility();
|
||||
refreshAllLogs();
|
||||
// Добавляем refresh для обновления логов
|
||||
if (state.current) {
|
||||
refreshLogsAndCounters();
|
||||
}
|
||||
// Обновляем multi-view если он активен
|
||||
if (state.multiViewMode) {
|
||||
refreshAllLogs();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4464,6 +4514,25 @@ if (els.lvlErr) {
|
||||
}
|
||||
};
|
||||
}
|
||||
if (els.lvlOther) {
|
||||
els.lvlOther.onchange = ()=> {
|
||||
state.levels.other = els.lvlOther.checked;
|
||||
updateCounterVisibility();
|
||||
refreshAllLogs();
|
||||
// Обновляем multi-view если он активен
|
||||
if (state.multiViewMode) {
|
||||
refreshAllLogs();
|
||||
setTimeout(() => {
|
||||
recalculateMultiViewCounters();
|
||||
}, 100);
|
||||
} else {
|
||||
// Пересчитываем счетчики для Single View
|
||||
setTimeout(() => {
|
||||
recalculateCounters();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Hotkeys: [ ] — navigation between containers
|
||||
window.addEventListener('keydown', async (e)=>{
|
||||
|
||||
Reference in New Issue
Block a user