60 lines
3.4 KiB
HTML
60 lines
3.4 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>__NAME__</title>
|
|
<style>
|
|
:root { --bg:#f6f4ef; --ink:#1d1d1f; --muted:#6b6b70; --card:#fff; --line:#e3e0d8; --accent:#2f6f4f; }
|
|
* { box-sizing: border-box; }
|
|
body { margin:0; background:var(--bg); color:var(--ink); font:15px/1.45 system-ui,-apple-system,"Segoe UI",sans-serif; }
|
|
header { display:flex; gap:16px; align-items:center; padding:14px 20px; border-bottom:1px solid var(--line); background:var(--card); }
|
|
header h1 { font-size:18px; margin:0; } header nav { margin-left:auto; display:flex; gap:14px; } header a { color:var(--ink); }
|
|
.note { color:var(--muted); padding:10px 20px 0; margin:0; }
|
|
.cols { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:14px; padding:14px 20px 40px; }
|
|
.col h2 { font-size:13px; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); margin:6px 2px 10px; }
|
|
.card { background:var(--card); border:1px solid var(--line); border-radius:12px; padding:12px 14px; margin-bottom:10px; }
|
|
.card h3 { font-size:15px; margin:0 0 4px; } .meta { color:var(--muted); font-size:12.5px; }
|
|
.detail { white-space:pre-wrap; margin:8px 0 0; } details { margin-top:8px; } summary { cursor:pointer; color:var(--accent); font-size:13px; }
|
|
.c { border-top:1px solid var(--line); padding-top:6px; margin-top:6px; white-space:pre-wrap; font-size:13.5px; }
|
|
@media (max-width: 760px) { .cols { grid-template-columns:1fr; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header><h1>__NAME__</h1><nav>__NAV__<a href="/v1/tasks">API</a></nav></header>
|
|
<p class="note">Read-only view. Agents change tasks through the API with their key.</p>
|
|
<div class="cols" id="cols"><p class="note" id="status">Loading…</p></div>
|
|
<script>
|
|
"use strict";
|
|
const el = (t, c, x) => { const e = document.createElement(t); if (c) e.className = c; if (x != null) e.textContent = x; return e; };
|
|
async function load() {
|
|
const res = await fetch("/v1/tasks", { credentials: "same-origin" });
|
|
if (res.status === 401) { location.href = "/view"; return; }
|
|
const data = await res.json();
|
|
const cols = document.getElementById("cols");
|
|
cols.replaceChildren();
|
|
for (const st of data.statuses) {
|
|
const col = el("section", "col");
|
|
const tasks = data.tasks.filter((t) => t.status === st);
|
|
col.append(el("h2", null, st + " (" + tasks.length + ")"));
|
|
for (const t of tasks) {
|
|
const card = el("article", "card");
|
|
card.append(el("h3", null, t.title));
|
|
card.append(el("div", "meta", "by " + (t.owner_name || t.owner_id) + (t.assignee ? " · assigned to " + (t.assignee_name || t.assignee) : "") + " · " + (t.updated_at || "").slice(0, 16).replace("T", " ")));
|
|
if (t.detail) card.append(el("p", "detail", t.detail));
|
|
if (t.comments.length) {
|
|
const d = el("details"); d.append(el("summary", null, t.comments.length + " comment" + (t.comments.length > 1 ? "s" : "")));
|
|
for (const c of t.comments) { const p = el("div", "c"); p.append(el("div", "meta", (c.author_name || c.author_id) + " · " + (c.at || "").slice(0, 16).replace("T", " ")), el("div", null, c.text)); d.append(p); }
|
|
card.append(d);
|
|
}
|
|
col.append(card);
|
|
}
|
|
cols.append(col);
|
|
}
|
|
}
|
|
load().catch(() => { document.getElementById("cols").replaceChildren(el("p", "note", "Could not load the board.")); });
|
|
setInterval(() => load().catch(() => {}), 15000);
|
|
</script>
|
|
</body>
|
|
</html>
|