v1.2.14: expandable history rows with execution stages
Backend: /api/history now returns stages (JSONB) Frontend: click on history row → expand to show execution stages: ✅ stage_name start → finish (duration) ❌ stage_name start → finish (duration) ⏳ stage_name start → ... (—) RUNNING rows: yellow background, FAIL rows: red background
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@ from routes.api_scenario_run import bp_run as api_scenario_run_bp
|
|||||||
from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp
|
from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp
|
||||||
|
|
||||||
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
|
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
|
||||||
VERSION = "1.2.13"
|
VERSION = "1.2.14"
|
||||||
|
|
||||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||||
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc")
|
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc")
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ def api_history():
|
|||||||
SELECT id, created_at, client_id, stand, user_email,
|
SELECT id, created_at, client_id, stand, user_email,
|
||||||
svc_id, svc_name, op_name, svc_op_id, op_uid,
|
svc_id, svc_name, op_name, svc_op_id, op_uid,
|
||||||
instance_uid, display_name,
|
instance_uid, display_name,
|
||||||
status, duration_sec, error_log, app_version
|
status, duration_sec, error_log, app_version, stages
|
||||||
FROM runs
|
FROM runs
|
||||||
WHERE client_id = %s AND stand = %s
|
WHERE client_id = %s AND stand = %s
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
|
|||||||
@@ -18,18 +18,43 @@ async function loadHistory(){
|
|||||||
if(!rows||!rows.length){body.innerHTML='<span style="color:var(--muted);font-size:11px;">Нет записей</span>'; return;}
|
if(!rows||!rows.length){body.innerHTML='<span style="color:var(--muted);font-size:11px;">Нет записей</span>'; return;}
|
||||||
let html='<table style="width:100%;font-size:11px;">';
|
let html='<table style="width:100%;font-size:11px;">';
|
||||||
html+='<tr><th style="padding:2px 6px;">Время</th><th style="padding:2px 6px;">Операция</th><th style="padding:2px 6px;">Инстанс</th><th style="padding:2px 6px;">Статус</th><th style="padding:2px 6px;">Длит.</th></tr>';
|
html+='<tr><th style="padding:2px 6px;">Время</th><th style="padding:2px 6px;">Операция</th><th style="padding:2px 6px;">Инстанс</th><th style="padding:2px 6px;">Статус</th><th style="padding:2px 6px;">Длит.</th></tr>';
|
||||||
rows.forEach(r=>{
|
rows.forEach((r, i)=>{
|
||||||
const time=r.created_at?r.created_at.replace('T',' ').substring(0,19):'?';
|
const time=r.created_at?r.created_at.replace('T',' ').substring(0,19):'?';
|
||||||
const cls=r.status==='OK'?'badge-success':'';
|
const cls=r.status==='OK'?'badge-success':r.status==='FAIL'?'badge' :'';
|
||||||
html+=`<tr>
|
const stCls=r.status==='RUNNING'?'background:#fef3c7;':r.status==='FAIL'?'background:#fef2f2;':'';
|
||||||
|
html+=`<tr style="cursor:pointer;${stCls}" onclick="toggleHistoryRow(${i})">
|
||||||
<td style="padding:2px 6px;">${time}</td>
|
<td style="padding:2px 6px;">${time}</td>
|
||||||
<td style="padding:2px 6px;">${_esc(r.op_name||'?')}</td>
|
<td style="padding:2px 6px;">${_esc(r.op_name||'?')}</td>
|
||||||
<td style="padding:2px 6px;">${_esc(r.display_name||r.instance_uid||'?')}</td>
|
<td style="padding:2px 6px;">${_esc(r.display_name||r.instance_uid||'?')}</td>
|
||||||
<td style="padding:2px 6px;"><span class="badge ${cls}" style="font-size:9px;">${_esc(r.status||'?')}</span></td>
|
<td style="padding:2px 6px;"><span class="badge ${cls}" style="font-size:9px;">${_esc(r.status||'?')}</span></td>
|
||||||
<td style="padding:2px 6px;">${r.duration_sec!=null?r.duration_sec.toFixed(1)+'s':'-'}</td>
|
<td style="padding:2px 6px;">${r.duration_sec!=null?r.duration_sec.toFixed(1)+'s':'-'}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
html+=`<tr id="hist-stages-${i}" style="display:none;"><td colspan="5" style="padding:4px 8px;">${renderStages(r.stages)}</td></tr>`;
|
||||||
});
|
});
|
||||||
html+='</table>';
|
html+='</table>';
|
||||||
body.innerHTML=html;
|
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>';}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleHistoryRow(i) {
|
||||||
|
const el = document.getElementById('hist-stages-' + i);
|
||||||
|
if (!el) return;
|
||||||
|
el.style.display = el.style.display === 'none' ? 'table-row' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStages(stages) {
|
||||||
|
if (!stages || !stages.length) return '<span style="color:var(--muted);font-size:10px;">Нет данных об этапах</span>';
|
||||||
|
let html = '<div style="font-size:11px;"><b>Этапы выполнения</b></div>';
|
||||||
|
stages.forEach(s => {
|
||||||
|
const done = !!s.dtFinish;
|
||||||
|
const icon = done ? (s.isSuccessful ? '✅' : '❌') : '⏳';
|
||||||
|
const start = s.dtStart ? s.dtStart.replace('T',' ').substring(0,19) : '?';
|
||||||
|
const finish = s.dtFinish ? s.dtFinish.replace('T',' ').substring(0,19) : '...';
|
||||||
|
const dur = s.duration != null ? s.duration.toFixed(1) + ' сек' : '—';
|
||||||
|
html += `<div style="margin:4px 0;padding:4px 6px;background:var(--brand-grey-light);border-radius:3px;">`;
|
||||||
|
html += `${icon} <b>${_esc(s.stage||'?')}</b>`;
|
||||||
|
html += `<span style="color:var(--muted);font-size:10px;margin-left:8px;">${start} → ${finish} (${dur})</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user