Подробные комментарии ко всем JS-файлам фронтенда
This commit is contained in:
+103
-11
@@ -1,7 +1,19 @@
|
||||
// scenario-list.js — список сценариев, запуск, поллинг
|
||||
//
|
||||
// Отвечает за:
|
||||
// - Загрузку списка сценариев из GET /api/scenarios
|
||||
// - Раскрытие шагов сценария (GET /api/scenario/definitions/{id})
|
||||
// - Запуск сценария (POST /api/scenario/run)
|
||||
// - Поллинг статуса запуска (GET /api/scenario/run/{run_id})
|
||||
//
|
||||
// Цветовая маркировка: RUNNING → жёлтый фон (#fef3c7)
|
||||
|
||||
let scenarioOpen=false, scenarioPollTimer=null;
|
||||
|
||||
/**
|
||||
* Переключить панель сценариев (открыть/закрыть).
|
||||
* При открытии — загружает список + последний запуск.
|
||||
*/
|
||||
async function toggleScenario(){
|
||||
const body=document.getElementById('scenario-body');
|
||||
scenarioOpen=!scenarioOpen;
|
||||
@@ -10,6 +22,13 @@ async function toggleScenario(){
|
||||
await loadScenarios();
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить список сценариев + последний запуск.
|
||||
*
|
||||
* Два параллельных запроса:
|
||||
* GET /api/scenarios — список определений [{id, name, steps}, ...]
|
||||
* GET /api/scenario/status — последние 10 запусков [{scenario_name, status, ...}, ...]
|
||||
*/
|
||||
async function loadScenarios(){
|
||||
const body=document.getElementById('scenario-body');
|
||||
try{
|
||||
@@ -17,18 +36,25 @@ async function loadScenarios(){
|
||||
fetch('/api/scenarios').then(r=>r.json()),
|
||||
fetch('/api/scenario/status').then(r=>r.json()),
|
||||
]);
|
||||
|
||||
let html='';
|
||||
|
||||
// Кнопка создания нового сценария
|
||||
html+=`<button class="btn btn-sm" style="font-size:11px;margin-bottom:4px;" onclick="createScenario()">+ Создать сценарий</button>`;
|
||||
|
||||
if(sr.length){
|
||||
// Список сценариев — каждый кликабелен
|
||||
html+='<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:4px;">';
|
||||
sr.forEach(s=>{
|
||||
const seed = s.is_seed;
|
||||
const seed = s.is_seed; // seed-сценарий (из YAML) — нельзя удалить
|
||||
html+=`<div style="border:1px solid var(--brand-gray);border-radius:4px;padding:4px 6px;">`;
|
||||
// Заголовок сценария — клик раскрывает шаги
|
||||
html+=`<div style="display:flex;align-items:center;gap:8px;cursor:pointer;padding:4px 0;" onclick="toggleScenarioSteps(${s.id})">`;
|
||||
html+=`<span style="font-size:13px;font-weight:600;flex:1;">${_esc(s.name)}</span>`;
|
||||
html+=`<span style="color:var(--muted);font-size:11px;min-width:50px;">${s.steps} шаг.</span>`;
|
||||
if(seed) html+=`<span style="font-size:9px;color:var(--muted);">seed</span>`;
|
||||
html+=`</div>`;
|
||||
// Контейнер для раскрытых шагов (изначально скрыт)
|
||||
html+=`<div id="scenario-steps-${s.id}" style="display:none;margin-top:4px;padding-left:8px;"></div>`;
|
||||
html+=`</div>`;
|
||||
});
|
||||
@@ -36,31 +62,53 @@ async function loadScenarios(){
|
||||
}else{
|
||||
html+='<span style="color:var(--muted);font-size:11px;">Нет сценариев в БД</span>';
|
||||
}
|
||||
|
||||
// Последний запуск — фильтруем по текущей версии приложения
|
||||
const curVer=window.APP&&window.APP.version||'';
|
||||
const verHistory=srHistory&&srHistory.filter(r=>r.app_version===curVer);
|
||||
if(verHistory && verHistory.length){
|
||||
const last=verHistory[0];
|
||||
const icon=last.status==='OK'?'✅':last.status==='FAIL'?'❌':last.status==='RUNNING'?'⏳':'⏱';
|
||||
const time=last.created_at?last.created_at.replace('T',' ').substring(0,19):'?';
|
||||
// RUNNING → жёлтый фон
|
||||
const runningStyle = last.status==='RUNNING' ? 'background:#fef3c7;padding:2px 4px;border-radius:3px;' : '';
|
||||
html+=`<div style="font-size:11px;margin-top:4px;color:var(--muted);${runningStyle}">Последний: ${icon} ${_esc(last.scenario_name)} — ${last.status} (шаг ${last.current_step}/${last.total_steps}) ${last.duration_sec!=null?last.duration_sec.toFixed(1)+'s':''} — ${time}</div>`;
|
||||
if(last.error_log) html+=`<div style="font-size:10px;color:var(--destructive);">${_esc(last.error_log)}</div>`;
|
||||
}
|
||||
|
||||
body.innerHTML=html;
|
||||
}catch(e){body.innerHTML='<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';}
|
||||
}catch(e){
|
||||
body.innerHTML='<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Раскрыть шаги сценария — загрузить полное определение.
|
||||
*
|
||||
* Показывает:
|
||||
* - Список шагов: операция → сервис + параметры
|
||||
* - Кнопки: ▶ Запустить, ✏ Редактировать, 📋 Копировать, 🗑 Удалить
|
||||
*
|
||||
* Закрывает другие раскрытые сценарии (только один открыт одновременно).
|
||||
*
|
||||
* @param {number} defId — ID сценария
|
||||
*/
|
||||
async function toggleScenarioSteps(defId) {
|
||||
const el = document.getElementById('scenario-steps-' + defId);
|
||||
if (!el) return;
|
||||
|
||||
// Если уже открыт — закрыть (toggle)
|
||||
if (el.style.display === 'block') { el.style.display = 'none'; return; }
|
||||
// Загрузить шаги
|
||||
|
||||
try {
|
||||
const r = await fetch('/api/scenario/definitions/' + defId);
|
||||
const def = await r.json();
|
||||
if (def.error) return;
|
||||
|
||||
const steps = def.steps || [];
|
||||
let html = '<div style="font-size:11px;padding:4px 0;">';
|
||||
|
||||
// Перечисляем шаги
|
||||
steps.forEach((s, i) => {
|
||||
const svcName = s.service_id ? 'сервис ' + s.service_id : '?';
|
||||
html += `<div style="padding:2px 0;">${i+1}. <b>${_esc(s.operation)}</b> → ${svcName}`;
|
||||
@@ -71,48 +119,89 @@ async function toggleScenarioSteps(defId) {
|
||||
if (params.length) html += ' <span style="color:var(--muted);">(' + params.map(([k,v]) => k+'='+v).join(', ') + ')</span>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
// Кнопки действий
|
||||
html += `<button class="btn btn-sm" style="margin-top:4px;font-size:11px;background:var(--brand-primary);color:#fff;" onclick="event.stopPropagation();runScenario(${defId})">▶ Запустить</button>`;
|
||||
html += `<button class="btn btn-sm" style="margin-top:4px;margin-left:4px;font-size:11px;" onclick="event.stopPropagation();editScenario(${defId})">✏ Редактировать</button>`;
|
||||
html += `<button class="btn btn-sm" style="margin-top:4px;margin-left:4px;font-size:11px;" onclick="event.stopPropagation();cloneScenario(${defId},'${_esc(def.name)}')">📋 Копировать</button>`;
|
||||
// Удалить — только для НЕ-seed сценариев
|
||||
if (!def.is_seed) html += `<button class="btn btn-sm" style="margin-top:4px;margin-left:4px;font-size:11px;color:var(--destructive);" onclick="event.stopPropagation();deleteScenario(${defId},'${_esc(def.name)}')">🗑 Удалить</button>`;
|
||||
html += '</div>';
|
||||
|
||||
el.innerHTML = html;
|
||||
el.style.display = 'block';
|
||||
// Закрыть другие
|
||||
document.querySelectorAll('[id^="scenario-steps-"]').forEach(e => { if (e !== el) e.style.display = 'none'; });
|
||||
|
||||
// Закрыть другие раскрытые сценарии
|
||||
document.querySelectorAll('[id^="scenario-steps-"]').forEach(e => {
|
||||
if (e !== el) e.style.display = 'none';
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить сценарий.
|
||||
*
|
||||
* Алгоритм:
|
||||
* 1. confirm — подтверждение
|
||||
* 2. POST /api/scenario/run → {status: "RUNNING", run_id}
|
||||
* 3. Поллинг каждые 3 секунды:
|
||||
* GET /api/scenario/run/{run_id} → {status, current_step, total_steps, ...}
|
||||
* 4. При OK/FAIL → остановить поллинг, обновить инстансы и историю
|
||||
*
|
||||
* @param {number} defId — ID определения сценария
|
||||
*/
|
||||
async function runScenario(defId){
|
||||
if(busy){ return; }
|
||||
if(busy){ return; } // другая операция уже идёт
|
||||
if(!confirm('Запустить сценарий?')) return;
|
||||
|
||||
busy=true;
|
||||
stopScenarioPoll();
|
||||
|
||||
const body=document.getElementById('scenario-body');
|
||||
body.innerHTML=`<div style="font-size:11px;">⏳ Запуск...</div>`;
|
||||
|
||||
try{
|
||||
const r=await fetch('/api/scenario/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({definition_id:defId})});
|
||||
const r=await fetch('/api/scenario/run',{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({definition_id:defId})
|
||||
});
|
||||
const d=await r.json();
|
||||
if(d.error){ body.innerHTML=`<span style="color:var(--destructive);">Ошибка: ${_esc(d.error)}</span>`; busy=false; return; }
|
||||
|
||||
if(d.error){
|
||||
body.innerHTML=`<span style="color:var(--destructive);">Ошибка: ${_esc(d.error)}</span>`;
|
||||
busy=false;
|
||||
return;
|
||||
}
|
||||
|
||||
const runId=d.run_id;
|
||||
if(!runId){ body.innerHTML=`<span style="color:var(--destructive);">Ошибка: нет run_id в ответе</span>`; busy=false; return; }
|
||||
if(!runId){
|
||||
body.innerHTML=`<span style="color:var(--destructive);">Ошибка: нет run_id в ответе</span>`;
|
||||
busy=false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Поллинг статуса
|
||||
scenarioPollTimer=setInterval(async()=>{
|
||||
try{
|
||||
const sr=await fetch('/api/scenario/run/'+runId);
|
||||
if(!sr.ok){ return; }
|
||||
const last=await sr.json();
|
||||
if(!last||last.error) return;
|
||||
|
||||
const icon=last.status==='OK'?'✅':last.status==='FAIL'?'❌':last.status==='RUNNING'?'⏳':'⏱';
|
||||
const time=last.created_at?last.created_at.replace('T',' ').substring(0,19):'?';
|
||||
const runningBg = last.status==='RUNNING' ? 'background:#fef3c7;padding:2px 4px;border-radius:3px;' : '';
|
||||
|
||||
let html=`<div style="font-size:11px;${runningBg}">${icon} ${_esc(last.scenario_name)} — ${last.status} (шаг ${last.current_step}/${last.total_steps}) ${last.duration_sec!=null?last.duration_sec.toFixed(1)+'s':''} — ${time}</div>`;
|
||||
if(last.error_log) html+=`<div style="font-size:10px;color:var(--destructive);">${_esc(last.error_log)}</div>`;
|
||||
body.innerHTML=html;
|
||||
|
||||
if(last.status!=='RUNNING'){
|
||||
stopScenarioPoll();
|
||||
busy=false;
|
||||
await refreshInstances();
|
||||
if(historyOpen) await loadHistory();
|
||||
await refreshInstances(); // обновить список инстансов
|
||||
if(historyOpen) await loadHistory(); // обновить историю
|
||||
}
|
||||
}catch(e){}
|
||||
},3000);
|
||||
@@ -122,6 +211,9 @@ async function runScenario(defId){
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановить поллинг сценария.
|
||||
*/
|
||||
function stopScenarioPoll(){
|
||||
if(scenarioPollTimer){ clearInterval(scenarioPollTimer); scenarioPollTimer=null; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user