Подробные комментарии ко всем JS-файлам фронтенда
This commit is contained in:
+58
-7
@@ -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 строка
|
||||
*
|
||||
* Заменяет:
|
||||
* & → & (первым! иначе сломает уже заменённые сущности)
|
||||
* " → " (атрибуты в кавычках)
|
||||
* < → < (XSS-вектор: <script>)
|
||||
*
|
||||
* > и ' НЕ экранируем — в нашем контексте они безопасны,
|
||||
* а лишние замены портят читаемость.
|
||||
*/
|
||||
function _esc(s){
|
||||
/* HTML-экранирование: " → " & → & < → < */
|
||||
return String(s||'').replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<');
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить что значение в поле — валидный 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 + 'м';
|
||||
|
||||
Reference in New Issue
Block a user