diff --git a/harness/dashboard.html b/harness/dashboard.html index 5efa290..6c8f905 100644 --- a/harness/dashboard.html +++ b/harness/dashboard.html @@ -39,6 +39,15 @@ th{background:var(--ink);color:var(--paper);text-align:left;padding:8px 9px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;letter-spacing:.09em;text-transform:uppercase} td{border-bottom:1px solid var(--line);padding:8px 9px;vertical-align:top} tr:hover td{background:#fff3ea} + tr.task{cursor:pointer} + tr.task.open td{background:#fff3ea} + tr.taskdetail td{background:var(--card);border-bottom:1px solid var(--ink)} + .taskdetail .box{display:grid;grid-template-columns:150px 1fr;gap:4px 14px;padding:8px 2px 12px;font-size:13px} + .taskdetail .k{font-family:ui-monospace,Menlo,monospace;font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--dim);padding-top:3px} + .taskdetail pre{white-space:pre-wrap;word-break:break-word;margin:0;font-family:ui-monospace,Menlo,monospace;font-size:12px;line-height:1.5} + .hint{color:var(--dim);font-size:12px;font-family:ui-monospace,Menlo,monospace} + .arrow{display:inline-block;transition:transform .15s;color:var(--dim);font-size:10px} + tr.task.open .arrow{transform:rotate(90deg)} .m{font-family:ui-monospace,Menlo,monospace;font-size:11.5px} .dim{color:var(--dim)} @@ -83,7 +92,7 @@
загружаю…

Задачи

-
сортировка: ready → running → failed → done, внутри — новые сверху
+
сортировка: ready → running → failed → done, внутри — новые сверху · клик по строке — детали задачи
загружаю…

Открытые эскалации

@@ -120,6 +129,30 @@ $("refresh").textContent = REFRESH; function pill(status){ return `${esc(status)}`; } function size(s){ return `${esc(s)}`; } +// детали задачи по клику (т-260929220004): раскрытие строки без перезагрузки +const openTasks = new Set(); // id раскрытых строк (переживает автообновление) +function toggleTask(id){ + if(openTasks.has(id)) openTasks.delete(id); else openTasks.add(id); + const el = document.getElementById("tr-" + id); + if(el) el.classList.toggle("open", openTasks.has(id)); + const det = document.getElementById("det-" + id); + if(det) det.style.display = openTasks.has(id) ? "" : "none"; +} +function taskDetail(t){ + const row = [ + ["id", t.id], + ["размер / статус", `${t.size} · ${t.status}${t.level ? " · уровень " + t.level : ""}`], + ["создана", fmtTime(t.created)], + ["завершена", t.finished ? fmtTime(t.finished) : "—"], + ["session", t.session_id || "—"], + t.promoted_to ? ["промоушен", `${t.promotions || 1}× → ${t.promoted_to}`] : null, + t.error ? ["ошибка", t.error] : null, + t.report ? ["отчёт (" + (t.report_level || "?") + ")", t.report] : null, + ].filter(Boolean); + const box = row.map(([k, v]) => `
${esc(k)}
${esc(v)}
`).join(""); + return `
${box}
`; +} + function setConn(cls, text){ $("dot").className = "dot " + cls; $("conn").textContent = text; } async function tick(){ @@ -168,18 +201,20 @@ function render(s){ `).join("")}` : `
нет данных
`; - // задачи + // задачи (клик по строке — детали, т-260929220004) $("taskcount").textContent = `(${s.tasks.length})`; $("tasks").innerHTML = s.tasks.length ? ` - - ${s.tasks.map(t => ` + + ${s.tasks.map(t => ` + - `).join("")}
idзадачаразмерстатусуровеньсоздана
idзадачаразмерстатусуровеньсоздана
▶ ${esc(t.id)} ${esc(t.title)}${t.error ? `
${esc(String(t.error).slice(0,140))}` : ""}
${size(t.size)} ${pill(t.status)} ${esc(t.level || "—")} ${fmtTime(t.created)}
` + + ${taskDetail(t)}`).join("")}` : `
задач нет
`; // эскалации diff --git a/harness/dashboard.py b/harness/dashboard.py index e338ae4..2e2fe95 100644 --- a/harness/dashboard.py +++ b/harness/dashboard.py @@ -51,13 +51,27 @@ def build_state() -> dict: """ model = es.run_projections() + # отчёт тика, закрывшего задачу: digest с summary_ref == result_ref + digest_by_ref = {} + for d in (model.get("digests") or {}).values(): + if d.get("summary_ref"): + digest_by_ref[d["summary_ref"]] = d + tasks = [] for t in model.get("tasks", {}).values(): + report = digest_by_ref.get(t.get("result_ref")) or {} tasks.append({ "id": t.get("id"), "title": t.get("title", ""), "size": t.get("size", "?"), "status": t.get("status", "?"), "level": t.get("level"), "created": t.get("created"), "finished": t.get("finished"), "error": t.get("error"), + # детали для раскрытия по клику (т-260929220004) + "details": (t.get("details") or "")[:2000], + "promotions": t.get("promotions"), "promoted_to": t.get("promoted_to"), + "session_id": t.get("session_id"), + "report_level": report.get("level"), + "report_ts": report.get("ts"), + "report": (report.get("text") or "")[:1500], }) # два стабильных прохода: новые сверху внутри статуса, # статусы по порядку ready→running→failed→done