scenario-form.js: - renderStepRow: non-create dropdown shows only 🆕 outputs by default - «📥 Из облака» button toggles cloud autotest-* instances in same select - toggleCloudInstances(idx): snapshot, flip _showCloud flag, re-render - Button label: 📥 Из облака ↔ ➖ Скрыть облако scenario-list.js: - RUNNING status: yellow background #fef3c7 on both loadScenarios() and runScenario() polling
93 lines
5.6 KiB
JavaScript
93 lines
5.6 KiB
JavaScript
// scenario-list.js — список сценариев, запуск, поллинг
|
|
|
|
let scenarioOpen=false, scenarioPollTimer=null;
|
|
|
|
async function toggleScenario(){
|
|
const body=document.getElementById('scenario-body');
|
|
scenarioOpen=!scenarioOpen;
|
|
body.style.display=scenarioOpen?'block':'none';
|
|
if(!scenarioOpen){ stopScenarioPoll(); return; }
|
|
await loadScenarios();
|
|
}
|
|
|
|
async function loadScenarios(){
|
|
const body=document.getElementById('scenario-body');
|
|
try{
|
|
const [sr, srHistory]=await Promise.all([
|
|
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;
|
|
html+=`<div style="display:flex;align-items:center;gap:4px;">`;
|
|
html+=`<button class="btn btn-sm" style="font-size:12px;flex:1;text-align:left;" onclick="runScenario(${s.id})" title="Запустить">▶ ${_esc(s.name)} <span style="color:var(--muted);">(${s.steps} шагов)</span></button>`;
|
|
if(!seed) html+=`<button class="btn btn-sm" style="font-size:13px;padding:0 6px;" onclick="editScenario(${s.id})" title="Редактировать">✏</button>`;
|
|
html+=`<button class="btn btn-sm" style="font-size:13px;padding:0 6px;" onclick="cloneScenario(${s.id},'${_esc(s.name)}')" title="Копировать">📋</button>`;
|
|
if(!seed) html+=`<button class="btn btn-sm" style="font-size:13px;padding:0 6px;color:var(--destructive);" onclick="deleteScenario(${s.id},'${_esc(s.name)}')" title="Удалить">🗑</button>`;
|
|
if(seed) html+=`<span style="font-size:9px;color:var(--muted);padding:0 4px;">seed</span>`;
|
|
html+=`</div>`;
|
|
});
|
|
html+='</div>';
|
|
}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):'?';
|
|
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>';}
|
|
}
|
|
|
|
async function runScenario(defId){
|
|
if(busy){ 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 d=await r.json();
|
|
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; }
|
|
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();
|
|
}
|
|
}catch(e){}
|
|
},3000);
|
|
}catch(e){
|
|
body.innerHTML=`<span style="color:var(--destructive);">Ошибка: ${_esc(e.message)}</span>`;
|
|
busy=false;
|
|
}
|
|
}
|
|
|
|
function stopScenarioPoll(){
|
|
if(scenarioPollTimer){ clearInterval(scenarioPollTimer); scenarioPollTimer=null; }
|
|
}
|