Подробные комментарии ко всем JS-файлам фронтенда

This commit is contained in:
2026-07-31 17:25:44 +04:00
parent 0783319fd1
commit c50aaf59d8
17 changed files with 1155 additions and 196 deletions
+58 -7
View File
@@ -1,42 +1,86 @@
// utils.js — общие хелперы: _esc, busy lock, validateJson, лог-панель
// utils.js — общие хелперы фронтенда
// Используется ВСЕМИ модулями (должен грузиться ПЕРВЫМ).
// Экспортирует (в глобальную область): _esc, validateJson, relativeTime,
// startLogPoll, toggleLog
/**
* HTML-экранирование — защита от XSS.
* Все данные из API/БД пропускаются через _esc() перед вставкой в innerHTML.
*
* @param {*} s — что угодно (приводится к строке через String())
* @returns {string} безопасная для HTML строка
*
* Заменяет:
* & → & (первым! иначе сломает уже заменённые сущности)
* " → " (атрибуты в кавычках)
* < → &lt; (XSS-вектор: <script>)
*
* > и ' НЕ экранируем — в нашем контексте они безопасны,
* а лишние замены портят читаемость.
*/
function _esc(s){
/* HTML-экранирование: " → &quot; & → &amp; < → &lt; */
return String(s||'').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;');
}
/**
* Проверить что значение в поле — валидный JSON.
* Используется для map-параметров перед отправкой формы.
*
* @param {HTMLElement} el — input/textarea элемент
* @param {boolean} quiet — true = не менять внешний вид (batch-проверка)
* @returns {boolean} true если JSON валидный или поле пустое
*/
function validateJson(el,quiet){
/* Проверить что значение в поле — валидный JSON.
quiet=true — не менять внешний вид (для batch-проверки перед отправкой). */
// Ищем соседний span.json-err для показа ошибки
const errEl=el.parentElement.querySelector('.json-err');
const v=el.value.trim();
// Пустое поле — ок (не ошибка)
if(!v) return true;
try{ JSON.parse(v); }
catch(e){
// Показываем ошибку в span.json-err (если не quiet)
if(!quiet&&errEl){ errEl.style.display='inline'; errEl.textContent='Ошибка JSON: '+e.message; }
el.style.borderColor='var(--destructive)';
return false;
}
// Всё ок — убираем ошибку
if(!quiet&&errEl) errEl.style.display='none';
el.style.borderColor='';
return true;
}
// === Панель логов ===
// ═══════════════════════════════════════════════════════
// Панель логов — поллинг /api/log каждые 2 секунды
// ═══════════════════════════════════════════════════════
let logPollTimer=null;
/**
* Запустить поллинг логов.
* Работает только если элемент #log-panel существует и видим.
* Безопасен для повторного вызова — не дублирует таймер.
*/
function startLogPoll(){
if(logPollTimer) return;
if(logPollTimer) return; // уже запущен
logPollTimer=setInterval(async()=>{
const el=document.getElementById('log-panel');
// Если панель скрыта — не грузим (экономия трафика)
if(!el||el.style.display==='none') return;
try{
const r=await fetch('/api/log');
const lines=await r.json();
// Каждая строка логирования в отдельном div, экранирована
el.innerHTML=lines.map(l=>`<div>${_esc(l)}</div>`).join('');
// Автопрокрутка вниз (следим за новыми записями)
el.scrollTop=el.scrollHeight;
}catch(e){}
},2000);
}
/**
* Переключить видимость лог-панели.
* При открытии — запускает поллинг.
*/
function toggleLog(){
const el=document.getElementById('log-panel');
if(!el) return;
@@ -44,12 +88,19 @@ function toggleLog(){
else el.style.display='none';
}
/**
* Относительное время — "5м", "2ч 30м", "1д 3ч".
* Используется в списке инстансов (instances.js) и истории (history.js).
*
* @param {string} iso — ISO-8601 дата (напр. "2026-07-31T12:00:00")
* @returns {string} относительное время или "-" если невалидно
*/
function relativeTime(iso) {
if (!iso) return '-';
const d = new Date(iso);
if (isNaN(d.getTime())) return '-';
const sec = Math.floor((Date.now() - d.getTime()) / 1000);
if (sec < 0) return 'только что';
if (sec < 0) return 'только что'; // будущее (расхождение часов)
if (sec < 60) return sec + 'с';
const min = Math.floor(sec / 60);
if (min < 60) return min + 'м';