From 62e9d756d76660c67efafcd31a71ff1607f765f9 Mon Sep 17 00:00:00 2001 From: Simbiot Harness Date: Wed, 30 Sep 2026 06:42:08 +0000 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20=D0=B4=D0=B5=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D0=BA=D0=BB=D0=B8=D0=BA=D1=83=20=E2=80=94=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=B0=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5,=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BC=D0=BE=D1=83=D1=88=D0=B5=D0=BD=D1=8B,?= =?UTF-8?q?=20=D0=BE=D1=82=D1=87=D1=91=D1=82=20=D0=B7=D0=B0=D0=BA=D1=80?= =?UTF-8?q?=D1=8B=D1=82=D0=B8=D1=8F,=20JS-=D1=80=D0=B0=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D1=8B=D1=82=D0=B8=D0=B5=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B8?= =?UTF-8?q?=20(t-260929220004-033c0f)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- harness/dashboard.html | 45 +++++++++++++++++++++++++++++++++++++----- harness/dashboard.py | 14 +++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) 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