docs: add comprehensive JSDoc comments to all JavaScript functions

This commit is contained in:
Сергей Антропов 2025-08-20 17:32:07 +03:00
parent a49714ab14
commit f5926b80ad
3 changed files with 231 additions and 66 deletions

View File

@ -1,4 +1,13 @@
// Theme toggle functionality /**
* LogBoard+ - Скрипт страницы ошибок
* Автор: Сергей Антропов
* Сайт: https://devops.org.ru
*/
/**
* Переключает тему между светлой и темной
* Обновляет атрибут data-theme и сохраняет выбор в localStorage
*/
function toggleTheme() { function toggleTheme() {
const html = document.documentElement; const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme'); const currentTheme = html.getAttribute('data-theme');
@ -14,7 +23,10 @@ function toggleTheme() {
} }
} }
// Initialize theme on page load /**
* Инициализация темы при загрузке страницы
* Загружает сохраненную тему из localStorage
*/
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const savedTheme = localStorage.getItem('lb_theme') || 'dark'; const savedTheme = localStorage.getItem('lb_theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme); document.documentElement.setAttribute('data-theme', savedTheme);
@ -26,7 +38,9 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
// Keyboard shortcut for theme toggle (Ctrl+T) /**
* Горячая клавиша для переключения темы (Ctrl+T)
*/
document.addEventListener('keydown', function(e) { document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 't') { if (e.ctrlKey && e.key === 't') {
e.preventDefault(); e.preventDefault();

View File

@ -1,62 +1,80 @@
/**
* LogBoard+ - Веб-панель для просмотра логов микросервисов
* Автор: Сергей Антропов
* Сайт: https://devops.org.ru
* Версия: 2.0
*/
console.log('LogBoard+ script loaded - VERSION 2'); console.log('LogBoard+ script loaded - VERSION 2');
/**
* Глобальное состояние приложения
* Содержит все данные о контейнерах, настройках и режимах отображения
*/
const state = { const state = {
services: [], services: [], // Список всех доступных сервисов
current: null, current: null, // Текущий выбранный контейнер для single view
open: {}, // id -> {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName} open: {}, // Открытые WebSocket соединения: id -> {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName}
layout: 'tabs', // 'tabs' | 'grid2' | 'grid3' | 'grid4' layout: 'tabs', // Режим отображения: 'tabs' | 'grid2' | 'grid3' | 'grid4'
filter: null, filter: null, // Текущий фильтр для логов
levels: {debug:true, info:true, warn:true, err:true, other:true}, levels: {debug:true, info:true, warn:true, err:true, other:true}, // Уровни логирования для отображения
selectedContainers: [], // Массив ID выбранных контейнеров для мультипросмотра selectedContainers: [], // Массив ID выбранных контейнеров для мультипросмотра
multiViewMode: false, // Режим мультипросмотра multiViewMode: false, // Режим мультипросмотра (true = multi-view, false = single-view)
}; };
/**
* Ссылки на DOM элементы интерфейса
* Содержит все элементы управления и отображения
*/
const els = { const els = {
// Legacy elements // Legacy elements (старые элементы для обратной совместимости)
tabs: document.getElementById('tabs'), tabs: document.getElementById('tabs'), // Контейнер с вкладками
grid: document.getElementById('grid'), grid: document.getElementById('grid'), // Контейнер с сеткой
tail: document.getElementById('tail'), tail: document.getElementById('tail'), // Поле ввода количества строк логов
autoscroll: document.getElementById('autoscroll'), autoscroll: document.getElementById('autoscroll'), // Чекбокс автопрокрутки
wrapToggle: document.getElementById('wrap'), wrapToggle: document.getElementById('wrap'), // Переключатель переноса строк
filter: document.getElementById('filter'), filter: document.getElementById('filter'), // Поле фильтра логов
wsstate: document.getElementById('wsstate'), wsstate: document.getElementById('wsstate'), // Индикатор состояния WebSocket
ajaxUpdateBtn: document.getElementById('ajaxUpdateBtn'), ajaxUpdateBtn: document.getElementById('ajaxUpdateBtn'), // Кнопка AJAX обновления
projectBadge: document.getElementById('projectBadge'), projectBadge: document.getElementById('projectBadge'), // Бейдж текущего проекта
clearBtn: document.getElementById('clear'), clearBtn: document.getElementById('clear'), // Кнопка очистки логов
refreshBtn: document.getElementById('refresh'), refreshBtn: document.getElementById('refresh'), // Кнопка обновления
snapshotBtn: document.getElementById('snapshot'), snapshotBtn: document.getElementById('snapshot'), // Кнопка создания снимка
lvlDebug: document.getElementById('lvlDebug'), lvlDebug: document.getElementById('lvlDebug'), // Кнопка уровня DEBUG
lvlInfo: document.getElementById('lvlInfo'), lvlInfo: document.getElementById('lvlInfo'), // Кнопка уровня INFO
lvlWarn: document.getElementById('lvlWarn'), lvlWarn: document.getElementById('lvlWarn'), // Кнопка уровня WARN
lvlErr: document.getElementById('lvlErr'), lvlErr: document.getElementById('lvlErr'), // Кнопка уровня ERROR
lvlOther: document.getElementById('lvlOther'), lvlOther: document.getElementById('lvlOther'), // Кнопка уровня OTHER
layoutBadge: document.getElementById('layoutBadge') || { textContent: '' }, layoutBadge: document.getElementById('layoutBadge') || { textContent: '' }, // Бейдж режима отображения
aggregate: document.getElementById('aggregate') || { checked: false }, aggregate: document.getElementById('aggregate') || { checked: false }, // Чекбокс агрегации
themeSwitch: document.getElementById('themeSwitch'), themeSwitch: document.getElementById('themeSwitch'), // Переключатель темы
copyFab: document.getElementById('copyFab'), copyFab: document.getElementById('copyFab'), // Кнопка копирования
groupBtn: document.getElementById('groupBtn') || { onclick: null }, groupBtn: document.getElementById('groupBtn') || { onclick: null }, // Кнопка группировки
// New modern elements // New modern elements (новые элементы современного интерфейса)
containerList: document.getElementById('containerList'), containerList: document.getElementById('containerList'), // Список контейнеров
logContent: document.getElementById('logContent'), logContent: document.getElementById('logContent'), // Основной контент логов
mobileToggle: document.getElementById('mobileToggle'), mobileToggle: document.getElementById('mobileToggle'), // Переключатель мобильного режима
optionsBtn: document.getElementById('optionsBtn'), optionsBtn: document.getElementById('optionsBtn'), // Кнопка настроек
helpBtn: document.getElementById('helpBtn'), helpBtn: document.getElementById('helpBtn'), // Кнопка помощи
logoutBtn: document.getElementById('logoutBtn'), logoutBtn: document.getElementById('logoutBtn'), // Кнопка выхода
sidebar: document.getElementById('sidebar'), sidebar: document.getElementById('sidebar'), // Боковая панель
sidebarToggle: document.getElementById('sidebarToggle'), sidebarToggle: document.getElementById('sidebarToggle'), // Переключатель боковой панели
header: document.getElementById('header'), header: document.getElementById('header'), // Заголовок
hotkeysModal: document.getElementById('hotkeysModal'), hotkeysModal: document.getElementById('hotkeysModal'), // Модальное окно горячих клавиш
hotkeysModalClose: document.getElementById('hotkeysModalClose'), hotkeysModalClose: document.getElementById('hotkeysModalClose'), // Кнопка закрытия модального окна
multiViewPanel: document.getElementById('multiViewPanel'), multiViewPanel: document.getElementById('multiViewPanel'), // Панель мультипросмотра
multiViewPanelTitle: document.getElementById('multiViewPanelTitle'), multiViewPanelTitle: document.getElementById('multiViewPanelTitle'), // Заголовок мультипросмотра
singleViewPanel: document.getElementById('singleViewPanel'), singleViewPanel: document.getElementById('singleViewPanel'), // Панель одиночного просмотра
singleViewTitle: document.getElementById('singleViewTitle'), singleViewTitle: document.getElementById('singleViewTitle'), // Заголовок одиночного просмотра
}; };
// ----- Theme toggle ----- /**
* Инициализация переключателя темы
* Загружает сохраненную тему из localStorage и настраивает переключатель
*/
(function initTheme(){ (function initTheme(){
const saved = localStorage.lb_theme || 'dark'; const saved = localStorage.lb_theme || 'dark';
document.documentElement.setAttribute('data-theme', saved); document.documentElement.setAttribute('data-theme', saved);
@ -68,6 +86,10 @@ const els = {
}); });
})(); })();
/**
* Устанавливает состояние WebSocket соединения в интерфейсе
* @param {string} s - Состояние: 'on', 'off', 'err', 'available'
*/
function setWsState(s){ function setWsState(s){
console.log('setWsState: Устанавливаем состояние', s); console.log('setWsState: Устанавливаем состояние', s);
console.log('setWsState: Текущие соединения:', Object.keys(state.open)); console.log('setWsState: Текущие соединения:', Object.keys(state.open));
@ -89,7 +111,10 @@ function setWsState(s){
} }
} }
// Функция для определения общего состояния WebSocket соединений /**
* Определяет общее состояние WebSocket соединений
* Проверяет все открытые соединения и устанавливает соответствующее состояние
*/
function determineWsState() { function determineWsState() {
const openConnections = Object.keys(state.open); const openConnections = Object.keys(state.open);
@ -238,6 +263,10 @@ function stopWebSocketStatusCheck() {
} }
} }
/**
* Устанавливает визуальное состояние кнопки AJAX обновления
* @param {boolean} enabled - Включено ли AJAX обновление
*/
function setAjaxUpdateState(enabled) { function setAjaxUpdateState(enabled) {
console.log('setAjaxUpdateState: enabled =', enabled, 'els.ajaxUpdateBtn =', !!els.ajaxUpdateBtn); console.log('setAjaxUpdateState: enabled =', enabled, 'els.ajaxUpdateBtn =', !!els.ajaxUpdateBtn);
@ -260,7 +289,10 @@ function setAjaxUpdateState(enabled) {
} }
} }
// Функция для обновления всех логов при изменении фильтров /**
* Обновляет отображение всех логов при изменении фильтров
* Перерисовывает логи с учетом текущих настроек фильтрации и уровней
*/
function refreshAllLogs() { function refreshAllLogs() {
// Обновляем обычный просмотр // Обновляем обычный просмотр
Object.keys(state.open).forEach(id => { Object.keys(state.open).forEach(id => {
@ -339,8 +371,19 @@ function refreshAllLogs() {
} }
}, 100); }, 100);
} }
/**
* Экранирует HTML символы для безопасного отображения
* @param {string} s - Строка для экранирования
* @returns {string} Экранированная строка
*/
function escapeHtml(s){ return s.replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); } function escapeHtml(s){ return s.replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); }
/**
* Классифицирует строку лога по уровню логирования
* Определяет уровень на основе ключевых слов и паттернов в строке
* @param {string} line - Строка лога для классификации
* @returns {string} Класс уровня: 'dbg', 'err', 'warn', 'ok', 'other'
*/
function classify(line){ function classify(line){
const l = line.toLowerCase(); const l = line.toLowerCase();
@ -374,6 +417,11 @@ function classify(line){
return 'other'; return 'other';
} }
/**
* Проверяет, разрешен ли отображение лога данного уровня
* @param {string} cls - Класс уровня лога ('dbg', 'err', 'warn', 'ok', 'other')
* @returns {boolean} Разрешен ли отображение
*/
function allowedByLevel(cls){ function allowedByLevel(cls){
if (cls==='dbg') return state.levels.debug; if (cls==='dbg') return state.levels.debug;
if (cls==='err') return state.levels.err; if (cls==='err') return state.levels.err;
@ -383,7 +431,13 @@ function allowedByLevel(cls){
return true; return true;
} }
// Функция для проверки уровня логирования для конкретного контейнера /**
* Проверяет, разрешен ли отображение лога данного уровня для конкретного контейнера
* Используется в режиме мультипросмотра для индивидуальных настроек контейнеров
* @param {string} cls - Класс уровня лога ('dbg', 'err', 'warn', 'ok', 'other')
* @param {string} containerId - ID контейнера
* @returns {boolean} Разрешен ли отображение
*/
function allowedByContainerLevel(cls, containerId) { function allowedByContainerLevel(cls, containerId) {
// Если настройки контейнера не инициализированы, инициализируем их // Если настройки контейнера не инициализированы, инициализируем их
if (!state.containerLevels) { if (!state.containerLevels) {
@ -408,7 +462,11 @@ function allowedByContainerLevel(cls, containerId) {
return result; return result;
} }
// Функция для обновления видимости логов в Single View /**
* Обновляет видимость логов в Single View режиме
* Перерисовывает логи с учетом текущих фильтров и настроек уровней
* @param {HTMLElement} logElement - Элемент для обновления
*/
function updateLogVisibility(logElement) { function updateLogVisibility(logElement) {
if (!logElement || !state.current) return; if (!logElement || !state.current) return;
@ -616,6 +674,12 @@ function initializeLevelButtons() {
// Применяем настройки wrap text // Применяем настройки wrap text
applyWrapSettings(); applyWrapSettings();
} }
/**
* Применяет фильтр к строке лога
* Проверяет, соответствует ли строка текущему фильтру (безопасный regex поиск)
* @param {string} line - Строка лога для проверки
* @returns {boolean} Проходит ли строка фильтр
*/
function applyFilter(line){ function applyFilter(line){
if(!state.filter) return true; if(!state.filter) return true;
try{ try{
@ -628,14 +692,30 @@ function applyFilter(line){
} }
} }
// ANSI → HTML (SGR: 0/1/3/4, 30-37) /**
* Константы и настройки для работы с ANSI цветами
* SGR (Select Graphic Rendition): 0/1/3/4, 30-37
*/
// ----- Instance color & filters ----- /**
const inst = { colors: {}, filters: {}, palette: [ * Настройки экземпляров контейнеров
* Содержит цвета, фильтры и палитру для визуального различия контейнеров
*/
const inst = {
colors: {}, // Кэш цветов для контейнеров
filters: {}, // Фильтры для экземпляров
palette: [ // Палитра цветов для контейнеров
'#7aa2f7','#9ece6a','#e0af68','#f7768e','#bb9af7','#7dcfff','#c0caf5','#f6bd60', '#7aa2f7','#9ece6a','#e0af68','#f7768e','#bb9af7','#7dcfff','#c0caf5','#f6bd60',
'#84cc16','#06b6d4','#fb923c','#ef4444','#22c55e','#a855f7' '#84cc16','#06b6d4','#fb923c','#ef4444','#22c55e','#a855f7'
]}; ]
};
/**
* Генерирует уникальный цвет для контейнера на основе его ID
* Использует хеш-функцию для детерминированного выбора цвета из палитры
* @param {string} id8 - Первые 8 символов ID контейнера
* @returns {string} HEX цвет для контейнера
*/
function idColor(id8){ function idColor(id8){
if (inst.colors[id8]) return inst.colors[id8]; if (inst.colors[id8]) return inst.colors[id8];
// simple hash to pick from palette // simple hash to pick from palette
@ -679,6 +759,12 @@ function parsePrefixAndStrip(line){
return {id8: m[1], rest: m[2]}; return {id8: m[1], rest: m[2]};
} }
/**
* Конвертирует ANSI escape-последовательности в HTML
* Поддерживает цвета (30-37), жирный (1), курсив (3), подчеркивание (4)
* @param {string} text - Текст с ANSI кодами
* @returns {string} HTML с CSS классами для стилизации
*/
function ansiToHtml(text){ function ansiToHtml(text){
const ESC = '\u001b['; const ESC = '\u001b[';
const parts = text.split(ESC); const parts = text.split(ESC);
@ -1614,6 +1700,11 @@ async function updateMultiViewMode() {
updateLogLevelsVisibility(); updateLogLevelsVisibility();
} }
/**
* Настраивает интерфейс для режима мультипросмотра (multi-view)
* Создает сетку панелей для одновременного просмотра нескольких контейнеров
* Открывает WebSocket соединения для всех выбранных контейнеров
*/
async function setupMultiView() { async function setupMultiView() {
console.log('setupMultiView called'); console.log('setupMultiView called');
@ -1749,6 +1840,12 @@ async function setupMultiView() {
updateLogLevelsVisibility(); updateLogLevelsVisibility();
} }
/**
* Создает панель для мультипросмотра контейнера
* Генерирует HTML структуру с заголовком, кнопками уровней и областью логов
* @param {Object} service - Объект сервиса/контейнера
* @returns {HTMLElement} Созданная панель мультипросмотра
*/
function createMultiViewPanel(service) { function createMultiViewPanel(service) {
console.log(`Creating multi-view panel for service: ${service.name} (${service.id})`); console.log(`Creating multi-view panel for service: ${service.name} (${service.id})`);
const panel = document.createElement('div'); const panel = document.createElement('div');
@ -1833,6 +1930,11 @@ function createMultiViewPanel(service) {
return panel; return panel;
} }
/**
* Открывает WebSocket соединение для контейнера в режиме мультипросмотра
* Настраивает обработчики сообщений и управляет отображением логов
* @param {Object} service - Объект сервиса/контейнера
*/
function openMultiViewWs(service) { function openMultiViewWs(service) {
const containerId = service.id; const containerId = service.id;
console.log(`openMultiViewWs: Starting WebSocket setup for ${service.name} (${containerId})`); console.log(`openMultiViewWs: Starting WebSocket setup for ${service.name} (${containerId})`);
@ -2099,6 +2201,10 @@ function wsUrl(containerId, service, project){
return `${proto}://${location.host}/api/websocket/logs/${encodeURIComponent(containerId)}?tail=${tail}&token=${token}${sp}${pj}`; return `${proto}://${location.host}/api/websocket/logs/${encodeURIComponent(containerId)}?tail=${tail}&token=${token}${sp}${pj}`;
} }
/**
* Закрывает WebSocket соединение для контейнера
* @param {string} id - ID контейнера
*/
function closeWs(id){ function closeWs(id){
const o = state.open[id]; const o = state.open[id];
if (!o) return; if (!o) return;
@ -2107,6 +2213,11 @@ function closeWs(id){
delete state.open[id]; delete state.open[id];
} }
/**
* Создает и скачивает снимок логов контейнера
* В режиме мультипросмотра создает отдельные файлы для каждого контейнера
* @param {string} id - ID контейнера
*/
async function sendSnapshot(id){ async function sendSnapshot(id){
const o = state.open[id]; const o = state.open[id];
if (!o){ alert('not open'); return; } if (!o){ alert('not open'); return; }
@ -2222,6 +2333,12 @@ async function sendSnapshot(id){
} }
} }
/**
* Открывает WebSocket соединение для контейнера
* Настраивает обработчики событий и управляет отображением логов
* @param {Object} svc - Объект сервиса/контейнера
* @param {HTMLElement} panel - Панель для отображения логов
*/
function openWs(svc, panel){ function openWs(svc, panel){
const id = svc.id; const id = svc.id;
console.log(`openWs: Called for ${svc.name} (${id}) in multiViewMode: ${state.multiViewMode}`); console.log(`openWs: Called for ${svc.name} (${id}) in multiViewMode: ${state.multiViewMode}`);
@ -2979,7 +3096,12 @@ function checkMultiViewHTML() {
console.log('=== Конец проверки HTML ==='); console.log('=== Конец проверки HTML ===');
} }
// Глобальная функция для обработки логов /**
* Основная функция обработки строк логов
* Классифицирует, фильтрует и отображает строки логов в зависимости от режима
* @param {string} id - ID контейнера
* @param {string} line - Строка лога для обработки
*/
function handleLine(id, line){ function handleLine(id, line){
const obj = state.open[id]; const obj = state.open[id];
@ -3191,6 +3313,11 @@ function ensurePanel(svc){
return panel; return panel;
} }
/**
* Переключает интерфейс в режим одиночного просмотра (single view)
* Закрывает мультипросмотр, открывает WebSocket для выбранного контейнера
* @param {Object} svc - Объект сервиса/контейнера для просмотра
*/
async function switchToSingle(svc){ async function switchToSingle(svc){
console.log('switchToSingle: ENTRY POINT - function called - VERSION 2'); console.log('switchToSingle: ENTRY POINT - function called - VERSION 2');
console.log('switchToSingle: svc parameter:', svc); console.log('switchToSingle: svc parameter:', svc);

View File

@ -1,4 +1,13 @@
// Theme toggle /**
* LogBoard+ - Скрипт страницы входа
* Автор: Сергей Антропов
* Сайт: https://devops.org.ru
*/
/**
* Инициализация переключателя темы
* Загружает сохраненную тему из localStorage и настраивает переключатель
*/
(function initTheme(){ (function initTheme(){
const saved = localStorage.lb_theme || 'dark'; const saved = localStorage.lb_theme || 'dark';
document.documentElement.setAttribute('data-theme', saved); document.documentElement.setAttribute('data-theme', saved);
@ -10,7 +19,10 @@
}); });
})(); })();
// Password toggle /**
* Обработчик переключения видимости пароля
* Показывает/скрывает пароль и меняет иконку
*/
document.getElementById('passwordToggle').addEventListener('click', function() { document.getElementById('passwordToggle').addEventListener('click', function() {
const passwordInput = document.getElementById('password'); const passwordInput = document.getElementById('password');
const icon = this.querySelector('i'); const icon = this.querySelector('i');
@ -24,7 +36,10 @@ document.getElementById('passwordToggle').addEventListener('click', function() {
} }
}); });
// Login form /**
* Обработчик отправки формы входа
* Выполняет аутентификацию пользователя через API
*/
document.getElementById('loginForm').addEventListener('submit', async function(e) { document.getElementById('loginForm').addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();
@ -82,18 +97,27 @@ document.getElementById('loginForm').addEventListener('submit', async function(e
} }
}); });
/**
* Показывает сообщение об ошибке
* @param {string} message - Текст ошибки
*/
function showError(message) { function showError(message) {
const errorMessage = document.getElementById('errorMessage'); const errorMessage = document.getElementById('errorMessage');
errorMessage.textContent = message; errorMessage.textContent = message;
errorMessage.classList.add('show'); errorMessage.classList.add('show');
} }
/**
* Скрывает сообщение об ошибке
*/
function hideError() { function hideError() {
const errorMessage = document.getElementById('errorMessage'); const errorMessage = document.getElementById('errorMessage');
errorMessage.classList.remove('show'); errorMessage.classList.remove('show');
} }
// Auto-focus on username field /**
* Автофокус на поле имени пользователя при загрузке страницы
*/
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.getElementById('username').focus(); document.getElementById('username').focus();
}); });