From 4d1b89de8f37fe48af92d7dd7cf22afb99a8bc18 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 27 May 2026 15:42:17 +0300 Subject: [PATCH] Custom hover tooltips for truncated dashboard cells --- .../haiku/rag/ingester/api/static/index.html | 89 ++++++++++++++++--- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/haiku_rag_slim/haiku/rag/ingester/api/static/index.html b/haiku_rag_slim/haiku/rag/ingester/api/static/index.html index 8b598bf2..7f740f5b 100644 --- a/haiku_rag_slim/haiku/rag/ingester/api/static/index.html +++ b/haiku_rag_slim/haiku/rag/ingester/api/static/index.html @@ -136,9 +136,35 @@ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } .breakdown span { margin-right: 12px; } + + /* Floating tooltip for truncated cells. Triggered via data-tip + on any element; positioned in JS so it escapes the table cell's + overflow:hidden context. */ + #tooltip { + position: fixed; + z-index: 1000; + max-width: 480px; + padding: 6px 10px; + background: #0a0d11; + color: var(--text); + border: 1px solid var(--border); + border-radius: 4px; + font-size: 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + line-height: 1.4; + word-break: break-all; + white-space: pre-wrap; + pointer-events: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + opacity: 0; + transition: opacity 0.08s ease-out; + } + #tooltip.visible { opacity: 1; } + [data-tip] { cursor: help; } +
@@ -377,7 +403,7 @@ ? SKIP_REASON_LABELS[reason] || reason : null; const skipBadge = reason - ? ` ${escapeHtml(label)}` + ? ` ${escapeHtml(label)}` : ""; const polledCell = s.last_polled_at ? relTime(s.last_polled_at) + skipBadge @@ -437,9 +463,9 @@ const detailClass = p.reachable ? "ok" : "err"; return ` docling-serve - ${escapeHtml(p.base_url)} + ${escapeHtml(p.base_url)} ${badge} - ${detail} + ${detail} `; }) .join(""); @@ -459,17 +485,17 @@ } const rows = jobs .map((j) => { - const tryTitle = j.last_error - ? ` title="Previous attempt failed: ${escapeHtml(j.last_error)}"` + const tryTip = j.last_error + ? ` data-tip="Previous attempt failed: ${escapeHtml(j.last_error)}"` : ""; const tryCell = j.attempts > 1 - ? `${j.attempts}/${j.max_attempts}` + ? `${j.attempts}/${j.max_attempts}` : `${j.attempts}/${j.max_attempts}`; return ` - ${shortId(j.id)} + ${shortId(j.id)} ${opBadge(j.op)} - ${escapeHtml(j.uri)} + ${escapeHtml(j.uri)} ${escapeHtml(j.claimed_by ?? "—")} ${tryCell} ${relTime(j.claimed_at)} @@ -497,9 +523,9 @@ const rows = jobs .map( (j) => ` - ${shortId(j.id)} - ${escapeHtml(j.uri)} - ${escapeHtml(j.last_error ?? "")} + ${shortId(j.id)} + ${escapeHtml(j.uri)} + ${escapeHtml(j.last_error ?? "")} ${relTime(j.completed_at)} @@ -530,7 +556,7 @@ } return ` ${opBadge(j.op)} - ${escapeHtml(j.uri)} + ${escapeHtml(j.uri)} ${escapeHtml(j.source_id)} ${duration} ${relTime(j.completed_at)} @@ -561,7 +587,7 @@ let detail = `${health.worker_count} workers · ${health.poller_count} pollers`; if (health.worker_breaker_open) { const fails = health.worker_breaker_consecutive_failures; - detail += ` · WORKER BREAKER OPEN`; + detail += ` · WORKER BREAKER OPEN`; } $("health-detail").innerHTML = detail; renderChips(health.queue_counts); @@ -605,6 +631,43 @@ refresh(); }; + // Floating tooltip: shows the full content of any [data-tip] element + // immediately on hover. Uses event delegation so it survives the + // table-body innerHTML reset each refresh(). + (() => { + const tip = $("tooltip"); + let current = null; + const place = (e) => { + const pad = 12; + const w = tip.offsetWidth; + const h = tip.offsetHeight; + let x = e.clientX + pad; + let y = e.clientY + pad; + if (x + w > window.innerWidth - 8) x = e.clientX - w - pad; + if (y + h > window.innerHeight - 8) y = e.clientY - h - pad; + tip.style.left = `${Math.max(8, x)}px`; + tip.style.top = `${Math.max(8, y)}px`; + }; + document.addEventListener("mouseover", (e) => { + const el = e.target.closest("[data-tip]"); + if (!el || el === current) return; + current = el; + tip.textContent = el.getAttribute("data-tip") || ""; + tip.classList.add("visible"); + place(e); + }); + document.addEventListener("mousemove", (e) => { + if (current) place(e); + }); + document.addEventListener("mouseout", (e) => { + if (!current) return; + const next = e.relatedTarget && e.relatedTarget.closest?.("[data-tip]"); + if (next === current) return; + tip.classList.remove("visible"); + current = null; + }); + })(); + setInterval(updateLastRefresh, 1000); refresh(); setInterval(refresh, POLL_MS);